From 3c9dcaf742cfa3e3c2b0843c8752cf7713aaf1fc Mon Sep 17 00:00:00 2001
From: lefengyang <lefengyang@tencent.com>
Date: Wed, 10 Sep 2025 19:38:41 +0800
Subject: [PATCH] init
---
vendor/pimple/pimple/ext/pimple/tests/014.phpt | 30
vendor/symfony/var-dumper/Caster/ConstStub.php | 33
vendor/topthink/framework/src/think/route/Resource.php | 251
vendor/symfony/translation/Tests/fixtures/resources-2.0-multi-segment-unit.xlf | 17
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/UpdateDenyConfigRequest.php | 170
vendor/symfony/translation/Tests/TranslatorCacheTest.php | 318
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/UpdatePullToPushTaskResponse.php | 75
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/no/functions | 416
vendor/symfony/translation/Tests/Formatter/MessageFormatterTest.php | 83
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnData.php | 27
vendor/topthink/framework/tests/ContainerTest.php | 314
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions | 408
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php | 229
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/GPBMetadata/VodSpace.php | 0
vendor/songshenzong/support/LICENSE.txt | 19
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/RelaySourceConfigList.php | 65
vendor/psr/http-client/LICENSE | 19
vendor/topthink/framework/src/think/cache/TagSet.php | 132
vendor/topthink/think-migration/src/Migrator.php | 27
vendor/volcengine/volc-sdk-php/examples/Vod/DemoCreateCdnPreloadTask.php | 28
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php | 138
vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.php | 12
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/ListDomainDetailRequest.php | 309
vendor/volcengine/volc-sdk-php/examples/Vod/DemoStartWorkflow.php | 31
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Client.php | 250
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config | 23
vendor/qcloud/cos-sdk-v5/sample/deleteBucketCors.php | 25
vendor/qiniu/php-sdk/examples/put_bucketAccessStyleMode.php | 21
vendor/topthink/framework/src/think/console/command/make/stubs/event.stub | 8
vendor/topthink/think-orm/src/db/builder/Sqlsrv.php | 184
vendor/xaboy/form-builder/src/UI/Elm/Traits/CascaderFactoryTrait.php | 80
vendor/volcengine/volc-sdk-php/examples/Vod/DemoUpdateMediaPublishStatus.php | 28
vendor/symfony/translation/LICENSE | 19
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodListCdnAccessLogResponse.php | 119
vendor/symfony/translation/Tests/fixtures/non-valid.yml | 1
vendor/topthink/framework/tests/AppTest.php | 215
vendor/volcengine/volc-sdk-php/examples/Live/DemoDescribeCDNSnapshotHistory.php | 34
vendor/volcengine/volc-sdk-php/examples/Vod/DemoQueryUploadTaskInfo.php | 27
vendor/volcengine/volc-sdk-php/examples/Vod/DemoDrm.php | 50
workerman.bat | 11
vendor/symfony/translation/MessageCatalogue.php | 301
vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php | 168
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php | 167
vendor/symfony/var-dumper/Cloner/ClonerInterface.php | 27
vendor/topthink/think-migration/src/helper.php | 40
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php | 200
vendor/topthink/think-orm/src/db/builder/Sqlite.php | 97
vendor/qcloud/cos-sdk-v5/sample/listObjects.php | 30
vendor/symfony/event-dispatcher/CHANGELOG.md | 23
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php | 236
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/DenyConfigDetail.php | 337
vendor/topthink/think-migration/src/command/migrate/Breakpoint.php | 92
vendor/topthink/think-queue/LICENSE | 201
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/GetDirectEditResult.php | 269
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnUpperIp.php | 19
vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php | 186
vendor/symfony/translation/Loader/JsonFileLoader.php | 64
vendor/topthink/framework/src/think/console/command/make/stubs/service.stub | 27
vendor/topthink/think-multi-app/src/MultiApp.php | 245
vendor/symfony/translation/Tests/Catalogue/AbstractOperationTest.php | 74
vendor/symfony/http-foundation/HeaderBag.php | 331
vendor/topthink/think-captcha/src/Captcha.php | 339
vendor/symfony/process/CHANGELOG.md | 90
vendor/volcengine/volc-sdk-php/examples/Live/DemoBase.php | 12
vendor/volcengine/volc-sdk-php/examples/Live/DemoCreatePullToPushTask.php | 35
vendor/topthink/framework/src/think/console/output/question/Choice.php | 163
vendor/xaboy/form-builder/src/components/FormStyle.php | 78
vendor/volcengine/volc-sdk-php/examples/Imp/job/DemoRetrieveJob.php | 32
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodDomainInstanceInfo.php | 167
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetAudioInfoForAudit.php | 28
vendor/psr/simple-cache/.editorconfig | 12
vendor/topthink/think-orm/src/model/concern/ModelEvent.php | 88
vendor/topthink/framework/src/think/console/command/optimize/Route.php | 66
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/ForbidStreamRequest.php | 217
vendor/topthink/framework/src/think/exception/InvalidArgumentException.php | 22
vendor/workerman/workerman/composer.json | 38
vendor/xaboy/form-builder/src/UI/Iview/Components/Option.php | 56
vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php | 154
vendor/topthink/framework/src/think/facade/Event.php | 42
vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php | 94
vendor/topthink/framework/src/think/facade/Validate.php | 95
vendor/topthink/framework/src/think/Facade.php | 98
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCommitData.php | 75
vendor/symfony/polyfill-php70/Resources/stubs/TypeError.php | 5
vendor/xaboy/form-builder/composer.json | 36
vendor/psr/cache/src/InvalidArgumentException.php | 13
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/WriterPart.php | 33
vendor/qiniu/php-sdk/src/Qiniu/Storage/ArgusManager.php | 73
vendor/topthink/think-queue/src/queue/command/Work.php | 151
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/RelaySourceGroupItemV2.php | 133
vendor/symfony/event-dispatcher/.gitignore | 3
vendor/psr/http-client/README.md | 12
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeEdgeTopNrtData.php | 25
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodUpdateSubtitleInfoResponse.php | 75
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetAuditFramesForAuditResponse.php | 119
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/NoSuchUploadException.php | 8
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetPlayInfoWithLiveTimeShiftSceneResult.php | 65
vendor/symfony/translation/IdentityTranslator.php | 61
vendor/topthink/framework/src/think/console/command/make/Validate.php | 39
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetDirectEditProgressResponse.php | 119
vendor/symfony/polyfill-intl-normalizer/composer.json | 39
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php | 106
vendor/topthink/think-queue/src/queue/connector/Redis.php | 298
vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php | 72
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php | 397
vendor/xaboy/form-builder/src/Annotation/ClassName.php | 39
vendor/qiniu/php-sdk/examples/put_referAntiLeech.php | 23
vendor/qcloud/cos-sdk-v5/sample/upload.php | 49
vendor/topthink/framework/src/think/Middleware.php | 257
vendor/topthink/framework/src/think/facade/Lang.php | 41
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCallbackAuthType.php | 52
vendor/xaboy/form-builder/src/UI/Elm/Traits/DatePickerFactoryTrait.php | 141
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodAdaptiveInfo.php | 133
vendor/topthink/think-orm/src/db/builder/Pgsql.php | 118
vendor/topthink/think-orm/src/db/BaseQuery.php | 1282
vendor/topthink/framework/composer.json | 54
vendor/symfony/http-foundation/composer.json | 38
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php | 34
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/Matrix.php | 1202
vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php | 49
vendor/songshenzong/support/CHANGELOG.md | 24
vendor/topthink/think-migration/phinx/src/Phinx/Migration/CreationInterface.php | 94
vendor/symfony/var-dumper/Tests/Caster/MemcachedCasterTest.php | 93
vendor/volcengine/volc-sdk-php/examples/Vod/DemoListSnapshots.php | 27
vendor/symfony/http-foundation/Tests/RequestMatcherTest.php | 151
vendor/topthink/think-filesystem/tests/bootstrap.php | 2
vendor/pimple/pimple/ext/pimple/pimple_compat.h | 81
vendor/topthink/think-queue/src/common.php | 31
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodDomainoInfo.php | 347
vendor/topthink/framework/src/think/validate/ValidateRule.php | 172
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodCreateCdnRefreshTaskRequest.php | 133
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodUploadMaterialRequest.php | 235
vendor/xaboy/form-builder/src/Annotation/Validate/Required.php | 38
vendor/topthink/framework/src/think/console/command/Lists.php | 74
vendor/topthink/framework/src/think/console/command/Make.php | 99
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config | 23
vendor/topthink/framework/src/think/console/LICENSE | 19
vendor/topthink/think-captcha/assets/ttfs/4.ttf | 0
vendor/volcengine/volc-sdk-php/examples/SecretNumber/DemoUnbindAXN.php | 17
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodListCdnUsageDataRequest.php | 235
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions | 416
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php | 483
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodPoint.php | 99
vendor/pimple/pimple/ext/pimple/tests/005.phpt | 27
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodUrlUploadJsonRequest.php | 99
vendor/symfony/var-dumper/Caster/CutStub.php | 59
vendor/qiniu/php-sdk/docs/rtc/README.md | 71
vendor/symfony/translation/Tests/DataCollectorTranslatorTest.php | 116
vendor/xaboy/form-builder/src/UI/Elm/Traits/CheckBoxFactoryTrait.php | 31
vendor/symfony/translation-contracts/TranslatorInterface.php | 65
vendor/symfony/var-dumper/Caster/PgSqlCaster.php | 154
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php | 722
vendor/qiniu/php-sdk/examples/rs_batch_move.php | 36
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodListCdnTasksRequest.php | 337
vendor/topthink/think-captcha/README.md | 57
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/WorkflowExecution.php | 581
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php | 189
vendor/volcengine/volc-sdk-php/examples/Live/DemoSnapshotAuditPreset.php | 51
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoStopCdnDomain.php | 19
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions | 416
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php | 169
vendor/topthink/framework/src/think/route/RuleName.php | 211
vendor/psr/http-message/src/RequestInterface.php | 129
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php | 481
vendor/qcloud/cos-sdk-v5/sample/catchException.php | 30
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DescribeCDNSnapshotHistoryResponse.php | 119
vendor/psr/http-client/src/ClientExceptionInterface.php | 10
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php | 538
vendor/topthink/think-captcha/src/CaptchaService.php | 21
vendor/xaboy/form-builder/src/Json.php | 38
vendor/xaboy/form-builder/src/UI/Elm/Traits/InputNumberFactoryTrait.php | 32
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php | 426
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodDeleteVideoClassificationRequest.php | 85
vendor/symfony/translation/MessageSelector.php | 98
vendor/topthink/think-image/src/Image.php | 610
vendor/volcengine/volc-sdk-php/examples/Live/DemoListPullToPushTask.php | 29
vendor/symfony/translation/Tests/fixtures/resources-target-attributes.xlf | 14
vendor/volcengine/volc-sdk-php/examples/Live/DemoDescribeInfo.php | 198
vendor/qiniu/php-sdk/src/Qiniu/Storage/FormUploader.php | 137
vendor/xaboy/form-builder/src/UI/Elm/Traits/TreeFactoryTrait.php | 74
vendor/workerman/workerman/Protocols/Websocket.php | 504
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory.php | 79
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetPrivateDrmPlayAuthResponse.php | 119
vendor/topthink/framework/src/think/Lang.php | 290
vendor/symfony/http-foundation/Tests/RedirectResponseTest.php | 97
vendor/symfony/translation/Dumper/IcuResFileDumper.php | 106
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php | 1846
vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractMigration.php | 273
vendor/symfony/process/Exception/RuntimeException.php | 21
vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php | 23
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php | 81
vendor/songshenzong/support/src/BashEcho.php | 72
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/LogoOverride.php | 99
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/ListVQScoreTaskRequest.php | 201
vendor/symfony/translation/Tests/fixtures/resources-2.0.xlf | 25
vendor/xaboy/form-builder/src/Rule/ChildrenRule.php | 97
vendor/symfony/var-dumper/Tests/Dumper/HtmlDumperTest.php | 163
vendor/topthink/think-orm/src/db/Fetch.php | 494
vendor/xaboy/form-builder/src/UI/Elm/Components/Popover.php | 55
vendor/topthink/framework/src/think/console/command/make/Model.php | 36
vendor/xaboy/form-builder/src/Annotation/Col.php | 45
vendor/qiniu/php-sdk/examples/upload_and_callback.php | 34
vendor/qiniu/php-sdk/examples/pulpvideo.php | 55
vendor/topthink/think-queue/src/queue/CallQueuedHandler.php | 36
vendor/psr/http-client/src/ClientInterface.php | 20
vendor/symfony/psr-http-message-bridge/composer.json | 42
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php | 870
vendor/topthink/framework/src/think/console/command/make/Middleware.php | 36
vendor/xaboy/form-builder/src/Factory/Elm.php | 154
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodRetrieveTranscodeResultRequest.php | 99
vendor/xaboy/form-builder/src/traits/form/FormInputTrait.php | 113
vendor/symfony/translation/Tests/fixtures/resources.yml | 1
vendor/symfony/polyfill-php70/Resources/stubs/AssertionError.php | 5
vendor/xaboy/form-builder/src/components/InputNumber.php | 84
vendor/symfony/translation/Tests/LoggingTranslatorTest.php | 68
vendor/volcengine/volc-sdk-php/examples/ImageX/DemoDeleteImages.php | 16
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodUpdateMediaInfoRequest.php | 420
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodDeleteMediaResponse.php | 119
vendor/xaboy/form-builder/src/UI/Elm/Components/Button.php | 51
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php | 33
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodDeleteTranscodesData.php | 65
vendor/psr/container/README.md | 5
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetAppInfo.php | 27
vendor/topthink/think-orm/src/model/relation/HasOne.php | 300
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoUpdateCdnConfig.php | 36
vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php | 398
vendor/volcengine/volc-sdk-php/examples/Vod/DemoPlay.php | 47
vendor/symfony/translation/Tests/fixtures/extractor/translation.html.php | 59
vendor/topthink/think-queue/.gitignore | 4
vendor/topthink/think-image/tests/InfoTest.php | 60
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DescribeDenyConfigResponse.php | 119
vendor/xaboy/form-builder/src/UI/Iview/Components/Upload.php | 119
vendor/volcengine/volc-sdk-php/examples/Vod/DemoDeleteTranscodes.php | 29
vendor/qiniu/php-sdk/examples/delete_bucketEvent.php | 21
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions | 416
vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php | 103
vendor/pimple/pimple/ext/pimple/config.m4 | 63
vendor/psr/log/Psr/Log/InvalidArgumentException.php | 7
vendor/volcengine/volc-sdk-php/examples/Live/DemoKillStream.php | 29
vendor/volcengine/volc-sdk-php/examples/SecretNumber/DemoUpgradeAXToAXB.php | 19
vendor/ralouphie/getallheaders/src/getallheaders.php | 46
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php | 835
vendor/topthink/framework/src/think/Log.php | 342
vendor/topthink/framework/src/think/Response.php | 413
vendor/volcengine/volc-sdk-php/examples/.gitignore | 1
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoSubmitPreloadTask.php | 19
vendor/volcengine/volc-sdk-php/README.md | 49
vendor/qiniu/php-sdk/examples/cdn_get_flux.php | 34
vendor/topthink/think-queue/src/queue/Worker.php | 414
vendor/xaboy/form-builder/src/UI/Iview/Components/Checkbox.php | 49
vendor/topthink/framework/src/think/service/PaginatorService.php | 52
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodApplyUploadInfoData.php | 75
vendor/symfony/translation/Tests/fixtures/messages.yml | 3
vendor/volcengine/volc-sdk-php/src/Service/Base/Models/Base/ResponseMetadata.php | 245
vendor/workerman/workerman/MIT-LICENSE.txt | 21
vendor/xaboy/form-builder/src/components/Checkbox.php | 91
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodListCdnAccessLogRequest.php | 167
vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php | 1136
vendor/workerman/channel/composer.json | 12
vendor/topthink/think-captcha/assets/ttfs/2.ttf | 0
vendor/topthink/framework/src/think/exception/ErrorException.php | 57
vendor/xaboy/form-builder/src/Contract/CustomComponentInterface.php | 25
vendor/symfony/translation/Writer/TranslationWriter.php | 90
vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js | 10
vendor/topthink/think-image/src/image/gif/Gif.php | 88
vendor/xaboy/form-builder/src/UI/Elm/Components/TimePicker.php | 111
vendor/xaboy/form-builder/src/UI/Elm/Style/Col.php | 166
vendor/symfony/http-foundation/File/Stream.php | 28
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/ListPullToPushTaskResponse.php | 119
vendor/symfony/psr-http-message-bridge/.travis.yml | 47
vendor/symfony/var-dumper/Caster/ReflectionCaster.php | 388
vendor/volcengine/volc-sdk-php/examples/Live/DemoDescribeVQScoreTask.php | 27
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config | 24
vendor/topthink/think-view/README.md | 36
vendor/xaboy/form-builder/src/Annotation/Validate/Enum.php | 40
vendor/symfony/translation/Tests/fixtures/resources-notes-meta.xlf | 26
vendor/xaboy/form-builder/src/UI/Iview/Components/TimePicker.php | 115
vendor/topthink/think-migration/src/command/seed/Run.php | 107
vendor/qcloud/cos-sdk-v5/sample/deleteBucket.php | 25
vendor/xaboy/form-builder/src/traits/form/FormColorPickerTrait.php | 32
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/Quality.php | 119
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnRegionAndIsp.php | 19
vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php | 71
vendor/symfony/translation/Extractor/ChainExtractor.php | 60
vendor/topthink/framework/src/think/initializer/Error.php | 117
vendor/psr/http-factory/README.md | 10
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodMediaInfo.php | 153
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php | 159
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php | 174
vendor/topthink/framework/src/think/facade/Env.php | 44
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php | 145
vendor/volcengine/volc-sdk-php/examples/Rtc/rtc-openapi-demo/Rtc/Rtc.php | 83
vendor/spatie/macroable/README.md | 130
vendor/symfony/translation/Tests/fixtures/resources.po | 24
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/GPBMetadata/SnapshotManage.php | 0
vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php | 38
vendor/topthink/think-captcha/assets/bgs/7.jpg | 0
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/ListPullToPushTaskRequest.php | 133
vendor/volcengine/volc-sdk-php/src/Service/Vod/Upload/OptionInfo.php | 14
vendor/volcengine/volc-sdk-php/src/Service/Cdn.php | 733
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodUpdateSpaceResponse.php | 75
vendor/xaboy/form-builder/src/UI/Elm/Traits/RadioFactoryTrait.php | 32
vendor/symfony/http-foundation/File/File.php | 138
vendor/topthink/framework/src/think/route/Dispatch.php | 264
vendor/xaboy/form-builder/src/components/Row.php | 67
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/CreateDomainRequest.php | 133
vendor/xaboy/form-builder/src/UI/Elm/Traits/ColorPickerFactoryTrait.php | 32
vendor/xaboy/form-builder/src/traits/form/FormHiddenTrait.php | 31
vendor/symfony/translation/Loader/YamlFileLoader.php | 50
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php | 532
vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceProperties.php | 8
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php | 946
vendor/topthink/think-migration/phinx/src/Phinx/Seed/SeedInterface.php | 166
vendor/topthink/framework/src/think/console/command/optimize/Schema.php | 104
vendor/xaboy/form-builder/src/Driver/FormComponent.php | 69
vendor/pimple/pimple/ext/pimple/tests/012.phpt | 28
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DescribeRecordTaskFileHistoryResponse.php | 119
vendor/symfony/http-foundation/IpUtils.php | 156
vendor/volcengine/volc-sdk-php/src/Service/Live/LiveOption.php | 272
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodUpdateSubtitleInfoRequest.php | 309
vendor/xaboy/form-builder/src/UI/Elm/Components/Select.php | 99
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php | 528
vendor/symfony/polyfill-intl-idn/Resources/unidata/Regex.php | 24
vendor/symfony/translation/Tests/Dumper/QtFileDumperTest.php | 48
vendor/topthink/think-migration/src/command/migrate/Create.php | 55
vendor/qcloud_sts/qcloud-sts-sdk/LICENSE | 21
vendor/psr/http-message/CHANGELOG.md | 36
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php | 85
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php | 85
vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php | 27
vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php | 21
vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php | 1165
vendor/topthink/think-orm/README.md | 27
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php | 20
vendor/topthink/framework/src/think/Http.php | 288
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/GPBMetadata/VodCallback.php | 0
vendor/psr/http-client/src/RequestExceptionInterface.php | 24
vendor/xaboy/form-builder/src/FormHandle.php | 153
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/ValuePair.php | 99
vendor/volcengine/volc-sdk-php/src/Service/Vod/Vod.php | 2208
vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.php | 10
vendor/topthink/think-image/tests/autoload.php | 15
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetAppInfoResult.php | 167
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc | 151
vendor/symfony/translation/Tests/fixtures/resources.dump.json | 1
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php | 111
vendor/topthink/framework/src/think/console/command/make/Service.php | 36
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/GeneratePlayURLResult.php | 65
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodSetCallbackEventRequest.php | 167
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetDirectEditProgressRequest.php | 58
vendor/xaboy/form-builder/src/UI/Iview/Components/DatePicker.php | 125
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php | 193
vendor/symfony/event-dispatcher/EventSubscriberInterface.php | 46
vendor/volcengine/volc-sdk-php/examples/ImageX/DemoUpdateImageUrls.php | 16
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/CreateTtsResource.php | 23
vendor/symfony/var-dumper/Cloner/Stub.php | 67
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php | 663
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeOriginNrtDataSummary.php | 24
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodSnapshot.php | 201
vendor/topthink/think-view/src/Think.php | 263
vendor/xaboy/form-builder/images/demo03.jpg | 0
vendor/qiniu/php-sdk/autoload.php | 14
vendor/topthink/think-orm/.gitignore | 3
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodListWorkflowExecutionRequest.php | 507
vendor/symfony/var-dumper/Caster/AmqpCaster.php | 210
vendor/symfony/translation/Formatter/MessageFormatter.php | 79
vendor/topthink/framework/CONTRIBUTING.md | 119
vendor/symfony/translation/MetadataAwareInterface.php | 54
vendor/symfony/psr-http-message-bridge/Tests/Functional/CovertTest.php | 239
vendor/pimple/pimple/ext/pimple/tests/016.phpt | 24
vendor/qiniu/php-sdk/examples/update_bucketEvent.php | 25
vendor/topthink/framework/src/think/response/View.php | 151
vendor/qcloud/cos-sdk-v5/sample/getBucketAcl.php | 25
vendor/volcengine/volc-sdk-php/examples/Live/DemoUpdatePullToPushTask.php | 36
vendor/volcengine/volc-sdk-php/examples/Live/DemoStopPullToPushTask.php | 27
vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php | 157
vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php | 209
vendor/volcengine/volc-sdk-php/examples/Live/DemoGeneratePlayURL.php | 34
vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php | 159
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodListCdnStatusDataRequest.php | 235
vendor/qcloud/cos-sdk-v5/phpunit.xml | 7
vendor/symfony/var-dumper/composer.json | 54
vendor/symfony/var-dumper/README.md | 15
vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php | 99
vendor/psr/container/src/ContainerExceptionInterface.php | 13
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/AddrScoreInfo.php | 99
vendor/symfony/http-foundation/Request.php | 2191
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/GPBMetadata/ResponseLive.php | 120
vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.php | 11
vendor/xaboy/form-builder/src/traits/form/FormTreeTrait.php | 73
vendor/symfony/var-dumper/Tests/Caster/SplCasterTest.php | 207
vendor/symfony/translation/Dumper/JsonFileDumper.php | 40
vendor/xaboy/form-builder/src/UI/Elm/Components/Group.php | 77
vendor/qiniu/php-sdk/examples/pfop_mkzip.php | 43
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/CDNSnapshotHistory.php | 371
vendor/symfony/var-dumper/Tests/Fixtures/GeneratorDemo.php | 21
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/UploadBodyListener.php | 79
vendor/symfony/var-dumper/Caster/TraceStub.php | 36
vendor/symfony/http-foundation/RedirectResponse.php | 109
vendor/topthink/think-queue/src/queue/job/Database.php | 91
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DescribeClosedStreamInfoByPageResponse.php | 119
vendor/psr/log/Psr/Log/AbstractLogger.php | 128
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php | 90
vendor/volcengine/volc-sdk-php/examples/Sms/DemoSendSms.php | 26
vendor/symfony/translation/Tests/Catalogue/MergeOperationTest.php | 97
vendor/symfony/process/Exception/ExceptionInterface.php | 21
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodListWorkflowExecutionResponse.php | 119
vendor/symfony/translation/Tests/Dumper/JsonFileDumperTest.php | 39
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php | 857
vendor/volcengine/volc-sdk-php/Changelog | 211
vendor/qiniu/php-sdk/src/Qiniu/Processing/PersistentFop.php | 94
vendor/volcengine/volc-sdk-php/examples/VEdit/DemoSubmitTemplateTask.php | 38
vendor/symfony/var-dumper/Caster/FrameStub.php | 30
vendor/volcengine/volc-sdk-php/src/Service/Vod/VodOption.php | 630
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/ForbidStreamResponse.php | 75
vendor/topthink/think-orm/src/db/concern/AggregateQuery.php | 107
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetAutomaticSpeechRecognitionForAuditRequest.php | 99
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetHlsDecryptionKeyResponse.php | 119
vendor/psr/log/Psr/Log/LoggerInterface.php | 125
vendor/symfony/polyfill-php70/README.md | 28
vendor/qiniu/php-sdk/examples/persistent_fop_init.php | 19
vendor/volcengine/volc-sdk-php/src/Service/Live/LiveUtils.php | 86
vendor/symfony/http-foundation/Tests/HeaderBagTest.php | 205
vendor/symfony/translation/Tests/fixtures/resources.php | 5
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php | 2054
vendor/symfony/http-foundation/Response.php | 1308
vendor/symfony/translation-contracts/LocaleAwareInterface.php | 31
vendor/topthink/think-captcha/src/helper.php | 52
vendor/xaboy/form-builder/src/UI/Elm/Components/Radio.php | 69
vendor/symfony/var-dumper/Caster/Caster.php | 157
vendor/symfony/translation/Tests/Loader/QtFileLoaderTest.php | 79
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodListVideoClassificationsRequest.php | 85
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php | 235
vendor/workerman/workerman/Events/React/Base.php | 262
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php | 68
vendor/symfony/translation/Tests/fixtures/resources.mo | 0
vendor/topthink/think-queue/src/queue/InteractsWithTime.php | 65
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetHlsDecryptionKeyResult.php | 65
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodSpriteSnapshot.php | 337
vendor/topthink/framework/src/think/cache/Driver.php | 359
vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php | 183
vendor/workerman/workerman/Events/React/ExtLibEventLoop.php | 27
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/StopPullToPushTaskResponse.php | 75
vendor/qiniu/php-sdk/examples/qetag.php | 11
vendor/xaboy/form-builder/src/Form.php | 535
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoSubmitUnblockTask.php | 19
vendor/volcengine/volc-sdk-php/examples/Vod/DemoUploadSts2.php | 21
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodAllPlayInfoModel.php | 415
vendor/workerman/workerman/Worker.php | 2490
vendor/topthink/think-queue/src/queue/Service.php | 50
vendor/spatie/macroable/config/.gitkeep | 0
vendor/topthink/framework/src/think/Exception.php | 60
vendor/workerman/workerman/Protocols/ProtocolInterface.php | 52
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Copy.php | 140
vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css | 130
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IOFactory.php | 217
vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php | 25
vendor/volcengine/volc-sdk-php/examples/BusinessSecurity/DemoRiskDetect.php | 36
vendor/symfony/polyfill-intl-normalizer/bootstrap.php | 19
vendor/topthink/think-captcha/assets/bgs/5.jpg | 0
vendor/symfony/psr-http-message-bridge/phpunit.xml.dist | 30
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetRecPosterData.php | 99
vendor/topthink/think-queue/src/facade/Queue.php | 18
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/GPBMetadata/VodUpload.php | 0
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodUrlUploadURLSet.php | 371
vendor/xaboy/form-builder/src/UI/Iview/Traits/ColorPickerFactoryTrait.php | 32
vendor/topthink/framework/src/think/response/Html.php | 34
vendor/topthink/framework/src/think/route/dispatch/Url.php | 118
vendor/xaboy/form-builder/src/UI/Elm/Style/Row.php | 90
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config | 25
vendor/qiniu/php-sdk/examples/rsf_v2list_bucket.php | 33
vendor/topthink/framework/src/think/console/output/formatter/Stack.php | 116
vendor/topthink/framework/src/think/exception/HttpException.php | 42
vendor/topthink/think-migration/src/db/Table.php | 135
vendor/topthink/think-migration/phinx/CHANGELOG.md | 370
vendor/symfony/translation/Dumper/DumperInterface.php | 31
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php | 1438
vendor/workerman/workerman/Protocols/Text.php | 70
vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php | 28
vendor/xaboy/form-builder/src/FormComponentDriver.php | 267
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php | 172
vendor/qiniu/php-sdk/examples/pfop_video_avthumb.php | 47
vendor/symfony/psr-http-message-bridge/Tests/Fixtures/ServerRequest.php | 141
vendor/symfony/http-foundation/Tests/CookieTest.php | 235
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php | 40
vendor/symfony/translation/LoggingTranslator.php | 157
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php | 282
vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.expected | 10
vendor/xaboy/form-builder/src/Contract/FormHandleInterface.php | 11
vendor/qiniu/php-sdk/examples/rs_delete.php | 17
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodUpdateMediaPublishStatusResponse.php | 75
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/ForbiddenStreamInfoList.php | 277
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodListCdnTopAccessUrlResponse.php | 119
vendor/volcengine/volc-sdk-php/examples/Vod/DemoUpdateVideoClassification.php | 29
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/BucketAlreadyExistsException.php | 5
vendor/topthink/framework/src/think/console/output/Descriptor.php | 323
vendor/xaboy/form-builder/src/components/Slider.php | 103
vendor/symfony/polyfill-mbstring/bootstrap.php | 58
vendor/symfony/var-dumper/Caster/DOMCaster.php | 302
vendor/topthink/think-migration/phinx/src/Phinx/Migration/MigrationInterface.php | 215
vendor/workerman/workerman.log | 740
vendor/topthink/think-orm/src/model/concern/TimeStamp.php | 208
vendor/symfony/translation/Tests/fixtures/resources.csv | 4
vendor/volcengine/volc-sdk-php/src/Service/Visual.php | 173
vendor/topthink/framework/src/helper.php | 664
vendor/songshenzong/support/phpunit.xml | 35
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetWorkflowExecutionDetailResult.php | 392
vendor/psr/log/Psr/Log/LogLevel.php | 18
vendor/qiniu/php-sdk/.travis.yml | 28
vendor/volcengine/volc-sdk-php/examples/Vod/DemoDescribeVodDomainTrafficData.php | 31
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodCreateCdnPreloadTaskResponse.php | 119
vendor/qiniu/php-sdk/src/Qiniu/functions.php | 264
vendor/xaboy/form-builder/src/Form/ElmForm.php | 24
vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceMethodCalls.php | 12
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions | 408
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions | 416
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCommitUploadInfoResult.php | 75
vendor/xaboy/form-builder/src/UI/Iview/Style/Row.php | 90
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetSmartStrategyLitePlayInfoRequest.php | 417
vendor/symfony/psr-http-message-bridge/LICENSE | 19
vendor/pimple/pimple/ext/pimple/tests/019.phpt | 18
vendor/symfony/polyfill-intl-idn/LICENSE | 19
vendor/symfony/var-dumper/Caster/ResourceCaster.php | 98
vendor/xaboy/form-builder/src/exception/FormBuilderException.php | 18
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/ClosedStreamInfo.php | 99
vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php | 91
vendor/volcengine/volc-sdk-php/examples/Live/DemoRestartPullToPushTask.php | 27
vendor/volcengine/volc-sdk-php/examples/Live/DemoUpdateDenyConfig.php | 30
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/FetchResource.php | 20
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php | 837
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodURLSet.php | 313
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DescribeDomainRequest.php | 65
vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php | 113
vendor/symfony/translation/Tests/fixtures/empty.mo | 0
vendor/xaboy/form-builder/src/Annotation/Validate/Pattern.php | 40
vendor/topthink/think-migration/src/command/stubs/migrate.stub | 33
vendor/topthink/framework/src/think/route/RuleGroup.php | 523
vendor/volcengine/volc-sdk-php/examples/SecretNumber/DemoClick2Call.php | 19
vendor/psr/http-factory/.gitignore | 2
vendor/psr/log/composer.json | 26
vendor/topthink/framework/src/think/console/command/make/stubs/subscribe.stub | 8
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/GeneratePushURLRequest.php | 235
vendor/workerman/crontab/src/Crontab.php | 177
vendor/topthink/think-queue/src/queue/job/Sync.php | 66
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetPlayInfoWithLiveTimeShiftScene.php | 31
vendor/qiniu/php-sdk/src/Qiniu/Auth.php | 187
vendor/symfony/process/Tests/PhpProcessTest.php | 63
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetWorkflowExecutionDetailResponse.php | 119
vendor/topthink/think-queue/tests/ListenerTest.php | 80
vendor/xaboy/form-builder/src/UI/Iview/Components/Tooltip.php | 47
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionParser.php | 109
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php | 556
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php | 57
vendor/workerman/workerman/Lib/Constants.php | 40
vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/fr.res | 0
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/GeneratePushURLResult.php | 201
vendor/topthink/think-orm/src/db/concern/ResultOperation.php | 247
vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/en.res | 0
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetMediaListRequest.php | 371
vendor/qcloud/cos-sdk-v5/sample/getBucketCors.php | 25
vendor/symfony/translation/MessageCatalogueInterface.php | 138
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodDeleteMediaData.php | 65
vendor/ralouphie/getallheaders/.travis.yml | 18
vendor/symfony/translation/CHANGELOG.md | 126
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php | 175
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php | 677
vendor/topthink/think-migration/phinx/src/Phinx/Util/Util.php | 190
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetSubtitleAuthToken.php | 26
vendor/symfony/translation/Tests/fixtures/empty.po | 0
vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub | 64
vendor/topthink/think-orm/src/db/connector/Sqlite.php | 96
vendor/topthink/think-queue/src/queue/command/Retry.php | 83
vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php | 92
vendor/volcengine/volc-sdk-php/src/Service/Imp/Models/Response/ImpRetrieveJobResponse.php | 92
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php | 117
vendor/qiniu/php-sdk/examples/upload_simple_file.php | 37
vendor/topthink/think-template/src/template/taglib/Cx.php | 715
vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php | 272
vendor/qiniu/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php | 60
vendor/symfony/translation/Extractor/AbstractFileExtractor.php | 80
vendor/volcengine/volc-sdk-php/examples/Visual/DemoOCR.php | 21
vendor/psr/log/Psr/Log/LoggerAwareTrait.php | 26
vendor/symfony/var-dumper/Caster/ExceptionCaster.php | 357
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodUpdateSubtitleStatusData.php | 65
vendor/topthink/framework/src/think/console/command/make/Command.php | 55
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodBetterFrameDataForAudit.php | 269
vendor/symfony/translation/Tests/PluralizationRulesTest.php | 124
vendor/symfony/http-foundation/Tests/JsonResponseTest.php | 266
vendor/volcengine/volc-sdk-php/examples/Vod/DemoListCdnTopAccessUrl.php | 30
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodDescribeVodDomainTrafficDataResult.php | 220
vendor/xaboy/form-builder/src/UI/Elm/Traits/FrameFactoryTrait.php | 126
vendor/ralouphie/getallheaders/tests/GetAllHeadersTest.php | 121
vendor/topthink/think-orm/src/model/relation/HasManyThrough.php | 382
vendor/symfony/var-dumper/Tests/Dumper/FunctionsTest.php | 57
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php | 24
vendor/xaboy/form-builder/src/Response.php | 107
vendor/xaboy/form-builder/src/UI/Iview/Traits/SelectFactoryTrait.php | 48
vendor/volcengine/volc-sdk-php/.gitignore | 9
vendor/qiniu/php-sdk/examples/README.md | 10
vendor/symfony/var-dumper/Tests/Fixtures/FooInterface.php | 11
vendor/psr/log/LICENSE | 19
vendor/qcloud/cos-sdk-v5/sample/createBucket.php | 25
vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php | 146
vendor/qcloud_sts/qcloud-sts-sdk/composer.json | 23
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/StageStatus.php | 94
vendor/symfony/psr-http-message-bridge/CHANGELOG.md | 18
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/CurlException.php | 5
vendor/symfony/var-dumper/Dumper/CliDumper.php | 643
vendor/topthink/think-migration/phinx/CONTRIBUTING.md | 66
vendor/symfony/translation/Tests/Loader/CsvFileLoaderTest.php | 61
vendor/topthink/framework/src/think/contract/ModelRelationInterface.php | 99
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php | 7
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodDescribeIpInfoResult.php | 58
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetAllPlayInfo.php | 44
vendor/xaboy/form-builder/README.md | 105
vendor/songshenzong/support/tests/bootstrap.php | 11
vendor/xaboy/form-builder/src/traits/form/FormTimePickerTrait.php | 60
vendor/qiniu/php-sdk/examples/prefop.php | 29
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/CreatePullToPushTaskRequest.php | 337
vendor/xaboy/form-builder/src/components/Cascader.php | 151
vendor/topthink/think-helper/src/contract/Arrayable.php | 8
vendor/xaboy/form-builder/src/UI/Elm/Components/Checkbox.php | 79
vendor/topthink/framework/tests/DbTest.php | 49
vendor/psr/simple-cache/src/InvalidArgumentException.php | 13
vendor/topthink/framework/tests/HttpTest.php | 155
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodCommitUploadInfoRequest.php | 167
vendor/qcloud/cos-sdk-v5/sample/createMultipartUpload.php | 39
vendor/symfony/var-dumper/Tests/Caster/PdoCasterTest.php | 64
vendor/symfony/http-foundation/Session/Flash/FlashBag.php | 152
vendor/symfony/polyfill-mbstring/LICENSE | 19
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php | 146
vendor/symfony/translation/phpunit.xml.dist | 30
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected | 17
vendor/topthink/framework/src/think/exception/ClassNotFoundException.php | 39
vendor/volcengine/volc-sdk-php/examples/Live/DemoDescribeRelaySourceV2.php | 27
vendor/volcengine/volc-sdk-php/examples/Visual/DemoGeneralSegment.php | 13
vendor/topthink/think-image/tests/RotateTest.php | 42
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCdnIpInfo.php | 167
vendor/symfony/translation/Resources/schemas/xml.xsd | 309
vendor/pimple/pimple/ext/pimple/tests/009.phpt | 13
vendor/topthink/framework/src/think/cache/driver/Wincache.php | 175
vendor/songshenzong/support/tests/Core/EchoBashTest.php | 29
vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Uri.php | 135
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodTrafficData.php | 92
vendor/symfony/translation/Tests/fixtures/empty-translation.mo | 0
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodVideoClassificationsData.php | 58
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCdnStatisticsCommonResult.php | 85
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php | 113
vendor/volcengine/volc-sdk-php/src/Service/Imp/Models/GPBMetadata/ImpCommon.php | 24
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions | 416
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php | 9
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/CategoryTagInfo.php | 201
vendor/symfony/var-dumper/Caster/DsPairStub.php | 28
vendor/volcengine/volc-sdk-php/src/Service/Vod/VodUtils.php | 56
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/ListDomainDetailResponse.php | 119
vendor/symfony/http-foundation/Tests/ResponseTestCase.php | 89
vendor/xaboy/form-builder/src/Annotation/Emit.php | 47
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetSubtitleInfoList.php | 38
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodSourceInfo.php | 541
vendor/pimple/pimple/ext/pimple/tests/010.phpt | 45
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/NoSuchKeyException.php | 6
vendor/volcengine/volc-sdk-php/examples/Rtc/rtc-openapi-demo/README.md | 34
vendor/symfony/translation/Extractor/PhpStringTokenParser.php | 142
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php | 64
vendor/topthink/think-image/src/image/gif/Encoder.php | 222
vendor/psr/http-client/src/NetworkExceptionInterface.php | 24
vendor/symfony/http-foundation/Tests/ParameterBagTest.php | 194
vendor/xaboy/form-builder/src/Template/form.php | 16
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php | 205
vendor/xaboy/form-builder/src/UI/Iview/Traits/SwitchesFactoryTrait.php | 32
vendor/volcengine/volc-sdk-php/examples/Live/DemoDomain.php | 61
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetPlayInfoWithLiveTimeShiftSceneResponse.php | 119
vendor/xaboy/form-builder/src/UI/Elm/Traits/GroupFactoryTrait.php | 33
vendor/symfony/var-dumper/Caster/PdoCaster.php | 120
vendor/qcloud/cos-sdk-v5/sample/getObject.php | 36
vendor/topthink/framework/src/think/event/LogWrite.php | 31
vendor/pimple/pimple/src/Pimple/Container.php | 298
vendor/volcengine/volc-sdk-php/src/Service/Imp/ImpOption.php | 70
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/CreateTask.php | 30
vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example | 0
vendor/symfony/http-foundation/Tests/ServerBagTest.php | 170
vendor/xaboy/form-builder/src/UI/Iview/Traits/InputFactoryTrait.php | 112
vendor/psr/http-message/src/MessageInterface.php | 187
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php | 445
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/KillStreamResponse.php | 75
vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php | 255
vendor/volcengine/volc-sdk-php/examples/Vod/DemoDescribeIpInfo.php | 27
vendor/qiniu/php-sdk/src/Qiniu/Http/Error.php | 35
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetPlayInfoRequest.php | 587
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php | 64
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/CreateDomainResponse.php | 75
vendor/symfony/translation/Tests/fixtures/resources-multi-files.xlf | 27
vendor/topthink/think-orm/src/db/connector/Mysql.php | 162
vendor/workerman/crontab/README.md | 29
vendor/xaboy/form-builder/src/UI/Elm/Components/Option.php | 56
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/CreateVQScoreTaskResponse.php | 119
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php | 462
vendor/symfony/translation/Dumper/PhpFileDumper.php | 38
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetAudioEventDetectionForAuditResult.php | 68
vendor/symfony/translation/Tests/fixtures/escaped-id-plurals.po | 10
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php | 51
vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php | 819
vendor/symfony/http-foundation/Tests/Session/SessionTest.php | 263
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php | 114
vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Index.php | 185
vendor/symfony/var-dumper/Tests/Caster/GmpCasterTest.php | 48
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DeleteDomainResponse.php | 75
vendor/volcengine/volc-sdk-php/src/Service/Imp/ImpUtils.php | 56
vendor/workerman/workerman/WebServer.php | 311
vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php | 43
vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php | 69
vendor/topthink/think-view/LICENSE | 201
vendor/topthink/framework/src/think/console/command/Version.php | 33
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetPlayInfoWithLiveTimeShiftSceneRequest.php | 201
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DeleteRelaySourceRequest.php | 99
vendor/topthink/framework/src/think/route/Rule.php | 923
vendor/xaboy/form-builder/src/Contract/ColComponentInterface.php | 19
vendor/qiniu/php-sdk/tests/Qiniu/Tests/ZoneTest.php | 59
vendor/topthink/framework/src/think/route/Url.php | 517
vendor/xaboy/form-builder/src/UI/Elm/Traits/ValidateFactoryTrait.php | 73
vendor/xaboy/form-builder/src/UI/Iview/Validate.php | 225
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodListWorkflowExecutionResult.php | 167
vendor/symfony/var-dumper/Tests/Caster/StubCasterTest.php | 213
vendor/topthink/framework/src/think/Manager.php | 177
vendor/topthink/think-migration/src/db/Column.php | 171
vendor/topthink/framework/tests/CacheTest.php | 149
vendor/topthink/think-captcha/assets/bgs/4.jpg | 0
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/GPBMetadata/VQScore.php | 0
vendor/topthink/framework/src/think/contract/LogHandlerInterface.php | 28
vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php | 507
vendor/xaboy/form-builder/src/Annotation/Info.php | 40
vendor/xaboy/form-builder/src/UI/Elm/Traits/InputFactoryTrait.php | 112
vendor/symfony/translation/Tests/Loader/JsonFileLoaderTest.php | 62
vendor/topthink/think-image/tests/WaterTest.php | 58
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetDirectEditResultRequest.php | 58
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions | 416
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/DocumentGenerator.php | 97
vendor/qcloud/cos-sdk-v5/sample/completeMultipartUpload.php | 37
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetAuditFramesForAudit.php | 30
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetAuditFramesForAuditRequest.php | 167
vendor/topthink/framework/src/think/Request.php | 2178
vendor/volcengine/volc-sdk-php/src/Service/Vod/Upload/FunctionInner.php | 28
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php | 232
vendor/symfony/translation/Tests/DependencyInjection/TranslationDumperPassTest.php | 48
vendor/symfony/polyfill-intl-idn/Resources/unidata/mapped.php | 5778
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionListener.php | 69
vendor/symfony/var-dumper/Tests/Test/VarDumperTestTraitTest.php | 46
vendor/topthink/framework/src/think/Container.php | 561
vendor/volcengine/volc-sdk-php/examples/Live/DemoDescribeRecordTaskFileHistory.php | 34
vendor/psr/container/LICENSE | 21
vendor/symfony/var-dumper/Caster/GmpCaster.php | 30
vendor/symfony/translation/Tests/Dumper/IcuResFileDumperTest.php | 29
vendor/topthink/think-multi-app/LICENSE | 201
vendor/topthink/think-queue/src/queue/command/stubs/failed_jobs.stub | 18
vendor/symfony/psr-http-message-bridge/.php_cs.dist | 24
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodDescribeVodSpaceStorageDataResult.php | 269
vendor/symfony/translation/DependencyInjection/TranslatorPass.php | 89
vendor/symfony/process/Exception/InvalidArgumentException.php | 21
vendor/volcengine/volc-sdk-php/src/Service/Vod/Upload/WorkflowInput.php | 18
vendor/topthink/think-image/tests/images/test.bmp | 0
vendor/xaboy/form-builder/src/Rule/OptionsRule.php | 109
vendor/workerman/workerman/Events/Ev.php | 194
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DeletePullToPushTaskResponse.php | 75
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetAudioInfoForAuditResult.php | 68
vendor/qiniu/php-sdk/LICENSE | 22
vendor/symfony/var-dumper/Caster/MemcachedCaster.php | 79
vendor/symfony/event-dispatcher/Event.php | 120
vendor/xaboy/form-builder/src/traits/form/FormInputNumberTrait.php | 33
vendor/topthink/framework/src/think/event/AppInit.php | 19
vendor/volcengine/volc-sdk-php/examples/Live/DescribeClosedStreamInfoByPage.php | 36
vendor/psr/cache/src/CacheException.php | 10
vendor/symfony/translation/Tests/fixtures/with-attributes.xlf | 21
vendor/songshenzong/support/tests/Core/StrTest.php | 108
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/RestartPullToPushTaskResponse.php | 75
vendor/topthink/think-orm/src/db/exception/BindParamException.php | 35
vendor/topthink/think-orm/src/db/connector/Oracle.php | 117
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCommitUploadInfoData.php | 177
vendor/psr/container/src/ContainerInterface.php | 37
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodListCdnTopAccessUrlRequest.php | 167
vendor/qiniu/php-sdk/examples/rs_batch_change_mime.php | 28
vendor/volcengine/volc-sdk-php/examples/Vod/DemoApplyUploadInfo.php | 31
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php | 898
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/WorkflowParams.php | 109
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetMLFramesForAudit.php | 38
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php | 1147
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config | 24
vendor/volcengine/volc-sdk-php/examples/Vod/DemoListCdnAccessLog.php | 30
vendor/qcloud_sts/qcloud-sts-sdk/src/Scope.php | 56
vendor/topthink/think-migration/src/command/stubs/seed.stub | 19
vendor/workerman/channel/README.md | 49
vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php | 88
vendor/xaboy/form-builder/demo/iview.php | 140
vendor/topthink/framework/src/think/console/command/make/stubs/controller.plain.stub | 9
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodDeleteTranscodesRequest.php | 133
vendor/ralouphie/getallheaders/phpunit.xml | 22
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/GetResourceUploadUrl.php | 20
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodPlayInfoModelVersion.php | 67
vendor/topthink/think-queue/src/queue/event/JobExceptionOccurred.php | 45
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodApplyUploadInfoResponse.php | 119
vendor/workerman/workerman/Protocols/Ws.php | 472
vendor/symfony/var-dumper/Tests/Caster/ReflectionCasterTest.php | 251
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions | 416
vendor/volcengine/volc-sdk-php/examples/Visual/DemoConvertPhoto.php | 13
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/StreamStateInfo.php | 119
vendor/symfony/translation/Tests/Loader/YamlFileLoaderTest.php | 71
vendor/xaboy/form-builder/src/Factory/Iview.php | 169
vendor/pimple/pimple/ext/pimple/tests/003.phpt | 16
vendor/symfony/translation/PluralizationRules.php | 218
vendor/volcengine/volc-sdk-php/examples/Live/DemoStream.php | 25
vendor/volcengine/volc-sdk-php/examples/Vod/DemoCreateCdnRefreshTask.php | 29
vendor/workerman/workerman/Events/Swoole.php | 221
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/ManagerPullPushDomainBindRequest.php | 99
vendor/pimple/pimple/.travis.yml | 40
vendor/symfony/http-foundation/Session/Storage/MetadataBag.php | 168
vendor/symfony/translation/Tests/Catalogue/TargetOperationTest.php | 96
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color.php | 36
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/ScoreInfo.php | 99
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php | 32
vendor/xaboy/form-builder/src/Contract/BootstrapInterface.php | 27
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php | 858
vendor/xaboy/form-builder/src/UI/Iview/Components/Radio.php | 65
vendor/volcengine/volc-sdk-php/examples/Vod/DemoCreateSpace.php | 31
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php | 47
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php | 60
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/TaskInfoItem.php | 439
vendor/symfony/http-foundation/RequestStack.php | 103
vendor/symfony/polyfill-mbstring/Mbstring.php | 800
vendor/symfony/polyfill-php72/bootstrap.php | 36
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodSpaceInfo.php | 371
vendor/symfony/translation/Tests/fixtures/resources.ini | 1
vendor/volcengine/volc-sdk-php/examples/SecretNumber/DemoUnBindAXB.php | 17
vendor/symfony/translation/Exception/InvalidResourceException.php | 21
vendor/symfony/process/Exception/ProcessSignaledException.php | 41
vendor/xaboy/form-builder/src/UI/Elm/Traits/SliderFactoryTrait.php | 50
vendor/qiniu/php-sdk/examples/upload_mgr_init.php | 18
vendor/topthink/think-filesystem/src/Filesystem.php | 89
vendor/songshenzong/support/src/Arrays.php | 84
vendor/xaboy/form-builder/src/UI/Elm/Components/Switches.php | 75
vendor/symfony/translation/Tests/fixtures/resources.ts | 21
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php | 407
vendor/topthink/think-orm/src/db/concern/Transaction.php | 117
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetDirectEditProgress.php | 26
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/Pagination.php | 167
vendor/xaboy/form-builder/src/UI/Elm/Components/Upload.php | 106
vendor/volcengine/volc-sdk-php/src/Base/SignatureV4.php | 316
vendor/topthink/think-migration/src/command/migrate/Status.php | 126
vendor/topthink/think-migration/phinx/src/Phinx/Seed/Seed.template.php.dist | 19
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodListSpaceResponse.php | 109
vendor/xaboy/form-builder/src/UI/Elm/Components/Frame.php | 84
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php | 9
vendor/psr/http-message/README.md | 13
vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php | 524
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php | 411
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php | 512
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodListCdnAccessLogResult.php | 65
vendor/xaboy/form-builder/src/UI/Elm/Components/InputNumber.php | 57
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php | 239
vendor/topthink/think-orm/LICENSE | 201
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DescribeLiveStreamInfoByPageRequest.php | 347
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodStorageData.php | 85
vendor/volcengine/volc-sdk-php/src/Service/Imp/Models/GPBMetadata/ResponseImp.php | 26
vendor/topthink/think-helper/src/helper.php | 279
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodDeleteMediaRequest.php | 99
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Comment.php | 329
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php | 249
vendor/qiniu/php-sdk/tests/Qiniu/Tests/Base64Test.php | 14
vendor/symfony/translation/.gitignore | 3
vendor/qiniu/php-sdk/examples/get_bucketEvents.php | 20
vendor/symfony/var-dumper/Tests/Cloner/DataTest.php | 115
vendor/symfony/var-dumper/Tests/Command/Descriptor/HtmlDescriptorTest.php | 195
vendor/symfony/translation/Dumper/PoFileDumper.php | 84
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/ExecutionStage.php | 187
vendor/topthink/framework/src/think/cache/driver/Redis.php | 249
vendor/volcengine/volc-sdk-php/examples/Live/DemoCreateVQScoreTask.php | 31
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php | 75
vendor/topthink/think-queue/tests/DatabaseConnectorTest.php | 123
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php | 380
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php | 125
vendor/symfony/process/README.md | 13
vendor/volcengine/volc-sdk-php/src/Service/VEdit.php | 84
vendor/qiniu/php-sdk/tests/Qiniu/Tests/FormUpTest.php | 59
vendor/symfony/var-dumper/Cloner/DumperInterface.php | 60
vendor/symfony/http-foundation/Session/SessionBagProxy.php | 89
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnService.php | 15
vendor/symfony/translation/Tests/Loader/MoFileLoaderTest.php | 72
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnAccessLog.php | 23
vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php | 34
vendor/qiniu/php-sdk/examples/upload_multi_demos.php | 85
vendor/xaboy/form-builder/src/UI/Elm/Components/Hidden.php | 57
vendor/topthink/think-captcha/assets/bgs/2.jpg | 0
vendor/topthink/framework/src/think/console/output/driver/Nothing.php | 33
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetPrivateDrmPlayAuthResult.php | 65
vendor/volcengine/volc-sdk-php/examples/Visual/DemoJPCartoon.php | 14
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Signature.php | 50
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php | 196
vendor/pimple/pimple/ext/pimple/tests/018.phpt | 23
vendor/qiniu/php-sdk/src/Qiniu/Http/Client.php | 160
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetMediaListResponse.php | 119
vendor/symfony/var-dumper/Tests/Server/ConnectionTest.php | 88
vendor/workerman/workerman/Protocols/Http/mime.types | 80
vendor/topthink/framework/src/think/console/command/VendorPublish.php | 69
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php | 9
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodSetCallbackEventResponse.php | 75
vendor/workerman/workerman/README.md | 603
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCommitUploadInfoResponseData.php | 211
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodStoreInfo.php | 99
vendor/symfony/translation/Loader/PoFileLoader.php | 148
vendor/volcengine/volc-sdk-php/examples/Vod/DemoCommitUploadInfo.php | 30
vendor/xaboy/form-builder/src/traits/form/FormValidateTrait.php | 84
vendor/volcengine/volc-sdk-php/examples/Vod/DemoUpdateSubtitleStatus.php | 31
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeEdgeNrtDataSummary.php | 29
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DescribeVQScoreTaskResponse.php | 119
vendor/symfony/process/Tests/PhpExecutableFinderTest.php | 49
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/Border.php | 42
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoListResourceTags.php | 15
vendor/xaboy/form-builder/src/traits/form/FormSelectTrait.php | 61
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php | 369
vendor/symfony/polyfill-intl-normalizer/LICENSE | 19
vendor/symfony/translation/Loader/QtFileLoader.php | 77
vendor/pimple/pimple/ext/pimple/tests/008.phpt | 29
vendor/songshenzong/support/src/Traits/Str.php | 295
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodListSpaceRequest.php | 99
vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php | 61
vendor/xaboy/form-builder/src/UI/Iview/Components/Group.php | 77
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php | 38
vendor/topthink/framework/tests/EnvTest.php | 80
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodUpdateSubtitleStatusResponse.php | 119
vendor/topthink/think-captcha/assets/ttfs/6.ttf | 0
vendor/xaboy/form-builder/src/traits/form/FormStyleTrait.php | 63
vendor/qiniu/php-sdk/examples/cdn_get_log_list.php | 29
vendor/topthink/think-orm/src/db/Raw.php | 71
vendor/volcengine/volc-sdk-php/src/Service/Rtc.php | 78
vendor/topthink/think-image/tests/FlipTest.php | 43
vendor/qcloud/cos-sdk-v5/README.md | 264
vendor/pimple/pimple/ext/pimple/tests/017_1.phpt | 17
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php | 3013
vendor/symfony/translation/Translator.php | 510
vendor/topthink/think-migration/src/command/factory/Create.php | 82
vendor/qiniu/php-sdk/tests/Qiniu/Tests/PfopTest.php | 66
vendor/topthink/framework/src/think/middleware/FormTokenCheck.php | 45
vendor/volcengine/volc-sdk-php/examples/ImageX/DemoRequest.php | 19
vendor/qcloud/cos-sdk-v5/sample/abortMultipartUpload.php | 27
vendor/qcloud/cos-sdk-v5/sample/putBucketCors.php | 33
vendor/topthink/think-orm/src/model/Relation.php | 258
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeOriginTopStatusCode.php | 20
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php | 224
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions | 416
vendor/topthink/think-orm/src/db/exception/ModelEventException.php | 19
vendor/topthink/think-queue/src/Queue.php | 77
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php | 92
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php | 92
vendor/topthink/framework/tests/bootstrap.php | 3
vendor/services.php | 9
vendor/topthink/framework/src/think/console/command/Help.php | 70
vendor/symfony/translation/Dumper/QtFileDumper.php | 61
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions | 416
vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php | 63
vendor/topthink/think-orm/src/db/Query.php | 451
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/RelaySourceConfig.php | 133
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php | 481
vendor/volcengine/volc-sdk-php/src/Service/Billing.php | 76
vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php | 134
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/PushURLItem.php | 133
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeContentQuota.php | 15
vendor/symfony/http-foundation/Session/SessionInterface.php | 180
vendor/topthink/think-queue/src/queue/command/ListFailed.php | 110
vendor/volcengine/volc-sdk-php/examples/GameProtect/DemoRiskResult.php | 14
vendor/psr/http-factory/LICENSE | 21
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DeletePullToPushTaskRequest.php | 65
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php | 249
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php | 115
vendor/topthink/think-migration/src/command/migrate/Run.php | 140
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/GPBMetadata/Domain.php | 0
vendor/topthink/think-queue/src/queue/failed/None.php | 63
vendor/qiniu/php-sdk/examples/php-logo.png | 0
vendor/topthink/think-helper/src/helper/Arr.php | 634
vendor/xaboy/form-builder/src/Rule/BaseRule.php | 323
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php | 333
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php | 698
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodUrlUploadResponse.php | 119
vendor/symfony/translation/Tests/fixtures/messages_linear.yml | 2
vendor/topthink/think-image/tests/TestCase.php | 33
vendor/symfony/translation/Formatter/MessageFormatterInterface.php | 30
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/GPBMetadata/VodWorkflow.php | 0
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DescribeRelaySourceResponse.php | 119
vendor/xaboy/form-builder/src/Rule/EmitRule.php | 68
vendor/symfony/var-dumper/Tests/Cloner/VarClonerTest.php | 439
vendor/qiniu/php-sdk/examples/rs_copy.php | 22
vendor/xaboy/form-builder/src/UI/Iview/Traits/DatePickerFactoryTrait.php | 128
vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php | 23
vendor/topthink/think-orm/src/db/exception/DbException.php | 81
vendor/qiniu/php-sdk/examples/rs_bucket_domains.php | 19
vendor/topthink/framework/src/think/exception/FuncNotFoundException.php | 30
vendor/topthink/framework/src/think/console/output/Formatter.php | 198
vendor/topthink/think-orm/src/db/connector/Pgsql.php | 108
vendor/psr/http-client/CHANGELOG.md | 23
vendor/qiniu/php-sdk/docs/rtc/example.php | 42
vendor/volcengine/volc-sdk-php/examples/Live/DemoDescribeForbiddenStreamInfoByPage.php | 32
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodSmartStrategyDataStoreStatus.php | 68
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config | 24
vendor/symfony/translation/Tests/fixtures/extractor/resource.format.engine | 0
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetMediaInfosResponse.php | 119
vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php | 72
vendor/xaboy/form-builder/src/components/Option.php | 76
vendor/xaboy/form-builder/src/UI/Iview/Traits/FormStyleFactoryTrait.php | 55
vendor/symfony/process/phpunit.xml.dist | 30
vendor/ralouphie/getallheaders/.gitignore | 5
vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty | 0
vendor/symfony/psr-http-message-bridge/Tests/Factory/HttpFoundationFactoryTest.php | 250
vendor/symfony/process/Tests/ProcessTest.php | 1557
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodDomainConfigInfo.php | 153
vendor/volcengine/volc-sdk-php/examples/Live/DemoCallback.php | 33
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodQueryUploadTaskInfoResponse.php | 119
vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php | 38
vendor/symfony/psr-http-message-bridge/Tests/Factory/AbstractHttpMessageFactoryTest.php | 229
vendor/symfony/translation/Tests/Dumper/PoFileDumperTest.php | 48
vendor/topthink/think-filesystem/.gitignore | 3
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetRecommendedPosterRequest.php | 65
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php | 81
vendor/qcloud/cos-sdk-v5/sample/headObject.php | 26
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config | 24
vendor/qiniu/php-sdk/tests/Qiniu/Tests/Crc32Test.php | 21
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetSmartStrategyLitePlayInfo.php | 37
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/BucketStyleListener.php | 107
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodAddCallbackSubscriptionResponse.php | 79
vendor/symfony/var-dumper/Test/VarDumperTestTrait.php | 58
vendor/qcloud/cos-sdk-v5/sample/listParts.php | 29
vendor/topthink/framework/src/think/facade/Log.php | 58
vendor/symfony/translation/Tests/Dumper/CsvFileDumperTest.php | 30
vendor/topthink/think-queue/composer.json | 47
vendor/symfony/translation/Tests/Loader/IniFileLoaderTest.php | 51
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/StreamInfoList.php | 405
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodSnapshotData.php | 303
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetAudioEventDetectionForAuditRequest.php | 99
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php | 463
vendor/topthink/framework/tests/SessionTest.php | 225
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php | 369
vendor/xaboy/form-builder/src/traits/component/CallPropsTrait.php | 52
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodApplyUploadInfoRequest.php | 201
vendor/xaboy/form-builder/src/UI/Elm/Traits/HiddenFactoryTrait.php | 31
vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.php | 8
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php | 231
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php | 21
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnDataDetail.php | 27
vendor/topthink/framework/src/tpl/think_exception.tpl | 502
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodQueryUploadResult.php | 99
vendor/symfony/var-dumper/Dumper/ServerDumper.php | 53
vendor/topthink/think-migration/LICENSE | 32
vendor/volcengine/volc-sdk-php/src/Service/Vod/Upload/VodUpload.php | 266
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php | 136
vendor/volcengine/volc-sdk-php/examples/Visual/DemoEnhancePhoto.php | 13
vendor/symfony/http-foundation/FileBag.php | 144
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetSubtitleInfoListResponse.php | 119
vendor/symfony/process/Pipes/WindowsPipes.php | 191
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php | 135
vendor/symfony/translation/Tests/fixtures/withnote.xlf | 22
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/ResumeStreamRequest.php | 179
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php | 59
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php | 1516
vendor/topthink/think-queue/src/queue/command/ForgetFailed.php | 25
vendor/topthink/think-filesystem/src/filesystem/Driver.php | 130
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/SignatureListener.php | 45
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/GetDirectEditProgress.php | 58
vendor/symfony/translation-contracts/LICENSE | 19
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php | 29
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/ListPullToPushTaskResult.php | 109
vendor/topthink/framework/LICENSE.txt | 32
vendor/symfony/var-dumper/Tests/Dumper/ServerDumperTest.php | 95
vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php | 3695
vendor/topthink/framework/src/think/log/driver/File.php | 207
vendor/symfony/var-dumper/LICENSE | 19
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php | 151
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php | 639
vendor/qiniu/php-sdk/examples/rs_delete_after_days.php | 20
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DescribeRecordTaskFileHistoryRequest.php | 327
vendor/symfony/http-foundation/Session/Session.php | 280
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoAddCdnDomain.php | 37
vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php | 45
vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php | 118
vendor/psr/simple-cache/src/CacheException.php | 10
vendor/qiniu/php-sdk/src/Qiniu/Processing/Operation.php | 60
vendor/symfony/translation/Loader/MoFileLoader.php | 145
vendor/workerman/crontab/example/test.php | 16
vendor/psr/log/.gitignore | 1
vendor/topthink/framework/src/think/log/ChannelSet.php | 39
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Service.php | 4360
vendor/symfony/var-dumper/Tests/Caster/IntlCasterTest.php | 297
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodSamplePosterSnapshot.php | 303
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php | 17
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodContentInfo.php | 235
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeOriginTopNrtData.php | 22
vendor/xaboy/form-builder/src/UI/Iview/Components/Cascader.php | 122
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodPlayInfoWithLiveTimeShiftScene.php | 167
vendor/songshenzong/support/.travis.yml | 22
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodCreateVideoClassificationRequest.php | 139
vendor/volcengine/volc-sdk-php/examples/Vod/DemoListCdnStatusData.php | 32
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodDescribeVodSpaceStorageDataRequest.php | 201
vendor/xaboy/form-builder/src/UI/Elm/Components/TreeData.php | 106
vendor/topthink/think-queue/src/queue/job/Redis.php | 121
vendor/symfony/translation/Tests/fixtures/resources-tool-info.xlf | 14
vendor/topthink/think-queue/src/queue/event/JobProcessed.php | 20
vendor/topthink/think-queue/src/queue/command/Table.php | 46
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php | 131
vendor/volcengine/volc-sdk-php/examples/Vod/DemoListCdnPvData.php | 31
vendor/topthink/framework/src/think/log/Channel.php | 286
vendor/topthink/framework/tests/EventTest.php | 134
vendor/symfony/translation/Exception/InvalidArgumentException.php | 21
vendor/symfony/event-dispatcher/Tests/EventTest.php | 97
vendor/topthink/think-queue/src/queue/connector/Sync.php | 74
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/GPBMetadata/VodCommon.php | 0
vendor/topthink/framework/tests/RouteTest.php | 291
vendor/symfony/process/Tests/ExecutableFinderTest.php | 178
vendor/symfony/translation/Command/XliffLintCommand.php | 279
vendor/symfony/translation/composer.json | 60
vendor/xaboy/form-builder/src/traits/form/FormRateTrait.php | 32
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php | 81
vendor/symfony/translation/Dumper/MoFileDumper.php | 82
vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php | 88
vendor/xaboy/form-builder/src/UI/Elm/Components/Rate.php | 69
vendor/qiniu/php-sdk/examples/rs_change_mime.php | 20
vendor/topthink/framework/src/think/exception/ValidateException.php | 37
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php | 52
vendor/symfony/polyfill-php72/LICENSE | 19
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodFrameExtractingOptionForAudit.php | 82
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetBetterFramesForAuditRequest.php | 133
vendor/topthink/framework/src/think/Session.php | 65
vendor/symfony/translation/Loader/IniFileLoader.php | 28
vendor/topthink/framework/src/think/console/input/Option.php | 221
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php | 676
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/CosException.php | 7
vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php | 286
vendor/volcengine/volc-sdk-php/src/Service/Imp/Models/Response/ImpKillJobResponse.php | 65
vendor/symfony/translation/Resources/bin/translation-status.php | 200
vendor/qiniu/php-sdk/tests/Qiniu/Tests/AuthTest.php | 71
vendor/volcengine/volc-sdk-php/src/Service/Base/Models/Base/ResponseError.php | 99
vendor/symfony/http-foundation/ParameterBag.php | 234
vendor/topthink/framework/src/think/console/command/make/stubs/validate.stub | 25
vendor/xaboy/form-builder/src/UI/Elm/Components/Tree.php | 108
vendor/symfony/translation/Tests/DataCollector/TranslationDataCollectorTest.php | 150
vendor/xaboy/form-builder/src/UI/Iview/Components/Poptip.php | 57
vendor/symfony/translation-contracts/TranslatorTrait.php | 255
vendor/symfony/http-foundation/Tests/ApacheRequestTest.php | 93
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/SingleBatchAppend.php | 28
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodFrameDataForAudit.php | 201
vendor/qiniu/php-sdk/tests/bootstrap.php | 43
vendor/xaboy/form-builder/src/UI/Iview/Style/FormStyle.php | 117
vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Stream.php | 99
vendor/topthink/framework/src/think/facade/Cache.php | 48
vendor/symfony/translation/Tests/IdentityTranslatorTest.php | 23
vendor/symfony/translation/Tests/fixtures/empty.ini | 0
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DescribeRelaySourceRequest.php | 65
vendor/volcengine/volc-sdk-php/src/Service/Imp/Models/Request/ImpKillJobRequest.php | 65
vendor/symfony/var-dumper/Dumper/AbstractDumper.php | 211
vendor/topthink/framework/src/think/facade/Console.php | 56
vendor/qiniu/php-sdk/examples/pfop_watermark.php | 52
vendor/topthink/framework/src/think/log/driver/Socket.php | 311
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodDomainCertificateInfo.php | 235
vendor/volcengine/volc-sdk-php/examples/Vod/DemoRetrieveTranscodeResult.php | 28
vendor/xaboy/form-builder/src/Rule/PropsRule.php | 132
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/GPBMetadata/VodPlay.php | 0
vendor/xaboy/form-builder/src/Annotation/Validate/Range.php | 40
vendor/qcloud/cos-sdk-v5/.travis.yml | 6
vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php | 44
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/InvalidArgumentException.php | 5
vendor/topthink/think-queue/src/queue/exception/MaxAttemptsExceededException.php | 10
vendor/qiniu/php-sdk/examples/rs_batch_change_type.php | 34
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetAutomaticSpeechRecognitionForAudit.php | 28
vendor/psr/http-message/src/ResponseInterface.php | 68
vendor/volcengine/volc-sdk-php/examples/VEdit/DemoSubmitDirectEditTask.php | 45
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php | 115
vendor/workerman/workerman/Events/EventInterface.php | 107
vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php | 214
vendor/xaboy/form-builder/src/traits/form/FormCheckBoxTrait.php | 31
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php | 952
vendor/volcengine/volc-sdk-php/examples/Vod/DemoListDomain.php | 27
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetFramesForAuditResult.php | 65
vendor/symfony/http-foundation/Tests/RequestTest.php | 2446
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.expected | 14
vendor/symfony/translation/Tests/fixtures/resources-2.0-clean.xlf | 23
vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php | 1104
vendor/xaboy/form-builder/src/components/Hidden.php | 55
vendor/symfony/polyfill-intl-idn/Info.php | 23
vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php | 31
vendor/topthink/think-image/tests/images/test.jpg | 0
vendor/xaboy/form-builder/src/components/Upload.php | 156
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php | 61
vendor/symfony/psr-http-message-bridge/Tests/Fixtures/UploadedFile.php | 65
vendor/topthink/framework/src/think/console/output/Ask.php | 336
vendor/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php | 172
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodUpdateSpaceUploadConfigResponse.php | 75
vendor/qiniu/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php | 261
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php | 195
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php | 498
vendor/topthink/think-migration/composer.json | 34
vendor/topthink/framework/src/think/response/Jsonp.php | 74
vendor/xaboy/form-builder/src/UI/Iview/Traits/InputNumberFactoryTrait.php | 32
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/GeneratePlayURLRequest.php | 303
vendor/symfony/translation/Tests/fixtures/resources-2.0+intl-icu.xlf | 11
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodVideoStreamMeta.php | 269
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php | 489
vendor/qiniu/php-sdk/examples/put_bucketAccessMode.php | 21
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config | 24
vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php | 254
vendor/symfony/process/LICENSE | 19
vendor/symfony/translation/Dumper/YamlFileDumper.php | 62
vendor/xaboy/form-builder/src/traits/form/FormRadioTrait.php | 32
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DisableDomainRequest.php | 65
vendor/topthink/framework/src/think/Event.php | 272
vendor/symfony/translation/Tests/fixtures/empty-translation.po | 3
vendor/volcengine/volc-sdk-php/examples/Live/DemoDeletePullToPushTask.php | 27
vendor/symfony/polyfill-intl-normalizer/Normalizer.php | 308
vendor/volcengine/volc-sdk-php/src/Service/Sms.php | 60
vendor/topthink/think-orm/composer.json | 28
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetPrivateDrmPlayAuthRequest.php | 167
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/VQScoreID.php | 65
vendor/topthink/think-image/tests/TextTest.php | 58
vendor/topthink/think-queue/src/queue/Connector.php | 163
vendor/symfony/polyfill-intl-idn/bootstrap.php | 141
vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php | 55
vendor/symfony/http-foundation/Tests/File/Fixtures/test | 0
vendor/workerman/workerman/Events/React/ExtEventLoop.php | 27
vendor/topthink/think-orm/src/db/Builder.php | 1303
vendor/symfony/translation/Tests/Util/ArrayConverterTest.php | 74
vendor/symfony/var-dumper/Caster/ClassStub.php | 106
vendor/topthink/think-orm/src/db/Mongo.php | 712
vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php | 38
vendor/qiniu/php-sdk/examples/rsf_list_bucket.php | 46
vendor/topthink/think-queue/README.md | 118
vendor/xaboy/form-builder/src/traits/form/FormUploadTrait.php | 103
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php | 7933 +
vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/resources.dat | 0
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/SubmitDirectEditTaskAsyncResult.php | 65
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php | 350
vendor/topthink/framework/src/think/response/Redirect.php | 98
vendor/volcengine/volc-sdk-php/examples/Vod/DemoUpdateMediaInfo.php | 32
vendor/volcengine/volc-sdk-php/examples/ImageX/DemoGetImageOCR.php | 17
vendor/qcloud/cos-sdk-v5/sample/copyObject.php | 28
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeEdgeTopStatusCode.php | 20
vendor/xaboy/form-builder/src/UI/Iview/Bootstrap.php | 53
vendor/topthink/framework/src/think/session/Store.php | 340
vendor/ralouphie/getallheaders/composer.json | 21
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php | 20
vendor/symfony/process/composer.json | 33
vendor/workerman/workerman/Events/Event.php | 219
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetMediaInfosData.php | 99
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/SingleInfo.php | 16
vendor/volcengine/volc-sdk-php/examples/Vod/DemoListCdnTasks.php | 35
vendor/xaboy/form-builder/src/Factory/Base.php | 30
vendor/qcloud/cos-sdk-v5/sample/deleteBucketLifecycle.php | 25
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/VhostWithDenyConfig.php | 167
vendor/qiniu/php-sdk/examples/rs_pub_domain.php | 19
vendor/topthink/framework/src/think/console/bin/README.md | 1
vendor/topthink/think-queue/src/queue/command/Restart.php | 33
vendor/topthink/framework/src/think/response/File.php | 160
vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php | 122
vendor/volcengine/volc-sdk-php/examples/Vod/DemoListCdnUsageData.php | 32
vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php | 103
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php | 158
vendor/volcengine/volc-sdk-php/src/Service/Vod/Upload/Functions.php | 36
vendor/volcengine/volc-sdk-php/examples/Visual/DemoFaceSwap.php | 14
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodUrlResponseData.php | 65
vendor/topthink/think-queue/src/queue/command/Listen.php | 63
vendor/xaboy/form-builder/LICENSE | 21
vendor/xaboy/form-builder/src/Handle/IviewFormHandle.php | 33
vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/WrapperInterface.php | 60
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/GPBMetadata/PullToPush.php | 0
vendor/symfony/translation/Tests/fixtures/resources-clean.xlf | 25
vendor/symfony/translation-contracts/README.md | 9
vendor/volcengine/volc-sdk-php/src/Service/Live.php | 871
vendor/xaboy/form-builder/src/UI/Iview/Components/Input.php | 91
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetAppInfoResponse.php | 123
vendor/topthink/think-migration/phinx/src/Phinx/Db/Table.php | 674
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php | 64
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php | 86
vendor/psr/log/README.md | 58
vendor/symfony/polyfill-intl-normalizer/README.md | 14
vendor/topthink/framework/src/think/console/command/make/Controller.php | 56
vendor/volcengine/volc-sdk-php/examples/Vod/DemoAddCallbackSubscription.php | 29
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetMediaListData.php | 201
vendor/xaboy/form-builder/src/Annotation/Validate/Min.php | 39
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDeleteResourceTags.php | 22
vendor/xaboy/form-builder/src/Contract/OptionComponentInterface.php | 19
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/GPBMetadata/RecordManage.php | 0
vendor/qiniu/php-sdk/examples/pfop_vframe.php | 46
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php | 147
vendor/volcengine/volc-sdk-php/examples/SecretNumber/DemoBindAXB.php | 22
vendor/qiniu/php-sdk/examples/saveas.php | 28
vendor/xaboy/form-builder/src/UI/Elm/Traits/FormStyleFactoryTrait.php | 55
vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php | 17
vendor/psr/http-factory/src/StreamFactoryInterface.php | 45
vendor/xaboy/form-builder/src/components/Select.php | 129
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php | 45
vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php | 148
vendor/topthink/think-captcha/assets/ttfs/3.ttf | 0
vendor/symfony/process/Tests/KillableProcessWithOutput.php | 25
vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php | 2638
vendor/topthink/think-captcha/LICENSE | 32
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodQueryUploadTaskInfoRequest.php | 65
vendor/qcloud/cos-sdk-v5/sample/putBucketLifecycle.php | 35
vendor/qiniu/php-sdk/examples/get_bucketLifecycleRules.php | 20
vendor/topthink/think-filesystem/src/filesystem/driver/Local.php | 98
vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php | 33
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodDeleteTranscodesResponse.php | 119
vendor/symfony/http-foundation/StreamedResponse.php | 146
vendor/pimple/pimple/ext/pimple/tests/006.phpt | 51
vendor/symfony/http-foundation/Tests/schema/iana-registry.rng | 198
vendor/topthink/think-orm/src/model/relation/OneToOne.php | 332
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodCreateCdnRefreshTaskResponse.php | 119
vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php | 5
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/DeleteResource.php | 12
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodUpdateMediaInfoResponse.php | 75
vendor/topthink/framework/src/think/Config.php | 197
vendor/topthink/think-orm/src/db/CacheItem.php | 209
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php | 406
vendor/symfony/translation/Tests/Writer/TranslationWriterTest.php | 69
vendor/topthink/framework/.travis.yml | 35
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/ForbiddenStreamInfo.php | 99
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php | 699
vendor/pimple/pimple/src/Pimple/Tests/Fixtures/PimpleServiceProvider.php | 54
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/SingleCancel.php | 16
vendor/topthink/think-orm/src/model/relation/BelongsToMany.php | 677
vendor/topthink/framework/src/think/console/output/question/Confirmation.php | 57
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetBetterFramesForAudit.php | 29
vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/packagelist.txt | 2
vendor/topthink/framework/src/think/facade/Session.php | 35
vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php | 45
vendor/psr/log/Psr/Log/NullLogger.php | 30
vendor/topthink/think-image/README.md | 29
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCdnTaskResult.php | 167
vendor/topthink/framework/src/think/exception/FileException.php | 17
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/GPBMetadata/VodCdn.php | 0
vendor/topthink/think-multi-app/src/command/Build.php | 180
vendor/symfony/var-dumper/Tests/Caster/XmlReaderCasterTest.php | 248
vendor/topthink/framework/src/think/Validate.php | 1688
vendor/symfony/translation/Catalogue/TargetOperation.php | 72
vendor/topthink/framework/src/think/console/Output.php | 231
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodUpdateSubtitleStatusRequest.php | 201
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetHlsDecryptionKey.php | 29
vendor/xaboy/form-builder/src/components/Switches.php | 98
vendor/spatie/macroable/LICENSE.md | 21
vendor/xaboy/form-builder/src/UI/Elm/Config.php | 218
vendor/qiniu/php-sdk/examples/upload_tokens.php | 68
vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php | 436
vendor/symfony/translation/Loader/CsvFileLoader.php | 65
vendor/topthink/think-queue/src/queue/event/JobFailed.php | 24
vendor/xaboy/form-builder/src/Rule/CallPropsRule.php | 45
vendor/topthink/think-filesystem/src/facade/Filesystem.php | 33
vendor/qcloud/cos-sdk-v5/sample/listBucket.php | 24
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/UpdatePullToPushTaskRequest.php | 379
vendor/topthink/framework/src/think/console/output/driver/Buffer.php | 52
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Md5Listener.php | 57
vendor/symfony/translation/Tests/Dumper/IniFileDumperTest.php | 29
vendor/pimple/pimple/src/Pimple/Psr11/Container.php | 55
vendor/topthink/framework/src/think/facade/Route.php | 84
vendor/volcengine/volc-sdk-php/examples/Vod/DemoUploadMedia.php | 49
vendor/xaboy/form-builder/src/UI/Elm/Components/Cascader.php | 126
vendor/workerman/crontab/composer.json | 34
vendor/qiniu/php-sdk/.gitignore | 12
vendor/xaboy/form-builder/src/UI/Elm/Validate.php | 20
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/Run.php | 65
vendor/workerman/channel/src/Server.php | 108
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php | 95
vendor/volcengine/volc-sdk-php/examples/Rtc/rtc-openapi-demo/composer.json | 19
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php | 24
vendor/xaboy/form-builder/src/UI/Iview/Components/Switches.php | 93
vendor/qiniu/php-sdk/phpunit.xml.dist | 18
vendor/symfony/translation/Dumper/CsvFileDumper.php | 63
vendor/volcengine/volc-sdk-php/examples/Vod/DemoCreateVideoClassification.php | 30
vendor/symfony/polyfill-intl-idn/Resources/unidata/virama.php | 65
vendor/xaboy/form-builder/src/UI/Elm/Traits/RateFactoryTrait.php | 32
vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php | 127
vendor/volcengine/volc-sdk-php/src/Service/Imp/Models/Request/ImpRetrieveJobRequest.php | 65
vendor/topthink/think-orm/src/db/concern/ParamsBind.php | 106
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodListSnapshotsResponse.php | 119
vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php | 375
vendor/xaboy/form-builder/src/UI/Iview/Traits/CheckBoxFactoryTrait.php | 31
vendor/topthink/think-migration/src/Factory.php | 313
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetAutomaticSpeechRecognitionForAuditResult.php | 68
vendor/topthink/framework/src/think/console/input/Argument.php | 138
vendor/symfony/process/InputStream.php | 90
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodSubmitDirectEditTaskAsyncRequest.php | 239
vendor/psr/container/src/NotFoundExceptionInterface.php | 13
vendor/topthink/framework/src/think/middleware/SessionInit.php | 80
vendor/topthink/think-migration/src/Command.php | 89
vendor/symfony/translation/Tests/DependencyInjection/fixtures/ControllerArguments.php | 16
vendor/symfony/event-dispatcher/EventDispatcher.php | 198
vendor/volcengine/volc-sdk-php/examples/Live/DemoAuth.php | 66
vendor/volcengine/volc-sdk-php/examples/Vod/DemoDeleteVideoClassification.php | 28
vendor/qiniu/php-sdk/examples/put_bucketQuota.php | 22
vendor/topthink/think-migration/src/command/Migrate.php | 146
vendor/xaboy/form-builder/src/view/formScript.php | 72
vendor/symfony/var-dumper/Tests/Caster/DateCasterTest.php | 390
vendor/pimple/pimple/ext/pimple/pimple.c | 1114
vendor/topthink/think-migration/phinx/LICENSE | 9
vendor/volcengine/volc-sdk-php/examples/Imp/job/DemoSubmitJob.php | 41
vendor/symfony/http-foundation/File/UploadedFile.php | 268
vendor/volcengine/volc-sdk-php/examples/Live/DemoDescribeLiveStreamState.php | 30
vendor/symfony/translation/Loader/LoaderInterface.php | 38
vendor/qiniu/php-sdk/tests/Qiniu/Tests/HttpTest.php | 43
vendor/volcengine/volc-sdk-php/examples/Sts/DemoAssumeRole.php | 19
vendor/topthink/think-queue/src/queue/event/JobProcessing.php | 20
vendor/topthink/think-view/composer.json | 20
vendor/topthink/think-captcha/composer.json | 30
vendor/topthink/framework/src/think/service/ValidateService.php | 31
vendor/symfony/var-dumper/Caster/EnumStub.php | 30
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php | 65
vendor/topthink/framework/src/think/console/Input.php | 465
vendor/volcengine/volc-sdk-php/examples/Live/DemoDescribeLiveStreamInfoByPage.php | 34
vendor/topthink/framework/src/think/console/command/make/stubs/controller.stub | 85
vendor/symfony/process/ProcessUtils.php | 69
vendor/qiniu/php-sdk/examples/rsf_list_files.php | 38
vendor/workerman/workerman/Lib/Timer.php | 179
vendor/songshenzong/support/.gitignore | 7
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php | 668
vendor/symfony/http-foundation/JsonResponse.php | 220
vendor/xaboy/form-builder/src/UI/Elm/Components/Input.php | 111
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetMLFramesForAuditResponse.php | 119
vendor/topthink/framework/src/think/console/command/RunServer.php | 72
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/VQScoreTaskInfo.php | 167
vendor/psr/http-client/composer.json | 27
vendor/topthink/think-helper/src/Collection.php | 651
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodApplyUploadInfoResult.php | 75
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/UpdateRelaySourceRequest.php | 201
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetWorkflowExecutionStatusResponse.php | 119
vendor/xaboy/form-builder/src/UI/Elm/Components/Slider.php | 75
vendor/songshenzong/support/src/StringsServiceProvider.php | 37
vendor/psr/http-factory/src/ResponseFactoryInterface.php | 18
vendor/xaboy/form-builder/src/components/Radio.php | 77
vendor/symfony/translation/Interval.php | 112
vendor/qiniu/php-sdk/examples/get_corsRules.php | 20
vendor/symfony/translation/Exception/ExceptionInterface.php | 21
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetSmartStrategyLitePlayInfoResult.php | 177
vendor/symfony/var-dumper/Tests/Dumper/CliDumperTest.php | 534
vendor/topthink/think-captcha/assets/ttfs/1.ttf | 0
vendor/topthink/think-captcha/assets/zhttfs/1.ttf | 0
vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php | 100
vendor/topthink/think-orm/src/db/Where.php | 182
vendor/topthink/framework/src/think/cache/driver/File.php | 304
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Command.php | 39
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php | 240
vendor/topthink/framework/src/think/Db.php | 117
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoUpdateResourceTags.php | 23
vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php | 96
vendor/symfony/translation/Tests/fixtures/empty.csv | 0
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DescribeClosedStreamInfoByPageRequest.php | 411
vendor/symfony/translation/Extractor/PhpExtractor.php | 278
vendor/pimple/pimple/README.rst | 326
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php | 100
vendor/topthink/think-factory/.gitignore | 4
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/ListVQScoreTaskResponse.php | 119
vendor/topthink/framework/src/think/middleware/LoadLangPack.php | 118
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetWorkflowExecutionStatusRequest.php | 65
vendor/symfony/translation/Loader/IcuResFileLoader.php | 91
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/Inspection.php | 109
vendor/songshenzong/support/src/Strings.php | 16
vendor/xaboy/form-builder/src/Contract/FormComponentInterface.php | 20
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DescribeLiveStreamInfoByPageResponse.php | 119
vendor/qiniu/php-sdk/examples/rs_batch_delete.php | 28
vendor/symfony/translation/Tests/fixtures/resname.xlf | 22
vendor/pimple/pimple/ext/pimple/.gitignore | 30
vendor/symfony/var-dumper/Tests/Caster/ExceptionCasterTest.php | 247
vendor/xaboy/form-builder/src/UI/Iview/Components/InputNumber.php | 51
vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php | 876
vendor/symfony/translation/Tests/fixtures/resourcebundle/corrupted/resources.dat | 1
vendor/topthink/think-image/LICENSE | 201
vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.expected | 9
vendor/topthink/think-captcha/.gitignore | 3
vendor/xaboy/form-builder/src/components/Rate.php | 63
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/StageDetail.php | 315
vendor/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php | 375
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/ClosedStream.php | 277
vendor/pimple/pimple/ext/pimple/tests/013.phpt | 33
vendor/spatie/macroable/composer.json | 40
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetWorkflowExecution | 26
vendor/qiniu/php-sdk/examples/get_bucketinfo.php | 20
vendor/symfony/translation/Tests/DependencyInjection/TranslationPathsPassTest.php | 89
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodSubmitDirectEditTaskAsyncResponse.php | 123
vendor/symfony/var-dumper/Caster/DsCaster.php | 68
vendor/topthink/think-orm/src/facade/Db.php | 86
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/ServiceResponseException.php | 189
vendor/qiniu/php-sdk/examples/upload_and_pfop.php | 38
vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/en.txt | 3
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php | 82
vendor/psr/simple-cache/src/CacheInterface.php | 114
vendor/psr/http-factory/composer.json | 35
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetPlayInfoResponse.php | 119
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCreateVideoClassificationData.php | 58
vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php | 45
vendor/topthink/think-filesystem/composer.json | 33
vendor/symfony/var-dumper/Caster/DoctrineCaster.php | 60
vendor/xaboy/form-builder/src/UI/Elm/Traits/TimePickerFactoryTrait.php | 61
vendor/psr/cache/composer.json | 25
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/VQScoreTaskListInfo.php | 167
vendor/symfony/translation/Util/ArrayConverter.php | 99
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php | 30
vendor/volcengine/volc-sdk-php/examples/Imp/job/DemoKillJob.php | 31
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodCreateSpaceRequest.php | 205
vendor/symfony/translation/README.md | 13
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeIPInfo.php | 19
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/Clip.php | 99
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php | 629
vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php | 40
vendor/symfony/translation-contracts/composer.json | 33
vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php | 35
vendor/qcloud/cos-sdk-v5/sample/putBucketAcl.php | 41
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php | 274
vendor/qiniu/php-sdk/src/Qiniu/Config.php | 140
vendor/volcengine/volc-sdk-php/composer.json | 25
vendor/topthink/think-image/tests/images/test.ttf | 0
vendor/symfony/http-foundation/Tests/File/Fixtures/case-sensitive-mime-type.xlsm | 0
vendor/symfony/polyfill-intl-idn/Idn.php | 915
vendor/volcengine/volc-sdk-php/src/Service/Vod/Upload/SnapshotTimeInput.php | 18
vendor/qiniu/php-sdk/src/Qiniu/Storage/ResumeUploader.php | 169
vendor/symfony/event-dispatcher/README.md | 15
vendor/topthink/framework/src/think/console/input/Definition.php | 375
vendor/topthink/think-orm/src/model/concern/OptimLock.php | 85
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodListCdnTasksResponse.php | 119
vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php | 101
vendor/qiniu/php-sdk/CONTRIBUTING.md | 30
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/CommitResourceUpload.php | 19
vendor/symfony/translation/Exception/LogicException.php | 21
vendor/xaboy/form-builder/src/UI/Iview/Traits/CascaderFactoryTrait.php | 80
vendor/topthink/think-migration/phinx/src/Phinx/Seed/AbstractSeed.php | 215
vendor/xaboy/form-builder/images/demo02.jpg | 0
vendor/topthink/framework/src/think/route/Domain.php | 183
vendor/qiniu/php-sdk/examples/rs_buckets.php | 19
vendor/xaboy/form-builder/src/Template/createScript.min.php | 1
vendor/topthink/framework/src/think/console/command/Clear.php | 85
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodDescribeIPInfoRequest.php | 65
vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php | 46
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodRetrieveTranscodeResultResponse.php | 119
vendor/qcloud_sts/qcloud-sts-sdk/README.md | 61
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/RestartPullToPushTaskRequest.php | 65
vendor/topthink/framework/tests/MiddlewareTest.php | 108
vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php | 161
vendor/topthink/think-helper/src/contract/Jsonable.php | 8
vendor/xaboy/form-builder/demo/demo.php | 34
vendor/topthink/think-image/tests/images/test.png | 0
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnConfig.php | 19
vendor/symfony/var-dumper/Tests/Fixtures/xml_reader.xml | 10
vendor/qcloud/cos-sdk-v5/sample/listMultipartUploads.php | 31
vendor/workerman/channel/test/server.php | 22
vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php | 1145
vendor/topthink/think-orm/src/db/concern/WhereQuery.php | 532
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCommonConfigInfo.php | 133
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php | 759
vendor/qiniu/php-sdk/examples/rs_batch_copy.php | 36
vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php | 122
vendor/qiniu/php-sdk/examples/get_bucketList.php | 20
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetPlayInfo.php | 42
vendor/psr/http-factory/.pullapprove.yml | 7
vendor/xaboy/form-builder/src/Contract/AnnotationInterface.php | 29
vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php | 365
vendor/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php | 23
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodUpdateVideoClassificationResponse.php | 75
vendor/volcengine/volc-sdk-php/examples/Live/DemoRecordPreset.php | 59
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/GPBMetadata/RequestVod.php | 398
vendor/qiniu/php-sdk/src/Qiniu/Rtc/AppClient.php | 209
vendor/symfony/process/Tests/SignalListener.php | 21
vendor/topthink/framework/src/think/facade/Cookie.php | 40
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodCreateVideoClassificationResponse.php | 119
vendor/topthink/framework/tests/DispatchTest.php | 32
vendor/xaboy/form-builder/src/Contract/ConfigInterface.php | 44
vendor/symfony/process/Pipes/UnixPipes.php | 153
vendor/topthink/think-queue/src/config.php | 38
vendor/volcengine/volc-sdk-php/examples/SecretNumber/DemoQuerySubscriptionForList.php | 20
vendor/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php | 5
vendor/xaboy/form-builder/src/UI/Iview/Components/Hidden.php | 57
vendor/xaboy/form-builder/src/Handle/ElmFormHandle.php | 30
vendor/topthink/framework/src/think/view/driver/Php.php | 191
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php | 205
vendor/topthink/framework/src/think/console/command/make/stubs/model.stub | 14
vendor/qiniu/php-sdk/examples/rs_change_type.php | 20
vendor/symfony/var-dumper/Caster/SplCaster.php | 220
vendor/symfony/translation/Tests/Dumper/YamlFileDumperTest.php | 45
vendor/topthink/framework/src/think/console/command/make/stubs/middleware.stub | 19
vendor/volcengine/volc-sdk-php/examples/Billing/DemoListBillOverviewByProd.php | 23
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/KillStreamRequest.php | 145
vendor/topthink/think-orm/src/db/Connection.php | 332
vendor/qcloud_sts/qcloud-sts-sdk/src/Sts.php | 166
vendor/xaboy/form-builder/src/view/form.php | 17
vendor/volcengine/volc-sdk-php/examples/DemoSign.php | 23
vendor/volcengine/volc-sdk-php/examples/SecretNumber/DemoSelectNumberAndBindAXB.php | 19
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodStartWorkflowResponse.php | 123
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected | 24
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config | 24
vendor/volcengine/volc-sdk-php/examples/Live/DemoDeleteRelaySourceV2.php | 28
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php | 93
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php | 548
vendor/qiniu/php-sdk/src/Qiniu/Http/Response.php | 176
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DescribeDomainResponse.php | 119
vendor/topthink/think-orm/src/db/builder/Mysql.php | 421
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnOriginData.php | 23
vendor/qcloud/cos-sdk-v5/sample/putObject.php | 41
vendor/volcengine/volc-sdk-php/examples/Vod/DemoUploadMediaByUrl.php | 27
vendor/volcengine/volc-sdk-php/examples/ImageX/DemoGetUploadToken.php | 20
vendor/qiniu/php-sdk/examples/put_bucketMaxAge.php | 21
vendor/volcengine/volc-sdk-php/examples/Vod/DemoDescribeVodSpaceStorageData.php | 31
vendor/qiniu/php-sdk/examples/update_bucketLifecycleRule.php | 30
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/RC4.php | 61
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php | 314
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/RichText.php | 170
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php | 79
vendor/symfony/translation/Resources/data/parents.json | 136
vendor/topthink/framework/src/think/service/ModelService.php | 53
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 1275
vendor/xaboy/form-builder/src/traits/component/ComponentOptionsTrait.php | 81
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php | 60
vendor/topthink/think-image/tests/tmp/.gitignore | 2
vendor/qiniu/php-sdk/examples/rs_change_status.php | 20
vendor/topthink/think-orm/src/model/concern/Attribute.php | 651
vendor/symfony/translation/Resources/schemas/xliff-core-2.0.xsd | 411
vendor/topthink/think-orm/src/db/builder/Mongo.php | 675
vendor/pimple/pimple/ext/pimple/tests/bench.phpb | 51
vendor/symfony/polyfill-php72/README.md | 27
vendor/psr/cache/README.md | 9
vendor/symfony/polyfill-intl-idn/Resources/unidata/deviation.php | 8
vendor/qiniu/php-sdk/tests/Qiniu/Tests/BucketTest.php | 227
vendor/volcengine/volc-sdk-php/src/Service/Imp/Imp.php | 146
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php | 120
vendor/xaboy/form-builder/src/UI/Iview/Traits/UploadFactoryTrait.php | 110
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php | 490
vendor/topthink/think-queue/src/queue/connector/Database.php | 220
vendor/topthink/think-orm/src/model/relation/MorphOne.php | 280
vendor/symfony/http-foundation/LICENSE | 19
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodASRInfoForAudit.php | 235
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetSpaceDetailResponse.php | 119
vendor/xaboy/form-builder/src/Contract/StyleInterface.php | 19
vendor/workerman/workerman/Connection/AsyncUdpConnection.php | 209
vendor/symfony/var-dumper/Cloner/Cursor.php | 43
vendor/volcengine/volc-sdk-php/examples/SecretNumber/DemoClick2CallLite.php | 18
vendor/qiniu/php-sdk/src/Qiniu/Zone.php | 47
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions | 416
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php | 92
vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php | 52
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php | 81
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCdnTopAccessUrlElement.php | 133
vendor/psr/http-factory/src/ServerRequestFactoryInterface.php | 24
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php | 567
vendor/topthink/think-orm/src/model/Pivot.php | 53
vendor/psr/log/Psr/Log/LoggerAwareInterface.php | 18
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/MultipartUpload.php | 132
vendor/workerman/workerman/.gitignore | 6
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodAudioInfoForAudit.php | 133
vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php | 204
vendor/topthink/think-captcha/assets/bgs/8.jpg | 0
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetSmartStrategyLitePlayInfoResponse.php | 123
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php | 510
vendor/volcengine/volc-sdk-php/src/Service/AdBlocker.php | 68
vendor/qiniu/php-sdk/src/Qiniu/Etag.php | 76
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodAEDEventItemForAudit.php | 133
vendor/symfony/translation/Tests/fixtures/invalid-xml-resources.xlf | 23
vendor/topthink/framework/tests/ViewTest.php | 127
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetSubtitleInfoListData.php | 235
vendor/symfony/translation/Tests/TranslatorTest.php | 612
vendor/topthink/think-queue/src/queue/event/WorkerStopping.php | 24
vendor/qiniu/php-sdk/src/Qiniu/Storage/BucketManager.php | 1107
vendor/topthink/think-queue/tests/TestCase.php | 23
vendor/psr/http-message/composer.json | 26
vendor/xaboy/form-builder/src/UI/Iview/Components/Select.php | 73
vendor/topthink/framework/README.md | 84
vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php | 22
vendor/topthink/framework/src/think/event/HttpEnd.php | 19
vendor/topthink/think-multi-app/src/command/Clear.php | 66
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodSpaceUploadConfigKey.php | 59
vendor/topthink/think-orm/src/Paginator.php | 518
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/GPBMetadata/VodSmartStrategy.php | 0
vendor/pimple/pimple/ext/pimple/php_pimple.h | 137
vendor/qiniu/php-sdk/examples/delete_bucketLifecycleRule.php | 21
vendor/topthink/think-migration/src/command/migrate/Rollback.php | 146
vendor/psr/http-factory/src/UploadedFileFactoryInterface.php | 34
vendor/symfony/polyfill-php72/composer.json | 31
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodASRUtteranceWordForAudit.php | 167
vendor/volcengine/volc-sdk-php/examples/Live/DemoCert.php | 58
vendor/topthink/think-migration/src/command/seed/Create.php | 83
vendor/xaboy/form-builder/src/Helper.php | 64
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VisualQuality.php | 235
vendor/symfony/var-dumper/Caster/ArgsStub.php | 80
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions | 408
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/PauseTask.php | 14
vendor/symfony/translation/Tests/Loader/LocalizedTestCase.php | 24
vendor/qiniu/php-sdk/src/Qiniu/Cdn/CdnManager.php | 192
vendor/pimple/pimple/ext/pimple/README.md | 12
vendor/symfony/var-dumper/Caster/SymfonyCaster.php | 51
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodDescribeVodDomainBandwidthDataResponse.php | 119
vendor/symfony/http-foundation/ServerBag.php | 102
vendor/topthink/think-factory/LICENSE | 201
vendor/pimple/pimple/src/Pimple/ServiceIterator.php | 69
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodListVideoClassificationsResponse.php | 119
vendor/topthink/think-migration/phinx/src/Phinx/Migration/Migration.template.php.dist | 32
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetAudioEventDetectionForAudit.php | 28
vendor/qiniu/php-sdk/tests/Qiniu/Tests/EtagTest.php | 52
vendor/volcengine/volc-sdk-php/examples/Vod/DemoDeleteMedia.php | 28
vendor/topthink/framework/src/think/facade/Middleware.php | 42
vendor/symfony/translation/Extractor/ExtractorInterface.php | 38
vendor/workerman/workerman/Events/Libevent.php | 227
vendor/volcengine/volc-sdk-php/examples/Live/DemoUpdateRelaySourceV2.php | 31
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetAudioEventDetectionForAuditResponse.php | 119
vendor/xaboy/form-builder/src/UI/Elm/Traits/SelectFactoryTrait.php | 48
vendor/volcengine/volc-sdk-php/examples/Billing/DemoListBill.php | 24
vendor/xaboy/form-builder/images/demo04.jpg | 0
vendor/psr/simple-cache/composer.json | 25
vendor/symfony/var-dumper/Tests/Fixtures/NotLoadableClass.php | 7
vendor/symfony/event-dispatcher/LICENSE | 19
vendor/topthink/think-image/composer.json | 26
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php | 89
vendor/pimple/pimple/composer.json | 29
vendor/symfony/translation/Tests/fixtures/empty.yml | 0
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodListCdnTopAccessUrlResult.php | 65
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetSpaceDetailRequest.php | 65
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php | 157
vendor/symfony/translation/Tests/fixtures/malformed.json | 3
vendor/topthink/framework/src/think/cache/driver/Memcached.php | 221
vendor/topthink/think-orm/src/model/Collection.php | 250
vendor/symfony/translation/Tests/MessageSelectorTest.php | 140
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/StopTask.php | 14
vendor/volcengine/volc-sdk-php/src/Service/Notify.php | 324
vendor/topthink/think-queue/phpunit.xml.dist | 30
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php | 449
vendor/symfony/http-foundation/ExpressionRequestMatcher.php | 47
vendor/topthink/think-orm/src/db/connector/Mongo.php | 1167
vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php | 77
vendor/topthink/think-migration/src/command/Seed.php | 73
vendor/xaboy/form-builder/src/interfaces/FormComponentInterFace.php | 25
vendor/topthink/think-migration/src/FactoryBuilder.php | 437
vendor/topthink/think-image/src/image/gif/Decoder.php | 207
vendor/symfony/translation/Tests/MessageCatalogueTest.php | 243
vendor/symfony/translation/Tests/Extractor/PhpExtractorTest.php | 101
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetOriginalPlayInfoResponse.php | 119
vendor/volcengine/volc-sdk-php/src/Service/ImageX.php | 433
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCdnAccessLogInfo.php | 99
vendor/volcengine/volc-sdk-php/examples/Vod/DemoListVideoClassifications.php | 28
vendor/qiniu/php-sdk/examples/rs_move.php | 22
vendor/xaboy/form-builder/src/components/Tree.php | 124
vendor/pimple/pimple/ext/pimple/tests/015.phpt | 17
vendor/symfony/process/PhpProcess.php | 75
vendor/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php | 43
vendor/topthink/think-queue/src/queue/Queueable.php | 46
vendor/xaboy/form-builder/src/Driver/FormOptionsComponent.php | 29
vendor/symfony/translation/Tests/Loader/XliffFileLoaderTest.php | 331
vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php | 131
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCdnStatisticsData.php | 139
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeEdgeStatisticalData.php | 26
vendor/symfony/psr-http-message-bridge/.gitignore | 4
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetAudioInfoForAuditResponse.php | 119
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodThumbInfo.php | 303
vendor/symfony/translation/Tests/Command/XliffLintCommandTest.php | 218
vendor/topthink/think-image/phpunit.xml | 20
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodListDomainResponse.php | 123
vendor/xaboy/form-builder/src/UI/Iview/Components/Frame.php | 83
vendor/workerman/workerman/Events/Select.php | 340
vendor/topthink/framework/src/think/File.php | 204
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodUrlUploadRequest.php | 99
vendor/xaboy/form-builder/src/traits/form/FormFrameTrait.php | 127
vendor/psr/http-factory/src/UriFactoryInterface.php | 17
vendor/xaboy/form-builder/src/UI/Elm/Components/ColorPicker.php | 47
vendor/volcengine/volc-sdk-php/examples/Vod/DemoListSpace.php | 28
vendor/qcloud/cos-sdk-v5/sample/getBucketLifecycle.php | 25
vendor/symfony/var-dumper/Cloner/VarCloner.php | 295
vendor/symfony/var-dumper/Exception/ThrowingCasterException.php | 26
vendor/topthink/think-migration/src/Service.php | 51
vendor/symfony/translation/Catalogue/AbstractOperation.php | 157
vendor/pimple/pimple/LICENSE | 19
vendor/topthink/framework/src/think/session/driver/Cache.php | 50
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodPrivateDrmPlayAuthInfo.php | 99
vendor/qiniu/php-sdk/examples/delete_bucket.php | 20
vendor/symfony/psr-http-message-bridge/README.md | 20
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReader.php | 133
vendor/topthink/think-image/tests/images/test.gif | 0
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeAccountingData.php | 22
vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceArguments.php | 12
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetAutomaticSpeechRecognitionForAuditResponse.php | 119
vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php | 918
vendor/topthink/think-orm/src/model/relation/MorphToMany.php | 458
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetSpaceDetail.php | 27
vendor/topthink/think-orm/src/db/ConnectionInterface.php | 190
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php | 253
vendor/topthink/framework/src/think/exception/HttpResponseException.php | 37
vendor/xaboy/form-builder/src/UI/Iview/Traits/TimePickerFactoryTrait.php | 61
vendor/qiniu/php-sdk/src/Qiniu/Sms/Sms.php | 337
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/ResumeStreamResponse.php | 75
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodDomainInstanceInfos.php | 99
vendor/volcengine/volc-sdk-php/src/Service/BusinessSecurity.php | 489
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/DescribeDenyConfigResult.php | 65
vendor/volcengine/volc-sdk-php/examples/SecretNumber/DemoUpdateAXN.php | 19
vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterFactory.php | 181
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php | 8
vendor/symfony/polyfill-intl-idn/README.md | 12
vendor/volcengine/volc-sdk-php/examples/AdBlocker/DemoAdBlock.php | 12
vendor/volcengine/volc-sdk-php/examples/Vod/DemoUpdateSpace.php | 30
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodFileSubtitleInfo.php | 99
vendor/qcloud/cos-sdk-v5/sample/headBucket.php | 25
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetAllPlayInfoResponse.php | 119
vendor/songshenzong/support/src/StringsFacade.php | 21
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt | 20
vendor/workerman/crontab/src/Parser.php | 171
vendor/symfony/var-dumper/Tests/Command/Descriptor/CliDescriptorTest.php | 173
vendor/symfony/translation/Formatter/ChoiceMessageFormatterInterface.php | 32
vendor/volcengine/volc-sdk-php/examples/Visual/DemoHumanSegment.php | 13
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/CDNSnapshotHistoryInfo.php | 109
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCdnAccessLogElement.php | 201
vendor/topthink/think-captcha/assets/bgs/6.jpg | 0
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/GPBMetadata/VodMedia.php | 0
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/TranscodeAudioOverride.php | 136
vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php | 137
vendor/volcengine/volc-sdk-php/examples/Vod/DemoUpdateSpaceUploadConfig.php | 29
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config | 24
vendor/symfony/var-dumper/.gitignore | 3
vendor/topthink/framework/src/think/console/command/make/stubs/listener.stub | 17
vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php | 113
vendor/xaboy/form-builder/src/UI/Iview/Traits/HiddenFactoryTrait.php | 31
vendor/xaboy/form-builder/src/Annotation/AnnotationReader.php | 122
vendor/symfony/http-foundation/Tests/RequestStackTest.php | 70
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DeleteDenyConfigResponse.php | 75
vendor/topthink/framework/src/think/console/command/ServiceDiscover.php | 52
vendor/spatie/macroable/CONTRIBUTING.md | 55
vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php | 126
vendor/topthink/think-filesystem/phpunit.xml.dist | 25
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php | 873
vendor/symfony/var-dumper/Caster/DateCaster.php | 122
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php | 159
vendor/symfony/var-dumper/Tests/Fixtures/dump_server.php | 38
vendor/symfony/translation/Formatter/IntlFormatterInterface.php | 27
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodListSnapshotsRequest.php | 58
vendor/topthink/think-queue/tests/QueueTest.php | 49
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php | 16
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodClassification.php | 257
vendor/volcengine/volc-sdk-php/src/Service/GameProduct.php | 73
vendor/qiniu/php-sdk/examples/image_url_builder.php | 74
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/PlayURL.php | 167
vendor/workerman/channel/src/Client.php | 275
vendor/xaboy/form-builder/src/UI/Iview/Components/TreeData.php | 106
vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.php | 12
vendor/symfony/process/Tests/ErrorProcessInitiator.php | 36
vendor/symfony/polyfill-intl-idn/composer.json | 45
vendor/symfony/translation/Tests/Loader/IcuDatFileLoaderTest.php | 64
vendor/topthink/think-orm/src/model/relation/BelongsTo.php | 331
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php | 163
vendor/topthink/think-captcha/src/facade/Captcha.php | 18
vendor/xaboy/form-builder/src/components/DatePicker.php | 172
vendor/topthink/think-helper/README.md | 33
vendor/symfony/translation/Tests/fixtures/escaped-id.po | 8
vendor/topthink/think-template/README.md | 70
vendor/symfony/process/ExecutableFinder.php | 88
vendor/volcengine/volc-sdk-php/src/Base/V4Curl.php | 330
vendor/spatie/macroable/.styleci.yml | 4
vendor/symfony/translation/Tests/DependencyInjection/TranslationExtractorPassTest.php | 66
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodMediaBasicInfo.php | 347
vendor/xaboy/form-builder/src/Annotation/Validate/Len.php | 39
vendor/topthink/think-queue/src/queue/Listener.php | 162
vendor/songshenzong/support/composer.json | 60
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodDescribeVodSpaceStorageDataResponse.php | 105
vendor/qiniu/php-sdk/examples/rs_fetch.php | 34
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DescribeForbiddenStreamInfoByPageRequest.php | 255
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoSubmitBlockTask.php | 20
vendor/xaboy/form-builder/src/components/Frame.php | 119
vendor/topthink/think-queue/src/queue/command/stubs/jobs.stub | 20
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php | 104
vendor/pimple/pimple/ext/pimple/tests/002.phpt | 15
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodUpdateVideoClassificationRequest.php | 112
vendor/symfony/var-dumper/Caster/IntlCaster.php | 170
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoStartCdnDomain.php | 19
vendor/volcengine/volc-sdk-php/src/Base/Model/SignParam.php | 150
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodUpdateMediaPublishStatusRequest.php | 99
vendor/topthink/framework/src/think/console/Table.php | 300
vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.expected | 10
vendor/topthink/framework/src/think/facade/App.php | 59
vendor/songshenzong/support/src/Time.php | 72
vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php | 93
vendor/qiniu/php-sdk/CHANGELOG.md | 105
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php | 8
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoSubmitRefreshTask.php | 20
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/VQScoreInfo.php | 337
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config | 8
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php | 289
vendor/symfony/event-dispatcher/GenericEvent.php | 175
vendor/symfony/psr-http-message-bridge/Tests/Factory/PsrHttpFactoryTest.php | 29
vendor/volcengine/volc-sdk-php/src/Service/Imp/Models/Request/ImpSubmitJobRequest.php | 167
vendor/symfony/translation/Catalogue/OperationInterface.php | 77
vendor/symfony/http-foundation/phpunit.xml.dist | 31
vendor/qiniu/php-sdk/examples/persistent_fop_status.php | 18
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php | 22
vendor/symfony/translation/Exception/RuntimeException.php | 21
vendor/psr/log/Psr/Log/LoggerTrait.php | 142
vendor/symfony/translation/Writer/TranslationWriterInterface.php | 34
vendor/volcengine/volc-sdk-php/examples/Cdn/init.php | 21
vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterInterface.php | 510
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodCreateSpaceResponse.php | 79
vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php | 144
vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php | 44
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php | 77
vendor/psr/http-message/src/StreamInterface.php | 158
vendor/symfony/http-foundation/AcceptHeader.php | 168
vendor/topthink/think-template/LICENSE | 201
vendor/topthink/think-queue/src/queue/command/FlushFailed.php | 21
vendor/pimple/pimple/CHANGELOG | 59
vendor/volcengine/volc-sdk-php/src/Service/Iam.php | 54
vendor/xaboy/form-builder/src/Template/createScript.php | 94
vendor/songshenzong/support/tests/OSTest.php | 46
vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/ForeignKey.php | 252
vendor/topthink/framework/src/think/Env.php | 201
vendor/volcengine/volc-sdk-php/examples/SecretNumber/DemoUpdateAXB.php | 19
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/GPBMetadata/RequestLive.php | 212
vendor/topthink/framework/src/think/facade/Config.php | 37
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/ResumeTask.php | 13
vendor/symfony/http-foundation/RequestMatcherInterface.php | 27
vendor/topthink/framework/tests/ConfigTest.php | 46
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php | 20
vendor/topthink/framework/src/think/console/Command.php | 504
vendor/symfony/translation/Tests/Loader/IcuResFileLoaderTest.php | 51
vendor/symfony/http-foundation/CHANGELOG.md | 165
vendor/qiniu/php-sdk/src/Qiniu/Http/Request.php | 18
vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Column.php | 550
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/TranscodeResult.php | 143
vendor/symfony/translation/Tests/DependencyInjection/TranslationPassTest.php | 122
vendor/topthink/think-migration/phinx/src/Phinx/Migration/IrreversibleMigrationException.php | 39
vendor/topthink/think-captcha/src/CaptchaController.php | 20
vendor/topthink/think-helper/.gitignore | 3
vendor/symfony/var-dumper/Caster/LinkStub.php | 108
vendor/xaboy/form-builder/src/Form/IviewForm.php | 24
vendor/qiniu/php-sdk/composer.json | 26
vendor/psr/http-message/LICENSE | 19
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodStartWorkflowRequest.php | 249
vendor/symfony/var-dumper/Tests/Caster/RedisCasterTest.php | 70
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodAddCallbackSubscriptionRequest.php | 145
vendor/psr/simple-cache/README.md | 8
vendor/xaboy/form-builder/src/UI/Iview/Traits/TreeFactoryTrait.php | 74
vendor/volcengine/volc-sdk-php/examples/Vod/DemoSubmitDirectEditTaskAsync.php | 33
vendor/psr/http-factory/src/RequestFactoryInterface.php | 18
vendor/qiniu/php-sdk/examples/rs_prefetch.php | 17
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodListDomainRequest.php | 69
vendor/qiniu/php-sdk/docs/sms/example.php | 70
vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php | 1096
vendor/topthink/framework/src/think/initializer/BootService.php | 26
vendor/topthink/framework/src/think/middleware/CheckRequestCache.php | 183
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php | 74
vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php | 229
vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php | 34
vendor/topthink/think-orm/src/model/relation/HasMany.php | 367
vendor/symfony/process/.gitignore | 3
vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Response.php | 45
vendor/symfony/var-dumper/Resources/bin/var-dump-server | 63
vendor/qiniu/php-sdk/README.md | 75
vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php | 90
vendor/topthink/think-captcha/assets/bgs/3.jpg | 0
vendor/xaboy/form-builder/src/Annotation/Validate/Max.php | 39
vendor/topthink/think-factory/src/Factory.php | 115
vendor/volcengine/volc-sdk-php/examples/Vod/DemoUpdateSubtitleInfo.php | 32
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodUploadAddress.php | 167
vendor/symfony/process/PhpExecutableFinder.php | 101
vendor/symfony/var-dumper/Caster/RedisCaster.php | 150
vendor/xaboy/form-builder/src/components/TimePicker.php | 155
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions | 416
vendor/topthink/think-queue/src/queue/FailedJob.php | 47
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetMediaList.php | 36
vendor/symfony/http-foundation/Tests/File/FileTest.php | 180
vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php | 51
vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng | 31
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/TestHelper.php | 48
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php | 279
vendor/xaboy/form-builder/src/UI/Iview/Traits/RateFactoryTrait.php | 32
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/DeLogoInfo.php | 235
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/DomainListInfo.php | 99
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodHeaderPair.php | 99
vendor/symfony/http-foundation/Tests/File/FakeFile.php | 45
vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.expected | 9
vendor/symfony/polyfill-php70/composer.json | 33
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodUpdateSpaceUploadConfigRequest.php | 141
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config | 24
vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Message.php | 93
vendor/topthink/think-template/src/facade/Template.php | 83
vendor/xaboy/form-builder/src/traits/form/FormDatePickerTrait.php | 129
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DeleteRelaySourceResponse.php | 75
vendor/xaboy/form-builder/src/UI/Iview/Components/Tree.php | 87
vendor/volcengine/volc-sdk-php/examples/Live/DemoDescribeDenyConfig.php | 29
vendor/pimple/pimple/.gitignore | 3
vendor/topthink/think-orm/src/model/relation/HasOneThrough.php | 163
vendor/topthink/think-migration/src/command/stubs/factory.stub | 11
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/LiveStreamInfo.php | 99
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php | 394
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeContentTasks.php | 20
vendor/qiniu/php-sdk/examples/get_bucketQuota.php | 20
vendor/qiniu/php-sdk/examples/cdn_refresh_urls_dirs.php | 52
vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceSubscriber.php | 19
vendor/topthink/think-helper/composer.json | 22
vendor/qiniu/php-sdk/examples/rs_batch_stat.php | 28
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php | 4484
vendor/symfony/polyfill-php70/Php70.php | 74
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnDomainTopData.php | 23
vendor/symfony/http-foundation/Tests/FileBagTest.php | 175
vendor/topthink/think-orm/src/db/PDOConnection.php | 1746
vendor/symfony/translation/Tests/fixtures/non-valid.xlf | 11
vendor/symfony/translation/Util/XliffUtils.php | 163
vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php | 119
vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php | 945
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetAudioInfoForAuditRequest.php | 99
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodASRUtteranceForAudit.php | 235
vendor/xaboy/form-builder/src/traits/form/FormSwitchesTrait.php | 32
vendor/symfony/translation/Reader/TranslationReaderInterface.php | 30
vendor/psr/cache/LICENSE.txt | 19
vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php | 161
vendor/symfony/var-dumper/CHANGELOG.md | 42
vendor/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php | 43
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodUploadMediaResponse.php | 119
vendor/topthink/framework/src/think/App.php | 620
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodUploadMediaRequest.php | 201
vendor/topthink/framework/logo.png | 0
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetMLFramesForAuditRequest.php | 439
vendor/symfony/var-dumper/Tests/Fixtures/dumb-var.php | 40
vendor/topthink/think-migration/src/Seeder.php | 24
vendor/workerman/workerman/Events/React/StreamSelectLoop.php | 26
vendor/xaboy/form-builder/src/traits/form/FormCascaderTrait.php | 80
vendor/topthink/think-orm/src/model/concern/Conversion.php | 278
vendor/topthink/think-orm/src/model/relation/MorphMany.php | 353
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions | 416
vendor/topthink/framework/src/think/route/dispatch/Controller.php | 194
vendor/topthink/think-queue/tests/WorkerTest.php | 420
vendor/topthink/framework/src/think/event/HttpRun.php | 19
vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php | 21
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodCdnStatisticsCommonResponse.php | 119
vendor/qiniu/php-sdk/examples/get_bucketinfos.php | 20
vendor/psr/http-message/src/UploadedFileInterface.php | 123
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodQueryData.php | 75
vendor/qiniu/php-sdk/test-env.sh | 4
vendor/symfony/var-dumper/Command/ServerDumpCommand.php | 99
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetWorkflowExecutionDetailRequest.php | 65
vendor/psr/http-message/src/UriInterface.php | 323
vendor/topthink/think-orm/src/Model.php | 1060
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/GPBMetadata/VodEdit.php | 39
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodAEDTimeRangeForAudit.php | 133
vendor/xaboy/form-builder/src/UI/Elm/Components/Tooltip.php | 54
vendor/symfony/translation/Tests/fixtures/resources.json | 3
vendor/volcengine/volc-sdk-php/src/Service/Imp/Models/Business/JobExecution.php | 323
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodDescribeIPInfoResponse.php | 109
vendor/volcengine/volc-sdk-php/examples/Billing/DemoListBillDetail.php | 26
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CHANGELOG.TXT | 16
vendor/symfony/translation/Loader/IcuDatFileLoader.php | 61
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config | 24
vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php | 59
vendor/topthink/framework/src/think/initializer/RegisterService.php | 48
vendor/topthink/think-image/.travis.yml | 22
vendor/symfony/http-foundation/Tests/ResponseTest.php | 1014
vendor/qiniu/php-sdk/examples/cdn_get_bandwidth.php | 40
vendor/songshenzong/support/src/BashEchoHelpers.php | 64
vendor/topthink/framework/src/think/Service.php | 66
vendor/topthink/think-queue/src/queue/ShouldQueue.php | 17
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/NoSuchBucketException.php | 6
vendor/symfony/process/Exception/LogicException.php | 21
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodListCdnPvDataRequest.php | 201
vendor/symfony/translation-contracts/Test/TranslatorTest.php | 353
vendor/symfony/var-dumper/Resources/functions/dump.php | 43
vendor/qiniu/php-sdk/examples/bucket_lifecycleRule.php | 30
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetRecommendedPosterResponse.php | 119
vendor/pimple/pimple/ext/pimple/tests/001.phpt | 45
vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php | 139
vendor/topthink/think-image/.gitignore | 4
vendor/workerman/workerman/Connection/ConnectionInterface.php | 125
vendor/topthink/think-factory/composer.json | 22
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected | 24
vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.expected | 11
vendor/symfony/translation/TranslatorInterface.php | 70
vendor/topthink/framework/src/think/route/RuleItem.php | 330
vendor/symfony/http-foundation/ApacheRequest.php | 43
vendor/xaboy/form-builder/src/traits/form/FormSliderTrait.php | 48
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected | 15
vendor/symfony/translation/Tests/Dumper/XliffFileDumperTest.php | 130
vendor/symfony/translation/Exception/NotFoundResourceException.php | 21
vendor/symfony/http-foundation/File/Exception/FileException.php | 21
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php | 10
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodCreateCdnPreloadTaskRequest.php | 99
vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php | 76
vendor/topthink/framework/tests/InteractsWithApp.php | 30
vendor/volcengine/volc-sdk-php/src/Service/Base/Models/GPBMetadata/Base.php | 0
vendor/symfony/translation/Tests/fixtures/resources.xlf | 23
vendor/symfony/event-dispatcher/Debug/WrappedListener.php | 71
vendor/volcengine/volc-sdk-php/src/Base/SignatureTrait.php | 49
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/GPBMetadata/StreamManage.php | 0
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodDescribeVodDomainTrafficDataRequest.php | 201
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php | 334
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php | 59
vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension | 1
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php | 122
vendor/symfony/http-foundation/Tests/IpUtilsTest.php | 104
vendor/topthink/think-captcha/assets/bgs/1.jpg | 0
vendor/qcloud/cos-sdk-v5/sample/uploadPart.php | 31
vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb | 25
vendor/qcloud/cos-sdk-v5/.gitignore | 2
vendor/topthink/think-queue/tests/bootstrap.php | 3
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config | 24
vendor/topthink/framework/src/think/console/command/RouteList.php | 128
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetHlsDecryptionKeyRequest.php | 133
vendor/qcloud/cos-sdk-v5/sample/restoreObject.php | 30
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php | 13
vendor/symfony/polyfill-php70/Resources/stubs/ParseError.php | 5
vendor/volcengine/volc-sdk-php/src/Service/Live/Live.php | 1232
vendor/topthink/framework/tests/LogTest.php | 130
vendor/topthink/think-queue/src/queue/command/FailedTable.php | 46
vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php | 363
vendor/symfony/psr-http-message-bridge/Tests/Factory/DiactorosFactoryTest.php | 32
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/RecordTaskFile.php | 398
vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php | 2065
vendor/symfony/translation/Tests/fixtures/plurals.mo | 0
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodDescribeVodDomainTrafficDataResponse.php | 105
vendor/qiniu/php-sdk/examples/rs_stat.php | 19
vendor/symfony/var-dumper/Caster/StubCaster.php | 82
vendor/qcloud/cos-sdk-v5/LICENSE | 21
vendor/topthink/think-filesystem/tests/FilesystemTest.php | 66
vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractTemplateCreation.php | 97
vendor/symfony/var-dumper/Caster/XmlResourceCaster.php | 61
vendor/symfony/var-dumper/phpunit.xml.dist | 33
vendor/volcengine/volc-sdk-php/src/Base/Model/SignResult.php | 136
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php | 839
vendor/symfony/translation/Tests/fixtures/withdoctype.xlf | 12
vendor/workerman/workerman/Connection/AsyncTcpConnection.php | 369
vendor/symfony/translation/Tests/Dumper/PhpFileDumperTest.php | 29
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php | 36
vendor/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php | 280
vendor/qiniu/php-sdk/src/Qiniu/Storage/UploadManager.php | 144
vendor/topthink/framework/src/think/console/command/make/Event.php | 35
vendor/topthink/framework/src/think/console/command/make/Subscribe.php | 35
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodDescribeVodDomainBandwidthDataRequest.php | 235
vendor/symfony/polyfill-php70/Resources/stubs/Error.php | 5
vendor/volcengine/volc-sdk-php/src/Service/Imp/Models/GPBMetadata/RequestImp.php | 25
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodTranscodeInfo.php | 459
vendor/qcloud_sts/qcloud-sts-sdk/.gitignore | 6
vendor/symfony/var-dumper/Cloner/Data.php | 423
vendor/xaboy/form-builder/src/UI/Elm/Components/DatePicker.php | 153
vendor/volcengine/volc-sdk-php/examples/SecretNumber/DemoQuerySubscription.php | 17
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php | 8
vendor/symfony/translation/Loader/FileLoader.php | 65
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoListCdnDomains.php | 22
vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php | 5
vendor/xaboy/form-builder/src/UI/Iview/Components/Button.php | 58
vendor/workerman/workerman/Protocols/Frame.php | 61
vendor/volcengine/volc-sdk-php/examples/Vod/DemoSetCallbackEvent.php | 30
vendor/symfony/translation/Tests/fixtures/fuzzy-translations.po | 10
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php | 184
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php | 861
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetMediaInfosRequest.php | 65
vendor/songshenzong/support/src/StringsHelpers.php | 13
vendor/topthink/framework/src/think/file/UploadedFile.php | 143
vendor/pimple/pimple/ext/pimple/tests/011.phpt | 19
vendor/spatie/macroable/CHANGELOG.md | 7
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/GPBMetadata/ResponseVod.php | 208
vendor/topthink/think-migration/README.md | 6
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/GeneratePlayURLResponse.php | 119
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/SnapshotOverride.php | 133
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodStoreUriGroup.php | 99
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php | 367
vendor/symfony/translation/Tests/fixtures/plurals.po | 5
vendor/symfony/http-foundation/AcceptHeaderItem.php | 209
vendor/symfony/translation/Tests/Formatter/IntlFormatterTest.php | 96
vendor/workerman/workerman/Connection/UdpConnection.php | 191
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetPrivateDrmPlayAuth.php | 30
vendor/qcloud/cos-sdk-v5/sample/deleteObject.php | 26
vendor/symfony/translation/Tests/Loader/PhpFileLoaderTest.php | 50
vendor/topthink/framework/src/think/response/Xml.php | 127
vendor/xaboy/form-builder/src/components/Col.php | 85
vendor/symfony/translation/Dumper/FileDumper.php | 132
vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/fr.txt | 3
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 567
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php | 72
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodAudioStreamMeta.php | 201
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DisableDomainResponse.php | 75
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php | 505
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodBandwidthData.php | 99
vendor/workerman/workerman/Protocols/Http.php | 701
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeContentBlockTasks.php | 21
vendor/qiniu/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php | 282
vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php | 76
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDeleteCdnDomain.php | 19
vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php | 133
vendor/topthink/think-helper/LICENSE | 201
vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php | 75
vendor/topthink/framework/src/think/Cache.php | 197
vendor/topthink/think-image/tests/CropTest.php | 67
vendor/symfony/event-dispatcher/phpunit.xml.dist | 31
vendor/topthink/framework/src/think/exception/Handle.php | 332
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.expected | 20
vendor/symfony/var-dumper/Dumper/DataDumperInterface.php | 24
vendor/symfony/translation/TranslatorBagInterface.php | 33
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetAllPlayInfoRequest.php | 667
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DeleteDenyConfigRequest.php | 137
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetMediaInfos.php | 27
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DescribeForbiddenStreamInfoByPageResponse.php | 119
vendor/xaboy/form-builder/src/UI/Elm/Bootstrap.php | 33
vendor/xaboy/form-builder/src/UI/Iview/Config.php | 218
vendor/xaboy/form-builder/src/components/Input.php | 143
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodAEDInfoForAudit.php | 65
vendor/xaboy/form-builder/src/UI/Iview/Components/Slider.php | 61
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php | 87
vendor/symfony/polyfill-php72/Php72.php | 216
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/GPBMetadata/DenyConfig.php | 0
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodCreateCdnTaskResult.php | 65
vendor/symfony/var-dumper/Server/Connection.php | 95
vendor/volcengine/volc-sdk-php/examples/Live/DemoTransPreset.php | 54
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/EnableDomainRequest.php | 65
vendor/topthink/framework/src/think/Console.php | 786
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetRecommendedPoster.php | 27
vendor/xaboy/form-builder/src/traits/form/FormOptionTrait.php | 33
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetDirectEditResultResponse.php | 109
vendor/symfony/var-dumper/Caster/XmlReaderCaster.php | 77
vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.php | 8
vendor/symfony/process/Tests/ProcessFailedExceptionTest.php | 137
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/BatchAppend.php | 24
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/HashTable.php | 175
vendor/symfony/translation/Tests/fixtures/valid.csv | 4
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeEdgeTopStatisticalData.php | 24
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/GPBMetadata/RelaySource.php | 0
vendor/topthink/think-template/src/template/driver/File.php | 83
vendor/qiniu/php-sdk/examples/rs_batch_delete_after_days.php | 34
vendor/spatie/macroable/src/Macroable.php | 75
vendor/volcengine/volc-sdk-php/examples/Vod/DemoGetDirectEditResult.php | 29
vendor/symfony/translation/Tests/Loader/PoFileLoaderTest.php | 109
vendor/volcengine/volc-sdk-php/examples/Cdn/DemoAddResourceTags.php | 22
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/TranscodeVideoOverride.php | 177
vendor/symfony/http-foundation/BinaryFileResponse.php | 359
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php | 77
vendor/topthink/think-orm/src/db/connector/Sqlsrv.php | 122
vendor/symfony/polyfill-php70/bootstrap.php | 27
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodGetBetterFramesForAuditResponse.php | 119
vendor/symfony/var-dumper/Caster/CutArrayStub.php | 30
vendor/symfony/var-dumper/Tests/Caster/CasterTest.php | 178
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config | 24
vendor/topthink/framework/src/think/event/LogRecord.php | 29
vendor/volcengine/volc-sdk-php/examples/SecretNumber/DemoBindAXN.php | 21
vendor/symfony/translation/Reader/TranslationReader.php | 63
vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php | 589
vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php | 325
vendor/symfony/translation/Loader/ArrayLoader.php | 66
vendor/symfony/translation/Dumper/IniFileDumper.php | 45
vendor/topthink/framework/src/think/console/command/make/stubs/command.stub | 26
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodVolumeInfo.php | 99
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DescribeCDNSnapshotHistoryRequest.php | 323
vendor/symfony/process/Exception/ProcessTimedOutException.php | 69
vendor/topthink/framework/src/think/Pipeline.php | 107
vendor/topthink/think-template/.gitignore | 1
vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php | 24
vendor/psr/cache/src/CacheItemInterface.php | 105
vendor/topthink/think-multi-app/src/Url.php | 232
vendor/qiniu/php-sdk/examples/cdn_timestamp_antileech.php | 19
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/UpdateRelaySourceResponse.php | 75
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetBetterFramesForAuditResult.php | 65
vendor/volcengine/volc-sdk-php/examples/Vod/DemoDescribeVodDomainBandwidthData.php | 32
vendor/songshenzong/support/src/Env.php | 101
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetSubtitleInfoListRequest.php | 439
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodUpdateSpaceRequest.php | 167
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/ManagerPullPushDomainBindResponse.php | 75
vendor/topthink/framework/src/think/facade/View.php | 42
vendor/topthink/think-filesystem/README.md | 5
vendor/symfony/http-foundation/Tests/ResponseFunctionalTest.php | 58
vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php | 32
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php | 214
vendor/qiniu/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php | 41
vendor/symfony/http-foundation/Cookie.php | 292
vendor/symfony/process/Exception/ProcessFailedException.php | 54
vendor/symfony/translation/Loader/XliffFileLoader.php | 212
vendor/topthink/framework/phpunit.xml.dist | 25
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DescribeDenyConfigRequest.php | 137
vendor/xaboy/form-builder/src/Util.php | 25
vendor/topthink/think-orm/src/db/connector/pgsql.sql | 117
vendor/symfony/polyfill-mbstring/README.md | 13
vendor/xaboy/form-builder/src/UI/Iview/Traits/FrameFactoryTrait.php | 126
vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php | 20
vendor/symfony/var-dumper/VarDumper.php | 56
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodDeleteVideoClassificationResponse.php | 75
vendor/psr/cache/src/CacheItemPoolInterface.php | 138
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodStartWorkflowResult.php | 65
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/UpdateTask.php | 21
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php | 282
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VolumeInfo.php | 167
vendor/xaboy/form-builder/src/UI/Iview/Traits/SliderFactoryTrait.php | 50
vendor/symfony/var-dumper/Tests/Fixtures/Twig.php | 38
vendor/xaboy/form-builder/src/Annotation/Validate/ValidateAnnotation.php | 44
vendor/psr/container/.gitignore | 3
vendor/topthink/think-view/.gitignore | 1
vendor/topthink/think-captcha/assets/ttfs/5.ttf | 0
vendor/pimple/pimple/phpunit.xml.dist | 14
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/GPBMetadata/VodAppsManage.php | 0
vendor/symfony/psr-http-message-bridge/Factory/DiactorosFactory.php | 175
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodASRLanguageDetailForAudit.php | 99
vendor/qiniu/php-sdk/examples/rs_download_urls.php | 17
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/DescribeLiveStreamStateResponse.php | 119
vendor/symfony/translation/Tests/Dumper/MoFileDumperTest.php | 29
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php | 156
vendor/xaboy/form-builder/src/UI/Elm/Traits/SwitchesFactoryTrait.php | 32
vendor/volcengine/volc-sdk-php/examples/Live/DemoListVQScoreTask.php | 31
vendor/topthink/framework/src/think/cache/driver/Memcache.php | 209
vendor/pimple/pimple/ext/pimple/tests/004.phpt | 30
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/StopPullToPushTaskRequest.php | 65
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodGetOriginalPlayInfoResult.php | 405
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php | 150
vendor/symfony/process/Pipes/AbstractPipes.php | 178
vendor/qiniu/php-sdk/tests/Qiniu/Tests/DownloadTest.php | 25
vendor/pimple/pimple/ext/pimple/config.w32 | 13
vendor/topthink/framework/src/think/Cookie.php | 232
vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php | 106
vendor/volcengine/volc-sdk-php/examples/Live/DemoSnapshotPreset.php | 47
vendor/psr/log/Psr/Log/Test/TestLogger.php | 147
vendor/qiniu/php-sdk/examples/upload_verify_callback.php | 32
vendor/topthink/framework/src/think/route/dispatch/Callback.php | 30
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/TokenListener.php | 48
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php | 548
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/BucketNotEmptyException.php | 6
vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php | 35
vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php | 1182
vendor/symfony/translation/DataCollector/TranslationDataCollector.php | 169
vendor/xaboy/form-builder/src/UI/Iview/Traits/RadioFactoryTrait.php | 32
vendor/symfony/event-dispatcher/Tests/GenericEventTest.php | 136
vendor/symfony/process/Process.php | 1653
vendor/volcengine/volc-sdk-php/examples/Vod/DemoPublish.php | 32
vendor/symfony/polyfill-php70/LICENSE | 19
vendor/topthink/framework/src/think/session/driver/File.php | 249
vendor/topthink/think-image/src/image/Exception.php | 18
vendor/volcengine/volc-sdk-php/examples/ImageX/DemoGetImageSegment.php | 26
vendor/xaboy/form-builder/src/Annotation/Group.php | 86
vendor/topthink/think-orm/src/db/builder/Oracle.php | 95
vendor/songshenzong/support/src/Traits/Uri.php | 73
vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php | 157
vendor/xaboy/form-builder/src/UI/Iview/Style/Col.php | 178
vendor/xaboy/form-builder/src/UI/Iview/Traits/ValidateFactoryTrait.php | 73
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php | 533
vendor/songshenzong/support/src/OS.php | 75
vendor/topthink/think-migration/src/Creator.php | 77
vendor/symfony/translation/Tests/Dumper/FileDumperTest.php | 90
vendor/topthink/think-template/src/template/TagLib.php | 349
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DescribeLiveStreamStateRequest.php | 175
vendor/symfony/http-foundation/.gitignore | 3
vendor/topthink/think-multi-app/composer.json | 28
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php | 142
vendor/songshenzong/support/README.md | 32
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/UpdateDenyConfigResponse.php | 75
vendor/symfony/process/Tests/NonStopableProcess.php | 47
vendor/symfony/translation/Dumper/XliffFileDumper.php | 209
vendor/volcengine/volc-sdk-php/src/Base/Singleton.php | 30
vendor/topthink/framework/src/think/console/command/make/Listener.php | 35
vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php | 21
vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php | 28
vendor/symfony/translation/Tests/fixtures/empty.json | 0
vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php | 256
vendor/topthink/think-template/composer.json | 20
vendor/xaboy/form-builder/src/components/ColorPicker.php | 86
vendor/volcengine/volc-sdk-php/examples/DemoIam.php | 21
vendor/topthink/think-helper/src/helper/Str.php | 234
vendor/xaboy/form-builder/src/Contract/ValidateInterface.php | 25
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DeleteDomainRequest.php | 65
vendor/topthink/framework/src/think/facade/Request.php | 134
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodPlayInfoModel.php | 483
vendor/symfony/http-foundation/README.md | 14
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CholeskyDecomposition.php | 147
vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php | 69
vendor/symfony/http-foundation/RequestMatcher.php | 178
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/GridLines.php | 454
vendor/xaboy/form-builder/src/UI/Iview/Components/ColorPicker.php | 49
vendor/symfony/http-foundation/Session/SessionBagInterface.php | 46
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodSmartStrategyResponseStreamType.php | 68
vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.expected | 6
vendor/xaboy/form-builder/src/components/TreeData.php | 102
vendor/topthink/framework/src/think/console/output/formatter/Style.php | 190
vendor/symfony/event-dispatcher/composer.json | 44
vendor/topthink/framework/.gitignore | 10
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/DescribeVQScoreTaskRequest.php | 65
vendor/topthink/think-multi-app/src/Service.php | 32
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php | 882
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php | 278
vendor/topthink/think-orm/src/model/relation/MorphTo.php | 332
vendor/workerman/workerman/Autoloader.php | 69
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config | 24
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php | 19
vendor/topthink/think-queue/src/queue/Job.php | 282
vendor/volcengine/volc-sdk-php/src/Service/Imp/Models/Business/JobOutput.php | 329
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php | 35
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php | 52
vendor/xaboy/form-builder/src/UI/Elm/Style/FormStyle.php | 86
vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php | 97
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php | 88
vendor/volcengine/volc-sdk-php/src/Service/SecretNumber.php | 282
vendor/xaboy/form-builder/src/components/Validate.php | 207
vendor/volcengine/volc-sdk-php/examples/ImageX/DemoUpload.php | 17
vendor/topthink/framework/src/think/event/RouteLoaded.php | 21
vendor/volcengine/volc-sdk-php/src/Service/Imp/Models/Business/InputPath.php | 146
vendor/topthink/framework/src/lang/zh-cn.php | 148
vendor/topthink/think-queue/src/queue/failed/Database.php | 99
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodSubtitleInfo.php | 507
vendor/symfony/process/Pipes/PipesInterface.php | 67
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Response/VodCommitUploadInfoResponse.php | 119
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php | 189
vendor/xaboy/form-builder/src/UI/Iview/Traits/GroupFactoryTrait.php | 33
vendor/xaboy/form-builder/src/UI/Iview/Components/Rate.php | 47
vendor/topthink/think-migration/phinx/README.md | 128
vendor/topthink/framework/src/think/exception/RouteNotFoundException.php | 26
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/CreatePullToPushTaskResult.php | 65
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/GeneratePushURLResponse.php | 119
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php | 411
vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/Test.php | 1347
vendor/symfony/translation/Loader/PhpFileLoader.php | 28
vendor/volcengine/volc-sdk-php/examples/Vod/DemoUploadMaterial.php | 148
vendor/psr/simple-cache/LICENSE.md | 21
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php | 114
vendor/topthink/framework/src/think/console/output/Question.php | 211
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Request/CreateVQScoreTaskRequest.php | 213
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/utils/Maths.php | 30
vendor/symfony/http-foundation/ResponseHeaderBag.php | 340
vendor/topthink/think-orm/src/DbManager.php | 396
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/Polyfill.php | 9
vendor/ralouphie/getallheaders/LICENSE | 21
vendor/symfony/translation/Tests/fixtures/empty.xlf | 0
vendor/symfony/translation/Tests/IntervalTest.php | 52
vendor/topthink/think-migration/.gitignore | 3
vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PdoAdapter.php | 587
vendor/volcengine/volc-sdk-php/examples/Live/DemoGeneratePushURL.php | 32
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Request/VodGetAppInfoRequest.php | 65
vendor/xaboy/form-builder/src/UI/Elm/Traits/UploadFactoryTrait.php | 93
vendor/topthink/framework/src/think/View.php | 191
vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd | 2223
vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php | 277
vendor/symfony/translation/Formatter/IntlFormatter.php | 55
vendor/volcengine/volc-sdk-php/src/Service/Sts.php | 45
vendor/topthink/framework/src/think/console/bin/hiddeninput.exe | 0
vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif | 0
vendor/qcloud/cos-sdk-v5/composer.json | 27
vendor/volcengine/volc-sdk-php/examples/Rtc/rtc-openapi-demo/Rtc/DemoRequest.php | 59
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/EnableDomainResponse.php | 75
vendor/symfony/var-dumper/Server/DumpServer.php | 107
vendor/psr/cache/CHANGELOG.md | 16
vendor/symfony/var-dumper/Cloner/AbstractCloner.php | 360
vendor/psr/http-message/src/ServerRequestInterface.php | 261
vendor/symfony/polyfill-mbstring/composer.json | 34
vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php | 20
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/no/config | 24
vendor/pimple/pimple/ext/pimple/tests/007.phpt | 22
vendor/symfony/http-foundation/File/Exception/UploadException.php | 21
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodDescribeVodDomainBandwidthDataResult.php | 303
vendor/topthink/framework/src/think/console/output/driver/Console.php | 369
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/RecordHistoryInfo.php | 109
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php | 186
vendor/topthink/think-orm/src/db/exception/PDOException.php | 41
vendor/topthink/think-orm/src/model/concern/SoftDelete.php | 248
vendor/xaboy/form-builder/src/Driver/CustomComponent.php | 121
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/GPBMetadata/Addr.php | 0
vendor/volcengine/volc-sdk-php/VERSION | 1
vendor/xaboy/form-builder/src/Rule/ValidateRule.php | 70
vendor/topthink/framework/src/think/response/Json.php | 62
vendor/psr/container/composer.json | 27
vendor/volcengine/volc-sdk-php/examples/ImageX/DemoFetchUrl.php | 20
vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php | 308
vendor/xaboy/form-builder/src/Contract/FormOptionsComponentInterface.php | 19
vendor/topthink/framework/src/think/Route.php | 939
vendor/topthink/think-orm/src/model/concern/RelationShip.php | 841
vendor/qiniu/php-sdk/.scrutinizer.yml | 35
vendor/symfony/var-dumper/Dumper/HtmlDumper.php | 969
vendor/symfony/translation/DataCollectorTranslator.php | 188
vendor/topthink/think-multi-app/src/command/stubs/controller.stub | 12
vendor/xaboy/form-builder/demo/elm.php | 162
vendor/ralouphie/getallheaders/README.md | 19
vendor/volcengine/volc-sdk-php/examples/Rtc/rtc-openapi-demo/VERSION | 1
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodPlayInfo.php | 823
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php | 1443
vendor/qiniu/php-sdk/src/Qiniu/Region.php | 196
vendor/symfony/translation/Tests/fixtures/resourcebundle/res/en.res | 0
vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php | 296
vendor/pimple/pimple/ext/pimple/tests/017.phpt | 17
vendor/symfony/http-foundation/Tests/StreamedResponseTest.php | 144
vendor/symfony/translation/Tests/fixtures/encoding.xlf | 16
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/VodAllPlayInfoResult.php | 126
vendor/topthink/think-multi-app/README.md | 14
vendor/xaboy/form-builder/images/components.png | 0
vendor/xaboy/form-builder/src/Rule/ControlRule.php | 80
vendor/volcengine/volc-sdk-php/src/Service/Imp/Models/Response/ImpSubmitJobResponse.php | 92
vendor/qiniu/php-sdk/tests/Qiniu/Tests/FopTest.php | 37
vendor/volcengine/volc-sdk-php/examples/VoiceNotify/QueryResource.php | 18
vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php | 99
vendor/symfony/http-foundation/Tests/Fixtures/response-functional/common.inc | 43
vendor/topthink/think-template/src/Template.php | 1320
vendor/qiniu/php-sdk/examples/put_bucketEvent.php | 25
vendor/songshenzong/support/tests/TimeTest.php | 22
vendor/volcengine/volc-sdk-php/src/Service/Vod/Models/Business/OverrideParams.php | 167
vendor/qcloud/cos-sdk-v5/sample.php | 824
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Business/DomainList.php | 567
vendor/volcengine/volc-sdk-php/src/Service/Live/Models/Response/CreatePullToPushTaskResponse.php | 119
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php | 13
vendor/topthink/framework/src/think/console/output/descriptor/Console.php | 153
vendor/topthink/think-image/tests/ThumbTest.php | 284
vendor/symfony/polyfill-intl-idn/Resources/unidata/ignored.php | 273
vendor/symfony/event-dispatcher/EventDispatcherInterface.php | 81
vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php | 152
vendor/symfony/translation/Catalogue/MergeOperation.php | 58
vendor/workerman/workerman/Connection/TcpConnection.php | 1001
vendor/symfony/translation/Tests/fixtures/extractor/this.is.a.template.format.engine | 0
2,489 files changed, 378,510 insertions(+), 0 deletions(-)
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config
new file mode 100755
index 0000000..df51373
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config
@@ -0,0 +1,23 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = Kč
+
+
+##
+## Excel Error Codes (For future use)
+##
+NULL = #NULL!
+DIV0 = #DIV/0!
+VALUE = #HODNOTA!
+REF = #REF!
+NAME = #NÁZEV?
+NUM = #NUM!
+NA = #N/A
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions
new file mode 100755
index 0000000..733d406
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions
@@ -0,0 +1,416 @@
+##
+## PhpSpreadsheet
+##
+## Data in this file derived from https://www.excel-function-translation.com/
+##
+##
+
+
+##
+## Add-in and Automation functions Funkce doplňků a automatizace
+##
+GETPIVOTDATA = ZÍSKATKONTDATA ## Vrátí data uložená v kontingenční tabulce. Pomocí funkce ZÍSKATKONTDATA můžete načíst souhrnná data z kontingenční tabulky, pokud jsou tato data v kontingenční sestavě zobrazena.
+
+
+##
+## Cube functions Funkce pro práci s krychlemi
+##
+CUBEKPIMEMBER = CUBEKPIMEMBER ## Vrátí název, vlastnost a velikost klíčového ukazatele výkonu (KUV) a zobrazí v buňce název a vlastnost. Klíčový ukazatel výkonu je kvantifikovatelná veličina, například hrubý měsíční zisk nebo čtvrtletní obrat na zaměstnance, která se používá pro sledování výkonnosti organizace.
+CUBEMEMBER = CUBEMEMBER ## Vrátí člen nebo n-tici v hierarchii krychle. Slouží k ověření, zda v krychli existuje člen nebo n-tice.
+CUBEMEMBERPROPERTY = CUBEMEMBERPROPERTY ## Vrátí hodnotu vlastnosti člena v krychli. Slouží k ověření, zda v krychli existuje člen s daným názvem, a k vrácení konkrétní vlastnosti tohoto člena.
+CUBERANKEDMEMBER = CUBERANKEDMEMBER ## Vrátí n-tý nebo pořadový člen sady. Použijte ji pro vrácení jednoho nebo více prvků sady, například obchodníka s nejvyšším obratem nebo deseti nejlepších studentů.
+CUBESET = CUBESET ## Definuje vypočtenou sadu členů nebo n-tic odesláním výrazu sady do krychle na serveru, který vytvoří sadu a potom ji vrátí do aplikace Microsoft Office Excel.
+CUBESETCOUNT = CUBESETCOUNT ## Vrátí počet položek v množině
+CUBEVALUE = CUBEVALUE ## Vrátí úhrnnou hodnotu z krychle.
+
+
+##
+## Database functions Funkce databáze
+##
+DAVERAGE = DPRŮMĚR ## Vrátí průměr vybraných položek databáze.
+DCOUNT = DPOČET ## Spočítá buňky databáze obsahující čísla.
+DCOUNTA = DPOČET2 ## Spočítá buňky databáze, které nejsou prázdné.
+DGET = DZÍSKAT ## Extrahuje z databáze jeden záznam splňující zadaná kritéria.
+DMAX = DMAX ## Vrátí maximální hodnotu z vybraných položek databáze.
+DMIN = DMIN ## Vrátí minimální hodnotu z vybraných položek databáze.
+DPRODUCT = DSOUČIN ## Vynásobí hodnoty určitého pole záznamů v databázi, které splňují daná kritéria.
+DSTDEV = DSMODCH.VÝBĚR ## Odhadne směrodatnou odchylku výběru vybraných položek databáze.
+DSTDEVP = DSMODCH ## Vypočte směrodatnou odchylku základního souboru vybraných položek databáze.
+DSUM = DSUMA ## Sečte čísla ve sloupcovém poli záznamů databáze, která splňují daná kritéria.
+DVAR = DVAR.VÝBĚR ## Odhadne rozptyl výběru vybraných položek databáze.
+DVARP = DVAR ## Vypočte rozptyl základního souboru vybraných položek databáze.
+
+
+##
+## Date and time functions Funkce data a času
+##
+DATE = DATUM ## Vrátí pořadové číslo určitého data.
+DATEVALUE = DATUMHODN ## Převede datum ve formě textu na pořadové číslo.
+DAY = DEN ## Převede pořadové číslo na den v měsíci.
+DAYS360 = ROK360 ## Vrátí počet dní mezi dvěma daty na základě roku s 360 dny.
+EDATE = EDATE ## Vrátí pořadové číslo data, které označuje určený počet měsíců před nebo po počátečním datu.
+EOMONTH = EOMONTH ## Vrátí pořadové číslo posledního dne měsíce před nebo po zadaném počtu měsíců.
+HOUR = HODINA ## Převede pořadové číslo na hodinu.
+MINUTE = MINUTA ## Převede pořadové číslo na minutu.
+MONTH = MĚSÍC ## Převede pořadové číslo na měsíc.
+NETWORKDAYS = NETWORKDAYS ## Vrátí počet celých pracovních dní mezi dvěma daty.
+NOW = NYNÍ ## Vrátí pořadové číslo aktuálního data a času.
+SECOND = SEKUNDA ## Převede pořadové číslo na sekundu.
+TIME = ČAS ## Vrátí pořadové číslo určitého času.
+TIMEVALUE = ČASHODN ## Převede čas ve formě textu na pořadové číslo.
+TODAY = DNES ## Vrátí pořadové číslo dnešního data.
+WEEKDAY = DENTÝDNE ## Převede pořadové číslo na den v týdnu.
+WEEKNUM = WEEKNUM ## Převede pořadové číslo na číslo představující číselnou pozici týdne v roce.
+WORKDAY = WORKDAY ## Vrátí pořadové číslo data před nebo po zadaném počtu pracovních dní.
+YEAR = ROK ## Převede pořadové číslo na rok.
+YEARFRAC = YEARFRAC ## Vrátí část roku vyjádřenou zlomkem a představující počet celých dní mezi počátečním a koncovým datem.
+
+
+##
+## Engineering functions Inženýrské funkce (Technické funkce)
+##
+BESSELI = BESSELI ## Vrátí modifikovanou Besselovu funkci In(x).
+BESSELJ = BESSELJ ## Vrátí modifikovanou Besselovu funkci Jn(x).
+BESSELK = BESSELK ## Vrátí modifikovanou Besselovu funkci Kn(x).
+BESSELY = BESSELY ## Vrátí Besselovu funkci Yn(x).
+BIN2DEC = BIN2DEC ## Převede binární číslo na desítkové.
+BIN2HEX = BIN2HEX ## Převede binární číslo na šestnáctkové.
+BIN2OCT = BIN2OCT ## Převede binární číslo na osmičkové.
+COMPLEX = COMPLEX ## Převede reálnou a imaginární část na komplexní číslo.
+CONVERT = CONVERT ## Převede číslo do jiného jednotkového měrného systému.
+DEC2BIN = DEC2BIN ## Převede desítkového čísla na dvojkové
+DEC2HEX = DEC2HEX ## Převede desítkové číslo na šestnáctkové.
+DEC2OCT = DEC2OCT ## Převede desítkové číslo na osmičkové.
+DELTA = DELTA ## Testuje rovnost dvou hodnot.
+ERF = ERF ## Vrátí chybovou funkci.
+ERFC = ERFC ## Vrátí doplňkovou chybovou funkci.
+GESTEP = GESTEP ## Testuje, zda je číslo větší než mezní hodnota.
+HEX2BIN = HEX2BIN ## Převede šestnáctkové číslo na binární.
+HEX2DEC = HEX2DEC ## Převede šestnáctkové číslo na desítkové.
+HEX2OCT = HEX2OCT ## Převede šestnáctkové číslo na osmičkové.
+IMABS = IMABS ## Vrátí absolutní hodnotu (modul) komplexního čísla.
+IMAGINARY = IMAGINARY ## Vrátí imaginární část komplexního čísla.
+IMARGUMENT = IMARGUMENT ## Vrátí argument théta, úhel vyjádřený v radiánech.
+IMCONJUGATE = IMCONJUGATE ## Vrátí komplexně sdružené číslo ke komplexnímu číslu.
+IMCOS = IMCOS ## Vrátí kosinus komplexního čísla.
+IMDIV = IMDIV ## Vrátí podíl dvou komplexních čísel.
+IMEXP = IMEXP ## Vrátí exponenciální tvar komplexního čísla.
+IMLN = IMLN ## Vrátí přirozený logaritmus komplexního čísla.
+IMLOG10 = IMLOG10 ## Vrátí dekadický logaritmus komplexního čísla.
+IMLOG2 = IMLOG2 ## Vrátí logaritmus komplexního čísla při základu 2.
+IMPOWER = IMPOWER ## Vrátí komplexní číslo umocněné na celé číslo.
+IMPRODUCT = IMPRODUCT ## Vrátí součin komplexních čísel.
+IMREAL = IMREAL ## Vrátí reálnou část komplexního čísla.
+IMSIN = IMSIN ## Vrátí sinus komplexního čísla.
+IMSQRT = IMSQRT ## Vrátí druhou odmocninu komplexního čísla.
+IMSUB = IMSUB ## Vrátí rozdíl mezi dvěma komplexními čísly.
+IMSUM = IMSUM ## Vrátí součet dvou komplexních čísel.
+OCT2BIN = OCT2BIN ## Převede osmičkové číslo na binární.
+OCT2DEC = OCT2DEC ## Převede osmičkové číslo na desítkové.
+OCT2HEX = OCT2HEX ## Převede osmičkové číslo na šestnáctkové.
+
+
+##
+## Financial functions Finanční funkce
+##
+ACCRINT = ACCRINT ## Vrátí nahromaděný úrok z cenného papíru, ze kterého je úrok placen v pravidelných termínech.
+ACCRINTM = ACCRINTM ## Vrátí nahromaděný úrok z cenného papíru, ze kterého je úrok placen k datu splatnosti.
+AMORDEGRC = AMORDEGRC ## Vrátí lineární amortizaci v každém účetním období pomocí koeficientu amortizace.
+AMORLINC = AMORLINC ## Vrátí lineární amortizaci v každém účetním období.
+COUPDAYBS = COUPDAYBS ## Vrátí počet dnů od začátku období placení kupónů do data splatnosti.
+COUPDAYS = COUPDAYS ## Vrátí počet dnů v období placení kupónů, které obsahuje den zúčtování.
+COUPDAYSNC = COUPDAYSNC ## Vrátí počet dnů od data zúčtování do následujícího data placení kupónu.
+COUPNCD = COUPNCD ## Vrátí následující datum placení kupónu po datu zúčtování.
+COUPNUM = COUPNUM ## Vrátí počet kupónů splatných mezi datem zúčtování a datem splatnosti.
+COUPPCD = COUPPCD ## Vrátí předchozí datum placení kupónu před datem zúčtování.
+CUMIPMT = CUMIPMT ## Vrátí kumulativní úrok splacený mezi dvěma obdobími.
+CUMPRINC = CUMPRINC ## Vrátí kumulativní jistinu splacenou mezi dvěma obdobími půjčky.
+DB = ODPIS.ZRYCH ## Vrátí odpis aktiva za určité období pomocí degresivní metody odpisu s pevným zůstatkem.
+DDB = ODPIS.ZRYCH2 ## Vrátí odpis aktiva za určité období pomocí dvojité degresivní metody odpisu nebo jiné metody, kterou zadáte.
+DISC = DISC ## Vrátí diskontní sazbu cenného papíru.
+DOLLARDE = DOLLARDE ## Převede částku v korunách vyjádřenou zlomkem na částku v korunách vyjádřenou desetinným číslem.
+DOLLARFR = DOLLARFR ## Převede částku v korunách vyjádřenou desetinným číslem na částku v korunách vyjádřenou zlomkem.
+DURATION = DURATION ## Vrátí roční dobu cenného papíru s pravidelnými úrokovými sazbami.
+EFFECT = EFFECT ## Vrátí efektivní roční úrokovou sazbu.
+FV = BUDHODNOTA ## Vrátí budoucí hodnotu investice.
+FVSCHEDULE = FVSCHEDULE ## Vrátí budoucí hodnotu počáteční jistiny po použití série sazeb složitého úroku.
+INTRATE = INTRATE ## Vrátí úrokovou sazbu plně investovaného cenného papíru.
+IPMT = PLATBA.ÚROK ## Vrátí výšku úroku investice za dané období.
+IRR = MÍRA.VÝNOSNOSTI ## Vrátí vnitřní výnosové procento série peněžních toků.
+ISPMT = ISPMT ## Vypočte výši úroku z investice zaplaceného během určitého období.
+MDURATION = MDURATION ## Vrátí Macauleyho modifikovanou dobu cenného papíru o nominální hodnotě 100 Kč.
+MIRR = MOD.MÍRA.VÝNOSNOSTI ## Vrátí vnitřní sazbu výnosu, přičemž kladné a záporné hodnoty peněžních prostředků jsou financovány podle různých sazeb.
+NOMINAL = NOMINAL ## Vrátí nominální roční úrokovou sazbu.
+NPER = POČET.OBDOBÍ ## Vrátí počet období pro investici.
+NPV = ČISTÁ.SOUČHODNOTA ## Vrátí čistou současnou hodnotu investice vypočítanou na základě série pravidelných peněžních toků a diskontní sazby.
+ODDFPRICE = ODDFPRICE ## Vrátí cenu cenného papíru o nominální hodnotě 100 Kč s odlišným prvním obdobím.
+ODDFYIELD = ODDFYIELD ## Vrátí výnos cenného papíru s odlišným prvním obdobím.
+ODDLPRICE = ODDLPRICE ## Vrátí cenu cenného papíru o nominální hodnotě 100 Kč s odlišným posledním obdobím.
+ODDLYIELD = ODDLYIELD ## Vrátí výnos cenného papíru s odlišným posledním obdobím.
+PMT = PLATBA ## Vrátí hodnotu pravidelné splátky anuity.
+PPMT = PLATBA.ZÁKLAD ## Vrátí hodnotu splátky jistiny pro zadanou investici za dané období.
+PRICE = PRICE ## Vrátí cenu cenného papíru o nominální hodnotě 100 Kč, ze kterého je úrok placen v pravidelných termínech.
+PRICEDISC = PRICEDISC ## Vrátí cenu diskontního cenného papíru o nominální hodnotě 100 Kč.
+PRICEMAT = PRICEMAT ## Vrátí cenu cenného papíru o nominální hodnotě 100 Kč, ze kterého je úrok placen k datu splatnosti.
+PV = SOUČHODNOTA ## Vrátí současnou hodnotu investice.
+RATE = ÚROKOVÁ.MÍRA ## Vrátí úrokovou sazbu vztaženou na období anuity.
+RECEIVED = RECEIVED ## Vrátí částku obdrženou k datu splatnosti plně investovaného cenného papíru.
+SLN = ODPIS.LIN ## Vrátí přímé odpisy aktiva pro jedno období.
+SYD = ODPIS.NELIN ## Vrátí směrné číslo ročních odpisů aktiva pro zadané období.
+TBILLEQ = TBILLEQ ## Vrátí výnos směnky státní pokladny ekvivalentní výnosu obligace.
+TBILLPRICE = TBILLPRICE ## Vrátí cenu směnky státní pokladny o nominální hodnotě 100 Kč.
+TBILLYIELD = TBILLYIELD ## Vrátí výnos směnky státní pokladny.
+VDB = ODPIS.ZA.INT ## Vrátí odpis aktiva pro určité období nebo část období pomocí degresivní metody odpisu.
+XIRR = XIRR ## Vrátí vnitřní výnosnost pro harmonogram peněžních toků, který nemusí být nutně periodický.
+XNPV = XNPV ## Vrátí čistou současnou hodnotu pro harmonogram peněžních toků, který nemusí být nutně periodický.
+YIELD = YIELD ## Vrátí výnos cenného papíru, ze kterého je úrok placen v pravidelných termínech.
+YIELDDISC = YIELDDISC ## Vrátí roční výnos diskontního cenného papíru, například směnky státní pokladny.
+YIELDMAT = YIELDMAT ## Vrátí roční výnos cenného papíru, ze kterého je úrok placen k datu splatnosti.
+
+
+##
+## Information functions Informační funkce
+##
+CELL = POLÍČKO ## Vrátí informace o formátování, umístění nebo obsahu buňky.
+ERROR.TYPE = CHYBA.TYP ## Vrátí číslo odpovídající typu chyby.
+INFO = O.PROSTŘEDÍ ## Vrátí informace o aktuálním pracovním prostředí.
+ISBLANK = JE.PRÁZDNÉ ## Vrátí hodnotu PRAVDA, pokud se argument hodnota odkazuje na prázdnou buňku.
+ISERR = JE.CHYBA ## Vrátí hodnotu PRAVDA, pokud je argument hodnota libovolná chybová hodnota (kromě #N/A).
+ISERROR = JE.CHYBHODN ## Vrátí hodnotu PRAVDA, pokud je argument hodnota libovolná chybová hodnota.
+ISEVEN = ISEVEN ## Vrátí hodnotu PRAVDA, pokud je číslo sudé.
+ISLOGICAL = JE.LOGHODN ## Vrátí hodnotu PRAVDA, pokud je argument hodnota logická hodnota.
+ISNA = JE.NEDEF ## Vrátí hodnotu PRAVDA, pokud je argument hodnota chybová hodnota #N/A.
+ISNONTEXT = JE.NETEXT ## Vrátí hodnotu PRAVDA, pokud argument hodnota není text.
+ISNUMBER = JE.ČÍSLO ## Vrátí hodnotu PRAVDA, pokud je argument hodnota číslo.
+ISODD = ISODD ## Vrátí hodnotu PRAVDA, pokud je číslo liché.
+ISREF = JE.ODKAZ ## Vrátí hodnotu PRAVDA, pokud je argument hodnota odkaz.
+ISTEXT = JE.TEXT ## Vrátí hodnotu PRAVDA, pokud je argument hodnota text.
+N = N ## Vrátí hodnotu převedenou na číslo.
+NA = NEDEF ## Vrátí chybovou hodnotu #N/A.
+TYPE = TYP ## Vrátí číslo označující datový typ hodnoty.
+
+
+##
+## Logical functions Logické funkce
+##
+AND = A ## Vrátí hodnotu PRAVDA, mají-li všechny argumenty hodnotu PRAVDA.
+FALSE = NEPRAVDA ## Vrátí logickou hodnotu NEPRAVDA.
+IF = KDYŽ ## Určí, který logický test má proběhnout.
+IFERROR = IFERROR ## Pokud je vzorec vyhodnocen jako chyba, vrátí zadanou hodnotu. V opačném případě vrátí výsledek vzorce.
+NOT = NE ## Provede logickou negaci argumentu funkce.
+OR = NEBO ## Vrátí hodnotu PRAVDA, je-li alespoň jeden argument roven hodnotě PRAVDA.
+TRUE = PRAVDA ## Vrátí logickou hodnotu PRAVDA.
+
+
+##
+## Lookup and reference functions Vyhledávací funkce
+##
+ADDRESS = ODKAZ ## Vrátí textový odkaz na jednu buňku listu.
+AREAS = POČET.BLOKŮ ## Vrátí počet oblastí v odkazu.
+CHOOSE = ZVOLIT ## Zvolí hodnotu ze seznamu hodnot.
+COLUMN = SLOUPEC ## Vrátí číslo sloupce odkazu.
+COLUMNS = SLOUPCE ## Vrátí počet sloupců v odkazu.
+HLOOKUP = VVYHLEDAT ## Prohledá horní řádek matice a vrátí hodnotu určené buňky.
+HYPERLINK = HYPERTEXTOVÝ.ODKAZ ## Vytvoří zástupce nebo odkaz, který otevře dokument uložený na síťovém serveru, v síti intranet nebo Internet.
+INDEX = INDEX ## Pomocí rejstříku zvolí hodnotu z odkazu nebo matice.
+INDIRECT = NEPŘÍMÝ.ODKAZ ## Vrátí odkaz určený textovou hodnotou.
+LOOKUP = VYHLEDAT ## Vyhledá hodnoty ve vektoru nebo matici.
+MATCH = POZVYHLEDAT ## Vyhledá hodnoty v odkazu nebo matici.
+OFFSET = POSUN ## Vrátí posun odkazu od zadaného odkazu.
+ROW = ŘÁDEK ## Vrátí číslo řádku odkazu.
+ROWS = ŘÁDKY ## Vrátí počet řádků v odkazu.
+RTD = RTD ## Načte data reálného času z programu, který podporuje automatizaci modelu COM (Automatizace: Způsob práce s objekty určité aplikace z jiné aplikace nebo nástroje pro vývoj. Automatizace (dříve nazývaná automatizace OLE) je počítačovým standardem a je funkcí modelu COM (Component Object Model).).
+TRANSPOSE = TRANSPOZICE ## Vrátí transponovanou matici.
+VLOOKUP = SVYHLEDAT ## Prohledá první sloupec matice, přesune kurzor v řádku a vrátí hodnotu buňky.
+
+
+##
+## Math and trigonometry functions Matematické a trigonometrické funkce
+##
+ABS = ABS ## Vrátí absolutní hodnotu čísla.
+ACOS = ARCCOS ## Vrátí arkuskosinus čísla.
+ACOSH = ARCCOSH ## Vrátí hyperbolický arkuskosinus čísla.
+ASIN = ARCSIN ## Vrátí arkussinus čísla.
+ASINH = ARCSINH ## Vrátí hyperbolický arkussinus čísla.
+ATAN = ARCTG ## Vrátí arkustangens čísla.
+ATAN2 = ARCTG2 ## Vrátí arkustangens x-ové a y-ové souřadnice.
+ATANH = ARCTGH ## Vrátí hyperbolický arkustangens čísla.
+CEILING = ZAOKR.NAHORU ## Zaokrouhlí číslo na nejbližší celé číslo nebo na nejbližší násobek zadané hodnoty.
+COMBIN = KOMBINACE ## Vrátí počet kombinací pro daný počet položek.
+COS = COS ## Vrátí kosinus čísla.
+COSH = COSH ## Vrátí hyperbolický kosinus čísla.
+DEGREES = DEGREES ## Převede radiány na stupně.
+EVEN = ZAOKROUHLIT.NA.SUDÉ ## Zaokrouhlí číslo nahoru na nejbližší celé sudé číslo.
+EXP = EXP ## Vrátí základ přirozeného logaritmu e umocněný na zadané číslo.
+FACT = FAKTORIÁL ## Vrátí faktoriál čísla.
+FACTDOUBLE = FACTDOUBLE ## Vrátí dvojitý faktoriál čísla.
+FLOOR = ZAOKR.DOLŮ ## Zaokrouhlí číslo dolů, směrem k nule.
+GCD = GCD ## Vrátí největší společný dělitel.
+INT = CELÁ.ČÁST ## Zaokrouhlí číslo dolů na nejbližší celé číslo.
+LCM = LCM ## Vrátí nejmenší společný násobek.
+LN = LN ## Vrátí přirozený logaritmus čísla.
+LOG = LOGZ ## Vrátí logaritmus čísla při zadaném základu.
+LOG10 = LOG ## Vrátí dekadický logaritmus čísla.
+MDETERM = DETERMINANT ## Vrátí determinant matice.
+MINVERSE = INVERZE ## Vrátí inverzní matici.
+MMULT = SOUČIN.MATIC ## Vrátí součin dvou matic.
+MOD = MOD ## Vrátí zbytek po dělení.
+MROUND = MROUND ## Vrátí číslo zaokrouhlené na požadovaný násobek.
+MULTINOMIAL = MULTINOMIAL ## Vrátí mnohočlen z množiny čísel.
+ODD = ZAOKROUHLIT.NA.LICHÉ ## Zaokrouhlí číslo nahoru na nejbližší celé liché číslo.
+PI = PI ## Vrátí hodnotu čísla pí.
+POWER = POWER ## Umocní číslo na zadanou mocninu.
+PRODUCT = SOUČIN ## Vynásobí argumenty funkce.
+QUOTIENT = QUOTIENT ## Vrátí celou část dělení.
+RADIANS = RADIANS ## Převede stupně na radiány.
+RAND = NÁHČÍSLO ## Vrátí náhodné číslo mezi 0 a 1.
+RANDBETWEEN = RANDBETWEEN ## Vrátí náhodné číslo mezi zadanými čísly.
+ROMAN = ROMAN ## Převede arabskou číslici na římskou ve formátu textu.
+ROUND = ZAOKROUHLIT ## Zaokrouhlí číslo na zadaný počet číslic.
+ROUNDDOWN = ROUNDDOWN ## Zaokrouhlí číslo dolů, směrem k nule.
+ROUNDUP = ROUNDUP ## Zaokrouhlí číslo nahoru, směrem od nuly.
+SERIESSUM = SERIESSUM ## Vrátí součet mocninné řady určené podle vzorce.
+SIGN = SIGN ## Vrátí znaménko čísla.
+SIN = SIN ## Vrátí sinus daného úhlu.
+SINH = SINH ## Vrátí hyperbolický sinus čísla.
+SQRT = ODMOCNINA ## Vrátí kladnou druhou odmocninu.
+SQRTPI = SQRTPI ## Vrátí druhou odmocninu výrazu (číslo * pí).
+SUBTOTAL = SUBTOTAL ## Vrátí souhrn v seznamu nebo databázi.
+SUM = SUMA ## Sečte argumenty funkce.
+SUMIF = SUMIF ## Sečte buňky vybrané podle zadaných kritérií.
+SUMIFS = SUMIFS ## Sečte buňky určené více zadanými podmínkami.
+SUMPRODUCT = SOUČIN.SKALÁRNÍ ## Vrátí součet součinů odpovídajících prvků matic.
+SUMSQ = SUMA.ČTVERCŮ ## Vrátí součet čtverců argumentů.
+SUMX2MY2 = SUMX2MY2 ## Vrátí součet rozdílu čtverců odpovídajících hodnot ve dvou maticích.
+SUMX2PY2 = SUMX2PY2 ## Vrátí součet součtu čtverců odpovídajících hodnot ve dvou maticích.
+SUMXMY2 = SUMXMY2 ## Vrátí součet čtverců rozdílů odpovídajících hodnot ve dvou maticích.
+TAN = TGTG ## Vrátí tangens čísla.
+TANH = TGH ## Vrátí hyperbolický tangens čísla.
+TRUNC = USEKNOUT ## Zkrátí číslo na celé číslo.
+
+
+##
+## Statistical functions Statistické funkce
+##
+AVEDEV = PRŮMODCHYLKA ## Vrátí průměrnou hodnotu absolutních odchylek datových bodů od jejich střední hodnoty.
+AVERAGE = PRŮMĚR ## Vrátí průměrnou hodnotu argumentů.
+AVERAGEA = AVERAGEA ## Vrátí průměrnou hodnotu argumentů včetně čísel, textu a logických hodnot.
+AVERAGEIF = AVERAGEIF ## Vrátí průměrnou hodnotu (aritmetický průměr) všech buněk v oblasti, které vyhovují příslušné podmínce.
+AVERAGEIFS = AVERAGEIFS ## Vrátí průměrnou hodnotu (aritmetický průměr) všech buněk vyhovujících několika podmínkám.
+BETADIST = BETADIST ## Vrátí hodnotu součtového rozdělení beta.
+BETAINV = BETAINV ## Vrátí inverzní hodnotu součtového rozdělení pro zadané rozdělení beta.
+BINOMDIST = BINOMDIST ## Vrátí hodnotu binomického rozdělení pravděpodobnosti jednotlivých veličin.
+CHIDIST = CHIDIST ## Vrátí jednostrannou pravděpodobnost rozdělení chí-kvadrát.
+CHIINV = CHIINV ## Vrátí hodnotu funkce inverzní k distribuční funkci jednostranné pravděpodobnosti rozdělení chí-kvadrát.
+CHITEST = CHITEST ## Vrátí test nezávislosti.
+CONFIDENCE = CONFIDENCE ## Vrátí interval spolehlivosti pro střední hodnotu základního souboru.
+CORREL = CORREL ## Vrátí korelační koeficient mezi dvěma množinami dat.
+COUNT = POČET ## Vrátí počet čísel v seznamu argumentů.
+COUNTA = POČET2 ## Vrátí počet hodnot v seznamu argumentů.
+COUNTBLANK = COUNTBLANK ## Spočítá počet prázdných buněk v oblasti.
+COUNTIF = COUNTIF ## Spočítá buňky v oblasti, které odpovídají zadaným kritériím.
+COUNTIFS = COUNTIFS ## Spočítá buňky v oblasti, které odpovídají více kritériím.
+COVAR = COVAR ## Vrátí hodnotu kovariance, průměrnou hodnotu součinů párových odchylek
+CRITBINOM = CRITBINOM ## Vrátí nejmenší hodnotu, pro kterou má součtové binomické rozdělení hodnotu větší nebo rovnu hodnotě kritéria.
+DEVSQ = DEVSQ ## Vrátí součet čtverců odchylek.
+EXPONDIST = EXPONDIST ## Vrátí hodnotu exponenciálního rozdělení.
+FDIST = FDIST ## Vrátí hodnotu rozdělení pravděpodobnosti F.
+FINV = FINV ## Vrátí hodnotu inverzní funkce k distribuční funkci rozdělení F.
+FISHER = FISHER ## Vrátí hodnotu Fisherovy transformace.
+FISHERINV = FISHERINV ## Vrátí hodnotu inverzní funkce k Fisherově transformaci.
+FORECAST = FORECAST ## Vrátí hodnotu lineárního trendu.
+FREQUENCY = ČETNOSTI ## Vrátí četnost rozdělení jako svislou matici.
+FTEST = FTEST ## Vrátí výsledek F-testu.
+GAMMADIST = GAMMADIST ## Vrátí hodnotu rozdělení gama.
+GAMMAINV = GAMMAINV ## Vrátí hodnotu inverzní funkce k distribuční funkci součtového rozdělení gama.
+GAMMALN = GAMMALN ## Vrátí přirozený logaritmus funkce gama, Γ(x).
+GEOMEAN = GEOMEAN ## Vrátí geometrický průměr.
+GROWTH = LOGLINTREND ## Vrátí hodnoty exponenciálního trendu.
+HARMEAN = HARMEAN ## Vrátí harmonický průměr.
+HYPGEOMDIST = HYPGEOMDIST ## Vrátí hodnotu hypergeometrického rozdělení.
+INTERCEPT = INTERCEPT ## Vrátí úsek lineární regresní čáry.
+KURT = KURT ## Vrátí hodnotu excesu množiny dat.
+LARGE = LARGE ## Vrátí k-tou největší hodnotu množiny dat.
+LINEST = LINREGRESE ## Vrátí parametry lineárního trendu.
+LOGEST = LOGLINREGRESE ## Vrátí parametry exponenciálního trendu.
+LOGINV = LOGINV ## Vrátí inverzní funkci k distribuční funkci logaritmicko-normálního rozdělení.
+LOGNORMDIST = LOGNORMDIST ## Vrátí hodnotu součtového logaritmicko-normálního rozdělení.
+MAX = MAX ## Vrátí maximální hodnotu seznamu argumentů.
+MAXA = MAXA ## Vrátí maximální hodnotu seznamu argumentů včetně čísel, textu a logických hodnot.
+MEDIAN = MEDIAN ## Vrátí střední hodnotu zadaných čísel.
+MIN = MIN ## Vrátí minimální hodnotu seznamu argumentů.
+MINA = MINA ## Vrátí nejmenší hodnotu v seznamu argumentů včetně čísel, textu a logických hodnot.
+MODE = MODE ## Vrátí hodnotu, která se v množině dat vyskytuje nejčastěji.
+NEGBINOMDIST = NEGBINOMDIST ## Vrátí hodnotu negativního binomického rozdělení.
+NORMDIST = NORMDIST ## Vrátí hodnotu normálního součtového rozdělení.
+NORMINV = NORMINV ## Vrátí inverzní funkci k funkci normálního součtového rozdělení.
+NORMSDIST = NORMSDIST ## Vrátí hodnotu standardního normálního součtového rozdělení.
+NORMSINV = NORMSINV ## Vrátí inverzní funkci k funkci standardního normálního součtového rozdělení.
+PEARSON = PEARSON ## Vrátí Pearsonův výsledný momentový korelační koeficient.
+PERCENTILE = PERCENTIL ## Vrátí hodnotu k-tého percentilu hodnot v oblasti.
+PERCENTRANK = PERCENTRANK ## Vrátí pořadí hodnoty v množině dat vyjádřené procentuální částí množiny dat.
+PERMUT = PERMUTACE ## Vrátí počet permutací pro zadaný počet objektů.
+POISSON = POISSON ## Vrátí hodnotu distribuční funkce Poissonova rozdělení.
+PROB = PROB ## Vrátí pravděpodobnost výskytu hodnot v oblasti mezi dvěma mezními hodnotami.
+QUARTILE = QUARTIL ## Vrátí hodnotu kvartilu množiny dat.
+RANK = RANK ## Vrátí pořadí čísla v seznamu čísel.
+RSQ = RKQ ## Vrátí druhou mocninu Pearsonova výsledného momentového korelačního koeficientu.
+SKEW = SKEW ## Vrátí zešikmení rozdělení.
+SLOPE = SLOPE ## Vrátí směrnici lineární regresní čáry.
+SMALL = SMALL ## Vrátí k-tou nejmenší hodnotu množiny dat.
+STANDARDIZE = STANDARDIZE ## Vrátí normalizovanou hodnotu.
+STDEV = SMODCH.VÝBĚR ## Vypočte směrodatnou odchylku výběru.
+STDEVA = STDEVA ## Vypočte směrodatnou odchylku výběru včetně čísel, textu a logických hodnot.
+STDEVP = SMODCH ## Vypočte směrodatnou odchylku základního souboru.
+STDEVPA = STDEVPA ## Vypočte směrodatnou odchylku základního souboru včetně čísel, textu a logických hodnot.
+STEYX = STEYX ## Vrátí standardní chybu předpovězené hodnoty y pro každou hodnotu x v regresi.
+TDIST = TDIST ## Vrátí hodnotu Studentova t-rozdělení.
+TINV = TINV ## Vrátí inverzní funkci k distribuční funkci Studentova t-rozdělení.
+TREND = LINTREND ## Vrátí hodnoty lineárního trendu.
+TRIMMEAN = TRIMMEAN ## Vrátí střední hodnotu vnitřní části množiny dat.
+TTEST = TTEST ## Vrátí pravděpodobnost spojenou se Studentovým t-testem.
+VAR = VAR.VÝBĚR ## Vypočte rozptyl výběru.
+VARA = VARA ## Vypočte rozptyl výběru včetně čísel, textu a logických hodnot.
+VARP = VAR ## Vypočte rozptyl základního souboru.
+VARPA = VARPA ## Vypočte rozptyl základního souboru včetně čísel, textu a logických hodnot.
+WEIBULL = WEIBULL ## Vrátí hodnotu Weibullova rozdělení.
+ZTEST = ZTEST ## Vrátí jednostrannou P-hodnotu z-testu.
+
+
+##
+## Text functions Textové funkce
+##
+ASC = ASC ## Změní znaky s plnou šířkou (dvoubajtové)v řetězci znaků na znaky s poloviční šířkou (jednobajtové).
+BAHTTEXT = BAHTTEXT ## Převede číslo na text ve formátu, měny ß (baht).
+CHAR = ZNAK ## Vrátí znak určený číslem kódu.
+CLEAN = VYČISTIT ## Odebere z textu všechny netisknutelné znaky.
+CODE = KÓD ## Vrátí číselný kód prvního znaku zadaného textového řetězce.
+CONCATENATE = CONCATENATE ## Spojí několik textových položek do jedné.
+DOLLAR = KČ ## Převede číslo na text ve formátu měny Kč (česká koruna).
+EXACT = STEJNÉ ## Zkontroluje, zda jsou dvě textové hodnoty shodné.
+FIND = NAJÍT ## Nalezne textovou hodnotu uvnitř jiné (rozlišuje malá a velká písmena).
+FINDB = FINDB ## Nalezne textovou hodnotu uvnitř jiné (rozlišuje malá a velká písmena).
+FIXED = ZAOKROUHLIT.NA.TEXT ## Zformátuje číslo jako text s pevným počtem desetinných míst.
+JIS = JIS ## Změní znaky s poloviční šířkou (jednobajtové) v řetězci znaků na znaky s plnou šířkou (dvoubajtové).
+LEFT = ZLEVA ## Vrátí první znaky textové hodnoty umístěné nejvíce vlevo.
+LEFTB = LEFTB ## Vrátí první znaky textové hodnoty umístěné nejvíce vlevo.
+LEN = DÉLKA ## Vrátí počet znaků textového řetězce.
+LENB = LENB ## Vrátí počet znaků textového řetězce.
+LOWER = MALÁ ## Převede text na malá písmena.
+MID = ČÁST ## Vrátí určitý počet znaků textového řetězce počínaje zadaným místem.
+MIDB = MIDB ## Vrátí určitý počet znaků textového řetězce počínaje zadaným místem.
+PHONETIC = ZVUKOVÉ ## Extrahuje fonetické znaky (furigana) z textového řetězce.
+PROPER = VELKÁ2 ## Převede první písmeno každého slova textové hodnoty na velké.
+REPLACE = NAHRADIT ## Nahradí znaky uvnitř textu.
+REPLACEB = NAHRADITB ## Nahradí znaky uvnitř textu.
+REPT = OPAKOVAT ## Zopakuje text podle zadaného počtu opakování.
+RIGHT = ZPRAVA ## Vrátí první znaky textové hodnoty umístěné nejvíce vpravo.
+RIGHTB = RIGHTB ## Vrátí první znaky textové hodnoty umístěné nejvíce vpravo.
+SEARCH = HLEDAT ## Nalezne textovou hodnotu uvnitř jiné (malá a velká písmena nejsou rozlišována).
+SEARCHB = SEARCHB ## Nalezne textovou hodnotu uvnitř jiné (malá a velká písmena nejsou rozlišována).
+SUBSTITUTE = DOSADIT ## V textovém řetězci nahradí starý text novým.
+T = T ## Převede argumenty na text.
+TEXT = HODNOTA.NA.TEXT ## Zformátuje číslo a převede ho na text.
+TRIM = PROČISTIT ## Odstraní z textu mezery.
+UPPER = VELKÁ ## Převede text na velká písmena.
+VALUE = HODNOTA ## Převede textový argument na číslo.
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config
new file mode 100755
index 0000000..a7aa8fe
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config
@@ -0,0 +1,25 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = kr
+
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #NUL!
+DIV0 = #DIVISION/0!
+VALUE = #VÆRDI!
+REF = #REFERENCE!
+NAME = #NAVN?
+NUM = #NUM!
+NA = #I/T
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions
new file mode 100755
index 0000000..d02aa2e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions
@@ -0,0 +1,416 @@
+##
+## PhpSpreadsheet
+##
+## Data in this file derived from https://www.excel-function-translation.com/
+##
+##
+
+
+##
+## Add-in and Automation functions Tilføjelsesprogram- og automatiseringsfunktioner
+##
+GETPIVOTDATA = HENTPIVOTDATA ## Returnerer data, der er lagret i en pivottabelrapport
+
+
+##
+## Cube functions Kubefunktioner
+##
+CUBEKPIMEMBER = KUBE.KPI.MEDLEM ## Returnerer navn, egenskab og mål for en KPI-indikator og viser navnet og egenskaben i cellen. En KPI-indikator er en målbar størrelse, f.eks. bruttooverskud pr. måned eller personaleudskiftning pr. kvartal, der bruges til at overvåge en organisations præstationer.
+CUBEMEMBER = KUBE.MEDLEM ## Returnerer et medlem eller en tupel fra kubehierarkiet. Bruges til at validere, om et medlem eller en tupel findes i kuben.
+CUBEMEMBERPROPERTY = KUBEMEDLEM.EGENSKAB ## Returnerer værdien af en egenskab for et medlem i kuben. Bruges til at validere, om et medlemsnavn findes i kuben, og returnere den angivne egenskab for medlemmet.
+CUBERANKEDMEMBER = KUBEMEDLEM.RANG ## Returnerer det n'te eller rangordnede medlem i et sæt. Bruges til at returnere et eller flere elementer i et sæt, f.eks. topsælgere eller de 10 bedste elever.
+CUBESET = KUBESÆT ## Definerer et beregnet sæt medlemmer eller tupler ved at sende et sætudtryk til kuben på serveren, som opretter sættet og returnerer det til Microsoft Office Excel.
+CUBESETCOUNT = KUBESÆT.TÆL ## Returnerer antallet af elementer i et sæt.
+CUBEVALUE = KUBEVÆRDI ## Returnerer en sammenlagt (aggregeret) værdi fra en kube.
+
+
+##
+## Database functions Databasefunktioner
+##
+DAVERAGE = DMIDDEL ## Returnerer gennemsnittet af markerede databaseposter
+DCOUNT = DTÆL ## Tæller de celler, der indeholder tal, i en database
+DCOUNTA = DTÆLV ## Tæller udfyldte celler i en database
+DGET = DHENT ## Uddrager en enkelt post, der opfylder de angivne kriterier, fra en database
+DMAX = DMAKS ## Returnerer den største værdi blandt markerede databaseposter
+DMIN = DMIN ## Returnerer den mindste værdi blandt markerede databaseposter
+DPRODUCT = DPRODUKT ## Ganger værdierne i et bestemt felt med poster, der opfylder kriterierne i en database
+DSTDEV = DSTDAFV ## Beregner et skøn over standardafvigelsen baseret på en stikprøve af markerede databaseposter
+DSTDEVP = DSTDAFVP ## Beregner standardafvigelsen baseret på hele populationen af markerede databaseposter
+DSUM = DSUM ## Sammenlægger de tal i feltkolonnen i databasen, der opfylder kriterierne
+DVAR = DVARIANS ## Beregner varians baseret på en stikprøve af markerede databaseposter
+DVARP = DVARIANSP ## Beregner varians baseret på hele populationen af markerede databaseposter
+
+
+##
+## Date and time functions Dato- og klokkeslætsfunktioner
+##
+DATE = DATO ## Returnerer serienummeret for en bestemt dato
+DATEVALUE = DATOVÆRDI ## Konverterer en dato i form af tekst til et serienummer
+DAY = DAG ## Konverterer et serienummer til en dag i måneden
+DAYS360 = DAGE360 ## Beregner antallet af dage mellem to datoer på grundlag af et år med 360 dage
+EDATE = EDATO ## Returnerer serienummeret for den dato, der ligger det angivne antal måneder før eller efter startdatoen
+EOMONTH = SLUT.PÅ.MÅNED ## Returnerer serienummeret på den sidste dag i måneden før eller efter et angivet antal måneder
+HOUR = TIME ## Konverterer et serienummer til en time
+MINUTE = MINUT ## Konverterer et serienummer til et minut
+MONTH = MÅNED ## Konverterer et serienummer til en måned
+NETWORKDAYS = ANTAL.ARBEJDSDAGE ## Returnerer antallet af hele arbejdsdage mellem to datoer
+NOW = NU ## Returnerer serienummeret for den aktuelle dato eller det aktuelle klokkeslæt
+SECOND = SEKUND ## Konverterer et serienummer til et sekund
+TIME = KLOKKESLÆT ## Returnerer serienummeret for et bestemt klokkeslæt
+TIMEVALUE = TIDSVÆRDI ## Konverterer et klokkeslæt i form af tekst til et serienummer
+TODAY = IDAG ## Returnerer serienummeret for dags dato
+WEEKDAY = UGEDAG ## Konverterer et serienummer til en ugedag
+WEEKNUM = UGE.NR ## Konverterer et serienummer til et tal, der angiver ugenummeret i året
+WORKDAY = ARBEJDSDAG ## Returnerer serienummeret for dagen før eller efter det angivne antal arbejdsdage
+YEAR = ÅR ## Konverterer et serienummer til et år
+YEARFRAC = ÅR.BRØK ## Returnerer årsbrøken, der repræsenterer antallet af hele dage mellem startdato og slutdato
+
+
+##
+## Engineering functions Tekniske funktioner
+##
+BESSELI = BESSELI ## Returnerer den modificerede Bessel-funktion In(x)
+BESSELJ = BESSELJ ## Returnerer Bessel-funktionen Jn(x)
+BESSELK = BESSELK ## Returnerer den modificerede Bessel-funktion Kn(x)
+BESSELY = BESSELY ## Returnerer Bessel-funktionen Yn(x)
+BIN2DEC = BIN.TIL.DEC ## Konverterer et binært tal til et decimaltal
+BIN2HEX = BIN.TIL.HEX ## Konverterer et binært tal til et heksadecimalt tal
+BIN2OCT = BIN.TIL.OKT ## Konverterer et binært tal til et oktaltal.
+COMPLEX = KOMPLEKS ## Konverterer reelle og imaginære koefficienter til et komplekst tal
+CONVERT = KONVERTER ## Konverterer et tal fra én måleenhed til en anden
+DEC2BIN = DEC.TIL.BIN ## Konverterer et decimaltal til et binært tal
+DEC2HEX = DEC.TIL.HEX ## Konverterer et decimaltal til et heksadecimalt tal
+DEC2OCT = DEC.TIL.OKT ## Konverterer et decimaltal til et oktaltal
+DELTA = DELTA ## Tester, om to værdier er ens
+ERF = FEJLFUNK ## Returner fejlfunktionen
+ERFC = FEJLFUNK.KOMP ## Returnerer den komplementære fejlfunktion
+GESTEP = GETRIN ## Tester, om et tal er større end en grænseværdi
+HEX2BIN = HEX.TIL.BIN ## Konverterer et heksadecimalt tal til et binært tal
+HEX2DEC = HEX.TIL.DEC ## Konverterer et decimaltal til et heksadecimalt tal
+HEX2OCT = HEX.TIL.OKT ## Konverterer et heksadecimalt tal til et oktaltal
+IMABS = IMAGABS ## Returnerer den absolutte værdi (modulus) for et komplekst tal
+IMAGINARY = IMAGINÆR ## Returnerer den imaginære koefficient for et komplekst tal
+IMARGUMENT = IMAGARGUMENT ## Returnerer argumentet theta, en vinkel udtrykt i radianer
+IMCONJUGATE = IMAGKONJUGERE ## Returnerer den komplekse konjugation af et komplekst tal
+IMCOS = IMAGCOS ## Returnerer et komplekst tals cosinus
+IMDIV = IMAGDIV ## Returnerer kvotienten for to komplekse tal
+IMEXP = IMAGEKSP ## Returnerer et komplekst tals eksponentialfunktion
+IMLN = IMAGLN ## Returnerer et komplekst tals naturlige logaritme
+IMLOG10 = IMAGLOG10 ## Returnerer et komplekst tals sædvanlige logaritme (titalslogaritme)
+IMLOG2 = IMAGLOG2 ## Returnerer et komplekst tals sædvanlige logaritme (totalslogaritme)
+IMPOWER = IMAGPOTENS ## Returnerer et komplekst tal opløftet i en heltalspotens
+IMPRODUCT = IMAGPRODUKT ## Returnerer produktet af komplekse tal
+IMREAL = IMAGREELT ## Returnerer den reelle koefficient for et komplekst tal
+IMSIN = IMAGSIN ## Returnerer et komplekst tals sinus
+IMSQRT = IMAGKVROD ## Returnerer et komplekst tals kvadratrod
+IMSUB = IMAGSUB ## Returnerer forskellen mellem to komplekse tal
+IMSUM = IMAGSUM ## Returnerer summen af komplekse tal
+OCT2BIN = OKT.TIL.BIN ## Konverterer et oktaltal til et binært tal
+OCT2DEC = OKT.TIL.DEC ## Konverterer et oktaltal til et decimaltal
+OCT2HEX = OKT.TIL.HEX ## Konverterer et oktaltal til et heksadecimalt tal
+
+
+##
+## Financial functions Finansielle funktioner
+##
+ACCRINT = PÅLØBRENTE ## Returnerer den påløbne rente for et værdipapir med periodiske renteudbetalinger
+ACCRINTM = PÅLØBRENTE.UDLØB ## Returnerer den påløbne rente for et værdipapir, hvor renteudbetalingen finder sted ved papirets udløb
+AMORDEGRC = AMORDEGRC ## Returnerer afskrivningsbeløbet for hver regnskabsperiode ved hjælp af en afskrivningskoefficient
+AMORLINC = AMORLINC ## Returnerer afskrivningsbeløbet for hver regnskabsperiode
+COUPDAYBS = KUPONDAGE.SA ## Returnerer antallet af dage fra starten af kuponperioden til afregningsdatoen
+COUPDAYS = KUPONDAGE.A ## Returnerer antallet af dage fra begyndelsen af kuponperioden til afregningsdatoen
+COUPDAYSNC = KUPONDAGE.ANK ## Returnerer antallet af dage i den kuponperiode, der indeholder afregningsdatoen
+COUPNCD = KUPONDAG.NÆSTE ## Returnerer den næste kupondato efter afregningsdatoen
+COUPNUM = KUPONBETALINGER ## Returnerer antallet af kuponudbetalinger mellem afregnings- og udløbsdatoen
+COUPPCD = KUPONDAG.FORRIGE ## Returnerer den forrige kupondato før afregningsdatoen
+CUMIPMT = AKKUM.RENTE ## Returnerer den akkumulerede rente, der betales på et lån mellem to perioder
+CUMPRINC = AKKUM.HOVEDSTOL ## Returnerer den akkumulerede nedbringelse af hovedstol mellem to perioder
+DB = DB ## Returnerer afskrivningen på et aktiv i en angivet periode ved anvendelse af saldometoden
+DDB = DSA ## Returnerer afskrivningsbeløbet for et aktiv over en bestemt periode ved anvendelse af dobbeltsaldometoden eller en anden afskrivningsmetode, som du angiver
+DISC = DISKONTO ## Returnerer et værdipapirs diskonto
+DOLLARDE = KR.DECIMAL ## Konverterer en kronepris udtrykt som brøk til en kronepris udtrykt som decimaltal
+DOLLARFR = KR.BRØK ## Konverterer en kronepris udtrykt som decimaltal til en kronepris udtrykt som brøk
+DURATION = VARIGHED ## Returnerer den årlige løbetid for et værdipapir med periodiske renteudbetalinger
+EFFECT = EFFEKTIV.RENTE ## Returnerer den årlige effektive rente
+FV = FV ## Returnerer fremtidsværdien af en investering
+FVSCHEDULE = FVTABEL ## Returnerer den fremtidige værdi af en hovedstol, når der er tilskrevet rente og rentes rente efter forskellige rentesatser
+INTRATE = RENTEFOD ## Returnerer renten på et fuldt ud investeret værdipapir
+IPMT = R.YDELSE ## Returnerer renten fra en investering for en given periode
+IRR = IA ## Returnerer den interne rente for en række pengestrømme
+ISPMT = ISPMT ## Beregner den betalte rente i løbet af en bestemt investeringsperiode
+MDURATION = MVARIGHED ## Returnerer Macauleys modificerede løbetid for et værdipapir med en formodet pari på kr. 100
+MIRR = MIA ## Returnerer den interne forrentning, hvor positive og negative pengestrømme finansieres til forskellig rente
+NOMINAL = NOMINEL ## Returnerer den årlige nominelle rente
+NPER = NPER ## Returnerer antallet af perioder for en investering
+NPV = NUTIDSVÆRDI ## Returnerer nettonutidsværdien for en investering baseret på en række periodiske pengestrømme og en diskonteringssats
+ODDFPRICE = ULIGE.KURS.PÅLYDENDE ## Returnerer kursen pr. kr. 100 nominel værdi for et værdipapir med en ulige (kort eller lang) første periode
+ODDFYIELD = ULIGE.FØRSTE.AFKAST ## Returnerer afkastet for et værdipapir med ulige første periode
+ODDLPRICE = ULIGE.SIDSTE.KURS ## Returnerer kursen pr. kr. 100 nominel værdi for et værdipapir med ulige sidste periode
+ODDLYIELD = ULIGE.SIDSTE.AFKAST ## Returnerer afkastet for et værdipapir med ulige sidste periode
+PMT = YDELSE ## Returnerer renten fra en investering for en given periode
+PPMT = H.YDELSE ## Returnerer ydelsen på hovedstolen for en investering i en given periode
+PRICE = KURS ## Returnerer kursen pr. kr 100 nominel værdi for et værdipapir med periodiske renteudbetalinger
+PRICEDISC = KURS.DISKONTO ## Returnerer kursen pr. kr 100 nominel værdi for et diskonteret værdipapir
+PRICEMAT = KURS.UDLØB ## Returnerer kursen pr. kr 100 nominel værdi for et værdipapir, hvor renten udbetales ved papirets udløb
+PV = NV ## Returnerer den nuværende værdi af en investering
+RATE = RENTE ## Returnerer renten i hver periode for en annuitet
+RECEIVED = MODTAGET.VED.UDLØB ## Returnerer det beløb, der modtages ved udløbet af et fuldt ud investeret værdipapir
+SLN = LA ## Returnerer den lineære afskrivning for et aktiv i en enkelt periode
+SYD = ÅRSAFSKRIVNING ## Returnerer den årlige afskrivning på et aktiv i en bestemt periode
+TBILLEQ = STATSOBLIGATION ## Returnerer det obligationsækvivalente afkast for en statsobligation
+TBILLPRICE = STATSOBLIGATION.KURS ## Returnerer kursen pr. kr 100 nominel værdi for en statsobligation
+TBILLYIELD = STATSOBLIGATION.AFKAST ## Returnerer en afkastet på en statsobligation
+VDB = VSA ## Returnerer afskrivningen på et aktiv i en angivet periode, herunder delperioder, ved brug af dobbeltsaldometoden
+XIRR = INTERN.RENTE ## Returnerer den interne rente for en plan over pengestrømme, der ikke behøver at være periodiske
+XNPV = NETTO.NUTIDSVÆRDI ## Returnerer nutidsværdien for en plan over pengestrømme, der ikke behøver at være periodiske
+YIELD = AFKAST ## Returnerer afkastet for et værdipapir med periodiske renteudbetalinger
+YIELDDISC = AFKAST.DISKONTO ## Returnerer det årlige afkast for et diskonteret værdipapir, f.eks. en statsobligation
+YIELDMAT = AFKAST.UDLØBSDATO ## Returnerer det årlige afkast for et værdipapir, hvor renten udbetales ved papirets udløb
+
+
+##
+## Information functions Informationsfunktioner
+##
+CELL = CELLE ## Returnerer oplysninger om formatering, placering eller indhold af en celle
+ERROR.TYPE = FEJLTYPE ## Returnerer et tal, der svarer til en fejltype
+INFO = INFO ## Returnerer oplysninger om det aktuelle operativmiljø
+ISBLANK = ER.TOM ## Returnerer SAND, hvis værdien er tom
+ISERR = ER.FJL ## Returnerer SAND, hvis værdien er en fejlværdi undtagen #I/T
+ISERROR = ER.FEJL ## Returnerer SAND, hvis værdien er en fejlværdi
+ISEVEN = ER.LIGE ## Returnerer SAND, hvis tallet er lige
+ISLOGICAL = ER.LOGISK ## Returnerer SAND, hvis værdien er en logisk værdi
+ISNA = ER.IKKE.TILGÆNGELIG ## Returnerer SAND, hvis værdien er fejlværdien #I/T
+ISNONTEXT = ER.IKKE.TEKST ## Returnerer SAND, hvis værdien ikke er tekst
+ISNUMBER = ER.TAL ## Returnerer SAND, hvis værdien er et tal
+ISODD = ER.ULIGE ## Returnerer SAND, hvis tallet er ulige
+ISREF = ER.REFERENCE ## Returnerer SAND, hvis værdien er en reference
+ISTEXT = ER.TEKST ## Returnerer SAND, hvis værdien er tekst
+N = TAL ## Returnerer en værdi konverteret til et tal
+NA = IKKE.TILGÆNGELIG ## Returnerer fejlværdien #I/T
+TYPE = VÆRDITYPE ## Returnerer et tal, der angiver datatypen for en værdi
+
+
+##
+## Logical functions Logiske funktioner
+##
+AND = OG ## Returnerer SAND, hvis alle argumenterne er sande
+FALSE = FALSK ## Returnerer den logiske værdi FALSK
+IF = HVIS ## Angiver en logisk test, der skal udføres
+IFERROR = HVIS.FEJL ## Returnerer en værdi, du angiver, hvis en formel evauleres som en fejl. Returnerer i modsat fald resultatet af formlen
+NOT = IKKE ## Vender argumentets logik om
+OR = ELLER ## Returneret værdien SAND, hvis mindst ét argument er sandt
+TRUE = SAND ## Returnerer den logiske værdi SAND
+
+
+##
+## Lookup and reference functions Opslags- og referencefunktioner
+##
+ADDRESS = ADRESSE ## Returnerer en reference som tekst til en enkelt celle i et regneark
+AREAS = OMRÅDER ## Returnerer antallet af områder i en reference
+CHOOSE = VÆLG ## Vælger en værdi på en liste med værdier
+COLUMN = KOLONNE ## Returnerer kolonnenummeret i en reference
+COLUMNS = KOLONNER ## Returnerer antallet af kolonner i en reference
+HLOOKUP = VOPSLAG ## Søger i den øverste række af en matrix og returnerer værdien af den angivne celle
+HYPERLINK = HYPERLINK ## Opretter en genvej kaldet et hyperlink, der åbner et dokument, som er lagret på en netværksserver, på et intranet eller på internettet
+INDEX = INDEKS ## Anvender et indeks til at vælge en værdi fra en reference eller en matrix
+INDIRECT = INDIREKTE ## Returnerer en reference, der er angivet af en tekstværdi
+LOOKUP = SLÅ.OP ## Søger værdier i en vektor eller en matrix
+MATCH = SAMMENLIGN ## Søger værdier i en reference eller en matrix
+OFFSET = FORSKYDNING ## Returnerer en reference forskudt i forhold til en given reference
+ROW = RÆKKE ## Returnerer rækkenummeret for en reference
+ROWS = RÆKKER ## Returnerer antallet af rækker i en reference
+RTD = RTD ## Henter realtidsdata fra et program, der understøtter COM-automatisering (Automation: En metode til at arbejde med objekter fra et andet program eller udviklingsværktøj. Automation, som tidligere blev kaldt OLE Automation, er en industristandard og en funktion i COM (Component Object Model).)
+TRANSPOSE = TRANSPONER ## Returnerer en transponeret matrix
+VLOOKUP = LOPSLAG ## Søger i øverste række af en matrix og flytter på tværs af rækken for at returnere en celleværdi
+
+
+##
+## Math and trigonometry functions Matematiske og trigonometriske funktioner
+##
+ABS = ABS ## Returnerer den absolutte værdi af et tal
+ACOS = ARCCOS ## Returnerer et tals arcus cosinus
+ACOSH = ARCCOSH ## Returnerer den inverse hyperbolske cosinus af tal
+ASIN = ARCSIN ## Returnerer et tals arcus sinus
+ASINH = ARCSINH ## Returnerer den inverse hyperbolske sinus for tal
+ATAN = ARCTAN ## Returnerer et tals arcus tangens
+ATAN2 = ARCTAN2 ## Returnerer de angivne x- og y-koordinaters arcus tangens
+ATANH = ARCTANH ## Returnerer et tals inverse hyperbolske tangens
+CEILING = AFRUND.LOFT ## Afrunder et tal til nærmeste heltal eller til nærmeste multiplum af betydning
+COMBIN = KOMBIN ## Returnerer antallet af kombinationer for et givet antal objekter
+COS = COS ## Returnerer et tals cosinus
+COSH = COSH ## Returnerer den inverse hyperbolske cosinus af et tal
+DEGREES = GRADER ## Konverterer radianer til grader
+EVEN = LIGE ## Runder et tal op til nærmeste lige heltal
+EXP = EKSP ## Returnerer e opløftet til en potens af et angivet tal
+FACT = FAKULTET ## Returnerer et tals fakultet
+FACTDOUBLE = DOBBELT.FAKULTET ## Returnerer et tals dobbelte fakultet
+FLOOR = AFRUND.GULV ## Runder et tal ned mod nul
+GCD = STØRSTE.FÆLLES.DIVISOR ## Returnerer den største fælles divisor
+INT = HELTAL ## Nedrunder et tal til det nærmeste heltal
+LCM = MINDSTE.FÆLLES.MULTIPLUM ## Returnerer det mindste fælles multiplum
+LN = LN ## Returnerer et tals naturlige logaritme
+LOG = LOG ## Returnerer logaritmen for et tal på grundlag af et angivet grundtal
+LOG10 = LOG10 ## Returnerer titalslogaritmen af et tal
+MDETERM = MDETERM ## Returnerer determinanten for en matrix
+MINVERSE = MINVERT ## Returnerer den inverse matrix for en matrix
+MMULT = MPRODUKT ## Returnerer matrixproduktet af to matrixer
+MOD = REST ## Returnerer restværdien fra division
+MROUND = MAFRUND ## Returnerer et tal afrundet til det ønskede multiplum
+MULTINOMIAL = MULTINOMIAL ## Returnerer et multinomialt talsæt
+ODD = ULIGE ## Runder et tal op til nærmeste ulige heltal
+PI = PI ## Returnerer værdien af pi
+POWER = POTENS ## Returnerer resultatet af et tal opløftet til en potens
+PRODUCT = PRODUKT ## Multiplicerer argumenterne
+QUOTIENT = KVOTIENT ## Returnerer heltalsdelen ved division
+RADIANS = RADIANER ## Konverterer grader til radianer
+RAND = SLUMP ## Returnerer et tilfældigt tal mellem 0 og 1
+RANDBETWEEN = SLUMP.MELLEM ## Returnerer et tilfældigt tal mellem de tal, der angives
+ROMAN = ROMERTAL ## Konverterer et arabertal til romertal som tekst
+ROUND = AFRUND ## Afrunder et tal til et angivet antal decimaler
+ROUNDDOWN = RUND.NED ## Runder et tal ned mod nul
+ROUNDUP = RUND.OP ## Runder et tal op, væk fra 0 (nul)
+SERIESSUM = SERIESUM ## Returnerer summen af en potensserie baseret på en formel
+SIGN = FORTEGN ## Returnerer et tals fortegn
+SIN = SIN ## Returnerer en given vinkels sinusværdi
+SINH = SINH ## Returnerer den hyperbolske sinus af et tal
+SQRT = KVROD ## Returnerer en positiv kvadratrod
+SQRTPI = KVRODPI ## Returnerer kvadratroden af (tal * pi;)
+SUBTOTAL = SUBTOTAL ## Returnerer en subtotal på en liste eller i en database
+SUM = SUM ## Lægger argumenterne sammen
+SUMIF = SUM.HVIS ## Lægger de celler sammen, der er specificeret af et givet kriterium.
+SUMIFS = SUM.HVISER ## Lægger de celler i et område sammen, der opfylder flere kriterier.
+SUMPRODUCT = SUMPRODUKT ## Returnerer summen af produkter af ens matrixkomponenter
+SUMSQ = SUMKV ## Returnerer summen af argumenternes kvadrater
+SUMX2MY2 = SUMX2MY2 ## Returnerer summen af differensen mellem kvadrater af ens værdier i to matrixer
+SUMX2PY2 = SUMX2PY2 ## Returnerer summen af summen af kvadrater af tilsvarende værdier i to matrixer
+SUMXMY2 = SUMXMY2 ## Returnerer summen af kvadrater af differenser mellem ens værdier i to matrixer
+TAN = TAN ## Returnerer et tals tangens
+TANH = TANH ## Returnerer et tals hyperbolske tangens
+TRUNC = AFKORT ## Afkorter et tal til et heltal
+
+
+##
+## Statistical functions Statistiske funktioner
+##
+AVEDEV = MAD ## Returnerer den gennemsnitlige numeriske afvigelse fra stikprøvens middelværdi
+AVERAGE = MIDDEL ## Returnerer middelværdien af argumenterne
+AVERAGEA = MIDDELV ## Returnerer middelværdien af argumenterne og medtager tal, tekst og logiske værdier
+AVERAGEIF = MIDDEL.HVIS ## Returnerer gennemsnittet (den aritmetiske middelværdi) af alle de celler, der opfylder et givet kriterium, i et område
+AVERAGEIFS = MIDDEL.HVISER ## Returnerer gennemsnittet (den aritmetiske middelværdi) af alle de celler, der opfylder flere kriterier.
+BETADIST = BETAFORDELING ## Returnerer den kumulative betafordelingsfunktion
+BETAINV = BETAINV ## Returnerer den inverse kumulative fordelingsfunktion for en angivet betafordeling
+BINOMDIST = BINOMIALFORDELING ## Returnerer punktsandsynligheden for binomialfordelingen
+CHIDIST = CHIFORDELING ## Returnerer fraktilsandsynligheden for en chi2-fordeling
+CHIINV = CHIINV ## Returnerer den inverse fraktilsandsynlighed for en chi2-fordeling
+CHITEST = CHITEST ## Foretager en test for uafhængighed
+CONFIDENCE = KONFIDENSINTERVAL ## Returnerer et konfidensinterval for en population
+CORREL = KORRELATION ## Returnerer korrelationskoefficienten mellem to datasæt
+COUNT = TÆL ## Tæller antallet af tal på en liste med argumenter
+COUNTA = TÆLV ## Tæller antallet af værdier på en liste med argumenter
+COUNTBLANK = ANTAL.BLANKE ## Tæller antallet af tomme celler i et område
+COUNTIF = TÆLHVIS ## Tæller antallet af celler, som opfylder de givne kriterier, i et område
+COUNTIFS = TÆL.HVISER ## Tæller antallet af de celler, som opfylder flere kriterier, i et område
+COVAR = KOVARIANS ## Beregner kovariansen mellem to stokastiske variabler
+CRITBINOM = KRITBINOM ## Returnerer den mindste værdi for x, for hvilken det gælder, at fordelingsfunktionen er mindre end eller lig med kriterieværdien.
+DEVSQ = SAK ## Returnerer summen af de kvadrerede afvigelser fra middelværdien
+EXPONDIST = EKSPFORDELING ## Returnerer eksponentialfordelingen
+FDIST = FFORDELING ## Returnerer fraktilsandsynligheden for F-fordelingen
+FINV = FINV ## Returnerer den inverse fraktilsandsynlighed for F-fordelingen
+FISHER = FISHER ## Returnerer Fisher-transformationen
+FISHERINV = FISHERINV ## Returnerer den inverse Fisher-transformation
+FORECAST = PROGNOSE ## Returnerer en prognoseværdi baseret på lineær tendens
+FREQUENCY = FREKVENS ## Returnerer en frekvensfordeling i en søjlevektor
+FTEST = FTEST ## Returnerer resultatet af en F-test til sammenligning af varians
+GAMMADIST = GAMMAFORDELING ## Returnerer fordelingsfunktionen for gammafordelingen
+GAMMAINV = GAMMAINV ## Returnerer den inverse fordelingsfunktion for gammafordelingen
+GAMMALN = GAMMALN ## Returnerer den naturlige logaritme til gammafordelingen, G(x)
+GEOMEAN = GEOMIDDELVÆRDI ## Returnerer det geometriske gennemsnit
+GROWTH = FORØGELSE ## Returnerer værdier langs en eksponentiel tendens
+HARMEAN = HARMIDDELVÆRDI ## Returnerer det harmoniske gennemsnit
+HYPGEOMDIST = HYPGEOFORDELING ## Returnerer punktsandsynligheden i en hypergeometrisk fordeling
+INTERCEPT = SKÆRING ## Returnerer afskæringsværdien på y-aksen i en lineær regression
+KURT = TOPSTEJL ## Returnerer kurtosisværdien for en stokastisk variabel
+LARGE = STOR ## Returnerer den k'te største værdi i et datasæt
+LINEST = LINREGR ## Returnerer parameterestimaterne for en lineær tendens
+LOGEST = LOGREGR ## Returnerer parameterestimaterne for en eksponentiel tendens
+LOGINV = LOGINV ## Returnerer den inverse fordelingsfunktion for lognormalfordelingen
+LOGNORMDIST = LOGNORMFORDELING ## Returnerer fordelingsfunktionen for lognormalfordelingen
+MAX = MAKS ## Returnerer den maksimale værdi på en liste med argumenter.
+MAXA = MAKSV ## Returnerer den maksimale værdi på en liste med argumenter og medtager tal, tekst og logiske værdier
+MEDIAN = MEDIAN ## Returnerer medianen for de angivne tal
+MIN = MIN ## Returnerer den mindste værdi på en liste med argumenter.
+MINA = MINV ## Returnerer den mindste værdi på en liste med argumenter og medtager tal, tekst og logiske værdier
+MODE = HYPPIGST ## Returnerer den hyppigste værdi i et datasæt
+NEGBINOMDIST = NEGBINOMFORDELING ## Returnerer den negative binomialfordeling
+NORMDIST = NORMFORDELING ## Returnerer fordelingsfunktionen for normalfordelingen
+NORMINV = NORMINV ## Returnerer den inverse fordelingsfunktion for normalfordelingen
+NORMSDIST = STANDARDNORMFORDELING ## Returnerer fordelingsfunktionen for standardnormalfordelingen
+NORMSINV = STANDARDNORMINV ## Returnerer den inverse fordelingsfunktion for standardnormalfordelingen
+PEARSON = PEARSON ## Returnerer Pearsons korrelationskoefficient
+PERCENTILE = FRAKTIL ## Returnerer den k'te fraktil for datasættet
+PERCENTRANK = PROCENTPLADS ## Returnerer den procentuelle rang for en given værdi i et datasæt
+PERMUT = PERMUT ## Returnerer antallet af permutationer for et givet sæt objekter
+POISSON = POISSON ## Returnerer fordelingsfunktionen for en Poisson-fordeling
+PROB = SANDSYNLIGHED ## Returnerer intervalsandsynligheden
+QUARTILE = KVARTIL ## Returnerer kvartilen i et givet datasæt
+RANK = PLADS ## Returnerer rangen for et tal på en liste med tal
+RSQ = FORKLARINGSGRAD ## Returnerer R2-værdien fra en simpel lineær regression
+SKEW = SKÆVHED ## Returnerer skævheden for en stokastisk variabel
+SLOPE = HÆLDNING ## Returnerer estimatet på hældningen fra en simpel lineær regression
+SMALL = MINDSTE ## Returnerer den k'te mindste værdi i datasættet
+STANDARDIZE = STANDARDISER ## Returnerer en standardiseret værdi
+STDEV = STDAFV ## Estimerer standardafvigelsen på basis af en stikprøve
+STDEVA = STDAFVV ## Beregner standardafvigelsen på basis af en prøve og medtager tal, tekst og logiske værdier
+STDEVP = STDAFVP ## Beregner standardafvigelsen på basis af en hel population
+STDEVPA = STDAFVPV ## Beregner standardafvigelsen på basis af en hel population og medtager tal, tekst og logiske værdier
+STEYX = STFYX ## Returnerer standardafvigelsen for de estimerede y-værdier i den simple lineære regression
+TDIST = TFORDELING ## Returnerer fordelingsfunktionen for Student's t-fordeling
+TINV = TINV ## Returnerer den inverse fordelingsfunktion for Student's t-fordeling
+TREND = TENDENS ## Returnerer værdi under antagelse af en lineær tendens
+TRIMMEAN = TRIMMIDDELVÆRDI ## Returnerer den trimmede middelværdi for datasættet
+TTEST = TTEST ## Returnerer den sandsynlighed, der er forbundet med Student's t-test
+VAR = VARIANS ## Beregner variansen på basis af en prøve
+VARA = VARIANSV ## Beregner variansen på basis af en prøve og medtager tal, tekst og logiske værdier
+VARP = VARIANSP ## Beregner variansen på basis af hele populationen
+VARPA = VARIANSPV ## Beregner variansen på basis af hele populationen og medtager tal, tekst og logiske værdier
+WEIBULL = WEIBULL ## Returnerer fordelingsfunktionen for Weibull-fordelingen
+ZTEST = ZTEST ## Returnerer sandsynlighedsværdien ved en en-sidet z-test
+
+
+##
+## Text functions Tekstfunktioner
+##
+ASC = ASC ## Ændrer engelske tegn i fuld bredde (dobbelt-byte) eller katakana i en tegnstreng til tegn i halv bredde (enkelt-byte)
+BAHTTEXT = BAHTTEKST ## Konverterer et tal til tekst ved hjælp af valutaformatet ß (baht)
+CHAR = TEGN ## Returnerer det tegn, der svarer til kodenummeret
+CLEAN = RENS ## Fjerner alle tegn, der ikke kan udskrives, fra tekst
+CODE = KODE ## Returnerer en numerisk kode for det første tegn i en tekststreng
+CONCATENATE = SAMMENKÆDNING ## Sammenkæder adskillige tekstelementer til ét tekstelement
+DOLLAR = KR ## Konverterer et tal til tekst ved hjælp af valutaformatet kr. (kroner)
+EXACT = EKSAKT ## Kontrollerer, om to tekstværdier er identiske
+FIND = FIND ## Søger efter en tekstværdi i en anden tekstværdi (der skelnes mellem store og små bogstaver)
+FINDB = FINDB ## Søger efter en tekstværdi i en anden tekstværdi (der skelnes mellem store og små bogstaver)
+FIXED = FAST ## Formaterer et tal som tekst med et fast antal decimaler
+JIS = JIS ## Ændrer engelske tegn i halv bredde (enkelt-byte) eller katakana i en tegnstreng til tegn i fuld bredde (dobbelt-byte)
+LEFT = VENSTRE ## Returnerer tegnet længst til venstre i en tekstværdi
+LEFTB = VENSTREB ## Returnerer tegnet længst til venstre i en tekstværdi
+LEN = LÆNGDE ## Returnerer antallet af tegn i en tekststreng
+LENB = LÆNGDEB ## Returnerer antallet af tegn i en tekststreng
+LOWER = SMÅ.BOGSTAVER ## Konverterer tekst til små bogstaver
+MID = MIDT ## Returnerer et bestemt antal tegn fra en tekststreng fra og med den angivne startposition
+MIDB = MIDTB ## Returnerer et bestemt antal tegn fra en tekststreng fra og med den angivne startposition
+PHONETIC = FONETISK ## Uddrager de fonetiske (furigana) tegn fra en tekststreng
+PROPER = STORT.FORBOGSTAV ## Konverterer første bogstav i hvert ord i teksten til stort bogstav
+REPLACE = ERSTAT ## Erstatter tegn i tekst
+REPLACEB = ERSTATB ## Erstatter tegn i tekst
+REPT = GENTAG ## Gentager tekst et givet antal gange
+RIGHT = HØJRE ## Returnerer tegnet længste til højre i en tekstværdi
+RIGHTB = HØJREB ## Returnerer tegnet længste til højre i en tekstværdi
+SEARCH = SØG ## Søger efter en tekstværdi i en anden tekstværdi (der skelnes ikke mellem store og små bogstaver)
+SEARCHB = SØGB ## Søger efter en tekstværdi i en anden tekstværdi (der skelnes ikke mellem store og små bogstaver)
+SUBSTITUTE = UDSKIFT ## Udskifter gammel tekst med ny tekst i en tekststreng
+T = T ## Konverterer argumenterne til tekst
+TEXT = TEKST ## Formaterer et tal og konverterer det til tekst
+TRIM = FJERN.OVERFLØDIGE.BLANKE ## Fjerner mellemrum fra tekst
+UPPER = STORE.BOGSTAVER ## Konverterer tekst til store bogstaver
+VALUE = VÆRDI ## Konverterer et tekstargument til et tal
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config
new file mode 100755
index 0000000..9751c4b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config
@@ -0,0 +1,24 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = €
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #NULL!
+DIV0 = #DIV/0!
+VALUE = #WERT!
+REF = #BEZUG!
+NAME = #NAME?
+NUM = #ZAHL!
+NA = #NV
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions
new file mode 100755
index 0000000..01df42f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions
@@ -0,0 +1,416 @@
+##
+## PhpSpreadsheet
+##
+## Data in this file derived from https://www.excel-function-translation.com/
+##
+##
+
+
+##
+## Add-in and Automation functions Add-In- und Automatisierungsfunktionen
+##
+GETPIVOTDATA = PIVOTDATENZUORDNEN ## In einem PivotTable-Bericht gespeicherte Daten werden zurückgegeben.
+
+
+##
+## Cube functions Cubefunktionen
+##
+CUBEKPIMEMBER = CUBEKPIELEMENT ## Gibt Name, Eigenschaft und Measure eines Key Performance Indicators (KPI) zurück und zeigt den Namen und die Eigenschaft in der Zelle an. Ein KPI ist ein quantifizierbares Maß, wie z. B. der monatliche Bruttogewinn oder die vierteljährliche Mitarbeiterfluktuation, mit dessen Hilfe das Leistungsverhalten eines Unternehmens überwacht werden kann.
+CUBEMEMBER = CUBEELEMENT ## Gibt ein Element oder ein Tuple in einer Cubehierarchie zurück. Wird verwendet, um zu überprüfen, ob das Element oder Tuple im Cube vorhanden ist.
+CUBEMEMBERPROPERTY = CUBEELEMENTEIGENSCHAFT ## Gibt den Wert einer Elementeigenschaft im Cube zurück. Wird verwendet, um zu überprüfen, ob ein Elementname im Cube vorhanden ist, und um die für dieses Element angegebene Eigenschaft zurückzugeben.
+CUBERANKEDMEMBER = CUBERANGELEMENT ## Gibt das n-te oder n-rangige Element in einer Menge zurück. Wird verwendet, um mindestens ein Element in einer Menge zurückzugeben, wie z. B. bester Vertriebsmitarbeiter oder 10 beste Kursteilnehmer.
+CUBESET = CUBEMENGE ## Definiert eine berechnete Menge Elemente oder Tuples durch Senden eines Mengenausdrucks an den Cube auf dem Server, der die Menge erstellt und an Microsoft Office Excel zurückgibt.
+CUBESETCOUNT = CUBEMENGENANZAHL ## Gibt die Anzahl der Elemente in einer Menge zurück.
+CUBEVALUE = CUBEWERT ## Gibt einen Aggregatwert aus einem Cube zurück.
+
+
+##
+## Database functions Datenbankfunktionen
+##
+DAVERAGE = DBMITTELWERT ## Gibt den Mittelwert der ausgewählten Datenbankeinträge zurück
+DCOUNT = DBANZAHL ## Zählt die Zellen mit Zahlen in einer Datenbank
+DCOUNTA = DBANZAHL2 ## Zählt nicht leere Zellen in einer Datenbank
+DGET = DBAUSZUG ## Extrahiert aus einer Datenbank einen einzelnen Datensatz, der den angegebenen Kriterien entspricht
+DMAX = DBMAX ## Gibt den größten Wert aus ausgewählten Datenbankeinträgen zurück
+DMIN = DBMIN ## Gibt den kleinsten Wert aus ausgewählten Datenbankeinträgen zurück
+DPRODUCT = DBPRODUKT ## Multipliziert die Werte in einem bestimmten Feld mit Datensätzen, die den Kriterien in einer Datenbank entsprechen
+DSTDEV = DBSTDABW ## Schätzt die Standardabweichung auf der Grundlage einer Stichprobe aus ausgewählten Datenbankeinträgen
+DSTDEVP = DBSTDABWN ## Berechnet die Standardabweichung auf der Grundlage der Grundgesamtheit ausgewählter Datenbankeinträge
+DSUM = DBSUMME ## Addiert die Zahlen in der Feldspalte mit Datensätzen in der Datenbank, die den Kriterien entsprechen
+DVAR = DBVARIANZ ## Schätzt die Varianz auf der Grundlage ausgewählter Datenbankeinträge
+DVARP = DBVARIANZEN ## Berechnet die Varianz auf der Grundlage der Grundgesamtheit ausgewählter Datenbankeinträge
+
+
+##
+## Date and time functions Datums- und Zeitfunktionen
+##
+DATE = DATUM ## Gibt die fortlaufende Zahl eines bestimmten Datums zurück
+DATEVALUE = DATWERT ## Wandelt ein Datum in Form von Text in eine fortlaufende Zahl um
+DAY = TAG ## Wandelt eine fortlaufende Zahl in den Tag des Monats um
+DAYS360 = TAGE360 ## Berechnet die Anzahl der Tage zwischen zwei Datumsangaben ausgehend von einem Jahr, das 360 Tage hat
+EDATE = EDATUM ## Gibt die fortlaufende Zahl des Datums zurück, bei dem es sich um die angegebene Anzahl von Monaten vor oder nach dem Anfangstermin handelt
+EOMONTH = MONATSENDE ## Gibt die fortlaufende Zahl des letzten Tags des Monats vor oder nach einer festgelegten Anzahl von Monaten zurück
+HOUR = STUNDE ## Wandelt eine fortlaufende Zahl in eine Stunde um
+MINUTE = MINUTE ## Wandelt eine fortlaufende Zahl in eine Minute um
+MONTH = MONAT ## Wandelt eine fortlaufende Zahl in einen Monat um
+NETWORKDAYS = NETTOARBEITSTAGE ## Gibt die Anzahl von ganzen Arbeitstagen zwischen zwei Datumswerten zurück
+NOW = JETZT ## Gibt die fortlaufende Zahl des aktuellen Datums und der aktuellen Uhrzeit zurück
+SECOND = SEKUNDE ## Wandelt eine fortlaufende Zahl in eine Sekunde um
+TIME = ZEIT ## Gibt die fortlaufende Zahl einer bestimmten Uhrzeit zurück
+TIMEVALUE = ZEITWERT ## Wandelt eine Uhrzeit in Form von Text in eine fortlaufende Zahl um
+TODAY = HEUTE ## Gibt die fortlaufende Zahl des heutigen Datums zurück
+WEEKDAY = WOCHENTAG ## Wandelt eine fortlaufende Zahl in den Wochentag um
+WEEKNUM = KALENDERWOCHE ## Wandelt eine fortlaufende Zahl in eine Zahl um, die angibt, in welche Woche eines Jahres das angegebene Datum fällt
+WORKDAY = ARBEITSTAG ## Gibt die fortlaufende Zahl des Datums vor oder nach einer bestimmten Anzahl von Arbeitstagen zurück
+YEAR = JAHR ## Wandelt eine fortlaufende Zahl in ein Jahr um
+YEARFRAC = BRTEILJAHRE ## Gibt die Anzahl der ganzen Tage zwischen Ausgangsdatum und Enddatum in Bruchteilen von Jahren zurück
+
+
+##
+## Engineering functions Konstruktionsfunktionen
+##
+BESSELI = BESSELI ## Gibt die geänderte Besselfunktion In(x) zurück
+BESSELJ = BESSELJ ## Gibt die Besselfunktion Jn(x) zurück
+BESSELK = BESSELK ## Gibt die geänderte Besselfunktion Kn(x) zurück
+BESSELY = BESSELY ## Gibt die Besselfunktion Yn(x) zurück
+BIN2DEC = BININDEZ ## Wandelt eine binäre Zahl (Dualzahl) in eine dezimale Zahl um
+BIN2HEX = BININHEX ## Wandelt eine binäre Zahl (Dualzahl) in eine hexadezimale Zahl um
+BIN2OCT = BININOKT ## Wandelt eine binäre Zahl (Dualzahl) in eine oktale Zahl um
+COMPLEX = KOMPLEXE ## Wandelt den Real- und Imaginärteil in eine komplexe Zahl um
+CONVERT = UMWANDELN ## Wandelt eine Zahl von einem Maßsystem in ein anderes um
+DEC2BIN = DEZINBIN ## Wandelt eine dezimale Zahl in eine binäre Zahl (Dualzahl) um
+DEC2HEX = DEZINHEX ## Wandelt eine dezimale Zahl in eine hexadezimale Zahl um
+DEC2OCT = DEZINOKT ## Wandelt eine dezimale Zahl in eine oktale Zahl um
+DELTA = DELTA ## Überprüft, ob zwei Werte gleich sind
+ERF = GAUSSFEHLER ## Gibt die Gauss'sche Fehlerfunktion zurück
+ERFC = GAUSSFKOMPL ## Gibt das Komplement zur Gauss'schen Fehlerfunktion zurück
+GESTEP = GGANZZAHL ## Überprüft, ob eine Zahl größer als ein gegebener Schwellenwert ist
+HEX2BIN = HEXINBIN ## Wandelt eine hexadezimale Zahl in eine Binärzahl um
+HEX2DEC = HEXINDEZ ## Wandelt eine hexadezimale Zahl in eine dezimale Zahl um
+HEX2OCT = HEXINOKT ## Wandelt eine hexadezimale Zahl in eine Oktalzahl um
+IMABS = IMABS ## Gibt den Absolutbetrag (Modulo) einer komplexen Zahl zurück
+IMAGINARY = IMAGINÄRTEIL ## Gibt den Imaginärteil einer komplexen Zahl zurück
+IMARGUMENT = IMARGUMENT ## Gibt das Argument Theta zurück, einen Winkel, der als Bogenmaß ausgedrückt wird
+IMCONJUGATE = IMKONJUGIERTE ## Gibt die konjugierte komplexe Zahl zu einer komplexen Zahl zurück
+IMCOS = IMCOS ## Gibt den Kosinus einer komplexen Zahl zurück
+IMDIV = IMDIV ## Gibt den Quotienten zweier komplexer Zahlen zurück
+IMEXP = IMEXP ## Gibt die algebraische Form einer in exponentieller Schreibweise vorliegenden komplexen Zahl zurück
+IMLN = IMLN ## Gibt den natürlichen Logarithmus einer komplexen Zahl zurück
+IMLOG10 = IMLOG10 ## Gibt den Logarithmus einer komplexen Zahl zur Basis 10 zurück
+IMLOG2 = IMLOG2 ## Gibt den Logarithmus einer komplexen Zahl zur Basis 2 zurück
+IMPOWER = IMAPOTENZ ## Potenziert eine komplexe Zahl mit einer ganzen Zahl
+IMPRODUCT = IMPRODUKT ## Gibt das Produkt von komplexen Zahlen zurück
+IMREAL = IMREALTEIL ## Gibt den Realteil einer komplexen Zahl zurück
+IMSIN = IMSIN ## Gibt den Sinus einer komplexen Zahl zurück
+IMSQRT = IMWURZEL ## Gibt die Quadratwurzel einer komplexen Zahl zurück
+IMSUB = IMSUB ## Gibt die Differenz zwischen zwei komplexen Zahlen zurück
+IMSUM = IMSUMME ## Gibt die Summe von komplexen Zahlen zurück
+OCT2BIN = OKTINBIN ## Wandelt eine oktale Zahl in eine binäre Zahl (Dualzahl) um
+OCT2DEC = OKTINDEZ ## Wandelt eine oktale Zahl in eine dezimale Zahl um
+OCT2HEX = OKTINHEX ## Wandelt eine oktale Zahl in eine hexadezimale Zahl um
+
+
+##
+## Financial functions Finanzmathematische Funktionen
+##
+ACCRINT = AUFGELZINS ## Gibt die aufgelaufenen Zinsen (Stückzinsen) eines Wertpapiers mit periodischen Zinszahlungen zurück
+ACCRINTM = AUFGELZINSF ## Gibt die aufgelaufenen Zinsen (Stückzinsen) eines Wertpapiers zurück, die bei Fälligkeit ausgezahlt werden
+AMORDEGRC = AMORDEGRK ## Gibt die Abschreibung für die einzelnen Abschreibungszeiträume mithilfe eines Abschreibungskoeffizienten zurück
+AMORLINC = AMORLINEARK ## Gibt die Abschreibung für die einzelnen Abschreibungszeiträume zurück
+COUPDAYBS = ZINSTERMTAGVA ## Gibt die Anzahl der Tage vom Anfang des Zinstermins bis zum Abrechnungstermin zurück
+COUPDAYS = ZINSTERMTAGE ## Gibt die Anzahl der Tage der Zinsperiode zurück, die den Abrechnungstermin einschließt
+COUPDAYSNC = ZINSTERMTAGNZ ## Gibt die Anzahl der Tage vom Abrechnungstermin bis zum nächsten Zinstermin zurück
+COUPNCD = ZINSTERMNZ ## Gibt das Datum des ersten Zinstermins nach dem Abrechnungstermin zurück
+COUPNUM = ZINSTERMZAHL ## Gibt die Anzahl der Zinstermine zwischen Abrechnungs- und Fälligkeitsdatum zurück
+COUPPCD = ZINSTERMVZ ## Gibt das Datum des letzten Zinstermins vor dem Abrechnungstermin zurück
+CUMIPMT = KUMZINSZ ## Berechnet die kumulierten Zinsen, die zwischen zwei Perioden zu zahlen sind
+CUMPRINC = KUMKAPITAL ## Berechnet die aufgelaufene Tilgung eines Darlehens, die zwischen zwei Perioden zu zahlen ist
+DB = GDA2 ## Gibt die geometrisch-degressive Abschreibung eines Wirtschaftsguts für eine bestimmte Periode zurück
+DDB = GDA ## Gibt die Abschreibung eines Anlageguts für einen angegebenen Zeitraum unter Verwendung der degressiven Doppelraten-Abschreibung oder eines anderen von Ihnen angegebenen Abschreibungsverfahrens zurück
+DISC = DISAGIO ## Gibt den in Prozent ausgedrückten Abzinsungssatz eines Wertpapiers zurück
+DOLLARDE = NOTIERUNGDEZ ## Wandelt eine Notierung, die als Dezimalbruch ausgedrückt wurde, in eine Dezimalzahl um
+DOLLARFR = NOTIERUNGBRU ## Wandelt eine Notierung, die als Dezimalzahl ausgedrückt wurde, in einen Dezimalbruch um
+DURATION = DURATION ## Gibt die jährliche Duration eines Wertpapiers mit periodischen Zinszahlungen zurück
+EFFECT = EFFEKTIV ## Gibt die jährliche Effektivverzinsung zurück
+FV = ZW ## Gibt den zukünftigen Wert (Endwert) einer Investition zurück
+FVSCHEDULE = ZW2 ## Gibt den aufgezinsten Wert des Anfangskapitals für eine Reihe periodisch unterschiedlicher Zinssätze zurück
+INTRATE = ZINSSATZ ## Gibt den Zinssatz eines voll investierten Wertpapiers zurück
+IPMT = ZINSZ ## Gibt die Zinszahlung einer Investition für die angegebene Periode zurück
+IRR = IKV ## Gibt den internen Zinsfuß einer Investition ohne Finanzierungskosten oder Reinvestitionsgewinne zurück
+ISPMT = ISPMT ## Berechnet die während eines bestimmten Zeitraums für eine Investition gezahlten Zinsen
+MDURATION = MDURATION ## Gibt die geänderte Dauer für ein Wertpapier mit einem angenommenen Nennwert von 100 € zurück
+MIRR = QIKV ## Gibt den internen Zinsfuß zurück, wobei positive und negative Zahlungen zu unterschiedlichen Sätzen finanziert werden
+NOMINAL = NOMINAL ## Gibt die jährliche Nominalverzinsung zurück
+NPER = ZZR ## Gibt die Anzahl der Zahlungsperioden einer Investition zurück
+NPV = NBW ## Gibt den Nettobarwert einer Investition auf Basis periodisch anfallender Zahlungen und eines Abzinsungsfaktors zurück
+ODDFPRICE = UNREGER.KURS ## Gibt den Kurs pro 100 € Nennwert eines Wertpapiers mit einem unregelmäßigen ersten Zinstermin zurück
+ODDFYIELD = UNREGER.REND ## Gibt die Rendite eines Wertpapiers mit einem unregelmäßigen ersten Zinstermin zurück
+ODDLPRICE = UNREGLE.KURS ## Gibt den Kurs pro 100 € Nennwert eines Wertpapiers mit einem unregelmäßigen letzten Zinstermin zurück
+ODDLYIELD = UNREGLE.REND ## Gibt die Rendite eines Wertpapiers mit einem unregelmäßigen letzten Zinstermin zurück
+PMT = RMZ ## Gibt die periodische Zahlung für eine Annuität zurück
+PPMT = KAPZ ## Gibt die Kapitalrückzahlung einer Investition für eine angegebene Periode zurück
+PRICE = KURS ## Gibt den Kurs pro 100 € Nennwert eines Wertpapiers zurück, das periodisch Zinsen auszahlt
+PRICEDISC = KURSDISAGIO ## Gibt den Kurs pro 100 € Nennwert eines unverzinslichen Wertpapiers zurück
+PRICEMAT = KURSFÄLLIG ## Gibt den Kurs pro 100 € Nennwert eines Wertpapiers zurück, das Zinsen am Fälligkeitsdatum auszahlt
+PV = BW ## Gibt den Barwert einer Investition zurück
+RATE = ZINS ## Gibt den Zinssatz pro Zeitraum einer Annuität zurück
+RECEIVED = AUSZAHLUNG ## Gibt den Auszahlungsbetrag eines voll investierten Wertpapiers am Fälligkeitstermin zurück
+SLN = LIA ## Gibt die lineare Abschreibung eines Wirtschaftsguts pro Periode zurück
+SYD = DIA ## Gibt die arithmetisch-degressive Abschreibung eines Wirtschaftsguts für eine bestimmte Periode zurück
+TBILLEQ = TBILLÄQUIV ## Gibt die Rendite für ein Wertpapier zurück
+TBILLPRICE = TBILLKURS ## Gibt den Kurs pro 100 € Nennwert eines Wertpapiers zurück
+TBILLYIELD = TBILLRENDITE ## Gibt die Rendite für ein Wertpapier zurück
+VDB = VDB ## Gibt die degressive Abschreibung eines Wirtschaftsguts für eine bestimmte Periode oder Teilperiode zurück
+XIRR = XINTZINSFUSS ## Gibt den internen Zinsfuß einer Reihe nicht periodisch anfallender Zahlungen zurück
+XNPV = XKAPITALWERT ## Gibt den Nettobarwert (Kapitalwert) einer Reihe nicht periodisch anfallender Zahlungen zurück
+YIELD = RENDITE ## Gibt die Rendite eines Wertpapiers zurück, das periodisch Zinsen auszahlt
+YIELDDISC = RENDITEDIS ## Gibt die jährliche Rendite eines unverzinslichen Wertpapiers zurück
+YIELDMAT = RENDITEFÄLL ## Gibt die jährliche Rendite eines Wertpapiers zurück, das Zinsen am Fälligkeitsdatum auszahlt
+
+
+##
+## Information functions Informationsfunktionen
+##
+CELL = ZELLE ## Gibt Informationen zu Formatierung, Position oder Inhalt einer Zelle zurück
+ERROR.TYPE = FEHLER.TYP ## Gibt eine Zahl zurück, die einem Fehlertyp entspricht
+INFO = INFO ## Gibt Informationen zur aktuellen Betriebssystemumgebung zurück
+ISBLANK = ISTLEER ## Gibt WAHR zurück, wenn der Wert leer ist
+ISERR = ISTFEHL ## Gibt WAHR zurück, wenn der Wert ein beliebiger Fehlerwert außer #N/V ist
+ISERROR = ISTFEHLER ## Gibt WAHR zurück, wenn der Wert ein beliebiger Fehlerwert ist
+ISEVEN = ISTGERADE ## Gibt WAHR zurück, wenn es sich um eine gerade Zahl handelt
+ISLOGICAL = ISTLOG ## Gibt WAHR zurück, wenn der Wert ein Wahrheitswert ist
+ISNA = ISTNV ## Gibt WAHR zurück, wenn der Wert der Fehlerwert #N/V ist
+ISNONTEXT = ISTKTEXT ## Gibt WAHR zurück, wenn der Wert ein Element ist, das keinen Text enthält
+ISNUMBER = ISTZAHL ## Gibt WAHR zurück, wenn der Wert eine Zahl ist
+ISODD = ISTUNGERADE ## Gibt WAHR zurück, wenn es sich um eine ungerade Zahl handelt
+ISREF = ISTBEZUG ## Gibt WAHR zurück, wenn der Wert ein Bezug ist
+ISTEXT = ISTTEXT ## Gibt WAHR zurück, wenn der Wert ein Element ist, das Text enthält
+N = N ## Gibt den in eine Zahl umgewandelten Wert zurück
+NA = NV ## Gibt den Fehlerwert #NV zurück
+TYPE = TYP ## Gibt eine Zahl zurück, die den Datentyp des angegebenen Werts anzeigt
+
+
+##
+## Logical functions Logische Funktionen
+##
+AND = UND ## Gibt WAHR zurück, wenn alle zugehörigen Argumente WAHR sind
+FALSE = FALSCH ## Gibt den Wahrheitswert FALSCH zurück
+IF = WENN ## Gibt einen logischen Test zum Ausführen an
+IFERROR = WENNFEHLER ## Gibt einen von Ihnen festgelegten Wert zurück, wenn die Auswertung der Formel zu einem Fehler führt; andernfalls wird das Ergebnis der Formel zurückgegeben
+NOT = NICHT ## Kehrt den Wahrheitswert der zugehörigen Argumente um
+OR = ODER ## Gibt WAHR zurück, wenn ein Argument WAHR ist
+TRUE = WAHR ## Gibt den Wahrheitswert WAHR zurück
+
+
+##
+## Lookup and reference functions Nachschlage- und Verweisfunktionen
+##
+ADDRESS = ADRESSE ## Gibt einen Bezug auf eine einzelne Zelle in einem Tabellenblatt als Text zurück
+AREAS = BEREICHE ## Gibt die Anzahl der innerhalb eines Bezugs aufgeführten Bereiche zurück
+CHOOSE = WAHL ## Wählt einen Wert aus eine Liste mit Werten aus
+COLUMN = SPALTE ## Gibt die Spaltennummer eines Bezugs zurück
+COLUMNS = SPALTEN ## Gibt die Anzahl der Spalten in einem Bezug zurück
+HLOOKUP = HVERWEIS ## Sucht in der obersten Zeile einer Matrix und gibt den Wert der angegebenen Zelle zurück
+HYPERLINK = HYPERLINK ## Erstellt eine Verknüpfung, über die ein auf einem Netzwerkserver, in einem Intranet oder im Internet gespeichertes Dokument geöffnet wird
+INDEX = INDEX ## Verwendet einen Index, um einen Wert aus einem Bezug oder einer Matrix auszuwählen
+INDIRECT = INDIREKT ## Gibt einen Bezug zurück, der von einem Textwert angegeben wird
+LOOKUP = LOOKUP ## Sucht Werte in einem Vektor oder einer Matrix
+MATCH = VERGLEICH ## Sucht Werte in einem Bezug oder einer Matrix
+OFFSET = BEREICH.VERSCHIEBEN ## Gibt einen Bezugoffset aus einem gegebenen Bezug zurück
+ROW = ZEILE ## Gibt die Zeilennummer eines Bezugs zurück
+ROWS = ZEILEN ## Gibt die Anzahl der Zeilen in einem Bezug zurück
+RTD = RTD ## Ruft Echtzeitdaten von einem Programm ab, das die COM-Automatisierung (Automatisierung: Ein Verfahren, bei dem aus einer Anwendung oder einem Entwicklungstool heraus mit den Objekten einer anderen Anwendung gearbeitet wird. Die früher als OLE-Automatisierung bezeichnete Automatisierung ist ein Industriestandard und eine Funktion von COM (Component Object Model).) unterstützt
+TRANSPOSE = MTRANS ## Gibt die transponierte Matrix einer Matrix zurück
+VLOOKUP = SVERWEIS ## Sucht in der ersten Spalte einer Matrix und arbeitet sich durch die Zeile, um den Wert einer Zelle zurückzugeben
+
+
+##
+## Math and trigonometry functions Mathematische und trigonometrische Funktionen
+##
+ABS = ABS ## Gibt den Absolutwert einer Zahl zurück
+ACOS = ARCCOS ## Gibt den Arkuskosinus einer Zahl zurück
+ACOSH = ARCCOSHYP ## Gibt den umgekehrten hyperbolischen Kosinus einer Zahl zurück
+ASIN = ARCSIN ## Gibt den Arkussinus einer Zahl zurück
+ASINH = ARCSINHYP ## Gibt den umgekehrten hyperbolischen Sinus einer Zahl zurück
+ATAN = ARCTAN ## Gibt den Arkustangens einer Zahl zurück
+ATAN2 = ARCTAN2 ## Gibt den Arkustangens einer x- und einer y-Koordinate zurück
+ATANH = ARCTANHYP ## Gibt den umgekehrten hyperbolischen Tangens einer Zahl zurück
+CEILING = OBERGRENZE ## Rundet eine Zahl auf die nächste ganze Zahl oder das nächste Vielfache von Schritt
+COMBIN = KOMBINATIONEN ## Gibt die Anzahl der Kombinationen für eine bestimmte Anzahl von Objekten zurück
+COS = COS ## Gibt den Kosinus einer Zahl zurück
+COSH = COSHYP ## Gibt den hyperbolischen Kosinus einer Zahl zurück
+DEGREES = GRAD ## Wandelt Bogenmaß (Radiant) in Grad um
+EVEN = GERADE ## Rundet eine Zahl auf die nächste gerade ganze Zahl auf
+EXP = EXP ## Potenziert die Basis e mit der als Argument angegebenen Zahl
+FACT = FAKULTÄT ## Gibt die Fakultät einer Zahl zurück
+FACTDOUBLE = ZWEIFAKULTÄT ## Gibt die Fakultät zu Zahl mit Schrittlänge 2 zurück
+FLOOR = UNTERGRENZE ## Rundet die Zahl auf Anzahl_Stellen ab
+GCD = GGT ## Gibt den größten gemeinsamen Teiler zurück
+INT = GANZZAHL ## Rundet eine Zahl auf die nächstkleinere ganze Zahl ab
+LCM = KGV ## Gibt das kleinste gemeinsame Vielfache zurück
+LN = LN ## Gibt den natürlichen Logarithmus einer Zahl zurück
+LOG = LOG ## Gibt den Logarithmus einer Zahl zu der angegebenen Basis zurück
+LOG10 = LOG10 ## Gibt den Logarithmus einer Zahl zur Basis 10 zurück
+MDETERM = MDET ## Gibt die Determinante einer Matrix zurück
+MINVERSE = MINV ## Gibt die inverse Matrix einer Matrix zurück
+MMULT = MMULT ## Gibt das Produkt zweier Matrizen zurück
+MOD = REST ## Gibt den Rest einer Division zurück
+MROUND = VRUNDEN ## Gibt eine auf das gewünschte Vielfache gerundete Zahl zurück
+MULTINOMIAL = POLYNOMIAL ## Gibt den Polynomialkoeffizienten einer Gruppe von Zahlen zurück
+ODD = UNGERADE ## Rundet eine Zahl auf die nächste ungerade ganze Zahl auf
+PI = PI ## Gibt den Wert Pi zurück
+POWER = POTENZ ## Gibt als Ergebnis eine potenzierte Zahl zurück
+PRODUCT = PRODUKT ## Multipliziert die zugehörigen Argumente
+QUOTIENT = QUOTIENT ## Gibt den ganzzahligen Anteil einer Division zurück
+RADIANS = BOGENMASS ## Wandelt Grad in Bogenmaß (Radiant) um
+RAND = ZUFALLSZAHL ## Gibt eine Zufallszahl zwischen 0 und 1 zurück
+RANDBETWEEN = ZUFALLSBEREICH ## Gibt eine Zufallszahl aus dem festgelegten Bereich zurück
+ROMAN = RÖMISCH ## Wandelt eine arabische Zahl in eine römische Zahl als Text um
+ROUND = RUNDEN ## Rundet eine Zahl auf eine bestimmte Anzahl von Dezimalstellen
+ROUNDDOWN = ABRUNDEN ## Rundet die Zahl auf Anzahl_Stellen ab
+ROUNDUP = AUFRUNDEN ## Rundet die Zahl auf Anzahl_Stellen auf
+SERIESSUM = POTENZREIHE ## Gibt die Summe von Potenzen (zur Berechnung von Potenzreihen und dichotomen Wahrscheinlichkeiten) zurück
+SIGN = VORZEICHEN ## Gibt das Vorzeichen einer Zahl zurück
+SIN = SIN ## Gibt den Sinus einer Zahl zurück
+SINH = SINHYP ## Gibt den hyperbolischen Sinus einer Zahl zurück
+SQRT = WURZEL ## Gibt die Quadratwurzel einer Zahl zurück
+SQRTPI = WURZELPI ## Gibt die Wurzel aus der mit Pi (pi) multiplizierten Zahl zurück
+SUBTOTAL = TEILERGEBNIS ## Gibt ein Teilergebnis in einer Liste oder Datenbank zurück
+SUM = SUMME ## Addiert die zugehörigen Argumente
+SUMIF = SUMMEWENN ## Addiert Zahlen, die mit den Suchkriterien übereinstimmen
+SUMIFS = SUMMEWENNS ## Die Zellen, die mehrere Kriterien erfüllen, werden in einem Bereich hinzugefügt
+SUMPRODUCT = SUMMENPRODUKT ## Gibt die Summe der Produkte zusammengehöriger Matrixkomponenten zurück
+SUMSQ = QUADRATESUMME ## Gibt die Summe der quadrierten Argumente zurück
+SUMX2MY2 = SUMMEX2MY2 ## Gibt die Summe der Differenzen der Quadrate für zusammengehörige Komponenten zweier Matrizen zurück
+SUMX2PY2 = SUMMEX2PY2 ## Gibt die Summe der Quadrate für zusammengehörige Komponenten zweier Matrizen zurück
+SUMXMY2 = SUMMEXMY2 ## Gibt die Summe der quadrierten Differenzen für zusammengehörige Komponenten zweier Matrizen zurück
+TAN = TAN ## Gibt den Tangens einer Zahl zurück
+TANH = TANHYP ## Gibt den hyperbolischen Tangens einer Zahl zurück
+TRUNC = KÜRZEN ## Schneidet die Kommastellen einer Zahl ab und gibt als Ergebnis eine ganze Zahl zurück
+
+
+##
+## Statistical functions Statistische Funktionen
+##
+AVEDEV = MITTELABW ## Gibt die durchschnittliche absolute Abweichung einer Reihe von Merkmalsausprägungen und ihrem Mittelwert zurück
+AVERAGE = MITTELWERT ## Gibt den Mittelwert der zugehörigen Argumente zurück
+AVERAGEA = MITTELWERTA ## Gibt den Mittelwert der zugehörigen Argumente, die Zahlen, Text und Wahrheitswerte enthalten, zurück
+AVERAGEIF = MITTELWERTWENN ## Der Durchschnittswert (arithmetisches Mittel) für alle Zellen in einem Bereich, die einem angegebenen Kriterium entsprechen, wird zurückgegeben
+AVERAGEIFS = MITTELWERTWENNS ## Gibt den Durchschnittswert (arithmetisches Mittel) aller Zellen zurück, die mehreren Kriterien entsprechen
+BETADIST = BETAVERT ## Gibt die Werte der kumulierten Betaverteilungsfunktion zurück
+BETAINV = BETAINV ## Gibt das Quantil der angegebenen Betaverteilung zurück
+BINOMDIST = BINOMVERT ## Gibt Wahrscheinlichkeiten einer binomialverteilten Zufallsvariablen zurück
+CHIDIST = CHIVERT ## Gibt Werte der Verteilungsfunktion (1-Alpha) einer Chi-Quadrat-verteilten Zufallsgröße zurück
+CHIINV = CHIINV ## Gibt Quantile der Verteilungsfunktion (1-Alpha) der Chi-Quadrat-Verteilung zurück
+CHITEST = CHITEST ## Gibt die Teststatistik eines Unabhängigkeitstests zurück
+CONFIDENCE = KONFIDENZ ## Ermöglicht die Berechnung des 1-Alpha Konfidenzintervalls für den Erwartungswert einer Zufallsvariablen
+CORREL = KORREL ## Gibt den Korrelationskoeffizienten zweier Reihen von Merkmalsausprägungen zurück
+COUNT = ANZAHL ## Gibt die Anzahl der Zahlen in der Liste mit Argumenten an
+COUNTA = ANZAHL2 ## Gibt die Anzahl der Werte in der Liste mit Argumenten an
+COUNTBLANK = ANZAHLLEEREZELLEN ## Gibt die Anzahl der leeren Zellen in einem Bereich an
+COUNTIF = ZÄHLENWENN ## Gibt die Anzahl der Zellen in einem Bereich an, deren Inhalte mit den Suchkriterien übereinstimmen
+COUNTIFS = ZÄHLENWENNS ## Gibt die Anzahl der Zellen in einem Bereich an, deren Inhalte mit mehreren Suchkriterien übereinstimmen
+COVAR = KOVAR ## Gibt die Kovarianz zurück, den Mittelwert der für alle Datenpunktpaare gebildeten Produkte der Abweichungen
+CRITBINOM = KRITBINOM ## Gibt den kleinsten Wert zurück, für den die kumulierten Wahrscheinlichkeiten der Binomialverteilung kleiner oder gleich einer Grenzwahrscheinlichkeit sind
+DEVSQ = SUMQUADABW ## Gibt die Summe der quadrierten Abweichungen der Datenpunkte von ihrem Stichprobenmittelwert zurück
+EXPONDIST = EXPONVERT ## Gibt Wahrscheinlichkeiten einer exponential verteilten Zufallsvariablen zurück
+FDIST = FVERT ## Gibt Werte der Verteilungsfunktion (1-Alpha) einer F-verteilten Zufallsvariablen zurück
+FINV = FINV ## Gibt Quantile der F-Verteilung zurück
+FISHER = FISHER ## Gibt die Fisher-Transformation zurück
+FISHERINV = FISHERINV ## Gibt die Umkehrung der Fisher-Transformation zurück
+FORECAST = PROGNOSE ## Gibt einen Wert zurück, der sich aus einem linearen Trend ergibt
+FREQUENCY = HÄUFIGKEIT ## Gibt eine Häufigkeitsverteilung als vertikale Matrix zurück
+FTEST = FTEST ## Gibt die Teststatistik eines F-Tests zurück
+GAMMADIST = GAMMAVERT ## Gibt Wahrscheinlichkeiten einer gammaverteilten Zufallsvariablen zurück
+GAMMAINV = GAMMAINV ## Gibt Quantile der Gammaverteilung zurück
+GAMMALN = GAMMALN ## Gibt den natürlichen Logarithmus der Gammafunktion zurück, Γ(x)
+GEOMEAN = GEOMITTEL ## Gibt das geometrische Mittel zurück
+GROWTH = VARIATION ## Gibt Werte zurück, die sich aus einem exponentiellen Trend ergeben
+HARMEAN = HARMITTEL ## Gibt das harmonische Mittel zurück
+HYPGEOMDIST = HYPGEOMVERT ## Gibt Wahrscheinlichkeiten einer hypergeometrisch-verteilten Zufallsvariablen zurück
+INTERCEPT = ACHSENABSCHNITT ## Gibt den Schnittpunkt der Regressionsgeraden zurück
+KURT = KURT ## Gibt die Kurtosis (Exzess) einer Datengruppe zurück
+LARGE = KGRÖSSTE ## Gibt den k-größten Wert einer Datengruppe zurück
+LINEST = RGP ## Gibt die Parameter eines linearen Trends zurück
+LOGEST = RKP ## Gibt die Parameter eines exponentiellen Trends zurück
+LOGINV = LOGINV ## Gibt Quantile der Lognormalverteilung zurück
+LOGNORMDIST = LOGNORMVERT ## Gibt Werte der Verteilungsfunktion einer lognormalverteilten Zufallsvariablen zurück
+MAX = MAX ## Gibt den Maximalwert einer Liste mit Argumenten zurück
+MAXA = MAXA ## Gibt den Maximalwert einer Liste mit Argumenten zurück, die Zahlen, Text und Wahrheitswerte enthalten
+MEDIAN = MEDIAN ## Gibt den Median der angegebenen Zahlen zurück
+MIN = MIN ## Gibt den Minimalwert einer Liste mit Argumenten zurück
+MINA = MINA ## Gibt den kleinsten Wert einer Liste mit Argumenten zurück, die Zahlen, Text und Wahrheitswerte enthalten
+MODE = MODALWERT ## Gibt den am häufigsten vorkommenden Wert in einer Datengruppe zurück
+NEGBINOMDIST = NEGBINOMVERT ## Gibt Wahrscheinlichkeiten einer negativen, binominal verteilten Zufallsvariablen zurück
+NORMDIST = NORMVERT ## Gibt Wahrscheinlichkeiten einer normal verteilten Zufallsvariablen zurück
+NORMINV = NORMINV ## Gibt Quantile der Normalverteilung zurück
+NORMSDIST = STANDNORMVERT ## Gibt Werte der Verteilungsfunktion einer standardnormalverteilten Zufallsvariablen zurück
+NORMSINV = STANDNORMINV ## Gibt Quantile der Standardnormalverteilung zurück
+PEARSON = PEARSON ## Gibt den Pearsonschen Korrelationskoeffizienten zurück
+PERCENTILE = QUANTIL ## Gibt das Alpha-Quantil einer Gruppe von Daten zurück
+PERCENTRANK = QUANTILSRANG ## Gibt den prozentualen Rang (Alpha) eines Werts in einer Datengruppe zurück
+PERMUT = VARIATIONEN ## Gibt die Anzahl der Möglichkeiten zurück, um k Elemente aus einer Menge von n Elementen ohne Zurücklegen zu ziehen
+POISSON = POISSON ## Gibt Wahrscheinlichkeiten einer poissonverteilten Zufallsvariablen zurück
+PROB = WAHRSCHBEREICH ## Gibt die Wahrscheinlichkeit für ein von zwei Werten eingeschlossenes Intervall zurück
+QUARTILE = QUARTILE ## Gibt die Quartile der Datengruppe zurück
+RANK = RANG ## Gibt den Rang zurück, den eine Zahl innerhalb einer Liste von Zahlen einnimmt
+RSQ = BESTIMMTHEITSMASS ## Gibt das Quadrat des Pearsonschen Korrelationskoeffizienten zurück
+SKEW = SCHIEFE ## Gibt die Schiefe einer Verteilung zurück
+SLOPE = STEIGUNG ## Gibt die Steigung der Regressionsgeraden zurück
+SMALL = KKLEINSTE ## Gibt den k-kleinsten Wert einer Datengruppe zurück
+STANDARDIZE = STANDARDISIERUNG ## Gibt den standardisierten Wert zurück
+STDEV = STABW ## Schätzt die Standardabweichung ausgehend von einer Stichprobe
+STDEVA = STABWA ## Schätzt die Standardabweichung ausgehend von einer Stichprobe, die Zahlen, Text und Wahrheitswerte enthält
+STDEVP = STABWN ## Berechnet die Standardabweichung ausgehend von der Grundgesamtheit
+STDEVPA = STABWNA ## Berechnet die Standardabweichung ausgehend von der Grundgesamtheit, die Zahlen, Text und Wahrheitswerte enthält
+STEYX = STFEHLERYX ## Gibt den Standardfehler der geschätzten y-Werte für alle x-Werte der Regression zurück
+TDIST = TVERT ## Gibt Werte der Verteilungsfunktion (1-Alpha) einer (Student) t-verteilten Zufallsvariablen zurück
+TINV = TINV ## Gibt Quantile der t-Verteilung zurück
+TREND = TREND ## Gibt Werte zurück, die sich aus einem linearen Trend ergeben
+TRIMMEAN = GESTUTZTMITTEL ## Gibt den Mittelwert einer Datengruppe zurück, ohne die Randwerte zu berücksichtigen
+TTEST = TTEST ## Gibt die Teststatistik eines Student'schen t-Tests zurück
+VAR = VARIANZ ## Schätzt die Varianz ausgehend von einer Stichprobe
+VARA = VARIANZA ## Schätzt die Varianz ausgehend von einer Stichprobe, die Zahlen, Text und Wahrheitswerte enthält
+VARP = VARIANZEN ## Berechnet die Varianz ausgehend von der Grundgesamtheit
+VARPA = VARIANZENA ## Berechnet die Varianz ausgehend von der Grundgesamtheit, die Zahlen, Text und Wahrheitswerte enthält
+WEIBULL = WEIBULL ## Gibt Wahrscheinlichkeiten einer weibullverteilten Zufallsvariablen zurück
+ZTEST = GTEST ## Gibt den einseitigen Wahrscheinlichkeitswert für einen Gausstest (Normalverteilung) zurück
+
+
+##
+## Text functions Textfunktionen
+##
+ASC = ASC ## Konvertiert DB-Text in einer Zeichenfolge (lateinische Buchstaben oder Katakana) in SB-Text
+BAHTTEXT = BAHTTEXT ## Wandelt eine Zahl in Text im Währungsformat ß (Baht) um
+CHAR = ZEICHEN ## Gibt das der Codezahl entsprechende Zeichen zurück
+CLEAN = SÄUBERN ## Löscht alle nicht druckbaren Zeichen aus einem Text
+CODE = CODE ## Gibt die Codezahl des ersten Zeichens in einem Text zurück
+CONCATENATE = VERKETTEN ## Verknüpft mehrere Textelemente zu einem Textelement
+DOLLAR = DM ## Wandelt eine Zahl in Text im Währungsformat € (Euro) um
+EXACT = IDENTISCH ## Prüft, ob zwei Textwerte identisch sind
+FIND = FINDEN ## Sucht nach einem Textwert, der in einem anderen Textwert enthalten ist (Groß-/Kleinschreibung wird unterschieden)
+FINDB = FINDENB ## Sucht nach einem Textwert, der in einem anderen Textwert enthalten ist (Groß-/Kleinschreibung wird unterschieden)
+FIXED = FEST ## Formatiert eine Zahl als Text mit einer festen Anzahl von Dezimalstellen
+JIS = JIS ## Konvertiert SB-Text in einer Zeichenfolge (lateinische Buchstaben oder Katakana) in DB-Text
+LEFT = LINKS ## Gibt die Zeichen ganz links in einem Textwert zurück
+LEFTB = LINKSB ## Gibt die Zeichen ganz links in einem Textwert zurück
+LEN = LÄNGE ## Gibt die Anzahl der Zeichen in einer Zeichenfolge zurück
+LENB = LÄNGEB ## Gibt die Anzahl der Zeichen in einer Zeichenfolge zurück
+LOWER = KLEIN ## Wandelt Text in Kleinbuchstaben um
+MID = TEIL ## Gibt eine bestimmte Anzahl Zeichen aus einer Zeichenfolge ab der von Ihnen angegebenen Stelle zurück
+MIDB = TEILB ## Gibt eine bestimmte Anzahl Zeichen aus einer Zeichenfolge ab der von Ihnen angegebenen Stelle zurück
+PHONETIC = PHONETIC ## Extrahiert die phonetischen (Furigana-)Zeichen aus einer Textzeichenfolge
+PROPER = GROSS2 ## Wandelt den ersten Buchstaben aller Wörter eines Textwerts in Großbuchstaben um
+REPLACE = ERSETZEN ## Ersetzt Zeichen in Text
+REPLACEB = ERSETZENB ## Ersetzt Zeichen in Text
+REPT = WIEDERHOLEN ## Wiederholt einen Text so oft wie angegeben
+RIGHT = RECHTS ## Gibt die Zeichen ganz rechts in einem Textwert zurück
+RIGHTB = RECHTSB ## Gibt die Zeichen ganz rechts in einem Textwert zurück
+SEARCH = SUCHEN ## Sucht nach einem Textwert, der in einem anderen Textwert enthalten ist (Groß-/Kleinschreibung wird nicht unterschieden)
+SEARCHB = SUCHENB ## Sucht nach einem Textwert, der in einem anderen Textwert enthalten ist (Groß-/Kleinschreibung wird nicht unterschieden)
+SUBSTITUTE = WECHSELN ## Ersetzt in einer Zeichenfolge neuen Text gegen alten
+T = T ## Wandelt die zugehörigen Argumente in Text um
+TEXT = TEXT ## Formatiert eine Zahl und wandelt sie in Text um
+TRIM = GLÄTTEN ## Entfernt Leerzeichen aus Text
+UPPER = GROSS ## Wandelt Text in Großbuchstaben um
+VALUE = WERT ## Wandelt ein Textargument in eine Zahl um
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config
new file mode 100755
index 0000000..859e4be
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config
@@ -0,0 +1,8 @@
+##
+## PhpSpreadsheet
+##
+
+##
+## (For future use)
+##
+currencySymbol = £
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config
new file mode 100755
index 0000000..5b9b948
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config
@@ -0,0 +1,24 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = $ ## I'm surprised that the Excel Documentation suggests $ rather than €
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #¡NULO!
+DIV0 = #¡DIV/0!
+VALUE = #¡VALOR!
+REF = #¡REF!
+NAME = #¿NOMBRE?
+NUM = #¡NÚM!
+NA = #N/A
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions
new file mode 100755
index 0000000..ac1ac86
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions
@@ -0,0 +1,416 @@
+##
+## PhpSpreadsheet
+##
+## Data in this file derived from https://www.excel-function-translation.com/
+##
+##
+
+
+##
+## Add-in and Automation functions Funciones de complementos y automatización
+##
+GETPIVOTDATA = IMPORTARDATOSDINAMICOS ## Devuelve los datos almacenados en un informe de tabla dinámica.
+
+
+##
+## Cube functions Funciones de cubo
+##
+CUBEKPIMEMBER = MIEMBROKPICUBO ## Devuelve un nombre, propiedad y medida de indicador de rendimiento clave (KPI) y muestra el nombre y la propiedad en la celda. Un KPI es una medida cuantificable, como los beneficios brutos mensuales o la facturación trimestral por empleado, que se usa para supervisar el rendimiento de una organización.
+CUBEMEMBER = MIEMBROCUBO ## Devuelve un miembro o tupla en una jerarquía de cubo. Se usa para validar la existencia del miembro o la tupla en el cubo.
+CUBEMEMBERPROPERTY = PROPIEDADMIEMBROCUBO ## Devuelve el valor de una propiedad de miembro del cubo Se usa para validar la existencia de un nombre de miembro en el cubo y para devolver la propiedad especificada para este miembro.
+CUBERANKEDMEMBER = MIEMBRORANGOCUBO ## Devuelve el miembro n, o clasificado, de un conjunto. Se usa para devolver uno o más elementos de un conjunto, por ejemplo, el representante con mejores ventas o los diez mejores alumnos.
+CUBESET = CONJUNTOCUBO ## Define un conjunto calculado de miembros o tuplas mediante el envío de una expresión de conjunto al cubo en el servidor, lo que crea el conjunto y, después, devuelve dicho conjunto a Microsoft Office Excel.
+CUBESETCOUNT = RECUENTOCONJUNTOCUBO ## Devuelve el número de elementos de un conjunto.
+CUBEVALUE = VALORCUBO ## Devuelve un valor agregado de un cubo.
+
+
+##
+## Database functions Funciones de base de datos
+##
+DAVERAGE = BDPROMEDIO ## Devuelve el promedio de las entradas seleccionadas en la base de datos.
+DCOUNT = BDCONTAR ## Cuenta el número de celdas que contienen números en una base de datos.
+DCOUNTA = BDCONTARA ## Cuenta el número de celdas no vacías en una base de datos.
+DGET = BDEXTRAER ## Extrae de una base de datos un único registro que cumple los criterios especificados.
+DMAX = BDMAX ## Devuelve el valor máximo de las entradas seleccionadas de la base de datos.
+DMIN = BDMIN ## Devuelve el valor mínimo de las entradas seleccionadas de la base de datos.
+DPRODUCT = BDPRODUCTO ## Multiplica los valores de un campo concreto de registros de una base de datos que cumplen los criterios especificados.
+DSTDEV = BDDESVEST ## Calcula la desviación estándar a partir de una muestra de entradas seleccionadas en la base de datos.
+DSTDEVP = BDDESVESTP ## Calcula la desviación estándar en función de la población total de las entradas seleccionadas de la base de datos.
+DSUM = BDSUMA ## Suma los números de la columna de campo de los registros de la base de datos que cumplen los criterios.
+DVAR = BDVAR ## Calcula la varianza a partir de una muestra de entradas seleccionadas de la base de datos.
+DVARP = BDVARP ## Calcula la varianza a partir de la población total de entradas seleccionadas de la base de datos.
+
+
+##
+## Date and time functions Funciones de fecha y hora
+##
+DATE = FECHA ## Devuelve el número de serie correspondiente a una fecha determinada.
+DATEVALUE = FECHANUMERO ## Convierte una fecha con formato de texto en un valor de número de serie.
+DAY = DIA ## Convierte un número de serie en un valor de día del mes.
+DAYS360 = DIAS360 ## Calcula el número de días entre dos fechas a partir de un año de 360 días.
+EDATE = FECHA.MES ## Devuelve el número de serie de la fecha equivalente al número indicado de meses anteriores o posteriores a la fecha inicial.
+EOMONTH = FIN.MES ## Devuelve el número de serie correspondiente al último día del mes anterior o posterior a un número de meses especificado.
+HOUR = HORA ## Convierte un número de serie en un valor de hora.
+MINUTE = MINUTO ## Convierte un número de serie en un valor de minuto.
+MONTH = MES ## Convierte un número de serie en un valor de mes.
+NETWORKDAYS = DIAS.LAB ## Devuelve el número de todos los días laborables existentes entre dos fechas.
+NOW = AHORA ## Devuelve el número de serie correspondiente a la fecha y hora actuales.
+SECOND = SEGUNDO ## Convierte un número de serie en un valor de segundo.
+TIME = HORA ## Devuelve el número de serie correspondiente a una hora determinada.
+TIMEVALUE = HORANUMERO ## Convierte una hora con formato de texto en un valor de número de serie.
+TODAY = HOY ## Devuelve el número de serie correspondiente al día actual.
+WEEKDAY = DIASEM ## Convierte un número de serie en un valor de día de la semana.
+WEEKNUM = NUM.DE.SEMANA ## Convierte un número de serie en un número que representa el lugar numérico correspondiente a una semana de un año.
+WORKDAY = DIA.LAB ## Devuelve el número de serie de la fecha que tiene lugar antes o después de un número determinado de días laborables.
+YEAR = AÑO ## Convierte un número de serie en un valor de año.
+YEARFRAC = FRAC.AÑO ## Devuelve la fracción de año que representa el número total de días existentes entre el valor de fecha_inicial y el de fecha_final.
+
+
+##
+## Engineering functions Funciones de ingeniería
+##
+BESSELI = BESSELI ## Devuelve la función Bessel In(x) modificada.
+BESSELJ = BESSELJ ## Devuelve la función Bessel Jn(x).
+BESSELK = BESSELK ## Devuelve la función Bessel Kn(x) modificada.
+BESSELY = BESSELY ## Devuelve la función Bessel Yn(x).
+BIN2DEC = BIN.A.DEC ## Convierte un número binario en decimal.
+BIN2HEX = BIN.A.HEX ## Convierte un número binario en hexadecimal.
+BIN2OCT = BIN.A.OCT ## Convierte un número binario en octal.
+COMPLEX = COMPLEJO ## Convierte coeficientes reales e imaginarios en un número complejo.
+CONVERT = CONVERTIR ## Convierte un número de un sistema de medida a otro.
+DEC2BIN = DEC.A.BIN ## Convierte un número decimal en binario.
+DEC2HEX = DEC.A.HEX ## Convierte un número decimal en hexadecimal.
+DEC2OCT = DEC.A.OCT ## Convierte un número decimal en octal.
+DELTA = DELTA ## Comprueba si dos valores son iguales.
+ERF = FUN.ERROR ## Devuelve la función de error.
+ERFC = FUN.ERROR.COMPL ## Devuelve la función de error complementario.
+GESTEP = MAYOR.O.IGUAL ## Comprueba si un número es mayor que un valor de umbral.
+HEX2BIN = HEX.A.BIN ## Convierte un número hexadecimal en binario.
+HEX2DEC = HEX.A.DEC ## Convierte un número hexadecimal en decimal.
+HEX2OCT = HEX.A.OCT ## Convierte un número hexadecimal en octal.
+IMABS = IM.ABS ## Devuelve el valor absoluto (módulo) de un número complejo.
+IMAGINARY = IMAGINARIO ## Devuelve el coeficiente imaginario de un número complejo.
+IMARGUMENT = IM.ANGULO ## Devuelve el argumento theta, un ángulo expresado en radianes.
+IMCONJUGATE = IM.CONJUGADA ## Devuelve la conjugada compleja de un número complejo.
+IMCOS = IM.COS ## Devuelve el coseno de un número complejo.
+IMDIV = IM.DIV ## Devuelve el cociente de dos números complejos.
+IMEXP = IM.EXP ## Devuelve el valor exponencial de un número complejo.
+IMLN = IM.LN ## Devuelve el logaritmo natural (neperiano) de un número complejo.
+IMLOG10 = IM.LOG10 ## Devuelve el logaritmo en base 10 de un número complejo.
+IMLOG2 = IM.LOG2 ## Devuelve el logaritmo en base 2 de un número complejo.
+IMPOWER = IM.POT ## Devuelve un número complejo elevado a una potencia entera.
+IMPRODUCT = IM.PRODUCT ## Devuelve el producto de números complejos.
+IMREAL = IM.REAL ## Devuelve el coeficiente real de un número complejo.
+IMSIN = IM.SENO ## Devuelve el seno de un número complejo.
+IMSQRT = IM.RAIZ2 ## Devuelve la raíz cuadrada de un número complejo.
+IMSUB = IM.SUSTR ## Devuelve la diferencia entre dos números complejos.
+IMSUM = IM.SUM ## Devuelve la suma de números complejos.
+OCT2BIN = OCT.A.BIN ## Convierte un número octal en binario.
+OCT2DEC = OCT.A.DEC ## Convierte un número octal en decimal.
+OCT2HEX = OCT.A.HEX ## Convierte un número octal en hexadecimal.
+
+
+##
+## Financial functions Funciones financieras
+##
+ACCRINT = INT.ACUM ## Devuelve el interés acumulado de un valor bursátil con pagos de interés periódicos.
+ACCRINTM = INT.ACUM.V ## Devuelve el interés acumulado de un valor bursátil con pagos de interés al vencimiento.
+AMORDEGRC = AMORTIZ.PROGRE ## Devuelve la amortización de cada período contable mediante el uso de un coeficiente de amortización.
+AMORLINC = AMORTIZ.LIN ## Devuelve la amortización de cada uno de los períodos contables.
+COUPDAYBS = CUPON.DIAS.L1 ## Devuelve el número de días desde el principio del período de un cupón hasta la fecha de liquidación.
+COUPDAYS = CUPON.DIAS ## Devuelve el número de días del período (entre dos cupones) donde se encuentra la fecha de liquidación.
+COUPDAYSNC = CUPON.DIAS.L2 ## Devuelve el número de días desde la fecha de liquidación hasta la fecha del próximo cupón.
+COUPNCD = CUPON.FECHA.L2 ## Devuelve la fecha del próximo cupón después de la fecha de liquidación.
+COUPNUM = CUPON.NUM ## Devuelve el número de pagos de cupón entre la fecha de liquidación y la fecha de vencimiento.
+COUPPCD = CUPON.FECHA.L1 ## Devuelve la fecha de cupón anterior a la fecha de liquidación.
+CUMIPMT = PAGO.INT.ENTRE ## Devuelve el interés acumulado pagado entre dos períodos.
+CUMPRINC = PAGO.PRINC.ENTRE ## Devuelve el capital acumulado pagado de un préstamo entre dos períodos.
+DB = DB ## Devuelve la amortización de un bien durante un período específico a través del método de amortización de saldo fijo.
+DDB = DDB ## Devuelve la amortización de un bien durante un período específico a través del método de amortización por doble disminución de saldo u otro método que se especifique.
+DISC = TASA.DESC ## Devuelve la tasa de descuento de un valor bursátil.
+DOLLARDE = MONEDA.DEC ## Convierte una cotización de un valor bursátil expresada en forma fraccionaria en una cotización de un valor bursátil expresada en forma decimal.
+DOLLARFR = MONEDA.FRAC ## Convierte una cotización de un valor bursátil expresada en forma decimal en una cotización de un valor bursátil expresada en forma fraccionaria.
+DURATION = DURACION ## Devuelve la duración anual de un valor bursátil con pagos de interés periódico.
+EFFECT = INT.EFECTIVO ## Devuelve la tasa de interés anual efectiva.
+FV = VF ## Devuelve el valor futuro de una inversión.
+FVSCHEDULE = VF.PLAN ## Devuelve el valor futuro de un capital inicial después de aplicar una serie de tasas de interés compuesto.
+INTRATE = TASA.INT ## Devuelve la tasa de interés para la inversión total de un valor bursátil.
+IPMT = PAGOINT ## Devuelve el pago de intereses de una inversión durante un período determinado.
+IRR = TIR ## Devuelve la tasa interna de retorno para una serie de flujos de efectivo periódicos.
+ISPMT = INT.PAGO.DIR ## Calcula el interés pagado durante un período específico de una inversión.
+MDURATION = DURACION.MODIF ## Devuelve la duración de Macauley modificada de un valor bursátil con un valor nominal supuesto de 100 $.
+MIRR = TIRM ## Devuelve la tasa interna de retorno donde se financian flujos de efectivo positivos y negativos a tasas diferentes.
+NOMINAL = TASA.NOMINAL ## Devuelve la tasa nominal de interés anual.
+NPER = NPER ## Devuelve el número de períodos de una inversión.
+NPV = VNA ## Devuelve el valor neto actual de una inversión en función de una serie de flujos periódicos de efectivo y una tasa de descuento.
+ODDFPRICE = PRECIO.PER.IRREGULAR.1 ## Devuelve el precio por un valor nominal de 100 $ de un valor bursátil con un primer período impar.
+ODDFYIELD = RENDTO.PER.IRREGULAR.1 ## Devuelve el rendimiento de un valor bursátil con un primer período impar.
+ODDLPRICE = PRECIO.PER.IRREGULAR.2 ## Devuelve el precio por un valor nominal de 100 $ de un valor bursátil con un último período impar.
+ODDLYIELD = RENDTO.PER.IRREGULAR.2 ## Devuelve el rendimiento de un valor bursátil con un último período impar.
+PMT = PAGO ## Devuelve el pago periódico de una anualidad.
+PPMT = PAGOPRIN ## Devuelve el pago de capital de una inversión durante un período determinado.
+PRICE = PRECIO ## Devuelve el precio por un valor nominal de 100 $ de un valor bursátil que paga una tasa de interés periódico.
+PRICEDISC = PRECIO.DESCUENTO ## Devuelve el precio por un valor nominal de 100 $ de un valor bursátil con descuento.
+PRICEMAT = PRECIO.VENCIMIENTO ## Devuelve el precio por un valor nominal de 100 $ de un valor bursátil que paga interés a su vencimiento.
+PV = VALACT ## Devuelve el valor actual de una inversión.
+RATE = TASA ## Devuelve la tasa de interés por período de una anualidad.
+RECEIVED = CANTIDAD.RECIBIDA ## Devuelve la cantidad recibida al vencimiento de un valor bursátil completamente invertido.
+SLN = SLN ## Devuelve la amortización por método directo de un bien en un período dado.
+SYD = SYD ## Devuelve la amortización por suma de dígitos de los años de un bien durante un período especificado.
+TBILLEQ = LETRA.DE.TES.EQV.A.BONO ## Devuelve el rendimiento de un bono equivalente a una letra del Tesoro (de EE.UU.)
+TBILLPRICE = LETRA.DE.TES.PRECIO ## Devuelve el precio por un valor nominal de 100 $ de una letra del Tesoro (de EE.UU.)
+TBILLYIELD = LETRA.DE.TES.RENDTO ## Devuelve el rendimiento de una letra del Tesoro (de EE.UU.)
+VDB = DVS ## Devuelve la amortización de un bien durante un período específico o parcial a través del método de cálculo del saldo en disminución.
+XIRR = TIR.NO.PER ## Devuelve la tasa interna de retorno para un flujo de efectivo que no es necesariamente periódico.
+XNPV = VNA.NO.PER ## Devuelve el valor neto actual para un flujo de efectivo que no es necesariamente periódico.
+YIELD = RENDTO ## Devuelve el rendimiento de un valor bursátil que paga intereses periódicos.
+YIELDDISC = RENDTO.DESC ## Devuelve el rendimiento anual de un valor bursátil con descuento; por ejemplo, una letra del Tesoro (de EE.UU.)
+YIELDMAT = RENDTO.VENCTO ## Devuelve el rendimiento anual de un valor bursátil que paga intereses al vencimiento.
+
+
+##
+## Information functions Funciones de información
+##
+CELL = CELDA ## Devuelve información acerca del formato, la ubicación o el contenido de una celda.
+ERROR.TYPE = TIPO.DE.ERROR ## Devuelve un número que corresponde a un tipo de error.
+INFO = INFO ## Devuelve información acerca del entorno operativo en uso.
+ISBLANK = ESBLANCO ## Devuelve VERDADERO si el valor está en blanco.
+ISERR = ESERR ## Devuelve VERDADERO si el valor es cualquier valor de error excepto #N/A.
+ISERROR = ESERROR ## Devuelve VERDADERO si el valor es cualquier valor de error.
+ISEVEN = ES.PAR ## Devuelve VERDADERO si el número es par.
+ISLOGICAL = ESLOGICO ## Devuelve VERDADERO si el valor es un valor lógico.
+ISNA = ESNOD ## Devuelve VERDADERO si el valor es el valor de error #N/A.
+ISNONTEXT = ESNOTEXTO ## Devuelve VERDADERO si el valor no es texto.
+ISNUMBER = ESNUMERO ## Devuelve VERDADERO si el valor es un número.
+ISODD = ES.IMPAR ## Devuelve VERDADERO si el número es impar.
+ISREF = ESREF ## Devuelve VERDADERO si el valor es una referencia.
+ISTEXT = ESTEXTO ## Devuelve VERDADERO si el valor es texto.
+N = N ## Devuelve un valor convertido en un número.
+NA = ND ## Devuelve el valor de error #N/A.
+TYPE = TIPO ## Devuelve un número que indica el tipo de datos de un valor.
+
+
+##
+## Logical functions Funciones lógicas
+##
+AND = Y ## Devuelve VERDADERO si todos sus argumentos son VERDADERO.
+FALSE = FALSO ## Devuelve el valor lógico FALSO.
+IF = SI ## Especifica una prueba lógica que realizar.
+IFERROR = SI.ERROR ## Devuelve un valor que se especifica si una fórmula lo evalúa como un error; de lo contrario, devuelve el resultado de la fórmula.
+NOT = NO ## Invierte el valor lógico del argumento.
+OR = O ## Devuelve VERDADERO si cualquier argumento es VERDADERO.
+TRUE = VERDADERO ## Devuelve el valor lógico VERDADERO.
+
+
+##
+## Lookup and reference functions Funciones de búsqueda y referencia
+##
+ADDRESS = DIRECCION ## Devuelve una referencia como texto a una sola celda de una hoja de cálculo.
+AREAS = AREAS ## Devuelve el número de áreas de una referencia.
+CHOOSE = ELEGIR ## Elige un valor de una lista de valores.
+COLUMN = COLUMNA ## Devuelve el número de columna de una referencia.
+COLUMNS = COLUMNAS ## Devuelve el número de columnas de una referencia.
+HLOOKUP = BUSCARH ## Busca en la fila superior de una matriz y devuelve el valor de la celda indicada.
+HYPERLINK = HIPERVINCULO ## Crea un acceso directo o un salto que abre un documento almacenado en un servidor de red, en una intranet o en Internet.
+INDEX = INDICE ## Usa un índice para elegir un valor de una referencia o matriz.
+INDIRECT = INDIRECTO ## Devuelve una referencia indicada por un valor de texto.
+LOOKUP = BUSCAR ## Busca valores de un vector o una matriz.
+MATCH = COINCIDIR ## Busca valores de una referencia o matriz.
+OFFSET = DESREF ## Devuelve un desplazamiento de referencia respecto a una referencia dada.
+ROW = FILA ## Devuelve el número de fila de una referencia.
+ROWS = FILAS ## Devuelve el número de filas de una referencia.
+RTD = RDTR ## Recupera datos en tiempo real desde un programa compatible con la automatización COM (automatización: modo de trabajar con los objetos de una aplicación desde otra aplicación o herramienta de entorno. La automatización, antes denominada automatización OLE, es un estándar de la industria y una función del Modelo de objetos componentes (COM).).
+TRANSPOSE = TRANSPONER ## Devuelve la transposición de una matriz.
+VLOOKUP = BUSCARV ## Busca en la primera columna de una matriz y se mueve en horizontal por la fila para devolver el valor de una celda.
+
+
+##
+## Math and trigonometry functions Funciones matemáticas y trigonométricas
+##
+ABS = ABS ## Devuelve el valor absoluto de un número.
+ACOS = ACOS ## Devuelve el arcocoseno de un número.
+ACOSH = ACOSH ## Devuelve el coseno hiperbólico inverso de un número.
+ASIN = ASENO ## Devuelve el arcoseno de un número.
+ASINH = ASENOH ## Devuelve el seno hiperbólico inverso de un número.
+ATAN = ATAN ## Devuelve la arcotangente de un número.
+ATAN2 = ATAN2 ## Devuelve la arcotangente de las coordenadas "x" e "y".
+ATANH = ATANH ## Devuelve la tangente hiperbólica inversa de un número.
+CEILING = MULTIPLO.SUPERIOR ## Redondea un número al entero más próximo o al múltiplo significativo más cercano.
+COMBIN = COMBINAT ## Devuelve el número de combinaciones para un número determinado de objetos.
+COS = COS ## Devuelve el coseno de un número.
+COSH = COSH ## Devuelve el coseno hiperbólico de un número.
+DEGREES = GRADOS ## Convierte radianes en grados.
+EVEN = REDONDEA.PAR ## Redondea un número hasta el entero par más próximo.
+EXP = EXP ## Devuelve e elevado a la potencia de un número dado.
+FACT = FACT ## Devuelve el factorial de un número.
+FACTDOUBLE = FACT.DOBLE ## Devuelve el factorial doble de un número.
+FLOOR = MULTIPLO.INFERIOR ## Redondea un número hacia abajo, en dirección hacia cero.
+GCD = M.C.D ## Devuelve el máximo común divisor.
+INT = ENTERO ## Redondea un número hacia abajo hasta el entero más próximo.
+LCM = M.C.M ## Devuelve el mínimo común múltiplo.
+LN = LN ## Devuelve el logaritmo natural (neperiano) de un número.
+LOG = LOG ## Devuelve el logaritmo de un número en una base especificada.
+LOG10 = LOG10 ## Devuelve el logaritmo en base 10 de un número.
+MDETERM = MDETERM ## Devuelve la determinante matricial de una matriz.
+MINVERSE = MINVERSA ## Devuelve la matriz inversa de una matriz.
+MMULT = MMULT ## Devuelve el producto de matriz de dos matrices.
+MOD = RESIDUO ## Devuelve el resto de la división.
+MROUND = REDOND.MULT ## Devuelve un número redondeado al múltiplo deseado.
+MULTINOMIAL = MULTINOMIAL ## Devuelve el polinomio de un conjunto de números.
+ODD = REDONDEA.IMPAR ## Redondea un número hacia arriba hasta el entero impar más próximo.
+PI = PI ## Devuelve el valor de pi.
+POWER = POTENCIA ## Devuelve el resultado de elevar un número a una potencia.
+PRODUCT = PRODUCTO ## Multiplica sus argumentos.
+QUOTIENT = COCIENTE ## Devuelve la parte entera de una división.
+RADIANS = RADIANES ## Convierte grados en radianes.
+RAND = ALEATORIO ## Devuelve un número aleatorio entre 0 y 1.
+RANDBETWEEN = ALEATORIO.ENTRE ## Devuelve un número aleatorio entre los números que especifique.
+ROMAN = NUMERO.ROMANO ## Convierte un número arábigo en número romano, con formato de texto.
+ROUND = REDONDEAR ## Redondea un número al número de decimales especificado.
+ROUNDDOWN = REDONDEAR.MENOS ## Redondea un número hacia abajo, en dirección hacia cero.
+ROUNDUP = REDONDEAR.MAS ## Redondea un número hacia arriba, en dirección contraria a cero.
+SERIESSUM = SUMA.SERIES ## Devuelve la suma de una serie de potencias en función de la fórmula.
+SIGN = SIGNO ## Devuelve el signo de un número.
+SIN = SENO ## Devuelve el seno de un ángulo determinado.
+SINH = SENOH ## Devuelve el seno hiperbólico de un número.
+SQRT = RAIZ ## Devuelve la raíz cuadrada positiva de un número.
+SQRTPI = RAIZ2PI ## Devuelve la raíz cuadrada de un número multiplicado por PI (número * pi).
+SUBTOTAL = SUBTOTALES ## Devuelve un subtotal en una lista o base de datos.
+SUM = SUMA ## Suma sus argumentos.
+SUMIF = SUMAR.SI ## Suma las celdas especificadas que cumplen unos criterios determinados.
+SUMIFS = SUMAR.SI.CONJUNTO ## Suma las celdas de un rango que cumplen varios criterios.
+SUMPRODUCT = SUMAPRODUCTO ## Devuelve la suma de los productos de los correspondientes componentes de matriz.
+SUMSQ = SUMA.CUADRADOS ## Devuelve la suma de los cuadrados de los argumentos.
+SUMX2MY2 = SUMAX2MENOSY2 ## Devuelve la suma de la diferencia de los cuadrados de los valores correspondientes de dos matrices.
+SUMX2PY2 = SUMAX2MASY2 ## Devuelve la suma de la suma de los cuadrados de los valores correspondientes de dos matrices.
+SUMXMY2 = SUMAXMENOSY2 ## Devuelve la suma de los cuadrados de las diferencias de los valores correspondientes de dos matrices.
+TAN = TAN ## Devuelve la tangente de un número.
+TANH = TANH ## Devuelve la tangente hiperbólica de un número.
+TRUNC = TRUNCAR ## Trunca un número a un entero.
+
+
+##
+## Statistical functions Funciones estadísticas
+##
+AVEDEV = DESVPROM ## Devuelve el promedio de las desviaciones absolutas de la media de los puntos de datos.
+AVERAGE = PROMEDIO ## Devuelve el promedio de sus argumentos.
+AVERAGEA = PROMEDIOA ## Devuelve el promedio de sus argumentos, incluidos números, texto y valores lógicos.
+AVERAGEIF = PROMEDIO.SI ## Devuelve el promedio (media aritmética) de todas las celdas de un rango que cumplen unos criterios determinados.
+AVERAGEIFS = PROMEDIO.SI.CONJUNTO ## Devuelve el promedio (media aritmética) de todas las celdas que cumplen múltiples criterios.
+BETADIST = DISTR.BETA ## Devuelve la función de distribución beta acumulativa.
+BETAINV = DISTR.BETA.INV ## Devuelve la función inversa de la función de distribución acumulativa de una distribución beta especificada.
+BINOMDIST = DISTR.BINOM ## Devuelve la probabilidad de una variable aleatoria discreta siguiendo una distribución binomial.
+CHIDIST = DISTR.CHI ## Devuelve la probabilidad de una variable aleatoria continua siguiendo una distribución chi cuadrado de una sola cola.
+CHIINV = PRUEBA.CHI.INV ## Devuelve la función inversa de la probabilidad de una variable aleatoria continua siguiendo una distribución chi cuadrado de una sola cola.
+CHITEST = PRUEBA.CHI ## Devuelve la prueba de independencia.
+CONFIDENCE = INTERVALO.CONFIANZA ## Devuelve el intervalo de confianza de la media de una población.
+CORREL = COEF.DE.CORREL ## Devuelve el coeficiente de correlación entre dos conjuntos de datos.
+COUNT = CONTAR ## Cuenta cuántos números hay en la lista de argumentos.
+COUNTA = CONTARA ## Cuenta cuántos valores hay en la lista de argumentos.
+COUNTBLANK = CONTAR.BLANCO ## Cuenta el número de celdas en blanco de un rango.
+COUNTIF = CONTAR.SI ## Cuenta el número de celdas, dentro del rango, que cumplen el criterio especificado.
+COUNTIFS = CONTAR.SI.CONJUNTO ## Cuenta el número de celdas, dentro del rango, que cumplen varios criterios.
+COVAR = COVAR ## Devuelve la covarianza, que es el promedio de los productos de las desviaciones para cada pareja de puntos de datos.
+CRITBINOM = BINOM.CRIT ## Devuelve el menor valor cuya distribución binomial acumulativa es menor o igual a un valor de criterio.
+DEVSQ = DESVIA2 ## Devuelve la suma de los cuadrados de las desviaciones.
+EXPONDIST = DISTR.EXP ## Devuelve la distribución exponencial.
+FDIST = DISTR.F ## Devuelve la distribución de probabilidad F.
+FINV = DISTR.F.INV ## Devuelve la función inversa de la distribución de probabilidad F.
+FISHER = FISHER ## Devuelve la transformación Fisher.
+FISHERINV = PRUEBA.FISHER.INV ## Devuelve la función inversa de la transformación Fisher.
+FORECAST = PRONOSTICO ## Devuelve un valor en una tendencia lineal.
+FREQUENCY = FRECUENCIA ## Devuelve una distribución de frecuencia como una matriz vertical.
+FTEST = PRUEBA.F ## Devuelve el resultado de una prueba F.
+GAMMADIST = DISTR.GAMMA ## Devuelve la distribución gamma.
+GAMMAINV = DISTR.GAMMA.INV ## Devuelve la función inversa de la distribución gamma acumulativa.
+GAMMALN = GAMMA.LN ## Devuelve el logaritmo natural de la función gamma, G(x).
+GEOMEAN = MEDIA.GEOM ## Devuelve la media geométrica.
+GROWTH = CRECIMIENTO ## Devuelve valores en una tendencia exponencial.
+HARMEAN = MEDIA.ARMO ## Devuelve la media armónica.
+HYPGEOMDIST = DISTR.HIPERGEOM ## Devuelve la distribución hipergeométrica.
+INTERCEPT = INTERSECCION.EJE ## Devuelve la intersección de la línea de regresión lineal.
+KURT = CURTOSIS ## Devuelve la curtosis de un conjunto de datos.
+LARGE = K.ESIMO.MAYOR ## Devuelve el k-ésimo mayor valor de un conjunto de datos.
+LINEST = ESTIMACION.LINEAL ## Devuelve los parámetros de una tendencia lineal.
+LOGEST = ESTIMACION.LOGARITMICA ## Devuelve los parámetros de una tendencia exponencial.
+LOGINV = DISTR.LOG.INV ## Devuelve la función inversa de la distribución logarítmico-normal.
+LOGNORMDIST = DISTR.LOG.NORM ## Devuelve la distribución logarítmico-normal acumulativa.
+MAX = MAX ## Devuelve el valor máximo de una lista de argumentos.
+MAXA = MAXA ## Devuelve el valor máximo de una lista de argumentos, incluidos números, texto y valores lógicos.
+MEDIAN = MEDIANA ## Devuelve la mediana de los números dados.
+MIN = MIN ## Devuelve el valor mínimo de una lista de argumentos.
+MINA = MINA ## Devuelve el valor mínimo de una lista de argumentos, incluidos números, texto y valores lógicos.
+MODE = MODA ## Devuelve el valor más común de un conjunto de datos.
+NEGBINOMDIST = NEGBINOMDIST ## Devuelve la distribución binomial negativa.
+NORMDIST = DISTR.NORM ## Devuelve la distribución normal acumulativa.
+NORMINV = DISTR.NORM.INV ## Devuelve la función inversa de la distribución normal acumulativa.
+NORMSDIST = DISTR.NORM.ESTAND ## Devuelve la distribución normal estándar acumulativa.
+NORMSINV = DISTR.NORM.ESTAND.INV ## Devuelve la función inversa de la distribución normal estándar acumulativa.
+PEARSON = PEARSON ## Devuelve el coeficiente de momento de correlación de producto Pearson.
+PERCENTILE = PERCENTIL ## Devuelve el k-ésimo percentil de los valores de un rango.
+PERCENTRANK = RANGO.PERCENTIL ## Devuelve el rango porcentual de un valor de un conjunto de datos.
+PERMUT = PERMUTACIONES ## Devuelve el número de permutaciones de un número determinado de objetos.
+POISSON = POISSON ## Devuelve la distribución de Poisson.
+PROB = PROBABILIDAD ## Devuelve la probabilidad de que los valores de un rango se encuentren entre dos límites.
+QUARTILE = CUARTIL ## Devuelve el cuartil de un conjunto de datos.
+RANK = JERARQUIA ## Devuelve la jerarquía de un número en una lista de números.
+RSQ = COEFICIENTE.R2 ## Devuelve el cuadrado del coeficiente de momento de correlación de producto Pearson.
+SKEW = COEFICIENTE.ASIMETRIA ## Devuelve la asimetría de una distribución.
+SLOPE = PENDIENTE ## Devuelve la pendiente de la línea de regresión lineal.
+SMALL = K.ESIMO.MENOR ## Devuelve el k-ésimo menor valor de un conjunto de datos.
+STANDARDIZE = NORMALIZACION ## Devuelve un valor normalizado.
+STDEV = DESVEST ## Calcula la desviación estándar a partir de una muestra.
+STDEVA = DESVESTA ## Calcula la desviación estándar a partir de una muestra, incluidos números, texto y valores lógicos.
+STDEVP = DESVESTP ## Calcula la desviación estándar en función de toda la población.
+STDEVPA = DESVESTPA ## Calcula la desviación estándar en función de toda la población, incluidos números, texto y valores lógicos.
+STEYX = ERROR.TIPICO.XY ## Devuelve el error estándar del valor de "y" previsto para cada "x" de la regresión.
+TDIST = DISTR.T ## Devuelve la distribución de t de Student.
+TINV = DISTR.T.INV ## Devuelve la función inversa de la distribución de t de Student.
+TREND = TENDENCIA ## Devuelve valores en una tendencia lineal.
+TRIMMEAN = MEDIA.ACOTADA ## Devuelve la media del interior de un conjunto de datos.
+TTEST = PRUEBA.T ## Devuelve la probabilidad asociada a una prueba t de Student.
+VAR = VAR ## Calcula la varianza en función de una muestra.
+VARA = VARA ## Calcula la varianza en función de una muestra, incluidos números, texto y valores lógicos.
+VARP = VARP ## Calcula la varianza en función de toda la población.
+VARPA = VARPA ## Calcula la varianza en función de toda la población, incluidos números, texto y valores lógicos.
+WEIBULL = DIST.WEIBULL ## Devuelve la distribución de Weibull.
+ZTEST = PRUEBA.Z ## Devuelve el valor de una probabilidad de una cola de una prueba z.
+
+
+##
+## Text functions Funciones de texto
+##
+ASC = ASC ## Convierte las letras inglesas o katakana de ancho completo (de dos bytes) dentro de una cadena de caracteres en caracteres de ancho medio (de un byte).
+BAHTTEXT = TEXTOBAHT ## Convierte un número en texto, con el formato de moneda ß (Baht).
+CHAR = CARACTER ## Devuelve el carácter especificado por el número de código.
+CLEAN = LIMPIAR ## Quita del texto todos los caracteres no imprimibles.
+CODE = CODIGO ## Devuelve un código numérico del primer carácter de una cadena de texto.
+CONCATENATE = CONCATENAR ## Concatena varios elementos de texto en uno solo.
+DOLLAR = MONEDA ## Convierte un número en texto, con el formato de moneda $ (dólar).
+EXACT = IGUAL ## Comprueba si dos valores de texto son idénticos.
+FIND = ENCONTRAR ## Busca un valor de texto dentro de otro (distingue mayúsculas de minúsculas).
+FINDB = ENCONTRARB ## Busca un valor de texto dentro de otro (distingue mayúsculas de minúsculas).
+FIXED = DECIMAL ## Da formato a un número como texto con un número fijo de decimales.
+JIS = JIS ## Convierte las letras inglesas o katakana de ancho medio (de un byte) dentro de una cadena de caracteres en caracteres de ancho completo (de dos bytes).
+LEFT = IZQUIERDA ## Devuelve los caracteres del lado izquierdo de un valor de texto.
+LEFTB = IZQUIERDAB ## Devuelve los caracteres del lado izquierdo de un valor de texto.
+LEN = LARGO ## Devuelve el número de caracteres de una cadena de texto.
+LENB = LARGOB ## Devuelve el número de caracteres de una cadena de texto.
+LOWER = MINUSC ## Pone el texto en minúsculas.
+MID = EXTRAE ## Devuelve un número específico de caracteres de una cadena de texto que comienza en la posición que se especifique.
+MIDB = EXTRAEB ## Devuelve un número específico de caracteres de una cadena de texto que comienza en la posición que se especifique.
+PHONETIC = FONETICO ## Extrae los caracteres fonéticos (furigana) de una cadena de texto.
+PROPER = NOMPROPIO ## Pone en mayúscula la primera letra de cada palabra de un valor de texto.
+REPLACE = REEMPLAZAR ## Reemplaza caracteres de texto.
+REPLACEB = REEMPLAZARB ## Reemplaza caracteres de texto.
+REPT = REPETIR ## Repite el texto un número determinado de veces.
+RIGHT = DERECHA ## Devuelve los caracteres del lado derecho de un valor de texto.
+RIGHTB = DERECHAB ## Devuelve los caracteres del lado derecho de un valor de texto.
+SEARCH = HALLAR ## Busca un valor de texto dentro de otro (no distingue mayúsculas de minúsculas).
+SEARCHB = HALLARB ## Busca un valor de texto dentro de otro (no distingue mayúsculas de minúsculas).
+SUBSTITUTE = SUSTITUIR ## Sustituye texto nuevo por texto antiguo en una cadena de texto.
+T = T ## Convierte sus argumentos a texto.
+TEXT = TEXTO ## Da formato a un número y lo convierte en texto.
+TRIM = ESPACIOS ## Quita los espacios del texto.
+UPPER = MAYUSC ## Pone el texto en mayúsculas.
+VALUE = VALOR ## Convierte un argumento de texto en un número.
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config
new file mode 100755
index 0000000..22aaf58
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config
@@ -0,0 +1,24 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = $ # Symbol not known, should it be a € (Euro)?
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #TYHJÄ!
+DIV0 = #JAKO/0!
+VALUE = #ARVO!
+REF = #VIITTAUS!
+NAME = #NIMI?
+NUM = #LUKU!
+NA = #PUUTTUU
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions
new file mode 100755
index 0000000..289e0ea
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions
@@ -0,0 +1,416 @@
+##
+## PhpSpreadsheet
+##
+## Data in this file derived from https://www.excel-function-translation.com/
+##
+##
+
+
+##
+## Add-in and Automation functions Apuohjelma- ja automaatiofunktiot
+##
+GETPIVOTDATA = NOUDA.PIVOT.TIEDOT ## Palauttaa pivot-taulukkoraporttiin tallennettuja tietoja.
+
+
+##
+## Cube functions Kuutiofunktiot
+##
+CUBEKPIMEMBER = KUUTIOKPIJÄSEN ## Palauttaa suorituskykyilmaisimen (KPI) nimen, ominaisuuden sekä mitan ja näyttää nimen sekä ominaisuuden solussa. KPI on mitattavissa oleva suure, kuten kuukauden bruttotuotto tai vuosineljänneksen työntekijäkohtainen liikevaihto, joiden avulla tarkkaillaan organisaation suorituskykyä.
+CUBEMEMBER = KUUTIONJÄSEN ## Palauttaa kuutiohierarkian jäsenen tai monikon. Tällä funktiolla voit tarkistaa, että jäsen tai monikko on olemassa kuutiossa.
+CUBEMEMBERPROPERTY = KUUTIONJÄSENENOMINAISUUS ## Palauttaa kuution jäsenominaisuuden arvon. Tällä funktiolla voit tarkistaa, että nimi on olemassa kuutiossa, ja palauttaa tämän jäsenen määritetyn ominaisuuden.
+CUBERANKEDMEMBER = KUUTIONLUOKITELTUJÄSEN ## Palauttaa joukon n:nnen jäsenen. Tällä funktiolla voit palauttaa joukosta elementtejä, kuten parhaan myyjän tai 10 parasta opiskelijaa.
+CUBESET = KUUTIOJOUKKO ## Määrittää lasketun jäsen- tai monikkojoukon lähettämällä joukon lausekkeita palvelimessa olevalle kuutiolle. Palvelin luo joukon ja palauttaa sen Microsoft Office Excelille.
+CUBESETCOUNT = KUUTIOJOUKKOJENMÄÄRÄ ## Palauttaa joukon kohteiden määrän.
+CUBEVALUE = KUUTIONARVO ## Palauttaa koostetun arvon kuutiosta.
+
+
+##
+## Database functions Tietokantafunktiot
+##
+DAVERAGE = TKESKIARVO ## Palauttaa valittujen tietokantamerkintöjen keskiarvon.
+DCOUNT = TLASKE ## Laskee tietokannan lukuja sisältävien solujen määrän.
+DCOUNTA = TLASKEA ## Laskee tietokannan tietoja sisältävien solujen määrän.
+DGET = TNOUDA ## Hakee määritettyjä ehtoja vastaavan tietueen tietokannasta.
+DMAX = TMAKS ## Palauttaa suurimman arvon tietokannasta valittujen arvojen joukosta.
+DMIN = TMIN ## Palauttaa pienimmän arvon tietokannasta valittujen arvojen joukosta.
+DPRODUCT = TTULO ## Kertoo määritetyn ehdon täyttävien tietokannan tietueiden tietyssä kentässä olevat arvot.
+DSTDEV = TKESKIHAJONTA ## Laskee keskihajonnan tietokannasta valituista arvoista muodostuvan otoksen perusteella.
+DSTDEVP = TKESKIHAJONTAP ## Laskee keskihajonnan tietokannasta valittujen arvojen koko populaation perusteella.
+DSUM = TSUMMA ## Lisää luvut määritetyn ehdon täyttävien tietokannan tietueiden kenttäsarakkeeseen.
+DVAR = TVARIANSSI ## Laskee varianssin tietokannasta valittujen arvojen otoksen perusteella.
+DVARP = TVARIANSSIP ## Laskee varianssin tietokannasta valittujen arvojen koko populaation perusteella.
+
+
+##
+## Date and time functions Päivämäärä- ja aikafunktiot
+##
+DATE = PÄIVÄYS ## Palauttaa annetun päivämäärän järjestysluvun.
+DATEVALUE = PÄIVÄYSARVO ## Muuntaa tekstimuodossa olevan päivämäärän järjestysluvuksi.
+DAY = PÄIVÄ ## Muuntaa järjestysluvun kuukauden päiväksi.
+DAYS360 = PÄIVÄT360 ## Laskee kahden päivämäärän välisten päivien määrän käyttäen perustana 360-päiväistä vuotta.
+EDATE = PÄIVÄ.KUUKAUSI ## Palauttaa järjestyslukuna päivämäärän, joka poikkeaa aloituspäivän päivämäärästä annetun kuukausimäärän verran joko eteen- tai taaksepäin.
+EOMONTH = KUUKAUSI.LOPPU ## Palauttaa järjestyslukuna sen kuukauden viimeisen päivämäärän, joka poikkeaa annetun kuukausimäärän verran eteen- tai taaksepäin.
+HOUR = TUNNIT ## Muuntaa järjestysluvun tunneiksi.
+MINUTE = MINUUTIT ## Muuntaa järjestysluvun minuuteiksi.
+MONTH = KUUKAUSI ## Muuntaa järjestysluvun kuukausiksi.
+NETWORKDAYS = TYÖPÄIVÄT ## Palauttaa kahden päivämäärän välissä olevien täysien työpäivien määrän.
+NOW = NYT ## Palauttaa kuluvan päivämäärän ja ajan järjestysnumeron.
+SECOND = SEKUNNIT ## Muuntaa järjestysluvun sekunneiksi.
+TIME = AIKA ## Palauttaa annetun kellonajan järjestysluvun.
+TIMEVALUE = AIKA_ARVO ## Muuntaa tekstimuodossa olevan kellonajan järjestysluvuksi.
+TODAY = TÄMÄ.PÄIVÄ ## Palauttaa kuluvan päivän päivämäärän järjestysluvun.
+WEEKDAY = VIIKONPÄIVÄ ## Muuntaa järjestysluvun viikonpäiväksi.
+WEEKNUM = VIIKKO.NRO ## Muuntaa järjestysluvun luvuksi, joka ilmaisee viikon järjestysluvun vuoden alusta laskettuna.
+WORKDAY = TYÖPÄIVÄ ## Palauttaa järjestysluvun päivämäärälle, joka sijaitsee annettujen työpäivien verran eteen tai taaksepäin.
+YEAR = VUOSI ## Muuntaa järjestysluvun vuosiksi.
+YEARFRAC = VUOSI.OSA ## Palauttaa määritettyjen päivämäärien (aloituspäivä ja lopetuspäivä) välisen osan vuodesta.
+
+
+##
+## Engineering functions Tekniset funktiot
+##
+BESSELI = BESSELI ## Palauttaa muunnetun Bessel-funktion In(x).
+BESSELJ = BESSELJ ## Palauttaa Bessel-funktion Jn(x).
+BESSELK = BESSELK ## Palauttaa muunnetun Bessel-funktion Kn(x).
+BESSELY = BESSELY ## Palauttaa Bessel-funktion Yn(x).
+BIN2DEC = BINDES ## Muuntaa binaariluvun desimaaliluvuksi.
+BIN2HEX = BINHEKSA ## Muuntaa binaariluvun heksadesimaaliluvuksi.
+BIN2OCT = BINOKT ## Muuntaa binaariluvun oktaaliluvuksi.
+COMPLEX = KOMPLEKSI ## Muuntaa reaali- ja imaginaariosien kertoimet kompleksiluvuksi.
+CONVERT = MUUNNA ## Muuntaa luvun toisen mittajärjestelmän mukaiseksi.
+DEC2BIN = DESBIN ## Muuntaa desimaaliluvun binaariluvuksi.
+DEC2HEX = DESHEKSA ## Muuntaa kymmenjärjestelmän luvun heksadesimaaliluvuksi.
+DEC2OCT = DESOKT ## Muuntaa kymmenjärjestelmän luvun oktaaliluvuksi.
+DELTA = SAMA.ARVO ## Tarkistaa, ovatko kaksi arvoa yhtä suuria.
+ERF = VIRHEFUNKTIO ## Palauttaa virhefunktion.
+ERFC = VIRHEFUNKTIO.KOMPLEMENTTI ## Palauttaa komplementtivirhefunktion.
+GESTEP = RAJA ## Testaa, onko luku suurempi kuin kynnysarvo.
+HEX2BIN = HEKSABIN ## Muuntaa heksadesimaaliluvun binaariluvuksi.
+HEX2DEC = HEKSADES ## Muuntaa heksadesimaaliluvun desimaaliluvuksi.
+HEX2OCT = HEKSAOKT ## Muuntaa heksadesimaaliluvun oktaaliluvuksi.
+IMABS = KOMPLEKSI.ITSEISARVO ## Palauttaa kompleksiluvun itseisarvon (moduluksen).
+IMAGINARY = KOMPLEKSI.IMAG ## Palauttaa kompleksiluvun imaginaariosan kertoimen.
+IMARGUMENT = KOMPLEKSI.ARG ## Palauttaa theeta-argumentin, joka on radiaaneina annettu kulma.
+IMCONJUGATE = KOMPLEKSI.KONJ ## Palauttaa kompleksiluvun konjugaattiluvun.
+IMCOS = KOMPLEKSI.COS ## Palauttaa kompleksiluvun kosinin.
+IMDIV = KOMPLEKSI.OSAM ## Palauttaa kahden kompleksiluvun osamäärän.
+IMEXP = KOMPLEKSI.EKSP ## Palauttaa kompleksiluvun eksponentin.
+IMLN = KOMPLEKSI.LN ## Palauttaa kompleksiluvun luonnollisen logaritmin.
+IMLOG10 = KOMPLEKSI.LOG10 ## Palauttaa kompleksiluvun kymmenkantaisen logaritmin.
+IMLOG2 = KOMPLEKSI.LOG2 ## Palauttaa kompleksiluvun kaksikantaisen logaritmin.
+IMPOWER = KOMPLEKSI.POT ## Palauttaa kokonaislukupotenssiin korotetun kompleksiluvun.
+IMPRODUCT = KOMPLEKSI.TULO ## Palauttaa kompleksilukujen tulon.
+IMREAL = KOMPLEKSI.REAALI ## Palauttaa kompleksiluvun reaaliosan kertoimen.
+IMSIN = KOMPLEKSI.SIN ## Palauttaa kompleksiluvun sinin.
+IMSQRT = KOMPLEKSI.NELIÖJ ## Palauttaa kompleksiluvun neliöjuuren.
+IMSUB = KOMPLEKSI.EROTUS ## Palauttaa kahden kompleksiluvun erotuksen.
+IMSUM = KOMPLEKSI.SUM ## Palauttaa kompleksilukujen summan.
+OCT2BIN = OKTBIN ## Muuntaa oktaaliluvun binaariluvuksi.
+OCT2DEC = OKTDES ## Muuntaa oktaaliluvun desimaaliluvuksi.
+OCT2HEX = OKTHEKSA ## Muuntaa oktaaliluvun heksadesimaaliluvuksi.
+
+
+##
+## Financial functions Rahoitusfunktiot
+##
+ACCRINT = KERTYNYT.KORKO ## Laskee arvopaperille kertyneen koron, kun korko kertyy säännöllisin väliajoin.
+ACCRINTM = KERTYNYT.KORKO.LOPUSSA ## Laskee arvopaperille kertyneen koron, kun korko maksetaan eräpäivänä.
+AMORDEGRC = AMORDEGRC ## Laskee kunkin laskentakauden poiston poistokerrointa käyttämällä.
+AMORLINC = AMORLINC ## Palauttaa kunkin laskentakauden poiston.
+COUPDAYBS = KORKOPÄIVÄT.ALUSTA ## Palauttaa koronmaksukauden aloituspäivän ja tilityspäivän välisen ajanjakson päivien määrän.
+COUPDAYS = KORKOPÄIVÄT ## Palauttaa päivien määrän koronmaksukaudelta, johon tilityspäivä kuuluu.
+COUPDAYSNC = KORKOPÄIVÄT.SEURAAVA ## Palauttaa tilityspäivän ja seuraavan koronmaksupäivän välisen ajanjakson päivien määrän.
+COUPNCD = KORKOMAKSU.SEURAAVA ## Palauttaa tilityspäivän jälkeisen seuraavan koronmaksupäivän.
+COUPNUM = KORKOPÄIVÄJAKSOT ## Palauttaa arvopaperin ostopäivän ja erääntymispäivän välisten koronmaksupäivien määrän.
+COUPPCD = KORKOPÄIVÄ.EDELLINEN ## Palauttaa tilityspäivää edeltävän koronmaksupäivän.
+CUMIPMT = MAKSETTU.KORKO ## Palauttaa kahden jakson välisenä aikana kertyneen koron.
+CUMPRINC = MAKSETTU.LYHENNYS ## Palauttaa lainalle kahden jakson välisenä aikana kertyneen lyhennyksen.
+DB = DB ## Palauttaa kauden kirjanpidollisen poiston amerikkalaisen DB-menetelmän (Fixed-declining balance) mukaan.
+DDB = DDB ## Palauttaa kauden kirjanpidollisen poiston amerikkalaisen DDB-menetelmän (Double-Declining Balance) tai jonkin muun määrittämäsi menetelmän mukaan.
+DISC = DISKONTTOKORKO ## Palauttaa arvopaperin diskonttokoron.
+DOLLARDE = VALUUTTA.DES ## Muuntaa murtolukuna ilmoitetun valuuttamäärän desimaaliluvuksi.
+DOLLARFR = VALUUTTA.MURTO ## Muuntaa desimaalilukuna ilmaistun valuuttamäärän murtoluvuksi.
+DURATION = KESTO ## Palauttaa keston arvopaperille, jonka koronmaksu tapahtuu säännöllisesti.
+EFFECT = KORKO.EFEKT ## Palauttaa todellisen vuosikoron.
+FV = TULEVA.ARVO ## Palauttaa sijoituksen tulevan arvon.
+FVSCHEDULE = TULEVA.ARVO.ERIKORKO ## Palauttaa pääoman tulevan arvon, kun pääomalle on kertynyt korkoa vaihtelevasti.
+INTRATE = KORKO.ARVOPAPERI ## Palauttaa arvopaperin korkokannan täysin sijoitetulle arvopaperille.
+IPMT = IPMT ## Laskee sijoitukselle tai lainalle tiettynä ajanjaksona kertyvän koron.
+IRR = SISÄINEN.KORKO ## Laskee sisäisen korkokannan kassavirrasta muodostuvalle sarjalle.
+ISPMT = ONMAKSU ## Laskee sijoituksen maksetun koron tietyllä jaksolla.
+MDURATION = KESTO.MUUNN ## Palauttaa muunnetun Macauley-keston arvopaperille, jonka oletettu nimellisarvo on 100 euroa.
+MIRR = MSISÄINEN ## Palauttaa sisäisen korkokannan, kun positiivisten ja negatiivisten kassavirtojen rahoituskorko on erilainen.
+NOMINAL = KORKO.VUOSI ## Palauttaa vuosittaisen nimelliskoron.
+NPER = NJAKSO ## Palauttaa sijoituksen jaksojen määrän.
+NPV = NNA ## Palauttaa sijoituksen nykyarvon toistuvista kassavirroista muodostuvan sarjan ja diskonttokoron perusteella.
+ODDFPRICE = PARITON.ENS.NIMELLISARVO ## Palauttaa arvopaperin hinnan tilanteessa, jossa ensimmäinen jakso on pariton.
+ODDFYIELD = PARITON.ENS.TUOTTO ## Palauttaa arvopaperin tuoton tilanteessa, jossa ensimmäinen jakso on pariton.
+ODDLPRICE = PARITON.VIIM.NIMELLISARVO ## Palauttaa arvopaperin hinnan tilanteessa, jossa viimeinen jakso on pariton.
+ODDLYIELD = PARITON.VIIM.TUOTTO ## Palauttaa arvopaperin tuoton tilanteessa, jossa viimeinen jakso on pariton.
+PMT = MAKSU ## Palauttaa annuiteetin kausittaisen maksuerän.
+PPMT = PPMT ## Laskee sijoitukselle tai lainalle tiettynä ajanjaksona maksettavan lyhennyksen.
+PRICE = HINTA ## Palauttaa hinnan 100 euron nimellisarvoa kohden arvopaperille, jonka korko maksetaan säännöllisin väliajoin.
+PRICEDISC = HINTA.DISK ## Palauttaa diskontatun arvopaperin hinnan 100 euron nimellisarvoa kohden.
+PRICEMAT = HINTA.LUNASTUS ## Palauttaa hinnan 100 euron nimellisarvoa kohden arvopaperille, jonka korko maksetaan erääntymispäivänä.
+PV = NA ## Palauttaa sijoituksen nykyarvon.
+RATE = KORKO ## Palauttaa annuiteetin kausittaisen korkokannan.
+RECEIVED = SAATU.HINTA ## Palauttaa arvopaperin tuoton erääntymispäivänä kokonaan maksetulle sijoitukselle.
+SLN = STP ## Palauttaa sijoituksen tasapoiston yhdeltä jaksolta.
+SYD = VUOSIPOISTO ## Palauttaa sijoituksen vuosipoiston annettuna kautena amerikkalaisen SYD-menetelmän (Sum-of-Year's Digits) avulla.
+TBILLEQ = OBLIG.TUOTTOPROS ## Palauttaa valtion obligaation tuoton vastaavana joukkovelkakirjan tuottona.
+TBILLPRICE = OBLIG.HINTA ## Palauttaa obligaation hinnan 100 euron nimellisarvoa kohden.
+TBILLYIELD = OBLIG.TUOTTO ## Palauttaa obligaation tuoton.
+VDB = VDB ## Palauttaa annetun kauden tai kauden osan kirjanpidollisen poiston amerikkalaisen DB-menetelmän (Fixed-declining balance) mukaan.
+XIRR = SISÄINEN.KORKO.JAKSOTON ## Palauttaa sisäisen korkokannan kassavirtojen sarjoille, jotka eivät välttämättä ole säännöllisiä.
+XNPV = NNA.JAKSOTON ## Palauttaa nettonykyarvon kassavirtasarjalle, joka ei välttämättä ole kausittainen.
+YIELD = TUOTTO ## Palauttaa tuoton arvopaperille, jonka korko maksetaan säännöllisin väliajoin.
+YIELDDISC = TUOTTO.DISK ## Palauttaa diskontatun arvopaperin, kuten obligaation, vuosittaisen tuoton.
+YIELDMAT = TUOTTO.ERÄP ## Palauttaa erääntymispäivänään korkoa tuottavan arvopaperin vuosittaisen tuoton.
+
+
+##
+## Information functions Erikoisfunktiot
+##
+CELL = SOLU ## Palauttaa tietoja solun muotoilusta, sijainnista ja sisällöstä.
+ERROR.TYPE = VIRHEEN.LAJI ## Palauttaa virhetyyppiä vastaavan luvun.
+INFO = KUVAUS ## Palauttaa tietoja nykyisestä käyttöympäristöstä.
+ISBLANK = ONTYHJÄ ## Palauttaa arvon TOSI, jos arvo on tyhjä.
+ISERR = ONVIRH ## Palauttaa arvon TOSI, jos arvo on mikä tahansa virhearvo paitsi arvo #PUUTTUU!.
+ISERROR = ONVIRHE ## Palauttaa arvon TOSI, jos arvo on mikä tahansa virhearvo.
+ISEVEN = ONPARILLINEN ## Palauttaa arvon TOSI, jos arvo on parillinen.
+ISLOGICAL = ONTOTUUS ## Palauttaa arvon TOSI, jos arvo on mikä tahansa looginen arvo.
+ISNA = ONPUUTTUU ## Palauttaa arvon TOSI, jos virhearvo on #PUUTTUU!.
+ISNONTEXT = ONEI_TEKSTI ## Palauttaa arvon TOSI, jos arvo ei ole teksti.
+ISNUMBER = ONLUKU ## Palauttaa arvon TOSI, jos arvo on luku.
+ISODD = ONPARITON ## Palauttaa arvon TOSI, jos arvo on pariton.
+ISREF = ONVIITT ## Palauttaa arvon TOSI, jos arvo on viittaus.
+ISTEXT = ONTEKSTI ## Palauttaa arvon TOSI, jos arvo on teksti.
+N = N ## Palauttaa arvon luvuksi muunnettuna.
+NA = PUUTTUU ## Palauttaa virhearvon #PUUTTUU!.
+TYPE = TYYPPI ## Palauttaa luvun, joka ilmaisee arvon tietotyypin.
+
+
+##
+## Logical functions Loogiset funktiot
+##
+AND = JA ## Palauttaa arvon TOSI, jos kaikkien argumenttien arvo on TOSI.
+FALSE = EPÄTOSI ## Palauttaa totuusarvon EPÄTOSI.
+IF = JOS ## Määrittää suoritettavan loogisen testin.
+IFERROR = JOSVIRHE ## Palauttaa määrittämäsi arvon, jos kaavan tulos on virhe; muussa tapauksessa palauttaa kaavan tuloksen.
+NOT = EI ## Kääntää argumentin loogisen arvon.
+OR = TAI ## Palauttaa arvon TOSI, jos minkä tahansa argumentin arvo on TOSI.
+TRUE = TOSI ## Palauttaa totuusarvon TOSI.
+
+
+##
+## Lookup and reference functions Haku- ja viitefunktiot
+##
+ADDRESS = OSOITE ## Palauttaa laskentataulukon soluun osoittavan viittauksen tekstinä.
+AREAS = ALUEET ## Palauttaa viittauksessa olevien alueiden määrän.
+CHOOSE = VALITSE.INDEKSI ## Valitsee arvon arvoluettelosta.
+COLUMN = SARAKE ## Palauttaa viittauksen sarakenumeron.
+COLUMNS = SARAKKEET ## Palauttaa viittauksessa olevien sarakkeiden määrän.
+HLOOKUP = VHAKU ## Suorittaa haun matriisin ylimmältä riviltä ja palauttaa määritetyn solun arvon.
+HYPERLINK = HYPERLINKKI ## Luo pikakuvakkeen tai tekstin, joka avaa verkkopalvelimeen, intranetiin tai Internetiin tallennetun tiedoston.
+INDEX = INDEKSI ## Valitsee arvon viittauksesta tai matriisista indeksin mukaan.
+INDIRECT = EPÄSUORA ## Palauttaa tekstiarvona ilmaistun viittauksen.
+LOOKUP = HAKU ## Etsii arvoja vektorista tai matriisista.
+MATCH = VASTINE ## Etsii arvoja viittauksesta tai matriisista.
+OFFSET = SIIRTYMÄ ## Palauttaa annetun viittauksen siirtymän.
+ROW = RIVI ## Palauttaa viittauksen rivinumeron.
+ROWS = RIVIT ## Palauttaa viittauksessa olevien rivien määrän.
+RTD = RTD ## Noutaa COM-automaatiota (automaatio: Tapa käsitellä sovelluksen objekteja toisesta sovelluksesta tai kehitystyökalusta. Automaatio, jota aiemmin kutsuttiin OLE-automaatioksi, on teollisuusstandardi ja COM-mallin (Component Object Model) ominaisuus.) tukevasta ohjelmasta reaaliaikaisia tietoja.
+TRANSPOSE = TRANSPONOI ## Palauttaa matriisin käänteismatriisin.
+VLOOKUP = PHAKU ## Suorittaa haun matriisin ensimmäisestä sarakkeesta ja palauttaa rivillä olevan solun arvon.
+
+
+##
+## Math and trigonometry functions Matemaattiset ja trigonometriset funktiot
+##
+ABS = ITSEISARVO ## Palauttaa luvun itseisarvon.
+ACOS = ACOS ## Palauttaa luvun arkuskosinin.
+ACOSH = ACOSH ## Palauttaa luvun käänteisen hyperbolisen kosinin.
+ASIN = ASIN ## Palauttaa luvun arkussinin.
+ASINH = ASINH ## Palauttaa luvun käänteisen hyperbolisen sinin.
+ATAN = ATAN ## Palauttaa luvun arkustangentin.
+ATAN2 = ATAN2 ## Palauttaa arkustangentin x- ja y-koordinaatin perusteella.
+ATANH = ATANH ## Palauttaa luvun käänteisen hyperbolisen tangentin.
+CEILING = PYÖRISTÄ.KERR.YLÖS ## Pyöristää luvun lähimpään kokonaislukuun tai tarkkuusargumentin lähimpään kerrannaiseen.
+COMBIN = KOMBINAATIO ## Palauttaa mahdollisten kombinaatioiden määrän annetulle objektien määrälle.
+COS = COS ## Palauttaa luvun kosinin.
+COSH = COSH ## Palauttaa luvun hyperbolisen kosinin.
+DEGREES = ASTEET ## Muuntaa radiaanit asteiksi.
+EVEN = PARILLINEN ## Pyöristää luvun ylöspäin lähimpään parilliseen kokonaislukuun.
+EXP = EKSPONENTTI ## Palauttaa e:n korotettuna annetun luvun osoittamaan potenssiin.
+FACT = KERTOMA ## Palauttaa luvun kertoman.
+FACTDOUBLE = KERTOMA.OSA ## Palauttaa luvun osakertoman.
+FLOOR = PYÖRISTÄ.KERR.ALAS ## Pyöristää luvun alaspäin (nollaa kohti).
+GCD = SUURIN.YHT.TEKIJÄ ## Palauttaa suurimman yhteisen tekijän.
+INT = KOKONAISLUKU ## Pyöristää luvun alaspäin lähimpään kokonaislukuun.
+LCM = PIENIN.YHT.JAETTAVA ## Palauttaa pienimmän yhteisen tekijän.
+LN = LUONNLOG ## Palauttaa luvun luonnollisen logaritmin.
+LOG = LOG ## Laskee luvun logaritmin käyttämällä annettua kantalukua.
+LOG10 = LOG10 ## Palauttaa luvun kymmenkantaisen logaritmin.
+MDETERM = MDETERM ## Palauttaa matriisin matriisideterminantin.
+MINVERSE = MKÄÄNTEINEN ## Palauttaa matriisin käänteismatriisin.
+MMULT = MKERRO ## Palauttaa kahden matriisin tulon.
+MOD = JAKOJ ## Palauttaa jakolaskun jäännöksen.
+MROUND = PYÖRISTÄ.KERR ## Palauttaa luvun pyöristettynä annetun luvun kerrannaiseen.
+MULTINOMIAL = MULTINOMI ## Palauttaa lukujoukon multinomin.
+ODD = PARITON ## Pyöristää luvun ylöspäin lähimpään parittomaan kokonaislukuun.
+PI = PII ## Palauttaa piin arvon.
+POWER = POTENSSI ## Palauttaa luvun korotettuna haluttuun potenssiin.
+PRODUCT = TULO ## Kertoo annetut argumentit.
+QUOTIENT = OSAMÄÄRÄ ## Palauttaa osamäärän kokonaislukuosan.
+RADIANS = RADIAANIT ## Muuntaa asteet radiaaneiksi.
+RAND = SATUNNAISLUKU ## Palauttaa satunnaisluvun väliltä 0–1.
+RANDBETWEEN = SATUNNAISLUKU.VÄLILTÄ ## Palauttaa satunnaisluvun määritettyjen lukujen väliltä.
+ROMAN = ROMAN ## Muuntaa arabialaisen numeron tekstimuotoiseksi roomalaiseksi numeroksi.
+ROUND = PYÖRISTÄ ## Pyöristää luvun annettuun määrään desimaaleja.
+ROUNDDOWN = PYÖRISTÄ.DES.ALAS ## Pyöristää luvun alaspäin (nollaa kohti).
+ROUNDUP = PYÖRISTÄ.DES.YLÖS ## Pyöristää luvun ylöspäin (poispäin nollasta).
+SERIESSUM = SARJA.SUMMA ## Palauttaa kaavaan perustuvan potenssisarjan arvon.
+SIGN = ETUMERKKI ## Palauttaa luvun etumerkin.
+SIN = SIN ## Palauttaa annetun kulman sinin.
+SINH = SINH ## Palauttaa luvun hyperbolisen sinin.
+SQRT = NELIÖJUURI ## Palauttaa positiivisen neliöjuuren.
+SQRTPI = NELIÖJUURI.PII ## Palauttaa tulon (luku * pii) neliöjuuren.
+SUBTOTAL = VÄLISUMMA ## Palauttaa luettelon tai tietokannan välisumman.
+SUM = SUMMA ## Laskee yhteen annetut argumentit.
+SUMIF = SUMMA.JOS ## Laskee ehdot täyttävien solujen summan.
+SUMIFS = SUMMA.JOS.JOUKKO ## Laskee yhteen solualueen useita ehtoja vastaavat solut.
+SUMPRODUCT = TULOJEN.SUMMA ## Palauttaa matriisin toisiaan vastaavien osien tulojen summan.
+SUMSQ = NELIÖSUMMA ## Palauttaa argumenttien neliöiden summan.
+SUMX2MY2 = NELIÖSUMMIEN.EROTUS ## Palauttaa kahden matriisin toisiaan vastaavien arvojen laskettujen neliösummien erotuksen.
+SUMX2PY2 = NELIÖSUMMIEN.SUMMA ## Palauttaa kahden matriisin toisiaan vastaavien arvojen neliösummien summan.
+SUMXMY2 = EROTUSTEN.NELIÖSUMMA ## Palauttaa kahden matriisin toisiaan vastaavien arvojen erotusten neliösumman.
+TAN = TAN ## Palauttaa luvun tangentin.
+TANH = TANH ## Palauttaa luvun hyperbolisen tangentin.
+TRUNC = KATKAISE ## Katkaisee luvun kokonaisluvuksi.
+
+
+##
+## Statistical functions Tilastolliset funktiot
+##
+AVEDEV = KESKIPOIKKEAMA ## Palauttaa hajontojen itseisarvojen keskiarvon.
+AVERAGE = KESKIARVO ## Palauttaa argumenttien keskiarvon.
+AVERAGEA = KESKIARVOA ## Palauttaa argumenttien, mukaan lukien lukujen, tekstin ja loogisten arvojen, keskiarvon.
+AVERAGEIF = KESKIARVO.JOS ## Palauttaa alueen niiden solujen keskiarvon (aritmeettisen keskiarvon), jotka täyttävät annetut ehdot.
+AVERAGEIFS = KESKIARVO.JOS.JOUKKO ## Palauttaa niiden solujen keskiarvon (aritmeettisen keskiarvon), jotka vastaavat useita ehtoja.
+BETADIST = BEETAJAKAUMA ## Palauttaa kumulatiivisen beetajakaumafunktion arvon.
+BETAINV = BEETAJAKAUMA.KÄÄNT ## Palauttaa määritetyn beetajakauman käänteisen kumulatiivisen jakaumafunktion arvon.
+BINOMDIST = BINOMIJAKAUMA ## Palauttaa yksittäisen termin binomijakaumatodennäköisyyden.
+CHIDIST = CHIJAKAUMA ## Palauttaa yksisuuntaisen chi-neliön jakauman todennäköisyyden.
+CHIINV = CHIJAKAUMA.KÄÄNT ## Palauttaa yksisuuntaisen chi-neliön jakauman todennäköisyyden käänteisarvon.
+CHITEST = CHITESTI ## Palauttaa riippumattomuustestin tuloksen.
+CONFIDENCE = LUOTTAMUSVÄLI ## Palauttaa luottamusvälin populaation keskiarvolle.
+CORREL = KORRELAATIO ## Palauttaa kahden arvojoukon korrelaatiokertoimen.
+COUNT = LASKE ## Laskee argumenttiluettelossa olevien lukujen määrän.
+COUNTA = LASKE.A ## Laskee argumenttiluettelossa olevien arvojen määrän.
+COUNTBLANK = LASKE.TYHJÄT ## Laskee alueella olevien tyhjien solujen määrän.
+COUNTIF = LASKE.JOS ## Laskee alueella olevien sellaisten solujen määrän, joiden sisältö vastaa annettuja ehtoja.
+COUNTIFS = LASKE.JOS.JOUKKO ## Laskee alueella olevien sellaisten solujen määrän, joiden sisältö vastaa useita ehtoja.
+COVAR = KOVARIANSSI ## Palauttaa kovarianssin, joka on keskiarvo havaintoaineiston kunkin pisteparin poikkeamien tuloista.
+CRITBINOM = BINOMIJAKAUMA.KRIT ## Palauttaa pienimmän arvon, jossa binomijakauman kertymäfunktion arvo on pienempi tai yhtä suuri kuin vertailuarvo.
+DEVSQ = OIKAISTU.NELIÖSUMMA ## Palauttaa keskipoikkeamien neliösumman.
+EXPONDIST = EKSPONENTIAALIJAKAUMA ## Palauttaa eksponentiaalijakauman.
+FDIST = FJAKAUMA ## Palauttaa F-todennäköisyysjakauman.
+FINV = FJAKAUMA.KÄÄNT ## Palauttaa F-todennäköisyysjakauman käänteisfunktion.
+FISHER = FISHER ## Palauttaa Fisher-muunnoksen.
+FISHERINV = FISHER.KÄÄNT ## Palauttaa käänteisen Fisher-muunnoksen.
+FORECAST = ENNUSTE ## Palauttaa lineaarisen trendin arvon.
+FREQUENCY = TAAJUUS ## Palauttaa frekvenssijakautuman pystysuuntaisena matriisina.
+FTEST = FTESTI ## Palauttaa F-testin tuloksen.
+GAMMADIST = GAMMAJAKAUMA ## Palauttaa gammajakauman.
+GAMMAINV = GAMMAJAKAUMA.KÄÄNT ## Palauttaa käänteisen gammajakauman kertymäfunktion.
+GAMMALN = GAMMALN ## Palauttaa gammafunktion luonnollisen logaritmin G(x).
+GEOMEAN = KESKIARVO.GEOM ## Palauttaa geometrisen keskiarvon.
+GROWTH = KASVU ## Palauttaa eksponentiaalisen trendin arvon.
+HARMEAN = KESKIARVO.HARM ## Palauttaa harmonisen keskiarvon.
+HYPGEOMDIST = HYPERGEOM.JAKAUMA ## Palauttaa hypergeometrisen jakauman.
+INTERCEPT = LEIKKAUSPISTE ## Palauttaa lineaarisen regressiosuoran leikkauspisteen.
+KURT = KURT ## Palauttaa tietoalueen vinous-arvon eli huipukkuuden.
+LARGE = SUURI ## Palauttaa tietojoukon k:nneksi suurimman arvon.
+LINEST = LINREGR ## Palauttaa lineaarisen trendin parametrit.
+LOGEST = LOGREGR ## Palauttaa eksponentiaalisen trendin parametrit.
+LOGINV = LOGNORM.JAKAUMA.KÄÄNT ## Palauttaa lognormeeratun jakauman käänteisfunktion.
+LOGNORMDIST = LOGNORM.JAKAUMA ## Palauttaa lognormaalisen jakauman kertymäfunktion.
+MAX = MAKS ## Palauttaa suurimman arvon argumenttiluettelosta.
+MAXA = MAKSA ## Palauttaa argumenttien, mukaan lukien lukujen, tekstin ja loogisten arvojen, suurimman arvon.
+MEDIAN = MEDIAANI ## Palauttaa annettujen lukujen mediaanin.
+MIN = MIN ## Palauttaa pienimmän arvon argumenttiluettelosta.
+MINA = MINA ## Palauttaa argumenttien, mukaan lukien lukujen, tekstin ja loogisten arvojen, pienimmän arvon.
+MODE = MOODI ## Palauttaa tietojoukossa useimmin esiintyvän arvon.
+NEGBINOMDIST = BINOMIJAKAUMA.NEG ## Palauttaa negatiivisen binomijakauman.
+NORMDIST = NORM.JAKAUMA ## Palauttaa normaalijakauman kertymäfunktion.
+NORMINV = NORM.JAKAUMA.KÄÄNT ## Palauttaa käänteisen normaalijakauman kertymäfunktion.
+NORMSDIST = NORM.JAKAUMA.NORMIT ## Palauttaa normitetun normaalijakauman kertymäfunktion.
+NORMSINV = NORM.JAKAUMA.NORMIT.KÄÄNT ## Palauttaa normitetun normaalijakauman kertymäfunktion käänteisarvon.
+PEARSON = PEARSON ## Palauttaa Pearsonin tulomomenttikorrelaatiokertoimen.
+PERCENTILE = PROSENTTIPISTE ## Palauttaa alueen arvojen k:nnen prosenttipisteen.
+PERCENTRANK = PROSENTTIJÄRJESTYS ## Palauttaa tietojoukon arvon prosentuaalisen järjestysluvun.
+PERMUT = PERMUTAATIO ## Palauttaa mahdollisten permutaatioiden määrän annetulle objektien määrälle.
+POISSON = POISSON ## Palauttaa Poissonin todennäköisyysjakauman.
+PROB = TODENNÄKÖISYYS ## Palauttaa todennäköisyyden sille, että arvot ovat tietyltä väliltä.
+QUARTILE = NELJÄNNES ## Palauttaa tietoalueen neljänneksen.
+RANK = ARVON.MUKAAN ## Palauttaa luvun paikan lukuarvoluettelossa.
+RSQ = PEARSON.NELIÖ ## Palauttaa Pearsonin tulomomenttikorrelaatiokertoimen neliön.
+SKEW = JAKAUMAN.VINOUS ## Palauttaa jakauman vinouden.
+SLOPE = KULMAKERROIN ## Palauttaa lineaarisen regressiosuoran kulmakertoimen.
+SMALL = PIENI ## Palauttaa tietojoukon k:nneksi pienimmän arvon.
+STANDARDIZE = NORMITA ## Palauttaa normitetun arvon.
+STDEV = KESKIHAJONTA ## Laskee populaation keskihajonnan otoksen perusteella.
+STDEVA = KESKIHAJONTAA ## Laskee populaation keskihajonnan otoksen perusteella, mukaan lukien luvut, tekstin ja loogiset arvot.
+STDEVP = KESKIHAJONTAP ## Laskee normaalijakautuman koko populaation perusteella.
+STDEVPA = KESKIHAJONTAPA ## Laskee populaation keskihajonnan koko populaation perusteella, mukaan lukien luvut, tekstin ja totuusarvot.
+STEYX = KESKIVIRHE ## Palauttaa regression kutakin x-arvoa vastaavan ennustetun y-arvon keskivirheen.
+TDIST = TJAKAUMA ## Palauttaa t-jakautuman.
+TINV = TJAKAUMA.KÄÄNT ## Palauttaa käänteisen t-jakauman.
+TREND = SUUNTAUS ## Palauttaa lineaarisen trendin arvoja.
+TRIMMEAN = KESKIARVO.TASATTU ## Palauttaa tietojoukon tasatun keskiarvon.
+TTEST = TTESTI ## Palauttaa t-testiin liittyvän todennäköisyyden.
+VAR = VAR ## Arvioi populaation varianssia otoksen perusteella.
+VARA = VARA ## Laskee populaation varianssin otoksen perusteella, mukaan lukien luvut, tekstin ja loogiset arvot.
+VARP = VARP ## Laskee varianssin koko populaation perusteella.
+VARPA = VARPA ## Laskee populaation varianssin koko populaation perusteella, mukaan lukien luvut, tekstin ja totuusarvot.
+WEIBULL = WEIBULL ## Palauttaa Weibullin jakauman.
+ZTEST = ZTESTI ## Palauttaa z-testin yksisuuntaisen todennäköisyysarvon.
+
+
+##
+## Text functions Tekstifunktiot
+##
+ASC = ASC ## Muuntaa merkkijonossa olevat englanninkieliset DBCS- tai katakana-merkit SBCS-merkeiksi.
+BAHTTEXT = BAHTTEKSTI ## Muuntaa luvun tekstiksi ß (baht) -valuuttamuotoa käyttämällä.
+CHAR = MERKKI ## Palauttaa koodin lukua vastaavan merkin.
+CLEAN = SIIVOA ## Poistaa tekstistä kaikki tulostumattomat merkit.
+CODE = KOODI ## Palauttaa tekstimerkkijonon ensimmäisen merkin numerokoodin.
+CONCATENATE = KETJUTA ## Yhdistää useat merkkijonot yhdeksi merkkijonoksi.
+DOLLAR = VALUUTTA ## Muuntaa luvun tekstiksi $ (dollari) -valuuttamuotoa käyttämällä.
+EXACT = VERTAA ## Tarkistaa, ovatko kaksi tekstiarvoa samanlaiset.
+FIND = ETSI ## Etsii tekstiarvon toisen tekstin sisältä (tunnistaa isot ja pienet kirjaimet).
+FINDB = ETSIB ## Etsii tekstiarvon toisen tekstin sisältä (tunnistaa isot ja pienet kirjaimet).
+FIXED = KIINTEÄ ## Muotoilee luvun tekstiksi, jossa on kiinteä määrä desimaaleja.
+JIS = JIS ## Muuntaa merkkijonossa olevat englanninkieliset SBCS- tai katakana-merkit DBCS-merkeiksi.
+LEFT = VASEN ## Palauttaa tekstiarvon vasemmanpuoliset merkit.
+LEFTB = VASENB ## Palauttaa tekstiarvon vasemmanpuoliset merkit.
+LEN = PITUUS ## Palauttaa tekstimerkkijonon merkkien määrän.
+LENB = PITUUSB ## Palauttaa tekstimerkkijonon merkkien määrän.
+LOWER = PIENET ## Muuntaa tekstin pieniksi kirjaimiksi.
+MID = POIMI.TEKSTI ## Palauttaa määritetyn määrän merkkejä merkkijonosta alkaen annetusta kohdasta.
+MIDB = POIMI.TEKSTIB ## Palauttaa määritetyn määrän merkkejä merkkijonosta alkaen annetusta kohdasta.
+PHONETIC = FONEETTINEN ## Hakee foneettiset (furigana) merkit merkkijonosta.
+PROPER = ERISNIMI ## Muuttaa merkkijonon kunkin sanan ensimmäisen kirjaimen isoksi.
+REPLACE = KORVAA ## Korvaa tekstissä olevat merkit.
+REPLACEB = KORVAAB ## Korvaa tekstissä olevat merkit.
+REPT = TOISTA ## Toistaa tekstin annetun määrän kertoja.
+RIGHT = OIKEA ## Palauttaa tekstiarvon oikeanpuoliset merkit.
+RIGHTB = OIKEAB ## Palauttaa tekstiarvon oikeanpuoliset merkit.
+SEARCH = KÄY.LÄPI ## Etsii tekstiarvon toisen tekstin sisältä (isot ja pienet kirjaimet tulkitaan samoiksi merkeiksi).
+SEARCHB = KÄY.LÄPIB ## Etsii tekstiarvon toisen tekstin sisältä (isot ja pienet kirjaimet tulkitaan samoiksi merkeiksi).
+SUBSTITUTE = VAIHDA ## Korvaa merkkijonossa olevan tekstin toisella.
+T = T ## Muuntaa argumentit tekstiksi.
+TEXT = TEKSTI ## Muotoilee luvun ja muuntaa sen tekstiksi.
+TRIM = POISTA.VÄLIT ## Poistaa välilyönnit tekstistä.
+UPPER = ISOT ## Muuntaa tekstin isoiksi kirjaimiksi.
+VALUE = ARVO ## Muuntaa tekstiargumentin luvuksi.
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config
new file mode 100755
index 0000000..8189598
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config
@@ -0,0 +1,24 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = €
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #NUL!
+DIV0 = #DIV/0!
+VALUE = #VALEUR!
+REF = #REF!
+NAME = #NOM?
+NUM = #NOMBRE!
+NA = #N/A
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions
new file mode 100755
index 0000000..7f40d5f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions
@@ -0,0 +1,416 @@
+##
+## PhpSpreadsheet
+##
+## Data in this file derived from https://www.excel-function-translation.com/
+##
+##
+
+
+##
+## Add-in and Automation functions Fonctions de complément et d’automatisation
+##
+GETPIVOTDATA = LIREDONNEESTABCROISDYNAMIQUE ## Renvoie les données stockées dans un rapport de tableau croisé dynamique.
+
+
+##
+## Cube functions Fonctions Cube
+##
+CUBEKPIMEMBER = MEMBREKPICUBE ## Renvoie un nom, une propriété et une mesure d’indicateur de performance clé et affiche le nom et la propriété dans la cellule. Un indicateur de performance clé est une mesure quantifiable, telle que la marge bénéficiaire brute mensuelle ou la rotation trimestrielle du personnel, utilisée pour évaluer les performances d’une entreprise.
+CUBEMEMBER = MEMBRECUBE ## Renvoie un membre ou un uplet dans une hiérarchie de cubes. Utilisez cette fonction pour valider l’existence du membre ou de l’uplet dans le cube.
+CUBEMEMBERPROPERTY = PROPRIETEMEMBRECUBE ## Renvoie la valeur d’une propriété de membre du cube. Utilisez cette fonction pour valider l’existence d’un nom de membre dans le cube et pour renvoyer la propriété spécifiée pour ce membre.
+CUBERANKEDMEMBER = RANGMEMBRECUBE ## Renvoie le nième membre ou le membre placé à un certain rang dans un ensemble. Utilisez cette fonction pour renvoyer un ou plusieurs éléments d’un ensemble, tels que les meilleurs vendeurs ou les 10 meilleurs étudiants.
+CUBESET = JEUCUBE ## Définit un ensemble calculé de membres ou d’uplets en envoyant une expression définie au cube sur le serveur qui crée l’ensemble et le renvoie à Microsoft Office Excel.
+CUBESETCOUNT = NBJEUCUBE ## Renvoie le nombre d’éléments dans un jeu.
+CUBEVALUE = VALEURCUBE ## Renvoie une valeur d’agrégation issue d’un cube.
+
+
+##
+## Database functions Fonctions de base de données
+##
+DAVERAGE = BDMOYENNE ## Renvoie la moyenne des entrées de base de données sélectionnées.
+DCOUNT = BCOMPTE ## Compte le nombre de cellules d’une base de données qui contiennent des nombres.
+DCOUNTA = BDNBVAL ## Compte les cellules non vides d’une base de données.
+DGET = BDLIRE ## Extrait d’une base de données un enregistrement unique répondant aux critères spécifiés.
+DMAX = BDMAX ## Renvoie la valeur maximale des entrées de base de données sélectionnées.
+DMIN = BDMIN ## Renvoie la valeur minimale des entrées de base de données sélectionnées.
+DPRODUCT = BDPRODUIT ## Multiplie les valeurs d’un champ particulier des enregistrements d’une base de données, qui répondent aux critères spécifiés.
+DSTDEV = BDECARTYPE ## Calcule l’écart type pour un échantillon d’entrées de base de données sélectionnées.
+DSTDEVP = BDECARTYPEP ## Calcule l’écart type pour l’ensemble d’une population d’entrées de base de données sélectionnées.
+DSUM = BDSOMME ## Ajoute les nombres dans la colonne de champ des enregistrements de la base de données, qui répondent aux critères.
+DVAR = BDVAR ## Calcule la variance pour un échantillon d’entrées de base de données sélectionnées.
+DVARP = BDVARP ## Calcule la variance pour l’ensemble d’une population d’entrées de base de données sélectionnées.
+
+
+##
+## Date and time functions Fonctions de date et d’heure
+##
+DATE = DATE ## Renvoie le numéro de série d’une date précise.
+DATEVALUE = DATEVAL ## Convertit une date représentée sous forme de texte en numéro de série.
+DAY = JOUR ## Convertit un numéro de série en jour du mois.
+DAYS360 = JOURS360 ## Calcule le nombre de jours qui séparent deux dates sur la base d’une année de 360 jours.
+EDATE = MOIS.DECALER ## Renvoie le numéro séquentiel de la date qui représente une date spécifiée (l’argument date_départ), corrigée en plus ou en moins du nombre de mois indiqué.
+EOMONTH = FIN.MOIS ## Renvoie le numéro séquentiel de la date du dernier jour du mois précédant ou suivant la date_départ du nombre de mois indiqué.
+HOUR = HEURE ## Convertit un numéro de série en heure.
+MINUTE = MINUTE ## Convertit un numéro de série en minute.
+MONTH = MOIS ## Convertit un numéro de série en mois.
+NETWORKDAYS = NB.JOURS.OUVRES ## Renvoie le nombre de jours ouvrés entiers compris entre deux dates.
+NOW = MAINTENANT ## Renvoie le numéro de série de la date et de l’heure du jour.
+SECOND = SECONDE ## Convertit un numéro de série en seconde.
+TIME = TEMPS ## Renvoie le numéro de série d’une heure précise.
+TIMEVALUE = TEMPSVAL ## Convertit une date représentée sous forme de texte en numéro de série.
+TODAY = AUJOURDHUI ## Renvoie le numéro de série de la date du jour.
+WEEKDAY = JOURSEM ## Convertit un numéro de série en jour de la semaine.
+WEEKNUM = NO.SEMAINE ## Convertit un numéro de série en un numéro représentant l’ordre de la semaine dans l’année.
+WORKDAY = SERIE.JOUR.OUVRE ## Renvoie le numéro de série de la date avant ou après le nombre de jours ouvrés spécifiés.
+YEAR = ANNEE ## Convertit un numéro de série en année.
+YEARFRAC = FRACTION.ANNEE ## Renvoie la fraction de l’année représentant le nombre de jours entre la date de début et la date de fin.
+
+
+##
+## Engineering functions Fonctions d’ingénierie
+##
+BESSELI = BESSELI ## Renvoie la fonction Bessel modifiée In(x).
+BESSELJ = BESSELJ ## Renvoie la fonction Bessel Jn(x).
+BESSELK = BESSELK ## Renvoie la fonction Bessel modifiée Kn(x).
+BESSELY = BESSELY ## Renvoie la fonction Bessel Yn(x).
+BIN2DEC = BINDEC ## Convertit un nombre binaire en nombre décimal.
+BIN2HEX = BINHEX ## Convertit un nombre binaire en nombre hexadécimal.
+BIN2OCT = BINOCT ## Convertit un nombre binaire en nombre octal.
+COMPLEX = COMPLEXE ## Convertit des coefficients réel et imaginaire en un nombre complexe.
+CONVERT = CONVERT ## Convertit un nombre d’une unité de mesure à une autre.
+DEC2BIN = DECBIN ## Convertit un nombre décimal en nombre binaire.
+DEC2HEX = DECHEX ## Convertit un nombre décimal en nombre hexadécimal.
+DEC2OCT = DECOCT ## Convertit un nombre décimal en nombre octal.
+DELTA = DELTA ## Teste l’égalité de deux nombres.
+ERF = ERF ## Renvoie la valeur de la fonction d’erreur.
+ERFC = ERFC ## Renvoie la valeur de la fonction d’erreur complémentaire.
+GESTEP = SUP.SEUIL ## Teste si un nombre est supérieur à une valeur de seuil.
+HEX2BIN = HEXBIN ## Convertit un nombre hexadécimal en nombre binaire.
+HEX2DEC = HEXDEC ## Convertit un nombre hexadécimal en nombre décimal.
+HEX2OCT = HEXOCT ## Convertit un nombre hexadécimal en nombre octal.
+IMABS = COMPLEXE.MODULE ## Renvoie la valeur absolue (module) d’un nombre complexe.
+IMAGINARY = COMPLEXE.IMAGINAIRE ## Renvoie le coefficient imaginaire d’un nombre complexe.
+IMARGUMENT = COMPLEXE.ARGUMENT ## Renvoie l’argument thêta, un angle exprimé en radians.
+IMCONJUGATE = COMPLEXE.CONJUGUE ## Renvoie le nombre complexe conjugué d’un nombre complexe.
+IMCOS = IMCOS ## Renvoie le cosinus d’un nombre complexe.
+IMDIV = COMPLEXE.DIV ## Renvoie le quotient de deux nombres complexes.
+IMEXP = COMPLEXE.EXP ## Renvoie la fonction exponentielle d’un nombre complexe.
+IMLN = COMPLEXE.LN ## Renvoie le logarithme népérien d’un nombre complexe.
+IMLOG10 = COMPLEXE.LOG10 ## Calcule le logarithme en base 10 d’un nombre complexe.
+IMLOG2 = COMPLEXE.LOG2 ## Calcule le logarithme en base 2 d’un nombre complexe.
+IMPOWER = COMPLEXE.PUISSANCE ## Renvoie un nombre complexe élevé à une puissance entière.
+IMPRODUCT = COMPLEXE.PRODUIT ## Renvoie le produit de plusieurs nombres complexes.
+IMREAL = COMPLEXE.REEL ## Renvoie le coefficient réel d’un nombre complexe.
+IMSIN = COMPLEXE.SIN ## Renvoie le sinus d’un nombre complexe.
+IMSQRT = COMPLEXE.RACINE ## Renvoie la racine carrée d’un nombre complexe.
+IMSUB = COMPLEXE.DIFFERENCE ## Renvoie la différence entre deux nombres complexes.
+IMSUM = COMPLEXE.SOMME ## Renvoie la somme de plusieurs nombres complexes.
+OCT2BIN = OCTBIN ## Convertit un nombre octal en nombre binaire.
+OCT2DEC = OCTDEC ## Convertit un nombre octal en nombre décimal.
+OCT2HEX = OCTHEX ## Convertit un nombre octal en nombre hexadécimal.
+
+
+##
+## Financial functions Fonctions financières
+##
+ACCRINT = INTERET.ACC ## Renvoie l’intérêt couru non échu d’un titre dont l’intérêt est perçu périodiquement.
+ACCRINTM = INTERET.ACC.MAT ## Renvoie l’intérêt couru non échu d’un titre dont l’intérêt est perçu à l’échéance.
+AMORDEGRC = AMORDEGRC ## Renvoie l’amortissement correspondant à chaque période comptable en utilisant un coefficient d’amortissement.
+AMORLINC = AMORLINC ## Renvoie l’amortissement d’un bien à la fin d’une période fiscale donnée.
+COUPDAYBS = NB.JOURS.COUPON.PREC ## Renvoie le nombre de jours entre le début de la période de coupon et la date de liquidation.
+COUPDAYS = NB.JOURS.COUPONS ## Renvoie le nombre de jours pour la période du coupon contenant la date de liquidation.
+COUPDAYSNC = NB.JOURS.COUPON.SUIV ## Renvoie le nombre de jours entre la date de liquidation et la date du coupon suivant la date de liquidation.
+COUPNCD = DATE.COUPON.SUIV ## Renvoie la première date de coupon ultérieure à la date de règlement.
+COUPNUM = NB.COUPONS ## Renvoie le nombre de coupons dus entre la date de règlement et la date d’échéance.
+COUPPCD = DATE.COUPON.PREC ## Renvoie la date de coupon précédant la date de règlement.
+CUMIPMT = CUMUL.INTER ## Renvoie l’intérêt cumulé payé sur un emprunt entre deux périodes.
+CUMPRINC = CUMUL.PRINCPER ## Renvoie le montant cumulé des remboursements du capital d’un emprunt effectués entre deux périodes.
+DB = DB ## Renvoie l’amortissement d’un bien pour une période spécifiée en utilisant la méthode de l’amortissement dégressif à taux fixe.
+DDB = DDB ## Renvoie l’amortissement d’un bien pour toute période spécifiée, en utilisant la méthode de l’amortissement dégressif à taux double ou selon un coefficient à spécifier.
+DISC = TAUX.ESCOMPTE ## Calcule le taux d’escompte d’une transaction.
+DOLLARDE = PRIX.DEC ## Convertit un prix en euros, exprimé sous forme de fraction, en un prix en euros exprimé sous forme de nombre décimal.
+DOLLARFR = PRIX.FRAC ## Convertit un prix en euros, exprimé sous forme de nombre décimal, en un prix en euros exprimé sous forme de fraction.
+DURATION = DUREE ## Renvoie la durée, en années, d’un titre dont l’intérêt est perçu périodiquement.
+EFFECT = TAUX.EFFECTIF ## Renvoie le taux d’intérêt annuel effectif.
+FV = VC ## Renvoie la valeur future d’un investissement.
+FVSCHEDULE = VC.PAIEMENTS ## Calcule la valeur future d’un investissement en appliquant une série de taux d’intérêt composites.
+INTRATE = TAUX.INTERET ## Affiche le taux d’intérêt d’un titre totalement investi.
+IPMT = INTPER ## Calcule le montant des intérêts d’un investissement pour une période donnée.
+IRR = TRI ## Calcule le taux de rentabilité interne d’un investissement pour une succession de trésoreries.
+ISPMT = ISPMT ## Calcule le montant des intérêts d’un investissement pour une période donnée.
+MDURATION = DUREE.MODIFIEE ## Renvoie la durée de Macauley modifiée pour un titre ayant une valeur nominale hypothétique de 100_euros.
+MIRR = TRIM ## Calcule le taux de rentabilité interne lorsque les paiements positifs et négatifs sont financés à des taux différents.
+NOMINAL = TAUX.NOMINAL ## Calcule le taux d’intérêt nominal annuel.
+NPER = NPM ## Renvoie le nombre de versements nécessaires pour rembourser un emprunt.
+NPV = VAN ## Calcule la valeur actuelle nette d’un investissement basé sur une série de décaissements et un taux d’escompte.
+ODDFPRICE = PRIX.PCOUPON.IRREG ## Renvoie le prix par tranche de valeur nominale de 100 euros d’un titre dont la première période de coupon est irrégulière.
+ODDFYIELD = REND.PCOUPON.IRREG ## Renvoie le taux de rendement d’un titre dont la première période de coupon est irrégulière.
+ODDLPRICE = PRIX.DCOUPON.IRREG ## Renvoie le prix par tranche de valeur nominale de 100 euros d’un titre dont la première période de coupon est irrégulière.
+ODDLYIELD = REND.DCOUPON.IRREG ## Renvoie le taux de rendement d’un titre dont la dernière période de coupon est irrégulière.
+PMT = VPM ## Calcule le paiement périodique d’un investissement donné.
+PPMT = PRINCPER ## Calcule, pour une période donnée, la part de remboursement du principal d’un investissement.
+PRICE = PRIX.TITRE ## Renvoie le prix d’un titre rapportant des intérêts périodiques, pour une valeur nominale de 100 euros.
+PRICEDISC = VALEUR.ENCAISSEMENT ## Renvoie la valeur d’encaissement d’un escompte commercial, pour une valeur nominale de 100 euros.
+PRICEMAT = PRIX.TITRE.ECHEANCE ## Renvoie le prix d’un titre dont la valeur nominale est 100 euros et qui rapporte des intérêts à l’échéance.
+PV = PV ## Calcule la valeur actuelle d’un investissement.
+RATE = TAUX ## Calcule le taux d’intérêt par période pour une annuité.
+RECEIVED = VALEUR.NOMINALE ## Renvoie la valeur nominale à échéance d’un effet de commerce.
+SLN = AMORLIN ## Calcule l’amortissement linéaire d’un bien pour une période donnée.
+SYD = SYD ## Calcule l’amortissement d’un bien pour une période donnée sur la base de la méthode américaine Sum-of-Years Digits (amortissement dégressif à taux décroissant appliqué à une valeur constante).
+TBILLEQ = TAUX.ESCOMPTE.R ## Renvoie le taux d’escompte rationnel d’un bon du Trésor.
+TBILLPRICE = PRIX.BON.TRESOR ## Renvoie le prix d’un bon du Trésor d’une valeur nominale de 100 euros.
+TBILLYIELD = RENDEMENT.BON.TRESOR ## Calcule le taux de rendement d’un bon du Trésor.
+VDB = VDB ## Renvoie l’amortissement d’un bien pour une période spécifiée ou partielle en utilisant une méthode de l’amortissement dégressif à taux fixe.
+XIRR = TRI.PAIEMENTS ## Calcule le taux de rentabilité interne d’un ensemble de paiements non périodiques.
+XNPV = VAN.PAIEMENTS ## Renvoie la valeur actuelle nette d’un ensemble de paiements non périodiques.
+YIELD = RENDEMENT.TITRE ## Calcule le rendement d’un titre rapportant des intérêts périodiquement.
+YIELDDISC = RENDEMENT.SIMPLE ## Calcule le taux de rendement d’un emprunt à intérêt simple (par exemple, un bon du Trésor).
+YIELDMAT = RENDEMENT.TITRE.ECHEANCE ## Renvoie le rendement annuel d’un titre qui rapporte des intérêts à l’échéance.
+
+
+##
+## Information functions Fonctions d’information
+##
+CELL = CELLULE ## Renvoie des informations sur la mise en forme, l’emplacement et le contenu d’une cellule.
+ERROR.TYPE = TYPE.ERREUR ## Renvoie un nombre correspondant à un type d’erreur.
+INFO = INFORMATIONS ## Renvoie des informations sur l’environnement d’exploitation actuel.
+ISBLANK = ESTVIDE ## Renvoie VRAI si l’argument valeur est vide.
+ISERR = ESTERR ## Renvoie VRAI si l’argument valeur fait référence à une valeur d’erreur, sauf #N/A.
+ISERROR = ESTERREUR ## Renvoie VRAI si l’argument valeur fait référence à une valeur d’erreur.
+ISEVEN = EST.PAIR ## Renvoie VRAI si le chiffre est pair.
+ISLOGICAL = ESTLOGIQUE ## Renvoie VRAI si l’argument valeur fait référence à une valeur logique.
+ISNA = ESTNA ## Renvoie VRAI si l’argument valeur fait référence à la valeur d’erreur #N/A.
+ISNONTEXT = ESTNONTEXTE ## Renvoie VRAI si l’argument valeur ne se présente pas sous forme de texte.
+ISNUMBER = ESTNUM ## Renvoie VRAI si l’argument valeur représente un nombre.
+ISODD = EST.IMPAIR ## Renvoie VRAI si le chiffre est impair.
+ISREF = ESTREF ## Renvoie VRAI si l’argument valeur est une référence.
+ISTEXT = ESTTEXTE ## Renvoie VRAI si l’argument valeur se présente sous forme de texte.
+N = N ## Renvoie une valeur convertie en nombre.
+NA = NA ## Renvoie la valeur d’erreur #N/A.
+TYPE = TYPE ## Renvoie un nombre indiquant le type de données d’une valeur.
+
+
+##
+## Logical functions Fonctions logiques
+##
+AND = ET ## Renvoie VRAI si tous ses arguments sont VRAI.
+FALSE = FAUX ## Renvoie la valeur logique FAUX.
+IF = SI ## Spécifie un test logique à effectuer.
+IFERROR = SIERREUR ## Renvoie une valeur que vous spécifiez si une formule génère une erreur ; sinon, elle renvoie le résultat de la formule.
+NOT = NON ## Inverse la logique de cet argument.
+OR = OU ## Renvoie VRAI si un des arguments est VRAI.
+TRUE = VRAI ## Renvoie la valeur logique VRAI.
+
+
+##
+## Lookup and reference functions Fonctions de recherche et de référence
+##
+ADDRESS = ADRESSE ## Renvoie une référence sous forme de texte à une seule cellule d’une feuille de calcul.
+AREAS = ZONES ## Renvoie le nombre de zones dans une référence.
+CHOOSE = CHOISIR ## Choisit une valeur dans une liste.
+COLUMN = COLONNE ## Renvoie le numéro de colonne d’une référence.
+COLUMNS = COLONNES ## Renvoie le nombre de colonnes dans une référence.
+HLOOKUP = RECHERCHEH ## Effectue une recherche dans la première ligne d’une matrice et renvoie la valeur de la cellule indiquée.
+HYPERLINK = LIEN_HYPERTEXTE ## Crée un raccourci ou un renvoi qui ouvre un document stocké sur un serveur réseau, sur un réseau Intranet ou sur Internet.
+INDEX = INDEX ## Utilise un index pour choisir une valeur provenant d’une référence ou d’une matrice.
+INDIRECT = INDIRECT ## Renvoie une référence indiquée par une valeur de texte.
+LOOKUP = RECHERCHE ## Recherche des valeurs dans un vecteur ou une matrice.
+MATCH = EQUIV ## Recherche des valeurs dans une référence ou une matrice.
+OFFSET = DECALER ## Renvoie une référence décalée par rapport à une référence donnée.
+ROW = LIGNE ## Renvoie le numéro de ligne d’une référence.
+ROWS = LIGNES ## Renvoie le nombre de lignes dans une référence.
+RTD = RTD ## Extrait les données en temps réel à partir d’un programme prenant en charge l’automation COM (Automation : utilisation des objets d'une application à partir d'une autre application ou d'un autre outil de développement. Autrefois appelée OLE Automation, Automation est une norme industrielle et une fonctionnalité du modèle d'objet COM (Component Object Model).).
+TRANSPOSE = TRANSPOSE ## Renvoie la transposition d’une matrice.
+VLOOKUP = RECHERCHEV ## Effectue une recherche dans la première colonne d’une matrice et se déplace sur la ligne pour renvoyer la valeur d’une cellule.
+
+
+##
+## Math and trigonometry functions Fonctions mathématiques et trigonométriques
+##
+ABS = ABS ## Renvoie la valeur absolue d’un nombre.
+ACOS = ACOS ## Renvoie l’arccosinus d’un nombre.
+ACOSH = ACOSH ## Renvoie le cosinus hyperbolique inverse d’un nombre.
+ASIN = ASIN ## Renvoie l’arcsinus d’un nombre.
+ASINH = ASINH ## Renvoie le sinus hyperbolique inverse d’un nombre.
+ATAN = ATAN ## Renvoie l’arctangente d’un nombre.
+ATAN2 = ATAN2 ## Renvoie l’arctangente des coordonnées x et y.
+ATANH = ATANH ## Renvoie la tangente hyperbolique inverse d’un nombre.
+CEILING = PLAFOND ## Arrondit un nombre au nombre entier le plus proche ou au multiple le plus proche de l’argument précision en s’éloignant de zéro.
+COMBIN = COMBIN ## Renvoie le nombre de combinaisons que l’on peut former avec un nombre donné d’objets.
+COS = COS ## Renvoie le cosinus d’un nombre.
+COSH = COSH ## Renvoie le cosinus hyperbolique d’un nombre.
+DEGREES = DEGRES ## Convertit des radians en degrés.
+EVEN = PAIR ## Arrondit un nombre au nombre entier pair le plus proche en s’éloignant de zéro.
+EXP = EXP ## Renvoie e élevé à la puissance d’un nombre donné.
+FACT = FACT ## Renvoie la factorielle d’un nombre.
+FACTDOUBLE = FACTDOUBLE ## Renvoie la factorielle double d’un nombre.
+FLOOR = PLANCHER ## Arrondit un nombre en tendant vers 0 (zéro).
+GCD = PGCD ## Renvoie le plus grand commun diviseur.
+INT = ENT ## Arrondit un nombre à l’entier immédiatement inférieur.
+LCM = PPCM ## Renvoie le plus petit commun multiple.
+LN = LN ## Renvoie le logarithme népérien d’un nombre.
+LOG = LOG ## Renvoie le logarithme d’un nombre dans la base spécifiée.
+LOG10 = LOG10 ## Calcule le logarithme en base 10 d’un nombre.
+MDETERM = DETERMAT ## Renvoie le déterminant d’une matrice.
+MINVERSE = INVERSEMAT ## Renvoie la matrice inverse d’une matrice.
+MMULT = PRODUITMAT ## Renvoie le produit de deux matrices.
+MOD = MOD ## Renvoie le reste d’une division.
+MROUND = ARRONDI.AU.MULTIPLE ## Donne l’arrondi d’un nombre au multiple spécifié.
+MULTINOMIAL = MULTINOMIALE ## Calcule la multinomiale d’un ensemble de nombres.
+ODD = IMPAIR ## Renvoie le nombre, arrondi à la valeur du nombre entier impair le plus proche en s’éloignant de zéro.
+PI = PI ## Renvoie la valeur de pi.
+POWER = PUISSANCE ## Renvoie la valeur du nombre élevé à une puissance.
+PRODUCT = PRODUIT ## Multiplie ses arguments.
+QUOTIENT = QUOTIENT ## Renvoie la partie entière du résultat d’une division.
+RADIANS = RADIANS ## Convertit des degrés en radians.
+RAND = ALEA ## Renvoie un nombre aléatoire compris entre 0 et 1.
+RANDBETWEEN = ALEA.ENTRE.BORNES ## Renvoie un nombre aléatoire entre les nombres que vous spécifiez.
+ROMAN = ROMAIN ## Convertit des chiffres arabes en chiffres romains, sous forme de texte.
+ROUND = ARRONDI ## Arrondit un nombre au nombre de chiffres indiqué.
+ROUNDDOWN = ARRONDI.INF ## Arrondit un nombre en tendant vers 0 (zéro).
+ROUNDUP = ARRONDI.SUP ## Arrondit un nombre à l’entier supérieur, en s’éloignant de zéro.
+SERIESSUM = SOMME.SERIES ## Renvoie la somme d’une série géométrique en s’appuyant sur la formule suivante :
+SIGN = SIGNE ## Renvoie le signe d’un nombre.
+SIN = SIN ## Renvoie le sinus d’un angle donné.
+SINH = SINH ## Renvoie le sinus hyperbolique d’un nombre.
+SQRT = RACINE ## Renvoie la racine carrée d’un nombre.
+SQRTPI = RACINE.PI ## Renvoie la racine carrée de (nombre * pi).
+SUBTOTAL = SOUS.TOTAL ## Renvoie un sous-total dans une liste ou une base de données.
+SUM = SOMME ## Calcule la somme de ses arguments.
+SUMIF = SOMME.SI ## Additionne les cellules spécifiées si elles répondent à un critère donné.
+SUMIFS = SOMME.SI.ENS ## Ajoute les cellules d’une plage qui répondent à plusieurs critères.
+SUMPRODUCT = SOMMEPROD ## Multiplie les valeurs correspondantes des matrices spécifiées et calcule la somme de ces produits.
+SUMSQ = SOMME.CARRES ## Renvoie la somme des carrés des arguments.
+SUMX2MY2 = SOMME.X2MY2 ## Renvoie la somme de la différence des carrés des valeurs correspondantes de deux matrices.
+SUMX2PY2 = SOMME.X2PY2 ## Renvoie la somme de la somme des carrés des valeurs correspondantes de deux matrices.
+SUMXMY2 = SOMME.XMY2 ## Renvoie la somme des carrés des différences entre les valeurs correspondantes de deux matrices.
+TAN = TAN ## Renvoie la tangente d’un nombre.
+TANH = TANH ## Renvoie la tangente hyperbolique d’un nombre.
+TRUNC = TRONQUE ## Renvoie la partie entière d’un nombre.
+
+
+##
+## Statistical functions Fonctions statistiques
+##
+AVEDEV = ECART.MOYEN ## Renvoie la moyenne des écarts absolus observés dans la moyenne des points de données.
+AVERAGE = MOYENNE ## Renvoie la moyenne de ses arguments.
+AVERAGEA = AVERAGEA ## Renvoie la moyenne de ses arguments, nombres, texte et valeurs logiques inclus.
+AVERAGEIF = MOYENNE.SI ## Renvoie la moyenne (arithmétique) de toutes les cellules d’une plage qui répondent à des critères donnés.
+AVERAGEIFS = MOYENNE.SI.ENS ## Renvoie la moyenne (arithmétique) de toutes les cellules qui répondent à plusieurs critères.
+BETADIST = LOI.BETA ## Renvoie la fonction de distribution cumulée.
+BETAINV = BETA.INVERSE ## Renvoie l’inverse de la fonction de distribution cumulée pour une distribution bêta spécifiée.
+BINOMDIST = LOI.BINOMIALE ## Renvoie la probabilité d’une variable aléatoire discrète suivant la loi binomiale.
+CHIDIST = LOI.KHIDEUX ## Renvoie la probabilité unilatérale de la distribution khi-deux.
+CHIINV = KHIDEUX.INVERSE ## Renvoie l’inverse de la probabilité unilatérale de la distribution khi-deux.
+CHITEST = TEST.KHIDEUX ## Renvoie le test d’indépendance.
+CONFIDENCE = INTERVALLE.CONFIANCE ## Renvoie l’intervalle de confiance pour une moyenne de population.
+CORREL = COEFFICIENT.CORRELATION ## Renvoie le coefficient de corrélation entre deux séries de données.
+COUNT = NB ## Détermine les nombres compris dans la liste des arguments.
+COUNTA = NBVAL ## Détermine le nombre de valeurs comprises dans la liste des arguments.
+COUNTBLANK = NB.VIDE ## Compte le nombre de cellules vides dans une plage.
+COUNTIF = NB.SI ## Compte le nombre de cellules qui répondent à un critère donné dans une plage.
+COUNTIFS = NB.SI.ENS ## Compte le nombre de cellules à l’intérieur d’une plage qui répondent à plusieurs critères.
+COVAR = COVARIANCE ## Renvoie la covariance, moyenne des produits des écarts pour chaque série d’observations.
+CRITBINOM = CRITERE.LOI.BINOMIALE ## Renvoie la plus petite valeur pour laquelle la distribution binomiale cumulée est inférieure ou égale à une valeur de critère.
+DEVSQ = SOMME.CARRES.ECARTS ## Renvoie la somme des carrés des écarts.
+EXPONDIST = LOI.EXPONENTIELLE ## Renvoie la distribution exponentielle.
+FDIST = LOI.F ## Renvoie la distribution de probabilité F.
+FINV = INVERSE.LOI.F ## Renvoie l’inverse de la distribution de probabilité F.
+FISHER = FISHER ## Renvoie la transformation de Fisher.
+FISHERINV = FISHER.INVERSE ## Renvoie l’inverse de la transformation de Fisher.
+FORECAST = PREVISION ## Calcule une valeur par rapport à une tendance linéaire.
+FREQUENCY = FREQUENCE ## Calcule la fréquence d’apparition des valeurs dans une plage de valeurs, puis renvoie des nombres sous forme de matrice verticale.
+FTEST = TEST.F ## Renvoie le résultat d’un test F.
+GAMMADIST = LOI.GAMMA ## Renvoie la probabilité d’une variable aléatoire suivant une loi Gamma.
+GAMMAINV = LOI.GAMMA.INVERSE ## Renvoie, pour une probabilité donnée, la valeur d’une variable aléatoire suivant une loi Gamma.
+GAMMALN = LNGAMMA ## Renvoie le logarithme népérien de la fonction Gamma, G(x)
+GEOMEAN = MOYENNE.GEOMETRIQUE ## Renvoie la moyenne géométrique.
+GROWTH = CROISSANCE ## Calcule des valeurs par rapport à une tendance exponentielle.
+HARMEAN = MOYENNE.HARMONIQUE ## Renvoie la moyenne harmonique.
+HYPGEOMDIST = LOI.HYPERGEOMETRIQUE ## Renvoie la probabilité d’une variable aléatoire discrète suivant une loi hypergéométrique.
+INTERCEPT = ORDONNEE.ORIGINE ## Renvoie l’ordonnée à l’origine d’une droite de régression linéaire.
+KURT = KURTOSIS ## Renvoie le kurtosis d’une série de données.
+LARGE = GRANDE.VALEUR ## Renvoie la k-ième plus grande valeur d’une série de données.
+LINEST = DROITEREG ## Renvoie les paramètres d’une tendance linéaire.
+LOGEST = LOGREG ## Renvoie les paramètres d’une tendance exponentielle.
+LOGINV = LOI.LOGNORMALE.INVERSE ## Renvoie l’inverse de la probabilité pour une variable aléatoire suivant la loi lognormale.
+LOGNORMDIST = LOI.LOGNORMALE ## Renvoie la probabilité d’une variable aléatoire continue suivant une loi lognormale.
+MAX = MAX ## Renvoie la valeur maximale contenue dans une liste d’arguments.
+MAXA = MAXA ## Renvoie la valeur maximale d’une liste d’arguments, nombres, texte et valeurs logiques inclus.
+MEDIAN = MEDIANE ## Renvoie la valeur médiane des nombres donnés.
+MIN = MIN ## Renvoie la valeur minimale contenue dans une liste d’arguments.
+MINA = MINA ## Renvoie la plus petite valeur d’une liste d’arguments, nombres, texte et valeurs logiques inclus.
+MODE = MODE ## Renvoie la valeur la plus courante d’une série de données.
+NEGBINOMDIST = LOI.BINOMIALE.NEG ## Renvoie la probabilité d’une variable aléatoire discrète suivant une loi binomiale négative.
+NORMDIST = LOI.NORMALE ## Renvoie la probabilité d’une variable aléatoire continue suivant une loi normale.
+NORMINV = LOI.NORMALE.INVERSE ## Renvoie, pour une probabilité donnée, la valeur d’une variable aléatoire suivant une loi normale standard.
+NORMSDIST = LOI.NORMALE.STANDARD ## Renvoie la probabilité d’une variable aléatoire continue suivant une loi normale standard.
+NORMSINV = LOI.NORMALE.STANDARD.INVERSE ## Renvoie l’inverse de la distribution cumulée normale standard.
+PEARSON = PEARSON ## Renvoie le coefficient de corrélation d’échantillonnage de Pearson.
+PERCENTILE = CENTILE ## Renvoie le k-ième centile des valeurs d’une plage.
+PERCENTRANK = RANG.POURCENTAGE ## Renvoie le rang en pourcentage d’une valeur d’une série de données.
+PERMUT = PERMUTATION ## Renvoie le nombre de permutations pour un nombre donné d’objets.
+POISSON = LOI.POISSON ## Renvoie la probabilité d’une variable aléatoire suivant une loi de Poisson.
+PROB = PROBABILITE ## Renvoie la probabilité que des valeurs d’une plage soient comprises entre deux limites.
+QUARTILE = QUARTILE ## Renvoie le quartile d’une série de données.
+RANK = RANG ## Renvoie le rang d’un nombre contenu dans une liste.
+RSQ = COEFFICIENT.DETERMINATION ## Renvoie la valeur du coefficient de détermination R^2 d’une régression linéaire.
+SKEW = COEFFICIENT.ASYMETRIE ## Renvoie l’asymétrie d’une distribution.
+SLOPE = PENTE ## Renvoie la pente d’une droite de régression linéaire.
+SMALL = PETITE.VALEUR ## Renvoie la k-ième plus petite valeur d’une série de données.
+STANDARDIZE = CENTREE.REDUITE ## Renvoie une valeur centrée réduite.
+STDEV = ECARTYPE ## Évalue l’écart type d’une population en se basant sur un échantillon de cette population.
+STDEVA = STDEVA ## Évalue l’écart type d’une population en se basant sur un échantillon de cette population, nombres, texte et valeurs logiques inclus.
+STDEVP = ECARTYPEP ## Calcule l’écart type d’une population à partir de la population entière.
+STDEVPA = STDEVPA ## Calcule l’écart type d’une population à partir de l’ensemble de la population, nombres, texte et valeurs logiques inclus.
+STEYX = ERREUR.TYPE.XY ## Renvoie l’erreur type de la valeur y prévue pour chaque x de la régression.
+TDIST = LOI.STUDENT ## Renvoie la probabilité d’une variable aléatoire suivant une loi T de Student.
+TINV = LOI.STUDENT.INVERSE ## Renvoie, pour une probabilité donnée, la valeur d’une variable aléatoire suivant une loi T de Student.
+TREND = TENDANCE ## Renvoie des valeurs par rapport à une tendance linéaire.
+TRIMMEAN = MOYENNE.REDUITE ## Renvoie la moyenne de l’intérieur d’une série de données.
+TTEST = TEST.STUDENT ## Renvoie la probabilité associée à un test T de Student.
+VAR = VAR ## Calcule la variance sur la base d’un échantillon.
+VARA = VARA ## Estime la variance d’une population en se basant sur un échantillon de cette population, nombres, texte et valeurs logiques incluses.
+VARP = VAR.P ## Calcule la variance sur la base de l’ensemble de la population.
+VARPA = VARPA ## Calcule la variance d’une population en se basant sur la population entière, nombres, texte et valeurs logiques inclus.
+WEIBULL = LOI.WEIBULL ## Renvoie la probabilité d’une variable aléatoire suivant une loi de Weibull.
+ZTEST = TEST.Z ## Renvoie la valeur de probabilité unilatérale d’un test z.
+
+
+##
+## Text functions Fonctions de texte
+##
+ASC = ASC ## Change les caractères anglais ou katakana à pleine chasse (codés sur deux octets) à l’intérieur d’une chaîne de caractères en caractères à demi-chasse (codés sur un octet).
+BAHTTEXT = BAHTTEXT ## Convertit un nombre en texte en utilisant le format monétaire ß (baht).
+CHAR = CAR ## Renvoie le caractère spécifié par le code numérique.
+CLEAN = EPURAGE ## Supprime tous les caractères de contrôle du texte.
+CODE = CODE ## Renvoie le numéro de code du premier caractère du texte.
+CONCATENATE = CONCATENER ## Assemble plusieurs éléments textuels de façon à n’en former qu’un seul.
+DOLLAR = EURO ## Convertit un nombre en texte en utilisant le format monétaire € (euro).
+EXACT = EXACT ## Vérifie si deux valeurs de texte sont identiques.
+FIND = TROUVE ## Trouve un valeur textuelle dans une autre, en respectant la casse.
+FINDB = TROUVERB ## Trouve un valeur textuelle dans une autre, en respectant la casse.
+FIXED = CTXT ## Convertit un nombre au format texte avec un nombre de décimales spécifié.
+JIS = JIS ## Change les caractères anglais ou katakana à demi-chasse (codés sur un octet) à l’intérieur d’une chaîne de caractères en caractères à à pleine chasse (codés sur deux octets).
+LEFT = GAUCHE ## Renvoie des caractères situés à l’extrême gauche d’une chaîne de caractères.
+LEFTB = GAUCHEB ## Renvoie des caractères situés à l’extrême gauche d’une chaîne de caractères.
+LEN = NBCAR ## Renvoie le nombre de caractères contenus dans une chaîne de texte.
+LENB = LENB ## Renvoie le nombre de caractères contenus dans une chaîne de texte.
+LOWER = MINUSCULE ## Convertit le texte en minuscules.
+MID = STXT ## Renvoie un nombre déterminé de caractères d’une chaîne de texte à partir de la position que vous indiquez.
+MIDB = STXTB ## Renvoie un nombre déterminé de caractères d’une chaîne de texte à partir de la position que vous indiquez.
+PHONETIC = PHONETIQUE ## Extrait les caractères phonétiques (furigana) d’une chaîne de texte.
+PROPER = NOMPROPRE ## Met en majuscules la première lettre de chaque mot dans une chaîne textuelle.
+REPLACE = REMPLACER ## Remplace des caractères dans un texte.
+REPLACEB = REMPLACERB ## Remplace des caractères dans un texte.
+REPT = REPT ## Répète un texte un certain nombre de fois.
+RIGHT = DROITE ## Renvoie des caractères situés à l’extrême droite d’une chaîne de caractères.
+RIGHTB = DROITEB ## Renvoie des caractères situés à l’extrême droite d’une chaîne de caractères.
+SEARCH = CHERCHE ## Trouve un texte dans un autre texte (sans respecter la casse).
+SEARCHB = CHERCHERB ## Trouve un texte dans un autre texte (sans respecter la casse).
+SUBSTITUTE = SUBSTITUE ## Remplace l’ancien texte d’une chaîne de caractères par un nouveau.
+T = T ## Convertit ses arguments en texte.
+TEXT = TEXTE ## Convertit un nombre au format texte.
+TRIM = SUPPRESPACE ## Supprime les espaces du texte.
+UPPER = MAJUSCULE ## Convertit le texte en majuscules.
+VALUE = CNUM ## Convertit un argument textuel en nombre
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config
new file mode 100755
index 0000000..db61436
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config
@@ -0,0 +1,23 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = Ft
+
+
+##
+## Excel Error Codes (For future use)
+##
+NULL = #NULLA!
+DIV0 = #ZÉRÓOSZTÓ!
+VALUE = #ÉRTÉK!
+REF = #HIV!
+NAME = #NÉV?
+NUM = #SZÁM!
+NA = #HIÁNYZIK
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions
new file mode 100755
index 0000000..3adffeb
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions
@@ -0,0 +1,416 @@
+##
+## PhpSpreadsheet
+##
+## Data in this file derived from https://www.excel-function-translation.com/
+##
+##
+
+
+##
+## Add-in and Automation functions Bővítmények és automatizálási függvények
+##
+GETPIVOTDATA = KIMUTATÁSADATOT.VESZ ## A kimutatásokban tárolt adatok visszaadására használható.
+
+
+##
+## Cube functions Kockafüggvények
+##
+CUBEKPIMEMBER = KOCKA.FŐTELJMUT ## Egy fő teljesítménymutató (KPI) nevét, tulajdonságát és mértékegységét adja eredményül, a nevet és a tulajdonságot megjeleníti a cellában. A KPI-k számszerűsíthető mérési lehetőséget jelentenek – ilyen mutató például a havi bruttó nyereség vagy az egy alkalmazottra jutó negyedéves forgalom –, egy szervezet teljesítményének nyomonkövetésére használhatók.
+CUBEMEMBER = KOCKA.TAG ## Kockahierachia tagját vagy rekordját adja eredményül. Ellenőrizhető vele, hogy szerepel-e a kockában az adott tag vagy rekord.
+CUBEMEMBERPROPERTY = KOCKA.TAG.TUL ## A kocka egyik tagtulajdonságának értékét adja eredményül. Használatával ellenőrizhető, hogy szerepel-e egy tagnév a kockában, eredménye pedig az erre a tagra vonatkozó, megadott tulajdonság.
+CUBERANKEDMEMBER = KOCKA.HALM.ELEM ## Egy halmaz rangsor szerinti n-edik tagját adja eredményül. Használatával egy halmaz egy vagy több elemét kaphatja meg, például a legnagyobb teljesítményű üzletkötőt vagy a 10 legjobb tanulót.
+CUBESET = KOCKA.HALM ## Számított tagok vagy rekordok halmazát adja eredményül, ehhez egy beállított kifejezést elküld a kiszolgálón található kockának, majd ezt a halmazt adja vissza a Microsoft Office Excel alkalmazásnak.
+CUBESETCOUNT = KOCKA.HALM.DB ## Egy halmaz elemszámát adja eredményül.
+CUBEVALUE = KOCKA.ÉRTÉK ## Kockából összesített értéket ad eredményül.
+
+
+##
+## Database functions Adatbázis-kezelő függvények
+##
+DAVERAGE = AB.ÁTLAG ## A kijelölt adatbáziselemek átlagát számítja ki.
+DCOUNT = AB.DARAB ## Megszámolja, hogy az adatbázisban hány cella tartalmaz számokat.
+DCOUNTA = AB.DARAB2 ## Megszámolja az adatbázisban lévő nem üres cellákat.
+DGET = AB.MEZŐ ## Egy adatbázisból egyetlen olyan rekordot ad vissza, amely megfelel a megadott feltételeknek.
+DMAX = AB.MAX ## A kiválasztott adatbáziselemek közül a legnagyobb értéket adja eredményül.
+DMIN = AB.MIN ## A kijelölt adatbáziselemek közül a legkisebb értéket adja eredményül.
+DPRODUCT = AB.SZORZAT ## Az adatbázis megadott feltételeknek eleget tevő rekordjaira összeszorozza a megadott mezőben található számértékeket, és eredményül ezt a szorzatot adja.
+DSTDEV = AB.SZÓRÁS ## A kijelölt adatbáziselemek egy mintája alapján megbecsüli a szórást.
+DSTDEVP = AB.SZÓRÁS2 ## A kijelölt adatbáziselemek teljes sokasága alapján kiszámítja a szórást.
+DSUM = AB.SZUM ## Összeadja a feltételnek megfelelő adatbázisrekordok mezőoszlopában a számokat.
+DVAR = AB.VAR ## A kijelölt adatbáziselemek mintája alapján becslést ad a szórásnégyzetre.
+DVARP = AB.VAR2 ## A kijelölt adatbáziselemek teljes sokasága alapján kiszámítja a szórásnégyzetet.
+
+
+##
+## Date and time functions Dátumfüggvények
+##
+DATE = DÁTUM ## Adott dátum dátumértékét adja eredményül.
+DATEVALUE = DÁTUMÉRTÉK ## Szövegként megadott dátumot dátumértékké alakít át.
+DAY = NAP ## Dátumértéket a hónap egy napjává (0-31) alakít.
+DAYS360 = NAP360 ## Két dátum közé eső napok számát számítja ki a 360 napos év alapján.
+EDATE = EDATE ## Adott dátumnál adott számú hónappal korábbi vagy későbbi dátum dátumértékét adja eredményül.
+EOMONTH = EOMONTH ## Adott dátumnál adott számú hónappal korábbi vagy későbbi hónap utolsó napjának dátumértékét adja eredményül.
+HOUR = ÓRA ## Időértéket órákká alakít.
+MINUTE = PERC ## Időértéket percekké alakít.
+MONTH = HÓNAP ## Időértéket hónapokká alakít.
+NETWORKDAYS = NETWORKDAYS ## Két dátum között a teljes munkanapok számát adja meg.
+NOW = MOST ## A napi dátum dátumértékét és a pontos idő időértékét adja eredményül.
+SECOND = MPERC ## Időértéket másodpercekké alakít át.
+TIME = IDŐ ## Adott időpont időértékét adja meg.
+TIMEVALUE = IDŐÉRTÉK ## Szövegként megadott időpontot időértékké alakít át.
+TODAY = MA ## A napi dátum dátumértékét adja eredményül.
+WEEKDAY = HÉT.NAPJA ## Dátumértéket a hét napjává alakítja át.
+WEEKNUM = WEEKNUM ## Visszatérési értéke egy szám, amely azt mutatja meg, hogy a megadott dátum az év hányadik hetére esik.
+WORKDAY = WORKDAY ## Adott dátumnál adott munkanappal korábbi vagy későbbi dátum dátumértékét adja eredményül.
+YEAR = ÉV ## Sorszámot évvé alakít át.
+YEARFRAC = YEARFRAC ## Az adott dátumok közötti teljes napok számát törtévként adja meg.
+
+
+##
+## Engineering functions Mérnöki függvények
+##
+BESSELI = BESSELI ## Az In(x) módosított Bessel-függvény értékét adja eredményül.
+BESSELJ = BESSELJ ## A Jn(x) Bessel-függvény értékét adja eredményül.
+BESSELK = BESSELK ## A Kn(x) módosított Bessel-függvény értékét adja eredményül.
+BESSELY = BESSELY ## Az Yn(x) módosított Bessel-függvény értékét adja eredményül.
+BIN2DEC = BIN2DEC ## Bináris számot decimálissá alakít át.
+BIN2HEX = BIN2HEX ## Bináris számot hexadecimálissá alakít át.
+BIN2OCT = BIN2OCT ## Bináris számot oktálissá alakít át.
+COMPLEX = COMPLEX ## Valós és képzetes részből komplex számot képez.
+CONVERT = CONVERT ## Mértékegységeket vált át.
+DEC2BIN = DEC2BIN ## Decimális számot binárissá alakít át.
+DEC2HEX = DEC2HEX ## Decimális számot hexadecimálissá alakít át.
+DEC2OCT = DEC2OCT ## Decimális számot oktálissá alakít át.
+DELTA = DELTA ## Azt vizsgálja, hogy két érték egyenlő-e.
+ERF = ERF ## A hibafüggvény értékét adja eredményül.
+ERFC = ERFC ## A kiegészített hibafüggvény értékét adja eredményül.
+GESTEP = GESTEP ## Azt vizsgálja, hogy egy szám nagyobb-e adott küszöbértéknél.
+HEX2BIN = HEX2BIN ## Hexadecimális számot binárissá alakít át.
+HEX2DEC = HEX2DEC ## Hexadecimális számot decimálissá alakít át.
+HEX2OCT = HEX2OCT ## Hexadecimális számot oktálissá alakít át.
+IMABS = IMABS ## Komplex szám abszolút értékét (modulusát) adja eredményül.
+IMAGINARY = IMAGINARY ## Komplex szám képzetes részét adja eredményül.
+IMARGUMENT = IMARGUMENT ## A komplex szám radiánban kifejezett théta argumentumát adja eredményül.
+IMCONJUGATE = IMCONJUGATE ## Komplex szám komplex konjugáltját adja eredményül.
+IMCOS = IMCOS ## Komplex szám koszinuszát adja eredményül.
+IMDIV = IMDIV ## Két komplex szám hányadosát adja eredményül.
+IMEXP = IMEXP ## Az e szám komplex kitevőjű hatványát adja eredményül.
+IMLN = IMLN ## Komplex szám természetes logaritmusát adja eredményül.
+IMLOG10 = IMLOG10 ## Komplex szám tízes alapú logaritmusát adja eredményül.
+IMLOG2 = IMLOG2 ## Komplex szám kettes alapú logaritmusát adja eredményül.
+IMPOWER = IMPOWER ## Komplex szám hatványát adja eredményül.
+IMPRODUCT = IMPRODUCT ## Komplex számok szorzatát adja eredményül.
+IMREAL = IMREAL ## Komplex szám valós részét adja eredményül.
+IMSIN = IMSIN ## Komplex szám szinuszát adja eredményül.
+IMSQRT = IMSQRT ## Komplex szám négyzetgyökét adja eredményül.
+IMSUB = IMSUB ## Két komplex szám különbségét adja eredményül.
+IMSUM = IMSUM ## Komplex számok összegét adja eredményül.
+OCT2BIN = OCT2BIN ## Oktális számot binárissá alakít át.
+OCT2DEC = OCT2DEC ## Oktális számot decimálissá alakít át.
+OCT2HEX = OCT2HEX ## Oktális számot hexadecimálissá alakít át.
+
+
+##
+## Financial functions Pénzügyi függvények
+##
+ACCRINT = ACCRINT ## Periodikusan kamatozó értékpapír felszaporodott kamatát adja eredményül.
+ACCRINTM = ACCRINTM ## Lejáratkor kamatozó értékpapír felszaporodott kamatát adja eredményül.
+AMORDEGRC = AMORDEGRC ## Állóeszköz lineáris értékcsökkenését adja meg az egyes könyvelési időszakokra vonatkozóan.
+AMORLINC = AMORLINC ## Az egyes könyvelési időszakokban az értékcsökkenést adja meg.
+COUPDAYBS = COUPDAYBS ## A szelvényidőszak kezdetétől a kifizetés időpontjáig eltelt napokat adja vissza.
+COUPDAYS = COUPDAYS ## A kifizetés időpontját magában foglaló szelvényperiódus hosszát adja meg napokban.
+COUPDAYSNC = COUPDAYSNC ## A kifizetés időpontja és a legközelebbi szelvénydátum közötti napok számát adja meg.
+COUPNCD = COUPNCD ## A kifizetést követő legelső szelvénydátumot adja eredményül.
+COUPNUM = COUPNUM ## A kifizetés és a lejárat időpontja között kifizetendő szelvények számát adja eredményül.
+COUPPCD = COUPPCD ## A kifizetés előtti utolsó szelvénydátumot adja eredményül.
+CUMIPMT = CUMIPMT ## Két fizetési időszak között kifizetett kamat halmozott értékét adja eredményül.
+CUMPRINC = CUMPRINC ## Két fizetési időszak között kifizetett részletek halmozott (kamatot nem tartalmazó) értékét adja eredményül.
+DB = KCS2 ## Eszköz adott időszak alatti értékcsökkenését számítja ki a lineáris leírási modell alkalmazásával.
+DDB = KCSA ## Eszköz értékcsökkenését számítja ki adott időszakra vonatkozóan a progresszív vagy egyéb megadott leírási modell alkalmazásával.
+DISC = DISC ## Értékpapír leszámítolási kamatlábát adja eredményül.
+DOLLARDE = DOLLARDE ## Egy közönséges törtként megadott számot tizedes törtté alakít át.
+DOLLARFR = DOLLARFR ## Tizedes törtként megadott számot közönséges törtté alakít át.
+DURATION = DURATION ## Periodikus kamatfizetésű értékpapír éves kamatérzékenységét adja eredményül.
+EFFECT = EFFECT ## Az éves tényleges kamatláb értékét adja eredményül.
+FV = JBÉ ## Befektetés jövőbeli értékét számítja ki.
+FVSCHEDULE = FVSCHEDULE ## A kezdőtőke adott kamatlábak szerint megnövelt jövőbeli értékét adja eredményül.
+INTRATE = INTRATE ## A lejáratig teljesen lekötött értékpapír kamatrátáját adja eredményül.
+IPMT = RRÉSZLET ## Hiteltörlesztésen belül a tőketörlesztés nagyságát számítja ki adott időszakra.
+IRR = BMR ## A befektetés belső megtérülési rátáját számítja ki pénzáramláshoz.
+ISPMT = LRÉSZLETKAMAT ## A befektetés adott időszakára fizetett kamatot számítja ki.
+MDURATION = MDURATION ## Egy 100 Ft névértékű értékpapír Macauley-féle módosított kamatérzékenységét adja eredményül.
+MIRR = MEGTÉRÜLÉS ## A befektetés belső megtérülési rátáját számítja ki a költségek és a bevételek különböző kamatlába mellett.
+NOMINAL = NOMINAL ## Az éves névleges kamatláb értékét adja eredményül.
+NPER = PER.SZÁM ## A törlesztési időszakok számát adja meg.
+NPV = NMÉ ## Befektetéshez kapcsolódó pénzáramlás nettó jelenértékét számítja ki ismert pénzáramlás és kamatláb mellett.
+ODDFPRICE = ODDFPRICE ## Egy 100 Ft névértékű, a futamidő elején töredék-időszakos értékpapír árát adja eredményül.
+ODDFYIELD = ODDFYIELD ## A futamidő elején töredék-időszakos értékpapír hozamát adja eredményül.
+ODDLPRICE = ODDLPRICE ## Egy 100 Ft névértékű, a futamidő végén töredék-időszakos értékpapír árát adja eredményül.
+ODDLYIELD = ODDLYIELD ## A futamidő végén töredék-időszakos értékpapír hozamát adja eredményül.
+PMT = RÉSZLET ## A törlesztési időszakra vonatkozó törlesztési összeget számítja ki.
+PPMT = PRÉSZLET ## Hiteltörlesztésen belül a tőketörlesztés nagyságát számítja ki adott időszakra.
+PRICE = PRICE ## Egy 100 Ft névértékű, periodikusan kamatozó értékpapír árát adja eredményül.
+PRICEDISC = PRICEDISC ## Egy 100 Ft névértékű leszámítolt értékpapír árát adja eredményül.
+PRICEMAT = PRICEMAT ## Egy 100 Ft névértékű, a lejáratkor kamatozó értékpapír árát adja eredményül.
+PV = MÉ ## Befektetés jelenlegi értékét számítja ki.
+RATE = RÁTA ## Egy törlesztési időszakban az egy időszakra eső kamatláb nagyságát számítja ki.
+RECEIVED = RECEIVED ## A lejáratig teljesen lekötött értékpapír lejáratakor kapott összegét adja eredményül.
+SLN = LCSA ## Tárgyi eszköz egy időszakra eső amortizációját adja meg bruttó érték szerinti lineáris leírási kulcsot alkalmazva.
+SYD = SYD ## Tárgyi eszköz értékcsökkenését számítja ki adott időszakra az évek számjegyösszegével dolgozó módszer alapján.
+TBILLEQ = TBILLEQ ## Kincstárjegy kötvény-egyenértékű hozamát adja eredményül.
+TBILLPRICE = TBILLPRICE ## Egy 100 Ft névértékű kincstárjegy árát adja eredményül.
+TBILLYIELD = TBILLYIELD ## Kincstárjegy hozamát adja eredményül.
+VDB = ÉCSRI ## Tárgyi eszköz amortizációját számítja ki megadott vagy részidőszakra a csökkenő egyenleg módszerének alkalmazásával.
+XIRR = XIRR ## Ütemezett készpénzforgalom (cash flow) belső megtérülési kamatrátáját adja eredményül.
+XNPV = XNPV ## Ütemezett készpénzforgalom (cash flow) nettó jelenlegi értékét adja eredményül.
+YIELD = YIELD ## Periodikusan kamatozó értékpapír hozamát adja eredményül.
+YIELDDISC = YIELDDISC ## Leszámítolt értékpapír (például kincstárjegy) éves hozamát adja eredményül.
+YIELDMAT = YIELDMAT ## Lejáratkor kamatozó értékpapír éves hozamát adja eredményül.
+
+
+##
+## Information functions Információs függvények
+##
+CELL = CELLA ## Egy cella formátumára, elhelyezkedésére vagy tartalmára vonatkozó adatokat ad eredményül.
+ERROR.TYPE = HIBA.TÍPUS ## Egy hibatípushoz tartozó számot ad eredményül.
+INFO = INFÓ ## A rendszer- és munkakörnyezet pillanatnyi állapotáról ad felvilágosítást.
+ISBLANK = ÜRES ## Eredménye IGAZ, ha az érték üres.
+ISERR = HIBA ## Eredménye IGAZ, ha az érték valamelyik hibaérték a #HIÁNYZIK kivételével.
+ISERROR = HIBÁS ## Eredménye IGAZ, ha az érték valamelyik hibaérték.
+ISEVEN = ISEVEN ## Eredménye IGAZ, ha argumentuma páros szám.
+ISLOGICAL = LOGIKAI ## Eredménye IGAZ, ha az érték logikai érték.
+ISNA = NINCS ## Eredménye IGAZ, ha az érték a #HIÁNYZIK hibaérték.
+ISNONTEXT = NEM.SZÖVEG ## Eredménye IGAZ, ha az érték nem szöveg.
+ISNUMBER = SZÁM ## Eredménye IGAZ, ha az érték szám.
+ISODD = ISODD ## Eredménye IGAZ, ha argumentuma páratlan szám.
+ISREF = HIVATKOZÁS ## Eredménye IGAZ, ha az érték hivatkozás.
+ISTEXT = SZÖVEG.E ## Eredménye IGAZ, ha az érték szöveg.
+N = N ## Argumentumának értékét számmá alakítja.
+NA = HIÁNYZIK ## Eredménye a #HIÁNYZIK hibaérték.
+TYPE = TÍPUS ## Érték adattípusának azonosítószámát adja eredményül.
+
+
+##
+## Logical functions Logikai függvények
+##
+AND = ÉS ## Eredménye IGAZ, ha minden argumentuma IGAZ.
+FALSE = HAMIS ## A HAMIS logikai értéket adja eredményül.
+IF = HA ## Logikai vizsgálatot hajt végre.
+IFERROR = HAHIBA ## A megadott értéket adja vissza, ha egy képlet hibához vezet; más esetben a képlet értékét adja eredményül.
+NOT = NEM ## Argumentuma értékének ellentettjét adja eredményül.
+OR = VAGY ## Eredménye IGAZ, ha bármely argumentuma IGAZ.
+TRUE = IGAZ ## Az IGAZ logikai értéket adja eredményül.
+
+
+##
+## Lookup and reference functions Keresési és hivatkozási függvények
+##
+ADDRESS = CÍM ## A munkalap egy cellájára való hivatkozást adja szövegként eredményül.
+AREAS = TERÜLET ## Hivatkozásban a területek számát adja eredményül.
+CHOOSE = VÁLASZT ## Értékek listájából választ ki egy elemet.
+COLUMN = OSZLOP ## Egy hivatkozás oszlopszámát adja eredményül.
+COLUMNS = OSZLOPOK ## A hivatkozásban található oszlopok számát adja eredményül.
+HLOOKUP = VKERES ## A megadott tömb felső sorában adott értékű elemet keres, és a megtalált elem oszlopából adott sorban elhelyezkedő értékkel tér vissza.
+HYPERLINK = HIPERHIVATKOZÁS ## Hálózati kiszolgálón, intraneten vagy az interneten tárolt dokumentumot megnyitó parancsikont vagy hivatkozást hoz létre.
+INDEX = INDEX ## Tömb- vagy hivatkozás indexszel megadott értékét adja vissza.
+INDIRECT = INDIREKT ## Szöveg megadott hivatkozást ad eredményül.
+LOOKUP = KERES ## Vektorban vagy tömbben keres meg értékeket.
+MATCH = HOL.VAN ## Hivatkozásban vagy tömbben értékeket keres.
+OFFSET = OFSZET ## Hivatkozás egy másik hivatkozástól számított távolságát adja meg.
+ROW = SOR ## Egy hivatkozás sorának számát adja meg.
+ROWS = SOROK ## Egy hivatkozás sorainak számát adja meg.
+RTD = RTD ## Valós idejű adatokat keres vissza a COM automatizmust (automatizálás: Egy alkalmazás objektumaival való munka másik alkalmazásból vagy fejlesztőeszközből. A korábban OLE automatizmusnak nevezett automatizálás iparági szabvány, a Component Object Model (COM) szolgáltatása.) támogató programból.
+TRANSPOSE = TRANSZPONÁLÁS ## Egy tömb transzponáltját adja eredményül.
+VLOOKUP = FKERES ## A megadott tömb bal szélső oszlopában megkeres egy értéket, majd annak sora és a megadott oszlop metszéspontjában levő értéked adja eredményül.
+
+
+##
+## Math and trigonometry functions Matematikai és trigonometrikus függvények
+##
+ABS = ABS ## Egy szám abszolút értékét adja eredményül.
+ACOS = ARCCOS ## Egy szám arkusz koszinuszát számítja ki.
+ACOSH = ACOSH ## Egy szám inverz koszinusz hiperbolikuszát számítja ki.
+ASIN = ARCSIN ## Egy szám arkusz szinuszát számítja ki.
+ASINH = ASINH ## Egy szám inverz szinusz hiperbolikuszát számítja ki.
+ATAN = ARCTAN ## Egy szám arkusz tangensét számítja ki.
+ATAN2 = ARCTAN2 ## X és y koordináták alapján számítja ki az arkusz tangens értéket.
+ATANH = ATANH ## A szám inverz tangens hiperbolikuszát számítja ki.
+CEILING = PLAFON ## Egy számot a legközelebbi egészre vagy a pontosságként megadott érték legközelebb eső többszörösére kerekít.
+COMBIN = KOMBINÁCIÓK ## Adott számú objektum összes lehetséges kombinációinak számát számítja ki.
+COS = COS ## Egy szám koszinuszát számítja ki.
+COSH = COSH ## Egy szám koszinusz hiperbolikuszát számítja ki.
+DEGREES = FOK ## Radiánt fokká alakít át.
+EVEN = PÁROS ## Egy számot a legközelebbi páros egész számra kerekít.
+EXP = KITEVŐ ## Az e adott kitevőjű hatványát adja eredményül.
+FACT = FAKT ## Egy szám faktoriálisát számítja ki.
+FACTDOUBLE = FACTDOUBLE ## Egy szám dupla faktoriálisát adja eredményül.
+FLOOR = PADLÓ ## Egy számot lefelé, a nulla felé kerekít.
+GCD = GCD ## A legnagyobb közös osztót adja eredményül.
+INT = INT ## Egy számot lefelé kerekít a legközelebbi egészre.
+LCM = LCM ## A legkisebb közös többszöröst adja eredményül.
+LN = LN ## Egy szám természetes logaritmusát számítja ki.
+LOG = LOG ## Egy szám adott alapú logaritmusát számítja ki.
+LOG10 = LOG10 ## Egy szám 10-es alapú logaritmusát számítja ki.
+MDETERM = MDETERM ## Egy tömb mátrix-determinánsát számítja ki.
+MINVERSE = INVERZ.MÁTRIX ## Egy tömb mátrix inverzét adja eredményül.
+MMULT = MSZORZAT ## Két tömb mátrix-szorzatát adja meg.
+MOD = MARADÉK ## Egy szám osztási maradékát adja eredményül.
+MROUND = MROUND ## A kívánt többszörösére kerekített értéket ad eredményül.
+MULTINOMIAL = MULTINOMIAL ## Számhalmaz multinomiálisát adja eredményül.
+ODD = PÁRATLAN ## Egy számot a legközelebbi páratlan számra kerekít.
+PI = PI ## A pi matematikai állandót adja vissza.
+POWER = HATVÁNY ## Egy szám adott kitevőjű hatványát számítja ki.
+PRODUCT = SZORZAT ## Argumentumai szorzatát számítja ki.
+QUOTIENT = QUOTIENT ## Egy hányados egész részét adja eredményül.
+RADIANS = RADIÁN ## Fokot radiánná alakít át.
+RAND = VÉL ## Egy 0 és 1 közötti véletlen számot ad eredményül.
+RANDBETWEEN = RANDBETWEEN ## Megadott számok közé eső véletlen számot állít elő.
+ROMAN = RÓMAI ## Egy számot római számokkal kifejezve szövegként ad eredményül.
+ROUND = KEREKÍTÉS ## Egy számot adott számú számjegyre kerekít.
+ROUNDDOWN = KEREKÍTÉS.LE ## Egy számot lefelé, a nulla felé kerekít.
+ROUNDUP = KEREKÍTÉS.FEL ## Egy számot felfelé, a nullától távolabbra kerekít.
+SERIESSUM = SERIESSUM ## Hatványsor összegét adja eredményül.
+SIGN = ELŐJEL ## Egy szám előjelét adja meg.
+SIN = SIN ## Egy szög szinuszát számítja ki.
+SINH = SINH ## Egy szám szinusz hiperbolikuszát számítja ki.
+SQRT = GYÖK ## Egy szám pozitív négyzetgyökét számítja ki.
+SQRTPI = SQRTPI ## A (szám*pi) négyzetgyökét adja eredményül.
+SUBTOTAL = RÉSZÖSSZEG ## Lista vagy adatbázis részösszegét adja eredményül.
+SUM = SZUM ## Összeadja az argumentumlistájában lévő számokat.
+SUMIF = SZUMHA ## A megadott feltételeknek eleget tevő cellákban található értékeket adja össze.
+SUMIFS = SZUMHATÖBB ## Több megadott feltételnek eleget tévő tartománycellák összegét adja eredményül.
+SUMPRODUCT = SZORZATÖSSZEG ## A megfelelő tömbelemek szorzatának összegét számítja ki.
+SUMSQ = NÉGYZETÖSSZEG ## Argumentumai négyzetének összegét számítja ki.
+SUMX2MY2 = SZUMX2BŐLY2 ## Két tömb megfelelő elemei négyzetének különbségét összegzi.
+SUMX2PY2 = SZUMX2MEGY2 ## Két tömb megfelelő elemei négyzetének összegét összegzi.
+SUMXMY2 = SZUMXBŐLY2 ## Két tömb megfelelő elemei különbségének négyzetösszegét számítja ki.
+TAN = TAN ## Egy szám tangensét számítja ki.
+TANH = TANH ## Egy szám tangens hiperbolikuszát számítja ki.
+TRUNC = CSONK ## Egy számot egésszé csonkít.
+
+
+##
+## Statistical functions Statisztikai függvények
+##
+AVEDEV = ÁTL.ELTÉRÉS ## Az adatpontoknak átlaguktól való átlagos abszolút eltérését számítja ki.
+AVERAGE = ÁTLAG ## Argumentumai átlagát számítja ki.
+AVERAGEA = ÁTLAGA ## Argumentumai átlagát számítja ki (beleértve a számokat, szöveget és logikai értékeket).
+AVERAGEIF = ÁTLAGHA ## A megadott feltételnek eleget tévő tartomány celláinak átlagát (számtani közepét) adja eredményül.
+AVERAGEIFS = ÁTLAGHATÖBB ## A megadott feltételeknek eleget tévő cellák átlagát (számtani közepét) adja eredményül.
+BETADIST = BÉTA.ELOSZLÁS ## A béta-eloszlás függvényt számítja ki.
+BETAINV = INVERZ.BÉTA ## Adott béta-eloszláshoz kiszámítja a béta eloszlásfüggvény inverzét.
+BINOMDIST = BINOM.ELOSZLÁS ## A diszkrét binomiális eloszlás valószínűségértékét számítja ki.
+CHIDIST = KHI.ELOSZLÁS ## A khi-négyzet-eloszlás egyszélű valószínűségértékét számítja ki.
+CHIINV = INVERZ.KHI ## A khi-négyzet-eloszlás egyszélű valószínűségértékének inverzét számítja ki.
+CHITEST = KHI.PRÓBA ## Függetlenségvizsgálatot hajt végre.
+CONFIDENCE = MEGBÍZHATÓSÁG ## Egy statisztikai sokaság várható értékének megbízhatósági intervallumát adja eredményül.
+CORREL = KORREL ## Két adathalmaz korrelációs együtthatóját számítja ki.
+COUNT = DARAB ## Megszámolja, hogy argumentumlistájában hány szám található.
+COUNTA = DARAB2 ## Megszámolja, hogy argumentumlistájában hány érték található.
+COUNTBLANK = DARABÜRES ## Egy tartományban összeszámolja az üres cellákat.
+COUNTIF = DARABTELI ## Egy tartományban összeszámolja azokat a cellákat, amelyek eleget tesznek a megadott feltételnek.
+COUNTIFS = DARABHATÖBB ## Egy tartományban összeszámolja azokat a cellákat, amelyek eleget tesznek több feltételnek.
+COVAR = KOVAR ## A kovarianciát, azaz a páronkénti eltérések szorzatának átlagát számítja ki.
+CRITBINOM = KRITBINOM ## Azt a legkisebb számot adja eredményül, amelyre a binomiális eloszlásfüggvény értéke nem kisebb egy adott határértéknél.
+DEVSQ = SQ ## Az átlagtól való eltérések négyzetének összegét számítja ki.
+EXPONDIST = EXP.ELOSZLÁS ## Az exponenciális eloszlás értékét számítja ki.
+FDIST = F.ELOSZLÁS ## Az F-eloszlás értékét számítja ki.
+FINV = INVERZ.F ## Az F-eloszlás inverzének értékét számítja ki.
+FISHER = FISHER ## Fisher-transzformációt hajt végre.
+FISHERINV = INVERZ.FISHER ## A Fisher-transzformáció inverzét hajtja végre.
+FORECAST = ELŐREJELZÉS ## Az ismert értékek alapján lineáris regresszióval becsült értéket ad eredményül.
+FREQUENCY = GYAKORISÁG ## A gyakorisági vagy empirikus eloszlás értékét függőleges tömbként adja eredményül.
+FTEST = F.PRÓBA ## Az F-próba értékét adja eredményül.
+GAMMADIST = GAMMA.ELOSZLÁS ## A gamma-eloszlás értékét számítja ki.
+GAMMAINV = INVERZ.GAMMA ## A gamma-eloszlás eloszlásfüggvénye inverzének értékét számítja ki.
+GAMMALN = GAMMALN ## A gamma-függvény természetes logaritmusát számítja ki.
+GEOMEAN = MÉRTANI.KÖZÉP ## Argumentumai mértani középértékét számítja ki.
+GROWTH = NÖV ## Exponenciális regresszió alapján ad becslést.
+HARMEAN = HARM.KÖZÉP ## Argumentumai harmonikus átlagát számítja ki.
+HYPGEOMDIST = HIPERGEOM.ELOSZLÁS ## A hipergeometriai eloszlás értékét számítja ki.
+INTERCEPT = METSZ ## A regressziós egyenes y tengellyel való metszéspontját határozza meg.
+KURT = CSÚCSOSSÁG ## Egy adathalmaz csúcsosságát számítja ki.
+LARGE = NAGY ## Egy adathalmaz k-adik legnagyobb elemét adja eredményül.
+LINEST = LIN.ILL ## A legkisebb négyzetek módszerével az adatokra illesztett egyenes paramétereit határozza meg.
+LOGEST = LOG.ILL ## Az adatokra illesztett exponenciális görbe paramétereit határozza meg.
+LOGINV = INVERZ.LOG.ELOSZLÁS ## A lognormális eloszlás inverzét számítja ki.
+LOGNORMDIST = LOG.ELOSZLÁS ## A lognormális eloszlásfüggvény értékét számítja ki.
+MAX = MAX ## Az argumentumai között szereplő legnagyobb számot adja meg.
+MAXA = MAX2 ## Az argumentumai között szereplő legnagyobb számot adja meg (beleértve a számokat, szöveget és logikai értékeket).
+MEDIAN = MEDIÁN ## Adott számhalmaz mediánját számítja ki.
+MIN = MIN ## Az argumentumai között szereplő legkisebb számot adja meg.
+MINA = MIN2 ## Az argumentumai között szereplő legkisebb számot adja meg, beleértve a számokat, szöveget és logikai értékeket.
+MODE = MÓDUSZ ## Egy adathalmazból kiválasztja a leggyakrabban előforduló számot.
+NEGBINOMDIST = NEGBINOM.ELOSZL ## A negatív binomiális eloszlás értékét számítja ki.
+NORMDIST = NORM.ELOSZL ## A normális eloszlás értékét számítja ki.
+NORMINV = INVERZ.NORM ## A normális eloszlás eloszlásfüggvénye inverzének értékét számítja ki.
+NORMSDIST = STNORMELOSZL ## A standard normális eloszlás eloszlásfüggvényének értékét számítja ki.
+NORMSINV = INVERZ.STNORM ## A standard normális eloszlás eloszlásfüggvénye inverzének értékét számítja ki.
+PEARSON = PEARSON ## A Pearson-féle korrelációs együtthatót számítja ki.
+PERCENTILE = PERCENTILIS ## Egy tartományban található értékek k-adik percentilisét, azaz százalékosztályát adja eredményül.
+PERCENTRANK = SZÁZALÉKRANG ## Egy értéknek egy adathalmazon belül vett százalékos rangját (elhelyezkedését) számítja ki.
+PERMUT = VARIÁCIÓK ## Adott számú objektum k-ad osztályú ismétlés nélküli variációinak számát számítja ki.
+POISSON = POISSON ## A Poisson-eloszlás értékét számítja ki.
+PROB = VALÓSZÍNŰSÉG ## Annak valószínűségét számítja ki, hogy adott értékek két határérték közé esnek.
+QUARTILE = KVARTILIS ## Egy adathalmaz kvartilisét (negyedszintjét) számítja ki.
+RANK = SORSZÁM ## Kiszámítja, hogy egy szám hányadik egy számsorozatban.
+RSQ = RNÉGYZET ## Kiszámítja a Pearson-féle szorzatmomentum korrelációs együtthatójának négyzetét.
+SKEW = FERDESÉG ## Egy eloszlás ferdeségét határozza meg.
+SLOPE = MEREDEKSÉG ## Egy lineáris regressziós egyenes meredekségét számítja ki.
+SMALL = KICSI ## Egy adathalmaz k-adik legkisebb elemét adja meg.
+STANDARDIZE = NORMALIZÁLÁS ## Normalizált értéket ad eredményül.
+STDEV = SZÓRÁS ## Egy statisztikai sokaság mintájából kiszámítja annak szórását.
+STDEVA = SZÓRÁSA ## Egy statisztikai sokaság mintájából kiszámítja annak szórását (beleértve a számokat, szöveget és logikai értékeket).
+STDEVP = SZÓRÁSP ## Egy statisztikai sokaság egészéből kiszámítja annak szórását.
+STDEVPA = SZÓRÁSPA ## Egy statisztikai sokaság egészéből kiszámítja annak szórását (beleértve számokat, szöveget és logikai értékeket).
+STEYX = STHIBAYX ## Egy regresszió esetén az egyes x-értékek alapján meghatározott y-értékek standard hibáját számítja ki.
+TDIST = T.ELOSZLÁS ## A Student-féle t-eloszlás értékét számítja ki.
+TINV = INVERZ.T ## A Student-féle t-eloszlás inverzét számítja ki.
+TREND = TREND ## Lineáris trend értékeit számítja ki.
+TRIMMEAN = RÉSZÁTLAG ## Egy adathalmaz középső részének átlagát számítja ki.
+TTEST = T.PRÓBA ## A Student-féle t-próbához tartozó valószínűséget számítja ki.
+VAR = VAR ## Minta alapján becslést ad a varianciára.
+VARA = VARA ## Minta alapján becslést ad a varianciára (beleértve számokat, szöveget és logikai értékeket).
+VARP = VARP ## Egy statisztikai sokaság varianciáját számítja ki.
+VARPA = VARPA ## Egy statisztikai sokaság varianciáját számítja ki (beleértve számokat, szöveget és logikai értékeket).
+WEIBULL = WEIBULL ## A Weibull-féle eloszlás értékét számítja ki.
+ZTEST = Z.PRÓBA ## Az egyszélű z-próbával kapott valószínűségértéket számítja ki.
+
+
+##
+## Text functions Szövegműveletekhez használható függvények
+##
+ASC = ASC ## Szöveg teljes szélességű (kétbájtos) latin és katakana karaktereit félszélességű (egybájtos) karakterekké alakítja.
+BAHTTEXT = BAHTSZÖVEG ## Számot szöveggé alakít a ß (baht) pénznemformátum használatával.
+CHAR = KARAKTER ## A kódszámmal meghatározott karaktert adja eredményül.
+CLEAN = TISZTÍT ## A szövegből eltávolítja az összes nem nyomtatható karaktert.
+CODE = KÓD ## Karaktersorozat első karakterének numerikus kódját adja eredményül.
+CONCATENATE = ÖSSZEFŰZ ## Több szövegelemet egyetlen szöveges elemmé fűz össze.
+DOLLAR = FORINT ## Számot pénznem formátumú szöveggé alakít át.
+EXACT = AZONOS ## Megvizsgálja, hogy két érték azonos-e.
+FIND = SZÖVEG.TALÁL ## Karaktersorozatot keres egy másikban (a kis- és nagybetűk megkülönböztetésével).
+FINDB = SZÖVEG.TALÁL2 ## Karaktersorozatot keres egy másikban (a kis- és nagybetűk megkülönböztetésével).
+FIXED = FIX ## Számot szöveges formátumúra alakít adott számú tizedesjegyre kerekítve.
+JIS = JIS ## A félszélességű (egybájtos) latin és a katakana karaktereket teljes szélességű (kétbájtos) karakterekké alakítja.
+LEFT = BAL ## Szöveg bal szélső karaktereit adja eredményül.
+LEFTB = BAL2 ## Szöveg bal szélső karaktereit adja eredményül.
+LEN = HOSSZ ## Szöveg karakterekben mért hosszát adja eredményül.
+LENB = HOSSZ2 ## Szöveg karakterekben mért hosszát adja eredményül.
+LOWER = KISBETŰ ## Szöveget kisbetűssé alakít át.
+MID = KÖZÉP ## A szöveg adott pozíciójától kezdve megadott számú karaktert ad vissza eredményként.
+MIDB = KÖZÉP2 ## A szöveg adott pozíciójától kezdve megadott számú karaktert ad vissza eredményként.
+PHONETIC = PHONETIC ## Szöveg furigana (fonetikus) karaktereit adja vissza.
+PROPER = TNÉV ## Szöveg minden szavának kezdőbetűjét nagybetűsre cseréli.
+REPLACE = CSERE ## A szövegen belül karaktereket cserél.
+REPLACEB = CSERE2 ## A szövegen belül karaktereket cserél.
+REPT = SOKSZOR ## Megadott számú alkalommal megismétel egy szövegrészt.
+RIGHT = JOBB ## Szövegrész jobb szélső karaktereit adja eredményül.
+RIGHTB = JOBB2 ## Szövegrész jobb szélső karaktereit adja eredményül.
+SEARCH = SZÖVEG.KERES ## Karaktersorozatot keres egy másikban (a kis- és nagybetűk között nem tesz különbséget).
+SEARCHB = SZÖVEG.KERES2 ## Karaktersorozatot keres egy másikban (a kis- és nagybetűk között nem tesz különbséget).
+SUBSTITUTE = HELYETTE ## Szövegben adott karaktereket másikra cserél.
+T = T ## Argumentumát szöveggé alakítja át.
+TEXT = SZÖVEG ## Számértéket alakít át adott számformátumú szöveggé.
+TRIM = TRIM ## A szövegből eltávolítja a szóközöket.
+UPPER = NAGYBETŰS ## Szöveget nagybetűssé alakít át.
+VALUE = ÉRTÉK ## Szöveget számmá alakít át.
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config
new file mode 100755
index 0000000..6cc013a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config
@@ -0,0 +1,24 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = €
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #NULLO!
+DIV0 = #DIV/0!
+VALUE = #VALORE!
+REF = #RIF!
+NAME = #NOME?
+NUM = #NUM!
+NA = #N/D
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions
new file mode 100755
index 0000000..1901baf
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions
@@ -0,0 +1,416 @@
+##
+## PhpSpreadsheet
+##
+## Data in this file derived from https://www.excel-function-translation.com/
+##
+##
+
+
+##
+## Add-in and Automation functions Funzioni di automazione e dei componenti aggiuntivi
+##
+GETPIVOTDATA = INFO.DATI.TAB.PIVOT ## Restituisce i dati memorizzati in un rapporto di tabella pivot
+
+
+##
+## Cube functions Funzioni cubo
+##
+CUBEKPIMEMBER = MEMBRO.KPI.CUBO ## Restituisce il nome, la proprietà e la misura di un indicatore di prestazioni chiave (KPI) e visualizza il nome e la proprietà nella cella. Un KPI è una misura quantificabile, ad esempio l'utile lordo mensile o il fatturato trimestrale dei dipendenti, utilizzata per il monitoraggio delle prestazioni di un'organizzazione.
+CUBEMEMBER = MEMBRO.CUBO ## Restituisce un membro o una tupla in una gerarchia di cubi. Consente di verificare l'esistenza del membro o della tupla nel cubo.
+CUBEMEMBERPROPERTY = PROPRIETÀ.MEMBRO.CUBO ## Restituisce il valore di una proprietà di un membro del cubo. Consente di verificare l'esistenza di un nome di membro all'interno del cubo e di restituire la proprietà specificata per tale membro.
+CUBERANKEDMEMBER = MEMBRO.CUBO.CON.RANGO ## Restituisce l'n-esimo membro o il membro ordinato di un insieme. Consente di restituire uno o più elementi in un insieme, ad esempio l'agente di vendita migliore o i primi 10 studenti.
+CUBESET = SET.CUBO ## Definisce un insieme di tuple o membri calcolati mediante l'invio di un'espressione di insieme al cubo sul server. In questo modo l'insieme viene creato e restituito a Microsoft Office Excel.
+CUBESETCOUNT = CONTA.SET.CUBO ## Restituisce il numero di elementi di un insieme.
+CUBEVALUE = VALORE.CUBO ## Restituisce un valore aggregato da un cubo.
+
+
+##
+## Database functions Funzioni di database
+##
+DAVERAGE = DB.MEDIA ## Restituisce la media di voci del database selezionate
+DCOUNT = DB.CONTA.NUMERI ## Conta le celle di un database contenenti numeri
+DCOUNTA = DB.CONTA.VALORI ## Conta le celle non vuote in un database
+DGET = DB.VALORI ## Estrae da un database un singolo record che soddisfa i criteri specificati
+DMAX = DB.MAX ## Restituisce il valore massimo dalle voci selezionate in un database
+DMIN = DB.MIN ## Restituisce il valore minimo dalle voci di un database selezionate
+DPRODUCT = DB.PRODOTTO ## Moltiplica i valori in un determinato campo di record che soddisfano i criteri del database
+DSTDEV = DB.DEV.ST ## Restituisce una stima della deviazione standard sulla base di un campione di voci di un database selezionate
+DSTDEVP = DB.DEV.ST.POP ## Calcola la deviazione standard sulla base di tutte le voci di un database selezionate
+DSUM = DB.SOMMA ## Aggiunge i numeri nel campo colonna di record del database che soddisfa determinati criteri
+DVAR = DB.VAR ## Restituisce una stima della varianza sulla base di un campione da voci di un database selezionate
+DVARP = DB.VAR.POP ## Calcola la varianza sulla base di tutte le voci di un database selezionate
+
+
+##
+## Date and time functions Funzioni data e ora
+##
+DATE = DATA ## Restituisce il numero seriale di una determinata data
+DATEVALUE = DATA.VALORE ## Converte una data sotto forma di testo in un numero seriale
+DAY = GIORNO ## Converte un numero seriale in un giorno del mese
+DAYS360 = GIORNO360 ## Calcola il numero di giorni compreso tra due date basandosi su un anno di 360 giorni
+EDATE = DATA.MESE ## Restituisce il numero seriale della data che rappresenta il numero di mesi prima o dopo la data di inizio
+EOMONTH = FINE.MESE ## Restituisce il numero seriale dell'ultimo giorno del mese, prima o dopo un determinato numero di mesi
+HOUR = ORA ## Converte un numero seriale in un'ora
+MINUTE = MINUTO ## Converte un numero seriale in un minuto
+MONTH = MESE ## Converte un numero seriale in un mese
+NETWORKDAYS = GIORNI.LAVORATIVI.TOT ## Restituisce il numero di tutti i giorni lavorativi compresi fra due date
+NOW = ADESSO ## Restituisce il numero seriale della data e dell'ora corrente
+SECOND = SECONDO ## Converte un numero seriale in un secondo
+TIME = ORARIO ## Restituisce il numero seriale di una determinata ora
+TIMEVALUE = ORARIO.VALORE ## Converte un orario in forma di testo in un numero seriale
+TODAY = OGGI ## Restituisce il numero seriale relativo alla data odierna
+WEEKDAY = GIORNO.SETTIMANA ## Converte un numero seriale in un giorno della settimana
+WEEKNUM = NUM.SETTIMANA ## Converte un numero seriale in un numero che rappresenta la posizione numerica di una settimana nell'anno
+WORKDAY = GIORNO.LAVORATIVO ## Restituisce il numero della data prima o dopo un determinato numero di giorni lavorativi
+YEAR = ANNO ## Converte un numero seriale in un anno
+YEARFRAC = FRAZIONE.ANNO ## Restituisce la frazione dell'anno che rappresenta il numero dei giorni compresi tra una data_ iniziale e una data_finale
+
+
+##
+## Engineering functions Funzioni ingegneristiche
+##
+BESSELI = BESSEL.I ## Restituisce la funzione di Bessel modificata In(x)
+BESSELJ = BESSEL.J ## Restituisce la funzione di Bessel Jn(x)
+BESSELK = BESSEL.K ## Restituisce la funzione di Bessel modificata Kn(x)
+BESSELY = BESSEL.Y ## Restituisce la funzione di Bessel Yn(x)
+BIN2DEC = BINARIO.DECIMALE ## Converte un numero binario in decimale
+BIN2HEX = BINARIO.HEX ## Converte un numero binario in esadecimale
+BIN2OCT = BINARIO.OCT ## Converte un numero binario in ottale
+COMPLEX = COMPLESSO ## Converte i coefficienti reali e immaginari in numeri complessi
+CONVERT = CONVERTI ## Converte un numero da un sistema di misura in un altro
+DEC2BIN = DECIMALE.BINARIO ## Converte un numero decimale in binario
+DEC2HEX = DECIMALE.HEX ## Converte un numero decimale in esadecimale
+DEC2OCT = DECIMALE.OCT ## Converte un numero decimale in ottale
+DELTA = DELTA ## Verifica se due valori sono uguali
+ERF = FUNZ.ERRORE ## Restituisce la funzione di errore
+ERFC = FUNZ.ERRORE.COMP ## Restituisce la funzione di errore complementare
+GESTEP = SOGLIA ## Verifica se un numero è maggiore del valore di soglia
+HEX2BIN = HEX.BINARIO ## Converte un numero esadecimale in binario
+HEX2DEC = HEX.DECIMALE ## Converte un numero esadecimale in decimale
+HEX2OCT = HEX.OCT ## Converte un numero esadecimale in ottale
+IMABS = COMP.MODULO ## Restituisce il valore assoluto (modulo) di un numero complesso
+IMAGINARY = COMP.IMMAGINARIO ## Restituisce il coefficiente immaginario di un numero complesso
+IMARGUMENT = COMP.ARGOMENTO ## Restituisce l'argomento theta, un angolo espresso in radianti
+IMCONJUGATE = COMP.CONIUGATO ## Restituisce il complesso coniugato del numero complesso
+IMCOS = COMP.COS ## Restituisce il coseno di un numero complesso
+IMDIV = COMP.DIV ## Restituisce il quoziente di due numeri complessi
+IMEXP = COMP.EXP ## Restituisce il valore esponenziale di un numero complesso
+IMLN = COMP.LN ## Restituisce il logaritmo naturale di un numero complesso
+IMLOG10 = COMP.LOG10 ## Restituisce il logaritmo in base 10 di un numero complesso
+IMLOG2 = COMP.LOG2 ## Restituisce un logaritmo in base 2 di un numero complesso
+IMPOWER = COMP.POTENZA ## Restituisce il numero complesso elevato a una potenza intera
+IMPRODUCT = COMP.PRODOTTO ## Restituisce il prodotto di numeri complessi compresi tra 2 e 29
+IMREAL = COMP.PARTE.REALE ## Restituisce il coefficiente reale di un numero complesso
+IMSIN = COMP.SEN ## Restituisce il seno di un numero complesso
+IMSQRT = COMP.RADQ ## Restituisce la radice quadrata di un numero complesso
+IMSUB = COMP.DIFF ## Restituisce la differenza fra due numeri complessi
+IMSUM = COMP.SOMMA ## Restituisce la somma di numeri complessi
+OCT2BIN = OCT.BINARIO ## Converte un numero ottale in binario
+OCT2DEC = OCT.DECIMALE ## Converte un numero ottale in decimale
+OCT2HEX = OCT.HEX ## Converte un numero ottale in esadecimale
+
+
+##
+## Financial functions Funzioni finanziarie
+##
+ACCRINT = INT.MATURATO.PER ## Restituisce l'interesse maturato di un titolo che paga interessi periodici
+ACCRINTM = INT.MATURATO.SCAD ## Restituisce l'interesse maturato di un titolo che paga interessi alla scadenza
+AMORDEGRC = AMMORT.DEGR ## Restituisce l'ammortamento per ogni periodo contabile utilizzando un coefficiente di ammortamento
+AMORLINC = AMMORT.PER ## Restituisce l'ammortamento per ogni periodo contabile
+COUPDAYBS = GIORNI.CED.INIZ.LIQ ## Restituisce il numero dei giorni che vanno dall'inizio del periodo di durata della cedola alla data di liquidazione
+COUPDAYS = GIORNI.CED ## Restituisce il numero dei giorni relativi al periodo della cedola che contiene la data di liquidazione
+COUPDAYSNC = GIORNI.CED.NUOVA ## Restituisce il numero di giorni che vanno dalla data di liquidazione alla data della cedola successiva
+COUPNCD = DATA.CED.SUCC ## Restituisce un numero che rappresenta la data della cedola successiva alla data di liquidazione
+COUPNUM = NUM.CED ## Restituisce il numero di cedole pagabili fra la data di liquidazione e la data di scadenza
+COUPPCD = DATA.CED.PREC ## Restituisce un numero che rappresenta la data della cedola precedente alla data di liquidazione
+CUMIPMT = INT.CUMUL ## Restituisce l'interesse cumulativo pagato fra due periodi
+CUMPRINC = CAP.CUM ## Restituisce il capitale cumulativo pagato per estinguere un debito fra due periodi
+DB = DB ## Restituisce l'ammortamento di un bene per un periodo specificato utilizzando il metodo di ammortamento a quote fisse decrescenti
+DDB = AMMORT ## Restituisce l'ammortamento di un bene per un periodo specificato utilizzando il metodo di ammortamento a doppie quote decrescenti o altri metodi specificati
+DISC = TASSO.SCONTO ## Restituisce il tasso di sconto per un titolo
+DOLLARDE = VALUTA.DEC ## Converte un prezzo valuta, espresso come frazione, in prezzo valuta, espresso come numero decimale
+DOLLARFR = VALUTA.FRAZ ## Converte un prezzo valuta, espresso come numero decimale, in prezzo valuta, espresso come frazione
+DURATION = DURATA ## Restituisce la durata annuale di un titolo con i pagamenti di interesse periodico
+EFFECT = EFFETTIVO ## Restituisce l'effettivo tasso di interesse annuo
+FV = VAL.FUT ## Restituisce il valore futuro di un investimento
+FVSCHEDULE = VAL.FUT.CAPITALE ## Restituisce il valore futuro di un capitale iniziale dopo aver applicato una serie di tassi di interesse composti
+INTRATE = TASSO.INT ## Restituisce il tasso di interesse per un titolo interamente investito
+IPMT = INTERESSI ## Restituisce il valore degli interessi per un investimento relativo a un periodo specifico
+IRR = TIR.COST ## Restituisce il tasso di rendimento interno per una serie di flussi di cassa
+ISPMT = INTERESSE.RATA ## Calcola l'interesse di un investimento pagato durante un periodo specifico
+MDURATION = DURATA.M ## Restituisce la durata Macauley modificata per un titolo con un valore presunto di € 100
+MIRR = TIR.VAR ## Restituisce il tasso di rendimento interno in cui i flussi di cassa positivi e negativi sono finanziati a tassi differenti
+NOMINAL = NOMINALE ## Restituisce il tasso di interesse nominale annuale
+NPER = NUM.RATE ## Restituisce un numero di periodi relativi a un investimento
+NPV = VAN ## Restituisce il valore attuale netto di un investimento basato su una serie di flussi di cassa periodici e sul tasso di sconto
+ODDFPRICE = PREZZO.PRIMO.IRR ## Restituisce il prezzo di un titolo dal valore nominale di € 100 avente il primo periodo di durata irregolare
+ODDFYIELD = REND.PRIMO.IRR ## Restituisce il rendimento di un titolo avente il primo periodo di durata irregolare
+ODDLPRICE = PREZZO.ULTIMO.IRR ## Restituisce il prezzo di un titolo dal valore nominale di € 100 avente l'ultimo periodo di durata irregolare
+ODDLYIELD = REND.ULTIMO.IRR ## Restituisce il rendimento di un titolo avente l'ultimo periodo di durata irregolare
+PMT = RATA ## Restituisce il pagamento periodico di una rendita annua
+PPMT = P.RATA ## Restituisce il pagamento sul capitale di un investimento per un dato periodo
+PRICE = PREZZO ## Restituisce il prezzo di un titolo dal valore nominale di € 100 che paga interessi periodici
+PRICEDISC = PREZZO.SCONT ## Restituisce il prezzo di un titolo scontato dal valore nominale di € 100
+PRICEMAT = PREZZO.SCAD ## Restituisce il prezzo di un titolo dal valore nominale di € 100 che paga gli interessi alla scadenza
+PV = VA ## Restituisce il valore attuale di un investimento
+RATE = TASSO ## Restituisce il tasso di interesse per un periodo di un'annualità
+RECEIVED = RICEV.SCAD ## Restituisce l'ammontare ricevuto alla scadenza di un titolo interamente investito
+SLN = AMMORT.COST ## Restituisce l'ammortamento a quote costanti di un bene per un singolo periodo
+SYD = AMMORT.ANNUO ## Restituisce l'ammortamento a somma degli anni di un bene per un periodo specificato
+TBILLEQ = BOT.EQUIV ## Restituisce il rendimento equivalente ad un'obbligazione per un Buono ordinario del Tesoro
+TBILLPRICE = BOT.PREZZO ## Restituisce il prezzo di un Buono del Tesoro dal valore nominale di € 100
+TBILLYIELD = BOT.REND ## Restituisce il rendimento di un Buono del Tesoro
+VDB = AMMORT.VAR ## Restituisce l'ammortamento di un bene per un periodo specificato o parziale utilizzando il metodo a doppie quote proporzionali ai valori residui
+XIRR = TIR.X ## Restituisce il tasso di rendimento interno di un impiego di flussi di cassa
+XNPV = VAN.X ## Restituisce il valore attuale netto di un impiego di flussi di cassa non necessariamente periodici
+YIELD = REND ## Restituisce il rendimento di un titolo che frutta interessi periodici
+YIELDDISC = REND.TITOLI.SCONT ## Restituisce il rendimento annuale di un titolo scontato, ad esempio un Buono del Tesoro
+YIELDMAT = REND.SCAD ## Restituisce il rendimento annuo di un titolo che paga interessi alla scadenza
+
+
+##
+## Information functions Funzioni relative alle informazioni
+##
+CELL = CELLA ## Restituisce le informazioni sulla formattazione, la posizione o i contenuti di una cella
+ERROR.TYPE = ERRORE.TIPO ## Restituisce un numero che corrisponde a un tipo di errore
+INFO = INFO ## Restituisce le informazioni sull'ambiente operativo corrente
+ISBLANK = VAL.VUOTO ## Restituisce VERO se il valore è vuoto
+ISERR = VAL.ERR ## Restituisce VERO se il valore è un valore di errore qualsiasi tranne #N/D
+ISERROR = VAL.ERRORE ## Restituisce VERO se il valore è un valore di errore qualsiasi
+ISEVEN = VAL.PARI ## Restituisce VERO se il numero è pari
+ISLOGICAL = VAL.LOGICO ## Restituisce VERO se il valore è un valore logico
+ISNA = VAL.NON.DISP ## Restituisce VERO se il valore è un valore di errore #N/D
+ISNONTEXT = VAL.NON.TESTO ## Restituisce VERO se il valore non è in formato testo
+ISNUMBER = VAL.NUMERO ## Restituisce VERO se il valore è un numero
+ISODD = VAL.DISPARI ## Restituisce VERO se il numero è dispari
+ISREF = VAL.RIF ## Restituisce VERO se il valore è un riferimento
+ISTEXT = VAL.TESTO ## Restituisce VERO se il valore è in formato testo
+N = NUM ## Restituisce un valore convertito in numero
+NA = NON.DISP ## Restituisce il valore di errore #N/D
+TYPE = TIPO ## Restituisce un numero che indica il tipo di dati relativi a un valore
+
+
+##
+## Logical functions Funzioni logiche
+##
+AND = E ## Restituisce VERO se tutti gli argomenti sono VERO
+FALSE = FALSO ## Restituisce il valore logico FALSO
+IF = SE ## Specifica un test logico da eseguire
+IFERROR = SE.ERRORE ## Restituisce un valore specificato se una formula fornisce un errore come risultato; in caso contrario, restituisce il risultato della formula
+NOT = NON ## Inverte la logica degli argomenti
+OR = O ## Restituisce VERO se un argomento qualsiasi è VERO
+TRUE = VERO ## Restituisce il valore logico VERO
+
+
+##
+## Lookup and reference functions Funzioni di ricerca e di riferimento
+##
+ADDRESS = INDIRIZZO ## Restituisce un riferimento come testo in una singola cella di un foglio di lavoro
+AREAS = AREE ## Restituisce il numero di aree in un riferimento
+CHOOSE = SCEGLI ## Sceglie un valore da un elenco di valori
+COLUMN = RIF.COLONNA ## Restituisce il numero di colonna di un riferimento
+COLUMNS = COLONNE ## Restituisce il numero di colonne in un riferimento
+HLOOKUP = CERCA.ORIZZ ## Effettua una ricerca nella riga superiore di una matrice e restituisce il valore della cella specificata
+HYPERLINK = COLLEG.IPERTESTUALE ## Crea un collegamento che apre un documento memorizzato in un server di rete, una rete Intranet o Internet
+INDEX = INDICE ## Utilizza un indice per scegliere un valore da un riferimento o da una matrice
+INDIRECT = INDIRETTO ## Restituisce un riferimento specificato da un valore testo
+LOOKUP = CERCA ## Ricerca i valori in un vettore o in una matrice
+MATCH = CONFRONTA ## Ricerca i valori in un riferimento o in una matrice
+OFFSET = SCARTO ## Restituisce uno scarto di riferimento da un riferimento dato
+ROW = RIF.RIGA ## Restituisce il numero di riga di un riferimento
+ROWS = RIGHE ## Restituisce il numero delle righe in un riferimento
+RTD = DATITEMPOREALE ## Recupera dati in tempo reale da un programma che supporta l'automazione COM (automazione: Metodo per utilizzare gli oggetti di un'applicazione da un'altra applicazione o da un altro strumento di sviluppo. Precedentemente nota come automazione OLE, l'automazione è uno standard del settore e una caratteristica del modello COM (Component Object Model).)
+TRANSPOSE = MATR.TRASPOSTA ## Restituisce la trasposizione di una matrice
+VLOOKUP = CERCA.VERT ## Effettua una ricerca nella prima colonna di una matrice e si sposta attraverso la riga per restituire il valore di una cella
+
+
+##
+## Math and trigonometry functions Funzioni matematiche e trigonometriche
+##
+ABS = ASS ## Restituisce il valore assoluto di un numero.
+ACOS = ARCCOS ## Restituisce l'arcocoseno di un numero
+ACOSH = ARCCOSH ## Restituisce l'inverso del coseno iperbolico di un numero
+ASIN = ARCSEN ## Restituisce l'arcoseno di un numero
+ASINH = ARCSENH ## Restituisce l'inverso del seno iperbolico di un numero
+ATAN = ARCTAN ## Restituisce l'arcotangente di un numero
+ATAN2 = ARCTAN.2 ## Restituisce l'arcotangente delle coordinate x e y specificate
+ATANH = ARCTANH ## Restituisce l'inverso della tangente iperbolica di un numero
+CEILING = ARROTONDA.ECCESSO ## Arrotonda un numero per eccesso all'intero più vicino o al multiplo più vicino a peso
+COMBIN = COMBINAZIONE ## Restituisce il numero di combinazioni possibili per un numero assegnato di elementi
+COS = COS ## Restituisce il coseno dell'angolo specificato
+COSH = COSH ## Restituisce il coseno iperbolico di un numero
+DEGREES = GRADI ## Converte i radianti in gradi
+EVEN = PARI ## Arrotonda il valore assoluto di un numero per eccesso al più vicino intero pari
+EXP = ESP ## Restituisce il numero e elevato alla potenza di num
+FACT = FATTORIALE ## Restituisce il fattoriale di un numero
+FACTDOUBLE = FATT.DOPPIO ## Restituisce il fattoriale doppio di un numero
+FLOOR = ARROTONDA.DIFETTO ## Arrotonda un numero per difetto al multiplo più vicino a zero
+GCD = MCD ## Restituisce il massimo comune divisore
+INT = INT ## Arrotonda un numero per difetto al numero intero più vicino
+LCM = MCM ## Restituisce il minimo comune multiplo
+LN = LN ## Restituisce il logaritmo naturale di un numero
+LOG = LOG ## Restituisce il logaritmo di un numero in una specificata base
+LOG10 = LOG10 ## Restituisce il logaritmo in base 10 di un numero
+MDETERM = MATR.DETERM ## Restituisce il determinante di una matrice
+MINVERSE = MATR.INVERSA ## Restituisce l'inverso di una matrice
+MMULT = MATR.PRODOTTO ## Restituisce il prodotto di due matrici
+MOD = RESTO ## Restituisce il resto della divisione
+MROUND = ARROTONDA.MULTIPLO ## Restituisce un numero arrotondato al multiplo desiderato
+MULTINOMIAL = MULTINOMIALE ## Restituisce il multinomiale di un insieme di numeri
+ODD = DISPARI ## Arrotonda un numero per eccesso al più vicino intero dispari
+PI = PI.GRECO ## Restituisce il valore di pi greco
+POWER = POTENZA ## Restituisce il risultato di un numero elevato a potenza
+PRODUCT = PRODOTTO ## Moltiplica i suoi argomenti
+QUOTIENT = QUOZIENTE ## Restituisce la parte intera di una divisione
+RADIANS = RADIANTI ## Converte i gradi in radianti
+RAND = CASUALE ## Restituisce un numero casuale compreso tra 0 e 1
+RANDBETWEEN = CASUALE.TRA ## Restituisce un numero casuale compreso tra i numeri specificati
+ROMAN = ROMANO ## Restituisce il numero come numero romano sotto forma di testo
+ROUND = ARROTONDA ## Arrotonda il numero al numero di cifre specificato
+ROUNDDOWN = ARROTONDA.PER.DIF ## Arrotonda il valore assoluto di un numero per difetto
+ROUNDUP = ARROTONDA.PER.ECC ## Arrotonda il valore assoluto di un numero per eccesso
+SERIESSUM = SOMMA.SERIE ## Restituisce la somma di una serie di potenze in base alla formula
+SIGN = SEGNO ## Restituisce il segno di un numero
+SIN = SEN ## Restituisce il seno di un dato angolo
+SINH = SENH ## Restituisce il seno iperbolico di un numero
+SQRT = RADQ ## Restituisce una radice quadrata
+SQRTPI = RADQ.PI.GRECO ## Restituisce la radice quadrata di un numero (numero * pi greco)
+SUBTOTAL = SUBTOTALE ## Restituisce un subtotale in un elenco o in un database
+SUM = SOMMA ## Somma i suoi argomenti
+SUMIF = SOMMA.SE ## Somma le celle specificate da un dato criterio
+SUMIFS = SOMMA.PIÙ.SE ## Somma le celle in un intervallo che soddisfano più criteri
+SUMPRODUCT = MATR.SOMMA.PRODOTTO ## Restituisce la somma dei prodotti dei componenti corrispondenti della matrice
+SUMSQ = SOMMA.Q ## Restituisce la somma dei quadrati degli argomenti
+SUMX2MY2 = SOMMA.DIFF.Q ## Restituisce la somma della differenza dei quadrati dei corrispondenti elementi in due matrici
+SUMX2PY2 = SOMMA.SOMMA.Q ## Restituisce la somma della somma dei quadrati dei corrispondenti elementi in due matrici
+SUMXMY2 = SOMMA.Q.DIFF ## Restituisce la somma dei quadrati delle differenze dei corrispondenti elementi in due matrici
+TAN = TAN ## Restituisce la tangente di un numero
+TANH = TANH ## Restituisce la tangente iperbolica di un numero
+TRUNC = TRONCA ## Tronca la parte decimale di un numero
+
+
+##
+## Statistical functions Funzioni statistiche
+##
+AVEDEV = MEDIA.DEV ## Restituisce la media delle deviazioni assolute delle coordinate rispetto alla loro media
+AVERAGE = MEDIA ## Restituisce la media degli argomenti
+AVERAGEA = MEDIA.VALORI ## Restituisce la media degli argomenti, inclusi i numeri, il testo e i valori logici
+AVERAGEIF = MEDIA.SE ## Restituisce la media aritmetica di tutte le celle in un intervallo che soddisfano un determinato criterio
+AVERAGEIFS = MEDIA.PIÙ.SE ## Restituisce la media aritmetica di tutte le celle che soddisfano più criteri
+BETADIST = DISTRIB.BETA ## Restituisce la funzione di distribuzione cumulativa beta
+BETAINV = INV.BETA ## Restituisce l'inverso della funzione di distribuzione cumulativa per una distribuzione beta specificata
+BINOMDIST = DISTRIB.BINOM ## Restituisce la distribuzione binomiale per il termine individuale
+CHIDIST = DISTRIB.CHI ## Restituisce la probabilità a una coda per la distribuzione del chi quadrato
+CHIINV = INV.CHI ## Restituisce l'inverso della probabilità ad una coda per la distribuzione del chi quadrato
+CHITEST = TEST.CHI ## Restituisce il test per l'indipendenza
+CONFIDENCE = CONFIDENZA ## Restituisce l'intervallo di confidenza per una popolazione
+CORREL = CORRELAZIONE ## Restituisce il coefficiente di correlazione tra due insiemi di dati
+COUNT = CONTA.NUMERI ## Conta la quantità di numeri nell'elenco di argomenti
+COUNTA = CONTA.VALORI ## Conta il numero di valori nell'elenco di argomenti
+COUNTBLANK = CONTA.VUOTE ## Conta il numero di celle vuote all'interno di un intervallo
+COUNTIF = CONTA.SE ## Conta il numero di celle all'interno di un intervallo che soddisfa i criteri specificati
+COUNTIFS = CONTA.PIÙ.SE ## Conta il numero di celle in un intervallo che soddisfano più criteri.
+COVAR = COVARIANZA ## Calcola la covarianza, la media dei prodotti delle deviazioni accoppiate
+CRITBINOM = CRIT.BINOM ## Restituisce il più piccolo valore per il quale la distribuzione cumulativa binomiale risulta maggiore o uguale ad un valore di criterio
+DEVSQ = DEV.Q ## Restituisce la somma dei quadrati delle deviazioni
+EXPONDIST = DISTRIB.EXP ## Restituisce la distribuzione esponenziale
+FDIST = DISTRIB.F ## Restituisce la distribuzione di probabilità F
+FINV = INV.F ## Restituisce l'inverso della distribuzione della probabilità F
+FISHER = FISHER ## Restituisce la trasformazione di Fisher
+FISHERINV = INV.FISHER ## Restituisce l'inverso della trasformazione di Fisher
+FORECAST = PREVISIONE ## Restituisce i valori lungo una tendenza lineare
+FREQUENCY = FREQUENZA ## Restituisce la distribuzione di frequenza come matrice verticale
+FTEST = TEST.F ## Restituisce il risultato di un test F
+GAMMADIST = DISTRIB.GAMMA ## Restituisce la distribuzione gamma
+GAMMAINV = INV.GAMMA ## Restituisce l'inverso della distribuzione cumulativa gamma
+GAMMALN = LN.GAMMA ## Restituisce il logaritmo naturale della funzione gamma, G(x)
+GEOMEAN = MEDIA.GEOMETRICA ## Restituisce la media geometrica
+GROWTH = CRESCITA ## Restituisce i valori lungo una linea di tendenza esponenziale
+HARMEAN = MEDIA.ARMONICA ## Restituisce la media armonica
+HYPGEOMDIST = DISTRIB.IPERGEOM ## Restituisce la distribuzione ipergeometrica
+INTERCEPT = INTERCETTA ## Restituisce l'intercetta della retta di regressione lineare
+KURT = CURTOSI ## Restituisce la curtosi di un insieme di dati
+LARGE = GRANDE ## Restituisce il k-esimo valore più grande in un insieme di dati
+LINEST = REGR.LIN ## Restituisce i parametri di una tendenza lineare
+LOGEST = REGR.LOG ## Restituisce i parametri di una linea di tendenza esponenziale
+LOGINV = INV.LOGNORM ## Restituisce l'inverso di una distribuzione lognormale
+LOGNORMDIST = DISTRIB.LOGNORM ## Restituisce la distribuzione lognormale cumulativa
+MAX = MAX ## Restituisce il valore massimo in un elenco di argomenti
+MAXA = MAX.VALORI ## Restituisce il valore massimo in un elenco di argomenti, inclusi i numeri, il testo e i valori logici
+MEDIAN = MEDIANA ## Restituisce la mediana dei numeri specificati
+MIN = MIN ## Restituisce il valore minimo in un elenco di argomenti
+MINA = MIN.VALORI ## Restituisce il più piccolo valore in un elenco di argomenti, inclusi i numeri, il testo e i valori logici
+MODE = MODA ## Restituisce il valore più comune in un insieme di dati
+NEGBINOMDIST = DISTRIB.BINOM.NEG ## Restituisce la distribuzione binomiale negativa
+NORMDIST = DISTRIB.NORM ## Restituisce la distribuzione cumulativa normale
+NORMINV = INV.NORM ## Restituisce l'inverso della distribuzione cumulativa normale standard
+NORMSDIST = DISTRIB.NORM.ST ## Restituisce la distribuzione cumulativa normale standard
+NORMSINV = INV.NORM.ST ## Restituisce l'inverso della distribuzione cumulativa normale
+PEARSON = PEARSON ## Restituisce il coefficiente del momento di correlazione di Pearson
+PERCENTILE = PERCENTILE ## Restituisce il k-esimo dato percentile di valori in un intervallo
+PERCENTRANK = PERCENT.RANGO ## Restituisce il rango di un valore in un insieme di dati come percentuale
+PERMUT = PERMUTAZIONE ## Restituisce il numero delle permutazioni per un determinato numero di oggetti
+POISSON = POISSON ## Restituisce la distribuzione di Poisson
+PROB = PROBABILITÀ ## Calcola la probabilità che dei valori in un intervallo siano compresi tra due limiti
+QUARTILE = QUARTILE ## Restituisce il quartile di un insieme di dati
+RANK = RANGO ## Restituisce il rango di un numero in un elenco di numeri
+RSQ = RQ ## Restituisce la radice quadrata del coefficiente di momento di correlazione di Pearson
+SKEW = ASIMMETRIA ## Restituisce il grado di asimmetria di una distribuzione
+SLOPE = PENDENZA ## Restituisce la pendenza di una retta di regressione lineare
+SMALL = PICCOLO ## Restituisce il k-esimo valore più piccolo in un insieme di dati
+STANDARDIZE = NORMALIZZA ## Restituisce un valore normalizzato
+STDEV = DEV.ST ## Restituisce una stima della deviazione standard sulla base di un campione
+STDEVA = DEV.ST.VALORI ## Restituisce una stima della deviazione standard sulla base di un campione, inclusi i numeri, il testo e i valori logici
+STDEVP = DEV.ST.POP ## Calcola la deviazione standard sulla base di un'intera popolazione
+STDEVPA = DEV.ST.POP.VALORI ## Calcola la deviazione standard sulla base sull'intera popolazione, inclusi i numeri, il testo e i valori logici
+STEYX = ERR.STD.YX ## Restituisce l'errore standard del valore previsto per y per ogni valore x nella regressione
+TDIST = DISTRIB.T ## Restituisce la distribuzione t di Student
+TINV = INV.T ## Restituisce l'inversa della distribuzione t di Student
+TREND = TENDENZA ## Restituisce i valori lungo una linea di tendenza lineare
+TRIMMEAN = MEDIA.TRONCATA ## Restituisce la media della parte interna di un insieme di dati
+TTEST = TEST.T ## Restituisce la probabilità associata ad un test t di Student
+VAR = VAR ## Stima la varianza sulla base di un campione
+VARA = VAR.VALORI ## Stima la varianza sulla base di un campione, inclusi i numeri, il testo e i valori logici
+VARP = VAR.POP ## Calcola la varianza sulla base dell'intera popolazione
+VARPA = VAR.POP.VALORI ## Calcola la deviazione standard sulla base sull'intera popolazione, inclusi i numeri, il testo e i valori logici
+WEIBULL = WEIBULL ## Restituisce la distribuzione di Weibull
+ZTEST = TEST.Z ## Restituisce il valore di probabilità a una coda per un test z
+
+
+##
+## Text functions Funzioni di testo
+##
+ASC = ASC ## Modifica le lettere inglesi o il katakana a doppio byte all'interno di una stringa di caratteri in caratteri a singolo byte
+BAHTTEXT = BAHTTESTO ## Converte un numero in testo, utilizzando il formato valuta ß (baht)
+CHAR = CODICE.CARATT ## Restituisce il carattere specificato dal numero di codice
+CLEAN = LIBERA ## Elimina dal testo tutti i caratteri che non è possibile stampare
+CODE = CODICE ## Restituisce il codice numerico del primo carattere di una stringa di testo
+CONCATENATE = CONCATENA ## Unisce diversi elementi di testo in un unico elemento di testo
+DOLLAR = VALUTA ## Converte un numero in testo, utilizzando il formato valuta € (euro)
+EXACT = IDENTICO ## Verifica se due valori di testo sono uguali
+FIND = TROVA ## Rileva un valore di testo all'interno di un altro (distinzione tra maiuscole e minuscole)
+FINDB = TROVA.B ## Rileva un valore di testo all'interno di un altro (distinzione tra maiuscole e minuscole)
+FIXED = FISSO ## Formatta un numero come testo con un numero fisso di decimali
+JIS = ORDINAMENTO.JIS ## Modifica le lettere inglesi o i caratteri katakana a byte singolo all'interno di una stringa di caratteri in caratteri a byte doppio.
+LEFT = SINISTRA ## Restituisce il carattere più a sinistra di un valore di testo
+LEFTB = SINISTRA.B ## Restituisce il carattere più a sinistra di un valore di testo
+LEN = LUNGHEZZA ## Restituisce il numero di caratteri di una stringa di testo
+LENB = LUNB ## Restituisce il numero di caratteri di una stringa di testo
+LOWER = MINUSC ## Converte il testo in lettere minuscole
+MID = MEDIA ## Restituisce un numero specifico di caratteri di una stringa di testo a partire dalla posizione specificata
+MIDB = MEDIA.B ## Restituisce un numero specifico di caratteri di una stringa di testo a partire dalla posizione specificata
+PHONETIC = FURIGANA ## Estrae i caratteri fonetici (furigana) da una stringa di testo.
+PROPER = MAIUSC.INIZ ## Converte in maiuscolo la prima lettera di ogni parola di un valore di testo
+REPLACE = RIMPIAZZA ## Sostituisce i caratteri all'interno di un testo
+REPLACEB = SOSTITUISCI.B ## Sostituisce i caratteri all'interno di un testo
+REPT = RIPETI ## Ripete un testo per un dato numero di volte
+RIGHT = DESTRA ## Restituisce il carattere più a destra di un valore di testo
+RIGHTB = DESTRA.B ## Restituisce il carattere più a destra di un valore di testo
+SEARCH = RICERCA ## Rileva un valore di testo all'interno di un altro (non è sensibile alle maiuscole e minuscole)
+SEARCHB = CERCA.B ## Rileva un valore di testo all'interno di un altro (non è sensibile alle maiuscole e minuscole)
+SUBSTITUTE = SOSTITUISCI ## Sostituisce il nuovo testo al testo contenuto in una stringa
+T = T ## Converte gli argomenti in testo
+TEXT = TESTO ## Formatta un numero e lo converte in testo
+TRIM = ANNULLA.SPAZI ## Elimina gli spazi dal testo
+UPPER = MAIUSC ## Converte il testo in lettere maiuscole
+VALUE = VALORE ## Converte un argomento di testo in numero
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config
new file mode 100755
index 0000000..8376022
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config
@@ -0,0 +1,24 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = €
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #LEEG!
+DIV0 = #DEEL/0!
+VALUE = #WAARDE!
+REF = #VERW!
+NAME = #NAAM?
+NUM = #GETAL!
+NA = #N/B
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions
new file mode 100755
index 0000000..2518f42
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions
@@ -0,0 +1,416 @@
+##
+## PhpSpreadsheet
+##
+## Data in this file derived from https://www.excel-function-translation.com/
+##
+##
+
+
+##
+## Add-in and Automation functions Automatiseringsfuncties en functies in invoegtoepassingen
+##
+GETPIVOTDATA = DRAAITABEL.OPHALEN ## Geeft gegevens uit een draaitabelrapport als resultaat
+
+
+##
+## Cube functions Kubusfuncties
+##
+CUBEKPIMEMBER = KUBUSKPILID ## Retourneert de naam, eigenschap en waarde van een KPI (prestatie-indicator) en geeft de naam en de eigenschap in de cel weer. Een KPI is een meetbare waarde, zoals de maandelijkse brutowinst of de omzet per kwartaal per werknemer, die wordt gebruikt om de prestaties van een organisatie te bewaken
+CUBEMEMBER = KUBUSLID ## Retourneert een lid of tupel in een kubushiërarchie. Wordt gebruikt om te controleren of het lid of de tupel in de kubus aanwezig is
+CUBEMEMBERPROPERTY = KUBUSLIDEIGENSCHAP ## Retourneert de waarde van een lideigenschap in de kubus. Wordt gebruikt om te controleren of de lidnaam in de kubus bestaat en retourneert de opgegeven eigenschap voor dit lid
+CUBERANKEDMEMBER = KUBUSGERANGCHIKTLID ## Retourneert het zoveelste, gerangschikte lid in een set. Wordt gebruikt om een of meer elementen in een set te retourneren, zoals de tien beste verkopers of de tien beste studenten
+CUBESET = KUBUSSET ## Definieert een berekende set leden of tupels door een ingestelde expressie naar de kubus op de server te sturen, alwaar de set wordt gemaakt en vervolgens wordt geretourneerd naar Microsoft Office Excel
+CUBESETCOUNT = KUBUSSETAANTAL ## Retourneert het aantal onderdelen in een set
+CUBEVALUE = KUBUSWAARDE ## Retourneert een samengestelde waarde van een kubus
+
+
+##
+## Database functions Databasefuncties
+##
+DAVERAGE = DBGEMIDDELDE ## Berekent de gemiddelde waarde in geselecteerde databasegegevens
+DCOUNT = DBAANTAL ## Telt de cellen met getallen in een database
+DCOUNTA = DBAANTALC ## Telt de niet-lege cellen in een database
+DGET = DBLEZEN ## Retourneert één record dat voldoet aan de opgegeven criteria uit een database
+DMAX = DBMAX ## Retourneert de maximumwaarde in de geselecteerde databasegegevens
+DMIN = DBMIN ## Retourneert de minimumwaarde in de geselecteerde databasegegevens
+DPRODUCT = DBPRODUCT ## Vermenigvuldigt de waarden in een bepaald veld van de records die voldoen aan de criteria in een database
+DSTDEV = DBSTDEV ## Maakt een schatting van de standaarddeviatie op basis van een steekproef uit geselecteerde databasegegevens
+DSTDEVP = DBSTDEVP ## Berekent de standaarddeviatie op basis van de volledige populatie van geselecteerde databasegegevens
+DSUM = DBSOM ## Telt de getallen uit een kolom records in de database op die voldoen aan de criteria
+DVAR = DBVAR ## Maakt een schatting van de variantie op basis van een steekproef uit geselecteerde databasegegevens
+DVARP = DBVARP ## Berekent de variantie op basis van de volledige populatie van geselecteerde databasegegevens
+
+
+##
+## Date and time functions Datum- en tijdfuncties
+##
+DATE = DATUM ## Geeft als resultaat het seriële getal van een opgegeven datum
+DATEVALUE = DATUMWAARDE ## Converteert een datum in de vorm van tekst naar een serieel getal
+DAY = DAG ## Converteert een serieel getal naar een dag van de maand
+DAYS360 = DAGEN360 ## Berekent het aantal dagen tussen twee datums op basis van een jaar met 360 dagen
+EDATE = ZELFDE.DAG ## Geeft als resultaat het seriële getal van een datum die het opgegeven aantal maanden voor of na de begindatum ligt
+EOMONTH = LAATSTE.DAG ## Geeft als resultaat het seriële getal van de laatste dag van de maand voor of na het opgegeven aantal maanden
+HOUR = UUR ## Converteert een serieel getal naar uren
+MINUTE = MINUUT ## Converteert een serieel naar getal minuten
+MONTH = MAAND ## Converteert een serieel getal naar een maand
+NETWORKDAYS = NETTO.WERKDAGEN ## Geeft als resultaat het aantal hele werkdagen tussen twee datums
+NOW = NU ## Geeft als resultaat het seriële getal van de huidige datum en tijd
+SECOND = SECONDE ## Converteert een serieel getal naar seconden
+TIME = TIJD ## Geeft als resultaat het seriële getal van een bepaald tijdstip
+TIMEVALUE = TIJDWAARDE ## Converteert de tijd in de vorm van tekst naar een serieel getal
+TODAY = VANDAAG ## Geeft als resultaat het seriële getal van de huidige datum
+WEEKDAY = WEEKDAG ## Converteert een serieel getal naar een weekdag
+WEEKNUM = WEEKNUMMER ## Converteert een serieel getal naar een weeknummer
+WORKDAY = WERKDAG ## Geeft als resultaat het seriële getal van de datum voor of na een bepaald aantal werkdagen
+YEAR = JAAR ## Converteert een serieel getal naar een jaar
+YEARFRAC = JAAR.DEEL ## Geeft als resultaat het gedeelte van het jaar, uitgedrukt in het aantal hele dagen tussen begindatum en einddatum
+
+
+##
+## Engineering functions Technische functies
+##
+BESSELI = BESSEL.Y ## Geeft als resultaat de gewijzigde Bessel-functie In(x)
+BESSELJ = BESSEL.J ## Geeft als resultaat de Bessel-functie Jn(x)
+BESSELK = BESSEL.K ## Geeft als resultaat de gewijzigde Bessel-functie Kn(x)
+BESSELY = BESSEL.Y ## Geeft als resultaat de gewijzigde Bessel-functie Yn(x)
+BIN2DEC = BIN.N.DEC ## Converteert een binair getal naar een decimaal getal
+BIN2HEX = BIN.N.HEX ## Converteert een binair getal naar een hexadecimaal getal
+BIN2OCT = BIN.N.OCT ## Converteert een binair getal naar een octaal getal
+COMPLEX = COMPLEX ## Converteert reële en imaginaire coëfficiënten naar een complex getal
+CONVERT = CONVERTEREN ## Converteert een getal in de ene maateenheid naar een getal in een andere maateenheid
+DEC2BIN = DEC.N.BIN ## Converteert een decimaal getal naar een binair getal
+DEC2HEX = DEC.N.HEX ## Converteert een decimaal getal naar een hexadecimaal getal
+DEC2OCT = DEC.N.OCT ## Converteert een decimaal getal naar een octaal getal
+DELTA = DELTA ## Test of twee waarden gelijk zijn
+ERF = FOUTFUNCTIE ## Geeft als resultaat de foutfunctie
+ERFC = FOUT.COMPLEMENT ## Geeft als resultaat de complementaire foutfunctie
+GESTEP = GROTER.DAN ## Test of een getal groter is dan de drempelwaarde
+HEX2BIN = HEX.N.BIN ## Converteert een hexadecimaal getal naar een binair getal
+HEX2DEC = HEX.N.DEC ## Converteert een hexadecimaal getal naar een decimaal getal
+HEX2OCT = HEX.N.OCT ## Converteert een hexadecimaal getal naar een octaal getal
+IMABS = C.ABS ## Geeft als resultaat de absolute waarde (modulus) van een complex getal
+IMAGINARY = C.IM.DEEL ## Geeft als resultaat de imaginaire coëfficiënt van een complex getal
+IMARGUMENT = C.ARGUMENT ## Geeft als resultaat het argument thèta, een hoek uitgedrukt in radialen
+IMCONJUGATE = C.TOEGEVOEGD ## Geeft als resultaat het complexe toegevoegde getal van een complex getal
+IMCOS = C.COS ## Geeft als resultaat de cosinus van een complex getal
+IMDIV = C.QUOTIENT ## Geeft als resultaat het quotiënt van twee complexe getallen
+IMEXP = C.EXP ## Geeft als resultaat de exponent van een complex getal
+IMLN = C.LN ## Geeft als resultaat de natuurlijke logaritme van een complex getal
+IMLOG10 = C.LOG10 ## Geeft als resultaat de logaritme met grondtal 10 van een complex getal
+IMLOG2 = C.LOG2 ## Geeft als resultaat de logaritme met grondtal 2 van een complex getal
+IMPOWER = C.MACHT ## Geeft als resultaat een complex getal dat is verheven tot de macht van een geheel getal
+IMPRODUCT = C.PRODUCT ## Geeft als resultaat het product van complexe getallen
+IMREAL = C.REEEL.DEEL ## Geeft als resultaat de reële coëfficiënt van een complex getal
+IMSIN = C.SIN ## Geeft als resultaat de sinus van een complex getal
+IMSQRT = C.WORTEL ## Geeft als resultaat de vierkantswortel van een complex getal
+IMSUB = C.VERSCHIL ## Geeft als resultaat het verschil tussen twee complexe getallen
+IMSUM = C.SOM ## Geeft als resultaat de som van complexe getallen
+OCT2BIN = OCT.N.BIN ## Converteert een octaal getal naar een binair getal
+OCT2DEC = OCT.N.DEC ## Converteert een octaal getal naar een decimaal getal
+OCT2HEX = OCT.N.HEX ## Converteert een octaal getal naar een hexadecimaal getal
+
+
+##
+## Financial functions Financiële functies
+##
+ACCRINT = SAMENG.RENTE ## Berekent de opgelopen rente voor een waardepapier waarvan de rente periodiek wordt uitgekeerd
+ACCRINTM = SAMENG.RENTE.V ## Berekent de opgelopen rente voor een waardepapier waarvan de rente op de vervaldatum wordt uitgekeerd
+AMORDEGRC = AMORDEGRC ## Geeft als resultaat de afschrijving voor elke boekingsperiode door een afschrijvingscoëfficiënt toe te passen
+AMORLINC = AMORLINC ## Berekent de afschrijving voor elke boekingsperiode
+COUPDAYBS = COUP.DAGEN.BB ## Berekent het aantal dagen vanaf het begin van de coupontermijn tot de stortingsdatum
+COUPDAYS = COUP.DAGEN ## Geeft als resultaat het aantal dagen in de coupontermijn waarin de stortingsdatum valt
+COUPDAYSNC = COUP.DAGEN.VV ## Geeft als resultaat het aantal dagen vanaf de stortingsdatum tot de volgende couponvervaldatum
+COUPNCD = COUP.DATUM.NB ## Geeft als resultaat de volgende coupondatum na de stortingsdatum
+COUPNUM = COUP.AANTAL ## Geeft als resultaat het aantal coupons dat nog moet worden uitbetaald tussen de stortingsdatum en de vervaldatum
+COUPPCD = COUP.DATUM.VB ## Geeft als resultaat de vorige couponvervaldatum vóór de stortingsdatum
+CUMIPMT = CUM.RENTE ## Geeft als resultaat de cumulatieve rente die tussen twee termijnen is uitgekeerd
+CUMPRINC = CUM.HOOFDSOM ## Geeft als resultaat de cumulatieve hoofdsom van een lening die tussen twee termijnen is terugbetaald
+DB = DB ## Geeft als resultaat de afschrijving van activa voor een bepaalde periode met behulp van de 'fixed declining balance'-methode
+DDB = DDB ## Geeft als resultaat de afschrijving van activa over een bepaalde termijn met behulp van de 'double declining balance'-methode of een andere methode die u opgeeft
+DISC = DISCONTO ## Geeft als resultaat het discontopercentage voor een waardepapier
+DOLLARDE = EURO.DE ## Converteert een prijs in euro's, uitgedrukt in een breuk, naar een prijs in euro's, uitgedrukt in een decimaal getal
+DOLLARFR = EURO.BR ## Converteert een prijs in euro's, uitgedrukt in een decimaal getal, naar een prijs in euro's, uitgedrukt in een breuk
+DURATION = DUUR ## Geeft als resultaat de gewogen gemiddelde looptijd voor een waardepapier met periodieke rentebetalingen
+EFFECT = EFFECT.RENTE ## Geeft als resultaat het effectieve jaarlijkse rentepercentage
+FV = TW ## Geeft als resultaat de toekomstige waarde van een investering
+FVSCHEDULE = TOEK.WAARDE2 ## Geeft als resultaat de toekomstige waarde van een bepaalde hoofdsom na het toepassen van een reeks samengestelde rentepercentages
+INTRATE = RENTEPERCENTAGE ## Geeft als resultaat het rentepercentage voor een volgestort waardepapier
+IPMT = IBET ## Geeft als resultaat de te betalen rente voor een investering over een bepaalde termijn
+IRR = IR ## Geeft als resultaat de interne rentabiliteit voor een reeks cashflows
+ISPMT = ISBET ## Geeft als resultaat de rente die is betaald tijdens een bepaalde termijn van een investering
+MDURATION = AANG.DUUR ## Geeft als resultaat de aangepaste Macauley-looptijd voor een waardepapier, aangenomen dat de nominale waarde € 100 bedraagt
+MIRR = GIR ## Geeft als resultaat de interne rentabiliteit voor een serie cashflows, waarbij voor betalingen een ander rentepercentage geldt dan voor inkomsten
+NOMINAL = NOMINALE.RENTE ## Geeft als resultaat het nominale jaarlijkse rentepercentage
+NPER = NPER ## Geeft als resultaat het aantal termijnen van een investering
+NPV = NHW ## Geeft als resultaat de netto huidige waarde van een investering op basis van een reeks periodieke cashflows en een discontopercentage
+ODDFPRICE = AFW.ET.PRIJS ## Geeft als resultaat de prijs per € 100 nominale waarde voor een waardepapier met een afwijkende eerste termijn
+ODDFYIELD = AFW.ET.REND ## Geeft als resultaat het rendement voor een waardepapier met een afwijkende eerste termijn
+ODDLPRICE = AFW.LT.PRIJS ## Geeft als resultaat de prijs per € 100 nominale waarde voor een waardepapier met een afwijkende laatste termijn
+ODDLYIELD = AFW.LT.REND ## Geeft als resultaat het rendement voor een waardepapier met een afwijkende laatste termijn
+PMT = BET ## Geeft als resultaat de periodieke betaling voor een annuïteit
+PPMT = PBET ## Geeft als resultaat de afbetaling op de hoofdsom voor een bepaalde termijn
+PRICE = PRIJS.NOM ## Geeft als resultaat de prijs per € 100 nominale waarde voor een waardepapier waarvan de rente periodiek wordt uitgekeerd
+PRICEDISC = PRIJS.DISCONTO ## Geeft als resultaat de prijs per € 100 nominale waarde voor een verdisconteerd waardepapier
+PRICEMAT = PRIJS.VERVALDAG ## Geeft als resultaat de prijs per € 100 nominale waarde voor een waardepapier waarvan de rente wordt uitgekeerd op de vervaldatum
+PV = HW ## Geeft als resultaat de huidige waarde van een investering
+RATE = RENTE ## Geeft als resultaat het periodieke rentepercentage voor een annuïteit
+RECEIVED = OPBRENGST ## Geeft als resultaat het bedrag dat op de vervaldatum wordt uitgekeerd voor een volgestort waardepapier
+SLN = LIN.AFSCHR ## Geeft als resultaat de lineaire afschrijving van activa over één termijn
+SYD = SYD ## Geeft als resultaat de afschrijving van activa over een bepaalde termijn met behulp van de 'Sum-Of-Years-Digits'-methode
+TBILLEQ = SCHATK.OBL ## Geeft als resultaat het rendement op schatkistpapier, dat op dezelfde manier wordt berekend als het rendement op obligaties
+TBILLPRICE = SCHATK.PRIJS ## Bepaalt de prijs per € 100 nominale waarde voor schatkistpapier
+TBILLYIELD = SCHATK.REND ## Berekent het rendement voor schatkistpapier
+VDB = VDB ## Geeft als resultaat de afschrijving van activa over een gehele of gedeeltelijke termijn met behulp van de 'declining balance'-methode
+XIRR = IR.SCHEMA ## Berekent de interne rentabiliteit voor een betalingsschema van cashflows
+XNPV = NHW2 ## Berekent de huidige nettowaarde voor een betalingsschema van cashflows
+YIELD = RENDEMENT ## Geeft als resultaat het rendement voor een waardepapier waarvan de rente periodiek wordt uitgekeerd
+YIELDDISC = REND.DISCONTO ## Geeft als resultaat het jaarlijkse rendement voor een verdisconteerd waardepapier, bijvoorbeeld schatkistpapier
+YIELDMAT = REND.VERVAL ## Geeft als resultaat het jaarlijkse rendement voor een waardepapier waarvan de rente wordt uitgekeerd op de vervaldatum
+
+
+##
+## Information functions Informatiefuncties
+##
+CELL = CEL ## Geeft als resultaat informatie over de opmaak, locatie of inhoud van een cel
+ERROR.TYPE = TYPE.FOUT ## Geeft als resultaat een getal dat overeenkomt met een van de foutwaarden van Microsoft Excel
+INFO = INFO ## Geeft als resultaat informatie over de huidige besturingsomgeving
+ISBLANK = ISLEEG ## Geeft als resultaat WAAR als de waarde leeg is
+ISERR = ISFOUT2 ## Geeft als resultaat WAAR als de waarde een foutwaarde is, met uitzondering van #N/B
+ISERROR = ISFOUT ## Geeft als resultaat WAAR als de waarde een foutwaarde is
+ISEVEN = IS.EVEN ## Geeft als resultaat WAAR als het getal even is
+ISLOGICAL = ISLOGISCH ## Geeft als resultaat WAAR als de waarde een logische waarde is
+ISNA = ISNB ## Geeft als resultaat WAAR als de waarde de foutwaarde #N/B is
+ISNONTEXT = ISGEENTEKST ## Geeft als resultaat WAAR als de waarde geen tekst is
+ISNUMBER = ISGETAL ## Geeft als resultaat WAAR als de waarde een getal is
+ISODD = IS.ONEVEN ## Geeft als resultaat WAAR als het getal oneven is
+ISREF = ISVERWIJZING ## Geeft als resultaat WAAR als de waarde een verwijzing is
+ISTEXT = ISTEKST ## Geeft als resultaat WAAR als de waarde tekst is
+N = N ## Geeft als resultaat een waarde die is geconverteerd naar een getal
+NA = NB ## Geeft als resultaat de foutwaarde #N/B
+TYPE = TYPE ## Geeft als resultaat een getal dat het gegevenstype van een waarde aangeeft
+
+
+##
+## Logical functions Logische functies
+##
+AND = EN ## Geeft als resultaat WAAR als alle argumenten WAAR zijn
+FALSE = ONWAAR ## Geeft als resultaat de logische waarde ONWAAR
+IF = ALS ## Geeft een logische test aan
+IFERROR = ALS.FOUT ## Retourneert een waarde die u opgeeft als een formule een fout oplevert, anders wordt het resultaat van de formule geretourneerd
+NOT = NIET ## Keert de logische waarde van het argument om
+OR = OF ## Geeft als resultaat WAAR als minimaal een van de argumenten WAAR is
+TRUE = WAAR ## Geeft als resultaat de logische waarde WAAR
+
+
+##
+## Lookup and reference functions Zoek- en verwijzingsfuncties
+##
+ADDRESS = ADRES ## Geeft als resultaat een verwijzing, in de vorm van tekst, naar één bepaalde cel in een werkblad
+AREAS = BEREIKEN ## Geeft als resultaat het aantal bereiken in een verwijzing
+CHOOSE = KIEZEN ## Kiest een waarde uit een lijst met waarden
+COLUMN = KOLOM ## Geeft als resultaat het kolomnummer van een verwijzing
+COLUMNS = KOLOMMEN ## Geeft als resultaat het aantal kolommen in een verwijzing
+HLOOKUP = HORIZ.ZOEKEN ## Zoekt in de bovenste rij van een matrix naar een bepaalde waarde en geeft als resultaat de gevonden waarde in de opgegeven cel
+HYPERLINK = HYPERLINK ## Maakt een snelkoppeling of een sprong waarmee een document wordt geopend dat is opgeslagen op een netwerkserver, een intranet of op internet
+INDEX = INDEX ## Kiest met een index een waarde uit een verwijzing of een matrix
+INDIRECT = INDIRECT ## Geeft als resultaat een verwijzing die wordt aangegeven met een tekstwaarde
+LOOKUP = ZOEKEN ## Zoekt naar bepaalde waarden in een vector of een matrix
+MATCH = VERGELIJKEN ## Zoekt naar bepaalde waarden in een verwijzing of een matrix
+OFFSET = VERSCHUIVING ## Geeft als resultaat een nieuwe verwijzing die is verschoven ten opzichte van een bepaalde verwijzing
+ROW = RIJ ## Geeft als resultaat het rijnummer van een verwijzing
+ROWS = RIJEN ## Geeft als resultaat het aantal rijen in een verwijzing
+RTD = RTG ## Haalt realtimegegevens op uit een programma dat COM-automatisering (automatisering: een methode waarmee de ene toepassing objecten van een andere toepassing of ontwikkelprogramma kan besturen. Automatisering werd vroeger OLE-automatisering genoemd. Automatisering is een industrienorm die deel uitmaakt van het Component Object Model (COM).) ondersteunt
+TRANSPOSE = TRANSPONEREN ## Geeft als resultaat de getransponeerde van een matrix
+VLOOKUP = VERT.ZOEKEN ## Zoekt in de meest linkse kolom van een matrix naar een bepaalde waarde en geeft als resultaat de waarde in de opgegeven cel
+
+
+##
+## Math and trigonometry functions Wiskundige en trigonometrische functies
+##
+ABS = ABS ## Geeft als resultaat de absolute waarde van een getal
+ACOS = BOOGCOS ## Geeft als resultaat de boogcosinus van een getal
+ACOSH = BOOGCOSH ## Geeft als resultaat de inverse cosinus hyperbolicus van een getal
+ASIN = BOOGSIN ## Geeft als resultaat de boogsinus van een getal
+ASINH = BOOGSINH ## Geeft als resultaat de inverse sinus hyperbolicus van een getal
+ATAN = BOOGTAN ## Geeft als resultaat de boogtangens van een getal
+ATAN2 = BOOGTAN2 ## Geeft als resultaat de boogtangens van de x- en y-coördinaten
+ATANH = BOOGTANH ## Geeft als resultaat de inverse tangens hyperbolicus van een getal
+CEILING = AFRONDEN.BOVEN ## Rondt de absolute waarde van een getal naar boven af op het dichtstbijzijnde gehele getal of het dichtstbijzijnde significante veelvoud
+COMBIN = COMBINATIES ## Geeft als resultaat het aantal combinaties voor een bepaald aantal objecten
+COS = COS ## Geeft als resultaat de cosinus van een getal
+COSH = COSH ## Geeft als resultaat de cosinus hyperbolicus van een getal
+DEGREES = GRADEN ## Converteert radialen naar graden
+EVEN = EVEN ## Rondt het getal af op het dichtstbijzijnde gehele even getal
+EXP = EXP ## Verheft e tot de macht van een bepaald getal
+FACT = FACULTEIT ## Geeft als resultaat de faculteit van een getal
+FACTDOUBLE = DUBBELE.FACULTEIT ## Geeft als resultaat de dubbele faculteit van een getal
+FLOOR = AFRONDEN.BENEDEN ## Rondt de absolute waarde van een getal naar beneden af
+GCD = GGD ## Geeft als resultaat de grootste gemene deler
+INT = INTEGER ## Rondt een getal naar beneden af op het dichtstbijzijnde gehele getal
+LCM = KGV ## Geeft als resultaat het kleinste gemene veelvoud
+LN = LN ## Geeft als resultaat de natuurlijke logaritme van een getal
+LOG = LOG ## Geeft als resultaat de logaritme met het opgegeven grondtal van een getal
+LOG10 = LOG10 ## Geeft als resultaat de logaritme met grondtal 10 van een getal
+MDETERM = DETERMINANTMAT ## Geeft als resultaat de determinant van een matrix
+MINVERSE = INVERSEMAT ## Geeft als resultaat de inverse van een matrix
+MMULT = PRODUCTMAT ## Geeft als resultaat het product van twee matrices
+MOD = REST ## Geeft als resultaat het restgetal van een deling
+MROUND = AFRONDEN.N.VEELVOUD ## Geeft als resultaat een getal afgerond op het gewenste veelvoud
+MULTINOMIAL = MULTINOMIAAL ## Geeft als resultaat de multinomiaalcoëfficiënt van een reeks getallen
+ODD = ONEVEN ## Rondt de absolute waarde van het getal naar boven af op het dichtstbijzijnde gehele oneven getal
+PI = PI ## Geeft als resultaat de waarde van pi
+POWER = MACHT ## Verheft een getal tot een macht
+PRODUCT = PRODUCT ## Vermenigvuldigt de argumenten met elkaar
+QUOTIENT = QUOTIENT ## Geeft als resultaat de uitkomst van een deling als geheel getal
+RADIANS = RADIALEN ## Converteert graden naar radialen
+RAND = ASELECT ## Geeft als resultaat een willekeurig getal tussen 0 en 1
+RANDBETWEEN = ASELECTTUSSEN ## Geeft een willekeurig getal tussen de getallen die u hebt opgegeven
+ROMAN = ROMEINS ## Converteert een Arabisch getal naar een Romeins getal en geeft het resultaat weer in de vorm van tekst
+ROUND = AFRONDEN ## Rondt een getal af op het opgegeven aantal decimalen
+ROUNDDOWN = AFRONDEN.NAAR.BENEDEN ## Rondt de absolute waarde van een getal naar beneden af
+ROUNDUP = AFRONDEN.NAAR.BOVEN ## Rondt de absolute waarde van een getal naar boven af
+SERIESSUM = SOM.MACHTREEKS ## Geeft als resultaat de som van een machtreeks die is gebaseerd op de formule
+SIGN = POS.NEG ## Geeft als resultaat het teken van een getal
+SIN = SIN ## Geeft als resultaat de sinus van de opgegeven hoek
+SINH = SINH ## Geeft als resultaat de sinus hyperbolicus van een getal
+SQRT = WORTEL ## Geeft als resultaat de positieve vierkantswortel van een getal
+SQRTPI = WORTEL.PI ## Geeft als resultaat de vierkantswortel van (getal * pi)
+SUBTOTAL = SUBTOTAAL ## Geeft als resultaat een subtotaal voor een bereik
+SUM = SOM ## Telt de argumenten op
+SUMIF = SOM.ALS ## Telt de getallen bij elkaar op die voldoen aan een bepaald criterium
+SUMIFS = SOMMEN.ALS ## Telt de cellen in een bereik op die aan meerdere criteria voldoen
+SUMPRODUCT = SOMPRODUCT ## Geeft als resultaat de som van de producten van de corresponderende matrixelementen
+SUMSQ = KWADRATENSOM ## Geeft als resultaat de som van de kwadraten van de argumenten
+SUMX2MY2 = SOM.X2MINY2 ## Geeft als resultaat de som van het verschil tussen de kwadraten van corresponderende waarden in twee matrices
+SUMX2PY2 = SOM.X2PLUSY2 ## Geeft als resultaat de som van de kwadratensom van corresponderende waarden in twee matrices
+SUMXMY2 = SOM.XMINY.2 ## Geeft als resultaat de som van de kwadraten van de verschillen tussen de corresponderende waarden in twee matrices
+TAN = TAN ## Geeft als resultaat de tangens van een getal
+TANH = TANH ## Geeft als resultaat de tangens hyperbolicus van een getal
+TRUNC = GEHEEL ## Kapt een getal af tot een geheel getal
+
+
+##
+## Statistical functions Statistische functies
+##
+AVEDEV = GEM.DEVIATIE ## Geeft als resultaat het gemiddelde van de absolute deviaties van gegevenspunten ten opzichte van hun gemiddelde waarde
+AVERAGE = GEMIDDELDE ## Geeft als resultaat het gemiddelde van de argumenten
+AVERAGEA = GEMIDDELDEA ## Geeft als resultaat het gemiddelde van de argumenten, inclusief getallen, tekst en logische waarden
+AVERAGEIF = GEMIDDELDE.ALS ## Geeft het gemiddelde (rekenkundig gemiddelde) als resultaat van alle cellen in een bereik die voldoen aan de opgegeven criteria
+AVERAGEIFS = GEMIDDELDEN.ALS ## Geeft het gemiddelde (rekenkundig gemiddelde) als resultaat van alle cellen die aan meerdere criteria voldoen
+BETADIST = BETA.VERD ## Geeft als resultaat de cumulatieve bèta-verdelingsfunctie
+BETAINV = BETA.INV ## Geeft als resultaat de inverse van de cumulatieve verdelingsfunctie voor een gegeven bèta-verdeling
+BINOMDIST = BINOMIALE.VERD ## Geeft als resultaat de binomiale verdeling
+CHIDIST = CHI.KWADRAAT ## Geeft als resultaat de eenzijdige kans van de chi-kwadraatverdeling
+CHIINV = CHI.KWADRAAT.INV ## Geeft als resultaat de inverse van een eenzijdige kans van de chi-kwadraatverdeling
+CHITEST = CHI.TOETS ## Geeft als resultaat de onafhankelijkheidstoets
+CONFIDENCE = BETROUWBAARHEID ## Geeft als resultaat het betrouwbaarheidsinterval van een gemiddelde waarde voor de elementen van een populatie
+CORREL = CORRELATIE ## Geeft als resultaat de correlatiecoëfficiënt van twee gegevensverzamelingen
+COUNT = AANTAL ## Telt het aantal getallen in de argumentenlijst
+COUNTA = AANTALARG ## Telt het aantal waarden in de argumentenlijst
+COUNTBLANK = AANTAL.LEGE.CELLEN ## Telt het aantal lege cellen in een bereik
+COUNTIF = AANTAL.ALS ## Telt in een bereik het aantal cellen die voldoen aan een bepaald criterium
+COUNTIFS = AANTALLEN.ALS ## Telt in een bereik het aantal cellen die voldoen aan meerdere criteria
+COVAR = COVARIANTIE ## Geeft als resultaat de covariantie, het gemiddelde van de producten van de gepaarde deviaties
+CRITBINOM = CRIT.BINOM ## Geeft als resultaat de kleinste waarde waarvoor de binomiale verdeling kleiner is dan of gelijk is aan het criterium
+DEVSQ = DEV.KWAD ## Geeft als resultaat de som van de deviaties in het kwadraat
+EXPONDIST = EXPON.VERD ## Geeft als resultaat de exponentiële verdeling
+FDIST = F.VERDELING ## Geeft als resultaat de F-verdeling
+FINV = F.INVERSE ## Geeft als resultaat de inverse van de F-verdeling
+FISHER = FISHER ## Geeft als resultaat de Fisher-transformatie
+FISHERINV = FISHER.INV ## Geeft als resultaat de inverse van de Fisher-transformatie
+FORECAST = VOORSPELLEN ## Geeft als resultaat een waarde op basis van een lineaire trend
+FREQUENCY = FREQUENTIE ## Geeft als resultaat een frequentieverdeling in de vorm van een verticale matrix
+FTEST = F.TOETS ## Geeft als resultaat een F-toets
+GAMMADIST = GAMMA.VERD ## Geeft als resultaat de gamma-verdeling
+GAMMAINV = GAMMA.INV ## Geeft als resultaat de inverse van de cumulatieve gamma-verdeling
+GAMMALN = GAMMA.LN ## Geeft als resultaat de natuurlijke logaritme van de gamma-functie, G(x)
+GEOMEAN = MEETK.GEM ## Geeft als resultaat het meetkundige gemiddelde
+GROWTH = GROEI ## Geeft als resultaat de waarden voor een exponentiële trend
+HARMEAN = HARM.GEM ## Geeft als resultaat het harmonische gemiddelde
+HYPGEOMDIST = HYPERGEO.VERD ## Geeft als resultaat de hypergeometrische verdeling
+INTERCEPT = SNIJPUNT ## Geeft als resultaat het snijpunt van de lineaire regressielijn met de y-as
+KURT = KURTOSIS ## Geeft als resultaat de kurtosis van een gegevensverzameling
+LARGE = GROOTSTE ## Geeft als resultaat de op k-1 na grootste waarde in een gegevensverzameling
+LINEST = LIJNSCH ## Geeft als resultaat de parameters van een lineaire trend
+LOGEST = LOGSCH ## Geeft als resultaat de parameters van een exponentiële trend
+LOGINV = LOG.NORM.INV ## Geeft als resultaat de inverse van de logaritmische normale verdeling
+LOGNORMDIST = LOG.NORM.VERD ## Geeft als resultaat de cumulatieve logaritmische normale verdeling
+MAX = MAX ## Geeft als resultaat de maximumwaarde in een lijst met argumenten
+MAXA = MAXA ## Geeft als resultaat de maximumwaarde in een lijst met argumenten, inclusief getallen, tekst en logische waarden
+MEDIAN = MEDIAAN ## Geeft als resultaat de mediaan van de opgegeven getallen
+MIN = MIN ## Geeft als resultaat de minimumwaarde in een lijst met argumenten
+MINA = MINA ## Geeft als resultaat de minimumwaarde in een lijst met argumenten, inclusief getallen, tekst en logische waarden
+MODE = MODUS ## Geeft als resultaat de meest voorkomende waarde in een gegevensverzameling
+NEGBINOMDIST = NEG.BINOM.VERD ## Geeft als resultaat de negatieve binomiaalverdeling
+NORMDIST = NORM.VERD ## Geeft als resultaat de cumulatieve normale verdeling
+NORMINV = NORM.INV ## Geeft als resultaat de inverse van de cumulatieve standaardnormale verdeling
+NORMSDIST = STAND.NORM.VERD ## Geeft als resultaat de cumulatieve standaardnormale verdeling
+NORMSINV = STAND.NORM.INV ## Geeft als resultaat de inverse van de cumulatieve normale verdeling
+PEARSON = PEARSON ## Geeft als resultaat de correlatiecoëfficiënt van Pearson
+PERCENTILE = PERCENTIEL ## Geeft als resultaat het k-de percentiel van waarden in een bereik
+PERCENTRANK = PERCENT.RANG ## Geeft als resultaat de positie, in procenten uitgedrukt, van een waarde in de rangorde van een gegevensverzameling
+PERMUT = PERMUTATIES ## Geeft als resultaat het aantal permutaties voor een gegeven aantal objecten
+POISSON = POISSON ## Geeft als resultaat de Poisson-verdeling
+PROB = KANS ## Geeft als resultaat de kans dat waarden zich tussen twee grenzen bevinden
+QUARTILE = KWARTIEL ## Geeft als resultaat het kwartiel van een gegevensverzameling
+RANK = RANG ## Geeft als resultaat het rangnummer van een getal in een lijst getallen
+RSQ = R.KWADRAAT ## Geeft als resultaat het kwadraat van de Pearson-correlatiecoëfficiënt
+SKEW = SCHEEFHEID ## Geeft als resultaat de mate van asymmetrie van een verdeling
+SLOPE = RICHTING ## Geeft als resultaat de richtingscoëfficiënt van een lineaire regressielijn
+SMALL = KLEINSTE ## Geeft als resultaat de op k-1 na kleinste waarde in een gegevensverzameling
+STANDARDIZE = NORMALISEREN ## Geeft als resultaat een genormaliseerde waarde
+STDEV = STDEV ## Maakt een schatting van de standaarddeviatie op basis van een steekproef
+STDEVA = STDEVA ## Maakt een schatting van de standaarddeviatie op basis van een steekproef, inclusief getallen, tekst en logische waarden
+STDEVP = STDEVP ## Berekent de standaarddeviatie op basis van de volledige populatie
+STDEVPA = STDEVPA ## Berekent de standaarddeviatie op basis van de volledige populatie, inclusief getallen, tekst en logische waarden
+STEYX = STAND.FOUT.YX ## Geeft als resultaat de standaardfout in de voorspelde y-waarde voor elke x in een regressie
+TDIST = T.VERD ## Geeft als resultaat de Student T-verdeling
+TINV = T.INV ## Geeft als resultaat de inverse van de Student T-verdeling
+TREND = TREND ## Geeft als resultaat de waarden voor een lineaire trend
+TRIMMEAN = GETRIMD.GEM ## Geeft als resultaat het gemiddelde van waarden in een gegevensverzameling
+TTEST = T.TOETS ## Geeft als resultaat de kans met behulp van de Student T-toets
+VAR = VAR ## Maakt een schatting van de variantie op basis van een steekproef
+VARA = VARA ## Maakt een schatting van de variantie op basis van een steekproef, inclusief getallen, tekst en logische waarden
+VARP = VARP ## Berekent de variantie op basis van de volledige populatie
+VARPA = VARPA ## Berekent de standaarddeviatie op basis van de volledige populatie, inclusief getallen, tekst en logische waarden
+WEIBULL = WEIBULL ## Geeft als resultaat de Weibull-verdeling
+ZTEST = Z.TOETS ## Geeft als resultaat de eenzijdige kanswaarde van een Z-toets
+
+
+##
+## Text functions Tekstfuncties
+##
+ASC = ASC ## Wijzigt Nederlandse letters of katakanatekens over de volle breedte (dubbel-bytetekens) binnen een tekenreeks in tekens over de halve breedte (enkel-bytetekens)
+BAHTTEXT = BAHT.TEKST ## Converteert een getal naar tekst met de valutanotatie ß (baht)
+CHAR = TEKEN ## Geeft als resultaat het teken dat hoort bij de opgegeven code
+CLEAN = WISSEN.CONTROL ## Verwijdert alle niet-afdrukbare tekens uit een tekst
+CODE = CODE ## Geeft als resultaat de numerieke code voor het eerste teken in een tekenreeks
+CONCATENATE = TEKST.SAMENVOEGEN ## Voegt verschillende tekstfragmenten samen tot één tekstfragment
+DOLLAR = EURO ## Converteert een getal naar tekst met de valutanotatie € (euro)
+EXACT = GELIJK ## Controleert of twee tekenreeksen identiek zijn
+FIND = VIND.ALLES ## Zoekt een bepaalde tekenreeks in een tekst (waarbij onderscheid wordt gemaakt tussen hoofdletters en kleine letters)
+FINDB = VIND.ALLES.B ## Zoekt een bepaalde tekenreeks in een tekst (waarbij onderscheid wordt gemaakt tussen hoofdletters en kleine letters)
+FIXED = VAST ## Maakt een getal als tekst met een vast aantal decimalen op
+JIS = JIS ## Wijzigt Nederlandse letters of katakanatekens over de halve breedte (enkel-bytetekens) binnen een tekenreeks in tekens over de volle breedte (dubbel-bytetekens)
+LEFT = LINKS ## Geeft als resultaat de meest linkse tekens in een tekenreeks
+LEFTB = LINKSB ## Geeft als resultaat de meest linkse tekens in een tekenreeks
+LEN = LENGTE ## Geeft als resultaat het aantal tekens in een tekenreeks
+LENB = LENGTEB ## Geeft als resultaat het aantal tekens in een tekenreeks
+LOWER = KLEINE.LETTERS ## Zet tekst om in kleine letters
+MID = MIDDEN ## Geeft als resultaat een bepaald aantal tekens van een tekenreeks vanaf de positie die u opgeeft
+MIDB = DEELB ## Geeft als resultaat een bepaald aantal tekens van een tekenreeks vanaf de positie die u opgeeft
+PHONETIC = FONETISCH ## Haalt de fonetische tekens (furigana) uit een tekenreeks op
+PROPER = BEGINLETTERS ## Zet de eerste letter van elk woord in een tekst om in een hoofdletter
+REPLACE = VERVANG ## Vervangt tekens binnen een tekst
+REPLACEB = VERVANGENB ## Vervangt tekens binnen een tekst
+REPT = HERHALING ## Herhaalt een tekst een aantal malen
+RIGHT = RECHTS ## Geeft als resultaat de meest rechtse tekens in een tekenreeks
+RIGHTB = RECHTSB ## Geeft als resultaat de meest rechtse tekens in een tekenreeks
+SEARCH = VIND.SPEC ## Zoekt een bepaalde tekenreeks in een tekst (waarbij geen onderscheid wordt gemaakt tussen hoofdletters en kleine letters)
+SEARCHB = VIND.SPEC.B ## Zoekt een bepaalde tekenreeks in een tekst (waarbij geen onderscheid wordt gemaakt tussen hoofdletters en kleine letters)
+SUBSTITUTE = SUBSTITUEREN ## Vervangt oude tekst door nieuwe tekst in een tekenreeks
+T = T ## Converteert de argumenten naar tekst
+TEXT = TEKST ## Maakt een getal op en converteert het getal naar tekst
+TRIM = SPATIES.WISSEN ## Verwijdert de spaties uit een tekst
+UPPER = HOOFDLETTERS ## Zet tekst om in hoofdletters
+VALUE = WAARDE ## Converteert tekst naar een getal
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/no/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/no/config
new file mode 100755
index 0000000..c7f4152
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/no/config
@@ -0,0 +1,24 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = kr
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #NULL!
+DIV0 = #DIV/0!
+VALUE = #VERDI!
+REF = #REF!
+NAME = #NAVN?
+NUM = #NUM!
+NA = #I/T
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/no/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/no/functions
new file mode 100755
index 0000000..ab2a379
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/no/functions
@@ -0,0 +1,416 @@
+##
+## PhpSpreadsheet
+##
+## Data in this file derived from https://www.excel-function-translation.com/
+##
+##
+
+
+##
+## Add-in and Automation functions Funksjonene Tillegg og Automatisering
+##
+GETPIVOTDATA = HENTPIVOTDATA ## Returnerer data som er lagret i en pivottabellrapport
+
+
+##
+## Cube functions Kubefunksjoner
+##
+CUBEKPIMEMBER = KUBEKPIMEDLEM ## Returnerer navnet, egenskapen og målet for en viktig ytelsesindikator (KPI), og viser navnet og egenskapen i cellen. En KPI er en målbar enhet, for eksempel månedlig bruttoinntjening eller kvartalsvis inntjening per ansatt, og brukes til å overvåke ytelsen i en organisasjon.
+CUBEMEMBER = KUBEMEDLEM ## Returnerer et medlem eller en tuppel i et kubehierarki. Brukes til å validere at medlemmet eller tuppelen finnes i kuben.
+CUBEMEMBERPROPERTY = KUBEMEDLEMEGENSKAP ## Returnerer verdien til en medlemsegenskap i kuben. Brukes til å validere at et medlemsnavn finnes i kuben, og til å returnere den angitte egenskapen for dette medlemmet.
+CUBERANKEDMEMBER = KUBERANGERTMEDLEM ## Returnerer det n-te, eller rangerte, medlemmet i et sett. Brukes til å returnere ett eller flere elementer i et sett, for eksempel de 10 beste studentene.
+CUBESET = KUBESETT ## Definerer et beregnet sett av medlemmer eller tuppeler ved å sende et settuttrykk til kuben på serveren, noe som oppretter settet og deretter returnerer dette settet til Microsoft Office Excel.
+CUBESETCOUNT = KUBESETTANTALL ## Returnerer antallet elementer i et sett.
+CUBEVALUE = KUBEVERDI ## Returnerer en aggregert verdi fra en kube.
+
+
+##
+## Database functions Databasefunksjoner
+##
+DAVERAGE = DGJENNOMSNITT ## Returnerer gjennomsnittet av merkede databaseposter
+DCOUNT = DANTALL ## Teller celler som inneholder tall i en database
+DCOUNTA = DANTALLA ## Teller celler som ikke er tomme i en database
+DGET = DHENT ## Trekker ut fra en database en post som oppfyller angitte vilkår
+DMAX = DMAKS ## Returnerer maksimumsverdien fra merkede databaseposter
+DMIN = DMIN ## Returnerer minimumsverdien fra merkede databaseposter
+DPRODUCT = DPRODUKT ## Multipliserer verdiene i et bestemt felt med poster som oppfyller vilkårene i en database
+DSTDEV = DSTDAV ## Estimerer standardavviket basert på et utvalg av merkede databaseposter
+DSTDEVP = DSTAVP ## Beregner standardavviket basert på at merkede databaseposter utgjør hele populasjonen
+DSUM = DSUMMER ## Legger til tallene i feltkolonnen med poster, i databasen som oppfyller vilkårene
+DVAR = DVARIANS ## Estimerer variansen basert på et utvalg av merkede databaseposter
+DVARP = DVARIANSP ## Beregner variansen basert på at merkede databaseposter utgjør hele populasjonen
+
+
+##
+## Date and time functions Dato- og tidsfunksjoner
+##
+DATE = DATO ## Returnerer serienummeret som svarer til en bestemt dato
+DATEVALUE = DATOVERDI ## Konverterer en dato med tekstformat til et serienummer
+DAY = DAG ## Konverterer et serienummer til en dag i måneden
+DAYS360 = DAGER360 ## Beregner antall dager mellom to datoer basert på et år med 360 dager
+EDATE = DAG.ETTER ## Returnerer serienummeret som svarer til datoen som er det indikerte antall måneder før eller etter startdatoen
+EOMONTH = MÅNEDSSLUTT ## Returnerer serienummeret som svarer til siste dag i måneden, før eller etter et angitt antall måneder
+HOUR = TIME ## Konverterer et serienummer til en time
+MINUTE = MINUTT ## Konverterer et serienummer til et minutt
+MONTH = MÅNED ## Konverterer et serienummer til en måned
+NETWORKDAYS = NETT.ARBEIDSDAGER ## Returnerer antall hele arbeidsdager mellom to datoer
+NOW = NÅ ## Returnerer serienummeret som svarer til gjeldende dato og klokkeslett
+SECOND = SEKUND ## Konverterer et serienummer til et sekund
+TIME = TID ## Returnerer serienummeret som svarer til et bestemt klokkeslett
+TIMEVALUE = TIDSVERDI ## Konverterer et klokkeslett i tekstformat til et serienummer
+TODAY = IDAG ## Returnerer serienummeret som svarer til dagens dato
+WEEKDAY = UKEDAG ## Konverterer et serienummer til en ukedag
+WEEKNUM = UKENR ## Konverterer et serienummer til et tall som representerer hvilket nummer uken har i et år
+WORKDAY = ARBEIDSDAG ## Returnerer serienummeret som svarer til datoen før eller etter et angitt antall arbeidsdager
+YEAR = ÅR ## Konverterer et serienummer til et år
+YEARFRAC = ÅRDEL ## Returnerer brøkdelen for året, som svarer til antall hele dager mellom startdato og sluttdato
+
+
+##
+## Engineering functions Tekniske funksjoner
+##
+BESSELI = BESSELI ## Returnerer den endrede Bessel-funksjonen In(x)
+BESSELJ = BESSELJ ## Returnerer Bessel-funksjonen Jn(x)
+BESSELK = BESSELK ## Returnerer den endrede Bessel-funksjonen Kn(x)
+BESSELY = BESSELY ## Returnerer Bessel-funksjonen Yn(x)
+BIN2DEC = BINTILDES ## Konverterer et binært tall til et desimaltall
+BIN2HEX = BINTILHEKS ## Konverterer et binært tall til et heksadesimaltall
+BIN2OCT = BINTILOKT ## Konverterer et binært tall til et oktaltall
+COMPLEX = KOMPLEKS ## Konverterer reelle og imaginære koeffisienter til et komplekst tall
+CONVERT = KONVERTER ## Konverterer et tall fra ett målsystem til et annet
+DEC2BIN = DESTILBIN ## Konverterer et desimaltall til et binærtall
+DEC2HEX = DESTILHEKS ## Konverterer et heltall i 10-tallsystemet til et heksadesimalt tall
+DEC2OCT = DESTILOKT ## Konverterer et heltall i 10-tallsystemet til et oktaltall
+DELTA = DELTA ## Undersøker om to verdier er like
+ERF = FEILF ## Returnerer feilfunksjonen
+ERFC = FEILFK ## Returnerer den komplementære feilfunksjonen
+GESTEP = GRENSEVERDI ## Tester om et tall er større enn en terskelverdi
+HEX2BIN = HEKSTILBIN ## Konverterer et heksadesimaltall til et binært tall
+HEX2DEC = HEKSTILDES ## Konverterer et heksadesimalt tall til et heltall i 10-tallsystemet
+HEX2OCT = HEKSTILOKT ## Konverterer et heksadesimalt tall til et oktaltall
+IMABS = IMABS ## Returnerer absoluttverdien (koeffisienten) til et komplekst tall
+IMAGINARY = IMAGINÆR ## Returnerer den imaginære koeffisienten til et komplekst tall
+IMARGUMENT = IMARGUMENT ## Returnerer argumentet theta, som er en vinkel uttrykt i radianer
+IMCONJUGATE = IMKONJUGERT ## Returnerer den komplekse konjugaten til et komplekst tall
+IMCOS = IMCOS ## Returnerer cosinus til et komplekst tall
+IMDIV = IMDIV ## Returnerer kvotienten til to komplekse tall
+IMEXP = IMEKSP ## Returnerer eksponenten til et komplekst tall
+IMLN = IMLN ## Returnerer den naturlige logaritmen for et komplekst tall
+IMLOG10 = IMLOG10 ## Returnerer logaritmen med grunntall 10 for et komplekst tall
+IMLOG2 = IMLOG2 ## Returnerer logaritmen med grunntall 2 for et komplekst tall
+IMPOWER = IMOPPHØY ## Returnerer et komplekst tall opphøyd til en heltallspotens
+IMPRODUCT = IMPRODUKT ## Returnerer produktet av komplekse tall
+IMREAL = IMREELL ## Returnerer den reelle koeffisienten til et komplekst tall
+IMSIN = IMSIN ## Returnerer sinus til et komplekst tall
+IMSQRT = IMROT ## Returnerer kvadratroten av et komplekst tall
+IMSUB = IMSUB ## Returnerer differansen mellom to komplekse tall
+IMSUM = IMSUMMER ## Returnerer summen av komplekse tall
+OCT2BIN = OKTTILBIN ## Konverterer et oktaltall til et binært tall
+OCT2DEC = OKTTILDES ## Konverterer et oktaltall til et desimaltall
+OCT2HEX = OKTTILHEKS ## Konverterer et oktaltall til et heksadesimaltall
+
+
+##
+## Financial functions Økonomiske funksjoner
+##
+ACCRINT = PÅLØPT.PERIODISK.RENTE ## Returnerer påløpte renter for et verdipapir som betaler periodisk rente
+ACCRINTM = PÅLØPT.FORFALLSRENTE ## Returnerer den påløpte renten for et verdipapir som betaler rente ved forfall
+AMORDEGRC = AMORDEGRC ## Returnerer avskrivningen for hver regnskapsperiode ved hjelp av en avskrivingskoeffisient
+AMORLINC = AMORLINC ## Returnerer avskrivingen for hver regnskapsperiode
+COUPDAYBS = OBLIG.DAGER.FF ## Returnerer antall dager fra begynnelsen av den rentebærende perioden til innløsningsdatoen
+COUPDAYS = OBLIG.DAGER ## Returnerer antall dager i den rentebærende perioden som inneholder innløsningsdatoen
+COUPDAYSNC = OBLIG.DAGER.NF ## Returnerer antall dager fra betalingsdato til neste renteinnbetalingsdato
+COUPNCD = OBLIG.DAGER.EF ## Returnerer obligasjonsdatoen som kommer etter oppgjørsdatoen
+COUPNUM = OBLIG.ANTALL ## Returnerer antall obligasjoner som skal betales mellom oppgjørsdatoen og forfallsdatoen
+COUPPCD = OBLIG.DAG.FORRIGE ## Returnerer obligasjonsdatoen som kommer før oppgjørsdatoen
+CUMIPMT = SAMLET.RENTE ## Returnerer den kumulative renten som er betalt mellom to perioder
+CUMPRINC = SAMLET.HOVEDSTOL ## Returnerer den kumulative hovedstolen som er betalt for et lån mellom to perioder
+DB = DAVSKR ## Returnerer avskrivningen for et aktivum i en angitt periode, foretatt med fast degressiv avskrivning
+DDB = DEGRAVS ## Returnerer avskrivningen for et aktivum for en gitt periode, ved hjelp av dobbel degressiv avskrivning eller en metode som du selv angir
+DISC = DISKONTERT ## Returnerer diskonteringsraten for et verdipapir
+DOLLARDE = DOLLARDE ## Konverterer en valutapris uttrykt som en brøk, til en valutapris uttrykt som et desimaltall
+DOLLARFR = DOLLARBR ## Konverterer en valutapris uttrykt som et desimaltall, til en valutapris uttrykt som en brøk
+DURATION = VARIGHET ## Returnerer årlig varighet for et verdipapir med renter som betales periodisk
+EFFECT = EFFEKTIV.RENTE ## Returnerer den effektive årlige rentesatsen
+FV = SLUTTVERDI ## Returnerer fremtidig verdi for en investering
+FVSCHEDULE = SVPLAN ## Returnerer den fremtidige verdien av en inngående hovedstol etter å ha anvendt en serie med sammensatte rentesatser
+INTRATE = RENTESATS ## Returnerer rentefoten av et fullfinansiert verdipapir
+IPMT = RAVDRAG ## Returnerer betalte renter på en investering for en gitt periode
+IRR = IR ## Returnerer internrenten for en serie kontantstrømmer
+ISPMT = ER.AVDRAG ## Beregner renten som er betalt for en investering i løpet av en bestemt periode
+MDURATION = MVARIGHET ## Returnerer Macauleys modifiserte varighet for et verdipapir med en antatt pålydende verdi på kr 100,00
+MIRR = MODIR ## Returnerer internrenten der positive og negative kontantstrømmer finansieres med forskjellige satser
+NOMINAL = NOMINELL ## Returnerer årlig nominell rentesats
+NPER = PERIODER ## Returnerer antall perioder for en investering
+NPV = NNV ## Returnerer netto nåverdi for en investering, basert på en serie periodiske kontantstrømmer og en rentesats
+ODDFPRICE = AVVIKFP.PRIS ## Returnerer pris pålydende kr 100 for et verdipapir med en odde første periode
+ODDFYIELD = AVVIKFP.AVKASTNING ## Returnerer avkastingen for et verdipapir med en odde første periode
+ODDLPRICE = AVVIKSP.PRIS ## Returnerer pris pålydende kr 100 for et verdipapir med en odde siste periode
+ODDLYIELD = AVVIKSP.AVKASTNING ## Returnerer avkastingen for et verdipapir med en odde siste periode
+PMT = AVDRAG ## Returnerer periodisk betaling for en annuitet
+PPMT = AMORT ## Returnerer betalingen på hovedstolen for en investering i en gitt periode
+PRICE = PRIS ## Returnerer prisen per pålydende kr 100 for et verdipapir som gir periodisk avkastning
+PRICEDISC = PRIS.DISKONTERT ## Returnerer prisen per pålydende kr 100 for et diskontert verdipapir
+PRICEMAT = PRIS.FORFALL ## Returnerer prisen per pålydende kr 100 av et verdipapir som betaler rente ved forfall
+PV = NÅVERDI ## Returnerer nåverdien av en investering
+RATE = RENTE ## Returnerer rentesatsen per periode for en annuitet
+RECEIVED = MOTTATT.AVKAST ## Returnerer summen som mottas ved forfallsdato for et fullinvestert verdipapir
+SLN = LINAVS ## Returnerer den lineære avskrivningen for et aktivum i én periode
+SYD = ÅRSAVS ## Returnerer årsavskrivningen for et aktivum i en angitt periode
+TBILLEQ = TBILLEKV ## Returnerer den obligasjonsekvivalente avkastningen for en statsobligasjon
+TBILLPRICE = TBILLPRIS ## Returnerer prisen per pålydende kr 100 for en statsobligasjon
+TBILLYIELD = TBILLAVKASTNING ## Returnerer avkastningen til en statsobligasjon
+VDB = VERDIAVS ## Returnerer avskrivningen for et aktivum i en angitt periode eller delperiode, ved hjelp av degressiv avskrivning
+XIRR = XIR ## Returnerer internrenten for en serie kontantstrømmer som ikke nødvendigvis er periodiske
+XNPV = XNNV ## Returnerer netto nåverdi for en serie kontantstrømmer som ikke nødvendigvis er periodiske
+YIELD = AVKAST ## Returnerer avkastningen på et verdipapir som betaler periodisk rente
+YIELDDISC = AVKAST.DISKONTERT ## Returnerer årlig avkastning for et diskontert verdipapir, for eksempel en statskasseveksel
+YIELDMAT = AVKAST.FORFALL ## Returnerer den årlige avkastningen for et verdipapir som betaler rente ved forfallsdato
+
+
+##
+## Information functions Informasjonsfunksjoner
+##
+CELL = CELLE ## Returnerer informasjon om formatering, plassering eller innholdet til en celle
+ERROR.TYPE = FEIL.TYPE ## Returnerer et tall som svarer til en feiltype
+INFO = INFO ## Returnerer informasjon om gjeldende operativmiljø
+ISBLANK = ERTOM ## Returnerer SANN hvis verdien er tom
+ISERR = ERFEIL ## Returnerer SANN hvis verdien er en hvilken som helst annen feilverdi enn #I/T
+ISERROR = ERFEIL ## Returnerer SANN hvis verdien er en hvilken som helst feilverdi
+ISEVEN = ERPARTALL ## Returnerer SANN hvis tallet er et partall
+ISLOGICAL = ERLOGISK ## Returnerer SANN hvis verdien er en logisk verdi
+ISNA = ERIT ## Returnerer SANN hvis verdien er feilverdien #I/T
+ISNONTEXT = ERIKKETEKST ## Returnerer SANN hvis verdien ikke er tekst
+ISNUMBER = ERTALL ## Returnerer SANN hvis verdien er et tall
+ISODD = ERODDETALL ## Returnerer SANN hvis tallet er et oddetall
+ISREF = ERREF ## Returnerer SANN hvis verdien er en referanse
+ISTEXT = ERTEKST ## Returnerer SANN hvis verdien er tekst
+N = N ## Returnerer en verdi som er konvertert til et tall
+NA = IT ## Returnerer feilverdien #I/T
+TYPE = VERDITYPE ## Returnerer et tall som indikerer datatypen til en verdi
+
+
+##
+## Logical functions Logiske funksjoner
+##
+AND = OG ## Returnerer SANN hvis alle argumentene er lik SANN
+FALSE = USANN ## Returnerer den logiske verdien USANN
+IF = HVIS ## Angir en logisk test som skal utføres
+IFERROR = HVISFEIL ## Returnerer en verdi du angir hvis en formel evaluerer til en feil. Ellers returnerer den resultatet av formelen.
+NOT = IKKE ## Reverserer logikken til argumentet
+OR = ELLER ## Returnerer SANN hvis ett eller flere argumenter er lik SANN
+TRUE = SANN ## Returnerer den logiske verdien SANN
+
+
+##
+## Lookup and reference functions Oppslag- og referansefunksjoner
+##
+ADDRESS = ADRESSE ## Returnerer en referanse som tekst til en enkelt celle i et regneark
+AREAS = OMRÅDER ## Returnerer antall områder i en referanse
+CHOOSE = VELG ## Velger en verdi fra en liste med verdier
+COLUMN = KOLONNE ## Returnerer kolonnenummeret for en referanse
+COLUMNS = KOLONNER ## Returnerer antall kolonner i en referanse
+HLOOKUP = FINN.KOLONNE ## Leter i den øverste raden i en matrise og returnerer verdien for den angitte cellen
+HYPERLINK = HYPERKOBLING ## Oppretter en snarvei eller et hopp som åpner et dokument som er lagret på en nettverksserver, et intranett eller Internett
+INDEX = INDEKS ## Bruker en indeks til å velge en verdi fra en referanse eller matrise
+INDIRECT = INDIREKTE ## Returnerer en referanse angitt av en tekstverdi
+LOOKUP = SLÅ.OPP ## Slår opp verdier i en vektor eller matrise
+MATCH = SAMMENLIGNE ## Slår opp verdier i en referanse eller matrise
+OFFSET = FORSKYVNING ## Returnerer en referanseforskyvning fra en gitt referanse
+ROW = RAD ## Returnerer radnummeret for en referanse
+ROWS = RADER ## Returnerer antall rader i en referanse
+RTD = RTD ## Henter sanntidsdata fra et program som støtter COM-automatisering (automatisering: En måte å arbeide på med programobjekter fra et annet program- eller utviklingsverktøy. Tidligere kalt OLE-automatisering. Automatisering er en bransjestandard og en funksjon i Component Object Model (COM).)
+TRANSPOSE = TRANSPONER ## Returnerer transponeringen av en matrise
+VLOOKUP = FINN.RAD ## Leter i den første kolonnen i en matrise og flytter bortover raden for å returnere verdien til en celle
+
+
+##
+## Math and trigonometry functions Matematikk- og trigonometrifunksjoner
+##
+ABS = ABS ## Returnerer absoluttverdien til et tall
+ACOS = ARCCOS ## Returnerer arcus cosinus til et tall
+ACOSH = ARCCOSH ## Returnerer den inverse hyperbolske cosinus til et tall
+ASIN = ARCSIN ## Returnerer arcus sinus til et tall
+ASINH = ARCSINH ## Returnerer den inverse hyperbolske sinus til et tall
+ATAN = ARCTAN ## Returnerer arcus tangens til et tall
+ATAN2 = ARCTAN2 ## Returnerer arcus tangens fra x- og y-koordinater
+ATANH = ARCTANH ## Returnerer den inverse hyperbolske tangens til et tall
+CEILING = AVRUND.GJELDENDE.MULTIPLUM ## Runder av et tall til nærmeste heltall eller til nærmeste signifikante multiplum
+COMBIN = KOMBINASJON ## Returnerer antall kombinasjoner for ett gitt antall objekter
+COS = COS ## Returnerer cosinus til et tall
+COSH = COSH ## Returnerer den hyperbolske cosinus til et tall
+DEGREES = GRADER ## Konverterer radianer til grader
+EVEN = AVRUND.TIL.PARTALL ## Runder av et tall oppover til nærmeste heltall som er et partall
+EXP = EKSP ## Returnerer e opphøyd i en angitt potens
+FACT = FAKULTET ## Returnerer fakultet til et tall
+FACTDOUBLE = DOBBELFAKT ## Returnerer et talls doble fakultet
+FLOOR = AVRUND.GJELDENDE.MULTIPLUM.NED ## Avrunder et tall nedover, mot null
+GCD = SFF ## Returnerer høyeste felles divisor
+INT = HELTALL ## Avrunder et tall nedover til nærmeste heltall
+LCM = MFM ## Returnerer minste felles multiplum
+LN = LN ## Returnerer den naturlige logaritmen til et tall
+LOG = LOG ## Returnerer logaritmen for et tall til et angitt grunntall
+LOG10 = LOG10 ## Returnerer logaritmen med grunntall 10 for et tall
+MDETERM = MDETERM ## Returnerer matrisedeterminanten til en matrise
+MINVERSE = MINVERS ## Returnerer den inverse matrisen til en matrise
+MMULT = MMULT ## Returnerer matriseproduktet av to matriser
+MOD = REST ## Returnerer resten fra en divisjon
+MROUND = MRUND ## Returnerer et tall avrundet til det ønskede multiplum
+MULTINOMIAL = MULTINOMINELL ## Returnerer det multinominelle for et sett med tall
+ODD = AVRUND.TIL.ODDETALL ## Runder av et tall oppover til nærmeste heltall som er et oddetall
+PI = PI ## Returnerer verdien av pi
+POWER = OPPHØYD.I ## Returnerer resultatet av et tall opphøyd i en potens
+PRODUCT = PRODUKT ## Multipliserer argumentene
+QUOTIENT = KVOTIENT ## Returnerer heltallsdelen av en divisjon
+RADIANS = RADIANER ## Konverterer grader til radianer
+RAND = TILFELDIG ## Returnerer et tilfeldig tall mellom 0 og 1
+RANDBETWEEN = TILFELDIGMELLOM ## Returnerer et tilfeldig tall innenfor et angitt område
+ROMAN = ROMERTALL ## Konverterer vanlige tall til romertall, som tekst
+ROUND = AVRUND ## Avrunder et tall til et angitt antall sifre
+ROUNDDOWN = AVRUND.NED ## Avrunder et tall nedover, mot null
+ROUNDUP = AVRUND.OPP ## Runder av et tall oppover, bort fra null
+SERIESSUM = SUMMER.REKKE ## Returnerer summen av en geometrisk rekke, basert på formelen
+SIGN = FORTEGN ## Returnerer fortegnet for et tall
+SIN = SIN ## Returnerer sinus til en gitt vinkel
+SINH = SINH ## Returnerer den hyperbolske sinus til et tall
+SQRT = ROT ## Returnerer en positiv kvadratrot
+SQRTPI = ROTPI ## Returnerer kvadratroten av (tall * pi)
+SUBTOTAL = DELSUM ## Returnerer en delsum i en liste eller database
+SUM = SUMMER ## Legger sammen argumentene
+SUMIF = SUMMERHVIS ## Legger sammen cellene angitt ved et gitt vilkår
+SUMIFS = SUMMER.HVIS.SETT ## Legger sammen cellene i et område som oppfyller flere vilkår
+SUMPRODUCT = SUMMERPRODUKT ## Returnerer summen av produktene av tilsvarende matrisekomponenter
+SUMSQ = SUMMERKVADRAT ## Returnerer kvadratsummen av argumentene
+SUMX2MY2 = SUMMERX2MY2 ## Returnerer summen av differansen av kvadratene for tilsvarende verdier i to matriser
+SUMX2PY2 = SUMMERX2PY2 ## Returnerer summen av kvadratsummene for tilsvarende verdier i to matriser
+SUMXMY2 = SUMMERXMY2 ## Returnerer summen av kvadratene av differansen for tilsvarende verdier i to matriser
+TAN = TAN ## Returnerer tangens for et tall
+TANH = TANH ## Returnerer den hyperbolske tangens for et tall
+TRUNC = AVKORT ## Korter av et tall til et heltall
+
+
+##
+## Statistical functions Statistiske funksjoner
+##
+AVEDEV = GJENNOMSNITTSAVVIK ## Returnerer datapunktenes gjennomsnittlige absoluttavvik fra middelverdien
+AVERAGE = GJENNOMSNITT ## Returnerer gjennomsnittet for argumentene
+AVERAGEA = GJENNOMSNITTA ## Returnerer gjennomsnittet for argumentene, inkludert tall, tekst og logiske verdier
+AVERAGEIF = GJENNOMSNITTHVIS ## Returnerer gjennomsnittet (aritmetisk gjennomsnitt) av alle cellene i et område som oppfyller et bestemt vilkår
+AVERAGEIFS = GJENNOMSNITT.HVIS.SETT ## Returnerer gjennomsnittet (aritmetisk middelverdi) av alle celler som oppfyller flere vilkår.
+BETADIST = BETA.FORDELING ## Returnerer den kumulative betafordelingsfunksjonen
+BETAINV = INVERS.BETA.FORDELING ## Returnerer den inverse verdien til fordelingsfunksjonen for en angitt betafordeling
+BINOMDIST = BINOM.FORDELING ## Returnerer den individuelle binomiske sannsynlighetsfordelingen
+CHIDIST = KJI.FORDELING ## Returnerer den ensidige sannsynligheten for en kjikvadrert fordeling
+CHIINV = INVERS.KJI.FORDELING ## Returnerer den inverse av den ensidige sannsynligheten for den kjikvadrerte fordelingen
+CHITEST = KJI.TEST ## Utfører testen for uavhengighet
+CONFIDENCE = KONFIDENS ## Returnerer konfidensintervallet til gjennomsnittet for en populasjon
+CORREL = KORRELASJON ## Returnerer korrelasjonskoeffisienten mellom to datasett
+COUNT = ANTALL ## Teller hvor mange tall som er i argumentlisten
+COUNTA = ANTALLA ## Teller hvor mange verdier som er i argumentlisten
+COUNTBLANK = TELLBLANKE ## Teller antall tomme celler i et område.
+COUNTIF = ANTALL.HVIS ## Teller antall celler i et område som oppfyller gitte vilkår
+COUNTIFS = ANTALL.HVIS.SETT ## Teller antallet ikke-tomme celler i et område som oppfyller flere vilkår
+COVAR = KOVARIANS ## Returnerer kovariansen, gjennomsnittet av produktene av parvise avvik
+CRITBINOM = GRENSE.BINOM ## Returnerer den minste verdien der den kumulative binomiske fordelingen er mindre enn eller lik en vilkårsverdi
+DEVSQ = AVVIK.KVADRERT ## Returnerer summen av kvadrerte avvik
+EXPONDIST = EKSP.FORDELING ## Returnerer eksponentialfordelingen
+FDIST = FFORDELING ## Returnerer F-sannsynlighetsfordelingen
+FINV = FFORDELING.INVERS ## Returnerer den inverse av den sannsynlige F-fordelingen
+FISHER = FISHER ## Returnerer Fisher-transformasjonen
+FISHERINV = FISHERINV ## Returnerer den inverse av Fisher-transformasjonen
+FORECAST = PROGNOSE ## Returnerer en verdi langs en lineær trend
+FREQUENCY = FREKVENS ## Returnerer en frekvensdistribusjon som en loddrett matrise
+FTEST = FTEST ## Returnerer resultatet av en F-test
+GAMMADIST = GAMMAFORDELING ## Returnerer gammafordelingen
+GAMMAINV = GAMMAINV ## Returnerer den inverse av den gammakumulative fordelingen
+GAMMALN = GAMMALN ## Returnerer den naturlige logaritmen til gammafunksjonen G(x)
+GEOMEAN = GJENNOMSNITT.GEOMETRISK ## Returnerer den geometriske middelverdien
+GROWTH = VEKST ## Returnerer verdier langs en eksponentiell trend
+HARMEAN = GJENNOMSNITT.HARMONISK ## Returnerer den harmoniske middelverdien
+HYPGEOMDIST = HYPGEOM.FORDELING ## Returnerer den hypergeometriske fordelingen
+INTERCEPT = SKJÆRINGSPUNKT ## Returnerer skjæringspunktet til den lineære regresjonslinjen
+KURT = KURT ## Returnerer kurtosen til et datasett
+LARGE = N.STØRST ## Returnerer den n-te største verdien i et datasett
+LINEST = RETTLINJE ## Returnerer parameterne til en lineær trend
+LOGEST = KURVE ## Returnerer parameterne til en eksponentiell trend
+LOGINV = LOGINV ## Returnerer den inverse lognormale fordelingen
+LOGNORMDIST = LOGNORMFORD ## Returnerer den kumulative lognormale fordelingen
+MAX = STØRST ## Returnerer maksimumsverdien i en argumentliste
+MAXA = MAKSA ## Returnerer maksimumsverdien i en argumentliste, inkludert tall, tekst og logiske verdier
+MEDIAN = MEDIAN ## Returnerer medianen til tallene som er gitt
+MIN = MIN ## Returnerer minimumsverdien i en argumentliste
+MINA = MINA ## Returnerer den minste verdien i en argumentliste, inkludert tall, tekst og logiske verdier
+MODE = MODUS ## Returnerer den vanligste verdien i et datasett
+NEGBINOMDIST = NEGBINOM.FORDELING ## Returnerer den negative binomiske fordelingen
+NORMDIST = NORMALFORDELING ## Returnerer den kumulative normalfordelingen
+NORMINV = NORMINV ## Returnerer den inverse kumulative normalfordelingen
+NORMSDIST = NORMSFORDELING ## Returnerer standard kumulativ normalfordeling
+NORMSINV = NORMSINV ## Returnerer den inverse av den den kumulative standard normalfordelingen
+PEARSON = PEARSON ## Returnerer produktmomentkorrelasjonskoeffisienten, Pearson
+PERCENTILE = PERSENTIL ## Returnerer den n-te persentil av verdiene i et område
+PERCENTRANK = PROSENTDEL ## Returnerer prosentrangeringen av en verdi i et datasett
+PERMUT = PERMUTER ## Returnerer antall permutasjoner for et gitt antall objekter
+POISSON = POISSON ## Returnerer Poissons sannsynlighetsfordeling
+PROB = SANNSYNLIG ## Returnerer sannsynligheten for at verdier i et område ligger mellom to grenser
+QUARTILE = KVARTIL ## Returnerer kvartilen til et datasett
+RANK = RANG ## Returnerer rangeringen av et tall, eller plassen tallet har i en rekke
+RSQ = RKVADRAT ## Returnerer kvadratet av produktmomentkorrelasjonskoeffisienten (Pearsons r)
+SKEW = SKJEVFORDELING ## Returnerer skjevheten i en fordeling
+SLOPE = STIGNINGSTALL ## Returnerer stigningtallet for den lineære regresjonslinjen
+SMALL = N.MINST ## Returnerer den n-te minste verdien i et datasett
+STANDARDIZE = NORMALISER ## Returnerer en normalisert verdi
+STDEV = STDAV ## Estimere standardavvik på grunnlag av et utvalg
+STDEVA = STDAVVIKA ## Estimerer standardavvik basert på et utvalg, inkludert tall, tekst og logiske verdier
+STDEVP = STDAVP ## Beregner standardavvik basert på hele populasjonen
+STDEVPA = STDAVVIKPA ## Beregner standardavvik basert på hele populasjonen, inkludert tall, tekst og logiske verdier
+STEYX = STANDARDFEIL ## Returnerer standardfeilen for den predikerte y-verdien for hver x i regresjonen
+TDIST = TFORDELING ## Returnerer en Student t-fordeling
+TINV = TINV ## Returnerer den inverse Student t-fordelingen
+TREND = TREND ## Returnerer verdier langs en lineær trend
+TRIMMEAN = TRIMMET.GJENNOMSNITT ## Returnerer den interne middelverdien til et datasett
+TTEST = TTEST ## Returnerer sannsynligheten assosiert med en Student t-test
+VAR = VARIANS ## Estimerer varians basert på et utvalg
+VARA = VARIANSA ## Estimerer varians basert på et utvalg, inkludert tall, tekst og logiske verdier
+VARP = VARIANSP ## Beregner varians basert på hele populasjonen
+VARPA = VARIANSPA ## Beregner varians basert på hele populasjonen, inkludert tall, tekst og logiske verdier
+WEIBULL = WEIBULL.FORDELING ## Returnerer Weibull-fordelingen
+ZTEST = ZTEST ## Returnerer den ensidige sannsynlighetsverdien for en z-test
+
+
+##
+## Text functions Tekstfunksjoner
+##
+ASC = STIGENDE ## Endrer fullbreddes (dobbeltbyte) engelske bokstaver eller katakana i en tegnstreng, til halvbreddes (enkeltbyte) tegn
+BAHTTEXT = BAHTTEKST ## Konverterer et tall til tekst, og bruker valutaformatet ß (baht)
+CHAR = TEGNKODE ## Returnerer tegnet som svarer til kodenummeret
+CLEAN = RENSK ## Fjerner alle tegn som ikke kan skrives ut, fra teksten
+CODE = KODE ## Returnerer en numerisk kode for det første tegnet i en tekststreng
+CONCATENATE = KJEDE.SAMMEN ## Slår sammen flere tekstelementer til ett tekstelement
+DOLLAR = VALUTA ## Konverterer et tall til tekst, og bruker valutaformatet $ (dollar)
+EXACT = EKSAKT ## Kontrollerer om to tekstverdier er like
+FIND = FINN ## Finner en tekstverdi inne i en annen (skiller mellom store og små bokstaver)
+FINDB = FINNB ## Finner en tekstverdi inne i en annen (skiller mellom store og små bokstaver)
+FIXED = FASTSATT ## Formaterer et tall som tekst med et bestemt antall desimaler
+JIS = JIS ## Endrer halvbreddes (enkeltbyte) engelske bokstaver eller katakana i en tegnstreng, til fullbreddes (dobbeltbyte) tegn
+LEFT = VENSTRE ## Returnerer tegnene lengst til venstre i en tekstverdi
+LEFTB = VENSTREB ## Returnerer tegnene lengst til venstre i en tekstverdi
+LEN = LENGDE ## Returnerer antall tegn i en tekststreng
+LENB = LENGDEB ## Returnerer antall tegn i en tekststreng
+LOWER = SMÅ ## Konverterer tekst til små bokstaver
+MID = DELTEKST ## Returnerer et angitt antall tegn fra en tekststreng, og begynner fra posisjonen du angir
+MIDB = DELTEKSTB ## Returnerer et angitt antall tegn fra en tekststreng, og begynner fra posisjonen du angir
+PHONETIC = FURIGANA ## Trekker ut fonetiske tegn (furigana) fra en tekststreng
+PROPER = STOR.FORBOKSTAV ## Gir den første bokstaven i hvert ord i en tekstverdi stor forbokstav
+REPLACE = ERSTATT ## Erstatter tegn i en tekst
+REPLACEB = ERSTATTB ## Erstatter tegn i en tekst
+REPT = GJENTA ## Gjentar tekst et gitt antall ganger
+RIGHT = HØYRE ## Returnerer tegnene lengst til høyre i en tekstverdi
+RIGHTB = HØYREB ## Returnerer tegnene lengst til høyre i en tekstverdi
+SEARCH = SØK ## Finner en tekstverdi inne i en annen (skiller ikke mellom store og små bokstaver)
+SEARCHB = SØKB ## Finner en tekstverdi inne i en annen (skiller ikke mellom store og små bokstaver)
+SUBSTITUTE = BYTT.UT ## Bytter ut gammel tekst med ny tekst i en tekststreng
+T = T ## Konverterer argumentene til tekst
+TEXT = TEKST ## Formaterer et tall og konverterer det til tekst
+TRIM = TRIMME ## Fjerner mellomrom fra tekst
+UPPER = STORE ## Konverterer tekst til store bokstaver
+VALUE = VERDI ## Konverterer et tekstargument til et tall
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config
new file mode 100755
index 0000000..00f8b9a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config
@@ -0,0 +1,24 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = zł
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #ZERO!
+DIV0 = #DZIEL/0!
+VALUE = #ARG!
+REF = #ADR!
+NAME = #NAZWA?
+NUM = #LICZBA!
+NA = #N/D!
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions
new file mode 100755
index 0000000..907a4ff
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions
@@ -0,0 +1,416 @@
+##
+## PhpSpreadsheet
+##
+## Data in this file derived from https://www.excel-function-translation.com/
+##
+##
+
+
+##
+## Add-in and Automation functions Funkcje dodatków i automatyzacji
+##
+GETPIVOTDATA = WEŹDANETABELI ## Zwraca dane przechowywane w raporcie tabeli przestawnej.
+
+
+##
+## Cube functions Funkcje modułów
+##
+CUBEKPIMEMBER = ELEMENT.KPI.MODUŁU ## Zwraca nazwę, właściwość i miarę kluczowego wskaźnika wydajności (KPI) oraz wyświetla nazwę i właściwość w komórce. Wskaźnik KPI jest miarą ilościową, taką jak miesięczny zysk brutto lub kwartalna fluktuacja pracowników, używaną do monitorowania wydajności organizacji.
+CUBEMEMBER = ELEMENT.MODUŁU ## Zwraca element lub krotkę z hierarchii modułu. Służy do sprawdzania, czy element lub krotka istnieje w module.
+CUBEMEMBERPROPERTY = WŁAŚCIWOŚĆ.ELEMENTU.MODUŁU ## Zwraca wartość właściwości elementu w module. Służy do sprawdzania, czy nazwa elementu istnieje w module, i zwracania określonej właściwości dla tego elementu.
+CUBERANKEDMEMBER = USZEREGOWANY.ELEMENT.MODUŁU ## Zwraca n-ty (albo uszeregowany) element zestawu. Służy do zwracania elementu lub elementów zestawu, na przykład najlepszego sprzedawcy lub 10 najlepszych studentów.
+CUBESET = ZESTAW.MODUŁÓW ## Definiuje obliczony zestaw elementów lub krotek, wysyłając wyrażenie zestawu do serwera modułu, który tworzy zestaw i zwraca go do programu Microsoft Office Excel.
+CUBESETCOUNT = LICZNIK.MODUŁÓW.ZESTAWU ## Zwraca liczbę elementów zestawu.
+CUBEVALUE = WARTOŚĆ.MODUŁU ## Zwraca zagregowaną wartość z modułu.
+
+
+##
+## Database functions Funkcje baz danych
+##
+DAVERAGE = BD.ŚREDNIA ## Zwraca wartość średniej wybranych wpisów bazy danych.
+DCOUNT = BD.ILE.REKORDÓW ## Zlicza komórki zawierające liczby w bazie danych.
+DCOUNTA = BD.ILE.REKORDÓW.A ## Zlicza niepuste komórki w bazie danych.
+DGET = BD.POLE ## Wyodrębnia z bazy danych jeden rekord spełniający określone kryteria.
+DMAX = BD.MAX ## Zwraca wartość maksymalną z wybranych wpisów bazy danych.
+DMIN = BD.MIN ## Zwraca wartość minimalną z wybranych wpisów bazy danych.
+DPRODUCT = BD.ILOCZYN ## Mnoży wartości w konkretnym, spełniającym kryteria polu rekordów bazy danych.
+DSTDEV = BD.ODCH.STANDARD ## Szacuje odchylenie standardowe na podstawie próbki z wybranych wpisów bazy danych.
+DSTDEVP = BD.ODCH.STANDARD.POPUL ## Oblicza odchylenie standardowe na podstawie całej populacji wybranych wpisów bazy danych.
+DSUM = BD.SUMA ## Dodaje liczby w kolumnie pól rekordów bazy danych, które spełniają kryteria.
+DVAR = BD.WARIANCJA ## Szacuje wariancję na podstawie próbki z wybranych wpisów bazy danych.
+DVARP = BD.WARIANCJA.POPUL ## Oblicza wariancję na podstawie całej populacji wybranych wpisów bazy danych.
+
+
+##
+## Date and time functions Funkcje dat, godzin i czasu
+##
+DATE = DATA ## Zwraca liczbę seryjną dla wybranej daty.
+DATEVALUE = DATA.WARTOŚĆ ## Konwertuje datę w formie tekstu na liczbę seryjną.
+DAY = DZIEŃ ## Konwertuje liczbę seryjną na dzień miesiąca.
+DAYS360 = DNI.360 ## Oblicza liczbę dni między dwiema datami na podstawie roku 360-dniowego.
+EDATE = UPŁDNI ## Zwraca liczbę seryjną daty jako wskazaną liczbę miesięcy przed określoną datą początkową lub po niej.
+EOMONTH = EOMONTH ## Zwraca liczbę seryjną ostatniego dnia miesiąca przed określoną liczbą miesięcy lub po niej.
+HOUR = GODZINA ## Konwertuje liczbę seryjną na godzinę.
+MINUTE = MINUTA ## Konwertuje liczbę seryjną na minutę.
+MONTH = MIESIĄC ## Konwertuje liczbę seryjną na miesiąc.
+NETWORKDAYS = NETWORKDAYS ## Zwraca liczbę pełnych dni roboczych między dwiema datami.
+NOW = TERAZ ## Zwraca liczbę seryjną bieżącej daty i godziny.
+SECOND = SEKUNDA ## Konwertuje liczbę seryjną na sekundę.
+TIME = CZAS ## Zwraca liczbę seryjną określonego czasu.
+TIMEVALUE = CZAS.WARTOŚĆ ## Konwertuje czas w formie tekstu na liczbę seryjną.
+TODAY = DZIŚ ## Zwraca liczbę seryjną dla daty bieżącej.
+WEEKDAY = DZIEŃ.TYG ## Konwertuje liczbę seryjną na dzień tygodnia.
+WEEKNUM = WEEKNUM ## Konwertuje liczbę seryjną na liczbę reprezentującą numer tygodnia w roku.
+WORKDAY = WORKDAY ## Zwraca liczbę seryjną dla daty przed określoną liczbą dni roboczych lub po niej.
+YEAR = ROK ## Konwertuje liczbę seryjną na rok.
+YEARFRAC = YEARFRAC ## Zwraca część roku reprezentowaną przez pełną liczbę dni między datą początkową a datą końcową.
+
+
+##
+## Engineering functions Funkcje inżynierskie
+##
+BESSELI = BESSELI ## Zwraca wartość zmodyfikowanej funkcji Bessela In(x).
+BESSELJ = BESSELJ ## Zwraca wartość funkcji Bessela Jn(x).
+BESSELK = BESSELK ## Zwraca wartość zmodyfikowanej funkcji Bessela Kn(x).
+BESSELY = BESSELY ## Zwraca wartość funkcji Bessela Yn(x).
+BIN2DEC = BIN2DEC ## Konwertuje liczbę w postaci dwójkowej na liczbę w postaci dziesiętnej.
+BIN2HEX = BIN2HEX ## Konwertuje liczbę w postaci dwójkowej na liczbę w postaci szesnastkowej.
+BIN2OCT = BIN2OCT ## Konwertuje liczbę w postaci dwójkowej na liczbę w postaci ósemkowej.
+COMPLEX = COMPLEX ## Konwertuje część rzeczywistą i urojoną na liczbę zespoloną.
+CONVERT = CONVERT ## Konwertuje liczbę z jednego systemu miar na inny.
+DEC2BIN = DEC2BIN ## Konwertuje liczbę w postaci dziesiętnej na postać dwójkową.
+DEC2HEX = DEC2HEX ## Konwertuje liczbę w postaci dziesiętnej na liczbę w postaci szesnastkowej.
+DEC2OCT = DEC2OCT ## Konwertuje liczbę w postaci dziesiętnej na liczbę w postaci ósemkowej.
+DELTA = DELTA ## Sprawdza, czy dwie wartości są równe.
+ERF = ERF ## Zwraca wartość funkcji błędu.
+ERFC = ERFC ## Zwraca wartość komplementarnej funkcji błędu.
+GESTEP = GESTEP ## Sprawdza, czy liczba jest większa niż wartość progowa.
+HEX2BIN = HEX2BIN ## Konwertuje liczbę w postaci szesnastkowej na liczbę w postaci dwójkowej.
+HEX2DEC = HEX2DEC ## Konwertuje liczbę w postaci szesnastkowej na liczbę w postaci dziesiętnej.
+HEX2OCT = HEX2OCT ## Konwertuje liczbę w postaci szesnastkowej na liczbę w postaci ósemkowej.
+IMABS = IMABS ## Zwraca wartość bezwzględną (moduł) liczby zespolonej.
+IMAGINARY = IMAGINARY ## Zwraca wartość części urojonej liczby zespolonej.
+IMARGUMENT = IMARGUMENT ## Zwraca wartość argumentu liczby zespolonej, przy czym kąt wyrażony jest w radianach.
+IMCONJUGATE = IMCONJUGATE ## Zwraca wartość liczby sprzężonej danej liczby zespolonej.
+IMCOS = IMCOS ## Zwraca wartość cosinusa liczby zespolonej.
+IMDIV = IMDIV ## Zwraca wartość ilorazu dwóch liczb zespolonych.
+IMEXP = IMEXP ## Zwraca postać wykładniczą liczby zespolonej.
+IMLN = IMLN ## Zwraca wartość logarytmu naturalnego liczby zespolonej.
+IMLOG10 = IMLOG10 ## Zwraca wartość logarytmu dziesiętnego liczby zespolonej.
+IMLOG2 = IMLOG2 ## Zwraca wartość logarytmu liczby zespolonej przy podstawie 2.
+IMPOWER = IMPOWER ## Zwraca wartość liczby zespolonej podniesionej do potęgi całkowitej.
+IMPRODUCT = IMPRODUCT ## Zwraca wartość iloczynu liczb zespolonych.
+IMREAL = IMREAL ## Zwraca wartość części rzeczywistej liczby zespolonej.
+IMSIN = IMSIN ## Zwraca wartość sinusa liczby zespolonej.
+IMSQRT = IMSQRT ## Zwraca wartość pierwiastka kwadratowego z liczby zespolonej.
+IMSUB = IMSUB ## Zwraca wartość różnicy dwóch liczb zespolonych.
+IMSUM = IMSUM ## Zwraca wartość sumy liczb zespolonych.
+OCT2BIN = OCT2BIN ## Konwertuje liczbę w postaci ósemkowej na liczbę w postaci dwójkowej.
+OCT2DEC = OCT2DEC ## Konwertuje liczbę w postaci ósemkowej na liczbę w postaci dziesiętnej.
+OCT2HEX = OCT2HEX ## Konwertuje liczbę w postaci ósemkowej na liczbę w postaci szesnastkowej.
+
+
+##
+## Financial functions Funkcje finansowe
+##
+ACCRINT = ACCRINT ## Zwraca narosłe odsetki dla papieru wartościowego z oprocentowaniem okresowym.
+ACCRINTM = ACCRINTM ## Zwraca narosłe odsetki dla papieru wartościowego z oprocentowaniem w terminie wykupu.
+AMORDEGRC = AMORDEGRC ## Zwraca amortyzację dla każdego okresu rozliczeniowego z wykorzystaniem współczynnika amortyzacji.
+AMORLINC = AMORLINC ## Zwraca amortyzację dla każdego okresu rozliczeniowego.
+COUPDAYBS = COUPDAYBS ## Zwraca liczbę dni od początku okresu dywidendy do dnia rozliczeniowego.
+COUPDAYS = COUPDAYS ## Zwraca liczbę dni w okresie dywidendy, z uwzględnieniem dnia rozliczeniowego.
+COUPDAYSNC = COUPDAYSNC ## Zwraca liczbę dni od dnia rozliczeniowego do daty następnego dnia dywidendy.
+COUPNCD = COUPNCD ## Zwraca dzień następnej dywidendy po dniu rozliczeniowym.
+COUPNUM = COUPNUM ## Zwraca liczbę dywidend płatnych między dniem rozliczeniowym a dniem wykupu.
+COUPPCD = COUPPCD ## Zwraca dzień poprzedniej dywidendy przed dniem rozliczeniowym.
+CUMIPMT = CUMIPMT ## Zwraca wartość procentu składanego płatnego między dwoma okresami.
+CUMPRINC = CUMPRINC ## Zwraca wartość kapitału skumulowanego spłaty pożyczki między dwoma okresami.
+DB = DB ## Zwraca amortyzację środka trwałego w danym okresie metodą degresywną z zastosowaniem stałej bazowej.
+DDB = DDB ## Zwraca amortyzację środka trwałego za podany okres metodą degresywną z zastosowaniem podwójnej bazowej lub metodą określoną przez użytkownika.
+DISC = DISC ## Zwraca wartość stopy dyskontowej papieru wartościowego.
+DOLLARDE = DOLLARDE ## Konwertuje cenę w postaci ułamkowej na cenę wyrażoną w postaci dziesiętnej.
+DOLLARFR = DOLLARFR ## Konwertuje cenę wyrażoną w postaci dziesiętnej na cenę wyrażoną w postaci ułamkowej.
+DURATION = DURATION ## Zwraca wartość rocznego przychodu z papieru wartościowego o okresowych wypłatach oprocentowania.
+EFFECT = EFFECT ## Zwraca wartość efektywnej rocznej stopy procentowej.
+FV = FV ## Zwraca przyszłą wartość lokaty.
+FVSCHEDULE = FVSCHEDULE ## Zwraca przyszłą wartość kapitału początkowego wraz z szeregiem procentów składanych.
+INTRATE = INTRATE ## Zwraca wartość stopy procentowej papieru wartościowego całkowicie ulokowanego.
+IPMT = IPMT ## Zwraca wysokość spłaty oprocentowania lokaty za dany okres.
+IRR = IRR ## Zwraca wartość wewnętrznej stopy zwrotu dla serii przepływów gotówkowych.
+ISPMT = ISPMT ## Oblicza wysokość spłaty oprocentowania za dany okres lokaty.
+MDURATION = MDURATION ## Zwraca wartość zmodyfikowanego okresu Macauleya dla papieru wartościowego o założonej wartości nominalnej 100 zł.
+MIRR = MIRR ## Zwraca wartość wewnętrznej stopy zwrotu dla przypadku, gdy dodatnie i ujemne przepływy gotówkowe mają różne stopy.
+NOMINAL = NOMINAL ## Zwraca wysokość nominalnej rocznej stopy procentowej.
+NPER = NPER ## Zwraca liczbę okresów dla lokaty.
+NPV = NPV ## Zwraca wartość bieżącą netto lokaty na podstawie szeregu okresowych przepływów gotówkowych i stopy dyskontowej.
+ODDFPRICE = ODDFPRICE ## Zwraca cenę za 100 zł wartości nominalnej papieru wartościowego z nietypowym pierwszym okresem.
+ODDFYIELD = ODDFYIELD ## Zwraca rentowność papieru wartościowego z nietypowym pierwszym okresem.
+ODDLPRICE = ODDLPRICE ## Zwraca cenę za 100 zł wartości nominalnej papieru wartościowego z nietypowym ostatnim okresem.
+ODDLYIELD = ODDLYIELD ## Zwraca rentowność papieru wartościowego z nietypowym ostatnim okresem.
+PMT = PMT ## Zwraca wartość okresowej płatności raty rocznej.
+PPMT = PPMT ## Zwraca wysokość spłaty kapitału w przypadku lokaty dla danego okresu.
+PRICE = PRICE ## Zwraca cenę za 100 zł wartości nominalnej papieru wartościowego z oprocentowaniem okresowym.
+PRICEDISC = PRICEDISC ## Zwraca cenę za 100 zł wartości nominalnej papieru wartościowego zdyskontowanego.
+PRICEMAT = PRICEMAT ## Zwraca cenę za 100 zł wartości nominalnej papieru wartościowego z oprocentowaniem w terminie wykupu.
+PV = PV ## Zwraca wartość bieżącą lokaty.
+RATE = RATE ## Zwraca wysokość stopy procentowej w okresie raty rocznej.
+RECEIVED = RECEIVED ## Zwraca wartość kapitału otrzymanego przy wykupie papieru wartościowego całkowicie ulokowanego.
+SLN = SLN ## Zwraca amortyzację środka trwałego za jeden okres metodą liniową.
+SYD = SYD ## Zwraca amortyzację środka trwałego za dany okres metodą sumy cyfr lat amortyzacji.
+TBILLEQ = TBILLEQ ## Zwraca rentowność ekwiwalentu obligacji dla bonu skarbowego.
+TBILLPRICE = TBILLPRICE ## Zwraca cenę za 100 zł wartości nominalnej bonu skarbowego.
+TBILLYIELD = TBILLYIELD ## Zwraca rentowność bonu skarbowego.
+VDB = VDB ## Oblicza amortyzację środka trwałego w danym okresie lub jego części metodą degresywną.
+XIRR = XIRR ## Zwraca wartość wewnętrznej stopy zwrotu dla serii rozłożonych w czasie przepływów gotówkowych, niekoniecznie okresowych.
+XNPV = XNPV ## Zwraca wartość bieżącą netto dla serii rozłożonych w czasie przepływów gotówkowych, niekoniecznie okresowych.
+YIELD = YIELD ## Zwraca rentowność papieru wartościowego z oprocentowaniem okresowym.
+YIELDDISC = YIELDDISC ## Zwraca roczną rentowność zdyskontowanego papieru wartościowego, na przykład bonu skarbowego.
+YIELDMAT = YIELDMAT ## Zwraca roczną rentowność papieru wartościowego oprocentowanego przy wykupie.
+
+
+##
+## Information functions Funkcje informacyjne
+##
+CELL = KOMÓRKA ## Zwraca informacje o formacie, położeniu lub zawartości komórki.
+ERROR.TYPE = NR.BŁĘDU ## Zwraca liczbę odpowiadającą typowi błędu.
+INFO = INFO ## Zwraca informację o aktualnym środowisku pracy.
+ISBLANK = CZY.PUSTA ## Zwraca wartość PRAWDA, jeśli wartość jest pusta.
+ISERR = CZY.BŁ ## Zwraca wartość PRAWDA, jeśli wartość jest dowolną wartością błędu, z wyjątkiem #N/D!.
+ISERROR = CZY.BŁĄD ## Zwraca wartość PRAWDA, jeśli wartość jest dowolną wartością błędu.
+ISEVEN = ISEVEN ## Zwraca wartość PRAWDA, jeśli liczba jest parzysta.
+ISLOGICAL = CZY.LOGICZNA ## Zwraca wartość PRAWDA, jeśli wartość jest wartością logiczną.
+ISNA = CZY.BRAK ## Zwraca wartość PRAWDA, jeśli wartość jest wartością błędu #N/D!.
+ISNONTEXT = CZY.NIE.TEKST ## Zwraca wartość PRAWDA, jeśli wartość nie jest tekstem.
+ISNUMBER = CZY.LICZBA ## Zwraca wartość PRAWDA, jeśli wartość jest liczbą.
+ISODD = ISODD ## Zwraca wartość PRAWDA, jeśli liczba jest nieparzysta.
+ISREF = CZY.ADR ## Zwraca wartość PRAWDA, jeśli wartość jest odwołaniem.
+ISTEXT = CZY.TEKST ## Zwraca wartość PRAWDA, jeśli wartość jest tekstem.
+N = L ## Zwraca wartość przekonwertowaną na postać liczbową.
+NA = BRAK ## Zwraca wartość błędu #N/D!.
+TYPE = TYP ## Zwraca liczbę wskazującą typ danych wartości.
+
+
+##
+## Logical functions Funkcje logiczne
+##
+AND = ORAZ ## Zwraca wartość PRAWDA, jeśli wszystkie argumenty mają wartość PRAWDA.
+FALSE = FAŁSZ ## Zwraca wartość logiczną FAŁSZ.
+IF = JEŻELI ## Określa warunek logiczny do sprawdzenia.
+IFERROR = JEŻELI.BŁĄD ## Zwraca określoną wartość, jeśli wynikiem obliczenia formuły jest błąd; w przeciwnym przypadku zwraca wynik formuły.
+NOT = NIE ## Odwraca wartość logiczną argumentu.
+OR = LUB ## Zwraca wartość PRAWDA, jeśli co najmniej jeden z argumentów ma wartość PRAWDA.
+TRUE = PRAWDA ## Zwraca wartość logiczną PRAWDA.
+
+
+##
+## Lookup and reference functions Funkcje wyszukiwania i odwołań
+##
+ADDRESS = ADRES ## Zwraca odwołanie do jednej komórki w arkuszu jako wartość tekstową.
+AREAS = OBSZARY ## Zwraca liczbę obszarów występujących w odwołaniu.
+CHOOSE = WYBIERZ ## Wybiera wartość z listy wartości.
+COLUMN = NR.KOLUMNY ## Zwraca numer kolumny z odwołania.
+COLUMNS = LICZBA.KOLUMN ## Zwraca liczbę kolumn dla danego odwołania.
+HLOOKUP = WYSZUKAJ.POZIOMO ## Przegląda górny wiersz tablicy i zwraca wartość wskazanej komórki.
+HYPERLINK = HIPERŁĄCZE ## Tworzy skrót lub skok, który pozwala otwierać dokument przechowywany na serwerze sieciowym, w sieci intranet lub w Internecie.
+INDEX = INDEKS ## Używa indeksu do wybierania wartości z odwołania lub tablicy.
+INDIRECT = ADR.POŚR ## Zwraca odwołanie określone przez wartość tekstową.
+LOOKUP = WYSZUKAJ ## Wyszukuje wartości w wektorze lub tablicy.
+MATCH = PODAJ.POZYCJĘ ## Wyszukuje wartości w odwołaniu lub w tablicy.
+OFFSET = PRZESUNIĘCIE ## Zwraca adres przesunięty od danego odwołania.
+ROW = WIERSZ ## Zwraca numer wiersza odwołania.
+ROWS = ILE.WIERSZY ## Zwraca liczbę wierszy dla danego odwołania.
+RTD = RTD ## Pobiera dane w czasie rzeczywistym z programu obsługującego automatyzację COM (Automatyzacja: Sposób pracy z obiektami aplikacji pochodzącymi z innej aplikacji lub narzędzia projektowania. Nazywana wcześniej Automatyzacją OLE, Automatyzacja jest standardem przemysłowym i funkcją obiektowego modelu składników (COM, Component Object Model).).
+TRANSPOSE = TRANSPONUJ ## Zwraca transponowaną tablicę.
+VLOOKUP = WYSZUKAJ.PIONOWO ## Przeszukuje pierwszą kolumnę tablicy i przechodzi wzdłuż wiersza, aby zwrócić wartość komórki.
+
+
+##
+## Math and trigonometry functions Funkcje matematyczne i trygonometryczne
+##
+ABS = MODUŁ.LICZBY ## Zwraca wartość absolutną liczby.
+ACOS = ACOS ## Zwraca arcus cosinus liczby.
+ACOSH = ACOSH ## Zwraca arcus cosinus hiperboliczny liczby.
+ASIN = ASIN ## Zwraca arcus sinus liczby.
+ASINH = ASINH ## Zwraca arcus sinus hiperboliczny liczby.
+ATAN = ATAN ## Zwraca arcus tangens liczby.
+ATAN2 = ATAN2 ## Zwraca arcus tangens liczby na podstawie współrzędnych x i y.
+ATANH = ATANH ## Zwraca arcus tangens hiperboliczny liczby.
+CEILING = ZAOKR.W.GÓRĘ ## Zaokrągla liczbę do najbliższej liczby całkowitej lub do najbliższej wielokrotności dokładności.
+COMBIN = KOMBINACJE ## Zwraca liczbę kombinacji dla danej liczby obiektów.
+COS = COS ## Zwraca cosinus liczby.
+COSH = COSH ## Zwraca cosinus hiperboliczny liczby.
+DEGREES = STOPNIE ## Konwertuje radiany na stopnie.
+EVEN = ZAOKR.DO.PARZ ## Zaokrągla liczbę w górę do najbliższej liczby parzystej.
+EXP = EXP ## Zwraca wartość liczby e podniesionej do potęgi określonej przez podaną liczbę.
+FACT = SILNIA ## Zwraca silnię liczby.
+FACTDOUBLE = FACTDOUBLE ## Zwraca podwójną silnię liczby.
+FLOOR = ZAOKR.W.DÓŁ ## Zaokrągla liczbę w dół, w kierunku zera.
+GCD = GCD ## Zwraca największy wspólny dzielnik.
+INT = ZAOKR.DO.CAŁK ## Zaokrągla liczbę w dół do najbliższej liczby całkowitej.
+LCM = LCM ## Zwraca najmniejszą wspólną wielokrotność.
+LN = LN ## Zwraca logarytm naturalny podanej liczby.
+LOG = LOG ## Zwraca logarytm danej liczby przy zadanej podstawie.
+LOG10 = LOG10 ## Zwraca logarytm dziesiętny liczby.
+MDETERM = WYZNACZNIK.MACIERZY ## Zwraca wyznacznik macierzy tablicy.
+MINVERSE = MACIERZ.ODW ## Zwraca odwrotność macierzy tablicy.
+MMULT = MACIERZ.ILOCZYN ## Zwraca iloczyn macierzy dwóch tablic.
+MOD = MOD ## Zwraca resztę z dzielenia.
+MROUND = MROUND ## Zwraca liczbę zaokrągloną do żądanej wielokrotności.
+MULTINOMIAL = MULTINOMIAL ## Zwraca wielomian dla zbioru liczb.
+ODD = ZAOKR.DO.NPARZ ## Zaokrągla liczbę w górę do najbliższej liczby nieparzystej.
+PI = PI ## Zwraca wartość liczby Pi.
+POWER = POTĘGA ## Zwraca liczbę podniesioną do potęgi.
+PRODUCT = ILOCZYN ## Mnoży argumenty.
+QUOTIENT = QUOTIENT ## Zwraca iloraz (całkowity).
+RADIANS = RADIANY ## Konwertuje stopnie na radiany.
+RAND = LOS ## Zwraca liczbę pseudolosową z zakresu od 0 do 1.
+RANDBETWEEN = RANDBETWEEN ## Zwraca liczbę pseudolosową z zakresu określonego przez podane argumenty.
+ROMAN = RZYMSKIE ## Konwertuje liczbę arabską na rzymską jako tekst.
+ROUND = ZAOKR ## Zaokrągla liczbę do określonej liczby cyfr.
+ROUNDDOWN = ZAOKR.DÓŁ ## Zaokrągla liczbę w dół, w kierunku zera.
+ROUNDUP = ZAOKR.GÓRA ## Zaokrągla liczbę w górę, w kierunku od zera.
+SERIESSUM = SERIESSUM ## Zwraca sumę szeregu potęgowego na podstawie wzoru.
+SIGN = ZNAK.LICZBY ## Zwraca znak liczby.
+SIN = SIN ## Zwraca sinus danego kąta.
+SINH = SINH ## Zwraca sinus hiperboliczny liczby.
+SQRT = PIERWIASTEK ## Zwraca dodatni pierwiastek kwadratowy.
+SQRTPI = SQRTPI ## Zwraca pierwiastek kwadratowy iloczynu (liczba * Pi).
+SUBTOTAL = SUMY.POŚREDNIE ## Zwraca sumę częściową listy lub bazy danych.
+SUM = SUMA ## Dodaje argumenty.
+SUMIF = SUMA.JEŻELI ## Dodaje komórki określone przez podane kryterium.
+SUMIFS = SUMA.WARUNKÓW ## Dodaje komórki w zakresie, które spełniają wiele kryteriów.
+SUMPRODUCT = SUMA.ILOCZYNÓW ## Zwraca sumę iloczynów odpowiednich elementów tablicy.
+SUMSQ = SUMA.KWADRATÓW ## Zwraca sumę kwadratów argumentów.
+SUMX2MY2 = SUMA.X2.M.Y2 ## Zwraca sumę różnic kwadratów odpowiednich wartości w dwóch tablicach.
+SUMX2PY2 = SUMA.X2.P.Y2 ## Zwraca sumę sum kwadratów odpowiednich wartości w dwóch tablicach.
+SUMXMY2 = SUMA.XMY.2 ## Zwraca sumę kwadratów różnic odpowiednich wartości w dwóch tablicach.
+TAN = TAN ## Zwraca tangens liczby.
+TANH = TANH ## Zwraca tangens hiperboliczny liczby.
+TRUNC = LICZBA.CAŁK ## Przycina liczbę do wartości całkowitej.
+
+
+##
+## Statistical functions Funkcje statystyczne
+##
+AVEDEV = ODCH.ŚREDNIE ## Zwraca średnią wartość odchyleń absolutnych punktów danych od ich wartości średniej.
+AVERAGE = ŚREDNIA ## Zwraca wartość średnią argumentów.
+AVERAGEA = ŚREDNIA.A ## Zwraca wartość średnią argumentów, z uwzględnieniem liczb, tekstów i wartości logicznych.
+AVERAGEIF = ŚREDNIA.JEŻELI ## Zwraca średnią (średnią arytmetyczną) wszystkich komórek w zakresie, które spełniają podane kryteria.
+AVERAGEIFS = ŚREDNIA.WARUNKÓW ## Zwraca średnią (średnią arytmetyczną) wszystkich komórek, które spełniają jedno lub więcej kryteriów.
+BETADIST = ROZKŁAD.BETA ## Zwraca skumulowaną funkcję gęstości prawdopodobieństwa beta.
+BETAINV = ROZKŁAD.BETA.ODW ## Zwraca odwrotność skumulowanej funkcji gęstości prawdopodobieństwa beta.
+BINOMDIST = ROZKŁAD.DWUM ## Zwraca pojedynczy składnik dwumianowego rozkładu prawdopodobieństwa.
+CHIDIST = ROZKŁAD.CHI ## Zwraca wartość jednostronnego prawdopodobieństwa rozkładu chi-kwadrat.
+CHIINV = ROZKŁAD.CHI.ODW ## Zwraca odwrotność wartości jednostronnego prawdopodobieństwa rozkładu chi-kwadrat.
+CHITEST = TEST.CHI ## Zwraca test niezależności.
+CONFIDENCE = UFNOŚĆ ## Zwraca interwał ufności dla średniej populacji.
+CORREL = WSP.KORELACJI ## Zwraca współczynnik korelacji dwóch zbiorów danych.
+COUNT = ILE.LICZB ## Zlicza liczby znajdujące się na liście argumentów.
+COUNTA = ILE.NIEPUSTYCH ## Zlicza wartości znajdujące się na liście argumentów.
+COUNTBLANK = LICZ.PUSTE ## Zwraca liczbę pustych komórek w pewnym zakresie.
+COUNTIF = LICZ.JEŻELI ## Zlicza komórki wewnątrz zakresu, które spełniają podane kryteria.
+COUNTIFS = LICZ.WARUNKI ## Zlicza komórki wewnątrz zakresu, które spełniają wiele kryteriów.
+COVAR = KOWARIANCJA ## Zwraca kowariancję, czyli średnią wartość iloczynów odpowiednich odchyleń.
+CRITBINOM = PRÓG.ROZKŁAD.DWUM ## Zwraca najmniejszą wartość, dla której skumulowany rozkład dwumianowy jest mniejszy niż wartość kryterium lub równy jej.
+DEVSQ = ODCH.KWADRATOWE ## Zwraca sumę kwadratów odchyleń.
+EXPONDIST = ROZKŁAD.EXP ## Zwraca rozkład wykładniczy.
+FDIST = ROZKŁAD.F ## Zwraca rozkład prawdopodobieństwa F.
+FINV = ROZKŁAD.F.ODW ## Zwraca odwrotność rozkładu prawdopodobieństwa F.
+FISHER = ROZKŁAD.FISHER ## Zwraca transformację Fishera.
+FISHERINV = ROZKŁAD.FISHER.ODW ## Zwraca odwrotność transformacji Fishera.
+FORECAST = REGLINX ## Zwraca wartość trendu liniowego.
+FREQUENCY = CZĘSTOŚĆ ## Zwraca rozkład częstotliwości jako tablicę pionową.
+FTEST = TEST.F ## Zwraca wynik testu F.
+GAMMADIST = ROZKŁAD.GAMMA ## Zwraca rozkład gamma.
+GAMMAINV = ROZKŁAD.GAMMA.ODW ## Zwraca odwrotność skumulowanego rozkładu gamma.
+GAMMALN = ROZKŁAD.LIN.GAMMA ## Zwraca logarytm naturalny funkcji gamma, Γ(x).
+GEOMEAN = ŚREDNIA.GEOMETRYCZNA ## Zwraca średnią geometryczną.
+GROWTH = REGEXPW ## Zwraca wartości trendu wykładniczego.
+HARMEAN = ŚREDNIA.HARMONICZNA ## Zwraca średnią harmoniczną.
+HYPGEOMDIST = ROZKŁAD.HIPERGEOM ## Zwraca rozkład hipergeometryczny.
+INTERCEPT = ODCIĘTA ## Zwraca punkt przecięcia osi pionowej z linią regresji liniowej.
+KURT = KURTOZA ## Zwraca kurtozę zbioru danych.
+LARGE = MAX.K ## Zwraca k-tą największą wartość ze zbioru danych.
+LINEST = REGLINP ## Zwraca parametry trendu liniowego.
+LOGEST = REGEXPP ## Zwraca parametry trendu wykładniczego.
+LOGINV = ROZKŁAD.LOG.ODW ## Zwraca odwrotność rozkładu logarytmu naturalnego.
+LOGNORMDIST = ROZKŁAD.LOG ## Zwraca skumulowany rozkład logarytmu naturalnego.
+MAX = MAX ## Zwraca maksymalną wartość listy argumentów.
+MAXA = MAX.A ## Zwraca maksymalną wartość listy argumentów, z uwzględnieniem liczb, tekstów i wartości logicznych.
+MEDIAN = MEDIANA ## Zwraca medianę podanych liczb.
+MIN = MIN ## Zwraca minimalną wartość listy argumentów.
+MINA = MIN.A ## Zwraca najmniejszą wartość listy argumentów, z uwzględnieniem liczb, tekstów i wartości logicznych.
+MODE = WYST.NAJCZĘŚCIEJ ## Zwraca wartość najczęściej występującą w zbiorze danych.
+NEGBINOMDIST = ROZKŁAD.DWUM.PRZEC ## Zwraca ujemny rozkład dwumianowy.
+NORMDIST = ROZKŁAD.NORMALNY ## Zwraca rozkład normalny skumulowany.
+NORMINV = ROZKŁAD.NORMALNY.ODW ## Zwraca odwrotność rozkładu normalnego skumulowanego.
+NORMSDIST = ROZKŁAD.NORMALNY.S ## Zwraca standardowy rozkład normalny skumulowany.
+NORMSINV = ROZKŁAD.NORMALNY.S.ODW ## Zwraca odwrotność standardowego rozkładu normalnego skumulowanego.
+PEARSON = PEARSON ## Zwraca współczynnik korelacji momentu iloczynu Pearsona.
+PERCENTILE = PERCENTYL ## Wyznacza k-ty percentyl wartości w zakresie.
+PERCENTRANK = PROCENT.POZYCJA ## Zwraca procentową pozycję wartości w zbiorze danych.
+PERMUT = PERMUTACJE ## Zwraca liczbę permutacji dla danej liczby obiektów.
+POISSON = ROZKŁAD.POISSON ## Zwraca rozkład Poissona.
+PROB = PRAWDPD ## Zwraca prawdopodobieństwo, że wartości w zakresie leżą pomiędzy dwiema granicami.
+QUARTILE = KWARTYL ## Wyznacza kwartyl zbioru danych.
+RANK = POZYCJA ## Zwraca pozycję liczby na liście liczb.
+RSQ = R.KWADRAT ## Zwraca kwadrat współczynnika korelacji momentu iloczynu Pearsona.
+SKEW = SKOŚNOŚĆ ## Zwraca skośność rozkładu.
+SLOPE = NACHYLENIE ## Zwraca nachylenie linii regresji liniowej.
+SMALL = MIN.K ## Zwraca k-tą najmniejszą wartość ze zbioru danych.
+STANDARDIZE = NORMALIZUJ ## Zwraca wartość znormalizowaną.
+STDEV = ODCH.STANDARDOWE ## Szacuje odchylenie standardowe na podstawie próbki.
+STDEVA = ODCH.STANDARDOWE.A ## Szacuje odchylenie standardowe na podstawie próbki, z uwzględnieniem liczb, tekstów i wartości logicznych.
+STDEVP = ODCH.STANDARD.POPUL ## Oblicza odchylenie standardowe na podstawie całej populacji.
+STDEVPA = ODCH.STANDARD.POPUL.A ## Oblicza odchylenie standardowe na podstawie całej populacji, z uwzględnieniem liczb, teksów i wartości logicznych.
+STEYX = REGBŁSTD ## Zwraca błąd standardowy przewidzianej wartości y dla każdej wartości x w regresji.
+TDIST = ROZKŁAD.T ## Zwraca rozkład t-Studenta.
+TINV = ROZKŁAD.T.ODW ## Zwraca odwrotność rozkładu t-Studenta.
+TREND = REGLINW ## Zwraca wartości trendu liniowego.
+TRIMMEAN = ŚREDNIA.WEWN ## Zwraca średnią wartość dla wnętrza zbioru danych.
+TTEST = TEST.T ## Zwraca prawdopodobieństwo związane z testem t-Studenta.
+VAR = WARIANCJA ## Szacuje wariancję na podstawie próbki.
+VARA = WARIANCJA.A ## Szacuje wariancję na podstawie próbki, z uwzględnieniem liczb, tekstów i wartości logicznych.
+VARP = WARIANCJA.POPUL ## Oblicza wariancję na podstawie całej populacji.
+VARPA = WARIANCJA.POPUL.A ## Oblicza wariancję na podstawie całej populacji, z uwzględnieniem liczb, tekstów i wartości logicznych.
+WEIBULL = ROZKŁAD.WEIBULL ## Zwraca rozkład Weibulla.
+ZTEST = TEST.Z ## Zwraca wartość jednostronnego prawdopodobieństwa testu z.
+
+
+##
+## Text functions Funkcje tekstowe
+##
+ASC = ASC ## Zamienia litery angielskie lub katakana o pełnej szerokości (dwubajtowe) w ciągu znaków na znaki o szerokości połówkowej (jednobajtowe).
+BAHTTEXT = BAHTTEXT ## Konwertuje liczbę na tekst, stosując format walutowy ß (baht).
+CHAR = ZNAK ## Zwraca znak o podanym numerze kodu.
+CLEAN = OCZYŚĆ ## Usuwa z tekstu wszystkie znaki, które nie mogą być drukowane.
+CODE = KOD ## Zwraca kod numeryczny pierwszego znaku w ciągu tekstowym.
+CONCATENATE = ZŁĄCZ.TEKSTY ## Łączy kilka oddzielnych tekstów w jeden tekst.
+DOLLAR = KWOTA ## Konwertuje liczbę na tekst, stosując format walutowy $ (dolar).
+EXACT = PORÓWNAJ ## Sprawdza identyczność dwóch wartości tekstowych.
+FIND = ZNAJDŹ ## Znajduje jedną wartość tekstową wewnątrz innej (z uwzględnieniem wielkich i małych liter).
+FINDB = ZNAJDŹB ## Znajduje jedną wartość tekstową wewnątrz innej (z uwzględnieniem wielkich i małych liter).
+FIXED = ZAOKR.DO.TEKST ## Formatuje liczbę jako tekst przy stałej liczbie miejsc dziesiętnych.
+JIS = JIS ## Zmienia litery angielskie lub katakana o szerokości połówkowej (jednobajtowe) w ciągu znaków na znaki o pełnej szerokości (dwubajtowe).
+LEFT = LEWY ## Zwraca skrajne lewe znaki z wartości tekstowej.
+LEFTB = LEWYB ## Zwraca skrajne lewe znaki z wartości tekstowej.
+LEN = DŁ ## Zwraca liczbę znaków ciągu tekstowego.
+LENB = DŁ.B ## Zwraca liczbę znaków ciągu tekstowego.
+LOWER = LITERY.MAŁE ## Konwertuje wielkie litery tekstu na małe litery.
+MID = FRAGMENT.TEKSTU ## Zwraca określoną liczbę znaków z ciągu tekstowego, zaczynając od zadanej pozycji.
+MIDB = FRAGMENT.TEKSTU.B ## Zwraca określoną liczbę znaków z ciągu tekstowego, zaczynając od zadanej pozycji.
+PHONETIC = PHONETIC ## Wybiera znaki fonetyczne (furigana) z ciągu tekstowego.
+PROPER = Z.WIELKIEJ.LITERY ## Zastępuje pierwszą literę każdego wyrazu tekstu wielką literą.
+REPLACE = ZASTĄP ## Zastępuje znaki w tekście.
+REPLACEB = ZASTĄP.B ## Zastępuje znaki w tekście.
+REPT = POWT ## Powiela tekst daną liczbę razy.
+RIGHT = PRAWY ## Zwraca skrajne prawe znaki z wartości tekstowej.
+RIGHTB = PRAWYB ## Zwraca skrajne prawe znaki z wartości tekstowej.
+SEARCH = SZUKAJ.TEKST ## Wyszukuje jedną wartość tekstową wewnątrz innej (bez uwzględniania wielkości liter).
+SEARCHB = SZUKAJ.TEKST.B ## Wyszukuje jedną wartość tekstową wewnątrz innej (bez uwzględniania wielkości liter).
+SUBSTITUTE = PODSTAW ## Podstawia nowy tekst w miejsce poprzedniego tekstu w ciągu tekstowym.
+T = T ## Konwertuje argumenty na tekst.
+TEXT = TEKST ## Formatuje liczbę i konwertuje ją na tekst.
+TRIM = USUŃ.ZBĘDNE.ODSTĘPY ## Usuwa spacje z tekstu.
+UPPER = LITERY.WIELKIE ## Konwertuje znaki tekstu na wielkie litery.
+VALUE = WARTOŚĆ ## Konwertuje argument tekstowy na liczbę.
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config
new file mode 100755
index 0000000..904f99f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config
@@ -0,0 +1,24 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = R$
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #NULO!
+DIV0 = #DIV/0!
+VALUE = #VALOR!
+REF = #REF!
+NAME = #NOME?
+NUM = #NÚM!
+NA = #N/D
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions
new file mode 100755
index 0000000..a062a7f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions
@@ -0,0 +1,408 @@
+##
+## Add-in and Automation functions Funções Suplemento e Automação
+##
+GETPIVOTDATA = INFODADOSTABELADINÂMICA ## Retorna os dados armazenados em um relatório de tabela dinâmica
+
+
+##
+## Cube functions Funções de Cubo
+##
+CUBEKPIMEMBER = MEMBROKPICUBO ## Retorna o nome de um KPI (indicador de desempenho-chave), uma propriedade e uma medida e exibe o nome e a propriedade na célula. Um KPI é uma medida quantificável, como o lucro bruto mensal ou a rotatividade trimestral dos funcionários, usada para monitorar o desempenho de uma organização.
+CUBEMEMBER = MEMBROCUBO ## Retorna um membro ou tupla em uma hierarquia de cubo. Use para validar se o membro ou tupla existe no cubo.
+CUBEMEMBERPROPERTY = PROPRIEDADEMEMBROCUBO ## Retorna o valor da propriedade de um membro no cubo. Usada para validar a existência do nome do membro no cubo e para retornar a propriedade especificada para esse membro.
+CUBERANKEDMEMBER = MEMBROCLASSIFICADOCUBO ## Retorna o enésimo membro, ou o membro ordenado, em um conjunto. Use para retornar um ou mais elementos em um conjunto, assim como o melhor vendedor ou os dez melhores alunos.
+CUBESET = CONJUNTOCUBO ## Define um conjunto calculado de membros ou tuplas enviando uma expressão do conjunto para o cubo no servidor, que cria o conjunto e o retorna para o Microsoft Office Excel.
+CUBESETCOUNT = CONTAGEMCONJUNTOCUBO ## Retorna o número de itens em um conjunto.
+CUBEVALUE = VALORCUBO ## Retorna um valor agregado de um cubo.
+
+
+##
+## Database functions Funções de banco de dados
+##
+DAVERAGE = BDMÉDIA ## Retorna a média das entradas selecionadas de um banco de dados
+DCOUNT = BDCONTAR ## Conta as células que contêm números em um banco de dados
+DCOUNTA = BDCONTARA ## Conta células não vazias em um banco de dados
+DGET = BDEXTRAIR ## Extrai de um banco de dados um único registro que corresponde a um critério específico
+DMAX = BDMÁX ## Retorna o valor máximo de entradas selecionadas de um banco de dados
+DMIN = BDMÍN ## Retorna o valor mínimo de entradas selecionadas de um banco de dados
+DPRODUCT = BDMULTIPL ## Multiplica os valores em um campo específico de registros que correspondem ao critério em um banco de dados
+DSTDEV = BDEST ## Estima o desvio padrão com base em uma amostra de entradas selecionadas de um banco de dados
+DSTDEVP = BDDESVPA ## Calcula o desvio padrão com base na população inteira de entradas selecionadas de um banco de dados
+DSUM = BDSOMA ## Adiciona os números à coluna de campos de registros do banco de dados que correspondem ao critério
+DVAR = BDVAREST ## Estima a variância com base em uma amostra de entradas selecionadas de um banco de dados
+DVARP = BDVARP ## Calcula a variância com base na população inteira de entradas selecionadas de um banco de dados
+
+
+##
+## Date and time functions Funções de data e hora
+##
+DATE = DATA ## Retorna o número de série de uma data específica
+DATEVALUE = DATA.VALOR ## Converte uma data na forma de texto para um número de série
+DAY = DIA ## Converte um número de série em um dia do mês
+DAYS360 = DIAS360 ## Calcula o número de dias entre duas datas com base em um ano de 360 dias
+EDATE = DATAM ## Retorna o número de série da data que é o número indicado de meses antes ou depois da data inicial
+EOMONTH = FIMMÊS ## Retorna o número de série do último dia do mês antes ou depois de um número especificado de meses
+HOUR = HORA ## Converte um número de série em uma hora
+MINUTE = MINUTO ## Converte um número de série em um minuto
+MONTH = MÊS ## Converte um número de série em um mês
+NETWORKDAYS = DIATRABALHOTOTAL ## Retorna o número de dias úteis inteiros entre duas datas
+NOW = AGORA ## Retorna o número de série seqüencial da data e hora atuais
+SECOND = SEGUNDO ## Converte um número de série em um segundo
+TIME = HORA ## Retorna o número de série de uma hora específica
+TIMEVALUE = VALOR.TEMPO ## Converte um horário na forma de texto para um número de série
+TODAY = HOJE ## Retorna o número de série da data de hoje
+WEEKDAY = DIA.DA.SEMANA ## Converte um número de série em um dia da semana
+WEEKNUM = NÚMSEMANA ## Converte um número de série em um número que representa onde a semana cai numericamente em um ano
+WORKDAY = DIATRABALHO ## Retorna o número de série da data antes ou depois de um número específico de dias úteis
+YEAR = ANO ## Converte um número de série em um ano
+YEARFRAC = FRAÇÃOANO ## Retorna a fração do ano que representa o número de dias entre data_inicial e data_final
+
+
+##
+## Engineering functions Funções de engenharia
+##
+BESSELI = BESSELI ## Retorna a função de Bessel In(x) modificada
+BESSELJ = BESSELJ ## Retorna a função de Bessel Jn(x)
+BESSELK = BESSELK ## Retorna a função de Bessel Kn(x) modificada
+BESSELY = BESSELY ## Retorna a função de Bessel Yn(x)
+BIN2DEC = BIN2DEC ## Converte um número binário em decimal
+BIN2HEX = BIN2HEX ## Converte um número binário em hexadecimal
+BIN2OCT = BIN2OCT ## Converte um número binário em octal
+COMPLEX = COMPLEX ## Converte coeficientes reais e imaginários e um número complexo
+CONVERT = CONVERTER ## Converte um número de um sistema de medida para outro
+DEC2BIN = DECABIN ## Converte um número decimal em binário
+DEC2HEX = DECAHEX ## Converte um número decimal em hexadecimal
+DEC2OCT = DECAOCT ## Converte um número decimal em octal
+DELTA = DELTA ## Testa se dois valores são iguais
+ERF = FUNERRO ## Retorna a função de erro
+ERFC = FUNERROCOMPL ## Retorna a função de erro complementar
+GESTEP = DEGRAU ## Testa se um número é maior do que um valor limite
+HEX2BIN = HEXABIN ## Converte um número hexadecimal em binário
+HEX2DEC = HEXADEC ## Converte um número hexadecimal em decimal
+HEX2OCT = HEXAOCT ## Converte um número hexadecimal em octal
+IMABS = IMABS ## Retorna o valor absoluto (módulo) de um número complexo
+IMAGINARY = IMAGINÁRIO ## Retorna o coeficiente imaginário de um número complexo
+IMARGUMENT = IMARG ## Retorna o argumento teta, um ângulo expresso em radianos
+IMCONJUGATE = IMCONJ ## Retorna o conjugado complexo de um número complexo
+IMCOS = IMCOS ## Retorna o cosseno de um número complexo
+IMDIV = IMDIV ## Retorna o quociente de dois números complexos
+IMEXP = IMEXP ## Retorna o exponencial de um número complexo
+IMLN = IMLN ## Retorna o logaritmo natural de um número complexo
+IMLOG10 = IMLOG10 ## Retorna o logaritmo de base 10 de um número complexo
+IMLOG2 = IMLOG2 ## Retorna o logaritmo de base 2 de um número complexo
+IMPOWER = IMPOT ## Retorna um número complexo elevado a uma potência inteira
+IMPRODUCT = IMPROD ## Retorna o produto de números complexos
+IMREAL = IMREAL ## Retorna o coeficiente real de um número complexo
+IMSIN = IMSENO ## Retorna o seno de um número complexo
+IMSQRT = IMRAIZ ## Retorna a raiz quadrada de um número complexo
+IMSUB = IMSUBTR ## Retorna a diferença entre dois números complexos
+IMSUM = IMSOMA ## Retorna a soma de números complexos
+OCT2BIN = OCTABIN ## Converte um número octal em binário
+OCT2DEC = OCTADEC ## Converte um número octal em decimal
+OCT2HEX = OCTAHEX ## Converte um número octal em hexadecimal
+
+
+##
+## Financial functions Funções financeiras
+##
+ACCRINT = JUROSACUM ## Retorna a taxa de juros acumulados de um título que paga uma taxa periódica de juros
+ACCRINTM = JUROSACUMV ## Retorna os juros acumulados de um título que paga juros no vencimento
+AMORDEGRC = AMORDEGRC ## Retorna a depreciação para cada período contábil usando o coeficiente de depreciação
+AMORLINC = AMORLINC ## Retorna a depreciação para cada período contábil
+COUPDAYBS = CUPDIASINLIQ ## Retorna o número de dias do início do período de cupom até a data de liquidação
+COUPDAYS = CUPDIAS ## Retorna o número de dias no período de cupom que contém a data de quitação
+COUPDAYSNC = CUPDIASPRÓX ## Retorna o número de dias da data de liquidação até a data do próximo cupom
+COUPNCD = CUPDATAPRÓX ## Retorna a próxima data de cupom após a data de quitação
+COUPNUM = CUPNÚM ## Retorna o número de cupons pagáveis entre as datas de quitação e vencimento
+COUPPCD = CUPDATAANT ## Retorna a data de cupom anterior à data de quitação
+CUMIPMT = PGTOJURACUM ## Retorna os juros acumulados pagos entre dois períodos
+CUMPRINC = PGTOCAPACUM ## Retorna o capital acumulado pago sobre um empréstimo entre dois períodos
+DB = BD ## Retorna a depreciação de um ativo para um período especificado, usando o método de balanço de declínio fixo
+DDB = BDD ## Retorna a depreciação de um ativo com relação a um período especificado usando o método de saldos decrescentes duplos ou qualquer outro método especificado por você
+DISC = DESC ## Retorna a taxa de desconto de um título
+DOLLARDE = MOEDADEC ## Converte um preço em formato de moeda, na forma fracionária, em um preço na forma decimal
+DOLLARFR = MOEDAFRA ## Converte um preço, apresentado na forma decimal, em um preço apresentado na forma fracionária
+DURATION = DURAÇÃO ## Retorna a duração anual de um título com pagamentos de juros periódicos
+EFFECT = EFETIVA ## Retorna a taxa de juros anual efetiva
+FV = VF ## Retorna o valor futuro de um investimento
+FVSCHEDULE = VFPLANO ## Retorna o valor futuro de um capital inicial após a aplicação de uma série de taxas de juros compostas
+INTRATE = TAXAJUROS ## Retorna a taxa de juros de um título totalmente investido
+IPMT = IPGTO ## Retorna o pagamento de juros para um investimento em um determinado período
+IRR = TIR ## Retorna a taxa interna de retorno de uma série de fluxos de caixa
+ISPMT = ÉPGTO ## Calcula os juros pagos durante um período específico de um investimento
+MDURATION = MDURAÇÃO ## Retorna a duração de Macauley modificada para um título com um valor de paridade equivalente a R$ 100
+MIRR = MTIR ## Calcula a taxa interna de retorno em que fluxos de caixa positivos e negativos são financiados com diferentes taxas
+NOMINAL = NOMINAL ## Retorna a taxa de juros nominal anual
+NPER = NPER ## Retorna o número de períodos de um investimento
+NPV = VPL ## Retorna o valor líquido atual de um investimento com base em uma série de fluxos de caixa periódicos e em uma taxa de desconto
+ODDFPRICE = PREÇOPRIMINC ## Retorna o preço por R$ 100 de valor nominal de um título com um primeiro período indefinido
+ODDFYIELD = LUCROPRIMINC ## Retorna o rendimento de um título com um primeiro período indefinido
+ODDLPRICE = PREÇOÚLTINC ## Retorna o preço por R$ 100 de valor nominal de um título com um último período de cupom indefinido
+ODDLYIELD = LUCROÚLTINC ## Retorna o rendimento de um título com um último período indefinido
+PMT = PGTO ## Retorna o pagamento periódico de uma anuidade
+PPMT = PPGTO ## Retorna o pagamento de capital para determinado período de investimento
+PRICE = PREÇO ## Retorna a preço por R$ 100,00 de valor nominal de um título que paga juros periódicos
+PRICEDISC = PREÇODESC ## Retorna o preço por R$ 100,00 de valor nominal de um título descontado
+PRICEMAT = PREÇOVENC ## Retorna o preço por R$ 100,00 de valor nominal de um título que paga juros no vencimento
+PV = VP ## Retorna o valor presente de um investimento
+RATE = TAXA ## Retorna a taxa de juros por período de uma anuidade
+RECEIVED = RECEBER ## Retorna a quantia recebida no vencimento de um título totalmente investido
+SLN = DPD ## Retorna a depreciação em linha reta de um ativo durante um período
+SYD = SDA ## Retorna a depreciação dos dígitos da soma dos anos de um ativo para um período especificado
+TBILLEQ = OTN ## Retorna o rendimento de um título equivalente a uma obrigação do Tesouro
+TBILLPRICE = OTNVALOR ## Retorna o preço por R$ 100,00 de valor nominal de uma obrigação do Tesouro
+TBILLYIELD = OTNLUCRO ## Retorna o rendimento de uma obrigação do Tesouro
+VDB = BDV ## Retorna a depreciação de um ativo para um período especificado ou parcial usando um método de balanço declinante
+XIRR = XTIR ## Fornece a taxa interna de retorno para um programa de fluxos de caixa que não é necessariamente periódico
+XNPV = XVPL ## Retorna o valor presente líquido de um programa de fluxos de caixa que não é necessariamente periódico
+YIELD = LUCRO ## Retorna o lucro de um título que paga juros periódicos
+YIELDDISC = LUCRODESC ## Retorna o rendimento anual de um título descontado. Por exemplo, uma obrigação do Tesouro
+YIELDMAT = LUCROVENC ## Retorna o lucro anual de um título que paga juros no vencimento
+
+
+##
+## Information functions Funções de informação
+##
+CELL = CÉL ## Retorna informações sobre formatação, localização ou conteúdo de uma célula
+ERROR.TYPE = TIPO.ERRO ## Retorna um número correspondente a um tipo de erro
+INFO = INFORMAÇÃO ## Retorna informações sobre o ambiente operacional atual
+ISBLANK = ÉCÉL.VAZIA ## Retorna VERDADEIRO se o valor for vazio
+ISERR = ÉERRO ## Retorna VERDADEIRO se o valor for um valor de erro diferente de #N/D
+ISERROR = ÉERROS ## Retorna VERDADEIRO se o valor for um valor de erro
+ISEVEN = ÉPAR ## Retorna VERDADEIRO se o número for par
+ISLOGICAL = ÉLÓGICO ## Retorna VERDADEIRO se o valor for um valor lógico
+ISNA = É.NÃO.DISP ## Retorna VERDADEIRO se o valor for o valor de erro #N/D
+ISNONTEXT = É.NÃO.TEXTO ## Retorna VERDADEIRO se o valor for diferente de texto
+ISNUMBER = ÉNÚM ## Retorna VERDADEIRO se o valor for um número
+ISODD = ÉIMPAR ## Retorna VERDADEIRO se o número for ímpar
+ISREF = ÉREF ## Retorna VERDADEIRO se o valor for uma referência
+ISTEXT = ÉTEXTO ## Retorna VERDADEIRO se o valor for texto
+N = N ## Retorna um valor convertido em um número
+NA = NÃO.DISP ## Retorna o valor de erro #N/D
+TYPE = TIPO ## Retorna um número indicando o tipo de dados de um valor
+
+
+##
+## Logical functions Funções lógicas
+##
+AND = E ## Retorna VERDADEIRO se todos os seus argumentos forem VERDADEIROS
+FALSE = FALSO ## Retorna o valor lógico FALSO
+IF = SE ## Especifica um teste lógico a ser executado
+IFERROR = SEERRO ## Retornará um valor que você especifica se uma fórmula for avaliada para um erro; do contrário, retornará o resultado da fórmula
+NOT = NÃO ## Inverte o valor lógico do argumento
+OR = OU ## Retorna VERDADEIRO se um dos argumentos for VERDADEIRO
+TRUE = VERDADEIRO ## Retorna o valor lógico VERDADEIRO
+
+
+##
+## Lookup and reference functions Funções de pesquisa e referência
+##
+ADDRESS = ENDEREÇO ## Retorna uma referência como texto para uma única célula em uma planilha
+AREAS = ÁREAS ## Retorna o número de áreas em uma referência
+CHOOSE = ESCOLHER ## Escolhe um valor a partir de uma lista de valores
+COLUMN = COL ## Retorna o número da coluna de uma referência
+COLUMNS = COLS ## Retorna o número de colunas em uma referência
+HLOOKUP = PROCH ## Procura na linha superior de uma matriz e retorna o valor da célula especificada
+HYPERLINK = HYPERLINK ## Cria um atalho ou salto que abre um documento armazenado em um servidor de rede, uma intranet ou na Internet
+INDEX = ÍNDICE ## Usa um índice para escolher um valor de uma referência ou matriz
+INDIRECT = INDIRETO ## Retorna uma referência indicada por um valor de texto
+LOOKUP = PROC ## Procura valores em um vetor ou em uma matriz
+MATCH = CORRESP ## Procura valores em uma referência ou em uma matriz
+OFFSET = DESLOC ## Retorna um deslocamento de referência com base em uma determinada referência
+ROW = LIN ## Retorna o número da linha de uma referência
+ROWS = LINS ## Retorna o número de linhas em uma referência
+RTD = RTD ## Recupera dados em tempo real de um programa que ofereça suporte a automação COM (automação: uma forma de trabalhar com objetos de um aplicativo a partir de outro aplicativo ou ferramenta de desenvolvimento. Chamada inicialmente de automação OLE, a automação é um padrão industrial e um recurso do modelo de objeto componente (COM).)
+TRANSPOSE = TRANSPOR ## Retorna a transposição de uma matriz
+VLOOKUP = PROCV ## Procura na primeira coluna de uma matriz e move ao longo da linha para retornar o valor de uma célula
+
+
+##
+## Math and trigonometry functions Funções matemáticas e trigonométricas
+##
+ABS = ABS ## Retorna o valor absoluto de um número
+ACOS = ACOS ## Retorna o arco cosseno de um número
+ACOSH = ACOSH ## Retorna o cosseno hiperbólico inverso de um número
+ASIN = ASEN ## Retorna o arco seno de um número
+ASINH = ASENH ## Retorna o seno hiperbólico inverso de um número
+ATAN = ATAN ## Retorna o arco tangente de um número
+ATAN2 = ATAN2 ## Retorna o arco tangente das coordenadas x e y especificadas
+ATANH = ATANH ## Retorna a tangente hiperbólica inversa de um número
+CEILING = TETO ## Arredonda um número para o inteiro mais próximo ou para o múltiplo mais próximo de significância
+COMBIN = COMBIN ## Retorna o número de combinações de um determinado número de objetos
+COS = COS ## Retorna o cosseno de um número
+COSH = COSH ## Retorna o cosseno hiperbólico de um número
+DEGREES = GRAUS ## Converte radianos em graus
+EVEN = PAR ## Arredonda um número para cima até o inteiro par mais próximo
+EXP = EXP ## Retorna e elevado à potência de um número especificado
+FACT = FATORIAL ## Retorna o fatorial de um número
+FACTDOUBLE = FATDUPLO ## Retorna o fatorial duplo de um número
+FLOOR = ARREDMULTB ## Arredonda um número para baixo até zero
+GCD = MDC ## Retorna o máximo divisor comum
+INT = INT ## Arredonda um número para baixo até o número inteiro mais próximo
+LCM = MMC ## Retorna o mínimo múltiplo comum
+LN = LN ## Retorna o logaritmo natural de um número
+LOG = LOG ## Retorna o logaritmo de um número de uma base especificada
+LOG10 = LOG10 ## Retorna o logaritmo de base 10 de um número
+MDETERM = MATRIZ.DETERM ## Retorna o determinante de uma matriz de uma variável do tipo matriz
+MINVERSE = MATRIZ.INVERSO ## Retorna a matriz inversa de uma matriz
+MMULT = MATRIZ.MULT ## Retorna o produto de duas matrizes
+MOD = RESTO ## Retorna o resto da divisão
+MROUND = MARRED ## Retorna um número arredondado ao múltiplo desejado
+MULTINOMIAL = MULTINOMIAL ## Retorna o multinomial de um conjunto de números
+ODD = ÍMPAR ## Arredonda um número para cima até o inteiro ímpar mais próximo
+PI = PI ## Retorna o valor de Pi
+POWER = POTÊNCIA ## Fornece o resultado de um número elevado a uma potência
+PRODUCT = MULT ## Multiplica seus argumentos
+QUOTIENT = QUOCIENTE ## Retorna a parte inteira de uma divisão
+RADIANS = RADIANOS ## Converte graus em radianos
+RAND = ALEATÓRIO ## Retorna um número aleatório entre 0 e 1
+RANDBETWEEN = ALEATÓRIOENTRE ## Retorna um número aleatório entre os números especificados
+ROMAN = ROMANO ## Converte um algarismo arábico em romano, como texto
+ROUND = ARRED ## Arredonda um número até uma quantidade especificada de dígitos
+ROUNDDOWN = ARREDONDAR.PARA.BAIXO ## Arredonda um número para baixo até zero
+ROUNDUP = ARREDONDAR.PARA.CIMA ## Arredonda um número para cima, afastando-o de zero
+SERIESSUM = SOMASEQÜÊNCIA ## Retorna a soma de uma série polinomial baseada na fórmula
+SIGN = SINAL ## Retorna o sinal de um número
+SIN = SEN ## Retorna o seno de um ângulo dado
+SINH = SENH ## Retorna o seno hiperbólico de um número
+SQRT = RAIZ ## Retorna uma raiz quadrada positiva
+SQRTPI = RAIZPI ## Retorna a raiz quadrada de (núm* pi)
+SUBTOTAL = SUBTOTAL ## Retorna um subtotal em uma lista ou em um banco de dados
+SUM = SOMA ## Soma seus argumentos
+SUMIF = SOMASE ## Adiciona as células especificadas por um determinado critério
+SUMIFS = SOMASE ## Adiciona as células em um intervalo que atende a vários critérios
+SUMPRODUCT = SOMARPRODUTO ## Retorna a soma dos produtos de componentes correspondentes de matrizes
+SUMSQ = SOMAQUAD ## Retorna a soma dos quadrados dos argumentos
+SUMX2MY2 = SOMAX2DY2 ## Retorna a soma da diferença dos quadrados dos valores correspondentes em duas matrizes
+SUMX2PY2 = SOMAX2SY2 ## Retorna a soma da soma dos quadrados dos valores correspondentes em duas matrizes
+SUMXMY2 = SOMAXMY2 ## Retorna a soma dos quadrados das diferenças dos valores correspondentes em duas matrizes
+TAN = TAN ## Retorna a tangente de um número
+TANH = TANH ## Retorna a tangente hiperbólica de um número
+TRUNC = TRUNCAR ## Trunca um número para um inteiro
+
+
+##
+## Statistical functions Funções estatísticas
+##
+AVEDEV = DESV.MÉDIO ## Retorna a média aritmética dos desvios médios dos pontos de dados a partir de sua média
+AVERAGE = MÉDIA ## Retorna a média dos argumentos
+AVERAGEA = MÉDIAA ## Retorna a média dos argumentos, inclusive números, texto e valores lógicos
+AVERAGEIF = MÉDIASE ## Retorna a média (média aritmética) de todas as células em um intervalo que atendem a um determinado critério
+AVERAGEIFS = MÉDIASES ## Retorna a média (média aritmética) de todas as células que atendem a múltiplos critérios.
+BETADIST = DISTBETA ## Retorna a função de distribuição cumulativa beta
+BETAINV = BETA.ACUM.INV ## Retorna o inverso da função de distribuição cumulativa para uma distribuição beta especificada
+BINOMDIST = DISTRBINOM ## Retorna a probabilidade de distribuição binomial do termo individual
+CHIDIST = DIST.QUI ## Retorna a probabilidade unicaudal da distribuição qui-quadrada
+CHIINV = INV.QUI ## Retorna o inverso da probabilidade uni-caudal da distribuição qui-quadrada
+CHITEST = TESTE.QUI ## Retorna o teste para independência
+CONFIDENCE = INT.CONFIANÇA ## Retorna o intervalo de confiança para uma média da população
+CORREL = CORREL ## Retorna o coeficiente de correlação entre dois conjuntos de dados
+COUNT = CONT.NÚM ## Calcula quantos números há na lista de argumentos
+COUNTA = CONT.VALORES ## Calcula quantos valores há na lista de argumentos
+COUNTBLANK = CONTAR.VAZIO ## Conta o número de células vazias no intervalo especificado
+COUNTIF = CONT.SE ## Calcula o número de células não vazias em um intervalo que corresponde a determinados critérios
+COUNTIFS = CONT.SES ## Conta o número de células dentro de um intervalo que atende a múltiplos critérios
+COVAR = COVAR ## Retorna a covariância, a média dos produtos dos desvios pares
+CRITBINOM = CRIT.BINOM ## Retorna o menor valor para o qual a distribuição binomial cumulativa é menor ou igual ao valor padrão
+DEVSQ = DESVQ ## Retorna a soma dos quadrados dos desvios
+EXPONDIST = DISTEXPON ## Retorna a distribuição exponencial
+FDIST = DISTF ## Retorna a distribuição de probabilidade F
+FINV = INVF ## Retorna o inverso da distribuição de probabilidades F
+FISHER = FISHER ## Retorna a transformação Fisher
+FISHERINV = FISHERINV ## Retorna o inverso da transformação Fisher
+FORECAST = PREVISÃO ## Retorna um valor ao longo de uma linha reta
+FREQUENCY = FREQÜÊNCIA ## Retorna uma distribuição de freqüência como uma matriz vertical
+FTEST = TESTEF ## Retorna o resultado de um teste F
+GAMMADIST = DISTGAMA ## Retorna a distribuição gama
+GAMMAINV = INVGAMA ## Retorna o inverso da distribuição cumulativa gama
+GAMMALN = LNGAMA ## Retorna o logaritmo natural da função gama, G(x)
+GEOMEAN = MÉDIA.GEOMÉTRICA ## Retorna a média geométrica
+GROWTH = CRESCIMENTO ## Retorna valores ao longo de uma tendência exponencial
+HARMEAN = MÉDIA.HARMÔNICA ## Retorna a média harmônica
+HYPGEOMDIST = DIST.HIPERGEOM ## Retorna a distribuição hipergeométrica
+INTERCEPT = INTERCEPÇÃO ## Retorna a intercepção da linha de regressão linear
+KURT = CURT ## Retorna a curtose de um conjunto de dados
+LARGE = MAIOR ## Retorna o maior valor k-ésimo de um conjunto de dados
+LINEST = PROJ.LIN ## Retorna os parâmetros de uma tendência linear
+LOGEST = PROJ.LOG ## Retorna os parâmetros de uma tendência exponencial
+LOGINV = INVLOG ## Retorna o inverso da distribuição lognormal
+LOGNORMDIST = DIST.LOGNORMAL ## Retorna a distribuição lognormal cumulativa
+MAX = MÁXIMO ## Retorna o valor máximo em uma lista de argumentos
+MAXA = MÁXIMOA ## Retorna o maior valor em uma lista de argumentos, inclusive números, texto e valores lógicos
+MEDIAN = MED ## Retorna a mediana dos números indicados
+MIN = MÍNIMO ## Retorna o valor mínimo em uma lista de argumentos
+MINA = MÍNIMOA ## Retorna o menor valor em uma lista de argumentos, inclusive números, texto e valores lógicos
+MODE = MODO ## Retorna o valor mais comum em um conjunto de dados
+NEGBINOMDIST = DIST.BIN.NEG ## Retorna a distribuição binomial negativa
+NORMDIST = DIST.NORM ## Retorna a distribuição cumulativa normal
+NORMINV = INV.NORM ## Retorna o inverso da distribuição cumulativa normal
+NORMSDIST = DIST.NORMP ## Retorna a distribuição cumulativa normal padrão
+NORMSINV = INV.NORMP ## Retorna o inverso da distribuição cumulativa normal padrão
+PEARSON = PEARSON ## Retorna o coeficiente de correlação do momento do produto Pearson
+PERCENTILE = PERCENTIL ## Retorna o k-ésimo percentil de valores em um intervalo
+PERCENTRANK = ORDEM.PORCENTUAL ## Retorna a ordem percentual de um valor em um conjunto de dados
+PERMUT = PERMUT ## Retorna o número de permutações de um determinado número de objetos
+POISSON = POISSON ## Retorna a distribuição Poisson
+PROB = PROB ## Retorna a probabilidade de valores em um intervalo estarem entre dois limites
+QUARTILE = QUARTIL ## Retorna o quartil do conjunto de dados
+RANK = ORDEM ## Retorna a posição de um número em uma lista de números
+RSQ = RQUAD ## Retorna o quadrado do coeficiente de correlação do momento do produto de Pearson
+SKEW = DISTORÇÃO ## Retorna a distorção de uma distribuição
+SLOPE = INCLINAÇÃO ## Retorna a inclinação da linha de regressão linear
+SMALL = MENOR ## Retorna o menor valor k-ésimo do conjunto de dados
+STANDARDIZE = PADRONIZAR ## Retorna um valor normalizado
+STDEV = DESVPAD ## Estima o desvio padrão com base em uma amostra
+STDEVA = DESVPADA ## Estima o desvio padrão com base em uma amostra, inclusive números, texto e valores lógicos
+STDEVP = DESVPADP ## Calcula o desvio padrão com base na população total
+STDEVPA = DESVPADPA ## Calcula o desvio padrão com base na população total, inclusive números, texto e valores lógicos
+STEYX = EPADYX ## Retorna o erro padrão do valor-y previsto para cada x da regressão
+TDIST = DISTT ## Retorna a distribuição t de Student
+TINV = INVT ## Retorna o inverso da distribuição t de Student
+TREND = TENDÊNCIA ## Retorna valores ao longo de uma tendência linear
+TRIMMEAN = MÉDIA.INTERNA ## Retorna a média do interior de um conjunto de dados
+TTEST = TESTET ## Retorna a probabilidade associada ao teste t de Student
+VAR = VAR ## Estima a variância com base em uma amostra
+VARA = VARA ## Estima a variância com base em uma amostra, inclusive números, texto e valores lógicos
+VARP = VARP ## Calcula a variância com base na população inteira
+VARPA = VARPA ## Calcula a variância com base na população total, inclusive números, texto e valores lógicos
+WEIBULL = WEIBULL ## Retorna a distribuição Weibull
+ZTEST = TESTEZ ## Retorna o valor de probabilidade uni-caudal de um teste-z
+
+
+##
+## Text functions Funções de texto
+##
+ASC = ASC ## Altera letras do inglês ou katakana de largura total (bytes duplos) dentro de uma seqüência de caracteres para caracteres de meia largura (byte único)
+BAHTTEXT = BAHTTEXT ## Converte um número em um texto, usando o formato de moeda ß (baht)
+CHAR = CARACT ## Retorna o caractere especificado pelo número de código
+CLEAN = TIRAR ## Remove todos os caracteres do texto que não podem ser impressos
+CODE = CÓDIGO ## Retorna um código numérico para o primeiro caractere de uma seqüência de caracteres de texto
+CONCATENATE = CONCATENAR ## Agrupa vários itens de texto em um único item de texto
+DOLLAR = MOEDA ## Converte um número em texto, usando o formato de moeda $ (dólar)
+EXACT = EXATO ## Verifica se dois valores de texto são idênticos
+FIND = PROCURAR ## Procura um valor de texto dentro de outro (diferencia maiúsculas de minúsculas)
+FINDB = PROCURARB ## Procura um valor de texto dentro de outro (diferencia maiúsculas de minúsculas)
+FIXED = DEF.NÚM.DEC ## Formata um número como texto com um número fixo de decimais
+JIS = JIS ## Altera letras do inglês ou katakana de meia largura (byte único) dentro de uma seqüência de caracteres para caracteres de largura total (bytes duplos)
+LEFT = ESQUERDA ## Retorna os caracteres mais à esquerda de um valor de texto
+LEFTB = ESQUERDAB ## Retorna os caracteres mais à esquerda de um valor de texto
+LEN = NÚM.CARACT ## Retorna o número de caracteres em uma seqüência de texto
+LENB = NÚM.CARACTB ## Retorna o número de caracteres em uma seqüência de texto
+LOWER = MINÚSCULA ## Converte texto para minúsculas
+MID = EXT.TEXTO ## Retorna um número específico de caracteres de uma seqüência de texto começando na posição especificada
+MIDB = EXT.TEXTOB ## Retorna um número específico de caracteres de uma seqüência de texto começando na posição especificada
+PHONETIC = FONÉTICA ## Extrai os caracteres fonéticos (furigana) de uma seqüência de caracteres de texto
+PROPER = PRI.MAIÚSCULA ## Coloca a primeira letra de cada palavra em maiúscula em um valor de texto
+REPLACE = MUDAR ## Muda os caracteres dentro do texto
+REPLACEB = MUDARB ## Muda os caracteres dentro do texto
+REPT = REPT ## Repete o texto um determinado número de vezes
+RIGHT = DIREITA ## Retorna os caracteres mais à direita de um valor de texto
+RIGHTB = DIREITAB ## Retorna os caracteres mais à direita de um valor de texto
+SEARCH = LOCALIZAR ## Localiza um valor de texto dentro de outro (não diferencia maiúsculas de minúsculas)
+SEARCHB = LOCALIZARB ## Localiza um valor de texto dentro de outro (não diferencia maiúsculas de minúsculas)
+SUBSTITUTE = SUBSTITUIR ## Substitui um novo texto por um texto antigo em uma seqüência de texto
+T = T ## Converte os argumentos em texto
+TEXT = TEXTO ## Formata um número e o converte em texto
+TRIM = ARRUMAR ## Remove espaços do texto
+UPPER = MAIÚSCULA ## Converte o texto em maiúsculas
+VALUE = VALOR ## Converte um argumento de texto em um número
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config
new file mode 100755
index 0000000..cd85c17
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config
@@ -0,0 +1,24 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = €
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #NULO!
+DIV0 = #DIV/0!
+VALUE = #VALOR!
+REF = #REF!
+NAME = #NOME?
+NUM = #NÚM!
+NA = #N/D
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions
new file mode 100755
index 0000000..ba4eb47
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions
@@ -0,0 +1,408 @@
+##
+## Add-in and Automation functions Funções de Suplemento e Automatização
+##
+GETPIVOTDATA = OBTERDADOSDIN ## Devolve dados armazenados num relatório de Tabela Dinâmica
+
+
+##
+## Cube functions Funções de cubo
+##
+CUBEKPIMEMBER = MEMBROKPICUBO ## Devolve o nome, propriedade e medição de um KPI (key performance indicator) e apresenta o nome e a propriedade na célula. Um KPI é uma medida quantificável, como, por exemplo, o lucro mensal bruto ou a rotatividade trimestral de pessoal, utilizada para monitorizar o desempenho de uma organização.
+CUBEMEMBER = MEMBROCUBO ## Devolve um membro ou cadeia de identificação numa hierarquia de cubo. Utilizada para validar a existência do membro ou cadeia de identificação no cubo.
+CUBEMEMBERPROPERTY = PROPRIEDADEMEMBROCUBO ## Devolve o valor de uma propriedade de membro no cubo. Utilizada para validar a existência de um nome de membro no cubo e para devolver a propriedade especificada para esse membro.
+CUBERANKEDMEMBER = MEMBROCLASSIFICADOCUBO ## Devolve o enésimo ou a classificação mais alta num conjunto. Utilizada para devolver um ou mais elementos num conjunto, tal como o melhor vendedor ou os 10 melhores alunos.
+CUBESET = CONJUNTOCUBO ## Define um conjunto calculado de membros ou cadeias de identificação enviando uma expressão de conjunto para o cubo no servidor, que cria o conjunto e, em seguida, devolve o conjunto ao Microsoft Office Excel.
+CUBESETCOUNT = CONTARCONJUNTOCUBO ## Devolve o número de itens num conjunto.
+CUBEVALUE = VALORCUBO ## Devolve um valor agregado do cubo.
+
+
+##
+## Database functions Funções de base de dados
+##
+DAVERAGE = BDMÉDIA ## Devolve a média das entradas da base de dados seleccionadas
+DCOUNT = BDCONTAR ## Conta as células que contêm números numa base de dados
+DCOUNTA = BDCONTAR.VAL ## Conta as células que não estejam em branco numa base de dados
+DGET = BDOBTER ## Extrai de uma base de dados um único registo que corresponde aos critérios especificados
+DMAX = BDMÁX ## Devolve o valor máximo das entradas da base de dados seleccionadas
+DMIN = BDMÍN ## Devolve o valor mínimo das entradas da base de dados seleccionadas
+DPRODUCT = BDMULTIPL ## Multiplica os valores de um determinado campo de registos que correspondem aos critérios numa base de dados
+DSTDEV = BDDESVPAD ## Calcula o desvio-padrão com base numa amostra de entradas da base de dados seleccionadas
+DSTDEVP = BDDESVPADP ## Calcula o desvio-padrão com base na população total das entradas da base de dados seleccionadas
+DSUM = BDSOMA ## Adiciona os números na coluna de campo dos registos de base de dados que correspondem aos critérios
+DVAR = BDVAR ## Calcula a variância com base numa amostra das entradas de base de dados seleccionadas
+DVARP = BDVARP ## Calcula a variância com base na população total das entradas de base de dados seleccionadas
+
+
+##
+## Date and time functions Funções de data e hora
+##
+DATE = DATA ## Devolve o número de série de uma determinada data
+DATEVALUE = DATA.VALOR ## Converte uma data em forma de texto num número de série
+DAY = DIA ## Converte um número de série num dia do mês
+DAYS360 = DIAS360 ## Calcula o número de dias entre duas datas com base num ano com 360 dias
+EDATE = DATAM ## Devolve um número de série de data que corresponde ao número de meses indicado antes ou depois da data de início
+EOMONTH = FIMMÊS ## Devolve o número de série do último dia do mês antes ou depois de um número de meses especificado
+HOUR = HORA ## Converte um número de série numa hora
+MINUTE = MINUTO ## Converte um número de série num minuto
+MONTH = MÊS ## Converte um número de série num mês
+NETWORKDAYS = DIATRABALHOTOTAL ## Devolve o número total de dias úteis entre duas datas
+NOW = AGORA ## Devolve o número de série da data e hora actuais
+SECOND = SEGUNDO ## Converte um número de série num segundo
+TIME = TEMPO ## Devolve o número de série de um determinado tempo
+TIMEVALUE = VALOR.TEMPO ## Converte um tempo em forma de texto num número de série
+TODAY = HOJE ## Devolve o número de série da data actual
+WEEKDAY = DIA.SEMANA ## Converte um número de série num dia da semana
+WEEKNUM = NÚMSEMANA ## Converte um número de série num número que representa o número da semana num determinado ano
+WORKDAY = DIA.TRABALHO ## Devolve o número de série da data antes ou depois de um número de dias úteis especificado
+YEAR = ANO ## Converte um número de série num ano
+YEARFRAC = FRACÇÃOANO ## Devolve a fracção de ano que representa o número de dias inteiros entre a data_de_início e a data_de_fim
+
+
+##
+## Engineering functions Funções de engenharia
+##
+BESSELI = BESSELI ## Devolve a função de Bessel modificada In(x)
+BESSELJ = BESSELJ ## Devolve a função de Bessel Jn(x)
+BESSELK = BESSELK ## Devolve a função de Bessel modificada Kn(x)
+BESSELY = BESSELY ## Devolve a função de Bessel Yn(x)
+BIN2DEC = BINADEC ## Converte um número binário em decimal
+BIN2HEX = BINAHEX ## Converte um número binário em hexadecimal
+BIN2OCT = BINAOCT ## Converte um número binário em octal
+COMPLEX = COMPLEXO ## Converte coeficientes reais e imaginários num número complexo
+CONVERT = CONVERTER ## Converte um número de um sistema de medida noutro
+DEC2BIN = DECABIN ## Converte um número decimal em binário
+DEC2HEX = DECAHEX ## Converte um número decimal em hexadecimal
+DEC2OCT = DECAOCT ## Converte um número decimal em octal
+DELTA = DELTA ## Testa se dois valores são iguais
+ERF = FUNCERRO ## Devolve a função de erro
+ERFC = FUNCERROCOMPL ## Devolve a função de erro complementar
+GESTEP = DEGRAU ## Testa se um número é maior do que um valor limite
+HEX2BIN = HEXABIN ## Converte um número hexadecimal em binário
+HEX2DEC = HEXADEC ## Converte um número hexadecimal em decimal
+HEX2OCT = HEXAOCT ## Converte um número hexadecimal em octal
+IMABS = IMABS ## Devolve o valor absoluto (módulo) de um número complexo
+IMAGINARY = IMAGINÁRIO ## Devolve o coeficiente imaginário de um número complexo
+IMARGUMENT = IMARG ## Devolve o argumento Teta, um ângulo expresso em radianos
+IMCONJUGATE = IMCONJ ## Devolve o conjugado complexo de um número complexo
+IMCOS = IMCOS ## Devolve o co-seno de um número complexo
+IMDIV = IMDIV ## Devolve o quociente de dois números complexos
+IMEXP = IMEXP ## Devolve o exponencial de um número complexo
+IMLN = IMLN ## Devolve o logaritmo natural de um número complexo
+IMLOG10 = IMLOG10 ## Devolve o logaritmo de base 10 de um número complexo
+IMLOG2 = IMLOG2 ## Devolve o logaritmo de base 2 de um número complexo
+IMPOWER = IMPOT ## Devolve um número complexo elevado a uma potência inteira
+IMPRODUCT = IMPROD ## Devolve o produto de números complexos
+IMREAL = IMREAL ## Devolve o coeficiente real de um número complexo
+IMSIN = IMSENO ## Devolve o seno de um número complexo
+IMSQRT = IMRAIZ ## Devolve a raiz quadrada de um número complexo
+IMSUB = IMSUBTR ## Devolve a diferença entre dois números complexos
+IMSUM = IMSOMA ## Devolve a soma de números complexos
+OCT2BIN = OCTABIN ## Converte um número octal em binário
+OCT2DEC = OCTADEC ## Converte um número octal em decimal
+OCT2HEX = OCTAHEX ## Converte um número octal em hexadecimal
+
+
+##
+## Financial functions Funções financeiras
+##
+ACCRINT = JUROSACUM ## Devolve os juros acumulados de um título que paga juros periódicos
+ACCRINTM = JUROSACUMV ## Devolve os juros acumulados de um título que paga juros no vencimento
+AMORDEGRC = AMORDEGRC ## Devolve a depreciação correspondente a cada período contabilístico utilizando um coeficiente de depreciação
+AMORLINC = AMORLINC ## Devolve a depreciação correspondente a cada período contabilístico
+COUPDAYBS = CUPDIASINLIQ ## Devolve o número de dias entre o início do período do cupão e a data de regularização
+COUPDAYS = CUPDIAS ## Devolve o número de dias no período do cupão que contém a data de regularização
+COUPDAYSNC = CUPDIASPRÓX ## Devolve o número de dias entre a data de regularização e a data do cupão seguinte
+COUPNCD = CUPDATAPRÓX ## Devolve a data do cupão seguinte após a data de regularização
+COUPNUM = CUPNÚM ## Devolve o número de cupões a serem pagos entre a data de regularização e a data de vencimento
+COUPPCD = CUPDATAANT ## Devolve a data do cupão anterior antes da data de regularização
+CUMIPMT = PGTOJURACUM ## Devolve os juros cumulativos pagos entre dois períodos
+CUMPRINC = PGTOCAPACUM ## Devolve o capital cumulativo pago a título de empréstimo entre dois períodos
+DB = BD ## Devolve a depreciação de um activo relativo a um período especificado utilizando o método das quotas degressivas fixas
+DDB = BDD ## Devolve a depreciação de um activo relativo a um período especificado utilizando o método das quotas degressivas duplas ou qualquer outro método especificado
+DISC = DESC ## Devolve a taxa de desconto de um título
+DOLLARDE = MOEDADEC ## Converte um preço em unidade monetária, expresso como uma fracção, num preço em unidade monetária, expresso como um número decimal
+DOLLARFR = MOEDAFRA ## Converte um preço em unidade monetária, expresso como um número decimal, num preço em unidade monetária, expresso como uma fracção
+DURATION = DURAÇÃO ## Devolve a duração anual de um título com pagamentos de juros periódicos
+EFFECT = EFECTIVA ## Devolve a taxa de juros anual efectiva
+FV = VF ## Devolve o valor futuro de um investimento
+FVSCHEDULE = VFPLANO ## Devolve o valor futuro de um capital inicial após a aplicação de uma série de taxas de juro compostas
+INTRATE = TAXAJUROS ## Devolve a taxa de juros de um título investido na totalidade
+IPMT = IPGTO ## Devolve o pagamento dos juros de um investimento durante um determinado período
+IRR = TIR ## Devolve a taxa de rentabilidade interna para uma série de fluxos monetários
+ISPMT = É.PGTO ## Calcula os juros pagos durante um período específico de um investimento
+MDURATION = MDURAÇÃO ## Devolve a duração modificada de Macauley de um título com um valor de paridade equivalente a € 100
+MIRR = MTIR ## Devolve a taxa interna de rentabilidade em que os fluxos monetários positivos e negativos são financiados com taxas diferentes
+NOMINAL = NOMINAL ## Devolve a taxa de juros nominal anual
+NPER = NPER ## Devolve o número de períodos de um investimento
+NPV = VAL ## Devolve o valor actual líquido de um investimento com base numa série de fluxos monetários periódicos e numa taxa de desconto
+ODDFPRICE = PREÇOPRIMINC ## Devolve o preço por € 100 do valor nominal de um título com um período inicial incompleto
+ODDFYIELD = LUCROPRIMINC ## Devolve o lucro de um título com um período inicial incompleto
+ODDLPRICE = PREÇOÚLTINC ## Devolve o preço por € 100 do valor nominal de um título com um período final incompleto
+ODDLYIELD = LUCROÚLTINC ## Devolve o lucro de um título com um período final incompleto
+PMT = PGTO ## Devolve o pagamento periódico de uma anuidade
+PPMT = PPGTO ## Devolve o pagamento sobre o capital de um investimento num determinado período
+PRICE = PREÇO ## Devolve o preço por € 100 do valor nominal de um título que paga juros periódicos
+PRICEDISC = PREÇODESC ## Devolve o preço por € 100 do valor nominal de um título descontado
+PRICEMAT = PREÇOVENC ## Devolve o preço por € 100 do valor nominal de um título que paga juros no vencimento
+PV = VA ## Devolve o valor actual de um investimento
+RATE = TAXA ## Devolve a taxa de juros por período de uma anuidade
+RECEIVED = RECEBER ## Devolve o montante recebido no vencimento de um título investido na totalidade
+SLN = AMORT ## Devolve uma depreciação linear de um activo durante um período
+SYD = AMORTD ## Devolve a depreciação por algarismos da soma dos anos de um activo durante um período especificado
+TBILLEQ = OTN ## Devolve o lucro de um título equivalente a uma Obrigação do Tesouro
+TBILLPRICE = OTNVALOR ## Devolve o preço por € 100 de valor nominal de uma Obrigação do Tesouro
+TBILLYIELD = OTNLUCRO ## Devolve o lucro de uma Obrigação do Tesouro
+VDB = BDV ## Devolve a depreciação de um activo relativo a um período específico ou parcial utilizando um método de quotas degressivas
+XIRR = XTIR ## Devolve a taxa interna de rentabilidade de um plano de fluxos monetários que não seja necessariamente periódica
+XNPV = XVAL ## Devolve o valor actual líquido de um plano de fluxos monetários que não seja necessariamente periódico
+YIELD = LUCRO ## Devolve o lucro de um título que paga juros periódicos
+YIELDDISC = LUCRODESC ## Devolve o lucro anual de um título emitido abaixo do valor nominal, por exemplo, uma Obrigação do Tesouro
+YIELDMAT = LUCROVENC ## Devolve o lucro anual de um título que paga juros na data de vencimento
+
+
+##
+## Information functions Funções de informação
+##
+CELL = CÉL ## Devolve informações sobre a formatação, localização ou conteúdo de uma célula
+ERROR.TYPE = TIPO.ERRO ## Devolve um número correspondente a um tipo de erro
+INFO = INFORMAÇÃO ## Devolve informações sobre o ambiente de funcionamento actual
+ISBLANK = É.CÉL.VAZIA ## Devolve VERDADEIRO se o valor estiver em branco
+ISERR = É.ERROS ## Devolve VERDADEIRO se o valor for um valor de erro diferente de #N/D
+ISERROR = É.ERRO ## Devolve VERDADEIRO se o valor for um valor de erro
+ISEVEN = ÉPAR ## Devolve VERDADEIRO se o número for par
+ISLOGICAL = É.LÓGICO ## Devolve VERDADEIRO se o valor for lógico
+ISNA = É.NÃO.DISP ## Devolve VERDADEIRO se o valor for o valor de erro #N/D
+ISNONTEXT = É.NÃO.TEXTO ## Devolve VERDADEIRO se o valor não for texto
+ISNUMBER = É.NÚM ## Devolve VERDADEIRO se o valor for um número
+ISODD = ÉÍMPAR ## Devolve VERDADEIRO se o número for ímpar
+ISREF = É.REF ## Devolve VERDADEIRO se o valor for uma referência
+ISTEXT = É.TEXTO ## Devolve VERDADEIRO se o valor for texto
+N = N ## Devolve um valor convertido num número
+NA = NÃO.DISP ## Devolve o valor de erro #N/D
+TYPE = TIPO ## Devolve um número que indica o tipo de dados de um valor
+
+
+##
+## Logical functions Funções lógicas
+##
+AND = E ## Devolve VERDADEIRO se todos os respectivos argumentos corresponderem a VERDADEIRO
+FALSE = FALSO ## Devolve o valor lógico FALSO
+IF = SE ## Especifica um teste lógico a ser executado
+IFERROR = SE.ERRO ## Devolve um valor definido pelo utilizador se ocorrer um erro na fórmula, e devolve o resultado da fórmula se não ocorrer nenhum erro
+NOT = NÃO ## Inverte a lógica do respectivo argumento
+OR = OU ## Devolve VERDADEIRO se qualquer argumento for VERDADEIRO
+TRUE = VERDADEIRO ## Devolve o valor lógico VERDADEIRO
+
+
+##
+## Lookup and reference functions Funções de pesquisa e referência
+##
+ADDRESS = ENDEREÇO ## Devolve uma referência a uma única célula numa folha de cálculo como texto
+AREAS = ÁREAS ## Devolve o número de áreas numa referência
+CHOOSE = SELECCIONAR ## Selecciona um valor a partir de uma lista de valores
+COLUMN = COL ## Devolve o número da coluna de uma referência
+COLUMNS = COLS ## Devolve o número de colunas numa referência
+HLOOKUP = PROCH ## Procura na linha superior de uma matriz e devolve o valor da célula indicada
+HYPERLINK = HIPERLIGAÇÃO ## Cria um atalho ou hiperligação que abre um documento armazenado num servidor de rede, numa intranet ou na Internet
+INDEX = ÍNDICE ## Utiliza um índice para escolher um valor de uma referência ou de uma matriz
+INDIRECT = INDIRECTO ## Devolve uma referência indicada por um valor de texto
+LOOKUP = PROC ## Procura valores num vector ou numa matriz
+MATCH = CORRESP ## Procura valores numa referência ou numa matriz
+OFFSET = DESLOCAMENTO ## Devolve o deslocamento de referência de uma determinada referência
+ROW = LIN ## Devolve o número da linha de uma referência
+ROWS = LINS ## Devolve o número de linhas numa referência
+RTD = RTD ## Obtém dados em tempo real a partir de um programa que suporte automatização COM (automatização: modo de trabalhar com objectos de uma aplicação a partir de outra aplicação ou ferramenta de desenvolvimento. Anteriormente conhecida como automatização OLE, a automatização é uma norma da indústria de software e uma funcionalidade COM (Component Object Model).)
+TRANSPOSE = TRANSPOR ## Devolve a transposição de uma matriz
+VLOOKUP = PROCV ## Procura na primeira coluna de uma matriz e percorre a linha para devolver o valor de uma célula
+
+
+##
+## Math and trigonometry functions Funções matemáticas e trigonométricas
+##
+ABS = ABS ## Devolve o valor absoluto de um número
+ACOS = ACOS ## Devolve o arco de co-seno de um número
+ACOSH = ACOSH ## Devolve o co-seno hiperbólico inverso de um número
+ASIN = ASEN ## Devolve o arco de seno de um número
+ASINH = ASENH ## Devolve o seno hiperbólico inverso de um número
+ATAN = ATAN ## Devolve o arco de tangente de um número
+ATAN2 = ATAN2 ## Devolve o arco de tangente das coordenadas x e y
+ATANH = ATANH ## Devolve a tangente hiperbólica inversa de um número
+CEILING = ARRED.EXCESSO ## Arredonda um número para o número inteiro mais próximo ou para o múltiplo de significância mais próximo
+COMBIN = COMBIN ## Devolve o número de combinações de um determinado número de objectos
+COS = COS ## Devolve o co-seno de um número
+COSH = COSH ## Devolve o co-seno hiperbólico de um número
+DEGREES = GRAUS ## Converte radianos em graus
+EVEN = PAR ## Arredonda um número por excesso para o número inteiro mais próximo
+EXP = EXP ## Devolve e elevado à potência de um determinado número
+FACT = FACTORIAL ## Devolve o factorial de um número
+FACTDOUBLE = FACTDUPLO ## Devolve o factorial duplo de um número
+FLOOR = ARRED.DEFEITO ## Arredonda um número por defeito até zero
+GCD = MDC ## Devolve o maior divisor comum
+INT = INT ## Arredonda um número por defeito para o número inteiro mais próximo
+LCM = MMC ## Devolve o mínimo múltiplo comum
+LN = LN ## Devolve o logaritmo natural de um número
+LOG = LOG ## Devolve o logaritmo de um número com uma base especificada
+LOG10 = LOG10 ## Devolve o logaritmo de base 10 de um número
+MDETERM = MATRIZ.DETERM ## Devolve o determinante matricial de uma matriz
+MINVERSE = MATRIZ.INVERSA ## Devolve o inverso matricial de uma matriz
+MMULT = MATRIZ.MULT ## Devolve o produto matricial de duas matrizes
+MOD = RESTO ## Devolve o resto da divisão
+MROUND = MARRED ## Devolve um número arredondado para o múltiplo pretendido
+MULTINOMIAL = POLINOMIAL ## Devolve o polinomial de um conjunto de números
+ODD = ÍMPAR ## Arredonda por excesso um número para o número inteiro ímpar mais próximo
+PI = PI ## Devolve o valor de pi
+POWER = POTÊNCIA ## Devolve o resultado de um número elevado a uma potência
+PRODUCT = PRODUTO ## Multiplica os respectivos argumentos
+QUOTIENT = QUOCIENTE ## Devolve a parte inteira de uma divisão
+RADIANS = RADIANOS ## Converte graus em radianos
+RAND = ALEATÓRIO ## Devolve um número aleatório entre 0 e 1
+RANDBETWEEN = ALEATÓRIOENTRE ## Devolve um número aleatório entre os números especificados
+ROMAN = ROMANO ## Converte um número árabe em romano, como texto
+ROUND = ARRED ## Arredonda um número para um número de dígitos especificado
+ROUNDDOWN = ARRED.PARA.BAIXO ## Arredonda um número por defeito até zero
+ROUNDUP = ARRED.PARA.CIMA ## Arredonda um número por excesso, afastando-o de zero
+SERIESSUM = SOMASÉRIE ## Devolve a soma de uma série de potências baseada na fórmula
+SIGN = SINAL ## Devolve o sinal de um número
+SIN = SEN ## Devolve o seno de um determinado ângulo
+SINH = SENH ## Devolve o seno hiperbólico de um número
+SQRT = RAIZQ ## Devolve uma raiz quadrada positiva
+SQRTPI = RAIZPI ## Devolve a raiz quadrada de (núm * pi)
+SUBTOTAL = SUBTOTAL ## Devolve um subtotal numa lista ou base de dados
+SUM = SOMA ## Adiciona os respectivos argumentos
+SUMIF = SOMA.SE ## Adiciona as células especificadas por um determinado critério
+SUMIFS = SOMA.SE.S ## Adiciona as células num intervalo que cumpre vários critérios
+SUMPRODUCT = SOMARPRODUTO ## Devolve a soma dos produtos de componentes de matrizes correspondentes
+SUMSQ = SOMARQUAD ## Devolve a soma dos quadrados dos argumentos
+SUMX2MY2 = SOMAX2DY2 ## Devolve a soma da diferença dos quadrados dos valores correspondentes em duas matrizes
+SUMX2PY2 = SOMAX2SY2 ## Devolve a soma da soma dos quadrados dos valores correspondentes em duas matrizes
+SUMXMY2 = SOMAXMY2 ## Devolve a soma dos quadrados da diferença dos valores correspondentes em duas matrizes
+TAN = TAN ## Devolve a tangente de um número
+TANH = TANH ## Devolve a tangente hiperbólica de um número
+TRUNC = TRUNCAR ## Trunca um número para um número inteiro
+
+
+##
+## Statistical functions Funções estatísticas
+##
+AVEDEV = DESV.MÉDIO ## Devolve a média aritmética dos desvios absolutos à média dos pontos de dados
+AVERAGE = MÉDIA ## Devolve a média dos respectivos argumentos
+AVERAGEA = MÉDIAA ## Devolve uma média dos respectivos argumentos, incluindo números, texto e valores lógicos
+AVERAGEIF = MÉDIA.SE ## Devolve a média aritmética de todas as células num intervalo que cumprem determinado critério
+AVERAGEIFS = MÉDIA.SE.S ## Devolve a média aritmética de todas as células que cumprem múltiplos critérios
+BETADIST = DISTBETA ## Devolve a função de distribuição cumulativa beta
+BETAINV = BETA.ACUM.INV ## Devolve o inverso da função de distribuição cumulativa relativamente a uma distribuição beta específica
+BINOMDIST = DISTRBINOM ## Devolve a probabilidade de distribuição binomial de termo individual
+CHIDIST = DIST.CHI ## Devolve a probabilidade unicaudal da distribuição qui-quadrada
+CHIINV = INV.CHI ## Devolve o inverso da probabilidade unicaudal da distribuição qui-quadrada
+CHITEST = TESTE.CHI ## Devolve o teste para independência
+CONFIDENCE = INT.CONFIANÇA ## Devolve o intervalo de confiança correspondente a uma média de população
+CORREL = CORREL ## Devolve o coeficiente de correlação entre dois conjuntos de dados
+COUNT = CONTAR ## Conta os números que existem na lista de argumentos
+COUNTA = CONTAR.VAL ## Conta os valores que existem na lista de argumentos
+COUNTBLANK = CONTAR.VAZIO ## Conta o número de células em branco num intervalo
+COUNTIF = CONTAR.SE ## Calcula o número de células num intervalo que corresponde aos critérios determinados
+COUNTIFS = CONTAR.SE.S ## Conta o número de células num intervalo que cumprem múltiplos critérios
+COVAR = COVAR ## Devolve a covariância, que é a média dos produtos de desvios de pares
+CRITBINOM = CRIT.BINOM ## Devolve o menor valor em que a distribuição binomial cumulativa é inferior ou igual a um valor de critério
+DEVSQ = DESVQ ## Devolve a soma dos quadrados dos desvios
+EXPONDIST = DISTEXPON ## Devolve a distribuição exponencial
+FDIST = DISTF ## Devolve a distribuição da probabilidade F
+FINV = INVF ## Devolve o inverso da distribuição da probabilidade F
+FISHER = FISHER ## Devolve a transformação Fisher
+FISHERINV = FISHERINV ## Devolve o inverso da transformação Fisher
+FORECAST = PREVISÃO ## Devolve um valor ao longo de uma tendência linear
+FREQUENCY = FREQUÊNCIA ## Devolve uma distribuição de frequência como uma matriz vertical
+FTEST = TESTEF ## Devolve o resultado de um teste F
+GAMMADIST = DISTGAMA ## Devolve a distribuição gama
+GAMMAINV = INVGAMA ## Devolve o inverso da distribuição gama cumulativa
+GAMMALN = LNGAMA ## Devolve o logaritmo natural da função gama, Γ(x)
+GEOMEAN = MÉDIA.GEOMÉTRICA ## Devolve a média geométrica
+GROWTH = CRESCIMENTO ## Devolve valores ao longo de uma tendência exponencial
+HARMEAN = MÉDIA.HARMÓNICA ## Devolve a média harmónica
+HYPGEOMDIST = DIST.HIPERGEOM ## Devolve a distribuição hipergeométrica
+INTERCEPT = INTERCEPTAR ## Devolve a intercepção da linha de regressão linear
+KURT = CURT ## Devolve a curtose de um conjunto de dados
+LARGE = MAIOR ## Devolve o maior valor k-ésimo de um conjunto de dados
+LINEST = PROJ.LIN ## Devolve os parâmetros de uma tendência linear
+LOGEST = PROJ.LOG ## Devolve os parâmetros de uma tendência exponencial
+LOGINV = INVLOG ## Devolve o inverso da distribuição normal logarítmica
+LOGNORMDIST = DIST.NORMALLOG ## Devolve a distribuição normal logarítmica cumulativa
+MAX = MÁXIMO ## Devolve o valor máximo numa lista de argumentos
+MAXA = MÁXIMOA ## Devolve o valor máximo numa lista de argumentos, incluindo números, texto e valores lógicos
+MEDIAN = MED ## Devolve a mediana dos números indicados
+MIN = MÍNIMO ## Devolve o valor mínimo numa lista de argumentos
+MINA = MÍNIMOA ## Devolve o valor mínimo numa lista de argumentos, incluindo números, texto e valores lógicos
+MODE = MODA ## Devolve o valor mais comum num conjunto de dados
+NEGBINOMDIST = DIST.BIN.NEG ## Devolve a distribuição binominal negativa
+NORMDIST = DIST.NORM ## Devolve a distribuição cumulativa normal
+NORMINV = INV.NORM ## Devolve o inverso da distribuição cumulativa normal
+NORMSDIST = DIST.NORMP ## Devolve a distribuição cumulativa normal padrão
+NORMSINV = INV.NORMP ## Devolve o inverso da distribuição cumulativa normal padrão
+PEARSON = PEARSON ## Devolve o coeficiente de correlação momento/produto de Pearson
+PERCENTILE = PERCENTIL ## Devolve o k-ésimo percentil de valores num intervalo
+PERCENTRANK = ORDEM.PERCENTUAL ## Devolve a ordem percentual de um valor num conjunto de dados
+PERMUT = PERMUTAR ## Devolve o número de permutações de um determinado número de objectos
+POISSON = POISSON ## Devolve a distribuição de Poisson
+PROB = PROB ## Devolve a probabilidade dos valores num intervalo se encontrarem entre dois limites
+QUARTILE = QUARTIL ## Devolve o quartil de um conjunto de dados
+RANK = ORDEM ## Devolve a ordem de um número numa lista numérica
+RSQ = RQUAD ## Devolve o quadrado do coeficiente de correlação momento/produto de Pearson
+SKEW = DISTORÇÃO ## Devolve a distorção de uma distribuição
+SLOPE = DECLIVE ## Devolve o declive da linha de regressão linear
+SMALL = MENOR ## Devolve o menor valor de k-ésimo de um conjunto de dados
+STANDARDIZE = NORMALIZAR ## Devolve um valor normalizado
+STDEV = DESVPAD ## Calcula o desvio-padrão com base numa amostra
+STDEVA = DESVPADA ## Calcula o desvio-padrão com base numa amostra, incluindo números, texto e valores lógicos
+STDEVP = DESVPADP ## Calcula o desvio-padrão com base na população total
+STDEVPA = DESVPADPA ## Calcula o desvio-padrão com base na população total, incluindo números, texto e valores lógicos
+STEYX = EPADYX ## Devolve o erro-padrão do valor de y previsto para cada x na regressão
+TDIST = DISTT ## Devolve a distribuição t de Student
+TINV = INVT ## Devolve o inverso da distribuição t de Student
+TREND = TENDÊNCIA ## Devolve valores ao longo de uma tendência linear
+TRIMMEAN = MÉDIA.INTERNA ## Devolve a média do interior de um conjunto de dados
+TTEST = TESTET ## Devolve a probabilidade associada ao teste t de Student
+VAR = VAR ## Calcula a variância com base numa amostra
+VARA = VARA ## Calcula a variância com base numa amostra, incluindo números, texto e valores lógicos
+VARP = VARP ## Calcula a variância com base na população total
+VARPA = VARPA ## Calcula a variância com base na população total, incluindo números, texto e valores lógicos
+WEIBULL = WEIBULL ## Devolve a distribuição Weibull
+ZTEST = TESTEZ ## Devolve o valor de probabilidade unicaudal de um teste-z
+
+
+##
+## Text functions Funções de texto
+##
+ASC = ASC ## Altera letras ou katakana de largura total (byte duplo) numa cadeia de caracteres para caracteres de largura média (byte único)
+BAHTTEXT = TEXTO.BAHT ## Converte um número em texto, utilizando o formato monetário ß (baht)
+CHAR = CARÁCT ## Devolve o carácter especificado pelo número de código
+CLEAN = LIMPAR ## Remove do texto todos os caracteres não imprimíveis
+CODE = CÓDIGO ## Devolve um código numérico correspondente ao primeiro carácter numa cadeia de texto
+CONCATENATE = CONCATENAR ## Agrupa vários itens de texto num único item de texto
+DOLLAR = MOEDA ## Converte um número em texto, utilizando o formato monetário € (Euro)
+EXACT = EXACTO ## Verifica se dois valores de texto são idênticos
+FIND = LOCALIZAR ## Localiza um valor de texto dentro de outro (sensível às maiúsculas e minúsculas)
+FINDB = LOCALIZARB ## Localiza um valor de texto dentro de outro (sensível às maiúsculas e minúsculas)
+FIXED = FIXA ## Formata um número como texto com um número fixo de decimais
+JIS = JIS ## Altera letras ou katakana de largura média (byte único) numa cadeia de caracteres para caracteres de largura total (byte duplo)
+LEFT = ESQUERDA ## Devolve os caracteres mais à esquerda de um valor de texto
+LEFTB = ESQUERDAB ## Devolve os caracteres mais à esquerda de um valor de texto
+LEN = NÚM.CARACT ## Devolve o número de caracteres de uma cadeia de texto
+LENB = NÚM.CARACTB ## Devolve o número de caracteres de uma cadeia de texto
+LOWER = MINÚSCULAS ## Converte o texto em minúsculas
+MID = SEG.TEXTO ## Devolve um número específico de caracteres de uma cadeia de texto, a partir da posição especificada
+MIDB = SEG.TEXTOB ## Devolve um número específico de caracteres de uma cadeia de texto, a partir da posição especificada
+PHONETIC = FONÉTICA ## Retira os caracteres fonéticos (furigana) de uma cadeia de texto
+PROPER = INICIAL.MAIÚSCULA ## Coloca em maiúsculas a primeira letra de cada palavra de um valor de texto
+REPLACE = SUBSTITUIR ## Substitui caracteres no texto
+REPLACEB = SUBSTITUIRB ## Substitui caracteres no texto
+REPT = REPETIR ## Repete texto um determinado número de vezes
+RIGHT = DIREITA ## Devolve os caracteres mais à direita de um valor de texto
+RIGHTB = DIREITAB ## Devolve os caracteres mais à direita de um valor de texto
+SEARCH = PROCURAR ## Localiza um valor de texto dentro de outro (não sensível a maiúsculas e minúsculas)
+SEARCHB = PROCURARB ## Localiza um valor de texto dentro de outro (não sensível a maiúsculas e minúsculas)
+SUBSTITUTE = SUBST ## Substitui texto novo por texto antigo numa cadeia de texto
+T = T ## Converte os respectivos argumentos em texto
+TEXT = TEXTO ## Formata um número e converte-o em texto
+TRIM = COMPACTAR ## Remove espaços do texto
+UPPER = MAIÚSCULAS ## Converte texto em maiúsculas
+VALUE = VALOR ## Converte um argumento de texto num número
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config
new file mode 100755
index 0000000..9ee9e6c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config
@@ -0,0 +1,24 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = р
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #ПУСТО!
+DIV0 = #ДЕЛ/0!
+VALUE = #ЗНАЧ!
+REF = #ССЫЛ!
+NAME = #ИМЯ?
+NUM = #ЧИСЛО!
+NA = #Н/Д
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions
new file mode 100755
index 0000000..3597dbf
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions
@@ -0,0 +1,416 @@
+##
+## PhpSpreadsheet
+##
+## Data in this file derived from information provided by web-junior (http://www.web-junior.net/)
+##
+##
+
+
+##
+## Add-in and Automation functions Функции надстроек и автоматизации
+##
+GETPIVOTDATA = ПОЛУЧИТЬ.ДАННЫЕ.СВОДНОЙ.ТАБЛИЦЫ ## Возвращает данные, хранящиеся в отчете сводной таблицы.
+
+
+##
+## Cube functions Функции Куб
+##
+CUBEKPIMEMBER = КУБЭЛЕМЕНТКИП ## Возвращает свойство ключевого индикатора производительности «(КИП)» и отображает имя «КИП» в ячейке. «КИП» представляет собой количественную величину, такую как ежемесячная валовая прибыль или ежеквартальная текучесть кадров, используемой для контроля эффективности работы организации.
+CUBEMEMBER = КУБЭЛЕМЕНТ ## Возвращает элемент или кортеж из куба. Используется для проверки существования элемента или кортежа в кубе.
+CUBEMEMBERPROPERTY = КУБСВОЙСТВОЭЛЕМЕНТА ## Возвращает значение свойства элемента из куба. Используется для проверки существования имени элемента в кубе и возвращает указанное свойство для этого элемента.
+CUBERANKEDMEMBER = КУБПОРЭЛЕМЕНТ ## Возвращает n-ый или ранжированный элемент в множество. Используется для возвращения одного или нескольких элементов в множество, например, лучшего продавца или 10 лучших студентов.
+CUBESET = КУБМНОЖ ## Определяет вычислительное множество элементов или кортежей, отправляя на сервер выражение, которое создает множество, а затем возвращает его в Microsoft Office Excel.
+CUBESETCOUNT = КУБЧИСЛОЭЛМНОЖ ## Возвращает число элементов множества.
+CUBEVALUE = КУБЗНАЧЕНИЕ ## Возвращает обобщенное значение из куба.
+
+
+##
+## Database functions Функции для работы с базами данных
+##
+DAVERAGE = ДСРЗНАЧ ## Возвращает среднее значение выбранных записей базы данных.
+DCOUNT = БСЧЁТ ## Подсчитывает количество числовых ячеек в базе данных.
+DCOUNTA = БСЧЁТА ## Подсчитывает количество непустых ячеек в базе данных.
+DGET = БИЗВЛЕЧЬ ## Извлекает из базы данных одну запись, удовлетворяющую заданному условию.
+DMAX = ДМАКС ## Возвращает максимальное значение среди выделенных записей базы данных.
+DMIN = ДМИН ## Возвращает минимальное значение среди выделенных записей базы данных.
+DPRODUCT = БДПРОИЗВЕД ## Перемножает значения определенного поля в записях базы данных, удовлетворяющих условию.
+DSTDEV = ДСТАНДОТКЛ ## Оценивает стандартное отклонение по выборке для выделенных записей базы данных.
+DSTDEVP = ДСТАНДОТКЛП ## Вычисляет стандартное отклонение по генеральной совокупности для выделенных записей базы данных
+DSUM = БДСУММ ## Суммирует числа в поле для записей базы данных, удовлетворяющих условию.
+DVAR = БДДИСП ## Оценивает дисперсию по выборке из выделенных записей базы данных
+DVARP = БДДИСПП ## Вычисляет дисперсию по генеральной совокупности для выделенных записей базы данных
+
+
+##
+## Date and time functions Функции даты и времени
+##
+DATE = ДАТА ## Возвращает заданную дату в числовом формате.
+DATEVALUE = ДАТАЗНАЧ ## Преобразует дату из текстового формата в числовой формат.
+DAY = ДЕНЬ ## Преобразует дату в числовом формате в день месяца.
+DAYS360 = ДНЕЙ360 ## Вычисляет количество дней между двумя датами на основе 360-дневного года.
+EDATE = ДАТАМЕС ## Возвращает дату в числовом формате, отстоящую на заданное число месяцев вперед или назад от начальной даты.
+EOMONTH = КОНМЕСЯЦА ## Возвращает дату в числовом формате для последнего дня месяца, отстоящего вперед или назад на заданное число месяцев.
+HOUR = ЧАС ## Преобразует дату в числовом формате в часы.
+MINUTE = МИНУТЫ ## Преобразует дату в числовом формате в минуты.
+MONTH = МЕСЯЦ ## Преобразует дату в числовом формате в месяцы.
+NETWORKDAYS = ЧИСТРАБДНИ ## Возвращает количество рабочих дней между двумя датами.
+NOW = ТДАТА ## Возвращает текущую дату и время в числовом формате.
+SECOND = СЕКУНДЫ ## Преобразует дату в числовом формате в секунды.
+TIME = ВРЕМЯ ## Возвращает заданное время в числовом формате.
+TIMEVALUE = ВРЕМЗНАЧ ## Преобразует время из текстового формата в числовой формат.
+TODAY = СЕГОДНЯ ## Возвращает текущую дату в числовом формате.
+WEEKDAY = ДЕНЬНЕД ## Преобразует дату в числовом формате в день недели.
+WEEKNUM = НОМНЕДЕЛИ ## Преобразует числовое представление в число, которое указывает, на какую неделю года приходится указанная дата.
+WORKDAY = РАБДЕНЬ ## Возвращает дату в числовом формате, отстоящую вперед или назад на заданное количество рабочих дней.
+YEAR = ГОД ## Преобразует дату в числовом формате в год.
+YEARFRAC = ДОЛЯГОДА ## Возвращает долю года, которую составляет количество дней между начальной и конечной датами.
+
+
+##
+## Engineering functions Инженерные функции
+##
+BESSELI = БЕССЕЛЬ.I ## Возвращает модифицированную функцию Бесселя In(x).
+BESSELJ = БЕССЕЛЬ.J ## Возвращает функцию Бесселя Jn(x).
+BESSELK = БЕССЕЛЬ.K ## Возвращает модифицированную функцию Бесселя Kn(x).
+BESSELY = БЕССЕЛЬ.Y ## Возвращает функцию Бесселя Yn(x).
+BIN2DEC = ДВ.В.ДЕС ## Преобразует двоичное число в десятичное.
+BIN2HEX = ДВ.В.ШЕСТН ## Преобразует двоичное число в шестнадцатеричное.
+BIN2OCT = ДВ.В.ВОСЬМ ## Преобразует двоичное число в восьмеричное.
+COMPLEX = КОМПЛЕКСН ## Преобразует коэффициенты при вещественной и мнимой частях комплексного числа в комплексное число.
+CONVERT = ПРЕОБР ## Преобразует число из одной системы единиц измерения в другую.
+DEC2BIN = ДЕС.В.ДВ ## Преобразует десятичное число в двоичное.
+DEC2HEX = ДЕС.В.ШЕСТН ## Преобразует десятичное число в шестнадцатеричное.
+DEC2OCT = ДЕС.В.ВОСЬМ ## Преобразует десятичное число в восьмеричное.
+DELTA = ДЕЛЬТА ## Проверяет равенство двух значений.
+ERF = ФОШ ## Возвращает функцию ошибки.
+ERFC = ДФОШ ## Возвращает дополнительную функцию ошибки.
+GESTEP = ПОРОГ ## Проверяет, не превышает ли данное число порогового значения.
+HEX2BIN = ШЕСТН.В.ДВ ## Преобразует шестнадцатеричное число в двоичное.
+HEX2DEC = ШЕСТН.В.ДЕС ## Преобразует шестнадцатеричное число в десятичное.
+HEX2OCT = ШЕСТН.В.ВОСЬМ ## Преобразует шестнадцатеричное число в восьмеричное.
+IMABS = МНИМ.ABS ## Возвращает абсолютную величину (модуль) комплексного числа.
+IMAGINARY = МНИМ.ЧАСТЬ ## Возвращает коэффициент при мнимой части комплексного числа.
+IMARGUMENT = МНИМ.АРГУМЕНТ ## Возвращает значение аргумента комплексного числа (тета) — угол, выраженный в радианах.
+IMCONJUGATE = МНИМ.СОПРЯЖ ## Возвращает комплексно-сопряженное комплексное число.
+IMCOS = МНИМ.COS ## Возвращает косинус комплексного числа.
+IMDIV = МНИМ.ДЕЛ ## Возвращает частное от деления двух комплексных чисел.
+IMEXP = МНИМ.EXP ## Возвращает экспоненту комплексного числа.
+IMLN = МНИМ.LN ## Возвращает натуральный логарифм комплексного числа.
+IMLOG10 = МНИМ.LOG10 ## Возвращает обычный (десятичный) логарифм комплексного числа.
+IMLOG2 = МНИМ.LOG2 ## Возвращает двоичный логарифм комплексного числа.
+IMPOWER = МНИМ.СТЕПЕНЬ ## Возвращает комплексное число, возведенное в целую степень.
+IMPRODUCT = МНИМ.ПРОИЗВЕД ## Возвращает произведение от 2 до 29 комплексных чисел.
+IMREAL = МНИМ.ВЕЩ ## Возвращает коэффициент при вещественной части комплексного числа.
+IMSIN = МНИМ.SIN ## Возвращает синус комплексного числа.
+IMSQRT = МНИМ.КОРЕНЬ ## Возвращает значение квадратного корня из комплексного числа.
+IMSUB = МНИМ.РАЗН ## Возвращает разность двух комплексных чисел.
+IMSUM = МНИМ.СУММ ## Возвращает сумму комплексных чисел.
+OCT2BIN = ВОСЬМ.В.ДВ ## Преобразует восьмеричное число в двоичное.
+OCT2DEC = ВОСЬМ.В.ДЕС ## Преобразует восьмеричное число в десятичное.
+OCT2HEX = ВОСЬМ.В.ШЕСТН ## Преобразует восьмеричное число в шестнадцатеричное.
+
+
+##
+## Financial functions Финансовые функции
+##
+ACCRINT = НАКОПДОХОД ## Возвращает накопленный процент по ценным бумагам с периодической выплатой процентов.
+ACCRINTM = НАКОПДОХОДПОГАШ ## Возвращает накопленный процент по ценным бумагам, проценты по которым выплачиваются в срок погашения.
+AMORDEGRC = АМОРУМ ## Возвращает величину амортизации для каждого периода, используя коэффициент амортизации.
+AMORLINC = АМОРУВ ## Возвращает величину амортизации для каждого периода.
+COUPDAYBS = ДНЕЙКУПОНДО ## Возвращает количество дней от начала действия купона до даты соглашения.
+COUPDAYS = ДНЕЙКУПОН ## Возвращает число дней в периоде купона, содержащем дату соглашения.
+COUPDAYSNC = ДНЕЙКУПОНПОСЛЕ ## Возвращает число дней от даты соглашения до срока следующего купона.
+COUPNCD = ДАТАКУПОНПОСЛЕ ## Возвращает следующую дату купона после даты соглашения.
+COUPNUM = ЧИСЛКУПОН ## Возвращает количество купонов, которые могут быть оплачены между датой соглашения и сроком вступления в силу.
+COUPPCD = ДАТАКУПОНДО ## Возвращает предыдущую дату купона перед датой соглашения.
+CUMIPMT = ОБЩПЛАТ ## Возвращает общую выплату, произведенную между двумя периодическими выплатами.
+CUMPRINC = ОБЩДОХОД ## Возвращает общую выплату по займу между двумя периодами.
+DB = ФУО ## Возвращает величину амортизации актива для заданного периода, рассчитанную методом фиксированного уменьшения остатка.
+DDB = ДДОБ ## Возвращает величину амортизации актива за данный период, используя метод двойного уменьшения остатка или иной явно указанный метод.
+DISC = СКИДКА ## Возвращает норму скидки для ценных бумаг.
+DOLLARDE = РУБЛЬ.ДЕС ## Преобразует цену в рублях, выраженную в виде дроби, в цену в рублях, выраженную десятичным числом.
+DOLLARFR = РУБЛЬ.ДРОБЬ ## Преобразует цену в рублях, выраженную десятичным числом, в цену в рублях, выраженную в виде дроби.
+DURATION = ДЛИТ ## Возвращает ежегодную продолжительность действия ценных бумаг с периодическими выплатами по процентам.
+EFFECT = ЭФФЕКТ ## Возвращает действующие ежегодные процентные ставки.
+FV = БС ## Возвращает будущую стоимость инвестиции.
+FVSCHEDULE = БЗРАСПИС ## Возвращает будущую стоимость первоначальной основной суммы после начисления ряда сложных процентов.
+INTRATE = ИНОРМА ## Возвращает процентную ставку для полностью инвестированных ценных бумаг.
+IPMT = ПРПЛТ ## Возвращает величину выплаты прибыли на вложения за данный период.
+IRR = ВСД ## Возвращает внутреннюю ставку доходности для ряда потоков денежных средств.
+ISPMT = ПРОЦПЛАТ ## Вычисляет выплаты за указанный период инвестиции.
+MDURATION = МДЛИТ ## Возвращает модифицированную длительность Маколея для ценных бумаг с предполагаемой номинальной стоимостью 100 рублей.
+MIRR = МВСД ## Возвращает внутреннюю ставку доходности, при которой положительные и отрицательные денежные потоки имеют разные значения ставки.
+NOMINAL = НОМИНАЛ ## Возвращает номинальную годовую процентную ставку.
+NPER = КПЕР ## Возвращает общее количество периодов выплаты для данного вклада.
+NPV = ЧПС ## Возвращает чистую приведенную стоимость инвестиции, основанной на серии периодических денежных потоков и ставке дисконтирования.
+ODDFPRICE = ЦЕНАПЕРВНЕРЕГ ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг с нерегулярным первым периодом.
+ODDFYIELD = ДОХОДПЕРВНЕРЕГ ## Возвращает доход по ценным бумагам с нерегулярным первым периодом.
+ODDLPRICE = ЦЕНАПОСЛНЕРЕГ ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг с нерегулярным последним периодом.
+ODDLYIELD = ДОХОДПОСЛНЕРЕГ ## Возвращает доход по ценным бумагам с нерегулярным последним периодом.
+PMT = ПЛТ ## Возвращает величину выплаты за один период аннуитета.
+PPMT = ОСПЛТ ## Возвращает величину выплат в погашение основной суммы по инвестиции за заданный период.
+PRICE = ЦЕНА ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг, по которым производится периодическая выплата процентов.
+PRICEDISC = ЦЕНАСКИДКА ## Возвращает цену за 100 рублей номинальной стоимости ценных бумаг, на которые сделана скидка.
+PRICEMAT = ЦЕНАПОГАШ ## Возвращает цену за 100 рублей номинальной стоимости ценных бумаг, проценты по которым выплачиваются в срок погашения.
+PV = ПС ## Возвращает приведенную (к текущему моменту) стоимость инвестиции.
+RATE = СТАВКА ## Возвращает процентную ставку по аннуитету за один период.
+RECEIVED = ПОЛУЧЕНО ## Возвращает сумму, полученную к сроку погашения полностью обеспеченных ценных бумаг.
+SLN = АПЛ ## Возвращает величину линейной амортизации актива за один период.
+SYD = АСЧ ## Возвращает величину амортизации актива за данный период, рассчитанную методом суммы годовых чисел.
+TBILLEQ = РАВНОКЧЕК ## Возвращает эквивалентный облигации доход по казначейскому чеку.
+TBILLPRICE = ЦЕНАКЧЕК ## Возвращает цену за 100 рублей нарицательной стоимости для казначейского чека.
+TBILLYIELD = ДОХОДКЧЕК ## Возвращает доход по казначейскому чеку.
+VDB = ПУО ## Возвращает величину амортизации актива для указанного или частичного периода при использовании метода сокращающегося баланса.
+XIRR = ЧИСТВНДОХ ## Возвращает внутреннюю ставку доходности для графика денежных потоков, которые не обязательно носят периодический характер.
+XNPV = ЧИСТНЗ ## Возвращает чистую приведенную стоимость для денежных потоков, которые не обязательно являются периодическими.
+YIELD = ДОХОД ## Возвращает доход от ценных бумаг, по которым производятся периодические выплаты процентов.
+YIELDDISC = ДОХОДСКИДКА ## Возвращает годовой доход по ценным бумагам, на которые сделана скидка (пример — казначейские чеки).
+YIELDMAT = ДОХОДПОГАШ ## Возвращает годовой доход от ценных бумаг, проценты по которым выплачиваются в срок погашения.
+
+
+##
+## Information functions Информационные функции
+##
+CELL = ЯЧЕЙКА ## Возвращает информацию о формате, расположении или содержимом ячейки.
+ERROR.TYPE = ТИП.ОШИБКИ ## Возвращает числовой код, соответствующий типу ошибки.
+INFO = ИНФОРМ ## Возвращает информацию о текущей операционной среде.
+ISBLANK = ЕПУСТО ## Возвращает значение ИСТИНА, если аргумент является ссылкой на пустую ячейку.
+ISERR = ЕОШ ## Возвращает значение ИСТИНА, если аргумент ссылается на любое значение ошибки, кроме #Н/Д.
+ISERROR = ЕОШИБКА ## Возвращает значение ИСТИНА, если аргумент ссылается на любое значение ошибки.
+ISEVEN = ЕЧЁТН ## Возвращает значение ИСТИНА, если значение аргумента является четным числом.
+ISLOGICAL = ЕЛОГИЧ ## Возвращает значение ИСТИНА, если аргумент ссылается на логическое значение.
+ISNA = ЕНД ## Возвращает значение ИСТИНА, если аргумент ссылается на значение ошибки #Н/Д.
+ISNONTEXT = ЕНЕТЕКСТ ## Возвращает значение ИСТИНА, если значение аргумента не является текстом.
+ISNUMBER = ЕЧИСЛО ## Возвращает значение ИСТИНА, если аргумент ссылается на число.
+ISODD = ЕНЕЧЁТ ## Возвращает значение ИСТИНА, если значение аргумента является нечетным числом.
+ISREF = ЕССЫЛКА ## Возвращает значение ИСТИНА, если значение аргумента является ссылкой.
+ISTEXT = ЕТЕКСТ ## Возвращает значение ИСТИНА, если значение аргумента является текстом.
+N = Ч ## Возвращает значение, преобразованное в число.
+NA = НД ## Возвращает значение ошибки #Н/Д.
+TYPE = ТИП ## Возвращает число, обозначающее тип данных значения.
+
+
+##
+## Logical functions Логические функции
+##
+AND = И ## Renvoie VRAI si tous ses arguments sont VRAI.
+FALSE = ЛОЖЬ ## Возвращает логическое значение ЛОЖЬ.
+IF = ЕСЛИ ## Выполняет проверку условия.
+IFERROR = ЕСЛИОШИБКА ## Возвращает введённое значение, если вычисление по формуле вызывает ошибку; в противном случае функция возвращает результат вычисления.
+NOT = НЕ ## Меняет логическое значение своего аргумента на противоположное.
+OR = ИЛИ ## Возвращает значение ИСТИНА, если хотя бы один аргумент имеет значение ИСТИНА.
+TRUE = ИСТИНА ## Возвращает логическое значение ИСТИНА.
+
+
+##
+## Lookup and reference functions Функции ссылки и поиска
+##
+ADDRESS = АДРЕС ## Возвращает ссылку на отдельную ячейку листа в виде текста.
+AREAS = ОБЛАСТИ ## Возвращает количество областей в ссылке.
+CHOOSE = ВЫБОР ## Выбирает значение из списка значений по индексу.
+COLUMN = СТОЛБЕЦ ## Возвращает номер столбца, на который указывает ссылка.
+COLUMNS = ЧИСЛСТОЛБ ## Возвращает количество столбцов в ссылке.
+HLOOKUP = ГПР ## Ищет в первой строке массива и возвращает значение отмеченной ячейки
+HYPERLINK = ГИПЕРССЫЛКА ## Создает ссылку, открывающую документ, который находится на сервере сети, в интрасети или в Интернете.
+INDEX = ИНДЕКС ## Использует индекс для выбора значения из ссылки или массива.
+INDIRECT = ДВССЫЛ ## Возвращает ссылку, заданную текстовым значением.
+LOOKUP = ПРОСМОТР ## Ищет значения в векторе или массиве.
+MATCH = ПОИСКПОЗ ## Ищет значения в ссылке или массиве.
+OFFSET = СМЕЩ ## Возвращает смещение ссылки относительно заданной ссылки.
+ROW = СТРОКА ## Возвращает номер строки, определяемой ссылкой.
+ROWS = ЧСТРОК ## Возвращает количество строк в ссылке.
+RTD = ДРВ ## Извлекает данные реального времени из программ, поддерживающих автоматизацию COM (Программирование объектов. Стандартное средство для работы с объектами некоторого приложения из другого приложения или средства разработки. Программирование объектов (ранее называемое программированием OLE) является функцией модели COM (Component Object Model, модель компонентных объектов).).
+TRANSPOSE = ТРАНСП ## Возвращает транспонированный массив.
+VLOOKUP = ВПР ## Ищет значение в первом столбце массива и возвращает значение из ячейки в найденной строке и указанном столбце.
+
+
+##
+## Math and trigonometry functions Математические и тригонометрические функции
+##
+ABS = ABS ## Возвращает модуль (абсолютную величину) числа.
+ACOS = ACOS ## Возвращает арккосинус числа.
+ACOSH = ACOSH ## Возвращает гиперболический арккосинус числа.
+ASIN = ASIN ## Возвращает арксинус числа.
+ASINH = ASINH ## Возвращает гиперболический арксинус числа.
+ATAN = ATAN ## Возвращает арктангенс числа.
+ATAN2 = ATAN2 ## Возвращает арктангенс для заданных координат x и y.
+ATANH = ATANH ## Возвращает гиперболический арктангенс числа.
+CEILING = ОКРВВЕРХ ## Округляет число до ближайшего целого или до ближайшего кратного указанному значению.
+COMBIN = ЧИСЛКОМБ ## Возвращает количество комбинаций для заданного числа объектов.
+COS = COS ## Возвращает косинус числа.
+COSH = COSH ## Возвращает гиперболический косинус числа.
+DEGREES = ГРАДУСЫ ## Преобразует радианы в градусы.
+EVEN = ЧЁТН ## Округляет число до ближайшего четного целого.
+EXP = EXP ## Возвращает число e, возведенное в указанную степень.
+FACT = ФАКТР ## Возвращает факториал числа.
+FACTDOUBLE = ДВФАКТР ## Возвращает двойной факториал числа.
+FLOOR = ОКРВНИЗ ## Округляет число до ближайшего меньшего по модулю значения.
+GCD = НОД ## Возвращает наибольший общий делитель.
+INT = ЦЕЛОЕ ## Округляет число до ближайшего меньшего целого.
+LCM = НОК ## Возвращает наименьшее общее кратное.
+LN = LN ## Возвращает натуральный логарифм числа.
+LOG = LOG ## Возвращает логарифм числа по заданному основанию.
+LOG10 = LOG10 ## Возвращает десятичный логарифм числа.
+MDETERM = МОПРЕД ## Возвращает определитель матрицы массива.
+MINVERSE = МОБР ## Возвращает обратную матрицу массива.
+MMULT = МУМНОЖ ## Возвращает произведение матриц двух массивов.
+MOD = ОСТАТ ## Возвращает остаток от деления.
+MROUND = ОКРУГЛТ ## Возвращает число, округленное с требуемой точностью.
+MULTINOMIAL = МУЛЬТИНОМ ## Возвращает мультиномиальный коэффициент множества чисел.
+ODD = НЕЧЁТ ## Округляет число до ближайшего нечетного целого.
+PI = ПИ ## Возвращает число пи.
+POWER = СТЕПЕНЬ ## Возвращает результат возведения числа в степень.
+PRODUCT = ПРОИЗВЕД ## Возвращает произведение аргументов.
+QUOTIENT = ЧАСТНОЕ ## Возвращает целую часть частного при делении.
+RADIANS = РАДИАНЫ ## Преобразует градусы в радианы.
+RAND = СЛЧИС ## Возвращает случайное число в интервале от 0 до 1.
+RANDBETWEEN = СЛУЧМЕЖДУ ## Возвращает случайное число в интервале между двумя заданными числами.
+ROMAN = РИМСКОЕ ## Преобразует арабские цифры в римские в виде текста.
+ROUND = ОКРУГЛ ## Округляет число до указанного количества десятичных разрядов.
+ROUNDDOWN = ОКРУГЛВНИЗ ## Округляет число до ближайшего меньшего по модулю значения.
+ROUNDUP = ОКРУГЛВВЕРХ ## Округляет число до ближайшего большего по модулю значения.
+SERIESSUM = РЯД.СУММ ## Возвращает сумму степенного ряда, вычисленную по формуле.
+SIGN = ЗНАК ## Возвращает знак числа.
+SIN = SIN ## Возвращает синус заданного угла.
+SINH = SINH ## Возвращает гиперболический синус числа.
+SQRT = КОРЕНЬ ## Возвращает положительное значение квадратного корня.
+SQRTPI = КОРЕНЬПИ ## Возвращает квадратный корень из значения выражения (число * ПИ).
+SUBTOTAL = ПРОМЕЖУТОЧНЫЕ.ИТОГИ ## Возвращает промежуточный итог в списке или базе данных.
+SUM = СУММ ## Суммирует аргументы.
+SUMIF = СУММЕСЛИ ## Суммирует ячейки, удовлетворяющие заданному условию.
+SUMIFS = СУММЕСЛИМН ## Суммирует диапазон ячеек, удовлетворяющих нескольким условиям.
+SUMPRODUCT = СУММПРОИЗВ ## Возвращает сумму произведений соответствующих элементов массивов.
+SUMSQ = СУММКВ ## Возвращает сумму квадратов аргументов.
+SUMX2MY2 = СУММРАЗНКВ ## Возвращает сумму разностей квадратов соответствующих значений в двух массивах.
+SUMX2PY2 = СУММСУММКВ ## Возвращает сумму сумм квадратов соответствующих элементов двух массивов.
+SUMXMY2 = СУММКВРАЗН ## Возвращает сумму квадратов разностей соответствующих значений в двух массивах.
+TAN = TAN ## Возвращает тангенс числа.
+TANH = TANH ## Возвращает гиперболический тангенс числа.
+TRUNC = ОТБР ## Отбрасывает дробную часть числа.
+
+
+##
+## Statistical functions Статистические функции
+##
+AVEDEV = СРОТКЛ ## Возвращает среднее арифметическое абсолютных значений отклонений точек данных от среднего.
+AVERAGE = СРЗНАЧ ## Возвращает среднее арифметическое аргументов.
+AVERAGEA = СРЗНАЧА ## Возвращает среднее арифметическое аргументов, включая числа, текст и логические значения.
+AVERAGEIF = СРЗНАЧЕСЛИ ## Возвращает среднее значение (среднее арифметическое) всех ячеек в диапазоне, которые удовлетворяют данному условию.
+AVERAGEIFS = СРЗНАЧЕСЛИМН ## Возвращает среднее значение (среднее арифметическое) всех ячеек, которые удовлетворяют нескольким условиям.
+BETADIST = БЕТАРАСП ## Возвращает интегральную функцию бета-распределения.
+BETAINV = БЕТАОБР ## Возвращает обратную интегральную функцию указанного бета-распределения.
+BINOMDIST = БИНОМРАСП ## Возвращает отдельное значение биномиального распределения.
+CHIDIST = ХИ2РАСП ## Возвращает одностороннюю вероятность распределения хи-квадрат.
+CHIINV = ХИ2ОБР ## Возвращает обратное значение односторонней вероятности распределения хи-квадрат.
+CHITEST = ХИ2ТЕСТ ## Возвращает тест на независимость.
+CONFIDENCE = ДОВЕРИТ ## Возвращает доверительный интервал для среднего значения по генеральной совокупности.
+CORREL = КОРРЕЛ ## Возвращает коэффициент корреляции между двумя множествами данных.
+COUNT = СЧЁТ ## Подсчитывает количество чисел в списке аргументов.
+COUNTA = СЧЁТЗ ## Подсчитывает количество значений в списке аргументов.
+COUNTBLANK = СЧИТАТЬПУСТОТЫ ## Подсчитывает количество пустых ячеек в диапазоне
+COUNTIF = СЧЁТЕСЛИ ## Подсчитывает количество ячеек в диапазоне, удовлетворяющих заданному условию
+COUNTIFS = СЧЁТЕСЛИМН ## Подсчитывает количество ячеек внутри диапазона, удовлетворяющих нескольким условиям.
+COVAR = КОВАР ## Возвращает ковариацию, среднее произведений парных отклонений
+CRITBINOM = КРИТБИНОМ ## Возвращает наименьшее значение, для которого интегральное биномиальное распределение меньше или равно заданному критерию.
+DEVSQ = КВАДРОТКЛ ## Возвращает сумму квадратов отклонений.
+EXPONDIST = ЭКСПРАСП ## Возвращает экспоненциальное распределение.
+FDIST = FРАСП ## Возвращает F-распределение вероятности.
+FINV = FРАСПОБР ## Возвращает обратное значение для F-распределения вероятности.
+FISHER = ФИШЕР ## Возвращает преобразование Фишера.
+FISHERINV = ФИШЕРОБР ## Возвращает обратное преобразование Фишера.
+FORECAST = ПРЕДСКАЗ ## Возвращает значение линейного тренда.
+FREQUENCY = ЧАСТОТА ## Возвращает распределение частот в виде вертикального массива.
+FTEST = ФТЕСТ ## Возвращает результат F-теста.
+GAMMADIST = ГАММАРАСП ## Возвращает гамма-распределение.
+GAMMAINV = ГАММАОБР ## Возвращает обратное гамма-распределение.
+GAMMALN = ГАММАНЛОГ ## Возвращает натуральный логарифм гамма функции, Γ(x).
+GEOMEAN = СРГЕОМ ## Возвращает среднее геометрическое.
+GROWTH = РОСТ ## Возвращает значения в соответствии с экспоненциальным трендом.
+HARMEAN = СРГАРМ ## Возвращает среднее гармоническое.
+HYPGEOMDIST = ГИПЕРГЕОМЕТ ## Возвращает гипергеометрическое распределение.
+INTERCEPT = ОТРЕЗОК ## Возвращает отрезок, отсекаемый на оси линией линейной регрессии.
+KURT = ЭКСЦЕСС ## Возвращает эксцесс множества данных.
+LARGE = НАИБОЛЬШИЙ ## Возвращает k-ое наибольшее значение в множестве данных.
+LINEST = ЛИНЕЙН ## Возвращает параметры линейного тренда.
+LOGEST = ЛГРФПРИБЛ ## Возвращает параметры экспоненциального тренда.
+LOGINV = ЛОГНОРМОБР ## Возвращает обратное логарифмическое нормальное распределение.
+LOGNORMDIST = ЛОГНОРМРАСП ## Возвращает интегральное логарифмическое нормальное распределение.
+MAX = МАКС ## Возвращает наибольшее значение в списке аргументов.
+MAXA = МАКСА ## Возвращает наибольшее значение в списке аргументов, включая числа, текст и логические значения.
+MEDIAN = МЕДИАНА ## Возвращает медиану заданных чисел.
+MIN = МИН ## Возвращает наименьшее значение в списке аргументов.
+MINA = МИНА ## Возвращает наименьшее значение в списке аргументов, включая числа, текст и логические значения.
+MODE = МОДА ## Возвращает значение моды множества данных.
+NEGBINOMDIST = ОТРБИНОМРАСП ## Возвращает отрицательное биномиальное распределение.
+NORMDIST = НОРМРАСП ## Возвращает нормальную функцию распределения.
+NORMINV = НОРМОБР ## Возвращает обратное нормальное распределение.
+NORMSDIST = НОРМСТРАСП ## Возвращает стандартное нормальное интегральное распределение.
+NORMSINV = НОРМСТОБР ## Возвращает обратное значение стандартного нормального распределения.
+PEARSON = ПИРСОН ## Возвращает коэффициент корреляции Пирсона.
+PERCENTILE = ПЕРСЕНТИЛЬ ## Возвращает k-ую персентиль для значений диапазона.
+PERCENTRANK = ПРОЦЕНТРАНГ ## Возвращает процентную норму значения в множестве данных.
+PERMUT = ПЕРЕСТ ## Возвращает количество перестановок для заданного числа объектов.
+POISSON = ПУАССОН ## Возвращает распределение Пуассона.
+PROB = ВЕРОЯТНОСТЬ ## Возвращает вероятность того, что значение из диапазона находится внутри заданных пределов.
+QUARTILE = КВАРТИЛЬ ## Возвращает квартиль множества данных.
+RANK = РАНГ ## Возвращает ранг числа в списке чисел.
+RSQ = КВПИРСОН ## Возвращает квадрат коэффициента корреляции Пирсона.
+SKEW = СКОС ## Возвращает асимметрию распределения.
+SLOPE = НАКЛОН ## Возвращает наклон линии линейной регрессии.
+SMALL = НАИМЕНЬШИЙ ## Возвращает k-ое наименьшее значение в множестве данных.
+STANDARDIZE = НОРМАЛИЗАЦИЯ ## Возвращает нормализованное значение.
+STDEV = СТАНДОТКЛОН ## Оценивает стандартное отклонение по выборке.
+STDEVA = СТАНДОТКЛОНА ## Оценивает стандартное отклонение по выборке, включая числа, текст и логические значения.
+STDEVP = СТАНДОТКЛОНП ## Вычисляет стандартное отклонение по генеральной совокупности.
+STDEVPA = СТАНДОТКЛОНПА ## Вычисляет стандартное отклонение по генеральной совокупности, включая числа, текст и логические значения.
+STEYX = СТОШYX ## Возвращает стандартную ошибку предсказанных значений y для каждого значения x в регрессии.
+TDIST = СТЬЮДРАСП ## Возвращает t-распределение Стьюдента.
+TINV = СТЬЮДРАСПОБР ## Возвращает обратное t-распределение Стьюдента.
+TREND = ТЕНДЕНЦИЯ ## Возвращает значения в соответствии с линейным трендом.
+TRIMMEAN = УРЕЗСРЕДНЕЕ ## Возвращает среднее внутренности множества данных.
+TTEST = ТТЕСТ ## Возвращает вероятность, соответствующую критерию Стьюдента.
+VAR = ДИСП ## Оценивает дисперсию по выборке.
+VARA = ДИСПА ## Оценивает дисперсию по выборке, включая числа, текст и логические значения.
+VARP = ДИСПР ## Вычисляет дисперсию для генеральной совокупности.
+VARPA = ДИСПРА ## Вычисляет дисперсию для генеральной совокупности, включая числа, текст и логические значения.
+WEIBULL = ВЕЙБУЛЛ ## Возвращает распределение Вейбулла.
+ZTEST = ZТЕСТ ## Возвращает двустороннее P-значение z-теста.
+
+
+##
+## Text functions Текстовые функции
+##
+ASC = ASC ## Для языков с двухбайтовыми наборами знаков (например, катакана) преобразует полноширинные (двухбайтовые) знаки в полуширинные (однобайтовые).
+BAHTTEXT = БАТТЕКСТ ## Преобразует число в текст, используя денежный формат ß (БАТ).
+CHAR = СИМВОЛ ## Возвращает знак с заданным кодом.
+CLEAN = ПЕЧСИМВ ## Удаляет все непечатаемые знаки из текста.
+CODE = КОДСИМВ ## Возвращает числовой код первого знака в текстовой строке.
+CONCATENATE = СЦЕПИТЬ ## Объединяет несколько текстовых элементов в один.
+DOLLAR = РУБЛЬ ## Преобразует число в текст, используя денежный формат.
+EXACT = СОВПАД ## Проверяет идентичность двух текстовых значений.
+FIND = НАЙТИ ## Ищет вхождения одного текстового значения в другом (с учетом регистра).
+FINDB = НАЙТИБ ## Ищет вхождения одного текстового значения в другом (с учетом регистра).
+FIXED = ФИКСИРОВАННЫЙ ## Форматирует число и преобразует его в текст с заданным числом десятичных знаков.
+JIS = JIS ## Для языков с двухбайтовыми наборами знаков (например, катакана) преобразует полуширинные (однобайтовые) знаки в текстовой строке в полноширинные (двухбайтовые).
+LEFT = ЛЕВСИМВ ## Возвращает крайние слева знаки текстового значения.
+LEFTB = ЛЕВБ ## Возвращает крайние слева знаки текстового значения.
+LEN = ДЛСТР ## Возвращает количество знаков в текстовой строке.
+LENB = ДЛИНБ ## Возвращает количество знаков в текстовой строке.
+LOWER = СТРОЧН ## Преобразует все буквы текста в строчные.
+MID = ПСТР ## Возвращает заданное число знаков из строки текста, начиная с указанной позиции.
+MIDB = ПСТРБ ## Возвращает заданное число знаков из строки текста, начиная с указанной позиции.
+PHONETIC = PHONETIC ## Извлекает фонетические (фуригана) знаки из текстовой строки.
+PROPER = ПРОПНАЧ ## Преобразует первую букву в каждом слове текста в прописную.
+REPLACE = ЗАМЕНИТЬ ## Заменяет знаки в тексте.
+REPLACEB = ЗАМЕНИТЬБ ## Заменяет знаки в тексте.
+REPT = ПОВТОР ## Повторяет текст заданное число раз.
+RIGHT = ПРАВСИМВ ## Возвращает крайние справа знаки текстовой строки.
+RIGHTB = ПРАВБ ## Возвращает крайние справа знаки текстовой строки.
+SEARCH = ПОИСК ## Ищет вхождения одного текстового значения в другом (без учета регистра).
+SEARCHB = ПОИСКБ ## Ищет вхождения одного текстового значения в другом (без учета регистра).
+SUBSTITUTE = ПОДСТАВИТЬ ## Заменяет в текстовой строке старый текст новым.
+T = Т ## Преобразует аргументы в текст.
+TEXT = ТЕКСТ ## Форматирует число и преобразует его в текст.
+TRIM = СЖПРОБЕЛЫ ## Удаляет из текста пробелы.
+UPPER = ПРОПИСН ## Преобразует все буквы текста в прописные.
+VALUE = ЗНАЧЕН ## Преобразует текстовый аргумент в число.
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config
new file mode 100755
index 0000000..bf72cc4
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config
@@ -0,0 +1,24 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = kr
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #Skärning!
+DIV0 = #Division/0!
+VALUE = #Värdefel!
+REF = #Referens!
+NAME = #Namn?
+NUM = #Ogiltigt!
+NA = #Saknas!
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions
new file mode 100755
index 0000000..73b2deb
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions
@@ -0,0 +1,408 @@
+##
+## Add-in and Automation functions Tilläggs- och automatiseringsfunktioner
+##
+GETPIVOTDATA = HÄMTA.PIVOTDATA ## Returnerar data som lagrats i en pivottabellrapport
+
+
+##
+## Cube functions Kubfunktioner
+##
+CUBEKPIMEMBER = KUBKPIMEDLEM ## Returnerar namn, egenskap och mått för en KPI och visar namnet och egenskapen i cellen. En KPI, eller prestandaindikator, är ett kvantifierbart mått, t.ex. månatlig bruttovinst eller personalomsättning per kvartal, som används för att analysera ett företags resultat.
+CUBEMEMBER = KUBMEDLEM ## Returnerar en medlem eller ett par i en kubhierarki. Används för att verifiera att medlemmen eller paret finns i kuben.
+CUBEMEMBERPROPERTY = KUBMEDLEMSEGENSKAP ## Returnerar värdet för en medlemsegenskap i kuben. Används för att verifiera att ett medlemsnamn finns i kuben, samt för att returnera den angivna egenskapen för medlemmen.
+CUBERANKEDMEMBER = KUBRANGORDNADMEDLEM ## Returnerar den n:te, eller rangordnade, medlemmen i en uppsättning. Används för att returnera ett eller flera element i en uppsättning, till exempelvis den bästa försäljaren eller de tio bästa eleverna.
+CUBESET = KUBINSTÄLLNING ## Definierar en beräknad uppsättning medlemmar eller par genom att skicka ett bestämt uttryck till kuben på servern, som skapar uppsättningen och sedan returnerar den till Microsoft Office Excel.
+CUBESETCOUNT = KUBINSTÄLLNINGANTAL ## Returnerar antalet objekt i en uppsättning.
+CUBEVALUE = KUBVÄRDE ## Returnerar ett mängdvärde från en kub.
+
+
+##
+## Database functions Databasfunktioner
+##
+DAVERAGE = DMEDEL ## Returnerar medelvärdet av databasposterna
+DCOUNT = DANTAL ## Räknar antalet celler som innehåller tal i en databas
+DCOUNTA = DANTALV ## Räknar ifyllda celler i en databas
+DGET = DHÄMTA ## Hämtar en enstaka post från en databas som uppfyller de angivna villkoren
+DMAX = DMAX ## Returnerar det största värdet från databasposterna
+DMIN = DMIN ## Returnerar det minsta värdet från databasposterna
+DPRODUCT = DPRODUKT ## Multiplicerar värdena i ett visst fält i poster som uppfyller villkoret
+DSTDEV = DSTDAV ## Uppskattar standardavvikelsen baserat på ett urval av databasposterna
+DSTDEVP = DSTDAVP ## Beräknar standardavvikelsen utifrån hela populationen av valda databasposter
+DSUM = DSUMMA ## Summerar talen i kolumnfält i databasposter som uppfyller villkoret
+DVAR = DVARIANS ## Uppskattar variansen baserat på ett urval av databasposterna
+DVARP = DVARIANSP ## Beräknar variansen utifrån hela populationen av valda databasposter
+
+
+##
+## Date and time functions Tid- och datumfunktioner
+##
+DATE = DATUM ## Returnerar ett serienummer för ett visst datum
+DATEVALUE = DATUMVÄRDE ## Konverterar ett datum i textformat till ett serienummer
+DAY = DAG ## Konverterar ett serienummer till dag i månaden
+DAYS360 = DAGAR360 ## Beräknar antalet dagar mellan två datum baserat på ett 360-dagarsår
+EDATE = EDATUM ## Returnerar serienumret för ett datum som infaller ett visst antal månader före eller efter startdatumet
+EOMONTH = SLUTMÅNAD ## Returnerar serienumret för sista dagen i månaden ett visst antal månader tidigare eller senare
+HOUR = TIMME ## Konverterar ett serienummer till en timme
+MINUTE = MINUT ## Konverterar ett serienummer till en minut
+MONTH = MÅNAD ## Konverterar ett serienummer till en månad
+NETWORKDAYS = NETTOARBETSDAGAR ## Returnerar antalet hela arbetsdagar mellan två datum
+NOW = NU ## Returnerar serienumret för dagens datum och aktuell tid
+SECOND = SEKUND ## Konverterar ett serienummer till en sekund
+TIME = KLOCKSLAG ## Returnerar serienumret för en viss tid
+TIMEVALUE = TIDVÄRDE ## Konverterar en tid i textformat till ett serienummer
+TODAY = IDAG ## Returnerar serienumret för dagens datum
+WEEKDAY = VECKODAG ## Konverterar ett serienummer till en dag i veckan
+WEEKNUM = VECKONR ## Konverterar ett serienummer till ett veckonummer
+WORKDAY = ARBETSDAGAR ## Returnerar serienumret för ett datum ett visst antal arbetsdagar tidigare eller senare
+YEAR = ÅR ## Konverterar ett serienummer till ett år
+YEARFRAC = ÅRDEL ## Returnerar en del av ett år som representerar antalet hela dagar mellan start- och slutdatum
+
+
+##
+## Engineering functions Tekniska funktioner
+##
+BESSELI = BESSELI ## Returnerar den modifierade Bessel-funktionen In(x)
+BESSELJ = BESSELJ ## Returnerar Bessel-funktionen Jn(x)
+BESSELK = BESSELK ## Returnerar den modifierade Bessel-funktionen Kn(x)
+BESSELY = BESSELY ## Returnerar Bessel-funktionen Yn(x)
+BIN2DEC = BIN.TILL.DEC ## Omvandlar ett binärt tal till decimalt
+BIN2HEX = BIN.TILL.HEX ## Omvandlar ett binärt tal till hexadecimalt
+BIN2OCT = BIN.TILL.OKT ## Omvandlar ett binärt tal till oktalt
+COMPLEX = KOMPLEX ## Omvandlar reella och imaginära koefficienter till ett komplext tal
+CONVERT = KONVERTERA ## Omvandlar ett tal från ett måttsystem till ett annat
+DEC2BIN = DEC.TILL.BIN ## Omvandlar ett decimalt tal till binärt
+DEC2HEX = DEC.TILL.HEX ## Omvandlar ett decimalt tal till hexadecimalt
+DEC2OCT = DEC.TILL.OKT ## Omvandlar ett decimalt tal till oktalt
+DELTA = DELTA ## Testar om två värden är lika
+ERF = FELF ## Returnerar felfunktionen
+ERFC = FELFK ## Returnerar den komplementära felfunktionen
+GESTEP = SLSTEG ## Testar om ett tal är större än ett tröskelvärde
+HEX2BIN = HEX.TILL.BIN ## Omvandlar ett hexadecimalt tal till binärt
+HEX2DEC = HEX.TILL.DEC ## Omvandlar ett hexadecimalt tal till decimalt
+HEX2OCT = HEX.TILL.OKT ## Omvandlar ett hexadecimalt tal till oktalt
+IMABS = IMABS ## Returnerar absolutvärdet (modulus) för ett komplext tal
+IMAGINARY = IMAGINÄR ## Returnerar den imaginära koefficienten för ett komplext tal
+IMARGUMENT = IMARGUMENT ## Returnerar det komplexa talets argument, en vinkel uttryckt i radianer
+IMCONJUGATE = IMKONJUGAT ## Returnerar det komplexa talets konjugat
+IMCOS = IMCOS ## Returnerar cosinus för ett komplext tal
+IMDIV = IMDIV ## Returnerar kvoten för två komplexa tal
+IMEXP = IMEUPPHÖJT ## Returnerar exponenten för ett komplext tal
+IMLN = IMLN ## Returnerar den naturliga logaritmen för ett komplext tal
+IMLOG10 = IMLOG10 ## Returnerar 10-logaritmen för ett komplext tal
+IMLOG2 = IMLOG2 ## Returnerar 2-logaritmen för ett komplext tal
+IMPOWER = IMUPPHÖJT ## Returnerar ett komplext tal upphöjt till en exponent
+IMPRODUCT = IMPRODUKT ## Returnerar produkten av komplexa tal
+IMREAL = IMREAL ## Returnerar den reella koefficienten för ett komplext tal
+IMSIN = IMSIN ## Returnerar sinus för ett komplext tal
+IMSQRT = IMROT ## Returnerar kvadratroten av ett komplext tal
+IMSUB = IMDIFF ## Returnerar differensen mellan två komplexa tal
+IMSUM = IMSUM ## Returnerar summan av komplexa tal
+OCT2BIN = OKT.TILL.BIN ## Omvandlar ett oktalt tal till binärt
+OCT2DEC = OKT.TILL.DEC ## Omvandlar ett oktalt tal till decimalt
+OCT2HEX = OKT.TILL.HEX ## Omvandlar ett oktalt tal till hexadecimalt
+
+
+##
+## Financial functions Finansiella funktioner
+##
+ACCRINT = UPPLRÄNTA ## Returnerar den upplupna räntan för värdepapper med periodisk ränta
+ACCRINTM = UPPLOBLRÄNTA ## Returnerar den upplupna räntan för ett värdepapper som ger avkastning på förfallodagen
+AMORDEGRC = AMORDEGRC ## Returnerar avskrivningen för varje redovisningsperiod med hjälp av en avskrivningskoefficient
+AMORLINC = AMORLINC ## Returnerar avskrivningen för varje redovisningsperiod
+COUPDAYBS = KUPDAGBB ## Returnerar antal dagar från början av kupongperioden till likviddagen
+COUPDAYS = KUPDAGARS ## Returnerar antalet dagar i kupongperioden som innehåller betalningsdatumet
+COUPDAYSNC = KUPDAGNK ## Returnerar antalet dagar från betalningsdatumet till nästa kupongdatum
+COUPNCD = KUPNKD ## Returnerar nästa kupongdatum efter likviddagen
+COUPNUM = KUPANT ## Returnerar kuponger som förfaller till betalning mellan likviddagen och förfallodagen
+COUPPCD = KUPFKD ## Returnerar föregående kupongdatum före likviddagen
+CUMIPMT = KUMRÄNTA ## Returnerar den ackumulerade räntan som betalats mellan två perioder
+CUMPRINC = KUMPRIS ## Returnerar det ackumulerade kapitalbeloppet som betalats på ett lån mellan två perioder
+DB = DB ## Returnerar avskrivningen för en tillgång under en angiven tid enligt metoden för fast degressiv avskrivning
+DDB = DEGAVSKR ## Returnerar en tillgångs värdeminskning under en viss period med hjälp av dubbel degressiv avskrivning eller någon annan metod som du anger
+DISC = DISK ## Returnerar diskonteringsräntan för ett värdepapper
+DOLLARDE = DECTAL ## Omvandlar ett pris uttryckt som ett bråk till ett decimaltal
+DOLLARFR = BRÅK ## Omvandlar ett pris i kronor uttryckt som ett decimaltal till ett bråk
+DURATION = LÖPTID ## Returnerar den årliga löptiden för en säkerhet med periodiska räntebetalningar
+EFFECT = EFFRÄNTA ## Returnerar den årliga effektiva räntesatsen
+FV = SLUTVÄRDE ## Returnerar det framtida värdet på en investering
+FVSCHEDULE = FÖRRÄNTNING ## Returnerar det framtida värdet av ett begynnelsekapital beräknat på olika räntenivåer
+INTRATE = ÅRSRÄNTA ## Returnerar räntesatsen för ett betalt värdepapper
+IPMT = RBETALNING ## Returnerar räntedelen av en betalning för en given period
+IRR = IR ## Returnerar internräntan för en serie betalningar
+ISPMT = RALÅN ## Beräknar räntan som har betalats under en specifik betalningsperiod
+MDURATION = MLÖPTID ## Returnerar den modifierade Macauley-löptiden för ett värdepapper med det antagna nominella värdet 100 kr
+MIRR = MODIR ## Returnerar internräntan där positiva och negativa betalningar finansieras med olika räntor
+NOMINAL = NOMRÄNTA ## Returnerar den årliga nominella räntesatsen
+NPER = PERIODER ## Returnerar antalet perioder för en investering
+NPV = NETNUVÄRDE ## Returnerar nuvärdet av en serie periodiska betalningar vid en given diskonteringsränta
+ODDFPRICE = UDDAFPRIS ## Returnerar priset per 100 kr nominellt värde för ett värdepapper med en udda första period
+ODDFYIELD = UDDAFAVKASTNING ## Returnerar avkastningen för en säkerhet med en udda första period
+ODDLPRICE = UDDASPRIS ## Returnerar priset per 100 kr nominellt värde för ett värdepapper med en udda sista period
+ODDLYIELD = UDDASAVKASTNING ## Returnerar avkastningen för en säkerhet med en udda sista period
+PMT = BETALNING ## Returnerar den periodiska betalningen för en annuitet
+PPMT = AMORT ## Returnerar amorteringsdelen av en annuitetsbetalning för en given period
+PRICE = PRIS ## Returnerar priset per 100 kr nominellt värde för ett värdepapper som ger periodisk ränta
+PRICEDISC = PRISDISK ## Returnerar priset per 100 kr nominellt värde för ett diskonterat värdepapper
+PRICEMAT = PRISFÖRF ## Returnerar priset per 100 kr nominellt värde för ett värdepapper som ger ränta på förfallodagen
+PV = PV ## Returnerar nuvärdet av en serie lika stora periodiska betalningar
+RATE = RÄNTA ## Returnerar räntesatsen per period i en annuitet
+RECEIVED = BELOPP ## Returnerar beloppet som utdelas på förfallodagen för ett betalat värdepapper
+SLN = LINAVSKR ## Returnerar den linjära avskrivningen för en tillgång under en period
+SYD = ÅRSAVSKR ## Returnerar den årliga avskrivningssumman för en tillgång under en angiven period
+TBILLEQ = SSVXEKV ## Returnerar avkastningen motsvarande en obligation för en statsskuldväxel
+TBILLPRICE = SSVXPRIS ## Returnerar priset per 100 kr nominellt värde för en statsskuldväxel
+TBILLYIELD = SSVXRÄNTA ## Returnerar avkastningen för en statsskuldväxel
+VDB = VDEGRAVSKR ## Returnerar avskrivningen för en tillgång under en angiven period (med degressiv avskrivning)
+XIRR = XIRR ## Returnerar internräntan för en serie betalningar som inte nödvändigtvis är periodiska
+XNPV = XNUVÄRDE ## Returnerar det nuvarande nettovärdet för en serie betalningar som inte nödvändigtvis är periodiska
+YIELD = NOMAVK ## Returnerar avkastningen för ett värdepapper som ger periodisk ränta
+YIELDDISC = NOMAVKDISK ## Returnerar den årliga avkastningen för diskonterade värdepapper, exempelvis en statsskuldväxel
+YIELDMAT = NOMAVKFÖRF ## Returnerar den årliga avkastningen för ett värdepapper som ger ränta på förfallodagen
+
+
+##
+## Information functions Informationsfunktioner
+##
+CELL = CELL ## Returnerar information om formatering, plats och innehåll i en cell
+ERROR.TYPE = FEL.TYP ## Returnerar ett tal som motsvarar ett felvärde
+INFO = INFO ## Returnerar information om operativsystemet
+ISBLANK = ÄRREF ## Returnerar SANT om värdet är tomt
+ISERR = Ä ## Returnerar SANT om värdet är ett felvärde annat än #SAKNAS!
+ISERROR = ÄRFEL ## Returnerar SANT om värdet är ett felvärde
+ISEVEN = ÄRJÄMN ## Returnerar SANT om talet är jämnt
+ISLOGICAL = ÄREJTEXT ## Returnerar SANT om värdet är ett logiskt värde
+ISNA = ÄRLOGISK ## Returnerar SANT om värdet är felvärdet #SAKNAS!
+ISNONTEXT = ÄRSAKNAD ## Returnerar SANT om värdet inte är text
+ISNUMBER = ÄRTAL ## Returnerar SANT om värdet är ett tal
+ISODD = ÄRUDDA ## Returnerar SANT om talet är udda
+ISREF = ÄRTOM ## Returnerar SANT om värdet är en referens
+ISTEXT = ÄRTEXT ## Returnerar SANT om värdet är text
+N = N ## Returnerar ett värde omvandlat till ett tal
+NA = SAKNAS ## Returnerar felvärdet #SAKNAS!
+TYPE = VÄRDETYP ## Returnerar ett tal som anger värdets datatyp
+
+
+##
+## Logical functions Logiska funktioner
+##
+AND = OCH ## Returnerar SANT om alla argument är sanna
+FALSE = FALSKT ## Returnerar det logiska värdet FALSKT
+IF = OM ## Anger vilket logiskt test som ska utföras
+IFERROR = OMFEL ## Returnerar ett värde som du anger om en formel utvärderar till ett fel; annars returneras resultatet av formeln
+NOT = ICKE ## Inverterar logiken för argumenten
+OR = ELLER ## Returnerar SANT om något argument är SANT
+TRUE = SANT ## Returnerar det logiska värdet SANT
+
+
+##
+## Lookup and reference functions Sök- och referensfunktioner
+##
+ADDRESS = ADRESS ## Returnerar en referens som text till en enstaka cell i ett kalkylblad
+AREAS = OMRÅDEN ## Returnerar antalet områden i en referens
+CHOOSE = VÄLJ ## Väljer ett värde i en lista över värden
+COLUMN = KOLUMN ## Returnerar kolumnnumret för en referens
+COLUMNS = KOLUMNER ## Returnerar antalet kolumner i en referens
+HLOOKUP = LETAKOLUMN ## Söker i den översta raden i en matris och returnerar värdet för angiven cell
+HYPERLINK = HYPERLÄNK ## Skapar en genväg eller ett hopp till ett dokument i nätverket, i ett intranät eller på Internet
+INDEX = INDEX ## Använder ett index för ett välja ett värde i en referens eller matris
+INDIRECT = INDIREKT ## Returnerar en referens som anges av ett textvärde
+LOOKUP = LETAUPP ## Letar upp värden i en vektor eller matris
+MATCH = PASSA ## Letar upp värden i en referens eller matris
+OFFSET = FÖRSKJUTNING ## Returnerar en referens förskjuten i förhållande till en given referens
+ROW = RAD ## Returnerar radnumret för en referens
+ROWS = RADER ## Returnerar antalet rader i en referens
+RTD = RTD ## Hämtar realtidsdata från ett program som stöder COM-automation (Automation: Ett sätt att arbeta med ett programs objekt från ett annat program eller utvecklingsverktyg. Detta kallades tidigare för OLE Automation, och är en branschstandard och ingår i Component Object Model (COM).)
+TRANSPOSE = TRANSPONERA ## Transponerar en matris
+VLOOKUP = LETARAD ## Letar i den första kolumnen i en matris och flyttar över raden för att returnera värdet för en cell
+
+
+##
+## Math and trigonometry functions Matematiska och trigonometriska funktioner
+##
+ABS = ABS ## Returnerar absolutvärdet av ett tal
+ACOS = ARCCOS ## Returnerar arcus cosinus för ett tal
+ACOSH = ARCCOSH ## Returnerar inverterad hyperbolisk cosinus för ett tal
+ASIN = ARCSIN ## Returnerar arcus cosinus för ett tal
+ASINH = ARCSINH ## Returnerar hyperbolisk arcus sinus för ett tal
+ATAN = ARCTAN ## Returnerar arcus tangens för ett tal
+ATAN2 = ARCTAN2 ## Returnerar arcus tangens för en x- och en y- koordinat
+ATANH = ARCTANH ## Returnerar hyperbolisk arcus tangens för ett tal
+CEILING = RUNDA.UPP ## Avrundar ett tal till närmaste heltal eller närmaste signifikanta multipel
+COMBIN = KOMBIN ## Returnerar antalet kombinationer för ett givet antal objekt
+COS = COS ## Returnerar cosinus för ett tal
+COSH = COSH ## Returnerar hyperboliskt cosinus för ett tal
+DEGREES = GRADER ## Omvandlar radianer till grader
+EVEN = JÄMN ## Avrundar ett tal uppåt till närmaste heltal
+EXP = EXP ## Returnerar e upphöjt till ett givet tal
+FACT = FAKULTET ## Returnerar fakulteten för ett tal
+FACTDOUBLE = DUBBELFAKULTET ## Returnerar dubbelfakulteten för ett tal
+FLOOR = RUNDA.NED ## Avrundar ett tal nedåt mot noll
+GCD = SGD ## Returnerar den största gemensamma nämnaren
+INT = HELTAL ## Avrundar ett tal nedåt till närmaste heltal
+LCM = MGM ## Returnerar den minsta gemensamma multipeln
+LN = LN ## Returnerar den naturliga logaritmen för ett tal
+LOG = LOG ## Returnerar logaritmen för ett tal för en given bas
+LOG10 = LOG10 ## Returnerar 10-logaritmen för ett tal
+MDETERM = MDETERM ## Returnerar matrisen som är avgörandet av en matris
+MINVERSE = MINVERT ## Returnerar matrisinversen av en matris
+MMULT = MMULT ## Returnerar matrisprodukten av två matriser
+MOD = REST ## Returnerar resten vid en division
+MROUND = MAVRUNDA ## Returnerar ett tal avrundat till en given multipel
+MULTINOMIAL = MULTINOMIAL ## Returnerar multinomialen för en uppsättning tal
+ODD = UDDA ## Avrundar ett tal uppåt till närmaste udda heltal
+PI = PI ## Returnerar värdet pi
+POWER = UPPHÖJT.TILL ## Returnerar resultatet av ett tal upphöjt till en exponent
+PRODUCT = PRODUKT ## Multiplicerar argumenten
+QUOTIENT = KVOT ## Returnerar heltalsdelen av en division
+RADIANS = RADIANER ## Omvandlar grader till radianer
+RAND = SLUMP ## Returnerar ett slumptal mellan 0 och 1
+RANDBETWEEN = SLUMP.MELLAN ## Returnerar ett slumptal mellan de tal som du anger
+ROMAN = ROMERSK ## Omvandlar vanliga (arabiska) siffror till romerska som text
+ROUND = AVRUNDA ## Avrundar ett tal till ett angivet antal siffror
+ROUNDDOWN = AVRUNDA.NEDÅT ## Avrundar ett tal nedåt mot noll
+ROUNDUP = AVRUNDA.UPPÅT ## Avrundar ett tal uppåt, från noll
+SERIESSUM = SERIESUMMA ## Returnerar summan av en potensserie baserat på formeln
+SIGN = TECKEN ## Returnerar tecknet för ett tal
+SIN = SIN ## Returnerar sinus för en given vinkel
+SINH = SINH ## Returnerar hyperbolisk sinus för ett tal
+SQRT = ROT ## Returnerar den positiva kvadratroten
+SQRTPI = ROTPI ## Returnerar kvadratroten för (tal * pi)
+SUBTOTAL = DELSUMMA ## Returnerar en delsumma i en lista eller databas
+SUM = SUMMA ## Summerar argumenten
+SUMIF = SUMMA.OM ## Summerar celler enligt ett angivet villkor
+SUMIFS = SUMMA.OMF ## Lägger till cellerna i ett område som uppfyller flera kriterier
+SUMPRODUCT = PRODUKTSUMMA ## Returnerar summan av produkterna i motsvarande matriskomponenter
+SUMSQ = KVADRATSUMMA ## Returnerar summan av argumentens kvadrater
+SUMX2MY2 = SUMMAX2MY2 ## Returnerar summan av differensen mellan kvadraterna för motsvarande värden i två matriser
+SUMX2PY2 = SUMMAX2PY2 ## Returnerar summan av summan av kvadraterna av motsvarande värden i två matriser
+SUMXMY2 = SUMMAXMY2 ## Returnerar summan av kvadraten av skillnaden mellan motsvarande värden i två matriser
+TAN = TAN ## Returnerar tangens för ett tal
+TANH = TANH ## Returnerar hyperbolisk tangens för ett tal
+TRUNC = AVKORTA ## Avkortar ett tal till ett heltal
+
+
+##
+## Statistical functions Statistiska funktioner
+##
+AVEDEV = MEDELAVV ## Returnerar medelvärdet för datapunkters absoluta avvikelse från deras medelvärde
+AVERAGE = MEDEL ## Returnerar medelvärdet av argumenten
+AVERAGEA = AVERAGEA ## Returnerar medelvärdet av argumenten, inklusive tal, text och logiska värden
+AVERAGEIF = MEDELOM ## Returnerar medelvärdet (aritmetiskt medelvärde) för alla celler i ett område som uppfyller ett givet kriterium
+AVERAGEIFS = MEDELOMF ## Returnerar medelvärdet (det aritmetiska medelvärdet) för alla celler som uppfyller flera villkor.
+BETADIST = BETAFÖRD ## Returnerar den kumulativa betafördelningsfunktionen
+BETAINV = BETAINV ## Returnerar inversen till den kumulativa fördelningsfunktionen för en viss betafördelning
+BINOMDIST = BINOMFÖRD ## Returnerar den individuella binomialfördelningen
+CHIDIST = CHI2FÖRD ## Returnerar den ensidiga sannolikheten av c2-fördelningen
+CHIINV = CHI2INV ## Returnerar inversen av chi2-fördelningen
+CHITEST = CHI2TEST ## Returnerar oberoendetesten
+CONFIDENCE = KONFIDENS ## Returnerar konfidensintervallet för en populations medelvärde
+CORREL = KORREL ## Returnerar korrelationskoefficienten mellan två datamängder
+COUNT = ANTAL ## Räknar hur många tal som finns bland argumenten
+COUNTA = ANTALV ## Räknar hur många värden som finns bland argumenten
+COUNTBLANK = ANTAL.TOMMA ## Räknar antalet tomma celler i ett område
+COUNTIF = ANTAL.OM ## Räknar antalet celler i ett område som uppfyller angivna villkor.
+COUNTIFS = ANTAL.OMF ## Räknar antalet celler i ett område som uppfyller flera villkor.
+COVAR = KOVAR ## Returnerar kovariansen, d.v.s. medelvärdet av produkterna för parade avvikelser
+CRITBINOM = KRITBINOM ## Returnerar det minsta värdet för vilket den kumulativa binomialfördelningen är mindre än eller lika med ett villkorsvärde
+DEVSQ = KVADAVV ## Returnerar summan av kvadrater på avvikelser
+EXPONDIST = EXPONFÖRD ## Returnerar exponentialfördelningen
+FDIST = FFÖRD ## Returnerar F-sannolikhetsfördelningen
+FINV = FINV ## Returnerar inversen till F-sannolikhetsfördelningen
+FISHER = FISHER ## Returnerar Fisher-transformationen
+FISHERINV = FISHERINV ## Returnerar inversen till Fisher-transformationen
+FORECAST = PREDIKTION ## Returnerar ett värde längs en linjär trendlinje
+FREQUENCY = FREKVENS ## Returnerar en frekvensfördelning som en lodrät matris
+FTEST = FTEST ## Returnerar resultatet av en F-test
+GAMMADIST = GAMMAFÖRD ## Returnerar gammafördelningen
+GAMMAINV = GAMMAINV ## Returnerar inversen till den kumulativa gammafördelningen
+GAMMALN = GAMMALN ## Returnerar den naturliga logaritmen för gammafunktionen, G(x)
+GEOMEAN = GEOMEDEL ## Returnerar det geometriska medelvärdet
+GROWTH = EXPTREND ## Returnerar värden längs en exponentiell trend
+HARMEAN = HARMMEDEL ## Returnerar det harmoniska medelvärdet
+HYPGEOMDIST = HYPGEOMFÖRD ## Returnerar den hypergeometriska fördelningen
+INTERCEPT = SKÄRNINGSPUNKT ## Returnerar skärningspunkten för en linjär regressionslinje
+KURT = TOPPIGHET ## Returnerar toppigheten av en mängd data
+LARGE = STÖRSTA ## Returnerar det n:te största värdet i en mängd data
+LINEST = REGR ## Returnerar parametrar till en linjär trendlinje
+LOGEST = EXPREGR ## Returnerar parametrarna i en exponentiell trend
+LOGINV = LOGINV ## Returnerar inversen till den lognormala fördelningen
+LOGNORMDIST = LOGNORMFÖRD ## Returnerar den kumulativa lognormala fördelningen
+MAX = MAX ## Returnerar det största värdet i en lista av argument
+MAXA = MAXA ## Returnerar det största värdet i en lista av argument, inklusive tal, text och logiska värden
+MEDIAN = MEDIAN ## Returnerar medianen för angivna tal
+MIN = MIN ## Returnerar det minsta värdet i en lista med argument
+MINA = MINA ## Returnerar det minsta värdet i en lista över argument, inklusive tal, text och logiska värden
+MODE = TYPVÄRDE ## Returnerar det vanligaste värdet i en datamängd
+NEGBINOMDIST = NEGBINOMFÖRD ## Returnerar den negativa binomialfördelningen
+NORMDIST = NORMFÖRD ## Returnerar den kumulativa normalfördelningen
+NORMINV = NORMINV ## Returnerar inversen till den kumulativa normalfördelningen
+NORMSDIST = NORMSFÖRD ## Returnerar den kumulativa standardnormalfördelningen
+NORMSINV = NORMSINV ## Returnerar inversen till den kumulativa standardnormalfördelningen
+PEARSON = PEARSON ## Returnerar korrelationskoefficienten till Pearsons momentprodukt
+PERCENTILE = PERCENTIL ## Returnerar den n:te percentilen av värden i ett område
+PERCENTRANK = PROCENTRANG ## Returnerar procentrangen för ett värde i en datamängd
+PERMUT = PERMUT ## Returnerar antal permutationer för ett givet antal objekt
+POISSON = POISSON ## Returnerar Poisson-fördelningen
+PROB = SANNOLIKHET ## Returnerar sannolikheten att värden i ett område ligger mellan två gränser
+QUARTILE = KVARTIL ## Returnerar kvartilen av en mängd data
+RANK = RANG ## Returnerar rangordningen för ett tal i en lista med tal
+RSQ = RKV ## Returnerar kvadraten av Pearsons produktmomentkorrelationskoefficient
+SKEW = SNEDHET ## Returnerar snedheten för en fördelning
+SLOPE = LUTNING ## Returnerar lutningen på en linjär regressionslinje
+SMALL = MINSTA ## Returnerar det n:te minsta värdet i en mängd data
+STANDARDIZE = STANDARDISERA ## Returnerar ett normaliserat värde
+STDEV = STDAV ## Uppskattar standardavvikelsen baserat på ett urval
+STDEVA = STDEVA ## Uppskattar standardavvikelsen baserat på ett urval, inklusive tal, text och logiska värden
+STDEVP = STDAVP ## Beräknar standardavvikelsen baserat på hela populationen
+STDEVPA = STDEVPA ## Beräknar standardavvikelsen baserat på hela populationen, inklusive tal, text och logiska värden
+STEYX = STDFELYX ## Returnerar standardfelet för ett förutspått y-värde för varje x-värde i regressionen
+TDIST = TFÖRD ## Returnerar Students t-fördelning
+TINV = TINV ## Returnerar inversen till Students t-fördelning
+TREND = TREND ## Returnerar värden längs en linjär trend
+TRIMMEAN = TRIMMEDEL ## Returnerar medelvärdet av mittpunkterna i en datamängd
+TTEST = TTEST ## Returnerar sannolikheten beräknad ur Students t-test
+VAR = VARIANS ## Uppskattar variansen baserat på ett urval
+VARA = VARA ## Uppskattar variansen baserat på ett urval, inklusive tal, text och logiska värden
+VARP = VARIANSP ## Beräknar variansen baserat på hela populationen
+VARPA = VARPA ## Beräknar variansen baserat på hela populationen, inklusive tal, text och logiska värden
+WEIBULL = WEIBULL ## Returnerar Weibull-fördelningen
+ZTEST = ZTEST ## Returnerar det ensidiga sannolikhetsvärdet av ett z-test
+
+
+##
+## Text functions Textfunktioner
+##
+ASC = ASC ## Ändrar helbredds (dubbel byte) engelska bokstäver eller katakana inom en teckensträng till tecken med halvt breddsteg (enkel byte)
+BAHTTEXT = BAHTTEXT ## Omvandlar ett tal till text med valutaformatet ß (baht)
+CHAR = TECKENKOD ## Returnerar tecknet som anges av kod
+CLEAN = STÄDA ## Tar bort alla icke utskrivbara tecken i en text
+CODE = KOD ## Returnerar en numerisk kod för det första tecknet i en textsträng
+CONCATENATE = SAMMANFOGA ## Sammanfogar flera textdelar till en textsträng
+DOLLAR = VALUTA ## Omvandlar ett tal till text med valutaformat
+EXACT = EXAKT ## Kontrollerar om två textvärden är identiska
+FIND = HITTA ## Hittar en text i en annan (skiljer på gemener och versaler)
+FINDB = HITTAB ## Hittar en text i en annan (skiljer på gemener och versaler)
+FIXED = FASTTAL ## Formaterar ett tal som text med ett fast antal decimaler
+JIS = JIS ## Ändrar halvbredds (enkel byte) engelska bokstäver eller katakana inom en teckensträng till tecken med helt breddsteg (dubbel byte)
+LEFT = VÄNSTER ## Returnerar tecken längst till vänster i en sträng
+LEFTB = VÄNSTERB ## Returnerar tecken längst till vänster i en sträng
+LEN = LÄNGD ## Returnerar antalet tecken i en textsträng
+LENB = LÄNGDB ## Returnerar antalet tecken i en textsträng
+LOWER = GEMENER ## Omvandlar text till gemener
+MID = EXTEXT ## Returnerar angivet antal tecken från en text med början vid den position som du anger
+MIDB = EXTEXTB ## Returnerar angivet antal tecken från en text med början vid den position som du anger
+PHONETIC = PHONETIC ## Returnerar de fonetiska (furigana) tecknen i en textsträng
+PROPER = INITIAL ## Ändrar första bokstaven i varje ord i ett textvärde till versal
+REPLACE = ERSÄTT ## Ersätter tecken i text
+REPLACEB = ERSÄTTB ## Ersätter tecken i text
+REPT = REP ## Upprepar en text ett bestämt antal gånger
+RIGHT = HÖGER ## Returnerar tecken längst till höger i en sträng
+RIGHTB = HÖGERB ## Returnerar tecken längst till höger i en sträng
+SEARCH = SÖK ## Hittar ett textvärde i ett annat (skiljer inte på gemener och versaler)
+SEARCHB = SÖKB ## Hittar ett textvärde i ett annat (skiljer inte på gemener och versaler)
+SUBSTITUTE = BYT.UT ## Ersätter gammal text med ny text i en textsträng
+T = T ## Omvandlar argumenten till text
+TEXT = TEXT ## Formaterar ett tal och omvandlar det till text
+TRIM = RENSA ## Tar bort blanksteg från text
+UPPER = VERSALER ## Omvandlar text till versaler
+VALUE = TEXTNUM ## Omvandlar ett textargument till ett tal
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config
new file mode 100755
index 0000000..266e000
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config
@@ -0,0 +1,24 @@
+##
+## PhpSpreadsheet
+##
+
+ArgumentSeparator = ;
+
+
+##
+## (For future use)
+##
+currencySymbol = YTL
+
+
+##
+## Excel Error Codes (For future use)
+
+##
+NULL = #BOŞ!
+DIV0 = #SAYI/0!
+VALUE = #DEĞER!
+REF = #BAŞV!
+NAME = #AD?
+NUM = #SAYI!
+NA = #YOK
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions
new file mode 100755
index 0000000..f03563a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions
@@ -0,0 +1,416 @@
+##
+## PhpSpreadsheet
+##
+## Data in this file derived from https://www.excel-function-translation.com/
+##
+##
+
+
+##
+## Add-in and Automation functions Eklenti ve Otomasyon fonksiyonları
+##
+GETPIVOTDATA = ÖZETVERİAL ## Bir Özet Tablo raporunda saklanan verileri verir.
+
+
+##
+## Cube functions Küp işlevleri
+##
+CUBEKPIMEMBER = KÜPKPIÜYE ## Kilit performans göstergesi (KPI-Key Performance Indicator) adını, özelliğini ve ölçüsünü verir ve hücredeki ad ve özelliği gösterir. KPI, bir kurumun performansını izlemek için kullanılan aylık brüt kâr ya da üç aylık çalışan giriş çıkışları gibi ölçülebilen bir birimdir.
+CUBEMEMBER = KÜPÜYE ## Bir küp hiyerarşisinde bir üyeyi veya kaydı verir. Üye veya kaydın küpte varolduğunu doğrulamak için kullanılır.
+CUBEMEMBERPROPERTY = KÜPÜYEÖZELLİĞİ ## Bir küpte bir üyenin özelliğinin değerini verir. Küp içinde üye adının varlığını doğrulamak ve bu üyenin belli özelliklerini getirmek için kullanılır.
+CUBERANKEDMEMBER = KÜPÜYESIRASI ## Bir küme içindeki üyenin derecesini veya kaçıncı olduğunu verir. En iyi satış elemanı, veya en iyi on öğrenci gibi bir kümedeki bir veya daha fazla öğeyi getirmek için kullanılır.
+CUBESET = KÜPKÜME ## Kümeyi oluşturan ve ardından bu kümeyi Microsoft Office Excel'e getiren sunucudaki küpe küme ifadelerini göndererek hesaplanan üye veya kayıt kümesini tanımlar.
+CUBESETCOUNT = KÜPKÜMESAY ## Bir kümedeki öğelerin sayısını getirir.
+CUBEVALUE = KÜPDEĞER ## Bir küpten toplam değeri getirir.
+
+
+##
+## Database functions Veritabanı işlevleri
+##
+DAVERAGE = VSEÇORT ## Seçili veritabanı girdilerinin ortalamasını verir.
+DCOUNT = VSEÇSAY ## Veritabanında sayı içeren hücre sayısını hesaplar.
+DCOUNTA = VSEÇSAYDOLU ## Veritabanındaki boş olmayan hücreleri sayar.
+DGET = VAL ## Veritabanından, belirtilen ölçütlerle eşleşen tek bir rapor çıkarır.
+DMAX = VSEÇMAK ## Seçili veritabanı girişlerinin en yüksek değerini verir.
+DMIN = VSEÇMİN ## Seçili veritabanı girişlerinin en düşük değerini verir.
+DPRODUCT = VSEÇÇARP ## Kayıtların belli bir alanında bulunan, bir veritabanındaki ölçütlerle eşleşen değerleri çarpar.
+DSTDEV = VSEÇSTDSAPMA ## Seçili veritabanı girişlerinden oluşan bir örneğe dayanarak, standart sapmayı tahmin eder.
+DSTDEVP = VSEÇSTDSAPMAS ## Standart sapmayı, seçili veritabanı girişlerinin tüm popülasyonunu esas alarak hesaplar.
+DSUM = VSEÇTOPLA ## Kayıtların alan sütununda bulunan, ölçütle eşleşen sayıları toplar.
+DVAR = VSEÇVAR ## Seçili veritabanı girişlerinden oluşan bir örneği esas alarak farkı tahmin eder.
+DVARP = VSEÇVARS ## Seçili veritabanı girişlerinin tüm popülasyonunu esas alarak farkı hesaplar.
+
+
+##
+## Date and time functions Tarih ve saat işlevleri
+##
+DATE = TARİH ## Belirli bir tarihin seri numarasını verir.
+DATEVALUE = TARİHSAYISI ## Metin biçimindeki bir tarihi seri numarasına dönüştürür.
+DAY = GÜN ## Seri numarasını, ayın bir gününe dönüştürür.
+DAYS360 = GÜN360 ## İki tarih arasındaki gün sayısını, 360 günlük yılı esas alarak hesaplar.
+EDATE = SERİTARİH ## Başlangıç tarihinden itibaren, belirtilen ay sayısından önce veya sonraki tarihin seri numarasını verir.
+EOMONTH = SERİAY ## Belirtilen sayıda ay önce veya sonraki ayın son gününün seri numarasını verir.
+HOUR = SAAT ## Bir seri numarasını saate dönüştürür.
+MINUTE = DAKİKA ## Bir seri numarasını dakikaya dönüştürür.
+MONTH = AY ## Bir seri numarasını aya dönüştürür.
+NETWORKDAYS = TAMİŞGÜNÜ ## İki tarih arasındaki tam çalışma günlerinin sayısını verir.
+NOW = ŞİMDİ ## Geçerli tarihin ve saatin seri numarasını verir.
+SECOND = SANİYE ## Bir seri numarasını saniyeye dönüştürür.
+TIME = ZAMAN ## Belirli bir zamanın seri numarasını verir.
+TIMEVALUE = ZAMANSAYISI ## Metin biçimindeki zamanı seri numarasına dönüştürür.
+TODAY = BUGÜN ## Bugünün tarihini seri numarasına dönüştürür.
+WEEKDAY = HAFTANINGÜNÜ ## Bir seri numarasını, haftanın gününe dönüştürür.
+WEEKNUM = HAFTASAY ## Dizisel değerini, haftanın yıl içinde bulunduğu konumu sayısal olarak gösteren sayıya dönüştürür.
+WORKDAY = İŞGÜNÜ ## Belirtilen sayıda çalışma günü öncesinin ya da sonrasının tarihinin seri numarasını verir.
+YEAR = YIL ## Bir seri numarasını yıla dönüştürür.
+YEARFRAC = YILORAN ## Başlangıç_tarihi ve bitiş_tarihi arasındaki tam günleri gösteren yıl kesrini verir.
+
+
+##
+## Engineering functions Mühendislik işlevleri
+##
+BESSELI = BESSELI ## Değiştirilmiş Bessel fonksiyonu In(x)'i verir.
+BESSELJ = BESSELJ ## Bessel fonksiyonu Jn(x)'i verir.
+BESSELK = BESSELK ## Değiştirilmiş Bessel fonksiyonu Kn(x)'i verir.
+BESSELY = BESSELY ## Bessel fonksiyonu Yn(x)'i verir.
+BIN2DEC = BIN2DEC ## İkili bir sayıyı, ondalık sayıya dönüştürür.
+BIN2HEX = BIN2HEX ## İkili bir sayıyı, onaltılıya dönüştürür.
+BIN2OCT = BIN2OCT ## İkili bir sayıyı, sekizliye dönüştürür.
+COMPLEX = KARMAŞIK ## Gerçek ve sanal katsayıları, karmaşık sayıya dönüştürür.
+CONVERT = ÇEVİR ## Bir sayıyı, bir ölçüm sisteminden bir başka ölçüm sistemine dönüştürür.
+DEC2BIN = DEC2BIN ## Ondalık bir sayıyı, ikiliye dönüştürür.
+DEC2HEX = DEC2HEX ## Ondalık bir sayıyı, onaltılıya dönüştürür.
+DEC2OCT = DEC2OCT ## Ondalık bir sayıyı sekizliğe dönüştürür.
+DELTA = DELTA ## İki değerin eşit olup olmadığını sınar.
+ERF = HATAİŞLEV ## Hata işlevini verir.
+ERFC = TÜMHATAİŞLEV ## Tümleyici hata işlevini verir.
+GESTEP = BESINIR ## Bir sayının eşik değerinden büyük olup olmadığını sınar.
+HEX2BIN = HEX2BIN ## Onaltılı bir sayıyı ikiliye dönüştürür.
+HEX2DEC = HEX2DEC ## Onaltılı bir sayıyı ondalığa dönüştürür.
+HEX2OCT = HEX2OCT ## Onaltılı bir sayıyı sekizliğe dönüştürür.
+IMABS = SANMUTLAK ## Karmaşık bir sayının mutlak değerini (modül) verir.
+IMAGINARY = SANAL ## Karmaşık bir sayının sanal katsayısını verir.
+IMARGUMENT = SANBAĞ_DEĞİŞKEN ## Radyanlarla belirtilen bir açı olan teta bağımsız değişkenini verir.
+IMCONJUGATE = SANEŞLENEK ## Karmaşık bir sayının karmaşık eşleniğini verir.
+IMCOS = SANCOS ## Karmaşık bir sayının kosinüsünü verir.
+IMDIV = SANBÖL ## İki karmaşık sayının bölümünü verir.
+IMEXP = SANÜS ## Karmaşık bir sayının üssünü verir.
+IMLN = SANLN ## Karmaşık bir sayının doğal logaritmasını verir.
+IMLOG10 = SANLOG10 ## Karmaşık bir sayının, 10 tabanında logaritmasını verir.
+IMLOG2 = SANLOG2 ## Karmaşık bir sayının 2 tabanında logaritmasını verir.
+IMPOWER = SANÜSSÜ ## Karmaşık bir sayıyı, bir tamsayı üssüne yükseltilmiş olarak verir.
+IMPRODUCT = SANÇARP ## Karmaşık sayıların çarpımını verir.
+IMREAL = SANGERÇEK ## Karmaşık bir sayının, gerçek katsayısını verir.
+IMSIN = SANSIN ## Karmaşık bir sayının sinüsünü verir.
+IMSQRT = SANKAREKÖK ## Karmaşık bir sayının karekökünü verir.
+IMSUB = SANÇIKAR ## İki karmaşık sayının farkını verir.
+IMSUM = SANTOPLA ## Karmaşık sayıların toplamını verir.
+OCT2BIN = OCT2BIN ## Sekizli bir sayıyı ikiliye dönüştürür.
+OCT2DEC = OCT2DEC ## Sekizli bir sayıyı ondalığa dönüştürür.
+OCT2HEX = OCT2HEX ## Sekizli bir sayıyı onaltılıya dönüştürür.
+
+
+##
+## Financial functions Finansal fonksiyonlar
+##
+ACCRINT = GERÇEKFAİZ ## Dönemsel faiz ödeyen hisse senedine ilişkin tahakkuk eden faizi getirir.
+ACCRINTM = GERÇEKFAİZV ## Vadesinde ödeme yapan bir tahvilin tahakkuk etmiş faizini verir.
+AMORDEGRC = AMORDEGRC ## Yıpranma katsayısı kullanarak her hesap döneminin değer kaybını verir.
+AMORLINC = AMORLINC ## Her hesap dönemi içindeki yıpranmayı verir.
+COUPDAYBS = KUPONGÜNBD ## Kupon süresinin başlangıcından alış tarihine kadar olan süredeki gün sayısını verir.
+COUPDAYS = KUPONGÜN ## Kupon süresindeki, gün sayısını, alış tarihini de içermek üzere, verir.
+COUPDAYSNC = KUPONGÜNDSK ## Alış tarihinden bir sonraki kupon tarihine kadar olan gün sayısını verir.
+COUPNCD = KUPONGÜNSKT ## Alış tarihinden bir sonraki kupon tarihini verir.
+COUPNUM = KUPONSAYI ## Alış tarihiyle vade tarihi arasında ödenecek kuponların sayısını verir.
+COUPPCD = KUPONGÜNÖKT ## Alış tarihinden bir önceki kupon tarihini verir.
+CUMIPMT = AİÇVERİMORANI ## İki dönem arasında ödenen kümülatif faizi verir.
+CUMPRINC = ANA_PARA_ÖDEMESİ ## İki dönem arasında bir borç üzerine ödenen birikimli temeli verir.
+DB = AZALANBAKİYE ## Bir malın belirtilen bir süre içindeki yıpranmasını, sabit azalan bakiye yöntemini kullanarak verir.
+DDB = ÇİFTAZALANBAKİYE ## Bir malın belirtilen bir süre içindeki yıpranmasını, çift azalan bakiye yöntemi ya da sizin belirttiğiniz başka bir yöntemi kullanarak verir.
+DISC = İNDİRİM ## Bir tahvilin indirim oranını verir.
+DOLLARDE = LİRAON ## Kesir olarak tanımlanmış lira fiyatını, ondalık sayı olarak tanımlanmış lira fiyatına dönüştürür.
+DOLLARFR = LİRAKES ## Ondalık sayı olarak tanımlanmış lira fiyatını, kesir olarak tanımlanmış lira fiyatına dönüştürür.
+DURATION = SÜRE ## Belli aralıklarla faiz ödemesi yapan bir tahvilin yıllık süresini verir.
+EFFECT = ETKİN ## Efektif yıllık faiz oranını verir.
+FV = ANBD ## Bir yatırımın gelecekteki değerini verir.
+FVSCHEDULE = GDPROGRAM ## Bir seri birleşik faiz oranı uyguladıktan sonra, bir başlangıçtaki anaparanın gelecekteki değerini verir.
+INTRATE = FAİZORANI ## Tam olarak yatırım yapılmış bir tahvilin faiz oranını verir.
+IPMT = FAİZTUTARI ## Bir yatırımın verilen bir süre için faiz ödemesini verir.
+IRR = İÇ_VERİM_ORANI ## Bir para akışı serisi için, iç verim oranını verir.
+ISPMT = ISPMT ## Yatırımın belirli bir dönemi boyunca ödenen faizi hesaplar.
+MDURATION = MSÜRE ## Varsayılan par değeri 10.000.000 lira olan bir tahvil için Macauley değiştirilmiş süreyi verir.
+MIRR = D_İÇ_VERİM_ORANI ## Pozitif ve negatif para akışlarının farklı oranlarda finanse edildiği durumlarda, iç verim oranını verir.
+NOMINAL = NOMİNAL ## Yıllık nominal faiz oranını verir.
+NPER = DÖNEM_SAYISI ## Bir yatırımın dönem sayısını verir.
+NPV = NBD ## Bir yatırımın bugünkü net değerini, bir dönemsel para akışları serisine ve bir indirim oranına bağlı olarak verir.
+ODDFPRICE = TEKYDEĞER ## Tek bir ilk dönemi olan bir tahvilin değerini, her 100.000.000 lirada bir verir.
+ODDFYIELD = TEKYÖDEME ## Tek bir ilk dönemi olan bir tahvilin ödemesini verir.
+ODDLPRICE = TEKSDEĞER ## Tek bir son dönemi olan bir tahvilin fiyatını her 10.000.000 lirada bir verir.
+ODDLYIELD = TEKSÖDEME ## Tek bir son dönemi olan bir tahvilin ödemesini verir.
+PMT = DEVRESEL_ÖDEME ## Bir yıllık dönemsel ödemeyi verir.
+PPMT = ANA_PARA_ÖDEMESİ ## Verilen bir süre için, bir yatırımın anaparasına dayanan ödemeyi verir.
+PRICE = DEĞER ## Dönemsel faiz ödeyen bir tahvilin fiyatını 10.000.00 liralık değer başına verir.
+PRICEDISC = DEĞERİND ## İndirimli bir tahvilin fiyatını 10.000.000 liralık nominal değer başına verir.
+PRICEMAT = DEĞERVADE ## Faizini vade sonunda ödeyen bir tahvilin fiyatını 10.000.000 nominal değer başına verir.
+PV = BD ## Bir yatırımın bugünkü değerini verir.
+RATE = FAİZ_ORANI ## Bir yıllık dönem başına düşen faiz oranını verir.
+RECEIVED = GETİRİ ## Tam olarak yatırılmış bir tahvilin vadesinin bitiminde alınan miktarı verir.
+SLN = DA ## Bir malın bir dönem içindeki doğrusal yıpranmasını verir.
+SYD = YAT ## Bir malın belirli bir dönem için olan amortismanını verir.
+TBILLEQ = HTAHEŞ ## Bir Hazine bonosunun bono eşdeğeri ödemesini verir.
+TBILLPRICE = HTAHDEĞER ## Bir Hazine bonosunun değerini, 10.000.000 liralık nominal değer başına verir.
+TBILLYIELD = HTAHÖDEME ## Bir Hazine bonosunun ödemesini verir.
+VDB = DAB ## Bir malın amortismanını, belirlenmiş ya da kısmi bir dönem için, bir azalan bakiye yöntemi kullanarak verir.
+XIRR = AİÇVERİMORANI ## Dönemsel olması gerekmeyen bir para akışları programı için, iç verim oranını verir.
+XNPV = ANBD ## Dönemsel olması gerekmeyen bir para akışları programı için, bugünkü net değeri verir.
+YIELD = ÖDEME ## Belirli aralıklarla faiz ödeyen bir tahvilin ödemesini verir.
+YIELDDISC = ÖDEMEİND ## İndirimli bir tahvilin yıllık ödemesini verir; örneğin, bir Hazine bonosunun.
+YIELDMAT = ÖDEMEVADE ## Vadesinin bitiminde faiz ödeyen bir tahvilin yıllık ödemesini verir.
+
+
+##
+## Information functions Bilgi fonksiyonları
+##
+CELL = HÜCRE ## Bir hücrenin biçimlendirmesi, konumu ya da içeriği hakkında bilgi verir.
+ERROR.TYPE = HATA.TİPİ ## Bir hata türüne ilişkin sayıları verir.
+INFO = BİLGİ ## Geçerli işletim ortamı hakkında bilgi verir.
+ISBLANK = EBOŞSA ## Değer boşsa, DOĞRU verir.
+ISERR = EHATA ## Değer, #YOK dışındaki bir hata değeriyse, DOĞRU verir.
+ISERROR = EHATALIYSA ## Değer, herhangi bir hata değeriyse, DOĞRU verir.
+ISEVEN = ÇİFTTİR ## Sayı çiftse, DOĞRU verir.
+ISLOGICAL = EMANTIKSALSA ## Değer, mantıksal bir değerse, DOĞRU verir.
+ISNA = EYOKSA ## Değer, #YOK hata değeriyse, DOĞRU verir.
+ISNONTEXT = EMETİNDEĞİLSE ## Değer, metin değilse, DOĞRU verir.
+ISNUMBER = ESAYIYSA ## Değer, bir sayıysa, DOĞRU verir.
+ISODD = TEKTİR ## Sayı tekse, DOĞRU verir.
+ISREF = EREFSE ## Değer bir başvuruysa, DOĞRU verir.
+ISTEXT = EMETİNSE ## Değer bir metinse DOĞRU verir.
+N = N ## Sayıya dönüştürülmüş bir değer verir.
+NA = YOKSAY ## #YOK hata değerini verir.
+TYPE = TİP ## Bir değerin veri türünü belirten bir sayı verir.
+
+
+##
+## Logical functions Mantıksal fonksiyonlar
+##
+AND = VE ## Bütün bağımsız değişkenleri DOĞRU ise, DOĞRU verir.
+FALSE = YANLIŞ ## YANLIŞ mantıksal değerini verir.
+IF = EĞER ## Gerçekleştirilecek bir mantıksal sınama belirtir.
+IFERROR = EĞERHATA ## Formül hatalıysa belirttiğiniz değeri verir; bunun dışındaki durumlarda formülün sonucunu verir.
+NOT = DEĞİL ## Bağımsız değişkeninin mantığını tersine çevirir.
+OR = YADA ## Bağımsız değişkenlerden herhangi birisi DOĞRU ise, DOĞRU verir.
+TRUE = DOĞRU ## DOĞRU mantıksal değerini verir.
+
+
+##
+## Lookup and reference functions Arama ve Başvuru fonksiyonları
+##
+ADDRESS = ADRES ## Bir başvuruyu, çalışma sayfasındaki tek bir hücreye metin olarak verir.
+AREAS = ALANSAY ## Renvoie le nombre de zones dans une référence.
+CHOOSE = ELEMAN ## Değerler listesinden bir değer seçer.
+COLUMN = SÜTUN ## Bir başvurunun sütun sayısını verir.
+COLUMNS = SÜTUNSAY ## Bir başvurudaki sütunların sayısını verir.
+HLOOKUP = YATAYARA ## Bir dizinin en üst satırına bakar ve belirtilen hücrenin değerini verir.
+HYPERLINK = KÖPRÜ ## Bir ağ sunucusunda, bir intranette ya da Internet'te depolanan bir belgeyi açan bir kısayol ya da atlama oluşturur.
+INDEX = İNDİS ## Başvurudan veya diziden bir değer seçmek için, bir dizin kullanır.
+INDIRECT = DOLAYLI ## Metin değeriyle belirtilen bir başvuru verir.
+LOOKUP = ARA ## Bir vektördeki veya dizideki değerleri arar.
+MATCH = KAÇINCI ## Bir başvurudaki veya dizideki değerleri arar.
+OFFSET = KAYDIR ## Verilen bir başvurudan, bir başvuru kaydırmayı verir.
+ROW = SATIR ## Bir başvurunun satır sayısını verir.
+ROWS = SATIRSAY ## Bir başvurudaki satırların sayısını verir.
+RTD = RTD ## COM otomasyonunu destekleyen programdan gerçek zaman verileri alır.
+TRANSPOSE = DEVRİK_DÖNÜŞÜM ## Bir dizinin devrik dönüşümünü verir.
+VLOOKUP = DÜŞEYARA ## Bir dizinin ilk sütununa bakar ve bir hücrenin değerini vermek için satır boyunca hareket eder.
+
+
+##
+## Math and trigonometry functions Matematik ve trigonometri fonksiyonları
+##
+ABS = MUTLAK ## Bir sayının mutlak değerini verir.
+ACOS = ACOS ## Bir sayının ark kosinüsünü verir.
+ACOSH = ACOSH ## Bir sayının ters hiperbolik kosinüsünü verir.
+ASIN = ASİN ## Bir sayının ark sinüsünü verir.
+ASINH = ASİNH ## Bir sayının ters hiperbolik sinüsünü verir.
+ATAN = ATAN ## Bir sayının ark tanjantını verir.
+ATAN2 = ATAN2 ## Ark tanjantı, x- ve y- koordinatlarından verir.
+ATANH = ATANH ## Bir sayının ters hiperbolik tanjantını verir.
+CEILING = TAVANAYUVARLA ## Bir sayıyı, en yakın tamsayıya ya da en yakın katına yuvarlar.
+COMBIN = KOMBİNASYON ## Verilen sayıda öğenin kombinasyon sayısını verir.
+COS = COS ## Bir sayının kosinüsünü verir.
+COSH = COSH ## Bir sayının hiperbolik kosinüsünü verir.
+DEGREES = DERECE ## Radyanları dereceye dönüştürür.
+EVEN = ÇİFT ## Bir sayıyı, en yakın daha büyük çift tamsayıya yuvarlar.
+EXP = ÜS ## e'yi, verilen bir sayının üssüne yükseltilmiş olarak verir.
+FACT = ÇARPINIM ## Bir sayının faktörünü verir.
+FACTDOUBLE = ÇİFTFAKTÖR ## Bir sayının çift çarpınımını verir.
+FLOOR = TABANAYUVARLA ## Bir sayıyı, daha küçük sayıya, sıfıra yakınsayarak yuvarlar.
+GCD = OBEB ## En büyük ortak böleni verir.
+INT = TAMSAYI ## Bir sayıyı aşağıya doğru en yakın tamsayıya yuvarlar.
+LCM = OKEK ## En küçük ortak katı verir.
+LN = LN ## Bir sayının doğal logaritmasını verir.
+LOG = LOG ## Bir sayının, belirtilen bir tabandaki logaritmasını verir.
+LOG10 = LOG10 ## Bir sayının 10 tabanında logaritmasını verir.
+MDETERM = DETERMİNANT ## Bir dizinin dizey determinantını verir.
+MINVERSE = DİZEY_TERS ## Bir dizinin dizey tersini verir.
+MMULT = DÇARP ## İki dizinin dizey çarpımını verir.
+MOD = MODÜLO ## Bölmeden kalanı verir.
+MROUND = KYUVARLA ## İstenen kata yuvarlanmış bir sayı verir.
+MULTINOMIAL = ÇOKTERİMLİ ## Bir sayılar kümesinin çok terimlisini verir.
+ODD = TEK ## Bir sayıyı en yakın daha büyük tek sayıya yuvarlar.
+PI = Pİ ## Pi değerini verir.
+POWER = KUVVET ## Bir üsse yükseltilmiş sayının sonucunu verir.
+PRODUCT = ÇARPIM ## Bağımsız değişkenlerini çarpar.
+QUOTIENT = BÖLÜM ## Bir bölme işleminin tamsayı kısmını verir.
+RADIANS = RADYAN ## Dereceleri radyanlara dönüştürür.
+RAND = S_SAYI_ÜRET ## 0 ile 1 arasında rastgele bir sayı verir.
+RANDBETWEEN = RASTGELEARALIK ## Belirttiğiniz sayılar arasında rastgele bir sayı verir.
+ROMAN = ROMEN ## Bir normal rakamı, metin olarak, romen rakamına çevirir.
+ROUND = YUVARLA ## Bir sayıyı, belirtilen basamak sayısına yuvarlar.
+ROUNDDOWN = AŞAĞIYUVARLA ## Bir sayıyı, daha küçük sayıya, sıfıra yakınsayarak yuvarlar.
+ROUNDUP = YUKARIYUVARLA ## Bir sayıyı daha büyük sayıya, sıfırdan ıraksayarak yuvarlar.
+SERIESSUM = SERİTOPLA ## Bir üs serisinin toplamını, formüle bağlı olarak verir.
+SIGN = İŞARET ## Bir sayının işaretini verir.
+SIN = SİN ## Verilen bir açının sinüsünü verir.
+SINH = SİNH ## Bir sayının hiperbolik sinüsünü verir.
+SQRT = KAREKÖK ## Pozitif bir karekök verir.
+SQRTPI = KAREKÖKPİ ## (* Pi sayısının) kare kökünü verir.
+SUBTOTAL = ALTTOPLAM ## Bir listedeki ya da veritabanındaki bir alt toplamı verir.
+SUM = TOPLA ## Bağımsız değişkenlerini toplar.
+SUMIF = ETOPLA ## Verilen ölçütle belirlenen hücreleri toplar.
+SUMIFS = SUMIFS ## Bir aralıktaki, birden fazla ölçüte uyan hücreleri ekler.
+SUMPRODUCT = TOPLA.ÇARPIM ## İlişkili dizi bileşenlerinin çarpımlarının toplamını verir.
+SUMSQ = TOPKARE ## Bağımsız değişkenlerin karelerinin toplamını verir.
+SUMX2MY2 = TOPX2EY2 ## İki dizideki ilişkili değerlerin farkının toplamını verir.
+SUMX2PY2 = TOPX2AY2 ## İki dizideki ilişkili değerlerin karelerinin toplamının toplamını verir.
+SUMXMY2 = TOPXEY2 ## İki dizideki ilişkili değerlerin farklarının karelerinin toplamını verir.
+TAN = TAN ## Bir sayının tanjantını verir.
+TANH = TANH ## Bir sayının hiperbolik tanjantını verir.
+TRUNC = NSAT ## Bir sayının, tamsayı durumuna gelecek şekilde, fazlalıklarını atar.
+
+
+##
+## Statistical functions İstatistiksel fonksiyonlar
+##
+AVEDEV = ORTSAP ## Veri noktalarının ortalamalarından mutlak sapmalarının ortalamasını verir.
+AVERAGE = ORTALAMA ## Bağımsız değişkenlerinin ortalamasını verir.
+AVERAGEA = ORTALAMAA ## Bağımsız değişkenlerinin, sayılar, metin ve mantıksal değerleri içermek üzere ortalamasını verir.
+AVERAGEIF = EĞERORTALAMA ## Verili ölçütü karşılayan bir aralıktaki bütün hücrelerin ortalamasını (aritmetik ortalama) hesaplar.
+AVERAGEIFS = EĞERLERORTALAMA ## Birden çok ölçüte uyan tüm hücrelerin ortalamasını (aritmetik ortalama) hesaplar.
+BETADIST = BETADAĞ ## Beta birikimli dağılım fonksiyonunu verir.
+BETAINV = BETATERS ## Belirli bir beta dağılımı için birikimli dağılım fonksiyonunun tersini verir.
+BINOMDIST = BİNOMDAĞ ## Tek terimli binom dağılımı olasılığını verir.
+CHIDIST = KİKAREDAĞ ## Kikare dağılımın tek kuyruklu olasılığını verir.
+CHIINV = KİKARETERS ## Kikare dağılımın kuyruklu olasılığının tersini verir.
+CHITEST = KİKARETEST ## Bağımsızlık sınamalarını verir.
+CONFIDENCE = GÜVENİRLİK ## Bir popülasyon ortalaması için güvenirlik aralığını verir.
+CORREL = KORELASYON ## İki veri kümesi arasındaki bağlantı katsayısını verir.
+COUNT = BAĞ_DEĞ_SAY ## Bağımsız değişkenler listesinde kaç tane sayı bulunduğunu sayar.
+COUNTA = BAĞ_DEĞ_DOLU_SAY ## Bağımsız değişkenler listesinde kaç tane değer bulunduğunu sayar.
+COUNTBLANK = BOŞLUKSAY ## Aralıktaki boş hücre sayısını hesaplar.
+COUNTIF = EĞERSAY ## Verilen ölçütlere uyan bir aralık içindeki hücreleri sayar.
+COUNTIFS = ÇOKEĞERSAY ## Birden çok ölçüte uyan bir aralık içindeki hücreleri sayar.
+COVAR = KOVARYANS ## Eşleştirilmiş sapmaların ortalaması olan kovaryansı verir.
+CRITBINOM = KRİTİKBİNOM ## Birikimli binom dağılımının bir ölçüt değerinden küçük veya ölçüt değerine eşit olduğu en küçük değeri verir.
+DEVSQ = SAPKARE ## Sapmaların karelerinin toplamını verir.
+EXPONDIST = ÜSTELDAĞ ## Üstel dağılımı verir.
+FDIST = FDAĞ ## F olasılık dağılımını verir.
+FINV = FTERS ## F olasılık dağılımının tersini verir.
+FISHER = FISHER ## Fisher dönüşümünü verir.
+FISHERINV = FISHERTERS ## Fisher dönüşümünün tersini verir.
+FORECAST = TAHMİN ## Bir doğrusal eğilim boyunca bir değer verir.
+FREQUENCY = SIKLIK ## Bir sıklık dağılımını, dikey bir dizi olarak verir.
+FTEST = FTEST ## Bir F-test'in sonucunu verir.
+GAMMADIST = GAMADAĞ ## Gama dağılımını verir.
+GAMMAINV = GAMATERS ## Gama kümülatif dağılımının tersini verir.
+GAMMALN = GAMALN ## Gama fonksiyonunun (?(x)) doğal logaritmasını verir.
+GEOMEAN = GEOORT ## Geometrik ortayı verir.
+GROWTH = BÜYÜME ## Üstel bir eğilim boyunca değerler verir.
+HARMEAN = HARORT ## Harmonik ortayı verir.
+HYPGEOMDIST = HİPERGEOMDAĞ ## Hipergeometrik dağılımı verir.
+INTERCEPT = KESMENOKTASI ## Doğrusal çakıştırma çizgisinin kesişme noktasını verir.
+KURT = BASIKLIK ## Bir veri kümesinin basıklığını verir.
+LARGE = BÜYÜK ## Bir veri kümesinde k. en büyük değeri verir.
+LINEST = DOT ## Doğrusal bir eğilimin parametrelerini verir.
+LOGEST = LOT ## Üstel bir eğilimin parametrelerini verir.
+LOGINV = LOGTERS ## Bir lognormal dağılımının tersini verir.
+LOGNORMDIST = LOGNORMDAĞ ## Birikimli lognormal dağılımını verir.
+MAX = MAK ## Bir bağımsız değişkenler listesindeki en büyük değeri verir.
+MAXA = MAKA ## Bir bağımsız değişkenler listesindeki, sayılar, metin ve mantıksal değerleri içermek üzere, en büyük değeri verir.
+MEDIAN = ORTANCA ## Belirtilen sayıların orta değerini verir.
+MIN = MİN ## Bir bağımsız değişkenler listesindeki en küçük değeri verir.
+MINA = MİNA ## Bir bağımsız değişkenler listesindeki, sayılar, metin ve mantıksal değerleri de içermek üzere, en küçük değeri verir.
+MODE = ENÇOK_OLAN ## Bir veri kümesindeki en sık rastlanan değeri verir.
+NEGBINOMDIST = NEGBİNOMDAĞ ## Negatif binom dağılımını verir.
+NORMDIST = NORMDAĞ ## Normal birikimli dağılımı verir.
+NORMINV = NORMTERS ## Normal kümülatif dağılımın tersini verir.
+NORMSDIST = NORMSDAĞ ## Standart normal birikimli dağılımı verir.
+NORMSINV = NORMSTERS ## Standart normal birikimli dağılımın tersini verir.
+PEARSON = PEARSON ## Pearson çarpım moment korelasyon katsayısını verir.
+PERCENTILE = YÜZDEBİRLİK ## Bir aralık içerisinde bulunan değerlerin k. frekans toplamını verir.
+PERCENTRANK = YÜZDERANK ## Bir veri kümesindeki bir değerin yüzde mertebesini verir.
+PERMUT = PERMÜTASYON ## Verilen sayıda nesne için permütasyon sayısını verir.
+POISSON = POISSON ## Poisson dağılımını verir.
+PROB = OLASILIK ## Bir aralıktaki değerlerin iki sınır arasında olması olasılığını verir.
+QUARTILE = DÖRTTEBİRLİK ## Bir veri kümesinin dörtte birliğini verir.
+RANK = RANK ## Bir sayılar listesinde bir sayının mertebesini verir.
+RSQ = RKARE ## Pearson çarpım moment korelasyon katsayısının karesini verir.
+SKEW = ÇARPIKLIK ## Bir dağılımın çarpıklığını verir.
+SLOPE = EĞİM ## Doğrusal çakışma çizgisinin eğimini verir.
+SMALL = KÜÇÜK ## Bir veri kümesinde k. en küçük değeri verir.
+STANDARDIZE = STANDARTLAŞTIRMA ## Normalleştirilmiş bir değer verir.
+STDEV = STDSAPMA ## Bir örneğe dayanarak standart sapmayı tahmin eder.
+STDEVA = STDSAPMAA ## Standart sapmayı, sayılar, metin ve mantıksal değerleri içermek üzere, bir örneğe bağlı olarak tahmin eder.
+STDEVP = STDSAPMAS ## Standart sapmayı, tüm popülasyona bağlı olarak hesaplar.
+STDEVPA = STDSAPMASA ## Standart sapmayı, sayılar, metin ve mantıksal değerleri içermek üzere, tüm popülasyona bağlı olarak hesaplar.
+STEYX = STHYX ## Regresyondaki her x için tahmini y değerinin standart hatasını verir.
+TDIST = TDAĞ ## T-dağılımını verir.
+TINV = TTERS ## T-dağılımının tersini verir.
+TREND = EĞİLİM ## Doğrusal bir eğilim boyunca değerler verir.
+TRIMMEAN = KIRPORTALAMA ## Bir veri kümesinin içinin ortalamasını verir.
+TTEST = TTEST ## T-test'le ilişkilendirilmiş olasılığı verir.
+VAR = VAR ## Varyansı, bir örneğe bağlı olarak tahmin eder.
+VARA = VARA ## Varyansı, sayılar, metin ve mantıksal değerleri içermek üzere, bir örneğe bağlı olarak tahmin eder.
+VARP = VARS ## Varyansı, tüm popülasyona dayanarak hesaplar.
+VARPA = VARSA ## Varyansı, sayılar, metin ve mantıksal değerleri içermek üzere, tüm popülasyona bağlı olarak hesaplar.
+WEIBULL = WEIBULL ## Weibull dağılımını hesaplar.
+ZTEST = ZTEST ## Z-testinin tek kuyruklu olasılık değerini hesaplar.
+
+
+##
+## Text functions Metin fonksiyonları
+##
+ASC = ASC ## Bir karakter dizesindeki çift enli (iki bayt) İngilizce harfleri veya katakanayı yarım enli (tek bayt) karakterlerle değiştirir.
+BAHTTEXT = BAHTTEXT ## Sayıyı, ß (baht) para birimi biçimini kullanarak metne dönüştürür.
+CHAR = DAMGA ## Kod sayısıyla belirtilen karakteri verir.
+CLEAN = TEMİZ ## Metindeki bütün yazdırılamaz karakterleri kaldırır.
+CODE = KOD ## Bir metin dizesindeki ilk karakter için sayısal bir kod verir.
+CONCATENATE = BİRLEŞTİR ## Pek çok metin öğesini bir metin öğesi olarak birleştirir.
+DOLLAR = LİRA ## Bir sayıyı YTL (yeni Türk lirası) para birimi biçimini kullanarak metne dönüştürür.
+EXACT = ÖZDEŞ ## İki metin değerinin özdeş olup olmadığını anlamak için, değerleri denetler.
+FIND = BUL ## Bir metin değerini, bir başkasının içinde bulur (büyük küçük harf duyarlıdır).
+FINDB = BULB ## Bir metin değerini, bir başkasının içinde bulur (büyük küçük harf duyarlıdır).
+FIXED = SAYIDÜZENLE ## Bir sayıyı, sabit sayıda ondalıkla, metin olarak biçimlendirir.
+JIS = JIS ## Bir karakter dizesindeki tek enli (tek bayt) İngilizce harfleri veya katakanayı çift enli (iki bayt) karakterlerle değiştirir.
+LEFT = SOL ## Bir metin değerinden en soldaki karakterleri verir.
+LEFTB = SOLB ## Bir metin değerinden en soldaki karakterleri verir.
+LEN = UZUNLUK ## Bir metin dizesindeki karakter sayısını verir.
+LENB = UZUNLUKB ## Bir metin dizesindeki karakter sayısını verir.
+LOWER = KÜÇÜKHARF ## Metni küçük harfe çevirir.
+MID = ORTA ## Bir metin dizesinden belirli sayıda karakteri, belirttiğiniz konumdan başlamak üzere verir.
+MIDB = ORTAB ## Bir metin dizesinden belirli sayıda karakteri, belirttiğiniz konumdan başlamak üzere verir.
+PHONETIC = SES ## Metin dizesinden ses (furigana) karakterlerini ayıklar.
+PROPER = YAZIM.DÜZENİ ## Bir metin değerinin her bir sözcüğünün ilk harfini büyük harfe çevirir.
+REPLACE = DEĞİŞTİR ## Metnin içindeki karakterleri değiştirir.
+REPLACEB = DEĞİŞTİRB ## Metnin içindeki karakterleri değiştirir.
+REPT = YİNELE ## Metni belirtilen sayıda yineler.
+RIGHT = SAĞ ## Bir metin değerinden en sağdaki karakterleri verir.
+RIGHTB = SAĞB ## Bir metin değerinden en sağdaki karakterleri verir.
+SEARCH = BUL ## Bir metin değerini, bir başkasının içinde bulur (büyük küçük harf duyarlı değildir).
+SEARCHB = BULB ## Bir metin değerini, bir başkasının içinde bulur (büyük küçük harf duyarlı değildir).
+SUBSTITUTE = YERİNEKOY ## Bir metin dizesinde, eski metnin yerine yeni metin koyar.
+T = M ## Bağımsız değerlerini metne dönüştürür.
+TEXT = METNEÇEVİR ## Bir sayıyı biçimlendirir ve metne dönüştürür.
+TRIM = KIRP ## Metindeki boşlukları kaldırır.
+UPPER = BÜYÜKHARF ## Metni büyük harfe çevirir.
+VALUE = SAYIYAÇEVİR ## Bir metin bağımsız değişkenini sayıya dönüştürür.
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php
new file mode 100755
index 0000000..77a521b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+
+class AddressHelper
+{
+ /**
+ * Converts an R1C1 format cell address to an A1 format cell address.
+ */
+ public static function convertToA1(
+ string $address,
+ int $currentRowNumber = 1,
+ int $currentColumnNumber = 1
+ ): string {
+ $validityCheck = preg_match('/^(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))$/i', $address, $cellReference);
+
+ if ($validityCheck === 0) {
+ throw new Exception('Invalid R1C1-format Cell Reference');
+ }
+
+ $rowReference = $cellReference[2];
+ // Empty R reference is the current row
+ if ($rowReference === '') {
+ $rowReference = (string) $currentRowNumber;
+ }
+ // Bracketed R references are relative to the current row
+ if ($rowReference[0] === '[') {
+ $rowReference = $currentRowNumber + trim($rowReference, '[]');
+ }
+ $columnReference = $cellReference[4];
+ // Empty C reference is the current column
+ if ($columnReference === '') {
+ $columnReference = (string) $currentColumnNumber;
+ }
+ // Bracketed C references are relative to the current column
+ if ($columnReference[0] === '[') {
+ $columnReference = $currentColumnNumber + trim($columnReference, '[]');
+ }
+
+ if ($columnReference <= 0 || $rowReference <= 0) {
+ throw new Exception('Invalid R1C1-format Cell Reference, Value out of range');
+ }
+ $A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference;
+
+ return $A1CellReference;
+ }
+
+ /**
+ * Converts an A1 format cell address to an R1C1 format cell address.
+ * If $currentRowNumber or $currentColumnNumber are provided, then the R1C1 address will be formatted as a relative address.
+ */
+ public static function convertToR1C1(
+ string $address,
+ ?int $currentRowNumber = null,
+ ?int $currentColumnNumber = null
+ ): string {
+ $validityCheck = preg_match('/^\$?([A-Z]{1,3})\$?(\d{1,7})$/i', $address, $cellReference);
+
+ if ($validityCheck === 0) {
+ throw new Exception('Invalid A1-format Cell Reference');
+ }
+
+ $columnId = Coordinate::columnIndexFromString($cellReference[1]);
+ $rowId = (int) $cellReference[2];
+
+ if ($currentRowNumber !== null) {
+ if ($rowId === $currentRowNumber) {
+ $rowId = '';
+ } else {
+ $rowId = '[' . ($rowId - $currentRowNumber) . ']';
+ }
+ }
+
+ if ($currentColumnNumber !== null) {
+ if ($columnId === $currentColumnNumber) {
+ $columnId = '';
+ } else {
+ $columnId = '[' . ($columnId - $currentColumnNumber) . ']';
+ }
+ }
+
+ $R1C1Address = "R{$rowId}C{$columnId}";
+
+ return $R1C1Address;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php
new file mode 100755
index 0000000..cf638d0
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+
+class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
+{
+ /**
+ * Bind value to a cell.
+ *
+ * @param Cell $cell Cell to bind value to
+ * @param mixed $value Value to bind in cell
+ *
+ * @return bool
+ */
+ public function bindValue(Cell $cell, $value = null)
+ {
+ // sanitize UTF-8 strings
+ if (is_string($value)) {
+ $value = StringHelper::sanitizeUTF8($value);
+ }
+
+ // Find out data type
+ $dataType = parent::dataTypeForValue($value);
+
+ // Style logic - strings
+ if ($dataType === DataType::TYPE_STRING && !$value instanceof RichText) {
+ // Test for booleans using locale-setting
+ if ($value == Calculation::getTRUE()) {
+ $cell->setValueExplicit(true, DataType::TYPE_BOOL);
+
+ return true;
+ } elseif ($value == Calculation::getFALSE()) {
+ $cell->setValueExplicit(false, DataType::TYPE_BOOL);
+
+ return true;
+ }
+
+ // Check for number in scientific format
+ if (preg_match('/^' . Calculation::CALCULATION_REGEXP_NUMBER . '$/', $value)) {
+ $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC);
+
+ return true;
+ }
+
+ // Check for fraction
+ if (preg_match('/^([+-]?)\s*(\d+)\s?\/\s*(\d+)$/', $value, $matches)) {
+ // Convert value to number
+ $value = $matches[2] / $matches[3];
+ if ($matches[1] == '-') {
+ $value = 0 - $value;
+ }
+ $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode('??/??');
+
+ return true;
+ } elseif (preg_match('/^([+-]?)(\d*) +(\d*)\s?\/\s*(\d*)$/', $value, $matches)) {
+ // Convert value to number
+ $value = $matches[2] + ($matches[3] / $matches[4]);
+ if ($matches[1] == '-') {
+ $value = 0 - $value;
+ }
+ $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode('# ??/??');
+
+ return true;
+ }
+
+ // Check for percentage
+ if (preg_match('/^\-?\d*\.?\d*\s?\%$/', $value)) {
+ // Convert value to number
+ $value = (float) str_replace('%', '', $value) / 100;
+ $cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_PERCENTAGE_00);
+
+ return true;
+ }
+
+ // Check for currency
+ $currencyCode = StringHelper::getCurrencyCode();
+ $decimalSeparator = StringHelper::getDecimalSeparator();
+ $thousandsSeparator = StringHelper::getThousandsSeparator();
+ if (preg_match('/^' . preg_quote($currencyCode, '/') . ' *(\d{1,3}(' . preg_quote($thousandsSeparator, '/') . '\d{3})*|(\d+))(' . preg_quote($decimalSeparator, '/') . '\d{2})?$/', $value)) {
+ // Convert value to number
+ $value = (float) trim(str_replace([$currencyCode, $thousandsSeparator, $decimalSeparator], ['', '', '.'], $value));
+ $cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode(
+ str_replace('$', $currencyCode, NumberFormat::FORMAT_CURRENCY_USD_SIMPLE)
+ );
+
+ return true;
+ } elseif (preg_match('/^\$ *(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?$/', $value)) {
+ // Convert value to number
+ $value = (float) trim(str_replace(['$', ','], '', $value));
+ $cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_USD_SIMPLE);
+
+ return true;
+ }
+
+ // Check for time without seconds e.g. '9:45', '09:45'
+ if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d$/', $value)) {
+ // Convert value to number
+ [$h, $m] = explode(':', $value);
+ $days = $h / 24 + $m / 1440;
+ $cell->setValueExplicit($days, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME3);
+
+ return true;
+ }
+
+ // Check for time with seconds '9:45:59', '09:45:59'
+ if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d:[0-5]\d$/', $value)) {
+ // Convert value to number
+ [$h, $m, $s] = explode(':', $value);
+ $days = $h / 24 + $m / 1440 + $s / 86400;
+ // Convert value to number
+ $cell->setValueExplicit($days, DataType::TYPE_NUMERIC);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME4);
+
+ return true;
+ }
+
+ // Check for datetime, e.g. '2008-12-31', '2008-12-31 15:59', '2008-12-31 15:59:10'
+ if (($d = Date::stringToExcel($value)) !== false) {
+ // Convert value to number
+ $cell->setValueExplicit($d, DataType::TYPE_NUMERIC);
+ // Determine style. Either there is a time part or not. Look for ':'
+ if (strpos($value, ':') !== false) {
+ $formatCode = 'yyyy-mm-dd h:mm';
+ } else {
+ $formatCode = 'yyyy-mm-dd';
+ }
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getNumberFormat()->setFormatCode($formatCode);
+
+ return true;
+ }
+
+ // Check for newline character "\n"
+ if (strpos($value, "\n") !== false) {
+ $value = StringHelper::sanitizeUTF8($value);
+ $cell->setValueExplicit($value, DataType::TYPE_STRING);
+ // Set style
+ $cell->getWorksheet()->getStyle($cell->getCoordinate())
+ ->getAlignment()->setWrapText(true);
+
+ return true;
+ }
+ }
+
+ // Not bound yet? Use parent...
+ return parent::bindValue($cell, $value);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php
new file mode 100755
index 0000000..0bca2fc
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php
@@ -0,0 +1,676 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Collection\Cells;
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class Cell
+{
+ /**
+ * Value binder to use.
+ *
+ * @var IValueBinder
+ */
+ private static $valueBinder;
+
+ /**
+ * Value of the cell.
+ *
+ * @var mixed
+ */
+ private $value;
+
+ /**
+ * Calculated value of the cell (used for caching)
+ * This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
+ * create the original spreadsheet file.
+ * Note that this value is not guaranteed to reflect the actual calculated value because it is
+ * possible that auto-calculation was disabled in the original spreadsheet, and underlying data
+ * values used by the formula have changed since it was last calculated.
+ *
+ * @var mixed
+ */
+ private $calculatedValue;
+
+ /**
+ * Type of the cell data.
+ *
+ * @var string
+ */
+ private $dataType;
+
+ /**
+ * Collection of cells.
+ *
+ * @var Cells
+ */
+ private $parent;
+
+ /**
+ * Index to cellXf.
+ *
+ * @var int
+ */
+ private $xfIndex = 0;
+
+ /**
+ * Attributes of the formula.
+ */
+ private $formulaAttributes;
+
+ /**
+ * Update the cell into the cell collection.
+ *
+ * @return $this
+ */
+ public function updateInCollection()
+ {
+ $this->parent->update($this);
+
+ return $this;
+ }
+
+ public function detach(): void
+ {
+ $this->parent = null;
+ }
+
+ public function attach(Cells $parent): void
+ {
+ $this->parent = $parent;
+ }
+
+ /**
+ * Create a new Cell.
+ *
+ * @param mixed $pValue
+ * @param string $pDataType
+ */
+ public function __construct($pValue, $pDataType, Worksheet $pSheet)
+ {
+ // Initialise cell value
+ $this->value = $pValue;
+
+ // Set worksheet cache
+ $this->parent = $pSheet->getCellCollection();
+
+ // Set datatype?
+ if ($pDataType !== null) {
+ if ($pDataType == DataType::TYPE_STRING2) {
+ $pDataType = DataType::TYPE_STRING;
+ }
+ $this->dataType = $pDataType;
+ } elseif (!self::getValueBinder()->bindValue($this, $pValue)) {
+ throw new Exception('Value could not be bound to cell.');
+ }
+ }
+
+ /**
+ * Get cell coordinate column.
+ *
+ * @return string
+ */
+ public function getColumn()
+ {
+ return $this->parent->getCurrentColumn();
+ }
+
+ /**
+ * Get cell coordinate row.
+ *
+ * @return int
+ */
+ public function getRow()
+ {
+ return $this->parent->getCurrentRow();
+ }
+
+ /**
+ * Get cell coordinate.
+ *
+ * @return string
+ */
+ public function getCoordinate()
+ {
+ return $this->parent->getCurrentCoordinate();
+ }
+
+ /**
+ * Get cell value.
+ *
+ * @return mixed
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Get cell value with formatting.
+ *
+ * @return string
+ */
+ public function getFormattedValue()
+ {
+ return (string) NumberFormat::toFormattedString(
+ $this->getCalculatedValue(),
+ $this->getStyle()
+ ->getNumberFormat()->getFormatCode()
+ );
+ }
+
+ /**
+ * Set cell value.
+ *
+ * Sets the value for a cell, automatically determining the datatype using the value binder
+ *
+ * @param mixed $pValue Value
+ *
+ * @return $this
+ */
+ public function setValue($pValue)
+ {
+ if (!self::getValueBinder()->bindValue($this, $pValue)) {
+ throw new Exception('Value could not be bound to cell.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder).
+ *
+ * @param mixed $pValue Value
+ * @param string $pDataType Explicit data type, see DataType::TYPE_*
+ *
+ * @return Cell
+ */
+ public function setValueExplicit($pValue, $pDataType)
+ {
+ // set the value according to data type
+ switch ($pDataType) {
+ case DataType::TYPE_NULL:
+ $this->value = $pValue;
+
+ break;
+ case DataType::TYPE_STRING2:
+ $pDataType = DataType::TYPE_STRING;
+ // no break
+ case DataType::TYPE_STRING:
+ // Synonym for string
+ case DataType::TYPE_INLINE:
+ // Rich text
+ $this->value = DataType::checkString($pValue);
+
+ break;
+ case DataType::TYPE_NUMERIC:
+ if (is_string($pValue) && !is_numeric($pValue)) {
+ throw new Exception('Invalid numeric value for datatype Numeric');
+ }
+ $this->value = 0 + $pValue;
+
+ break;
+ case DataType::TYPE_FORMULA:
+ $this->value = (string) $pValue;
+
+ break;
+ case DataType::TYPE_BOOL:
+ $this->value = (bool) $pValue;
+
+ break;
+ case DataType::TYPE_ERROR:
+ $this->value = DataType::checkErrorCode($pValue);
+
+ break;
+ default:
+ throw new Exception('Invalid datatype: ' . $pDataType);
+
+ break;
+ }
+
+ // set the datatype
+ $this->dataType = $pDataType;
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Get calculated cell value.
+ *
+ * @param bool $resetLog Whether the calculation engine logger should be reset or not
+ *
+ * @return mixed
+ */
+ public function getCalculatedValue($resetLog = true)
+ {
+ if ($this->dataType == DataType::TYPE_FORMULA) {
+ try {
+ $result = Calculation::getInstance(
+ $this->getWorksheet()->getParent()
+ )->calculateCellValue($this, $resetLog);
+ // We don't yet handle array returns
+ if (is_array($result)) {
+ while (is_array($result)) {
+ $result = array_shift($result);
+ }
+ }
+ } catch (Exception $ex) {
+ if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) {
+ return $this->calculatedValue; // Fallback for calculations referencing external files.
+ }
+
+ throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception(
+ $this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage()
+ );
+ }
+
+ if ($result === '#Not Yet Implemented') {
+ return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
+ }
+
+ return $result;
+ } elseif ($this->value instanceof RichText) {
+ return $this->value->getPlainText();
+ }
+
+ return $this->value;
+ }
+
+ /**
+ * Set old calculated value (cached).
+ *
+ * @param mixed $pValue Value
+ *
+ * @return Cell
+ */
+ public function setCalculatedValue($pValue)
+ {
+ if ($pValue !== null) {
+ $this->calculatedValue = (is_numeric($pValue)) ? (float) $pValue : $pValue;
+ }
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Get old calculated value (cached)
+ * This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
+ * create the original spreadsheet file.
+ * Note that this value is not guaranteed to reflect the actual calculated value because it is
+ * possible that auto-calculation was disabled in the original spreadsheet, and underlying data
+ * values used by the formula have changed since it was last calculated.
+ *
+ * @return mixed
+ */
+ public function getOldCalculatedValue()
+ {
+ return $this->calculatedValue;
+ }
+
+ /**
+ * Get cell data type.
+ *
+ * @return string
+ */
+ public function getDataType()
+ {
+ return $this->dataType;
+ }
+
+ /**
+ * Set cell data type.
+ *
+ * @param string $pDataType see DataType::TYPE_*
+ *
+ * @return Cell
+ */
+ public function setDataType($pDataType)
+ {
+ if ($pDataType == DataType::TYPE_STRING2) {
+ $pDataType = DataType::TYPE_STRING;
+ }
+ $this->dataType = $pDataType;
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Identify if the cell contains a formula.
+ *
+ * @return bool
+ */
+ public function isFormula()
+ {
+ return $this->dataType == DataType::TYPE_FORMULA;
+ }
+
+ /**
+ * Does this cell contain Data validation rules?
+ *
+ * @return bool
+ */
+ public function hasDataValidation()
+ {
+ if (!isset($this->parent)) {
+ throw new Exception('Cannot check for data validation when cell is not bound to a worksheet');
+ }
+
+ return $this->getWorksheet()->dataValidationExists($this->getCoordinate());
+ }
+
+ /**
+ * Get Data validation rules.
+ *
+ * @return DataValidation
+ */
+ public function getDataValidation()
+ {
+ if (!isset($this->parent)) {
+ throw new Exception('Cannot get data validation for cell that is not bound to a worksheet');
+ }
+
+ return $this->getWorksheet()->getDataValidation($this->getCoordinate());
+ }
+
+ /**
+ * Set Data validation rules.
+ *
+ * @param DataValidation $pDataValidation
+ *
+ * @return Cell
+ */
+ public function setDataValidation(?DataValidation $pDataValidation = null)
+ {
+ if (!isset($this->parent)) {
+ throw new Exception('Cannot set data validation for cell that is not bound to a worksheet');
+ }
+
+ $this->getWorksheet()->setDataValidation($this->getCoordinate(), $pDataValidation);
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Does this cell contain valid value?
+ *
+ * @return bool
+ */
+ public function hasValidValue()
+ {
+ $validator = new DataValidator();
+
+ return $validator->isValid($this);
+ }
+
+ /**
+ * Does this cell contain a Hyperlink?
+ *
+ * @return bool
+ */
+ public function hasHyperlink()
+ {
+ if (!isset($this->parent)) {
+ throw new Exception('Cannot check for hyperlink when cell is not bound to a worksheet');
+ }
+
+ return $this->getWorksheet()->hyperlinkExists($this->getCoordinate());
+ }
+
+ /**
+ * Get Hyperlink.
+ *
+ * @return Hyperlink
+ */
+ public function getHyperlink()
+ {
+ if (!isset($this->parent)) {
+ throw new Exception('Cannot get hyperlink for cell that is not bound to a worksheet');
+ }
+
+ return $this->getWorksheet()->getHyperlink($this->getCoordinate());
+ }
+
+ /**
+ * Set Hyperlink.
+ *
+ * @param Hyperlink $pHyperlink
+ *
+ * @return Cell
+ */
+ public function setHyperlink(?Hyperlink $pHyperlink = null)
+ {
+ if (!isset($this->parent)) {
+ throw new Exception('Cannot set hyperlink for cell that is not bound to a worksheet');
+ }
+
+ $this->getWorksheet()->setHyperlink($this->getCoordinate(), $pHyperlink);
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Get cell collection.
+ *
+ * @return Cells
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Get parent worksheet.
+ *
+ * @return Worksheet
+ */
+ public function getWorksheet()
+ {
+ return $this->parent->getParent();
+ }
+
+ /**
+ * Is this cell in a merge range.
+ *
+ * @return bool
+ */
+ public function isInMergeRange()
+ {
+ return (bool) $this->getMergeRange();
+ }
+
+ /**
+ * Is this cell the master (top left cell) in a merge range (that holds the actual data value).
+ *
+ * @return bool
+ */
+ public function isMergeRangeValueCell()
+ {
+ if ($mergeRange = $this->getMergeRange()) {
+ $mergeRange = Coordinate::splitRange($mergeRange);
+ [$startCell] = $mergeRange[0];
+ if ($this->getCoordinate() === $startCell) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * If this cell is in a merge range, then return the range.
+ *
+ * @return false|string
+ */
+ public function getMergeRange()
+ {
+ foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) {
+ if ($this->isInRange($mergeRange)) {
+ return $mergeRange;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get cell style.
+ *
+ * @return Style
+ */
+ public function getStyle()
+ {
+ return $this->getWorksheet()->getStyle($this->getCoordinate());
+ }
+
+ /**
+ * Re-bind parent.
+ *
+ * @return Cell
+ */
+ public function rebindParent(Worksheet $parent)
+ {
+ $this->parent = $parent->getCellCollection();
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Is cell in a specific range?
+ *
+ * @param string $pRange Cell range (e.g. A1:A1)
+ *
+ * @return bool
+ */
+ public function isInRange($pRange)
+ {
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange);
+
+ // Translate properties
+ $myColumn = Coordinate::columnIndexFromString($this->getColumn());
+ $myRow = $this->getRow();
+
+ // Verify if cell is in range
+ return ($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) &&
+ ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow);
+ }
+
+ /**
+ * Compare 2 cells.
+ *
+ * @param Cell $a Cell a
+ * @param Cell $b Cell b
+ *
+ * @return int Result of comparison (always -1 or 1, never zero!)
+ */
+ public static function compareCells(self $a, self $b)
+ {
+ if ($a->getRow() < $b->getRow()) {
+ return -1;
+ } elseif ($a->getRow() > $b->getRow()) {
+ return 1;
+ } elseif (Coordinate::columnIndexFromString($a->getColumn()) < Coordinate::columnIndexFromString($b->getColumn())) {
+ return -1;
+ }
+
+ return 1;
+ }
+
+ /**
+ * Get value binder to use.
+ *
+ * @return IValueBinder
+ */
+ public static function getValueBinder()
+ {
+ if (self::$valueBinder === null) {
+ self::$valueBinder = new DefaultValueBinder();
+ }
+
+ return self::$valueBinder;
+ }
+
+ /**
+ * Set value binder to use.
+ */
+ public static function setValueBinder(IValueBinder $binder): void
+ {
+ self::$valueBinder = $binder;
+ }
+
+ /**
+ * 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)) && ($key != 'parent')) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ /**
+ * Get index to cellXf.
+ *
+ * @return int
+ */
+ public function getXfIndex()
+ {
+ return $this->xfIndex;
+ }
+
+ /**
+ * Set index to cellXf.
+ *
+ * @param int $pValue
+ *
+ * @return Cell
+ */
+ public function setXfIndex($pValue)
+ {
+ $this->xfIndex = $pValue;
+
+ return $this->updateInCollection();
+ }
+
+ /**
+ * Set the formula attributes.
+ *
+ * @param mixed $pAttributes
+ *
+ * @return $this
+ */
+ public function setFormulaAttributes($pAttributes)
+ {
+ $this->formulaAttributes = $pAttributes;
+
+ return $this;
+ }
+
+ /**
+ * Get the formula attributes.
+ */
+ public function getFormulaAttributes()
+ {
+ return $this->formulaAttributes;
+ }
+
+ /**
+ * Convert to string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->getValue();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php
new file mode 100755
index 0000000..8c67991
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php
@@ -0,0 +1,512 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+/**
+ * Helper class to manipulate cell coordinates.
+ *
+ * Columns indexes and rows are always based on 1, **not** on 0. This match the behavior
+ * that Excel users are used to, and also match the Excel functions `COLUMN()` and `ROW()`.
+ */
+abstract class Coordinate
+{
+ /**
+ * Default range variable constant.
+ *
+ * @var string
+ */
+ const DEFAULT_RANGE = 'A1:A1';
+
+ /**
+ * Coordinate from string.
+ *
+ * @param string $pCoordinateString eg: 'A1'
+ *
+ * @return string[] Array containing column and row (indexes 0 and 1)
+ */
+ public static function coordinateFromString($pCoordinateString)
+ {
+ if (preg_match('/^([$]?[A-Z]{1,3})([$]?\\d{1,7})$/', $pCoordinateString, $matches)) {
+ return [$matches[1], $matches[2]];
+ } elseif (self::coordinateIsRange($pCoordinateString)) {
+ throw new Exception('Cell coordinate string can not be a range of cells');
+ } elseif ($pCoordinateString == '') {
+ throw new Exception('Cell coordinate can not be zero-length string');
+ }
+
+ throw new Exception('Invalid cell coordinate ' . $pCoordinateString);
+ }
+
+ /**
+ * Checks if a coordinate represents a range of cells.
+ *
+ * @param string $coord eg: 'A1' or 'A1:A2' or 'A1:A2,C1:C2'
+ *
+ * @return bool Whether the coordinate represents a range of cells
+ */
+ public static function coordinateIsRange($coord)
+ {
+ return (strpos($coord, ':') !== false) || (strpos($coord, ',') !== false);
+ }
+
+ /**
+ * Make string row, column or cell coordinate absolute.
+ *
+ * @param string $pCoordinateString e.g. 'A' or '1' or 'A1'
+ * Note that this value can be a row or column reference as well as a cell reference
+ *
+ * @return string Absolute coordinate e.g. '$A' or '$1' or '$A$1'
+ */
+ public static function absoluteReference($pCoordinateString)
+ {
+ if (self::coordinateIsRange($pCoordinateString)) {
+ throw new Exception('Cell coordinate string can not be a range of cells');
+ }
+
+ // Split out any worksheet name from the reference
+ [$worksheet, $pCoordinateString] = Worksheet::extractSheetTitle($pCoordinateString, true);
+ if ($worksheet > '') {
+ $worksheet .= '!';
+ }
+
+ // Create absolute coordinate
+ if (ctype_digit($pCoordinateString)) {
+ return $worksheet . '$' . $pCoordinateString;
+ } elseif (ctype_alpha($pCoordinateString)) {
+ return $worksheet . '$' . strtoupper($pCoordinateString);
+ }
+
+ return $worksheet . self::absoluteCoordinate($pCoordinateString);
+ }
+
+ /**
+ * Make string coordinate absolute.
+ *
+ * @param string $pCoordinateString e.g. 'A1'
+ *
+ * @return string Absolute coordinate e.g. '$A$1'
+ */
+ public static function absoluteCoordinate($pCoordinateString)
+ {
+ if (self::coordinateIsRange($pCoordinateString)) {
+ throw new Exception('Cell coordinate string can not be a range of cells');
+ }
+
+ // Split out any worksheet name from the coordinate
+ [$worksheet, $pCoordinateString] = Worksheet::extractSheetTitle($pCoordinateString, true);
+ if ($worksheet > '') {
+ $worksheet .= '!';
+ }
+
+ // Create absolute coordinate
+ [$column, $row] = self::coordinateFromString($pCoordinateString);
+ $column = ltrim($column, '$');
+ $row = ltrim($row, '$');
+
+ return $worksheet . '$' . $column . '$' . $row;
+ }
+
+ /**
+ * Split range into coordinate strings.
+ *
+ * @param string $pRange e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4'
+ *
+ * @return array Array containing one or more arrays containing one or two coordinate strings
+ * e.g. ['B4','D9'] or [['B4','D9'], ['H2','O11']]
+ * or ['B4']
+ */
+ public static function splitRange($pRange)
+ {
+ // Ensure $pRange is a valid range
+ if (empty($pRange)) {
+ $pRange = self::DEFAULT_RANGE;
+ }
+
+ $exploded = explode(',', $pRange);
+ $counter = count($exploded);
+ for ($i = 0; $i < $counter; ++$i) {
+ $exploded[$i] = explode(':', $exploded[$i]);
+ }
+
+ return $exploded;
+ }
+
+ /**
+ * Build range from coordinate strings.
+ *
+ * @param array $pRange Array containg one or more arrays containing one or two coordinate strings
+ *
+ * @return string String representation of $pRange
+ */
+ public static function buildRange(array $pRange)
+ {
+ // Verify range
+ if (empty($pRange) || !is_array($pRange[0])) {
+ throw new Exception('Range does not contain any information');
+ }
+
+ // Build range
+ $counter = count($pRange);
+ for ($i = 0; $i < $counter; ++$i) {
+ $pRange[$i] = implode(':', $pRange[$i]);
+ }
+
+ return implode(',', $pRange);
+ }
+
+ /**
+ * Calculate range boundaries.
+ *
+ * @param string $pRange Cell range (e.g. A1:A1)
+ *
+ * @return array Range coordinates [Start Cell, End Cell]
+ * where Start Cell and End Cell are arrays (Column Number, Row Number)
+ */
+ public static function rangeBoundaries($pRange)
+ {
+ // Ensure $pRange is a valid range
+ if (empty($pRange)) {
+ $pRange = self::DEFAULT_RANGE;
+ }
+
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ // Extract range
+ if (strpos($pRange, ':') === false) {
+ $rangeA = $rangeB = $pRange;
+ } else {
+ [$rangeA, $rangeB] = explode(':', $pRange);
+ }
+
+ // Calculate range outer borders
+ $rangeStart = self::coordinateFromString($rangeA);
+ $rangeEnd = self::coordinateFromString($rangeB);
+
+ // Translate column into index
+ $rangeStart[0] = self::columnIndexFromString($rangeStart[0]);
+ $rangeEnd[0] = self::columnIndexFromString($rangeEnd[0]);
+
+ return [$rangeStart, $rangeEnd];
+ }
+
+ /**
+ * Calculate range dimension.
+ *
+ * @param string $pRange Cell range (e.g. A1:A1)
+ *
+ * @return array Range dimension (width, height)
+ */
+ public static function rangeDimension($pRange)
+ {
+ // Calculate range outer borders
+ [$rangeStart, $rangeEnd] = self::rangeBoundaries($pRange);
+
+ return [($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1)];
+ }
+
+ /**
+ * Calculate range boundaries.
+ *
+ * @param string $pRange Cell range (e.g. A1:A1)
+ *
+ * @return array Range coordinates [Start Cell, End Cell]
+ * where Start Cell and End Cell are arrays [Column ID, Row Number]
+ */
+ public static function getRangeBoundaries($pRange)
+ {
+ // Ensure $pRange is a valid range
+ if (empty($pRange)) {
+ $pRange = self::DEFAULT_RANGE;
+ }
+
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ // Extract range
+ if (strpos($pRange, ':') === false) {
+ $rangeA = $rangeB = $pRange;
+ } else {
+ [$rangeA, $rangeB] = explode(':', $pRange);
+ }
+
+ return [self::coordinateFromString($rangeA), self::coordinateFromString($rangeB)];
+ }
+
+ /**
+ * Column index from string.
+ *
+ * @param string $pString eg 'A'
+ *
+ * @return int Column index (A = 1)
+ */
+ public static function columnIndexFromString($pString)
+ {
+ // Using a lookup cache adds a slight memory overhead, but boosts speed
+ // caching using a static within the method is faster than a class static,
+ // though it's additional memory overhead
+ static $indexCache = [];
+
+ if (isset($indexCache[$pString])) {
+ return $indexCache[$pString];
+ }
+ // It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array rather than use ord()
+ // and make it case insensitive to get rid of the strtoupper() as well. Because it's a static, there's no significant
+ // memory overhead either
+ static $columnLookup = [
+ 'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8, 'I' => 9, 'J' => 10, 'K' => 11, 'L' => 12, 'M' => 13,
+ 'N' => 14, 'O' => 15, 'P' => 16, 'Q' => 17, 'R' => 18, 'S' => 19, 'T' => 20, 'U' => 21, 'V' => 22, 'W' => 23, 'X' => 24, 'Y' => 25, 'Z' => 26,
+ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10, 'k' => 11, 'l' => 12, 'm' => 13,
+ 'n' => 14, 'o' => 15, 'p' => 16, 'q' => 17, 'r' => 18, 's' => 19, 't' => 20, 'u' => 21, 'v' => 22, 'w' => 23, 'x' => 24, 'y' => 25, 'z' => 26,
+ ];
+
+ // We also use the language construct isset() rather than the more costly strlen() function to match the length of $pString
+ // for improved performance
+ if (isset($pString[0])) {
+ if (!isset($pString[1])) {
+ $indexCache[$pString] = $columnLookup[$pString];
+
+ return $indexCache[$pString];
+ } elseif (!isset($pString[2])) {
+ $indexCache[$pString] = $columnLookup[$pString[0]] * 26 + $columnLookup[$pString[1]];
+
+ return $indexCache[$pString];
+ } elseif (!isset($pString[3])) {
+ $indexCache[$pString] = $columnLookup[$pString[0]] * 676 + $columnLookup[$pString[1]] * 26 + $columnLookup[$pString[2]];
+
+ return $indexCache[$pString];
+ }
+ }
+
+ throw new Exception('Column string index can not be ' . ((isset($pString[0])) ? 'longer than 3 characters' : 'empty'));
+ }
+
+ /**
+ * String from column index.
+ *
+ * @param int $columnIndex Column index (A = 1)
+ *
+ * @return string
+ */
+ public static function stringFromColumnIndex($columnIndex)
+ {
+ static $indexCache = [];
+
+ if (!isset($indexCache[$columnIndex])) {
+ $indexValue = $columnIndex;
+ $base26 = null;
+ do {
+ $characterValue = ($indexValue % 26) ?: 26;
+ $indexValue = ($indexValue - $characterValue) / 26;
+ $base26 = chr($characterValue + 64) . ($base26 ?: '');
+ } while ($indexValue > 0);
+ $indexCache[$columnIndex] = $base26;
+ }
+
+ return $indexCache[$columnIndex];
+ }
+
+ /**
+ * Extract all cell references in range, which may be comprised of multiple cell ranges.
+ *
+ * @param string $pRange Range (e.g. A1 or A1:C10 or A1:E10 A20:E25)
+ *
+ * @return array Array containing single cell references
+ */
+ public static function extractAllCellReferencesInRange($pRange)
+ {
+ $returnValue = [];
+
+ // Explode spaces
+ $cellBlocks = self::getCellBlocksFromRangeString($pRange);
+ foreach ($cellBlocks as $cellBlock) {
+ $returnValue = array_merge($returnValue, self::getReferencesForCellBlock($cellBlock));
+ }
+
+ // Sort the result by column and row
+ $sortKeys = [];
+ foreach (array_unique($returnValue) as $coord) {
+ $column = '';
+ $row = 0;
+
+ sscanf($coord, '%[A-Z]%d', $column, $row);
+ $sortKeys[sprintf('%3s%09d', $column, $row)] = $coord;
+ }
+ ksort($sortKeys);
+
+ // Return value
+ return array_values($sortKeys);
+ }
+
+ /**
+ * Get all cell references for an individual cell block.
+ *
+ * @param string $cellBlock A cell range e.g. A4:B5
+ *
+ * @return array All individual cells in that range
+ */
+ private static function getReferencesForCellBlock($cellBlock)
+ {
+ $returnValue = [];
+
+ // Single cell?
+ if (!self::coordinateIsRange($cellBlock)) {
+ return (array) $cellBlock;
+ }
+
+ // Range...
+ $ranges = self::splitRange($cellBlock);
+ foreach ($ranges as $range) {
+ // Single cell?
+ if (!isset($range[1])) {
+ $returnValue[] = $range[0];
+
+ continue;
+ }
+
+ // Range...
+ [$rangeStart, $rangeEnd] = $range;
+ [$startColumn, $startRow] = self::coordinateFromString($rangeStart);
+ [$endColumn, $endRow] = self::coordinateFromString($rangeEnd);
+ $startColumnIndex = self::columnIndexFromString($startColumn);
+ $endColumnIndex = self::columnIndexFromString($endColumn);
+ ++$endColumnIndex;
+
+ // Current data
+ $currentColumnIndex = $startColumnIndex;
+ $currentRow = $startRow;
+
+ self::validateRange($cellBlock, $startColumnIndex, $endColumnIndex, $currentRow, $endRow);
+
+ // Loop cells
+ while ($currentColumnIndex < $endColumnIndex) {
+ while ($currentRow <= $endRow) {
+ $returnValue[] = self::stringFromColumnIndex($currentColumnIndex) . $currentRow;
+ ++$currentRow;
+ }
+ ++$currentColumnIndex;
+ $currentRow = $startRow;
+ }
+ }
+
+ return $returnValue;
+ }
+
+ /**
+ * Convert an associative array of single cell coordinates to values to an associative array
+ * of cell ranges to values. Only adjacent cell coordinates with the same
+ * value will be merged. If the value is an object, it must implement the method getHashCode().
+ *
+ * For example, this function converts:
+ *
+ * [ 'A1' => 'x', 'A2' => 'x', 'A3' => 'x', 'A4' => 'y' ]
+ *
+ * to:
+ *
+ * [ 'A1:A3' => 'x', 'A4' => 'y' ]
+ *
+ * @param array $pCoordCollection associative array mapping coordinates to values
+ *
+ * @return array associative array mapping coordinate ranges to valuea
+ */
+ public static function mergeRangesInCollection(array $pCoordCollection)
+ {
+ $hashedValues = [];
+ $mergedCoordCollection = [];
+
+ foreach ($pCoordCollection as $coord => $value) {
+ if (self::coordinateIsRange($coord)) {
+ $mergedCoordCollection[$coord] = $value;
+
+ continue;
+ }
+
+ [$column, $row] = self::coordinateFromString($coord);
+ $row = (int) (ltrim($row, '$'));
+ $hashCode = $column . '-' . (is_object($value) ? $value->getHashCode() : $value);
+
+ if (!isset($hashedValues[$hashCode])) {
+ $hashedValues[$hashCode] = (object) [
+ 'value' => $value,
+ 'col' => $column,
+ 'rows' => [$row],
+ ];
+ } else {
+ $hashedValues[$hashCode]->rows[] = $row;
+ }
+ }
+
+ ksort($hashedValues);
+
+ foreach ($hashedValues as $hashedValue) {
+ sort($hashedValue->rows);
+ $rowStart = null;
+ $rowEnd = null;
+ $ranges = [];
+
+ foreach ($hashedValue->rows as $row) {
+ if ($rowStart === null) {
+ $rowStart = $row;
+ $rowEnd = $row;
+ } elseif ($rowEnd === $row - 1) {
+ $rowEnd = $row;
+ } else {
+ if ($rowStart == $rowEnd) {
+ $ranges[] = $hashedValue->col . $rowStart;
+ } else {
+ $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd;
+ }
+
+ $rowStart = $row;
+ $rowEnd = $row;
+ }
+ }
+
+ if ($rowStart !== null) {
+ if ($rowStart == $rowEnd) {
+ $ranges[] = $hashedValue->col . $rowStart;
+ } else {
+ $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd;
+ }
+ }
+
+ foreach ($ranges as $range) {
+ $mergedCoordCollection[$range] = $hashedValue->value;
+ }
+ }
+
+ return $mergedCoordCollection;
+ }
+
+ /**
+ * Get the individual cell blocks from a range string, splitting by space and removing any $ characters.
+ *
+ * @param string $pRange
+ *
+ * @return string[]
+ */
+ private static function getCellBlocksFromRangeString($pRange)
+ {
+ return explode(' ', str_replace('$', '', strtoupper($pRange)));
+ }
+
+ /**
+ * Check that the given range is valid, i.e. that the start column and row are not greater than the end column and
+ * row.
+ *
+ * @param string $cellBlock The original range, for displaying a meaningful error message
+ * @param int $startColumnIndex
+ * @param int $endColumnIndex
+ * @param int $currentRow
+ * @param int $endRow
+ */
+ private static function validateRange($cellBlock, $startColumnIndex, $endColumnIndex, $currentRow, $endRow): void
+ {
+ if ($startColumnIndex >= $endColumnIndex || $currentRow > $endRow) {
+ throw new Exception('Invalid range: "' . $cellBlock . '"');
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php
new file mode 100755
index 0000000..ba03579
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class DataType
+{
+ // Data types
+ const TYPE_STRING2 = 'str';
+ const TYPE_STRING = 's';
+ const TYPE_FORMULA = 'f';
+ const TYPE_NUMERIC = 'n';
+ const TYPE_BOOL = 'b';
+ const TYPE_NULL = 'null';
+ const TYPE_INLINE = 'inlineStr';
+ const TYPE_ERROR = 'e';
+
+ /**
+ * List of error codes.
+ *
+ * @var array
+ */
+ private static $errorCodes = [
+ '#NULL!' => 0,
+ '#DIV/0!' => 1,
+ '#VALUE!' => 2,
+ '#REF!' => 3,
+ '#NAME?' => 4,
+ '#NUM!' => 5,
+ '#N/A' => 6,
+ ];
+
+ /**
+ * Get list of error codes.
+ *
+ * @return array
+ */
+ public static function getErrorCodes()
+ {
+ return self::$errorCodes;
+ }
+
+ /**
+ * Check a string that it satisfies Excel requirements.
+ *
+ * @param null|RichText|string $pValue Value to sanitize to an Excel string
+ *
+ * @return null|RichText|string Sanitized value
+ */
+ public static function checkString($pValue)
+ {
+ if ($pValue instanceof RichText) {
+ // TODO: Sanitize Rich-Text string (max. character count is 32,767)
+ return $pValue;
+ }
+
+ // string must never be longer than 32,767 characters, truncate if necessary
+ $pValue = StringHelper::substring($pValue, 0, 32767);
+
+ // we require that newline is represented as "\n" in core, not as "\r\n" or "\r"
+ $pValue = str_replace(["\r\n", "\r"], "\n", $pValue);
+
+ return $pValue;
+ }
+
+ /**
+ * Check a value that it is a valid error code.
+ *
+ * @param mixed $pValue Value to sanitize to an Excel error code
+ *
+ * @return string Sanitized value
+ */
+ public static function checkErrorCode($pValue)
+ {
+ $pValue = (string) $pValue;
+
+ if (!isset(self::$errorCodes[$pValue])) {
+ $pValue = '#NULL!';
+ }
+
+ return $pValue;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php
new file mode 100755
index 0000000..dfeb024
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php
@@ -0,0 +1,481 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+class DataValidation
+{
+ // Data validation types
+ const TYPE_NONE = 'none';
+ const TYPE_CUSTOM = 'custom';
+ const TYPE_DATE = 'date';
+ const TYPE_DECIMAL = 'decimal';
+ const TYPE_LIST = 'list';
+ const TYPE_TEXTLENGTH = 'textLength';
+ const TYPE_TIME = 'time';
+ const TYPE_WHOLE = 'whole';
+
+ // Data validation error styles
+ const STYLE_STOP = 'stop';
+ const STYLE_WARNING = 'warning';
+ const STYLE_INFORMATION = 'information';
+
+ // Data validation operators
+ const OPERATOR_BETWEEN = 'between';
+ const OPERATOR_EQUAL = 'equal';
+ const OPERATOR_GREATERTHAN = 'greaterThan';
+ const OPERATOR_GREATERTHANOREQUAL = 'greaterThanOrEqual';
+ const OPERATOR_LESSTHAN = 'lessThan';
+ const OPERATOR_LESSTHANOREQUAL = 'lessThanOrEqual';
+ const OPERATOR_NOTBETWEEN = 'notBetween';
+ const OPERATOR_NOTEQUAL = 'notEqual';
+
+ /**
+ * Formula 1.
+ *
+ * @var string
+ */
+ private $formula1 = '';
+
+ /**
+ * Formula 2.
+ *
+ * @var string
+ */
+ private $formula2 = '';
+
+ /**
+ * Type.
+ *
+ * @var string
+ */
+ private $type = self::TYPE_NONE;
+
+ /**
+ * Error style.
+ *
+ * @var string
+ */
+ private $errorStyle = self::STYLE_STOP;
+
+ /**
+ * Operator.
+ *
+ * @var string
+ */
+ private $operator = self::OPERATOR_BETWEEN;
+
+ /**
+ * Allow Blank.
+ *
+ * @var bool
+ */
+ private $allowBlank = false;
+
+ /**
+ * Show DropDown.
+ *
+ * @var bool
+ */
+ private $showDropDown = false;
+
+ /**
+ * Show InputMessage.
+ *
+ * @var bool
+ */
+ private $showInputMessage = false;
+
+ /**
+ * Show ErrorMessage.
+ *
+ * @var bool
+ */
+ private $showErrorMessage = false;
+
+ /**
+ * Error title.
+ *
+ * @var string
+ */
+ private $errorTitle = '';
+
+ /**
+ * Error.
+ *
+ * @var string
+ */
+ private $error = '';
+
+ /**
+ * Prompt title.
+ *
+ * @var string
+ */
+ private $promptTitle = '';
+
+ /**
+ * Prompt.
+ *
+ * @var string
+ */
+ private $prompt = '';
+
+ /**
+ * Create a new DataValidation.
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * Get Formula 1.
+ *
+ * @return string
+ */
+ public function getFormula1()
+ {
+ return $this->formula1;
+ }
+
+ /**
+ * Set Formula 1.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setFormula1($value)
+ {
+ $this->formula1 = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Formula 2.
+ *
+ * @return string
+ */
+ public function getFormula2()
+ {
+ return $this->formula2;
+ }
+
+ /**
+ * Set Formula 2.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setFormula2($value)
+ {
+ $this->formula2 = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Type.
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Set Type.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setType($value)
+ {
+ $this->type = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Error style.
+ *
+ * @return string
+ */
+ public function getErrorStyle()
+ {
+ return $this->errorStyle;
+ }
+
+ /**
+ * Set Error style.
+ *
+ * @param string $value see self::STYLE_*
+ *
+ * @return $this
+ */
+ public function setErrorStyle($value)
+ {
+ $this->errorStyle = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Operator.
+ *
+ * @return string
+ */
+ public function getOperator()
+ {
+ return $this->operator;
+ }
+
+ /**
+ * Set Operator.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setOperator($value)
+ {
+ $this->operator = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Allow Blank.
+ *
+ * @return bool
+ */
+ public function getAllowBlank()
+ {
+ return $this->allowBlank;
+ }
+
+ /**
+ * Set Allow Blank.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setAllowBlank($value)
+ {
+ $this->allowBlank = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Show DropDown.
+ *
+ * @return bool
+ */
+ public function getShowDropDown()
+ {
+ return $this->showDropDown;
+ }
+
+ /**
+ * Set Show DropDown.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setShowDropDown($value)
+ {
+ $this->showDropDown = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Show InputMessage.
+ *
+ * @return bool
+ */
+ public function getShowInputMessage()
+ {
+ return $this->showInputMessage;
+ }
+
+ /**
+ * Set Show InputMessage.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setShowInputMessage($value)
+ {
+ $this->showInputMessage = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Show ErrorMessage.
+ *
+ * @return bool
+ */
+ public function getShowErrorMessage()
+ {
+ return $this->showErrorMessage;
+ }
+
+ /**
+ * Set Show ErrorMessage.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setShowErrorMessage($value)
+ {
+ $this->showErrorMessage = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Error title.
+ *
+ * @return string
+ */
+ public function getErrorTitle()
+ {
+ return $this->errorTitle;
+ }
+
+ /**
+ * Set Error title.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setErrorTitle($value)
+ {
+ $this->errorTitle = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Error.
+ *
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * Set Error.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setError($value)
+ {
+ $this->error = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Prompt title.
+ *
+ * @return string
+ */
+ public function getPromptTitle()
+ {
+ return $this->promptTitle;
+ }
+
+ /**
+ * Set Prompt title.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setPromptTitle($value)
+ {
+ $this->promptTitle = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Prompt.
+ *
+ * @return string
+ */
+ public function getPrompt()
+ {
+ return $this->prompt;
+ }
+
+ /**
+ * Set Prompt.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setPrompt($value)
+ {
+ $this->prompt = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->formula1 .
+ $this->formula2 .
+ $this->type .
+ $this->errorStyle .
+ $this->operator .
+ ($this->allowBlank ? 't' : 'f') .
+ ($this->showDropDown ? 't' : 'f') .
+ ($this->showInputMessage ? 't' : 'f') .
+ ($this->showErrorMessage ? 't' : 'f') .
+ $this->errorTitle .
+ $this->error .
+ $this->promptTitle .
+ $this->prompt .
+ __CLASS__
+ );
+ }
+
+ /**
+ * 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php
new file mode 100755
index 0000000..430d81b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Exception;
+
+/**
+ * Validate a cell value according to its validation rules.
+ */
+class DataValidator
+{
+ /**
+ * Does this cell contain valid value?
+ *
+ * @param Cell $cell Cell to check the value
+ *
+ * @return bool
+ */
+ public function isValid(Cell $cell)
+ {
+ if (!$cell->hasDataValidation()) {
+ return true;
+ }
+
+ $cellValue = $cell->getValue();
+ $dataValidation = $cell->getDataValidation();
+
+ if (!$dataValidation->getAllowBlank() && ($cellValue === null || $cellValue === '')) {
+ return false;
+ }
+
+ // TODO: write check on all cases
+ switch ($dataValidation->getType()) {
+ case DataValidation::TYPE_LIST:
+ return $this->isValueInList($cell);
+ }
+
+ return false;
+ }
+
+ /**
+ * Does this cell contain valid value, based on list?
+ *
+ * @param Cell $cell Cell to check the value
+ *
+ * @return bool
+ */
+ private function isValueInList(Cell $cell)
+ {
+ $cellValue = $cell->getValue();
+ $dataValidation = $cell->getDataValidation();
+
+ $formula1 = $dataValidation->getFormula1();
+ if (!empty($formula1)) {
+ // inline values list
+ if ($formula1[0] === '"') {
+ return in_array(strtolower($cellValue), explode(',', strtolower(trim($formula1, '"'))), true);
+ } elseif (strpos($formula1, ':') > 0) {
+ // values list cells
+ $matchFormula = '=MATCH(' . $cell->getCoordinate() . ', ' . $formula1 . ', 0)';
+ $calculation = Calculation::getInstance($cell->getWorksheet()->getParent());
+
+ try {
+ $result = $calculation->calculateFormula($matchFormula, $cell->getCoordinate(), $cell);
+
+ return $result !== Functions::NA();
+ } catch (Exception $ex) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php
new file mode 100755
index 0000000..693446e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use DateTimeInterface;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class DefaultValueBinder implements IValueBinder
+{
+ /**
+ * Bind value to a cell.
+ *
+ * @param Cell $cell Cell to bind value to
+ * @param mixed $value Value to bind in cell
+ *
+ * @return bool
+ */
+ public function bindValue(Cell $cell, $value)
+ {
+ // sanitize UTF-8 strings
+ if (is_string($value)) {
+ $value = StringHelper::sanitizeUTF8($value);
+ } elseif (is_object($value)) {
+ // Handle any objects that might be injected
+ if ($value instanceof DateTimeInterface) {
+ $value = $value->format('Y-m-d H:i:s');
+ } elseif (!($value instanceof RichText)) {
+ $value = (string) $value;
+ }
+ }
+
+ // Set value explicit
+ $cell->setValueExplicit($value, static::dataTypeForValue($value));
+
+ // Done!
+ return true;
+ }
+
+ /**
+ * DataType for value.
+ *
+ * @param mixed $pValue
+ *
+ * @return string
+ */
+ public static function dataTypeForValue($pValue)
+ {
+ // Match the value against a few data types
+ if ($pValue === null) {
+ return DataType::TYPE_NULL;
+ } elseif (is_float($pValue) || is_int($pValue)) {
+ return DataType::TYPE_NUMERIC;
+ } elseif (is_bool($pValue)) {
+ return DataType::TYPE_BOOL;
+ } elseif ($pValue === '') {
+ return DataType::TYPE_STRING;
+ } elseif ($pValue instanceof RichText) {
+ return DataType::TYPE_INLINE;
+ } elseif (is_string($pValue) && $pValue[0] === '=' && strlen($pValue) > 1) {
+ return DataType::TYPE_FORMULA;
+ } elseif (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $pValue)) {
+ $tValue = ltrim($pValue, '+-');
+ if (is_string($pValue) && $tValue[0] === '0' && strlen($tValue) > 1 && $tValue[1] !== '.') {
+ return DataType::TYPE_STRING;
+ } elseif ((strpos($pValue, '.') === false) && ($pValue > PHP_INT_MAX)) {
+ return DataType::TYPE_STRING;
+ } elseif (!is_numeric($pValue)) {
+ return DataType::TYPE_STRING;
+ }
+
+ return DataType::TYPE_NUMERIC;
+ } elseif (is_string($pValue)) {
+ $errorCodes = DataType::getErrorCodes();
+ if (isset($errorCodes[$pValue])) {
+ return DataType::TYPE_ERROR;
+ }
+ }
+
+ return DataType::TYPE_STRING;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php
new file mode 100755
index 0000000..003d510
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+class Hyperlink
+{
+ /**
+ * URL to link the cell to.
+ *
+ * @var string
+ */
+ private $url;
+
+ /**
+ * Tooltip to display on the hyperlink.
+ *
+ * @var string
+ */
+ private $tooltip;
+
+ /**
+ * Create a new Hyperlink.
+ *
+ * @param string $pUrl Url to link the cell to
+ * @param string $pTooltip Tooltip to display on the hyperlink
+ */
+ public function __construct($pUrl = '', $pTooltip = '')
+ {
+ // Initialise member variables
+ $this->url = $pUrl;
+ $this->tooltip = $pTooltip;
+ }
+
+ /**
+ * Get URL.
+ *
+ * @return string
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * Set URL.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setUrl($value)
+ {
+ $this->url = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get tooltip.
+ *
+ * @return string
+ */
+ public function getTooltip()
+ {
+ return $this->tooltip;
+ }
+
+ /**
+ * Set tooltip.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setTooltip($value)
+ {
+ $this->tooltip = $value;
+
+ return $this;
+ }
+
+ /**
+ * Is this hyperlink internal? (to another worksheet).
+ *
+ * @return bool
+ */
+ public function isInternal()
+ {
+ return strpos($this->url, 'sheet://') !== false;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTypeHyperlink()
+ {
+ return $this->isInternal() ? '' : 'External';
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->url .
+ $this->tooltip .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php
new file mode 100755
index 0000000..5af9f5f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+interface IValueBinder
+{
+ /**
+ * Bind value to a cell.
+ *
+ * @param Cell $cell Cell to bind value to
+ * @param mixed $value Value to bind in cell
+ *
+ * @return bool
+ */
+ public function bindValue(Cell $cell, $value);
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php
new file mode 100755
index 0000000..346d025
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Cell;
+
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class StringValueBinder implements IValueBinder
+{
+ /**
+ * Bind value to a cell.
+ *
+ * @param Cell $cell Cell to bind value to
+ * @param mixed $value Value to bind in cell
+ *
+ * @return bool
+ */
+ public function bindValue(Cell $cell, $value)
+ {
+ // sanitize UTF-8 strings
+ if (is_string($value)) {
+ $value = StringHelper::sanitizeUTF8($value);
+ }
+
+ $cell->setValueExplicit((string) $value, DataType::TYPE_STRING);
+
+ // Done!
+ return true;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php
new file mode 100755
index 0000000..7995c3b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php
@@ -0,0 +1,556 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+/**
+ * Created by PhpStorm.
+ * User: Wiktor Trzonkowski
+ * Date: 6/17/14
+ * Time: 12:11 PM.
+ */
+class Axis extends Properties
+{
+ /**
+ * Axis Number.
+ *
+ * @var array of mixed
+ */
+ private $axisNumber = [
+ 'format' => self::FORMAT_CODE_GENERAL,
+ 'source_linked' => 1,
+ ];
+
+ /**
+ * Axis Options.
+ *
+ * @var array of mixed
+ */
+ private $axisOptions = [
+ 'minimum' => null,
+ 'maximum' => null,
+ 'major_unit' => null,
+ 'minor_unit' => null,
+ 'orientation' => self::ORIENTATION_NORMAL,
+ 'minor_tick_mark' => self::TICK_MARK_NONE,
+ 'major_tick_mark' => self::TICK_MARK_NONE,
+ 'axis_labels' => self::AXIS_LABELS_NEXT_TO,
+ 'horizontal_crosses' => self::HORIZONTAL_CROSSES_AUTOZERO,
+ 'horizontal_crosses_value' => null,
+ ];
+
+ /**
+ * Fill Properties.
+ *
+ * @var array of mixed
+ */
+ private $fillProperties = [
+ 'type' => self::EXCEL_COLOR_TYPE_ARGB,
+ 'value' => null,
+ 'alpha' => 0,
+ ];
+
+ /**
+ * Line Properties.
+ *
+ * @var array of mixed
+ */
+ private $lineProperties = [
+ 'type' => self::EXCEL_COLOR_TYPE_ARGB,
+ 'value' => null,
+ 'alpha' => 0,
+ ];
+
+ /**
+ * Line Style Properties.
+ *
+ * @var array of mixed
+ */
+ private $lineStyleProperties = [
+ 'width' => '9525',
+ 'compound' => self::LINE_STYLE_COMPOUND_SIMPLE,
+ 'dash' => self::LINE_STYLE_DASH_SOLID,
+ 'cap' => self::LINE_STYLE_CAP_FLAT,
+ 'join' => self::LINE_STYLE_JOIN_BEVEL,
+ 'arrow' => [
+ 'head' => [
+ 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW,
+ 'size' => self::LINE_STYLE_ARROW_SIZE_5,
+ ],
+ 'end' => [
+ 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW,
+ 'size' => self::LINE_STYLE_ARROW_SIZE_8,
+ ],
+ ],
+ ];
+
+ /**
+ * Shadow Properties.
+ *
+ * @var array of mixed
+ */
+ private $shadowProperties = [
+ 'presets' => self::SHADOW_PRESETS_NOSHADOW,
+ 'effect' => null,
+ 'color' => [
+ 'type' => self::EXCEL_COLOR_TYPE_STANDARD,
+ 'value' => 'black',
+ 'alpha' => 40,
+ ],
+ 'size' => [
+ 'sx' => null,
+ 'sy' => null,
+ 'kx' => null,
+ ],
+ 'blur' => null,
+ 'direction' => null,
+ 'distance' => null,
+ 'algn' => null,
+ 'rotWithShape' => null,
+ ];
+
+ /**
+ * Glow Properties.
+ *
+ * @var array of mixed
+ */
+ private $glowProperties = [
+ 'size' => null,
+ 'color' => [
+ 'type' => self::EXCEL_COLOR_TYPE_STANDARD,
+ 'value' => 'black',
+ 'alpha' => 40,
+ ],
+ ];
+
+ /**
+ * Soft Edge Properties.
+ *
+ * @var array of mixed
+ */
+ private $softEdges = [
+ 'size' => null,
+ ];
+
+ /**
+ * Get Series Data Type.
+ *
+ * @param mixed $format_code
+ *
+ * @return string
+ */
+ public function setAxisNumberProperties($format_code)
+ {
+ $this->axisNumber['format'] = (string) $format_code;
+ $this->axisNumber['source_linked'] = 0;
+ }
+
+ /**
+ * Get Axis Number Format Data Type.
+ *
+ * @return string
+ */
+ public function getAxisNumberFormat()
+ {
+ return $this->axisNumber['format'];
+ }
+
+ /**
+ * Get Axis Number Source Linked.
+ *
+ * @return string
+ */
+ public function getAxisNumberSourceLinked()
+ {
+ return (string) $this->axisNumber['source_linked'];
+ }
+
+ /**
+ * Set Axis Options Properties.
+ *
+ * @param string $axis_labels
+ * @param string $horizontal_crosses_value
+ * @param string $horizontal_crosses
+ * @param string $axis_orientation
+ * @param string $major_tmt
+ * @param string $minor_tmt
+ * @param string $minimum
+ * @param string $maximum
+ * @param string $major_unit
+ * @param string $minor_unit
+ */
+ public function setAxisOptionsProperties($axis_labels, $horizontal_crosses_value = null, $horizontal_crosses = null, $axis_orientation = null, $major_tmt = null, $minor_tmt = null, $minimum = null, $maximum = null, $major_unit = null, $minor_unit = null): void
+ {
+ $this->axisOptions['axis_labels'] = (string) $axis_labels;
+ ($horizontal_crosses_value !== null) ? $this->axisOptions['horizontal_crosses_value'] = (string) $horizontal_crosses_value : null;
+ ($horizontal_crosses !== null) ? $this->axisOptions['horizontal_crosses'] = (string) $horizontal_crosses : null;
+ ($axis_orientation !== null) ? $this->axisOptions['orientation'] = (string) $axis_orientation : null;
+ ($major_tmt !== null) ? $this->axisOptions['major_tick_mark'] = (string) $major_tmt : null;
+ ($minor_tmt !== null) ? $this->axisOptions['minor_tick_mark'] = (string) $minor_tmt : null;
+ ($minor_tmt !== null) ? $this->axisOptions['minor_tick_mark'] = (string) $minor_tmt : null;
+ ($minimum !== null) ? $this->axisOptions['minimum'] = (string) $minimum : null;
+ ($maximum !== null) ? $this->axisOptions['maximum'] = (string) $maximum : null;
+ ($major_unit !== null) ? $this->axisOptions['major_unit'] = (string) $major_unit : null;
+ ($minor_unit !== null) ? $this->axisOptions['minor_unit'] = (string) $minor_unit : null;
+ }
+
+ /**
+ * Get Axis Options Property.
+ *
+ * @param string $property
+ *
+ * @return string
+ */
+ public function getAxisOptionsProperty($property)
+ {
+ return $this->axisOptions[$property];
+ }
+
+ /**
+ * Set Axis Orientation Property.
+ *
+ * @param string $orientation
+ */
+ public function setAxisOrientation($orientation): void
+ {
+ $this->axisOptions['orientation'] = (string) $orientation;
+ }
+
+ /**
+ * Set Fill Property.
+ *
+ * @param string $color
+ * @param int $alpha
+ * @param string $type
+ */
+ public function setFillParameters($color, $alpha = 0, $type = self::EXCEL_COLOR_TYPE_ARGB): void
+ {
+ $this->fillProperties = $this->setColorProperties($color, $alpha, $type);
+ }
+
+ /**
+ * Set Line Property.
+ *
+ * @param string $color
+ * @param int $alpha
+ * @param string $type
+ */
+ public function setLineParameters($color, $alpha = 0, $type = self::EXCEL_COLOR_TYPE_ARGB): void
+ {
+ $this->lineProperties = $this->setColorProperties($color, $alpha, $type);
+ }
+
+ /**
+ * Get Fill Property.
+ *
+ * @param string $property
+ *
+ * @return string
+ */
+ public function getFillProperty($property)
+ {
+ return $this->fillProperties[$property];
+ }
+
+ /**
+ * Get Line Property.
+ *
+ * @param string $property
+ *
+ * @return string
+ */
+ public function getLineProperty($property)
+ {
+ return $this->lineProperties[$property];
+ }
+
+ /**
+ * Set Line Style Properties.
+ *
+ * @param float $line_width
+ * @param string $compound_type
+ * @param string $dash_type
+ * @param string $cap_type
+ * @param string $join_type
+ * @param string $head_arrow_type
+ * @param string $head_arrow_size
+ * @param string $end_arrow_type
+ * @param string $end_arrow_size
+ */
+ public function setLineStyleProperties($line_width = null, $compound_type = null, $dash_type = null, $cap_type = null, $join_type = null, $head_arrow_type = null, $head_arrow_size = null, $end_arrow_type = null, $end_arrow_size = null): void
+ {
+ ($line_width !== null) ? $this->lineStyleProperties['width'] = $this->getExcelPointsWidth((float) $line_width) : null;
+ ($compound_type !== null) ? $this->lineStyleProperties['compound'] = (string) $compound_type : null;
+ ($dash_type !== null) ? $this->lineStyleProperties['dash'] = (string) $dash_type : null;
+ ($cap_type !== null) ? $this->lineStyleProperties['cap'] = (string) $cap_type : null;
+ ($join_type !== null) ? $this->lineStyleProperties['join'] = (string) $join_type : null;
+ ($head_arrow_type !== null) ? $this->lineStyleProperties['arrow']['head']['type'] = (string) $head_arrow_type : null;
+ ($head_arrow_size !== null) ? $this->lineStyleProperties['arrow']['head']['size'] = (string) $head_arrow_size : null;
+ ($end_arrow_type !== null) ? $this->lineStyleProperties['arrow']['end']['type'] = (string) $end_arrow_type : null;
+ ($end_arrow_size !== null) ? $this->lineStyleProperties['arrow']['end']['size'] = (string) $end_arrow_size : null;
+ }
+
+ /**
+ * Get Line Style Property.
+ *
+ * @param array|string $elements
+ *
+ * @return string
+ */
+ public function getLineStyleProperty($elements)
+ {
+ return $this->getArrayElementsValue($this->lineStyleProperties, $elements);
+ }
+
+ /**
+ * Get Line Style Arrow Excel Width.
+ *
+ * @param string $arrow
+ *
+ * @return string
+ */
+ public function getLineStyleArrowWidth($arrow)
+ {
+ return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrow]['size'], 'w');
+ }
+
+ /**
+ * Get Line Style Arrow Excel Length.
+ *
+ * @param string $arrow
+ *
+ * @return string
+ */
+ public function getLineStyleArrowLength($arrow)
+ {
+ return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrow]['size'], 'len');
+ }
+
+ /**
+ * Set Shadow Properties.
+ *
+ * @param int $sh_presets
+ * @param string $sh_color_value
+ * @param string $sh_color_type
+ * @param string $sh_color_alpha
+ * @param float $sh_blur
+ * @param int $sh_angle
+ * @param float $sh_distance
+ */
+ public function setShadowProperties($sh_presets, $sh_color_value = null, $sh_color_type = null, $sh_color_alpha = null, $sh_blur = null, $sh_angle = null, $sh_distance = null): void
+ {
+ $this->setShadowPresetsProperties((int) $sh_presets)
+ ->setShadowColor(
+ $sh_color_value === null ? $this->shadowProperties['color']['value'] : $sh_color_value,
+ $sh_color_alpha === null ? (int) $this->shadowProperties['color']['alpha'] : $sh_color_alpha,
+ $sh_color_type === null ? $this->shadowProperties['color']['type'] : $sh_color_type
+ )
+ ->setShadowBlur($sh_blur)
+ ->setShadowAngle($sh_angle)
+ ->setShadowDistance($sh_distance);
+ }
+
+ /**
+ * Set Shadow Color.
+ *
+ * @param int $shadow_presets
+ *
+ * @return $this
+ */
+ private function setShadowPresetsProperties($shadow_presets)
+ {
+ $this->shadowProperties['presets'] = $shadow_presets;
+ $this->setShadowProperiesMapValues($this->getShadowPresetsMap($shadow_presets));
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Properties from Mapped Values.
+ *
+ * @param mixed &$reference
+ *
+ * @return $this
+ */
+ private function setShadowProperiesMapValues(array $properties_map, &$reference = null)
+ {
+ $base_reference = $reference;
+ foreach ($properties_map as $property_key => $property_val) {
+ if (is_array($property_val)) {
+ if ($reference === null) {
+ $reference = &$this->shadowProperties[$property_key];
+ } else {
+ $reference = &$reference[$property_key];
+ }
+ $this->setShadowProperiesMapValues($property_val, $reference);
+ } else {
+ if ($base_reference === null) {
+ $this->shadowProperties[$property_key] = $property_val;
+ } else {
+ $reference[$property_key] = $property_val;
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Color.
+ *
+ * @param string $color
+ * @param int $alpha
+ * @param string $type
+ *
+ * @return $this
+ */
+ private function setShadowColor($color, $alpha, $type)
+ {
+ $this->shadowProperties['color'] = $this->setColorProperties($color, $alpha, $type);
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Blur.
+ *
+ * @param float $blur
+ *
+ * @return $this
+ */
+ private function setShadowBlur($blur)
+ {
+ if ($blur !== null) {
+ $this->shadowProperties['blur'] = (string) $this->getExcelPointsWidth($blur);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Angle.
+ *
+ * @param int $angle
+ *
+ * @return $this
+ */
+ private function setShadowAngle($angle)
+ {
+ if ($angle !== null) {
+ $this->shadowProperties['direction'] = (string) $this->getExcelPointsAngle($angle);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Distance.
+ *
+ * @param float $distance
+ *
+ * @return $this
+ */
+ private function setShadowDistance($distance)
+ {
+ if ($distance !== null) {
+ $this->shadowProperties['distance'] = (string) $this->getExcelPointsWidth($distance);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Shadow Property.
+ *
+ * @param string|string[] $elements
+ *
+ * @return null|array|int|string
+ */
+ public function getShadowProperty($elements)
+ {
+ return $this->getArrayElementsValue($this->shadowProperties, $elements);
+ }
+
+ /**
+ * Set Glow Properties.
+ *
+ * @param float $size
+ * @param string $color_value
+ * @param int $color_alpha
+ * @param string $color_type
+ */
+ public function setGlowProperties($size, $color_value = null, $color_alpha = null, $color_type = null): void
+ {
+ $this->setGlowSize($size)
+ ->setGlowColor(
+ $color_value === null ? $this->glowProperties['color']['value'] : $color_value,
+ $color_alpha === null ? (int) $this->glowProperties['color']['alpha'] : $color_alpha,
+ $color_type === null ? $this->glowProperties['color']['type'] : $color_type
+ );
+ }
+
+ /**
+ * Get Glow Property.
+ *
+ * @param array|string $property
+ *
+ * @return string
+ */
+ public function getGlowProperty($property)
+ {
+ return $this->getArrayElementsValue($this->glowProperties, $property);
+ }
+
+ /**
+ * Set Glow Color.
+ *
+ * @param float $size
+ *
+ * @return $this
+ */
+ private function setGlowSize($size)
+ {
+ if ($size !== null) {
+ $this->glowProperties['size'] = $this->getExcelPointsWidth($size);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Glow Color.
+ *
+ * @param string $color
+ * @param int $alpha
+ * @param string $type
+ *
+ * @return $this
+ */
+ private function setGlowColor($color, $alpha, $type)
+ {
+ $this->glowProperties['color'] = $this->setColorProperties($color, $alpha, $type);
+
+ return $this;
+ }
+
+ /**
+ * Set Soft Edges Size.
+ *
+ * @param float $size
+ */
+ public function setSoftEdges($size): void
+ {
+ if ($size !== null) {
+ $softEdges['size'] = (string) $this->getExcelPointsWidth($size);
+ }
+ }
+
+ /**
+ * Get Soft Edges Size.
+ *
+ * @return string
+ */
+ public function getSoftEdgesSize()
+ {
+ return $this->softEdges['size'];
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php
new file mode 100755
index 0000000..20eb2ae
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php
@@ -0,0 +1,663 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+use PhpOffice\PhpSpreadsheet\Settings;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class Chart
+{
+ /**
+ * Chart Name.
+ *
+ * @var string
+ */
+ private $name = '';
+
+ /**
+ * Worksheet.
+ *
+ * @var Worksheet
+ */
+ private $worksheet;
+
+ /**
+ * Chart Title.
+ *
+ * @var Title
+ */
+ private $title;
+
+ /**
+ * Chart Legend.
+ *
+ * @var Legend
+ */
+ private $legend;
+
+ /**
+ * X-Axis Label.
+ *
+ * @var Title
+ */
+ private $xAxisLabel;
+
+ /**
+ * Y-Axis Label.
+ *
+ * @var Title
+ */
+ private $yAxisLabel;
+
+ /**
+ * Chart Plot Area.
+ *
+ * @var PlotArea
+ */
+ private $plotArea;
+
+ /**
+ * Plot Visible Only.
+ *
+ * @var bool
+ */
+ private $plotVisibleOnly = true;
+
+ /**
+ * Display Blanks as.
+ *
+ * @var string
+ */
+ private $displayBlanksAs = DataSeries::EMPTY_AS_GAP;
+
+ /**
+ * Chart Asix Y as.
+ *
+ * @var Axis
+ */
+ private $yAxis;
+
+ /**
+ * Chart Asix X as.
+ *
+ * @var Axis
+ */
+ private $xAxis;
+
+ /**
+ * Chart Major Gridlines as.
+ *
+ * @var GridLines
+ */
+ private $majorGridlines;
+
+ /**
+ * Chart Minor Gridlines as.
+ *
+ * @var GridLines
+ */
+ private $minorGridlines;
+
+ /**
+ * Top-Left Cell Position.
+ *
+ * @var string
+ */
+ private $topLeftCellRef = 'A1';
+
+ /**
+ * Top-Left X-Offset.
+ *
+ * @var int
+ */
+ private $topLeftXOffset = 0;
+
+ /**
+ * Top-Left Y-Offset.
+ *
+ * @var int
+ */
+ private $topLeftYOffset = 0;
+
+ /**
+ * Bottom-Right Cell Position.
+ *
+ * @var string
+ */
+ private $bottomRightCellRef = 'A1';
+
+ /**
+ * Bottom-Right X-Offset.
+ *
+ * @var int
+ */
+ private $bottomRightXOffset = 10;
+
+ /**
+ * Bottom-Right Y-Offset.
+ *
+ * @var int
+ */
+ private $bottomRightYOffset = 10;
+
+ /**
+ * Create a new Chart.
+ *
+ * @param mixed $name
+ * @param mixed $plotVisibleOnly
+ * @param string $displayBlanksAs
+ */
+ public function __construct($name, ?Title $title = null, ?Legend $legend = null, ?PlotArea $plotArea = null, $plotVisibleOnly = true, $displayBlanksAs = DataSeries::EMPTY_AS_GAP, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null, ?GridLines $majorGridlines = null, ?GridLines $minorGridlines = null)
+ {
+ $this->name = $name;
+ $this->title = $title;
+ $this->legend = $legend;
+ $this->xAxisLabel = $xAxisLabel;
+ $this->yAxisLabel = $yAxisLabel;
+ $this->plotArea = $plotArea;
+ $this->plotVisibleOnly = $plotVisibleOnly;
+ $this->displayBlanksAs = $displayBlanksAs;
+ $this->xAxis = $xAxis;
+ $this->yAxis = $yAxis;
+ $this->majorGridlines = $majorGridlines;
+ $this->minorGridlines = $minorGridlines;
+ }
+
+ /**
+ * Get Name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get Worksheet.
+ *
+ * @return Worksheet
+ */
+ public function getWorksheet()
+ {
+ return $this->worksheet;
+ }
+
+ /**
+ * Set Worksheet.
+ *
+ * @param Worksheet $pValue
+ *
+ * @return $this
+ */
+ public function setWorksheet(?Worksheet $pValue = null)
+ {
+ $this->worksheet = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Title.
+ *
+ * @return Title
+ */
+ public function getTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * Set Title.
+ *
+ * @return $this
+ */
+ public function setTitle(Title $title)
+ {
+ $this->title = $title;
+
+ return $this;
+ }
+
+ /**
+ * Get Legend.
+ *
+ * @return Legend
+ */
+ public function getLegend()
+ {
+ return $this->legend;
+ }
+
+ /**
+ * Set Legend.
+ *
+ * @return $this
+ */
+ public function setLegend(Legend $legend)
+ {
+ $this->legend = $legend;
+
+ return $this;
+ }
+
+ /**
+ * Get X-Axis Label.
+ *
+ * @return Title
+ */
+ public function getXAxisLabel()
+ {
+ return $this->xAxisLabel;
+ }
+
+ /**
+ * Set X-Axis Label.
+ *
+ * @return $this
+ */
+ public function setXAxisLabel(Title $label)
+ {
+ $this->xAxisLabel = $label;
+
+ return $this;
+ }
+
+ /**
+ * Get Y-Axis Label.
+ *
+ * @return Title
+ */
+ public function getYAxisLabel()
+ {
+ return $this->yAxisLabel;
+ }
+
+ /**
+ * Set Y-Axis Label.
+ *
+ * @return $this
+ */
+ public function setYAxisLabel(Title $label)
+ {
+ $this->yAxisLabel = $label;
+
+ return $this;
+ }
+
+ /**
+ * Get Plot Area.
+ *
+ * @return PlotArea
+ */
+ public function getPlotArea()
+ {
+ return $this->plotArea;
+ }
+
+ /**
+ * Get Plot Visible Only.
+ *
+ * @return bool
+ */
+ public function getPlotVisibleOnly()
+ {
+ return $this->plotVisibleOnly;
+ }
+
+ /**
+ * Set Plot Visible Only.
+ *
+ * @param bool $plotVisibleOnly
+ *
+ * @return $this
+ */
+ public function setPlotVisibleOnly($plotVisibleOnly)
+ {
+ $this->plotVisibleOnly = $plotVisibleOnly;
+
+ return $this;
+ }
+
+ /**
+ * Get Display Blanks as.
+ *
+ * @return string
+ */
+ public function getDisplayBlanksAs()
+ {
+ return $this->displayBlanksAs;
+ }
+
+ /**
+ * Set Display Blanks as.
+ *
+ * @param string $displayBlanksAs
+ *
+ * @return $this
+ */
+ public function setDisplayBlanksAs($displayBlanksAs)
+ {
+ $this->displayBlanksAs = $displayBlanksAs;
+
+ return $this;
+ }
+
+ /**
+ * Get yAxis.
+ *
+ * @return Axis
+ */
+ public function getChartAxisY()
+ {
+ if ($this->yAxis !== null) {
+ return $this->yAxis;
+ }
+
+ return new Axis();
+ }
+
+ /**
+ * Get xAxis.
+ *
+ * @return Axis
+ */
+ public function getChartAxisX()
+ {
+ if ($this->xAxis !== null) {
+ return $this->xAxis;
+ }
+
+ return new Axis();
+ }
+
+ /**
+ * Get Major Gridlines.
+ *
+ * @return GridLines
+ */
+ public function getMajorGridlines()
+ {
+ if ($this->majorGridlines !== null) {
+ return $this->majorGridlines;
+ }
+
+ return new GridLines();
+ }
+
+ /**
+ * Get Minor Gridlines.
+ *
+ * @return GridLines
+ */
+ public function getMinorGridlines()
+ {
+ if ($this->minorGridlines !== null) {
+ return $this->minorGridlines;
+ }
+
+ return new GridLines();
+ }
+
+ /**
+ * Set the Top Left position for the chart.
+ *
+ * @param string $cell
+ * @param int $xOffset
+ * @param int $yOffset
+ *
+ * @return $this
+ */
+ public function setTopLeftPosition($cell, $xOffset = null, $yOffset = null)
+ {
+ $this->topLeftCellRef = $cell;
+ if ($xOffset !== null) {
+ $this->setTopLeftXOffset($xOffset);
+ }
+ if ($yOffset !== null) {
+ $this->setTopLeftYOffset($yOffset);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the top left position of the chart.
+ *
+ * @return array an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell
+ */
+ public function getTopLeftPosition()
+ {
+ return [
+ 'cell' => $this->topLeftCellRef,
+ 'xOffset' => $this->topLeftXOffset,
+ 'yOffset' => $this->topLeftYOffset,
+ ];
+ }
+
+ /**
+ * Get the cell address where the top left of the chart is fixed.
+ *
+ * @return string
+ */
+ public function getTopLeftCell()
+ {
+ return $this->topLeftCellRef;
+ }
+
+ /**
+ * Set the Top Left cell position for the chart.
+ *
+ * @param string $cell
+ *
+ * @return $this
+ */
+ public function setTopLeftCell($cell)
+ {
+ $this->topLeftCellRef = $cell;
+
+ return $this;
+ }
+
+ /**
+ * Set the offset position within the Top Left cell for the chart.
+ *
+ * @param int $xOffset
+ * @param int $yOffset
+ *
+ * @return $this
+ */
+ public function setTopLeftOffset($xOffset, $yOffset)
+ {
+ if ($xOffset !== null) {
+ $this->setTopLeftXOffset($xOffset);
+ }
+
+ if ($yOffset !== null) {
+ $this->setTopLeftYOffset($yOffset);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the offset position within the Top Left cell for the chart.
+ *
+ * @return int[]
+ */
+ public function getTopLeftOffset()
+ {
+ return [
+ 'X' => $this->topLeftXOffset,
+ 'Y' => $this->topLeftYOffset,
+ ];
+ }
+
+ public function setTopLeftXOffset($xOffset)
+ {
+ $this->topLeftXOffset = $xOffset;
+
+ return $this;
+ }
+
+ public function getTopLeftXOffset()
+ {
+ return $this->topLeftXOffset;
+ }
+
+ public function setTopLeftYOffset($yOffset)
+ {
+ $this->topLeftYOffset = $yOffset;
+
+ return $this;
+ }
+
+ public function getTopLeftYOffset()
+ {
+ return $this->topLeftYOffset;
+ }
+
+ /**
+ * Set the Bottom Right position of the chart.
+ *
+ * @param string $cell
+ * @param int $xOffset
+ * @param int $yOffset
+ *
+ * @return $this
+ */
+ public function setBottomRightPosition($cell, $xOffset = null, $yOffset = null)
+ {
+ $this->bottomRightCellRef = $cell;
+ if ($xOffset !== null) {
+ $this->setBottomRightXOffset($xOffset);
+ }
+ if ($yOffset !== null) {
+ $this->setBottomRightYOffset($yOffset);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the bottom right position of the chart.
+ *
+ * @return array an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell
+ */
+ public function getBottomRightPosition()
+ {
+ return [
+ 'cell' => $this->bottomRightCellRef,
+ 'xOffset' => $this->bottomRightXOffset,
+ 'yOffset' => $this->bottomRightYOffset,
+ ];
+ }
+
+ public function setBottomRightCell($cell)
+ {
+ $this->bottomRightCellRef = $cell;
+
+ return $this;
+ }
+
+ /**
+ * Get the cell address where the bottom right of the chart is fixed.
+ *
+ * @return string
+ */
+ public function getBottomRightCell()
+ {
+ return $this->bottomRightCellRef;
+ }
+
+ /**
+ * Set the offset position within the Bottom Right cell for the chart.
+ *
+ * @param int $xOffset
+ * @param int $yOffset
+ *
+ * @return $this
+ */
+ public function setBottomRightOffset($xOffset, $yOffset)
+ {
+ if ($xOffset !== null) {
+ $this->setBottomRightXOffset($xOffset);
+ }
+
+ if ($yOffset !== null) {
+ $this->setBottomRightYOffset($yOffset);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the offset position within the Bottom Right cell for the chart.
+ *
+ * @return int[]
+ */
+ public function getBottomRightOffset()
+ {
+ return [
+ 'X' => $this->bottomRightXOffset,
+ 'Y' => $this->bottomRightYOffset,
+ ];
+ }
+
+ public function setBottomRightXOffset($xOffset)
+ {
+ $this->bottomRightXOffset = $xOffset;
+
+ return $this;
+ }
+
+ public function getBottomRightXOffset()
+ {
+ return $this->bottomRightXOffset;
+ }
+
+ public function setBottomRightYOffset($yOffset)
+ {
+ $this->bottomRightYOffset = $yOffset;
+
+ return $this;
+ }
+
+ public function getBottomRightYOffset()
+ {
+ return $this->bottomRightYOffset;
+ }
+
+ public function refresh(): void
+ {
+ if ($this->worksheet !== null) {
+ $this->plotArea->refresh($this->worksheet);
+ }
+ }
+
+ /**
+ * Render the chart to given file (or stream).
+ *
+ * @param string $outputDestination Name of the file render to
+ *
+ * @return bool true on success
+ */
+ public function render($outputDestination = null)
+ {
+ if ($outputDestination == 'php://output') {
+ $outputDestination = null;
+ }
+
+ $libraryName = Settings::getChartRenderer();
+ if ($libraryName === null) {
+ return false;
+ }
+
+ // Ensure that data series values are up-to-date before we render
+ $this->refresh();
+
+ $renderer = new $libraryName($this);
+
+ return $renderer->render($outputDestination);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php
new file mode 100755
index 0000000..3a44b33
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php
@@ -0,0 +1,394 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class DataSeries
+{
+ const TYPE_BARCHART = 'barChart';
+ const TYPE_BARCHART_3D = 'bar3DChart';
+ const TYPE_LINECHART = 'lineChart';
+ const TYPE_LINECHART_3D = 'line3DChart';
+ const TYPE_AREACHART = 'areaChart';
+ const TYPE_AREACHART_3D = 'area3DChart';
+ const TYPE_PIECHART = 'pieChart';
+ const TYPE_PIECHART_3D = 'pie3DChart';
+ const TYPE_DOUGHNUTCHART = 'doughnutChart';
+ const TYPE_DONUTCHART = self::TYPE_DOUGHNUTCHART; // Synonym
+ const TYPE_SCATTERCHART = 'scatterChart';
+ const TYPE_SURFACECHART = 'surfaceChart';
+ const TYPE_SURFACECHART_3D = 'surface3DChart';
+ const TYPE_RADARCHART = 'radarChart';
+ const TYPE_BUBBLECHART = 'bubbleChart';
+ const TYPE_STOCKCHART = 'stockChart';
+ const TYPE_CANDLECHART = self::TYPE_STOCKCHART; // Synonym
+
+ const GROUPING_CLUSTERED = 'clustered';
+ const GROUPING_STACKED = 'stacked';
+ const GROUPING_PERCENT_STACKED = 'percentStacked';
+ const GROUPING_STANDARD = 'standard';
+
+ const DIRECTION_BAR = 'bar';
+ const DIRECTION_HORIZONTAL = self::DIRECTION_BAR;
+ const DIRECTION_COL = 'col';
+ const DIRECTION_COLUMN = self::DIRECTION_COL;
+ const DIRECTION_VERTICAL = self::DIRECTION_COL;
+
+ const STYLE_LINEMARKER = 'lineMarker';
+ const STYLE_SMOOTHMARKER = 'smoothMarker';
+ const STYLE_MARKER = 'marker';
+ const STYLE_FILLED = 'filled';
+
+ const EMPTY_AS_GAP = 'gap';
+ const EMPTY_AS_ZERO = 'zero';
+ const EMPTY_AS_SPAN = 'span';
+
+ /**
+ * Series Plot Type.
+ *
+ * @var string
+ */
+ private $plotType;
+
+ /**
+ * Plot Grouping Type.
+ *
+ * @var string
+ */
+ private $plotGrouping;
+
+ /**
+ * Plot Direction.
+ *
+ * @var string
+ */
+ private $plotDirection;
+
+ /**
+ * Plot Style.
+ *
+ * @var null|string
+ */
+ private $plotStyle;
+
+ /**
+ * Order of plots in Series.
+ *
+ * @var array of integer
+ */
+ private $plotOrder = [];
+
+ /**
+ * Plot Label.
+ *
+ * @var array of DataSeriesValues
+ */
+ private $plotLabel = [];
+
+ /**
+ * Plot Category.
+ *
+ * @var array of DataSeriesValues
+ */
+ private $plotCategory = [];
+
+ /**
+ * Smooth Line.
+ *
+ * @var bool
+ */
+ private $smoothLine;
+
+ /**
+ * Plot Values.
+ *
+ * @var array of DataSeriesValues
+ */
+ private $plotValues = [];
+
+ /**
+ * Create a new DataSeries.
+ *
+ * @param null|mixed $plotType
+ * @param null|mixed $plotGrouping
+ * @param int[] $plotOrder
+ * @param DataSeriesValues[] $plotLabel
+ * @param DataSeriesValues[] $plotCategory
+ * @param DataSeriesValues[] $plotValues
+ * @param null|string $plotDirection
+ * @param bool $smoothLine
+ * @param null|string $plotStyle
+ */
+ public function __construct($plotType = null, $plotGrouping = null, array $plotOrder = [], array $plotLabel = [], array $plotCategory = [], array $plotValues = [], $plotDirection = null, $smoothLine = false, $plotStyle = null)
+ {
+ $this->plotType = $plotType;
+ $this->plotGrouping = $plotGrouping;
+ $this->plotOrder = $plotOrder;
+ $keys = array_keys($plotValues);
+ $this->plotValues = $plotValues;
+ if ((count($plotLabel) == 0) || ($plotLabel[$keys[0]] === null)) {
+ $plotLabel[$keys[0]] = new DataSeriesValues();
+ }
+ $this->plotLabel = $plotLabel;
+
+ if ((count($plotCategory) == 0) || ($plotCategory[$keys[0]] === null)) {
+ $plotCategory[$keys[0]] = new DataSeriesValues();
+ }
+ $this->plotCategory = $plotCategory;
+
+ $this->smoothLine = $smoothLine;
+ $this->plotStyle = $plotStyle;
+
+ if ($plotDirection === null) {
+ $plotDirection = self::DIRECTION_COL;
+ }
+ $this->plotDirection = $plotDirection;
+ }
+
+ /**
+ * Get Plot Type.
+ *
+ * @return string
+ */
+ public function getPlotType()
+ {
+ return $this->plotType;
+ }
+
+ /**
+ * Set Plot Type.
+ *
+ * @param string $plotType
+ *
+ * @return $this
+ */
+ public function setPlotType($plotType)
+ {
+ $this->plotType = $plotType;
+
+ return $this;
+ }
+
+ /**
+ * Get Plot Grouping Type.
+ *
+ * @return string
+ */
+ public function getPlotGrouping()
+ {
+ return $this->plotGrouping;
+ }
+
+ /**
+ * Set Plot Grouping Type.
+ *
+ * @param string $groupingType
+ *
+ * @return $this
+ */
+ public function setPlotGrouping($groupingType)
+ {
+ $this->plotGrouping = $groupingType;
+
+ return $this;
+ }
+
+ /**
+ * Get Plot Direction.
+ *
+ * @return string
+ */
+ public function getPlotDirection()
+ {
+ return $this->plotDirection;
+ }
+
+ /**
+ * Set Plot Direction.
+ *
+ * @param string $plotDirection
+ *
+ * @return $this
+ */
+ public function setPlotDirection($plotDirection)
+ {
+ $this->plotDirection = $plotDirection;
+
+ return $this;
+ }
+
+ /**
+ * Get Plot Order.
+ *
+ * @return int[]
+ */
+ public function getPlotOrder()
+ {
+ return $this->plotOrder;
+ }
+
+ /**
+ * Get Plot Labels.
+ *
+ * @return array of DataSeriesValues
+ */
+ public function getPlotLabels()
+ {
+ return $this->plotLabel;
+ }
+
+ /**
+ * Get Plot Label by Index.
+ *
+ * @param mixed $index
+ *
+ * @return DataSeriesValues
+ */
+ public function getPlotLabelByIndex($index)
+ {
+ $keys = array_keys($this->plotLabel);
+ if (in_array($index, $keys)) {
+ return $this->plotLabel[$index];
+ } elseif (isset($keys[$index])) {
+ return $this->plotLabel[$keys[$index]];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get Plot Categories.
+ *
+ * @return array of DataSeriesValues
+ */
+ public function getPlotCategories()
+ {
+ return $this->plotCategory;
+ }
+
+ /**
+ * Get Plot Category by Index.
+ *
+ * @param mixed $index
+ *
+ * @return DataSeriesValues
+ */
+ public function getPlotCategoryByIndex($index)
+ {
+ $keys = array_keys($this->plotCategory);
+ if (in_array($index, $keys)) {
+ return $this->plotCategory[$index];
+ } elseif (isset($keys[$index])) {
+ return $this->plotCategory[$keys[$index]];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get Plot Style.
+ *
+ * @return null|string
+ */
+ public function getPlotStyle()
+ {
+ return $this->plotStyle;
+ }
+
+ /**
+ * Set Plot Style.
+ *
+ * @param null|string $plotStyle
+ *
+ * @return $this
+ */
+ public function setPlotStyle($plotStyle)
+ {
+ $this->plotStyle = $plotStyle;
+
+ return $this;
+ }
+
+ /**
+ * Get Plot Values.
+ *
+ * @return array of DataSeriesValues
+ */
+ public function getPlotValues()
+ {
+ return $this->plotValues;
+ }
+
+ /**
+ * Get Plot Values by Index.
+ *
+ * @param mixed $index
+ *
+ * @return DataSeriesValues
+ */
+ public function getPlotValuesByIndex($index)
+ {
+ $keys = array_keys($this->plotValues);
+ if (in_array($index, $keys)) {
+ return $this->plotValues[$index];
+ } elseif (isset($keys[$index])) {
+ return $this->plotValues[$keys[$index]];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get Number of Plot Series.
+ *
+ * @return int
+ */
+ public function getPlotSeriesCount()
+ {
+ return count($this->plotValues);
+ }
+
+ /**
+ * Get Smooth Line.
+ *
+ * @return bool
+ */
+ public function getSmoothLine()
+ {
+ return $this->smoothLine;
+ }
+
+ /**
+ * Set Smooth Line.
+ *
+ * @param bool $smoothLine
+ *
+ * @return $this
+ */
+ public function setSmoothLine($smoothLine)
+ {
+ $this->smoothLine = $smoothLine;
+
+ return $this;
+ }
+
+ public function refresh(Worksheet $worksheet): void
+ {
+ foreach ($this->plotValues as $plotValues) {
+ if ($plotValues !== null) {
+ $plotValues->refresh($worksheet, true);
+ }
+ }
+ foreach ($this->plotLabel as $plotValues) {
+ if ($plotValues !== null) {
+ $plotValues->refresh($worksheet, true);
+ }
+ }
+ foreach ($this->plotCategory as $plotValues) {
+ if ($plotValues !== null) {
+ $plotValues->refresh($worksheet, false);
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php
new file mode 100755
index 0000000..c1bd973
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php
@@ -0,0 +1,397 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class DataSeriesValues
+{
+ const DATASERIES_TYPE_STRING = 'String';
+ const DATASERIES_TYPE_NUMBER = 'Number';
+
+ private static $dataTypeValues = [
+ self::DATASERIES_TYPE_STRING,
+ self::DATASERIES_TYPE_NUMBER,
+ ];
+
+ /**
+ * Series Data Type.
+ *
+ * @var string
+ */
+ private $dataType;
+
+ /**
+ * Series Data Source.
+ *
+ * @var string
+ */
+ private $dataSource;
+
+ /**
+ * Format Code.
+ *
+ * @var string
+ */
+ private $formatCode;
+
+ /**
+ * Series Point Marker.
+ *
+ * @var string
+ */
+ private $pointMarker;
+
+ /**
+ * Point Count (The number of datapoints in the dataseries).
+ *
+ * @var int
+ */
+ private $pointCount = 0;
+
+ /**
+ * Data Values.
+ *
+ * @var array of mixed
+ */
+ private $dataValues = [];
+
+ /**
+ * Fill color (can be array with colors if dataseries have custom colors).
+ *
+ * @var string|string[]
+ */
+ private $fillColor;
+
+ /**
+ * Line Width.
+ *
+ * @var int
+ */
+ private $lineWidth = 12700;
+
+ /**
+ * Create a new DataSeriesValues object.
+ *
+ * @param string $dataType
+ * @param string $dataSource
+ * @param null|mixed $formatCode
+ * @param int $pointCount
+ * @param mixed $dataValues
+ * @param null|mixed $marker
+ * @param null|string|string[] $fillColor
+ */
+ public function __construct($dataType = self::DATASERIES_TYPE_NUMBER, $dataSource = null, $formatCode = null, $pointCount = 0, $dataValues = [], $marker = null, $fillColor = null)
+ {
+ $this->setDataType($dataType);
+ $this->dataSource = $dataSource;
+ $this->formatCode = $formatCode;
+ $this->pointCount = $pointCount;
+ $this->dataValues = $dataValues;
+ $this->pointMarker = $marker;
+ $this->fillColor = $fillColor;
+ }
+
+ /**
+ * Get Series Data Type.
+ *
+ * @return string
+ */
+ public function getDataType()
+ {
+ return $this->dataType;
+ }
+
+ /**
+ * Set Series Data Type.
+ *
+ * @param string $dataType Datatype of this data series
+ * Typical values are:
+ * DataSeriesValues::DATASERIES_TYPE_STRING
+ * Normally used for axis point values
+ * DataSeriesValues::DATASERIES_TYPE_NUMBER
+ * Normally used for chart data values
+ *
+ * @return $this
+ */
+ public function setDataType($dataType)
+ {
+ if (!in_array($dataType, self::$dataTypeValues)) {
+ throw new Exception('Invalid datatype for chart data series values');
+ }
+ $this->dataType = $dataType;
+
+ return $this;
+ }
+
+ /**
+ * Get Series Data Source (formula).
+ *
+ * @return string
+ */
+ public function getDataSource()
+ {
+ return $this->dataSource;
+ }
+
+ /**
+ * Set Series Data Source (formula).
+ *
+ * @param string $dataSource
+ *
+ * @return $this
+ */
+ public function setDataSource($dataSource)
+ {
+ $this->dataSource = $dataSource;
+
+ return $this;
+ }
+
+ /**
+ * Get Point Marker.
+ *
+ * @return string
+ */
+ public function getPointMarker()
+ {
+ return $this->pointMarker;
+ }
+
+ /**
+ * Set Point Marker.
+ *
+ * @param string $marker
+ *
+ * @return $this
+ */
+ public function setPointMarker($marker)
+ {
+ $this->pointMarker = $marker;
+
+ return $this;
+ }
+
+ /**
+ * Get Series Format Code.
+ *
+ * @return string
+ */
+ public function getFormatCode()
+ {
+ return $this->formatCode;
+ }
+
+ /**
+ * Set Series Format Code.
+ *
+ * @param string $formatCode
+ *
+ * @return $this
+ */
+ public function setFormatCode($formatCode)
+ {
+ $this->formatCode = $formatCode;
+
+ return $this;
+ }
+
+ /**
+ * Get Series Point Count.
+ *
+ * @return int
+ */
+ public function getPointCount()
+ {
+ return $this->pointCount;
+ }
+
+ /**
+ * Get fill color.
+ *
+ * @return string|string[] HEX color or array with HEX colors
+ */
+ public function getFillColor()
+ {
+ return $this->fillColor;
+ }
+
+ /**
+ * Set fill color for series.
+ *
+ * @param string|string[] $color HEX color or array with HEX colors
+ *
+ * @return DataSeriesValues
+ */
+ public function setFillColor($color)
+ {
+ if (is_array($color)) {
+ foreach ($color as $colorValue) {
+ $this->validateColor($colorValue);
+ }
+ } else {
+ $this->validateColor($color);
+ }
+ $this->fillColor = $color;
+
+ return $this;
+ }
+
+ /**
+ * Method for validating hex color.
+ *
+ * @param string $color value for color
+ *
+ * @return bool true if validation was successful
+ */
+ private function validateColor($color)
+ {
+ if (!preg_match('/^[a-f0-9]{6}$/i', $color)) {
+ throw new Exception(sprintf('Invalid hex color for chart series (color: "%s")', $color));
+ }
+
+ return true;
+ }
+
+ /**
+ * Get line width for series.
+ *
+ * @return int
+ */
+ public function getLineWidth()
+ {
+ return $this->lineWidth;
+ }
+
+ /**
+ * Set line width for the series.
+ *
+ * @param int $width
+ *
+ * @return $this
+ */
+ public function setLineWidth($width)
+ {
+ $minWidth = 12700;
+ $this->lineWidth = max($minWidth, $width);
+
+ return $this;
+ }
+
+ /**
+ * Identify if the Data Series is a multi-level or a simple series.
+ *
+ * @return null|bool
+ */
+ public function isMultiLevelSeries()
+ {
+ if (count($this->dataValues) > 0) {
+ return is_array(array_values($this->dataValues)[0]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the level count of a multi-level Data Series.
+ *
+ * @return int
+ */
+ public function multiLevelCount()
+ {
+ $levelCount = 0;
+ foreach ($this->dataValues as $dataValueSet) {
+ $levelCount = max($levelCount, count($dataValueSet));
+ }
+
+ return $levelCount;
+ }
+
+ /**
+ * Get Series Data Values.
+ *
+ * @return array of mixed
+ */
+ public function getDataValues()
+ {
+ return $this->dataValues;
+ }
+
+ /**
+ * Get the first Series Data value.
+ *
+ * @return mixed
+ */
+ public function getDataValue()
+ {
+ $count = count($this->dataValues);
+ if ($count == 0) {
+ return null;
+ } elseif ($count == 1) {
+ return $this->dataValues[0];
+ }
+
+ return $this->dataValues;
+ }
+
+ /**
+ * Set Series Data Values.
+ *
+ * @param array $dataValues
+ *
+ * @return $this
+ */
+ public function setDataValues($dataValues)
+ {
+ $this->dataValues = Functions::flattenArray($dataValues);
+ $this->pointCount = count($dataValues);
+
+ return $this;
+ }
+
+ public function refresh(Worksheet $worksheet, $flatten = true): void
+ {
+ if ($this->dataSource !== null) {
+ $calcEngine = Calculation::getInstance($worksheet->getParent());
+ $newDataValues = Calculation::unwrapResult(
+ $calcEngine->_calculateFormulaValue(
+ '=' . $this->dataSource,
+ null,
+ $worksheet->getCell('A1')
+ )
+ );
+ if ($flatten) {
+ $this->dataValues = Functions::flattenArray($newDataValues);
+ foreach ($this->dataValues as &$dataValue) {
+ if (is_string($dataValue) && !empty($dataValue) && $dataValue[0] == '#') {
+ $dataValue = 0.0;
+ }
+ }
+ unset($dataValue);
+ } else {
+ [$worksheet, $cellRange] = Worksheet::extractSheetTitle($this->dataSource, true);
+ $dimensions = Coordinate::rangeDimension(str_replace('$', '', $cellRange));
+ if (($dimensions[0] == 1) || ($dimensions[1] == 1)) {
+ $this->dataValues = Functions::flattenArray($newDataValues);
+ } else {
+ $newArray = array_values(array_shift($newDataValues));
+ foreach ($newArray as $i => $newDataSet) {
+ $newArray[$i] = [$newDataSet];
+ }
+
+ foreach ($newDataValues as $newDataSet) {
+ $i = 0;
+ foreach ($newDataSet as $newDataVal) {
+ array_unshift($newArray[$i++], $newDataVal);
+ }
+ }
+ $this->dataValues = $newArray;
+ }
+ }
+ $this->pointCount = count($this->dataValues);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php
new file mode 100755
index 0000000..3f95b59
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class Exception extends PhpSpreadsheetException
+{
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/GridLines.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/GridLines.php
new file mode 100755
index 0000000..2e424bc
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/GridLines.php
@@ -0,0 +1,454 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+/**
+ * Created by PhpStorm.
+ * User: Wiktor Trzonkowski
+ * Date: 7/2/14
+ * Time: 2:36 PM.
+ */
+class GridLines extends Properties
+{
+ /**
+ * Properties of Class:
+ * Object State (State for Minor Tick Mark) @var bool
+ * Line Properties @var array of mixed
+ * Shadow Properties @var array of mixed
+ * Glow Properties @var array of mixed
+ * Soft Properties @var array of mixed.
+ */
+ private $objectState = false;
+
+ private $lineProperties = [
+ 'color' => [
+ 'type' => self::EXCEL_COLOR_TYPE_STANDARD,
+ 'value' => null,
+ 'alpha' => 0,
+ ],
+ 'style' => [
+ 'width' => '9525',
+ 'compound' => self::LINE_STYLE_COMPOUND_SIMPLE,
+ 'dash' => self::LINE_STYLE_DASH_SOLID,
+ 'cap' => self::LINE_STYLE_CAP_FLAT,
+ 'join' => self::LINE_STYLE_JOIN_BEVEL,
+ 'arrow' => [
+ 'head' => [
+ 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW,
+ 'size' => self::LINE_STYLE_ARROW_SIZE_5,
+ ],
+ 'end' => [
+ 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW,
+ 'size' => self::LINE_STYLE_ARROW_SIZE_8,
+ ],
+ ],
+ ],
+ ];
+
+ private $shadowProperties = [
+ 'presets' => self::SHADOW_PRESETS_NOSHADOW,
+ 'effect' => null,
+ 'color' => [
+ 'type' => self::EXCEL_COLOR_TYPE_STANDARD,
+ 'value' => 'black',
+ 'alpha' => 85,
+ ],
+ 'size' => [
+ 'sx' => null,
+ 'sy' => null,
+ 'kx' => null,
+ ],
+ 'blur' => null,
+ 'direction' => null,
+ 'distance' => null,
+ 'algn' => null,
+ 'rotWithShape' => null,
+ ];
+
+ private $glowProperties = [
+ 'size' => null,
+ 'color' => [
+ 'type' => self::EXCEL_COLOR_TYPE_STANDARD,
+ 'value' => 'black',
+ 'alpha' => 40,
+ ],
+ ];
+
+ private $softEdges = [
+ 'size' => null,
+ ];
+
+ /**
+ * Get Object State.
+ *
+ * @return bool
+ */
+ public function getObjectState()
+ {
+ return $this->objectState;
+ }
+
+ /**
+ * Change Object State to True.
+ *
+ * @return $this
+ */
+ private function activateObject()
+ {
+ $this->objectState = true;
+
+ return $this;
+ }
+
+ /**
+ * Set Line Color Properties.
+ *
+ * @param string $value
+ * @param int $alpha
+ * @param string $type
+ */
+ public function setLineColorProperties($value, $alpha = 0, $type = self::EXCEL_COLOR_TYPE_STANDARD): void
+ {
+ $this->activateObject()
+ ->lineProperties['color'] = $this->setColorProperties(
+ $value,
+ $alpha,
+ $type
+ );
+ }
+
+ /**
+ * Set Line Color Properties.
+ *
+ * @param float $line_width
+ * @param string $compound_type
+ * @param string $dash_type
+ * @param string $cap_type
+ * @param string $join_type
+ * @param string $head_arrow_type
+ * @param string $head_arrow_size
+ * @param string $end_arrow_type
+ * @param string $end_arrow_size
+ */
+ public function setLineStyleProperties($line_width = null, $compound_type = null, $dash_type = null, $cap_type = null, $join_type = null, $head_arrow_type = null, $head_arrow_size = null, $end_arrow_type = null, $end_arrow_size = null): void
+ {
+ $this->activateObject();
+ ($line_width !== null)
+ ? $this->lineProperties['style']['width'] = $this->getExcelPointsWidth((float) $line_width)
+ : null;
+ ($compound_type !== null)
+ ? $this->lineProperties['style']['compound'] = (string) $compound_type
+ : null;
+ ($dash_type !== null)
+ ? $this->lineProperties['style']['dash'] = (string) $dash_type
+ : null;
+ ($cap_type !== null)
+ ? $this->lineProperties['style']['cap'] = (string) $cap_type
+ : null;
+ ($join_type !== null)
+ ? $this->lineProperties['style']['join'] = (string) $join_type
+ : null;
+ ($head_arrow_type !== null)
+ ? $this->lineProperties['style']['arrow']['head']['type'] = (string) $head_arrow_type
+ : null;
+ ($head_arrow_size !== null)
+ ? $this->lineProperties['style']['arrow']['head']['size'] = (string) $head_arrow_size
+ : null;
+ ($end_arrow_type !== null)
+ ? $this->lineProperties['style']['arrow']['end']['type'] = (string) $end_arrow_type
+ : null;
+ ($end_arrow_size !== null)
+ ? $this->lineProperties['style']['arrow']['end']['size'] = (string) $end_arrow_size
+ : null;
+ }
+
+ /**
+ * Get Line Color Property.
+ *
+ * @param string $parameter
+ *
+ * @return string
+ */
+ public function getLineColorProperty($parameter)
+ {
+ return $this->lineProperties['color'][$parameter];
+ }
+
+ /**
+ * Get Line Style Property.
+ *
+ * @param array|string $elements
+ *
+ * @return string
+ */
+ public function getLineStyleProperty($elements)
+ {
+ return $this->getArrayElementsValue($this->lineProperties['style'], $elements);
+ }
+
+ /**
+ * Set Glow Properties.
+ *
+ * @param float $size
+ * @param string $color_value
+ * @param int $color_alpha
+ * @param string $color_type
+ */
+ public function setGlowProperties($size, $color_value = null, $color_alpha = null, $color_type = null): void
+ {
+ $this
+ ->activateObject()
+ ->setGlowSize($size)
+ ->setGlowColor($color_value, $color_alpha, $color_type);
+ }
+
+ /**
+ * Get Glow Color Property.
+ *
+ * @param string $property
+ *
+ * @return string
+ */
+ public function getGlowColor($property)
+ {
+ return $this->glowProperties['color'][$property];
+ }
+
+ /**
+ * Get Glow Size.
+ *
+ * @return string
+ */
+ public function getGlowSize()
+ {
+ return $this->glowProperties['size'];
+ }
+
+ /**
+ * Set Glow Size.
+ *
+ * @param float $size
+ *
+ * @return $this
+ */
+ private function setGlowSize($size)
+ {
+ $this->glowProperties['size'] = $this->getExcelPointsWidth((float) $size);
+
+ return $this;
+ }
+
+ /**
+ * Set Glow Color.
+ *
+ * @param string $color
+ * @param int $alpha
+ * @param string $type
+ *
+ * @return $this
+ */
+ private function setGlowColor($color, $alpha, $type)
+ {
+ if ($color !== null) {
+ $this->glowProperties['color']['value'] = (string) $color;
+ }
+ if ($alpha !== null) {
+ $this->glowProperties['color']['alpha'] = $this->getTrueAlpha((int) $alpha);
+ }
+ if ($type !== null) {
+ $this->glowProperties['color']['type'] = (string) $type;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Line Style Arrow Parameters.
+ *
+ * @param string $arrow_selector
+ * @param string $property_selector
+ *
+ * @return string
+ */
+ public function getLineStyleArrowParameters($arrow_selector, $property_selector)
+ {
+ return $this->getLineStyleArrowSize($this->lineProperties['style']['arrow'][$arrow_selector]['size'], $property_selector);
+ }
+
+ /**
+ * Set Shadow Properties.
+ *
+ * @param int $sh_presets
+ * @param string $sh_color_value
+ * @param string $sh_color_type
+ * @param int $sh_color_alpha
+ * @param string $sh_blur
+ * @param int $sh_angle
+ * @param float $sh_distance
+ */
+ public function setShadowProperties($sh_presets, $sh_color_value = null, $sh_color_type = null, $sh_color_alpha = null, $sh_blur = null, $sh_angle = null, $sh_distance = null): void
+ {
+ $this->activateObject()
+ ->setShadowPresetsProperties((int) $sh_presets)
+ ->setShadowColor(
+ $sh_color_value === null ? $this->shadowProperties['color']['value'] : $sh_color_value,
+ $sh_color_alpha === null ? (int) $this->shadowProperties['color']['alpha'] : $this->getTrueAlpha($sh_color_alpha),
+ $sh_color_type === null ? $this->shadowProperties['color']['type'] : $sh_color_type
+ )
+ ->setShadowBlur($sh_blur)
+ ->setShadowAngle($sh_angle)
+ ->setShadowDistance($sh_distance);
+ }
+
+ /**
+ * Set Shadow Presets Properties.
+ *
+ * @param int $shadow_presets
+ *
+ * @return $this
+ */
+ private function setShadowPresetsProperties($shadow_presets)
+ {
+ $this->shadowProperties['presets'] = $shadow_presets;
+ $this->setShadowProperiesMapValues($this->getShadowPresetsMap($shadow_presets));
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Properties Values.
+ *
+ * @param mixed &$reference
+ *
+ * @return $this
+ */
+ private function setShadowProperiesMapValues(array $properties_map, &$reference = null)
+ {
+ $base_reference = $reference;
+ foreach ($properties_map as $property_key => $property_val) {
+ if (is_array($property_val)) {
+ if ($reference === null) {
+ $reference = &$this->shadowProperties[$property_key];
+ } else {
+ $reference = &$reference[$property_key];
+ }
+ $this->setShadowProperiesMapValues($property_val, $reference);
+ } else {
+ if ($base_reference === null) {
+ $this->shadowProperties[$property_key] = $property_val;
+ } else {
+ $reference[$property_key] = $property_val;
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Color.
+ *
+ * @param string $color
+ * @param int $alpha
+ * @param string $type
+ *
+ * @return $this
+ */
+ private function setShadowColor($color, $alpha, $type)
+ {
+ if ($color !== null) {
+ $this->shadowProperties['color']['value'] = (string) $color;
+ }
+ if ($alpha !== null) {
+ $this->shadowProperties['color']['alpha'] = $this->getTrueAlpha((int) $alpha);
+ }
+ if ($type !== null) {
+ $this->shadowProperties['color']['type'] = (string) $type;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Blur.
+ *
+ * @param float $blur
+ *
+ * @return $this
+ */
+ private function setShadowBlur($blur)
+ {
+ if ($blur !== null) {
+ $this->shadowProperties['blur'] = (string) $this->getExcelPointsWidth($blur);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Angle.
+ *
+ * @param int $angle
+ *
+ * @return $this
+ */
+ private function setShadowAngle($angle)
+ {
+ if ($angle !== null) {
+ $this->shadowProperties['direction'] = (string) $this->getExcelPointsAngle($angle);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Distance.
+ *
+ * @param float $distance
+ *
+ * @return $this
+ */
+ private function setShadowDistance($distance)
+ {
+ if ($distance !== null) {
+ $this->shadowProperties['distance'] = (string) $this->getExcelPointsWidth($distance);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Shadow Property.
+ *
+ * @param string|string[] $elements
+ *
+ * @return string
+ */
+ public function getShadowProperty($elements)
+ {
+ return $this->getArrayElementsValue($this->shadowProperties, $elements);
+ }
+
+ /**
+ * Set Soft Edges Size.
+ *
+ * @param float $size
+ */
+ public function setSoftEdgesSize($size): void
+ {
+ if ($size !== null) {
+ $this->activateObject();
+ $this->softEdges['size'] = (string) $this->getExcelPointsWidth($size);
+ }
+ }
+
+ /**
+ * Get Soft Edges Size.
+ *
+ * @return string
+ */
+ public function getSoftEdgesSize()
+ {
+ return $this->softEdges['size'];
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php
new file mode 100755
index 0000000..51c8995
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php
@@ -0,0 +1,481 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+class Layout
+{
+ /**
+ * layoutTarget.
+ *
+ * @var string
+ */
+ private $layoutTarget;
+
+ /**
+ * X Mode.
+ *
+ * @var string
+ */
+ private $xMode;
+
+ /**
+ * Y Mode.
+ *
+ * @var string
+ */
+ private $yMode;
+
+ /**
+ * X-Position.
+ *
+ * @var float
+ */
+ private $xPos;
+
+ /**
+ * Y-Position.
+ *
+ * @var float
+ */
+ private $yPos;
+
+ /**
+ * width.
+ *
+ * @var float
+ */
+ private $width;
+
+ /**
+ * height.
+ *
+ * @var float
+ */
+ private $height;
+
+ /**
+ * show legend key
+ * Specifies that legend keys should be shown in data labels.
+ *
+ * @var bool
+ */
+ private $showLegendKey;
+
+ /**
+ * show value
+ * Specifies that the value should be shown in a data label.
+ *
+ * @var bool
+ */
+ private $showVal;
+
+ /**
+ * show category name
+ * Specifies that the category name should be shown in the data label.
+ *
+ * @var bool
+ */
+ private $showCatName;
+
+ /**
+ * show data series name
+ * Specifies that the series name should be shown in the data label.
+ *
+ * @var bool
+ */
+ private $showSerName;
+
+ /**
+ * show percentage
+ * Specifies that the percentage should be shown in the data label.
+ *
+ * @var bool
+ */
+ private $showPercent;
+
+ /**
+ * show bubble size.
+ *
+ * @var bool
+ */
+ private $showBubbleSize;
+
+ /**
+ * show leader lines
+ * Specifies that leader lines should be shown for the data label.
+ *
+ * @var bool
+ */
+ private $showLeaderLines;
+
+ /**
+ * Create a new Layout.
+ */
+ public function __construct(array $layout = [])
+ {
+ if (isset($layout['layoutTarget'])) {
+ $this->layoutTarget = $layout['layoutTarget'];
+ }
+ if (isset($layout['xMode'])) {
+ $this->xMode = $layout['xMode'];
+ }
+ if (isset($layout['yMode'])) {
+ $this->yMode = $layout['yMode'];
+ }
+ if (isset($layout['x'])) {
+ $this->xPos = (float) $layout['x'];
+ }
+ if (isset($layout['y'])) {
+ $this->yPos = (float) $layout['y'];
+ }
+ if (isset($layout['w'])) {
+ $this->width = (float) $layout['w'];
+ }
+ if (isset($layout['h'])) {
+ $this->height = (float) $layout['h'];
+ }
+ }
+
+ /**
+ * Get Layout Target.
+ *
+ * @return string
+ */
+ public function getLayoutTarget()
+ {
+ return $this->layoutTarget;
+ }
+
+ /**
+ * Set Layout Target.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setLayoutTarget($value)
+ {
+ $this->layoutTarget = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get X-Mode.
+ *
+ * @return string
+ */
+ public function getXMode()
+ {
+ return $this->xMode;
+ }
+
+ /**
+ * Set X-Mode.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setXMode($value)
+ {
+ $this->xMode = (string) $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Y-Mode.
+ *
+ * @return string
+ */
+ public function getYMode()
+ {
+ return $this->yMode;
+ }
+
+ /**
+ * Set Y-Mode.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setYMode($value)
+ {
+ $this->yMode = (string) $value;
+
+ return $this;
+ }
+
+ /**
+ * Get X-Position.
+ *
+ * @return number
+ */
+ public function getXPosition()
+ {
+ return $this->xPos;
+ }
+
+ /**
+ * Set X-Position.
+ *
+ * @param float $value
+ *
+ * @return $this
+ */
+ public function setXPosition($value)
+ {
+ $this->xPos = (float) $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Y-Position.
+ *
+ * @return number
+ */
+ public function getYPosition()
+ {
+ return $this->yPos;
+ }
+
+ /**
+ * Set Y-Position.
+ *
+ * @param float $value
+ *
+ * @return $this
+ */
+ public function setYPosition($value)
+ {
+ $this->yPos = (float) $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Width.
+ *
+ * @return number
+ */
+ public function getWidth()
+ {
+ return $this->width;
+ }
+
+ /**
+ * Set Width.
+ *
+ * @param float $value
+ *
+ * @return $this
+ */
+ public function setWidth($value)
+ {
+ $this->width = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Height.
+ *
+ * @return number
+ */
+ public function getHeight()
+ {
+ return $this->height;
+ }
+
+ /**
+ * Set Height.
+ *
+ * @param float $value
+ *
+ * @return $this
+ */
+ public function setHeight($value)
+ {
+ $this->height = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get show legend key.
+ *
+ * @return bool
+ */
+ public function getShowLegendKey()
+ {
+ return $this->showLegendKey;
+ }
+
+ /**
+ * Set show legend key
+ * Specifies that legend keys should be shown in data labels.
+ *
+ * @param bool $value Show legend key
+ *
+ * @return $this
+ */
+ public function setShowLegendKey($value)
+ {
+ $this->showLegendKey = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get show value.
+ *
+ * @return bool
+ */
+ public function getShowVal()
+ {
+ return $this->showVal;
+ }
+
+ /**
+ * Set show val
+ * Specifies that the value should be shown in data labels.
+ *
+ * @param bool $value Show val
+ *
+ * @return $this
+ */
+ public function setShowVal($value)
+ {
+ $this->showVal = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get show category name.
+ *
+ * @return bool
+ */
+ public function getShowCatName()
+ {
+ return $this->showCatName;
+ }
+
+ /**
+ * Set show cat name
+ * Specifies that the category name should be shown in data labels.
+ *
+ * @param bool $value Show cat name
+ *
+ * @return $this
+ */
+ public function setShowCatName($value)
+ {
+ $this->showCatName = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get show data series name.
+ *
+ * @return bool
+ */
+ public function getShowSerName()
+ {
+ return $this->showSerName;
+ }
+
+ /**
+ * Set show ser name
+ * Specifies that the series name should be shown in data labels.
+ *
+ * @param bool $value Show series name
+ *
+ * @return $this
+ */
+ public function setShowSerName($value)
+ {
+ $this->showSerName = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get show percentage.
+ *
+ * @return bool
+ */
+ public function getShowPercent()
+ {
+ return $this->showPercent;
+ }
+
+ /**
+ * Set show percentage
+ * Specifies that the percentage should be shown in data labels.
+ *
+ * @param bool $value Show percentage
+ *
+ * @return $this
+ */
+ public function setShowPercent($value)
+ {
+ $this->showPercent = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get show bubble size.
+ *
+ * @return bool
+ */
+ public function getShowBubbleSize()
+ {
+ return $this->showBubbleSize;
+ }
+
+ /**
+ * Set show bubble size
+ * Specifies that the bubble size should be shown in data labels.
+ *
+ * @param bool $value Show bubble size
+ *
+ * @return $this
+ */
+ public function setShowBubbleSize($value)
+ {
+ $this->showBubbleSize = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get show leader lines.
+ *
+ * @return bool
+ */
+ public function getShowLeaderLines()
+ {
+ return $this->showLeaderLines;
+ }
+
+ /**
+ * Set show leader lines
+ * Specifies that leader lines should be shown in data labels.
+ *
+ * @param bool $value Show leader lines
+ *
+ * @return $this
+ */
+ public function setShowLeaderLines($value)
+ {
+ $this->showLeaderLines = $value;
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php
new file mode 100755
index 0000000..fc0ed14
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php
@@ -0,0 +1,157 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+class Legend
+{
+ /** Legend positions */
+ const XL_LEGEND_POSITION_BOTTOM = -4107; // Below the chart.
+ const XL_LEGEND_POSITION_CORNER = 2; // In the upper right-hand corner of the chart border.
+ const XL_LEGEND_POSITION_CUSTOM = -4161; // A custom position.
+ const XL_LEGEND_POSITION_LEFT = -4131; // Left of the chart.
+ const XL_LEGEND_POSITION_RIGHT = -4152; // Right of the chart.
+ const XL_LEGEND_POSITION_TOP = -4160; // Above the chart.
+
+ const POSITION_RIGHT = 'r';
+ const POSITION_LEFT = 'l';
+ const POSITION_BOTTOM = 'b';
+ const POSITION_TOP = 't';
+ const POSITION_TOPRIGHT = 'tr';
+
+ private static $positionXLref = [
+ self::XL_LEGEND_POSITION_BOTTOM => self::POSITION_BOTTOM,
+ self::XL_LEGEND_POSITION_CORNER => self::POSITION_TOPRIGHT,
+ self::XL_LEGEND_POSITION_CUSTOM => '??',
+ self::XL_LEGEND_POSITION_LEFT => self::POSITION_LEFT,
+ self::XL_LEGEND_POSITION_RIGHT => self::POSITION_RIGHT,
+ self::XL_LEGEND_POSITION_TOP => self::POSITION_TOP,
+ ];
+
+ /**
+ * Legend position.
+ *
+ * @var string
+ */
+ private $position = self::POSITION_RIGHT;
+
+ /**
+ * Allow overlay of other elements?
+ *
+ * @var bool
+ */
+ private $overlay = true;
+
+ /**
+ * Legend Layout.
+ *
+ * @var Layout
+ */
+ private $layout;
+
+ /**
+ * Create a new Legend.
+ *
+ * @param string $position
+ * @param bool $overlay
+ */
+ public function __construct($position = self::POSITION_RIGHT, ?Layout $layout = null, $overlay = false)
+ {
+ $this->setPosition($position);
+ $this->layout = $layout;
+ $this->setOverlay($overlay);
+ }
+
+ /**
+ * Get legend position as an excel string value.
+ *
+ * @return string
+ */
+ public function getPosition()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Get legend position using an excel string value.
+ *
+ * @param string $position see self::POSITION_*
+ *
+ * @return bool
+ */
+ public function setPosition($position)
+ {
+ if (!in_array($position, self::$positionXLref)) {
+ return false;
+ }
+
+ $this->position = $position;
+
+ return true;
+ }
+
+ /**
+ * Get legend position as an Excel internal numeric value.
+ *
+ * @return int
+ */
+ public function getPositionXL()
+ {
+ return array_search($this->position, self::$positionXLref);
+ }
+
+ /**
+ * Set legend position using an Excel internal numeric value.
+ *
+ * @param int $positionXL see self::XL_LEGEND_POSITION_*
+ *
+ * @return bool
+ */
+ public function setPositionXL($positionXL)
+ {
+ if (!isset(self::$positionXLref[$positionXL])) {
+ return false;
+ }
+
+ $this->position = self::$positionXLref[$positionXL];
+
+ return true;
+ }
+
+ /**
+ * Get allow overlay of other elements?
+ *
+ * @return bool
+ */
+ public function getOverlay()
+ {
+ return $this->overlay;
+ }
+
+ /**
+ * Set allow overlay of other elements?
+ *
+ * @param bool $overlay
+ *
+ * @return bool
+ */
+ public function setOverlay($overlay)
+ {
+ if (!is_bool($overlay)) {
+ return false;
+ }
+
+ $this->overlay = $overlay;
+
+ return true;
+ }
+
+ /**
+ * Get Layout.
+ *
+ * @return Layout
+ */
+ public function getLayout()
+ {
+ return $this->layout;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php
new file mode 100755
index 0000000..954777c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class PlotArea
+{
+ /**
+ * PlotArea Layout.
+ *
+ * @var Layout
+ */
+ private $layout;
+
+ /**
+ * Plot Series.
+ *
+ * @var DataSeries[]
+ */
+ private $plotSeries = [];
+
+ /**
+ * Create a new PlotArea.
+ *
+ * @param DataSeries[] $plotSeries
+ */
+ public function __construct(?Layout $layout = null, array $plotSeries = [])
+ {
+ $this->layout = $layout;
+ $this->plotSeries = $plotSeries;
+ }
+
+ /**
+ * Get Layout.
+ *
+ * @return Layout
+ */
+ public function getLayout()
+ {
+ return $this->layout;
+ }
+
+ /**
+ * Get Number of Plot Groups.
+ *
+ * @return array of DataSeries
+ */
+ public function getPlotGroupCount()
+ {
+ return count($this->plotSeries);
+ }
+
+ /**
+ * Get Number of Plot Series.
+ *
+ * @return int
+ */
+ public function getPlotSeriesCount()
+ {
+ $seriesCount = 0;
+ foreach ($this->plotSeries as $plot) {
+ $seriesCount += $plot->getPlotSeriesCount();
+ }
+
+ return $seriesCount;
+ }
+
+ /**
+ * Get Plot Series.
+ *
+ * @return array of DataSeries
+ */
+ public function getPlotGroup()
+ {
+ return $this->plotSeries;
+ }
+
+ /**
+ * Get Plot Series by Index.
+ *
+ * @param mixed $index
+ *
+ * @return DataSeries
+ */
+ public function getPlotGroupByIndex($index)
+ {
+ return $this->plotSeries[$index];
+ }
+
+ /**
+ * Set Plot Series.
+ *
+ * @param DataSeries[] $plotSeries
+ *
+ * @return $this
+ */
+ public function setPlotSeries(array $plotSeries)
+ {
+ $this->plotSeries = $plotSeries;
+
+ return $this;
+ }
+
+ public function refresh(Worksheet $worksheet): void
+ {
+ foreach ($this->plotSeries as $plotSeries) {
+ $plotSeries->refresh($worksheet);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php
new file mode 100755
index 0000000..98095f0
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php
@@ -0,0 +1,369 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+/**
+ * Created by PhpStorm.
+ * User: nhw2h8s
+ * Date: 7/2/14
+ * Time: 5:45 PM.
+ */
+abstract class Properties
+{
+ const
+ EXCEL_COLOR_TYPE_STANDARD = 'prstClr';
+ const EXCEL_COLOR_TYPE_SCHEME = 'schemeClr';
+ const EXCEL_COLOR_TYPE_ARGB = 'srgbClr';
+
+ const
+ AXIS_LABELS_LOW = 'low';
+ const AXIS_LABELS_HIGH = 'high';
+ const AXIS_LABELS_NEXT_TO = 'nextTo';
+ const AXIS_LABELS_NONE = 'none';
+
+ const
+ TICK_MARK_NONE = 'none';
+ const TICK_MARK_INSIDE = 'in';
+ const TICK_MARK_OUTSIDE = 'out';
+ const TICK_MARK_CROSS = 'cross';
+
+ const
+ HORIZONTAL_CROSSES_AUTOZERO = 'autoZero';
+ const HORIZONTAL_CROSSES_MAXIMUM = 'max';
+
+ const
+ FORMAT_CODE_GENERAL = 'General';
+ const FORMAT_CODE_NUMBER = '#,##0.00';
+ const FORMAT_CODE_CURRENCY = '$#,##0.00';
+ const FORMAT_CODE_ACCOUNTING = '_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_)';
+ const FORMAT_CODE_DATE = 'm/d/yyyy';
+ const FORMAT_CODE_TIME = '[$-F400]h:mm:ss AM/PM';
+ const FORMAT_CODE_PERCENTAGE = '0.00%';
+ const FORMAT_CODE_FRACTION = '# ?/?';
+ const FORMAT_CODE_SCIENTIFIC = '0.00E+00';
+ const FORMAT_CODE_TEXT = '@';
+ const FORMAT_CODE_SPECIAL = '00000';
+
+ const
+ ORIENTATION_NORMAL = 'minMax';
+ const ORIENTATION_REVERSED = 'maxMin';
+
+ const
+ LINE_STYLE_COMPOUND_SIMPLE = 'sng';
+ const LINE_STYLE_COMPOUND_DOUBLE = 'dbl';
+ const LINE_STYLE_COMPOUND_THICKTHIN = 'thickThin';
+ const LINE_STYLE_COMPOUND_THINTHICK = 'thinThick';
+ const LINE_STYLE_COMPOUND_TRIPLE = 'tri';
+ const LINE_STYLE_DASH_SOLID = 'solid';
+ const LINE_STYLE_DASH_ROUND_DOT = 'sysDot';
+ const LINE_STYLE_DASH_SQUERE_DOT = 'sysDash';
+ const LINE_STYPE_DASH_DASH = 'dash';
+ const LINE_STYLE_DASH_DASH_DOT = 'dashDot';
+ const LINE_STYLE_DASH_LONG_DASH = 'lgDash';
+ const LINE_STYLE_DASH_LONG_DASH_DOT = 'lgDashDot';
+ const LINE_STYLE_DASH_LONG_DASH_DOT_DOT = 'lgDashDotDot';
+ const LINE_STYLE_CAP_SQUARE = 'sq';
+ const LINE_STYLE_CAP_ROUND = 'rnd';
+ const LINE_STYLE_CAP_FLAT = 'flat';
+ const LINE_STYLE_JOIN_ROUND = 'bevel';
+ const LINE_STYLE_JOIN_MITER = 'miter';
+ const LINE_STYLE_JOIN_BEVEL = 'bevel';
+ const LINE_STYLE_ARROW_TYPE_NOARROW = null;
+ const LINE_STYLE_ARROW_TYPE_ARROW = 'triangle';
+ const LINE_STYLE_ARROW_TYPE_OPEN = 'arrow';
+ const LINE_STYLE_ARROW_TYPE_STEALTH = 'stealth';
+ const LINE_STYLE_ARROW_TYPE_DIAMOND = 'diamond';
+ const LINE_STYLE_ARROW_TYPE_OVAL = 'oval';
+ const LINE_STYLE_ARROW_SIZE_1 = 1;
+ const LINE_STYLE_ARROW_SIZE_2 = 2;
+ const LINE_STYLE_ARROW_SIZE_3 = 3;
+ const LINE_STYLE_ARROW_SIZE_4 = 4;
+ const LINE_STYLE_ARROW_SIZE_5 = 5;
+ const LINE_STYLE_ARROW_SIZE_6 = 6;
+ const LINE_STYLE_ARROW_SIZE_7 = 7;
+ const LINE_STYLE_ARROW_SIZE_8 = 8;
+ const LINE_STYLE_ARROW_SIZE_9 = 9;
+
+ const
+ SHADOW_PRESETS_NOSHADOW = null;
+ const SHADOW_PRESETS_OUTER_BOTTTOM_RIGHT = 1;
+ const SHADOW_PRESETS_OUTER_BOTTOM = 2;
+ const SHADOW_PRESETS_OUTER_BOTTOM_LEFT = 3;
+ const SHADOW_PRESETS_OUTER_RIGHT = 4;
+ const SHADOW_PRESETS_OUTER_CENTER = 5;
+ const SHADOW_PRESETS_OUTER_LEFT = 6;
+ const SHADOW_PRESETS_OUTER_TOP_RIGHT = 7;
+ const SHADOW_PRESETS_OUTER_TOP = 8;
+ const SHADOW_PRESETS_OUTER_TOP_LEFT = 9;
+ const SHADOW_PRESETS_INNER_BOTTTOM_RIGHT = 10;
+ const SHADOW_PRESETS_INNER_BOTTOM = 11;
+ const SHADOW_PRESETS_INNER_BOTTOM_LEFT = 12;
+ const SHADOW_PRESETS_INNER_RIGHT = 13;
+ const SHADOW_PRESETS_INNER_CENTER = 14;
+ const SHADOW_PRESETS_INNER_LEFT = 15;
+ const SHADOW_PRESETS_INNER_TOP_RIGHT = 16;
+ const SHADOW_PRESETS_INNER_TOP = 17;
+ const SHADOW_PRESETS_INNER_TOP_LEFT = 18;
+ const SHADOW_PRESETS_PERSPECTIVE_BELOW = 19;
+ const SHADOW_PRESETS_PERSPECTIVE_UPPER_RIGHT = 20;
+ const SHADOW_PRESETS_PERSPECTIVE_UPPER_LEFT = 21;
+ const SHADOW_PRESETS_PERSPECTIVE_LOWER_RIGHT = 22;
+ const SHADOW_PRESETS_PERSPECTIVE_LOWER_LEFT = 23;
+
+ /**
+ * @param float $width
+ *
+ * @return float
+ */
+ protected function getExcelPointsWidth($width)
+ {
+ return $width * 12700;
+ }
+
+ /**
+ * @param float $angle
+ *
+ * @return float
+ */
+ protected function getExcelPointsAngle($angle)
+ {
+ return $angle * 60000;
+ }
+
+ protected function getTrueAlpha($alpha)
+ {
+ return (string) 100 - $alpha . '000';
+ }
+
+ protected function setColorProperties($color, $alpha, $type)
+ {
+ return [
+ 'type' => (string) $type,
+ 'value' => (string) $color,
+ 'alpha' => (string) $this->getTrueAlpha($alpha),
+ ];
+ }
+
+ protected function getLineStyleArrowSize($array_selector, $array_kay_selector)
+ {
+ $sizes = [
+ 1 => ['w' => 'sm', 'len' => 'sm'],
+ 2 => ['w' => 'sm', 'len' => 'med'],
+ 3 => ['w' => 'sm', 'len' => 'lg'],
+ 4 => ['w' => 'med', 'len' => 'sm'],
+ 5 => ['w' => 'med', 'len' => 'med'],
+ 6 => ['w' => 'med', 'len' => 'lg'],
+ 7 => ['w' => 'lg', 'len' => 'sm'],
+ 8 => ['w' => 'lg', 'len' => 'med'],
+ 9 => ['w' => 'lg', 'len' => 'lg'],
+ ];
+
+ return $sizes[$array_selector][$array_kay_selector];
+ }
+
+ protected function getShadowPresetsMap($shadow_presets_option)
+ {
+ $presets_options = [
+ //OUTER
+ 1 => [
+ 'effect' => 'outerShdw',
+ 'blur' => '50800',
+ 'distance' => '38100',
+ 'direction' => '2700000',
+ 'algn' => 'tl',
+ 'rotWithShape' => '0',
+ ],
+ 2 => [
+ 'effect' => 'outerShdw',
+ 'blur' => '50800',
+ 'distance' => '38100',
+ 'direction' => '5400000',
+ 'algn' => 't',
+ 'rotWithShape' => '0',
+ ],
+ 3 => [
+ 'effect' => 'outerShdw',
+ 'blur' => '50800',
+ 'distance' => '38100',
+ 'direction' => '8100000',
+ 'algn' => 'tr',
+ 'rotWithShape' => '0',
+ ],
+ 4 => [
+ 'effect' => 'outerShdw',
+ 'blur' => '50800',
+ 'distance' => '38100',
+ 'algn' => 'l',
+ 'rotWithShape' => '0',
+ ],
+ 5 => [
+ 'effect' => 'outerShdw',
+ 'size' => [
+ 'sx' => '102000',
+ 'sy' => '102000',
+ ],
+ 'blur' => '63500',
+ 'distance' => '38100',
+ 'algn' => 'ctr',
+ 'rotWithShape' => '0',
+ ],
+ 6 => [
+ 'effect' => 'outerShdw',
+ 'blur' => '50800',
+ 'distance' => '38100',
+ 'direction' => '10800000',
+ 'algn' => 'r',
+ 'rotWithShape' => '0',
+ ],
+ 7 => [
+ 'effect' => 'outerShdw',
+ 'blur' => '50800',
+ 'distance' => '38100',
+ 'direction' => '18900000',
+ 'algn' => 'bl',
+ 'rotWithShape' => '0',
+ ],
+ 8 => [
+ 'effect' => 'outerShdw',
+ 'blur' => '50800',
+ 'distance' => '38100',
+ 'direction' => '16200000',
+ 'rotWithShape' => '0',
+ ],
+ 9 => [
+ 'effect' => 'outerShdw',
+ 'blur' => '50800',
+ 'distance' => '38100',
+ 'direction' => '13500000',
+ 'algn' => 'br',
+ 'rotWithShape' => '0',
+ ],
+ //INNER
+ 10 => [
+ 'effect' => 'innerShdw',
+ 'blur' => '63500',
+ 'distance' => '50800',
+ 'direction' => '2700000',
+ ],
+ 11 => [
+ 'effect' => 'innerShdw',
+ 'blur' => '63500',
+ 'distance' => '50800',
+ 'direction' => '5400000',
+ ],
+ 12 => [
+ 'effect' => 'innerShdw',
+ 'blur' => '63500',
+ 'distance' => '50800',
+ 'direction' => '8100000',
+ ],
+ 13 => [
+ 'effect' => 'innerShdw',
+ 'blur' => '63500',
+ 'distance' => '50800',
+ ],
+ 14 => [
+ 'effect' => 'innerShdw',
+ 'blur' => '114300',
+ ],
+ 15 => [
+ 'effect' => 'innerShdw',
+ 'blur' => '63500',
+ 'distance' => '50800',
+ 'direction' => '10800000',
+ ],
+ 16 => [
+ 'effect' => 'innerShdw',
+ 'blur' => '63500',
+ 'distance' => '50800',
+ 'direction' => '18900000',
+ ],
+ 17 => [
+ 'effect' => 'innerShdw',
+ 'blur' => '63500',
+ 'distance' => '50800',
+ 'direction' => '16200000',
+ ],
+ 18 => [
+ 'effect' => 'innerShdw',
+ 'blur' => '63500',
+ 'distance' => '50800',
+ 'direction' => '13500000',
+ ],
+ //perspective
+ 19 => [
+ 'effect' => 'outerShdw',
+ 'blur' => '152400',
+ 'distance' => '317500',
+ 'size' => [
+ 'sx' => '90000',
+ 'sy' => '-19000',
+ ],
+ 'direction' => '5400000',
+ 'rotWithShape' => '0',
+ ],
+ 20 => [
+ 'effect' => 'outerShdw',
+ 'blur' => '76200',
+ 'direction' => '18900000',
+ 'size' => [
+ 'sy' => '23000',
+ 'kx' => '-1200000',
+ ],
+ 'algn' => 'bl',
+ 'rotWithShape' => '0',
+ ],
+ 21 => [
+ 'effect' => 'outerShdw',
+ 'blur' => '76200',
+ 'direction' => '13500000',
+ 'size' => [
+ 'sy' => '23000',
+ 'kx' => '1200000',
+ ],
+ 'algn' => 'br',
+ 'rotWithShape' => '0',
+ ],
+ 22 => [
+ 'effect' => 'outerShdw',
+ 'blur' => '76200',
+ 'distance' => '12700',
+ 'direction' => '2700000',
+ 'size' => [
+ 'sy' => '-23000',
+ 'kx' => '-800400',
+ ],
+ 'algn' => 'bl',
+ 'rotWithShape' => '0',
+ ],
+ 23 => [
+ 'effect' => 'outerShdw',
+ 'blur' => '76200',
+ 'distance' => '12700',
+ 'direction' => '8100000',
+ 'size' => [
+ 'sy' => '-23000',
+ 'kx' => '800400',
+ ],
+ 'algn' => 'br',
+ 'rotWithShape' => '0',
+ ],
+ ];
+
+ return $presets_options[$shadow_presets_option];
+ }
+
+ protected function getArrayElementsValue($properties, $elements)
+ {
+ $reference = &$properties;
+ if (!is_array($elements)) {
+ return $reference[$elements];
+ }
+
+ foreach ($elements as $keys) {
+ $reference = &$reference[$keys];
+ }
+
+ return $reference;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php
new file mode 100755
index 0000000..3032f6b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
+
+use PhpOffice\PhpSpreadsheet\Chart\Chart;
+
+interface IRenderer
+{
+ /**
+ * IRenderer constructor.
+ */
+ public function __construct(Chart $chart);
+
+ /**
+ * Render the chart to given file (or stream).
+ *
+ * @param string $filename Name of the file render to
+ *
+ * @return bool true on success
+ */
+ public function render($filename);
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php
new file mode 100755
index 0000000..bd931c6
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php
@@ -0,0 +1,870 @@
+<?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;
+
+require_once __DIR__ . '/Polyfill.php';
+
+class JpGraph 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)
+ {
+ self::init();
+ $this->graph = null;
+ $this->chart = $chart;
+ }
+
+ private static function init(): void
+ {
+ static $loaded = false;
+ if ($loaded) {
+ return;
+ }
+
+ \JpGraph\JpGraph::load();
+ \JpGraph\JpGraph::module('bar');
+ \JpGraph\JpGraph::module('contour');
+ \JpGraph\JpGraph::module('line');
+ \JpGraph\JpGraph::module('pie');
+ \JpGraph\JpGraph::module('pie3d');
+ \JpGraph\JpGraph::module('radar');
+ \JpGraph\JpGraph::module('regstat');
+ \JpGraph\JpGraph::module('scatter');
+ \JpGraph\JpGraph::module('stock');
+
+ 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,
+ ];
+
+ $loaded = true;
+ }
+
+ 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, $labelCount, $rotation = '')
+ {
+ $datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode();
+ if ($datasetLabelFormatCode !== null) {
+ // 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, $dimensions = '2d'): void
+ {
+ $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
+
+ $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, $labelCount);
+ $this->graph->xaxis->SetTickLabels($datasetLabels);
+ }
+
+ $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+ $seriesPlots = [];
+ if ($grouping == 'percentStacked') {
+ $sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
+ }
+
+ // Loop through each data series in turn
+ for ($i = 0; $i < $seriesCount; ++$i) {
+ $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
+ $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->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($i)->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();
+
+ $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, $labelCount, $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);
+ }
+
+ // Loop through each data series in turn
+ for ($j = 0; $j < $seriesCount; ++$j) {
+ $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->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
+ {
+ $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
+ $scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
+
+ $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+ $seriesPlots = [];
+
+ // 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();
+
+ foreach ($dataValuesY as $k => $dataValueY) {
+ $dataValuesY[$k] = $k;
+ }
+
+ $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();
+ $seriesPlots = [];
+
+ // 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] = implode(' ', array_reverse($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
+ {
+ $contourStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
+
+ $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+ $seriesPlots = [];
+
+ $dataValues = [];
+ // 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();
+
+ $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)->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];
+ }
+ }
+
+ // 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, $labelCount);
+ $this->graph->xaxis->SetTickLabels($datasetLabels);
+ }
+
+ $seriesPlot = new StockPlot($dataValuesPlot);
+ $seriesPlot->SetWidth(20);
+
+ $this->graph->Add($seriesPlot);
+ }
+
+ private function renderAreaChart($groupCount, $dimensions = '2d'): void
+ {
+ $this->renderCartesianPlotArea();
+
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $this->renderPlotLine($i, true, false, $dimensions);
+ }
+ }
+
+ private function renderLineChart($groupCount, $dimensions = '2d'): void
+ {
+ $this->renderCartesianPlotArea();
+
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $this->renderPlotLine($i, false, false, $dimensions);
+ }
+ }
+
+ 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) {
+ $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
+ $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, $labelCount);
+ }
+ }
+
+ $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+ $seriesPlots = [];
+ // 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) {
+ $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, $dimensions): void
+ {
+ $this->renderCartesianPlotArea('intint');
+
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $this->renderPlotContour($i);
+ }
+ }
+
+ private function renderCombinationChart($groupCount, $dimensions, $outputDestination)
+ {
+ $this->renderCartesianPlotArea();
+
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $dimensions = null;
+ $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
+ switch ($chartType) {
+ case 'area3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'areaChart':
+ $this->renderPlotLine($i, true, true, $dimensions);
+
+ break;
+ case 'bar3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'barChart':
+ $this->renderPlotBar($i, $dimensions);
+
+ break;
+ case 'line3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'lineChart':
+ $this->renderPlotLine($i, false, true, $dimensions);
+
+ 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, $dimensions, $outputDestination);
+ }
+ }
+
+ switch ($chartType) {
+ case 'area3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'areaChart':
+ $this->renderAreaChart($groupCount, $dimensions);
+
+ 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, $dimensions);
+
+ 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':
+ $dimensions = '3d';
+ // no break
+ case 'surfaceChart':
+ $this->renderContourChart($groupCount, $dimensions);
+
+ 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;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt
new file mode 100755
index 0000000..4abab7a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt
@@ -0,0 +1,20 @@
+ChartDirector
+ https://www.advsofteng.com/cdphp.html
+
+GraPHPite
+ http://graphpite.sourceforge.net/
+
+JpGraph
+ http://www.aditus.nu/jpgraph/
+
+LibChart
+ https://naku.dohcrew.com/libchart/pages/introduction/
+
+pChart
+ http://pchart.sourceforge.net/
+
+TeeChart
+ https://www.steema.com/
+
+PHPGraphLib
+ http://www.ebrueggeman.com/phpgraphlib
\ No newline at end of file
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/Polyfill.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/Polyfill.php
new file mode 100755
index 0000000..5e233ab
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/Polyfill.php
@@ -0,0 +1,9 @@
+<?php
+
+// This is a dirty workaround to output JpGraph charts even when antialiasing is not available
+if (!function_exists('imageantialias')) {
+ function imageantialias(...$args): void
+ {
+ // Do nothing
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php
new file mode 100755
index 0000000..af9fa08
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Chart;
+
+class Title
+{
+ /**
+ * Title Caption.
+ *
+ * @var string
+ */
+ private $caption;
+
+ /**
+ * Title Layout.
+ *
+ * @var Layout
+ */
+ private $layout;
+
+ /**
+ * Create a new Title.
+ *
+ * @param null|mixed $caption
+ */
+ public function __construct($caption = null, ?Layout $layout = null)
+ {
+ $this->caption = $caption;
+ $this->layout = $layout;
+ }
+
+ /**
+ * Get caption.
+ *
+ * @return string
+ */
+ public function getCaption()
+ {
+ return $this->caption;
+ }
+
+ /**
+ * Set caption.
+ *
+ * @param string $caption
+ *
+ * @return $this
+ */
+ public function setCaption($caption)
+ {
+ $this->caption = $caption;
+
+ return $this;
+ }
+
+ /**
+ * Get Layout.
+ *
+ * @return Layout
+ */
+ public function getLayout()
+ {
+ return $this->layout;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php
new file mode 100755
index 0000000..48f34f4
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php
@@ -0,0 +1,498 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Collection;
+
+use Generator;
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use Psr\SimpleCache\CacheInterface;
+
+class Cells
+{
+ /**
+ * @var \Psr\SimpleCache\CacheInterface
+ */
+ private $cache;
+
+ /**
+ * Parent worksheet.
+ *
+ * @var Worksheet
+ */
+ private $parent;
+
+ /**
+ * The currently active Cell.
+ *
+ * @var Cell
+ */
+ private $currentCell;
+
+ /**
+ * Coordinate of the currently active Cell.
+ *
+ * @var string
+ */
+ private $currentCoordinate;
+
+ /**
+ * Flag indicating whether the currently active Cell requires saving.
+ *
+ * @var bool
+ */
+ private $currentCellIsDirty = false;
+
+ /**
+ * An index of existing cells. Booleans indexed by their coordinate.
+ *
+ * @var bool[]
+ */
+ private $index = [];
+
+ /**
+ * Prefix used to uniquely identify cache data for this worksheet.
+ *
+ * @var string
+ */
+ private $cachePrefix;
+
+ /**
+ * Initialise this new cell collection.
+ *
+ * @param Worksheet $parent The worksheet for this cell collection
+ */
+ public function __construct(Worksheet $parent, CacheInterface $cache)
+ {
+ // Set our parent worksheet.
+ // This is maintained here to facilitate re-attaching it to Cell objects when
+ // they are woken from a serialized state
+ $this->parent = $parent;
+ $this->cache = $cache;
+ $this->cachePrefix = $this->getUniqueID();
+ }
+
+ /**
+ * Return the parent worksheet for this cell collection.
+ *
+ * @return Worksheet
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Whether the collection holds a cell for the given coordinate.
+ *
+ * @param string $pCoord Coordinate of the cell to check
+ *
+ * @return bool
+ */
+ public function has($pCoord)
+ {
+ if ($pCoord === $this->currentCoordinate) {
+ return true;
+ }
+
+ // Check if the requested entry exists in the index
+ return isset($this->index[$pCoord]);
+ }
+
+ /**
+ * Add or update a cell in the collection.
+ *
+ * @param Cell $cell Cell to update
+ *
+ * @return Cell
+ */
+ public function update(Cell $cell)
+ {
+ return $this->add($cell->getCoordinate(), $cell);
+ }
+
+ /**
+ * Delete a cell in cache identified by coordinate.
+ *
+ * @param string $pCoord Coordinate of the cell to delete
+ */
+ public function delete($pCoord): void
+ {
+ if ($pCoord === $this->currentCoordinate && $this->currentCell !== null) {
+ $this->currentCell->detach();
+ $this->currentCoordinate = null;
+ $this->currentCell = null;
+ $this->currentCellIsDirty = false;
+ }
+
+ unset($this->index[$pCoord]);
+
+ // Delete the entry from cache
+ $this->cache->delete($this->cachePrefix . $pCoord);
+ }
+
+ /**
+ * Get a list of all cell coordinates currently held in the collection.
+ *
+ * @return string[]
+ */
+ public function getCoordinates()
+ {
+ return array_keys($this->index);
+ }
+
+ /**
+ * Get a sorted list of all cell coordinates currently held in the collection by row and column.
+ *
+ * @return string[]
+ */
+ public function getSortedCoordinates()
+ {
+ $sortKeys = [];
+ foreach ($this->getCoordinates() as $coord) {
+ $column = '';
+ $row = 0;
+ sscanf($coord, '%[A-Z]%d', $column, $row);
+ $sortKeys[sprintf('%09d%3s', $row, $column)] = $coord;
+ }
+ ksort($sortKeys);
+
+ return array_values($sortKeys);
+ }
+
+ /**
+ * Get highest worksheet column and highest row that have cell records.
+ *
+ * @return array Highest column name and highest row number
+ */
+ public function getHighestRowAndColumn()
+ {
+ // Lookup highest column and highest row
+ $col = ['A' => '1A'];
+ $row = [1];
+ foreach ($this->getCoordinates() as $coord) {
+ $c = '';
+ $r = 0;
+ sscanf($coord, '%[A-Z]%d', $c, $r);
+ $row[$r] = $r;
+ $col[$c] = strlen($c) . $c;
+ }
+
+ // Determine highest column and row
+ $highestRow = max($row);
+ $highestColumn = substr(max($col), 1);
+
+ return [
+ 'row' => $highestRow,
+ 'column' => $highestColumn,
+ ];
+ }
+
+ /**
+ * Return the cell coordinate of the currently active cell object.
+ *
+ * @return string
+ */
+ public function getCurrentCoordinate()
+ {
+ return $this->currentCoordinate;
+ }
+
+ /**
+ * Return the column coordinate of the currently active cell object.
+ *
+ * @return string
+ */
+ public function getCurrentColumn()
+ {
+ $column = '';
+ $row = 0;
+
+ sscanf($this->currentCoordinate, '%[A-Z]%d', $column, $row);
+
+ return $column;
+ }
+
+ /**
+ * Return the row coordinate of the currently active cell object.
+ *
+ * @return int
+ */
+ public function getCurrentRow()
+ {
+ $column = '';
+ $row = 0;
+
+ sscanf($this->currentCoordinate, '%[A-Z]%d', $column, $row);
+
+ return (int) $row;
+ }
+
+ /**
+ * Get highest worksheet column.
+ *
+ * @param string $row Return the highest column for the specified row,
+ * or the highest column of any row if no row number is passed
+ *
+ * @return string Highest column name
+ */
+ public function getHighestColumn($row = null)
+ {
+ if ($row === null) {
+ $colRow = $this->getHighestRowAndColumn();
+
+ return $colRow['column'];
+ }
+
+ $columnList = [1];
+ foreach ($this->getCoordinates() as $coord) {
+ $c = '';
+ $r = 0;
+
+ sscanf($coord, '%[A-Z]%d', $c, $r);
+ if ($r != $row) {
+ continue;
+ }
+ $columnList[] = Coordinate::columnIndexFromString($c);
+ }
+
+ return Coordinate::stringFromColumnIndex(max($columnList));
+ }
+
+ /**
+ * Get highest worksheet row.
+ *
+ * @param string $column Return the highest row for the specified column,
+ * or the highest row of any column if no column letter is passed
+ *
+ * @return int Highest row number
+ */
+ public function getHighestRow($column = null)
+ {
+ if ($column === null) {
+ $colRow = $this->getHighestRowAndColumn();
+
+ return $colRow['row'];
+ }
+
+ $rowList = [0];
+ foreach ($this->getCoordinates() as $coord) {
+ $c = '';
+ $r = 0;
+
+ sscanf($coord, '%[A-Z]%d', $c, $r);
+ if ($c != $column) {
+ continue;
+ }
+ $rowList[] = $r;
+ }
+
+ return max($rowList);
+ }
+
+ /**
+ * Generate a unique ID for cache referencing.
+ *
+ * @return string Unique Reference
+ */
+ private function getUniqueID()
+ {
+ return uniqid('phpspreadsheet.', true) . '.';
+ }
+
+ /**
+ * Clone the cell collection.
+ *
+ * @param Worksheet $parent The new worksheet that we're copying to
+ *
+ * @return self
+ */
+ public function cloneCellCollection(Worksheet $parent)
+ {
+ $this->storeCurrentCell();
+ $newCollection = clone $this;
+
+ $newCollection->parent = $parent;
+ if (($newCollection->currentCell !== null) && (is_object($newCollection->currentCell))) {
+ $newCollection->currentCell->attach($this);
+ }
+
+ // Get old values
+ $oldKeys = $newCollection->getAllCacheKeys();
+ $oldValues = $newCollection->cache->getMultiple($oldKeys);
+ $newValues = [];
+ $oldCachePrefix = $newCollection->cachePrefix;
+
+ // Change prefix
+ $newCollection->cachePrefix = $newCollection->getUniqueID();
+ foreach ($oldValues as $oldKey => $value) {
+ $newValues[str_replace($oldCachePrefix, $newCollection->cachePrefix, $oldKey)] = clone $value;
+ }
+
+ // Store new values
+ $stored = $newCollection->cache->setMultiple($newValues);
+ if (!$stored) {
+ $newCollection->__destruct();
+
+ throw new PhpSpreadsheetException('Failed to copy cells in cache');
+ }
+
+ return $newCollection;
+ }
+
+ /**
+ * Remove a row, deleting all cells in that row.
+ *
+ * @param string $row Row number to remove
+ */
+ public function removeRow($row): void
+ {
+ foreach ($this->getCoordinates() as $coord) {
+ $c = '';
+ $r = 0;
+
+ sscanf($coord, '%[A-Z]%d', $c, $r);
+ if ($r == $row) {
+ $this->delete($coord);
+ }
+ }
+ }
+
+ /**
+ * Remove a column, deleting all cells in that column.
+ *
+ * @param string $column Column ID to remove
+ */
+ public function removeColumn($column): void
+ {
+ foreach ($this->getCoordinates() as $coord) {
+ $c = '';
+ $r = 0;
+
+ sscanf($coord, '%[A-Z]%d', $c, $r);
+ if ($c == $column) {
+ $this->delete($coord);
+ }
+ }
+ }
+
+ /**
+ * Store cell data in cache for the current cell object if it's "dirty",
+ * and the 'nullify' the current cell object.
+ */
+ private function storeCurrentCell(): void
+ {
+ if ($this->currentCellIsDirty && !empty($this->currentCoordinate)) {
+ $this->currentCell->detach();
+
+ $stored = $this->cache->set($this->cachePrefix . $this->currentCoordinate, $this->currentCell);
+ if (!$stored) {
+ $this->__destruct();
+
+ throw new PhpSpreadsheetException("Failed to store cell {$this->currentCoordinate} in cache");
+ }
+ $this->currentCellIsDirty = false;
+ }
+
+ $this->currentCoordinate = null;
+ $this->currentCell = null;
+ }
+
+ /**
+ * Add or update a cell identified by its coordinate into the collection.
+ *
+ * @param string $pCoord Coordinate of the cell to update
+ * @param Cell $cell Cell to update
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Cell\Cell
+ */
+ public function add($pCoord, Cell $cell)
+ {
+ if ($pCoord !== $this->currentCoordinate) {
+ $this->storeCurrentCell();
+ }
+ $this->index[$pCoord] = true;
+
+ $this->currentCoordinate = $pCoord;
+ $this->currentCell = $cell;
+ $this->currentCellIsDirty = true;
+
+ return $cell;
+ }
+
+ /**
+ * Get cell at a specific coordinate.
+ *
+ * @param string $pCoord Coordinate of the cell
+ *
+ * @return null|\PhpOffice\PhpSpreadsheet\Cell\Cell Cell that was found, or null if not found
+ */
+ public function get($pCoord)
+ {
+ if ($pCoord === $this->currentCoordinate) {
+ return $this->currentCell;
+ }
+ $this->storeCurrentCell();
+
+ // Return null if requested entry doesn't exist in collection
+ if (!$this->has($pCoord)) {
+ return null;
+ }
+
+ // Check if the entry that has been requested actually exists
+ $cell = $this->cache->get($this->cachePrefix . $pCoord);
+ if ($cell === null) {
+ throw new PhpSpreadsheetException("Cell entry {$pCoord} no longer exists in cache. This probably means that the cache was cleared by someone else.");
+ }
+
+ // Set current entry to the requested entry
+ $this->currentCoordinate = $pCoord;
+ $this->currentCell = $cell;
+ // Re-attach this as the cell's parent
+ $this->currentCell->attach($this);
+
+ // Return requested entry
+ return $this->currentCell;
+ }
+
+ /**
+ * Clear the cell collection and disconnect from our parent.
+ */
+ public function unsetWorksheetCells(): void
+ {
+ if ($this->currentCell !== null) {
+ $this->currentCell->detach();
+ $this->currentCell = null;
+ $this->currentCoordinate = null;
+ }
+
+ // Flush the cache
+ $this->__destruct();
+
+ $this->index = [];
+
+ // detach ourself from the worksheet, so that it can then delete this object successfully
+ $this->parent = null;
+ }
+
+ /**
+ * Destroy this cell collection.
+ */
+ public function __destruct()
+ {
+ $this->cache->deleteMultiple($this->getAllCacheKeys());
+ }
+
+ /**
+ * Returns all known cache keys.
+ *
+ * @return Generator|string[]
+ */
+ private function getAllCacheKeys()
+ {
+ foreach ($this->getCoordinates() as $coordinate) {
+ yield $this->cachePrefix . $coordinate;
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php
new file mode 100755
index 0000000..7f34c23
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Collection;
+
+use PhpOffice\PhpSpreadsheet\Settings;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+abstract class CellsFactory
+{
+ /**
+ * Initialise the cache storage.
+ *
+ * @param Worksheet $parent Enable cell caching for this worksheet
+ *
+ * @return Cells
+ * */
+ public static function getInstance(Worksheet $parent)
+ {
+ return new Cells($parent, Settings::getCache());
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory.php
new file mode 100755
index 0000000..a32551b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Collection;
+
+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 Memory implements CacheInterface
+{
+ private $cache = [];
+
+ public function clear()
+ {
+ $this->cache = [];
+
+ return true;
+ }
+
+ public function delete($key)
+ {
+ unset($this->cache[$key]);
+
+ return true;
+ }
+
+ public function deleteMultiple($keys)
+ {
+ foreach ($keys as $key) {
+ $this->delete($key);
+ }
+
+ return true;
+ }
+
+ public function get($key, $default = null)
+ {
+ if ($this->has($key)) {
+ return $this->cache[$key];
+ }
+
+ return $default;
+ }
+
+ public function getMultiple($keys, $default = null)
+ {
+ $results = [];
+ foreach ($keys as $key) {
+ $results[$key] = $this->get($key, $default);
+ }
+
+ return $results;
+ }
+
+ public function has($key)
+ {
+ return array_key_exists($key, $this->cache);
+ }
+
+ public function set($key, $value, $ttl = null)
+ {
+ $this->cache[$key] = $value;
+
+ return true;
+ }
+
+ public function setMultiple($values, $ttl = null)
+ {
+ foreach ($values as $key => $value) {
+ $this->set($key, $value);
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Comment.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Comment.php
new file mode 100755
index 0000000..31f7664
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Comment.php
@@ -0,0 +1,329 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet;
+
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+
+class Comment implements IComparable
+{
+ /**
+ * Author.
+ *
+ * @var string
+ */
+ private $author;
+
+ /**
+ * Rich text comment.
+ *
+ * @var RichText
+ */
+ private $text;
+
+ /**
+ * Comment width (CSS style, i.e. XXpx or YYpt).
+ *
+ * @var string
+ */
+ private $width = '96pt';
+
+ /**
+ * Left margin (CSS style, i.e. XXpx or YYpt).
+ *
+ * @var string
+ */
+ private $marginLeft = '59.25pt';
+
+ /**
+ * Top margin (CSS style, i.e. XXpx or YYpt).
+ *
+ * @var string
+ */
+ private $marginTop = '1.5pt';
+
+ /**
+ * Visible.
+ *
+ * @var bool
+ */
+ private $visible = false;
+
+ /**
+ * Comment height (CSS style, i.e. XXpx or YYpt).
+ *
+ * @var string
+ */
+ private $height = '55.5pt';
+
+ /**
+ * Comment fill color.
+ *
+ * @var Style\Color
+ */
+ private $fillColor;
+
+ /**
+ * Alignment.
+ *
+ * @var string
+ */
+ private $alignment;
+
+ /**
+ * Create a new Comment.
+ */
+ public function __construct()
+ {
+ // Initialise variables
+ $this->author = 'Author';
+ $this->text = new RichText();
+ $this->fillColor = new Style\Color('FFFFFFE1');
+ $this->alignment = Style\Alignment::HORIZONTAL_GENERAL;
+ }
+
+ /**
+ * Get Author.
+ *
+ * @return string
+ */
+ public function getAuthor()
+ {
+ return $this->author;
+ }
+
+ /**
+ * Set Author.
+ *
+ * @param string $author
+ *
+ * @return $this
+ */
+ public function setAuthor($author)
+ {
+ $this->author = $author;
+
+ return $this;
+ }
+
+ /**
+ * Get Rich text comment.
+ *
+ * @return RichText
+ */
+ public function getText()
+ {
+ return $this->text;
+ }
+
+ /**
+ * Set Rich text comment.
+ *
+ * @return $this
+ */
+ public function setText(RichText $pValue)
+ {
+ $this->text = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get comment width (CSS style, i.e. XXpx or YYpt).
+ *
+ * @return string
+ */
+ public function getWidth()
+ {
+ return $this->width;
+ }
+
+ /**
+ * Set comment width (CSS style, i.e. XXpx or YYpt).
+ *
+ * @param string $width
+ *
+ * @return $this
+ */
+ public function setWidth($width)
+ {
+ $this->width = $width;
+
+ return $this;
+ }
+
+ /**
+ * Get comment height (CSS style, i.e. XXpx or YYpt).
+ *
+ * @return string
+ */
+ public function getHeight()
+ {
+ return $this->height;
+ }
+
+ /**
+ * Set comment height (CSS style, i.e. XXpx or YYpt).
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setHeight($value)
+ {
+ $this->height = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get left margin (CSS style, i.e. XXpx or YYpt).
+ *
+ * @return string
+ */
+ public function getMarginLeft()
+ {
+ return $this->marginLeft;
+ }
+
+ /**
+ * Set left margin (CSS style, i.e. XXpx or YYpt).
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setMarginLeft($value)
+ {
+ $this->marginLeft = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get top margin (CSS style, i.e. XXpx or YYpt).
+ *
+ * @return string
+ */
+ public function getMarginTop()
+ {
+ return $this->marginTop;
+ }
+
+ /**
+ * Set top margin (CSS style, i.e. XXpx or YYpt).
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setMarginTop($value)
+ {
+ $this->marginTop = $value;
+
+ return $this;
+ }
+
+ /**
+ * Is the comment visible by default?
+ *
+ * @return bool
+ */
+ public function getVisible()
+ {
+ return $this->visible;
+ }
+
+ /**
+ * Set comment default visibility.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setVisible($value)
+ {
+ $this->visible = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get fill color.
+ *
+ * @return Style\Color
+ */
+ public function getFillColor()
+ {
+ return $this->fillColor;
+ }
+
+ /**
+ * Set Alignment.
+ *
+ * @param string $alignment see Style\Alignment::HORIZONTAL_*
+ *
+ * @return $this
+ */
+ public function setAlignment($alignment)
+ {
+ $this->alignment = $alignment;
+
+ return $this;
+ }
+
+ /**
+ * Get Alignment.
+ *
+ * @return string
+ */
+ public function getAlignment()
+ {
+ return $this->alignment;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->author .
+ $this->text->getHashCode() .
+ $this->width .
+ $this->height .
+ $this->marginLeft .
+ $this->marginTop .
+ ($this->visible ? 1 : 0) .
+ $this->fillColor->getHashCode() .
+ $this->alignment .
+ __CLASS__
+ );
+ }
+
+ /**
+ * 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ /**
+ * Convert to string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->text->getPlainText();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php
new file mode 100755
index 0000000..cbaae2d
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php
@@ -0,0 +1,629 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Document;
+
+class Properties
+{
+ /** constants */
+ const PROPERTY_TYPE_BOOLEAN = 'b';
+ const PROPERTY_TYPE_INTEGER = 'i';
+ const PROPERTY_TYPE_FLOAT = 'f';
+ const PROPERTY_TYPE_DATE = 'd';
+ const PROPERTY_TYPE_STRING = 's';
+ const PROPERTY_TYPE_UNKNOWN = 'u';
+
+ /**
+ * Creator.
+ *
+ * @var string
+ */
+ private $creator = 'Unknown Creator';
+
+ /**
+ * LastModifiedBy.
+ *
+ * @var string
+ */
+ private $lastModifiedBy;
+
+ /**
+ * Created.
+ *
+ * @var int
+ */
+ private $created;
+
+ /**
+ * Modified.
+ *
+ * @var int
+ */
+ private $modified;
+
+ /**
+ * Title.
+ *
+ * @var string
+ */
+ private $title = 'Untitled Spreadsheet';
+
+ /**
+ * Description.
+ *
+ * @var string
+ */
+ private $description = '';
+
+ /**
+ * Subject.
+ *
+ * @var string
+ */
+ private $subject = '';
+
+ /**
+ * Keywords.
+ *
+ * @var string
+ */
+ private $keywords = '';
+
+ /**
+ * Category.
+ *
+ * @var string
+ */
+ private $category = '';
+
+ /**
+ * Manager.
+ *
+ * @var string
+ */
+ private $manager = '';
+
+ /**
+ * Company.
+ *
+ * @var string
+ */
+ private $company = 'Microsoft Corporation';
+
+ /**
+ * Custom Properties.
+ *
+ * @var string
+ */
+ private $customProperties = [];
+
+ /**
+ * Create a new Document Properties instance.
+ */
+ public function __construct()
+ {
+ // Initialise values
+ $this->lastModifiedBy = $this->creator;
+ $this->created = time();
+ $this->modified = time();
+ }
+
+ /**
+ * Get Creator.
+ *
+ * @return string
+ */
+ public function getCreator()
+ {
+ return $this->creator;
+ }
+
+ /**
+ * Set Creator.
+ *
+ * @param string $creator
+ *
+ * @return $this
+ */
+ public function setCreator($creator)
+ {
+ $this->creator = $creator;
+
+ return $this;
+ }
+
+ /**
+ * Get Last Modified By.
+ *
+ * @return string
+ */
+ public function getLastModifiedBy()
+ {
+ return $this->lastModifiedBy;
+ }
+
+ /**
+ * Set Last Modified By.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setLastModifiedBy($pValue)
+ {
+ $this->lastModifiedBy = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Created.
+ *
+ * @return int
+ */
+ public function getCreated()
+ {
+ return $this->created;
+ }
+
+ /**
+ * Set Created.
+ *
+ * @param int|string $time
+ *
+ * @return $this
+ */
+ public function setCreated($time)
+ {
+ if ($time === null) {
+ $time = time();
+ } elseif (is_string($time)) {
+ if (is_numeric($time)) {
+ $time = (int) $time;
+ } else {
+ $time = strtotime($time);
+ }
+ }
+
+ $this->created = $time;
+
+ return $this;
+ }
+
+ /**
+ * Get Modified.
+ *
+ * @return int
+ */
+ public function getModified()
+ {
+ return $this->modified;
+ }
+
+ /**
+ * Set Modified.
+ *
+ * @param int|string $time
+ *
+ * @return $this
+ */
+ public function setModified($time)
+ {
+ if ($time === null) {
+ $time = time();
+ } elseif (is_string($time)) {
+ if (is_numeric($time)) {
+ $time = (int) $time;
+ } else {
+ $time = strtotime($time);
+ }
+ }
+
+ $this->modified = $time;
+
+ return $this;
+ }
+
+ /**
+ * Get Title.
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * Set Title.
+ *
+ * @param string $title
+ *
+ * @return $this
+ */
+ public function setTitle($title)
+ {
+ $this->title = $title;
+
+ return $this;
+ }
+
+ /**
+ * Get Description.
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * Set Description.
+ *
+ * @param string $description
+ *
+ * @return $this
+ */
+ public function setDescription($description)
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ /**
+ * Get Subject.
+ *
+ * @return string
+ */
+ public function getSubject()
+ {
+ return $this->subject;
+ }
+
+ /**
+ * Set Subject.
+ *
+ * @param string $subject
+ *
+ * @return $this
+ */
+ public function setSubject($subject)
+ {
+ $this->subject = $subject;
+
+ return $this;
+ }
+
+ /**
+ * Get Keywords.
+ *
+ * @return string
+ */
+ public function getKeywords()
+ {
+ return $this->keywords;
+ }
+
+ /**
+ * Set Keywords.
+ *
+ * @param string $keywords
+ *
+ * @return $this
+ */
+ public function setKeywords($keywords)
+ {
+ $this->keywords = $keywords;
+
+ return $this;
+ }
+
+ /**
+ * Get Category.
+ *
+ * @return string
+ */
+ public function getCategory()
+ {
+ return $this->category;
+ }
+
+ /**
+ * Set Category.
+ *
+ * @param string $category
+ *
+ * @return $this
+ */
+ public function setCategory($category)
+ {
+ $this->category = $category;
+
+ return $this;
+ }
+
+ /**
+ * Get Company.
+ *
+ * @return string
+ */
+ public function getCompany()
+ {
+ return $this->company;
+ }
+
+ /**
+ * Set Company.
+ *
+ * @param string $company
+ *
+ * @return $this
+ */
+ public function setCompany($company)
+ {
+ $this->company = $company;
+
+ return $this;
+ }
+
+ /**
+ * Get Manager.
+ *
+ * @return string
+ */
+ public function getManager()
+ {
+ return $this->manager;
+ }
+
+ /**
+ * Set Manager.
+ *
+ * @param string $manager
+ *
+ * @return $this
+ */
+ public function setManager($manager)
+ {
+ $this->manager = $manager;
+
+ return $this;
+ }
+
+ /**
+ * Get a List of Custom Property Names.
+ *
+ * @return array of string
+ */
+ public function getCustomProperties()
+ {
+ return array_keys($this->customProperties);
+ }
+
+ /**
+ * Check if a Custom Property is defined.
+ *
+ * @param string $propertyName
+ *
+ * @return bool
+ */
+ public function isCustomPropertySet($propertyName)
+ {
+ return isset($this->customProperties[$propertyName]);
+ }
+
+ /**
+ * Get a Custom Property Value.
+ *
+ * @param string $propertyName
+ *
+ * @return mixed
+ */
+ public function getCustomPropertyValue($propertyName)
+ {
+ if (isset($this->customProperties[$propertyName])) {
+ return $this->customProperties[$propertyName]['value'];
+ }
+ }
+
+ /**
+ * Get a Custom Property Type.
+ *
+ * @param string $propertyName
+ *
+ * @return string
+ */
+ public function getCustomPropertyType($propertyName)
+ {
+ if (isset($this->customProperties[$propertyName])) {
+ return $this->customProperties[$propertyName]['type'];
+ }
+ }
+
+ /**
+ * Set a Custom Property.
+ *
+ * @param string $propertyName
+ * @param mixed $propertyValue
+ * @param string $propertyType
+ * 'i' : Integer
+ * 'f' : Floating Point
+ * 's' : String
+ * 'd' : Date/Time
+ * 'b' : Boolean
+ *
+ * @return $this
+ */
+ public function setCustomProperty($propertyName, $propertyValue = '', $propertyType = null)
+ {
+ if (($propertyType === null) || (!in_array($propertyType, [self::PROPERTY_TYPE_INTEGER,
+ self::PROPERTY_TYPE_FLOAT,
+ self::PROPERTY_TYPE_STRING,
+ self::PROPERTY_TYPE_DATE,
+ self::PROPERTY_TYPE_BOOLEAN, ]))) {
+ if ($propertyValue === null) {
+ $propertyType = self::PROPERTY_TYPE_STRING;
+ } elseif (is_float($propertyValue)) {
+ $propertyType = self::PROPERTY_TYPE_FLOAT;
+ } elseif (is_int($propertyValue)) {
+ $propertyType = self::PROPERTY_TYPE_INTEGER;
+ } elseif (is_bool($propertyValue)) {
+ $propertyType = self::PROPERTY_TYPE_BOOLEAN;
+ } else {
+ $propertyType = self::PROPERTY_TYPE_STRING;
+ }
+ }
+
+ $this->customProperties[$propertyName] = [
+ 'value' => $propertyValue,
+ 'type' => $propertyType,
+ ];
+
+ 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ public static function convertProperty($propertyValue, $propertyType)
+ {
+ switch ($propertyType) {
+ case 'empty': // Empty
+ return '';
+
+ break;
+ case 'null': // Null
+ return null;
+
+ break;
+ case 'i1': // 1-Byte Signed Integer
+ case 'i2': // 2-Byte Signed Integer
+ case 'i4': // 4-Byte Signed Integer
+ case 'i8': // 8-Byte Signed Integer
+ case 'int': // Integer
+ return (int) $propertyValue;
+
+ break;
+ case 'ui1': // 1-Byte Unsigned Integer
+ case 'ui2': // 2-Byte Unsigned Integer
+ case 'ui4': // 4-Byte Unsigned Integer
+ case 'ui8': // 8-Byte Unsigned Integer
+ case 'uint': // Unsigned Integer
+ return abs((int) $propertyValue);
+
+ break;
+ case 'r4': // 4-Byte Real Number
+ case 'r8': // 8-Byte Real Number
+ case 'decimal': // Decimal
+ return (float) $propertyValue;
+
+ break;
+ case 'lpstr': // LPSTR
+ case 'lpwstr': // LPWSTR
+ case 'bstr': // Basic String
+ return $propertyValue;
+
+ break;
+ case 'date': // Date and Time
+ case 'filetime': // File Time
+ return strtotime($propertyValue);
+
+ break;
+ case 'bool': // Boolean
+ return $propertyValue == 'true';
+
+ break;
+ case 'cy': // Currency
+ case 'error': // Error Status Code
+ case 'vector': // Vector
+ case 'array': // Array
+ case 'blob': // Binary Blob
+ case 'oblob': // Binary Blob Object
+ case 'stream': // Binary Stream
+ case 'ostream': // Binary Stream Object
+ case 'storage': // Binary Storage
+ case 'ostorage': // Binary Storage Object
+ case 'vstream': // Binary Versioned Stream
+ case 'clsid': // Class ID
+ case 'cf': // Clipboard Data
+ return $propertyValue;
+
+ break;
+ }
+
+ return $propertyValue;
+ }
+
+ public static function convertPropertyType($propertyType)
+ {
+ switch ($propertyType) {
+ case 'i1': // 1-Byte Signed Integer
+ case 'i2': // 2-Byte Signed Integer
+ case 'i4': // 4-Byte Signed Integer
+ case 'i8': // 8-Byte Signed Integer
+ case 'int': // Integer
+ case 'ui1': // 1-Byte Unsigned Integer
+ case 'ui2': // 2-Byte Unsigned Integer
+ case 'ui4': // 4-Byte Unsigned Integer
+ case 'ui8': // 8-Byte Unsigned Integer
+ case 'uint': // Unsigned Integer
+ return self::PROPERTY_TYPE_INTEGER;
+
+ break;
+ case 'r4': // 4-Byte Real Number
+ case 'r8': // 8-Byte Real Number
+ case 'decimal': // Decimal
+ return self::PROPERTY_TYPE_FLOAT;
+
+ break;
+ case 'empty': // Empty
+ case 'null': // Null
+ case 'lpstr': // LPSTR
+ case 'lpwstr': // LPWSTR
+ case 'bstr': // Basic String
+ return self::PROPERTY_TYPE_STRING;
+
+ break;
+ case 'date': // Date and Time
+ case 'filetime': // File Time
+ return self::PROPERTY_TYPE_DATE;
+
+ break;
+ case 'bool': // Boolean
+ return self::PROPERTY_TYPE_BOOLEAN;
+
+ break;
+ case 'cy': // Currency
+ case 'error': // Error Status Code
+ case 'vector': // Vector
+ case 'array': // Array
+ case 'blob': // Binary Blob
+ case 'oblob': // Binary Blob Object
+ case 'stream': // Binary Stream
+ case 'ostream': // Binary Stream Object
+ case 'storage': // Binary Storage
+ case 'ostorage': // Binary Storage Object
+ case 'vstream': // Binary Versioned Stream
+ case 'clsid': // Class ID
+ case 'cf': // Clipboard Data
+ return self::PROPERTY_TYPE_UNKNOWN;
+
+ break;
+ }
+
+ return self::PROPERTY_TYPE_UNKNOWN;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php
new file mode 100755
index 0000000..cef3db8
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php
@@ -0,0 +1,205 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Document;
+
+use PhpOffice\PhpSpreadsheet\Shared\PasswordHasher;
+
+class Security
+{
+ /**
+ * LockRevision.
+ *
+ * @var bool
+ */
+ private $lockRevision = false;
+
+ /**
+ * LockStructure.
+ *
+ * @var bool
+ */
+ private $lockStructure = false;
+
+ /**
+ * LockWindows.
+ *
+ * @var bool
+ */
+ private $lockWindows = false;
+
+ /**
+ * RevisionsPassword.
+ *
+ * @var string
+ */
+ private $revisionsPassword = '';
+
+ /**
+ * WorkbookPassword.
+ *
+ * @var string
+ */
+ private $workbookPassword = '';
+
+ /**
+ * Create a new Document Security instance.
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * Is some sort of document security enabled?
+ *
+ * @return bool
+ */
+ public function isSecurityEnabled()
+ {
+ return $this->lockRevision ||
+ $this->lockStructure ||
+ $this->lockWindows;
+ }
+
+ /**
+ * Get LockRevision.
+ *
+ * @return bool
+ */
+ public function getLockRevision()
+ {
+ return $this->lockRevision;
+ }
+
+ /**
+ * Set LockRevision.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setLockRevision($pValue)
+ {
+ $this->lockRevision = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get LockStructure.
+ *
+ * @return bool
+ */
+ public function getLockStructure()
+ {
+ return $this->lockStructure;
+ }
+
+ /**
+ * Set LockStructure.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setLockStructure($pValue)
+ {
+ $this->lockStructure = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get LockWindows.
+ *
+ * @return bool
+ */
+ public function getLockWindows()
+ {
+ return $this->lockWindows;
+ }
+
+ /**
+ * Set LockWindows.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setLockWindows($pValue)
+ {
+ $this->lockWindows = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get RevisionsPassword (hashed).
+ *
+ * @return string
+ */
+ public function getRevisionsPassword()
+ {
+ return $this->revisionsPassword;
+ }
+
+ /**
+ * Set RevisionsPassword.
+ *
+ * @param string $pValue
+ * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
+ *
+ * @return $this
+ */
+ public function setRevisionsPassword($pValue, $pAlreadyHashed = false)
+ {
+ if (!$pAlreadyHashed) {
+ $pValue = PasswordHasher::hashPassword($pValue);
+ }
+ $this->revisionsPassword = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get WorkbookPassword (hashed).
+ *
+ * @return string
+ */
+ public function getWorkbookPassword()
+ {
+ return $this->workbookPassword;
+ }
+
+ /**
+ * Set WorkbookPassword.
+ *
+ * @param string $pValue
+ * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
+ *
+ * @return $this
+ */
+ public function setWorkbookPassword($pValue, $pAlreadyHashed = false)
+ {
+ if (!$pAlreadyHashed) {
+ $pValue = PasswordHasher::hashPassword($pValue);
+ }
+ $this->workbookPassword = $pValue;
+
+ 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/DocumentGenerator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/DocumentGenerator.php
new file mode 100755
index 0000000..5e06af9
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/DocumentGenerator.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Category;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use ReflectionClass;
+use UnexpectedValueException;
+
+class DocumentGenerator
+{
+ /**
+ * @param array[] $phpSpreadsheetFunctions
+ */
+ public static function generateFunctionListByCategory(array $phpSpreadsheetFunctions): string
+ {
+ $result = "# Function list by category\n";
+ foreach (self::getCategories() as $categoryConstant => $category) {
+ $result .= "\n";
+ $result .= "## {$categoryConstant}\n";
+ $result .= "\n";
+ $lengths = [20, 42];
+ $result .= self::tableRow($lengths, ['Excel Function', 'PhpSpreadsheet Function']) . "\n";
+ $result .= self::tableRow($lengths, null) . "\n";
+ foreach ($phpSpreadsheetFunctions as $excelFunction => $functionInfo) {
+ if ($category === $functionInfo['category']) {
+ $phpFunction = self::getPhpSpreadsheetFunctionText($functionInfo['functionCall']);
+ $result .= self::tableRow($lengths, [$excelFunction, $phpFunction]) . "\n";
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ private static function getCategories(): array
+ {
+ return (new ReflectionClass(Category::class))->getConstants();
+ }
+
+ private static function tableRow(array $lengths, ?array $values = null): string
+ {
+ $result = '';
+ foreach (array_map(null, $lengths, $values ?? []) as $i => [$length, $value]) {
+ $pad = $value === null ? '-' : ' ';
+ if ($i > 0) {
+ $result .= '|' . $pad;
+ }
+ $result .= str_pad($value ?? '', $length, $pad);
+ }
+
+ return rtrim($result, ' ');
+ }
+
+ private static function getPhpSpreadsheetFunctionText($functionCall): string
+ {
+ if (is_string($functionCall)) {
+ return $functionCall;
+ }
+ if ($functionCall === [Functions::class, 'DUMMY']) {
+ return '**Not yet Implemented**';
+ }
+ if (is_array($functionCall)) {
+ return "\\{$functionCall[0]}::{$functionCall[1]}";
+ }
+
+ throw new UnexpectedValueException(
+ '$functionCall is of type ' . gettype($functionCall) . '. string or array expected'
+ );
+ }
+
+ /**
+ * @param array[] $phpSpreadsheetFunctions
+ */
+ public static function generateFunctionListByName(array $phpSpreadsheetFunctions): string
+ {
+ $categoryConstants = array_flip(self::getCategories());
+ $result = "# Function list by name\n";
+ $lastAlphabet = null;
+ foreach ($phpSpreadsheetFunctions as $excelFunction => $functionInfo) {
+ $lengths = [20, 31, 42];
+ if ($lastAlphabet !== $excelFunction[0]) {
+ $lastAlphabet = $excelFunction[0];
+ $result .= "\n";
+ $result .= "## {$lastAlphabet}\n";
+ $result .= "\n";
+ $result .= self::tableRow($lengths, ['Excel Function', 'Category', 'PhpSpreadsheet Function']) . "\n";
+ $result .= self::tableRow($lengths, null) . "\n";
+ }
+ $category = $categoryConstants[$functionInfo['category']];
+ $phpFunction = self::getPhpSpreadsheetFunctionText($functionInfo['functionCall']);
+ $result .= self::tableRow($lengths, [$excelFunction, $category, $phpFunction]) . "\n";
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php
new file mode 100755
index 0000000..9c5ab30
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet;
+
+class Exception extends \Exception
+{
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/HashTable.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/HashTable.php
new file mode 100755
index 0000000..90ea806
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/HashTable.php
@@ -0,0 +1,175 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet;
+
+class HashTable
+{
+ /**
+ * HashTable elements.
+ *
+ * @var IComparable[]
+ */
+ protected $items = [];
+
+ /**
+ * HashTable key map.
+ *
+ * @var string[]
+ */
+ protected $keyMap = [];
+
+ /**
+ * Create a new \PhpOffice\PhpSpreadsheet\HashTable.
+ *
+ * @param IComparable[] $pSource Optional source array to create HashTable from
+ */
+ public function __construct($pSource = null)
+ {
+ if ($pSource !== null) {
+ // Create HashTable
+ $this->addFromSource($pSource);
+ }
+ }
+
+ /**
+ * Add HashTable items from source.
+ *
+ * @param IComparable[] $pSource Source array to create HashTable from
+ */
+ public function addFromSource(?array $pSource = null): void
+ {
+ // Check if an array was passed
+ if ($pSource == null) {
+ return;
+ }
+
+ foreach ($pSource as $item) {
+ $this->add($item);
+ }
+ }
+
+ /**
+ * Add HashTable item.
+ *
+ * @param IComparable $pSource Item to add
+ */
+ public function add(IComparable $pSource): void
+ {
+ $hash = $pSource->getHashCode();
+ if (!isset($this->items[$hash])) {
+ $this->items[$hash] = $pSource;
+ $this->keyMap[count($this->items) - 1] = $hash;
+ }
+ }
+
+ /**
+ * Remove HashTable item.
+ *
+ * @param IComparable $pSource Item to remove
+ */
+ public function remove(IComparable $pSource): void
+ {
+ $hash = $pSource->getHashCode();
+ if (isset($this->items[$hash])) {
+ unset($this->items[$hash]);
+
+ $deleteKey = -1;
+ foreach ($this->keyMap as $key => $value) {
+ if ($deleteKey >= 0) {
+ $this->keyMap[$key - 1] = $value;
+ }
+
+ if ($value == $hash) {
+ $deleteKey = $key;
+ }
+ }
+ unset($this->keyMap[count($this->keyMap) - 1]);
+ }
+ }
+
+ /**
+ * Clear HashTable.
+ */
+ public function clear(): void
+ {
+ $this->items = [];
+ $this->keyMap = [];
+ }
+
+ /**
+ * Count.
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->items);
+ }
+
+ /**
+ * Get index for hash code.
+ *
+ * @param string $pHashCode
+ *
+ * @return int Index
+ */
+ public function getIndexForHashCode($pHashCode)
+ {
+ return array_search($pHashCode, $this->keyMap);
+ }
+
+ /**
+ * Get by index.
+ *
+ * @param int $pIndex
+ *
+ * @return IComparable
+ */
+ public function getByIndex($pIndex)
+ {
+ if (isset($this->keyMap[$pIndex])) {
+ return $this->getByHashCode($this->keyMap[$pIndex]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Get by hashcode.
+ *
+ * @param string $pHashCode
+ *
+ * @return IComparable
+ */
+ public function getByHashCode($pHashCode)
+ {
+ if (isset($this->items[$pHashCode])) {
+ return $this->items[$pHashCode];
+ }
+
+ return null;
+ }
+
+ /**
+ * HashTable to array.
+ *
+ * @return IComparable[]
+ */
+ public function toArray()
+ {
+ return $this->items;
+ }
+
+ /**
+ * 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)) {
+ $this->$key = clone $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php
new file mode 100755
index 0000000..252e14a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php
@@ -0,0 +1,839 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Helper;
+
+use DOMDocument;
+use DOMElement;
+use DOMNode;
+use DOMText;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Style\Color;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+
+class Html
+{
+ protected static $colourMap = [
+ 'aliceblue' => 'f0f8ff',
+ 'antiquewhite' => 'faebd7',
+ 'antiquewhite1' => 'ffefdb',
+ 'antiquewhite2' => 'eedfcc',
+ 'antiquewhite3' => 'cdc0b0',
+ 'antiquewhite4' => '8b8378',
+ 'aqua' => '00ffff',
+ 'aquamarine1' => '7fffd4',
+ 'aquamarine2' => '76eec6',
+ 'aquamarine4' => '458b74',
+ 'azure1' => 'f0ffff',
+ 'azure2' => 'e0eeee',
+ 'azure3' => 'c1cdcd',
+ 'azure4' => '838b8b',
+ 'beige' => 'f5f5dc',
+ 'bisque1' => 'ffe4c4',
+ 'bisque2' => 'eed5b7',
+ 'bisque3' => 'cdb79e',
+ 'bisque4' => '8b7d6b',
+ 'black' => '000000',
+ 'blanchedalmond' => 'ffebcd',
+ 'blue' => '0000ff',
+ 'blue1' => '0000ff',
+ 'blue2' => '0000ee',
+ 'blue4' => '00008b',
+ 'blueviolet' => '8a2be2',
+ 'brown' => 'a52a2a',
+ 'brown1' => 'ff4040',
+ 'brown2' => 'ee3b3b',
+ 'brown3' => 'cd3333',
+ 'brown4' => '8b2323',
+ 'burlywood' => 'deb887',
+ 'burlywood1' => 'ffd39b',
+ 'burlywood2' => 'eec591',
+ 'burlywood3' => 'cdaa7d',
+ 'burlywood4' => '8b7355',
+ 'cadetblue' => '5f9ea0',
+ 'cadetblue1' => '98f5ff',
+ 'cadetblue2' => '8ee5ee',
+ 'cadetblue3' => '7ac5cd',
+ 'cadetblue4' => '53868b',
+ 'chartreuse1' => '7fff00',
+ 'chartreuse2' => '76ee00',
+ 'chartreuse3' => '66cd00',
+ 'chartreuse4' => '458b00',
+ 'chocolate' => 'd2691e',
+ 'chocolate1' => 'ff7f24',
+ 'chocolate2' => 'ee7621',
+ 'chocolate3' => 'cd661d',
+ 'coral' => 'ff7f50',
+ 'coral1' => 'ff7256',
+ 'coral2' => 'ee6a50',
+ 'coral3' => 'cd5b45',
+ 'coral4' => '8b3e2f',
+ 'cornflowerblue' => '6495ed',
+ 'cornsilk1' => 'fff8dc',
+ 'cornsilk2' => 'eee8cd',
+ 'cornsilk3' => 'cdc8b1',
+ 'cornsilk4' => '8b8878',
+ 'cyan1' => '00ffff',
+ 'cyan2' => '00eeee',
+ 'cyan3' => '00cdcd',
+ 'cyan4' => '008b8b',
+ 'darkgoldenrod' => 'b8860b',
+ 'darkgoldenrod1' => 'ffb90f',
+ 'darkgoldenrod2' => 'eead0e',
+ 'darkgoldenrod3' => 'cd950c',
+ 'darkgoldenrod4' => '8b6508',
+ 'darkgreen' => '006400',
+ 'darkkhaki' => 'bdb76b',
+ 'darkolivegreen' => '556b2f',
+ 'darkolivegreen1' => 'caff70',
+ 'darkolivegreen2' => 'bcee68',
+ 'darkolivegreen3' => 'a2cd5a',
+ 'darkolivegreen4' => '6e8b3d',
+ 'darkorange' => 'ff8c00',
+ 'darkorange1' => 'ff7f00',
+ 'darkorange2' => 'ee7600',
+ 'darkorange3' => 'cd6600',
+ 'darkorange4' => '8b4500',
+ 'darkorchid' => '9932cc',
+ 'darkorchid1' => 'bf3eff',
+ 'darkorchid2' => 'b23aee',
+ 'darkorchid3' => '9a32cd',
+ 'darkorchid4' => '68228b',
+ 'darksalmon' => 'e9967a',
+ 'darkseagreen' => '8fbc8f',
+ 'darkseagreen1' => 'c1ffc1',
+ 'darkseagreen2' => 'b4eeb4',
+ 'darkseagreen3' => '9bcd9b',
+ 'darkseagreen4' => '698b69',
+ 'darkslateblue' => '483d8b',
+ 'darkslategray' => '2f4f4f',
+ 'darkslategray1' => '97ffff',
+ 'darkslategray2' => '8deeee',
+ 'darkslategray3' => '79cdcd',
+ 'darkslategray4' => '528b8b',
+ 'darkturquoise' => '00ced1',
+ 'darkviolet' => '9400d3',
+ 'deeppink1' => 'ff1493',
+ 'deeppink2' => 'ee1289',
+ 'deeppink3' => 'cd1076',
+ 'deeppink4' => '8b0a50',
+ 'deepskyblue1' => '00bfff',
+ 'deepskyblue2' => '00b2ee',
+ 'deepskyblue3' => '009acd',
+ 'deepskyblue4' => '00688b',
+ 'dimgray' => '696969',
+ 'dodgerblue1' => '1e90ff',
+ 'dodgerblue2' => '1c86ee',
+ 'dodgerblue3' => '1874cd',
+ 'dodgerblue4' => '104e8b',
+ 'firebrick' => 'b22222',
+ 'firebrick1' => 'ff3030',
+ 'firebrick2' => 'ee2c2c',
+ 'firebrick3' => 'cd2626',
+ 'firebrick4' => '8b1a1a',
+ 'floralwhite' => 'fffaf0',
+ 'forestgreen' => '228b22',
+ 'fuchsia' => 'ff00ff',
+ 'gainsboro' => 'dcdcdc',
+ 'ghostwhite' => 'f8f8ff',
+ 'gold1' => 'ffd700',
+ 'gold2' => 'eec900',
+ 'gold3' => 'cdad00',
+ 'gold4' => '8b7500',
+ 'goldenrod' => 'daa520',
+ 'goldenrod1' => 'ffc125',
+ 'goldenrod2' => 'eeb422',
+ 'goldenrod3' => 'cd9b1d',
+ 'goldenrod4' => '8b6914',
+ 'gray' => 'bebebe',
+ 'gray1' => '030303',
+ 'gray10' => '1a1a1a',
+ 'gray11' => '1c1c1c',
+ 'gray12' => '1f1f1f',
+ 'gray13' => '212121',
+ 'gray14' => '242424',
+ 'gray15' => '262626',
+ 'gray16' => '292929',
+ 'gray17' => '2b2b2b',
+ 'gray18' => '2e2e2e',
+ 'gray19' => '303030',
+ 'gray2' => '050505',
+ 'gray20' => '333333',
+ 'gray21' => '363636',
+ 'gray22' => '383838',
+ 'gray23' => '3b3b3b',
+ 'gray24' => '3d3d3d',
+ 'gray25' => '404040',
+ 'gray26' => '424242',
+ 'gray27' => '454545',
+ 'gray28' => '474747',
+ 'gray29' => '4a4a4a',
+ 'gray3' => '080808',
+ 'gray30' => '4d4d4d',
+ 'gray31' => '4f4f4f',
+ 'gray32' => '525252',
+ 'gray33' => '545454',
+ 'gray34' => '575757',
+ 'gray35' => '595959',
+ 'gray36' => '5c5c5c',
+ 'gray37' => '5e5e5e',
+ 'gray38' => '616161',
+ 'gray39' => '636363',
+ 'gray4' => '0a0a0a',
+ 'gray40' => '666666',
+ 'gray41' => '696969',
+ 'gray42' => '6b6b6b',
+ 'gray43' => '6e6e6e',
+ 'gray44' => '707070',
+ 'gray45' => '737373',
+ 'gray46' => '757575',
+ 'gray47' => '787878',
+ 'gray48' => '7a7a7a',
+ 'gray49' => '7d7d7d',
+ 'gray5' => '0d0d0d',
+ 'gray50' => '7f7f7f',
+ 'gray51' => '828282',
+ 'gray52' => '858585',
+ 'gray53' => '878787',
+ 'gray54' => '8a8a8a',
+ 'gray55' => '8c8c8c',
+ 'gray56' => '8f8f8f',
+ 'gray57' => '919191',
+ 'gray58' => '949494',
+ 'gray59' => '969696',
+ 'gray6' => '0f0f0f',
+ 'gray60' => '999999',
+ 'gray61' => '9c9c9c',
+ 'gray62' => '9e9e9e',
+ 'gray63' => 'a1a1a1',
+ 'gray64' => 'a3a3a3',
+ 'gray65' => 'a6a6a6',
+ 'gray66' => 'a8a8a8',
+ 'gray67' => 'ababab',
+ 'gray68' => 'adadad',
+ 'gray69' => 'b0b0b0',
+ 'gray7' => '121212',
+ 'gray70' => 'b3b3b3',
+ 'gray71' => 'b5b5b5',
+ 'gray72' => 'b8b8b8',
+ 'gray73' => 'bababa',
+ 'gray74' => 'bdbdbd',
+ 'gray75' => 'bfbfbf',
+ 'gray76' => 'c2c2c2',
+ 'gray77' => 'c4c4c4',
+ 'gray78' => 'c7c7c7',
+ 'gray79' => 'c9c9c9',
+ 'gray8' => '141414',
+ 'gray80' => 'cccccc',
+ 'gray81' => 'cfcfcf',
+ 'gray82' => 'd1d1d1',
+ 'gray83' => 'd4d4d4',
+ 'gray84' => 'd6d6d6',
+ 'gray85' => 'd9d9d9',
+ 'gray86' => 'dbdbdb',
+ 'gray87' => 'dedede',
+ 'gray88' => 'e0e0e0',
+ 'gray89' => 'e3e3e3',
+ 'gray9' => '171717',
+ 'gray90' => 'e5e5e5',
+ 'gray91' => 'e8e8e8',
+ 'gray92' => 'ebebeb',
+ 'gray93' => 'ededed',
+ 'gray94' => 'f0f0f0',
+ 'gray95' => 'f2f2f2',
+ 'gray97' => 'f7f7f7',
+ 'gray98' => 'fafafa',
+ 'gray99' => 'fcfcfc',
+ 'green' => '00ff00',
+ 'green1' => '00ff00',
+ 'green2' => '00ee00',
+ 'green3' => '00cd00',
+ 'green4' => '008b00',
+ 'greenyellow' => 'adff2f',
+ 'honeydew1' => 'f0fff0',
+ 'honeydew2' => 'e0eee0',
+ 'honeydew3' => 'c1cdc1',
+ 'honeydew4' => '838b83',
+ 'hotpink' => 'ff69b4',
+ 'hotpink1' => 'ff6eb4',
+ 'hotpink2' => 'ee6aa7',
+ 'hotpink3' => 'cd6090',
+ 'hotpink4' => '8b3a62',
+ 'indianred' => 'cd5c5c',
+ 'indianred1' => 'ff6a6a',
+ 'indianred2' => 'ee6363',
+ 'indianred3' => 'cd5555',
+ 'indianred4' => '8b3a3a',
+ 'ivory1' => 'fffff0',
+ 'ivory2' => 'eeeee0',
+ 'ivory3' => 'cdcdc1',
+ 'ivory4' => '8b8b83',
+ 'khaki' => 'f0e68c',
+ 'khaki1' => 'fff68f',
+ 'khaki2' => 'eee685',
+ 'khaki3' => 'cdc673',
+ 'khaki4' => '8b864e',
+ 'lavender' => 'e6e6fa',
+ 'lavenderblush1' => 'fff0f5',
+ 'lavenderblush2' => 'eee0e5',
+ 'lavenderblush3' => 'cdc1c5',
+ 'lavenderblush4' => '8b8386',
+ 'lawngreen' => '7cfc00',
+ 'lemonchiffon1' => 'fffacd',
+ 'lemonchiffon2' => 'eee9bf',
+ 'lemonchiffon3' => 'cdc9a5',
+ 'lemonchiffon4' => '8b8970',
+ 'light' => 'eedd82',
+ 'lightblue' => 'add8e6',
+ 'lightblue1' => 'bfefff',
+ 'lightblue2' => 'b2dfee',
+ 'lightblue3' => '9ac0cd',
+ 'lightblue4' => '68838b',
+ 'lightcoral' => 'f08080',
+ 'lightcyan1' => 'e0ffff',
+ 'lightcyan2' => 'd1eeee',
+ 'lightcyan3' => 'b4cdcd',
+ 'lightcyan4' => '7a8b8b',
+ 'lightgoldenrod1' => 'ffec8b',
+ 'lightgoldenrod2' => 'eedc82',
+ 'lightgoldenrod3' => 'cdbe70',
+ 'lightgoldenrod4' => '8b814c',
+ 'lightgoldenrodyellow' => 'fafad2',
+ 'lightgray' => 'd3d3d3',
+ 'lightpink' => 'ffb6c1',
+ 'lightpink1' => 'ffaeb9',
+ 'lightpink2' => 'eea2ad',
+ 'lightpink3' => 'cd8c95',
+ 'lightpink4' => '8b5f65',
+ 'lightsalmon1' => 'ffa07a',
+ 'lightsalmon2' => 'ee9572',
+ 'lightsalmon3' => 'cd8162',
+ 'lightsalmon4' => '8b5742',
+ 'lightseagreen' => '20b2aa',
+ 'lightskyblue' => '87cefa',
+ 'lightskyblue1' => 'b0e2ff',
+ 'lightskyblue2' => 'a4d3ee',
+ 'lightskyblue3' => '8db6cd',
+ 'lightskyblue4' => '607b8b',
+ 'lightslateblue' => '8470ff',
+ 'lightslategray' => '778899',
+ 'lightsteelblue' => 'b0c4de',
+ 'lightsteelblue1' => 'cae1ff',
+ 'lightsteelblue2' => 'bcd2ee',
+ 'lightsteelblue3' => 'a2b5cd',
+ 'lightsteelblue4' => '6e7b8b',
+ 'lightyellow1' => 'ffffe0',
+ 'lightyellow2' => 'eeeed1',
+ 'lightyellow3' => 'cdcdb4',
+ 'lightyellow4' => '8b8b7a',
+ 'lime' => '00ff00',
+ 'limegreen' => '32cd32',
+ 'linen' => 'faf0e6',
+ 'magenta' => 'ff00ff',
+ 'magenta2' => 'ee00ee',
+ 'magenta3' => 'cd00cd',
+ 'magenta4' => '8b008b',
+ 'maroon' => 'b03060',
+ 'maroon1' => 'ff34b3',
+ 'maroon2' => 'ee30a7',
+ 'maroon3' => 'cd2990',
+ 'maroon4' => '8b1c62',
+ 'medium' => '66cdaa',
+ 'mediumaquamarine' => '66cdaa',
+ 'mediumblue' => '0000cd',
+ 'mediumorchid' => 'ba55d3',
+ 'mediumorchid1' => 'e066ff',
+ 'mediumorchid2' => 'd15fee',
+ 'mediumorchid3' => 'b452cd',
+ 'mediumorchid4' => '7a378b',
+ 'mediumpurple' => '9370db',
+ 'mediumpurple1' => 'ab82ff',
+ 'mediumpurple2' => '9f79ee',
+ 'mediumpurple3' => '8968cd',
+ 'mediumpurple4' => '5d478b',
+ 'mediumseagreen' => '3cb371',
+ 'mediumslateblue' => '7b68ee',
+ 'mediumspringgreen' => '00fa9a',
+ 'mediumturquoise' => '48d1cc',
+ 'mediumvioletred' => 'c71585',
+ 'midnightblue' => '191970',
+ 'mintcream' => 'f5fffa',
+ 'mistyrose1' => 'ffe4e1',
+ 'mistyrose2' => 'eed5d2',
+ 'mistyrose3' => 'cdb7b5',
+ 'mistyrose4' => '8b7d7b',
+ 'moccasin' => 'ffe4b5',
+ 'navajowhite1' => 'ffdead',
+ 'navajowhite2' => 'eecfa1',
+ 'navajowhite3' => 'cdb38b',
+ 'navajowhite4' => '8b795e',
+ 'navy' => '000080',
+ 'navyblue' => '000080',
+ 'oldlace' => 'fdf5e6',
+ 'olive' => '808000',
+ 'olivedrab' => '6b8e23',
+ 'olivedrab1' => 'c0ff3e',
+ 'olivedrab2' => 'b3ee3a',
+ 'olivedrab4' => '698b22',
+ 'orange' => 'ffa500',
+ 'orange1' => 'ffa500',
+ 'orange2' => 'ee9a00',
+ 'orange3' => 'cd8500',
+ 'orange4' => '8b5a00',
+ 'orangered1' => 'ff4500',
+ 'orangered2' => 'ee4000',
+ 'orangered3' => 'cd3700',
+ 'orangered4' => '8b2500',
+ 'orchid' => 'da70d6',
+ 'orchid1' => 'ff83fa',
+ 'orchid2' => 'ee7ae9',
+ 'orchid3' => 'cd69c9',
+ 'orchid4' => '8b4789',
+ 'pale' => 'db7093',
+ 'palegoldenrod' => 'eee8aa',
+ 'palegreen' => '98fb98',
+ 'palegreen1' => '9aff9a',
+ 'palegreen2' => '90ee90',
+ 'palegreen3' => '7ccd7c',
+ 'palegreen4' => '548b54',
+ 'paleturquoise' => 'afeeee',
+ 'paleturquoise1' => 'bbffff',
+ 'paleturquoise2' => 'aeeeee',
+ 'paleturquoise3' => '96cdcd',
+ 'paleturquoise4' => '668b8b',
+ 'palevioletred' => 'db7093',
+ 'palevioletred1' => 'ff82ab',
+ 'palevioletred2' => 'ee799f',
+ 'palevioletred3' => 'cd6889',
+ 'palevioletred4' => '8b475d',
+ 'papayawhip' => 'ffefd5',
+ 'peachpuff1' => 'ffdab9',
+ 'peachpuff2' => 'eecbad',
+ 'peachpuff3' => 'cdaf95',
+ 'peachpuff4' => '8b7765',
+ 'pink' => 'ffc0cb',
+ 'pink1' => 'ffb5c5',
+ 'pink2' => 'eea9b8',
+ 'pink3' => 'cd919e',
+ 'pink4' => '8b636c',
+ 'plum' => 'dda0dd',
+ 'plum1' => 'ffbbff',
+ 'plum2' => 'eeaeee',
+ 'plum3' => 'cd96cd',
+ 'plum4' => '8b668b',
+ 'powderblue' => 'b0e0e6',
+ 'purple' => 'a020f0',
+ 'rebeccapurple' => '663399',
+ 'purple1' => '9b30ff',
+ 'purple2' => '912cee',
+ 'purple3' => '7d26cd',
+ 'purple4' => '551a8b',
+ 'red' => 'ff0000',
+ 'red1' => 'ff0000',
+ 'red2' => 'ee0000',
+ 'red3' => 'cd0000',
+ 'red4' => '8b0000',
+ 'rosybrown' => 'bc8f8f',
+ 'rosybrown1' => 'ffc1c1',
+ 'rosybrown2' => 'eeb4b4',
+ 'rosybrown3' => 'cd9b9b',
+ 'rosybrown4' => '8b6969',
+ 'royalblue' => '4169e1',
+ 'royalblue1' => '4876ff',
+ 'royalblue2' => '436eee',
+ 'royalblue3' => '3a5fcd',
+ 'royalblue4' => '27408b',
+ 'saddlebrown' => '8b4513',
+ 'salmon' => 'fa8072',
+ 'salmon1' => 'ff8c69',
+ 'salmon2' => 'ee8262',
+ 'salmon3' => 'cd7054',
+ 'salmon4' => '8b4c39',
+ 'sandybrown' => 'f4a460',
+ 'seagreen1' => '54ff9f',
+ 'seagreen2' => '4eee94',
+ 'seagreen3' => '43cd80',
+ 'seagreen4' => '2e8b57',
+ 'seashell1' => 'fff5ee',
+ 'seashell2' => 'eee5de',
+ 'seashell3' => 'cdc5bf',
+ 'seashell4' => '8b8682',
+ 'sienna' => 'a0522d',
+ 'sienna1' => 'ff8247',
+ 'sienna2' => 'ee7942',
+ 'sienna3' => 'cd6839',
+ 'sienna4' => '8b4726',
+ 'silver' => 'c0c0c0',
+ 'skyblue' => '87ceeb',
+ 'skyblue1' => '87ceff',
+ 'skyblue2' => '7ec0ee',
+ 'skyblue3' => '6ca6cd',
+ 'skyblue4' => '4a708b',
+ 'slateblue' => '6a5acd',
+ 'slateblue1' => '836fff',
+ 'slateblue2' => '7a67ee',
+ 'slateblue3' => '6959cd',
+ 'slateblue4' => '473c8b',
+ 'slategray' => '708090',
+ 'slategray1' => 'c6e2ff',
+ 'slategray2' => 'b9d3ee',
+ 'slategray3' => '9fb6cd',
+ 'slategray4' => '6c7b8b',
+ 'snow1' => 'fffafa',
+ 'snow2' => 'eee9e9',
+ 'snow3' => 'cdc9c9',
+ 'snow4' => '8b8989',
+ 'springgreen1' => '00ff7f',
+ 'springgreen2' => '00ee76',
+ 'springgreen3' => '00cd66',
+ 'springgreen4' => '008b45',
+ 'steelblue' => '4682b4',
+ 'steelblue1' => '63b8ff',
+ 'steelblue2' => '5cacee',
+ 'steelblue3' => '4f94cd',
+ 'steelblue4' => '36648b',
+ 'tan' => 'd2b48c',
+ 'tan1' => 'ffa54f',
+ 'tan2' => 'ee9a49',
+ 'tan3' => 'cd853f',
+ 'tan4' => '8b5a2b',
+ 'teal' => '008080',
+ 'thistle' => 'd8bfd8',
+ 'thistle1' => 'ffe1ff',
+ 'thistle2' => 'eed2ee',
+ 'thistle3' => 'cdb5cd',
+ 'thistle4' => '8b7b8b',
+ 'tomato1' => 'ff6347',
+ 'tomato2' => 'ee5c42',
+ 'tomato3' => 'cd4f39',
+ 'tomato4' => '8b3626',
+ 'turquoise' => '40e0d0',
+ 'turquoise1' => '00f5ff',
+ 'turquoise2' => '00e5ee',
+ 'turquoise3' => '00c5cd',
+ 'turquoise4' => '00868b',
+ 'violet' => 'ee82ee',
+ 'violetred' => 'd02090',
+ 'violetred1' => 'ff3e96',
+ 'violetred2' => 'ee3a8c',
+ 'violetred3' => 'cd3278',
+ 'violetred4' => '8b2252',
+ 'wheat' => 'f5deb3',
+ 'wheat1' => 'ffe7ba',
+ 'wheat2' => 'eed8ae',
+ 'wheat3' => 'cdba96',
+ 'wheat4' => '8b7e66',
+ 'white' => 'ffffff',
+ 'whitesmoke' => 'f5f5f5',
+ 'yellow' => 'ffff00',
+ 'yellow1' => 'ffff00',
+ 'yellow2' => 'eeee00',
+ 'yellow3' => 'cdcd00',
+ 'yellow4' => '8b8b00',
+ 'yellowgreen' => '9acd32',
+ ];
+
+ protected $face;
+
+ protected $size;
+
+ protected $color;
+
+ protected $bold = false;
+
+ protected $italic = false;
+
+ protected $underline = false;
+
+ protected $superscript = false;
+
+ protected $subscript = false;
+
+ protected $strikethrough = false;
+
+ protected $startTagCallbacks = [
+ 'font' => 'startFontTag',
+ 'b' => 'startBoldTag',
+ 'strong' => 'startBoldTag',
+ 'i' => 'startItalicTag',
+ 'em' => 'startItalicTag',
+ 'u' => 'startUnderlineTag',
+ 'ins' => 'startUnderlineTag',
+ 'del' => 'startStrikethruTag',
+ 'sup' => 'startSuperscriptTag',
+ 'sub' => 'startSubscriptTag',
+ ];
+
+ protected $endTagCallbacks = [
+ 'font' => 'endFontTag',
+ 'b' => 'endBoldTag',
+ 'strong' => 'endBoldTag',
+ 'i' => 'endItalicTag',
+ 'em' => 'endItalicTag',
+ 'u' => 'endUnderlineTag',
+ 'ins' => 'endUnderlineTag',
+ 'del' => 'endStrikethruTag',
+ 'sup' => 'endSuperscriptTag',
+ 'sub' => 'endSubscriptTag',
+ 'br' => 'breakTag',
+ 'p' => 'breakTag',
+ 'h1' => 'breakTag',
+ 'h2' => 'breakTag',
+ 'h3' => 'breakTag',
+ 'h4' => 'breakTag',
+ 'h5' => 'breakTag',
+ 'h6' => 'breakTag',
+ ];
+
+ protected $stack = [];
+
+ protected $stringData = '';
+
+ /**
+ * @var RichText
+ */
+ protected $richTextObject;
+
+ protected function initialise(): void
+ {
+ $this->face = $this->size = $this->color = null;
+ $this->bold = $this->italic = $this->underline = $this->superscript = $this->subscript = $this->strikethrough = false;
+
+ $this->stack = [];
+
+ $this->stringData = '';
+ }
+
+ /**
+ * Parse HTML formatting and return the resulting RichText.
+ *
+ * @param string $html
+ *
+ * @return RichText
+ */
+ public function toRichTextObject($html)
+ {
+ $this->initialise();
+
+ // Create a new DOM object
+ $dom = new DOMDocument();
+ // Load the HTML file into the DOM object
+ // Note the use of error suppression, because typically this will be an html fragment, so not fully valid markup
+ $prefix = '<?xml encoding="UTF-8">';
+ @$dom->loadHTML($prefix . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
+ // Discard excess white space
+ $dom->preserveWhiteSpace = false;
+
+ $this->richTextObject = new RichText();
+ $this->parseElements($dom);
+
+ // Clean any further spurious whitespace
+ $this->cleanWhitespace();
+
+ return $this->richTextObject;
+ }
+
+ protected function cleanWhitespace(): void
+ {
+ foreach ($this->richTextObject->getRichTextElements() as $key => $element) {
+ $text = $element->getText();
+ // Trim any leading spaces on the first run
+ if ($key == 0) {
+ $text = ltrim($text);
+ }
+ // Trim any spaces immediately after a line break
+ $text = preg_replace('/\n */mu', "\n", $text);
+ $element->setText($text);
+ }
+ }
+
+ protected function buildTextRun(): void
+ {
+ $text = $this->stringData;
+ if (trim($text) === '') {
+ return;
+ }
+
+ $richtextRun = $this->richTextObject->createTextRun($this->stringData);
+ if ($this->face) {
+ $richtextRun->getFont()->setName($this->face);
+ }
+ if ($this->size) {
+ $richtextRun->getFont()->setSize($this->size);
+ }
+ if ($this->color) {
+ $richtextRun->getFont()->setColor(new Color('ff' . $this->color));
+ }
+ if ($this->bold) {
+ $richtextRun->getFont()->setBold(true);
+ }
+ if ($this->italic) {
+ $richtextRun->getFont()->setItalic(true);
+ }
+ if ($this->underline) {
+ $richtextRun->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
+ }
+ if ($this->superscript) {
+ $richtextRun->getFont()->setSuperscript(true);
+ }
+ if ($this->subscript) {
+ $richtextRun->getFont()->setSubscript(true);
+ }
+ if ($this->strikethrough) {
+ $richtextRun->getFont()->setStrikethrough(true);
+ }
+ $this->stringData = '';
+ }
+
+ protected function rgbToColour($rgb)
+ {
+ preg_match_all('/\d+/', $rgb, $values);
+ foreach ($values[0] as &$value) {
+ $value = str_pad(dechex($value), 2, '0', STR_PAD_LEFT);
+ }
+
+ return implode('', $values[0]);
+ }
+
+ protected function colourNameLookup($rgb)
+ {
+ return self::$colourMap[$rgb];
+ }
+
+ protected function startFontTag($tag): void
+ {
+ foreach ($tag->attributes as $attribute) {
+ $attributeName = strtolower($attribute->name);
+ $attributeValue = $attribute->value;
+
+ if ($attributeName == 'color') {
+ if (preg_match('/rgb\s*\(/', $attributeValue)) {
+ $this->$attributeName = $this->rgbToColour($attributeValue);
+ } elseif (strpos(trim($attributeValue), '#') === 0) {
+ $this->$attributeName = ltrim($attributeValue, '#');
+ } else {
+ $this->$attributeName = $this->colourNameLookup($attributeValue);
+ }
+ } else {
+ $this->$attributeName = $attributeValue;
+ }
+ }
+ }
+
+ protected function endFontTag(): void
+ {
+ $this->face = $this->size = $this->color = null;
+ }
+
+ protected function startBoldTag(): void
+ {
+ $this->bold = true;
+ }
+
+ protected function endBoldTag(): void
+ {
+ $this->bold = false;
+ }
+
+ protected function startItalicTag(): void
+ {
+ $this->italic = true;
+ }
+
+ protected function endItalicTag(): void
+ {
+ $this->italic = false;
+ }
+
+ protected function startUnderlineTag(): void
+ {
+ $this->underline = true;
+ }
+
+ protected function endUnderlineTag(): void
+ {
+ $this->underline = false;
+ }
+
+ protected function startSubscriptTag(): void
+ {
+ $this->subscript = true;
+ }
+
+ protected function endSubscriptTag(): void
+ {
+ $this->subscript = false;
+ }
+
+ protected function startSuperscriptTag(): void
+ {
+ $this->superscript = true;
+ }
+
+ protected function endSuperscriptTag(): void
+ {
+ $this->superscript = false;
+ }
+
+ protected function startStrikethruTag(): void
+ {
+ $this->strikethrough = true;
+ }
+
+ protected function endStrikethruTag(): void
+ {
+ $this->strikethrough = false;
+ }
+
+ protected function breakTag(): void
+ {
+ $this->stringData .= "\n";
+ }
+
+ protected function parseTextNode(DOMText $textNode): void
+ {
+ $domText = preg_replace(
+ '/\s+/u',
+ ' ',
+ str_replace(["\r", "\n"], ' ', $textNode->nodeValue)
+ );
+ $this->stringData .= $domText;
+ $this->buildTextRun();
+ }
+
+ /**
+ * @param string $callbackTag
+ */
+ protected function handleCallback(DOMElement $element, $callbackTag, array $callbacks): void
+ {
+ if (isset($callbacks[$callbackTag])) {
+ $elementHandler = $callbacks[$callbackTag];
+ if (method_exists($this, $elementHandler)) {
+ call_user_func([$this, $elementHandler], $element);
+ }
+ }
+ }
+
+ protected function parseElementNode(DOMElement $element): void
+ {
+ $callbackTag = strtolower($element->nodeName);
+ $this->stack[] = $callbackTag;
+
+ $this->handleCallback($element, $callbackTag, $this->startTagCallbacks);
+
+ $this->parseElements($element);
+ array_pop($this->stack);
+
+ $this->handleCallback($element, $callbackTag, $this->endTagCallbacks);
+ }
+
+ protected function parseElements(DOMNode $element): void
+ {
+ foreach ($element->childNodes as $child) {
+ if ($child instanceof DOMText) {
+ $this->parseTextNode($child);
+ } elseif ($child instanceof DOMElement) {
+ $this->parseElementNode($child);
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php
new file mode 100755
index 0000000..a91b195
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php
@@ -0,0 +1,229 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Helper;
+
+use PhpOffice\PhpSpreadsheet\IOFactory;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Writer\IWriter;
+use PhpOffice\PhpSpreadsheet\Writer\Pdf;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use RecursiveRegexIterator;
+use ReflectionClass;
+use RegexIterator;
+use RuntimeException;
+
+/**
+ * Helper class to be used in sample code.
+ */
+class Sample
+{
+ /**
+ * Returns whether we run on CLI or browser.
+ *
+ * @return bool
+ */
+ public function isCli()
+ {
+ return PHP_SAPI === 'cli';
+ }
+
+ /**
+ * Return the filename currently being executed.
+ *
+ * @return string
+ */
+ public function getScriptFilename()
+ {
+ return basename($_SERVER['SCRIPT_FILENAME'], '.php');
+ }
+
+ /**
+ * Whether we are executing the index page.
+ *
+ * @return bool
+ */
+ public function isIndex()
+ {
+ return $this->getScriptFilename() === 'index';
+ }
+
+ /**
+ * Return the page title.
+ *
+ * @return string
+ */
+ public function getPageTitle()
+ {
+ return $this->isIndex() ? 'PHPSpreadsheet' : $this->getScriptFilename();
+ }
+
+ /**
+ * Return the page heading.
+ *
+ * @return string
+ */
+ public function getPageHeading()
+ {
+ return $this->isIndex() ? '' : '<h1>' . str_replace('_', ' ', $this->getScriptFilename()) . '</h1>';
+ }
+
+ /**
+ * Returns an array of all known samples.
+ *
+ * @return string[] [$name => $path]
+ */
+ public function getSamples()
+ {
+ // Populate samples
+ $baseDir = realpath(__DIR__ . '/../../../samples');
+ $directory = new RecursiveDirectoryIterator($baseDir);
+ $iterator = new RecursiveIteratorIterator($directory);
+ $regex = new RegexIterator($iterator, '/^.+\.php$/', RecursiveRegexIterator::GET_MATCH);
+
+ $files = [];
+ foreach ($regex as $file) {
+ $file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0]));
+ $info = pathinfo($file);
+ $category = str_replace('_', ' ', $info['dirname']);
+ $name = str_replace('_', ' ', preg_replace('/(|\.php)/', '', $info['filename']));
+ if (!in_array($category, ['.', 'boostrap', 'templates'])) {
+ if (!isset($files[$category])) {
+ $files[$category] = [];
+ }
+ $files[$category][$name] = $file;
+ }
+ }
+
+ // Sort everything
+ ksort($files);
+ foreach ($files as &$f) {
+ asort($f);
+ }
+
+ return $files;
+ }
+
+ /**
+ * Write documents.
+ *
+ * @param string $filename
+ * @param string[] $writers
+ */
+ public function write(Spreadsheet $spreadsheet, $filename, array $writers = ['Xlsx', 'Xls']): void
+ {
+ // Set active sheet index to the first sheet, so Excel opens this as the first sheet
+ $spreadsheet->setActiveSheetIndex(0);
+
+ // Write documents
+ foreach ($writers as $writerType) {
+ $path = $this->getFilename($filename, mb_strtolower($writerType));
+ $writer = IOFactory::createWriter($spreadsheet, $writerType);
+ if ($writer instanceof Pdf) {
+ // PDF writer needs temporary directory
+ $tempDir = $this->getTemporaryFolder();
+ $writer->setTempDir($tempDir);
+ }
+ $callStartTime = microtime(true);
+ $writer->save($path);
+ $this->logWrite($writer, $path, $callStartTime);
+ }
+
+ $this->logEndingNotes();
+ }
+
+ /**
+ * Returns the temporary directory and make sure it exists.
+ *
+ * @return string
+ */
+ private function getTemporaryFolder()
+ {
+ $tempFolder = sys_get_temp_dir() . '/phpspreadsheet';
+ if (!is_dir($tempFolder)) {
+ if (!mkdir($tempFolder) && !is_dir($tempFolder)) {
+ throw new RuntimeException(sprintf('Directory "%s" was not created', $tempFolder));
+ }
+ }
+
+ return $tempFolder;
+ }
+
+ /**
+ * Returns the filename that should be used for sample output.
+ *
+ * @param string $filename
+ * @param string $extension
+ *
+ * @return string
+ */
+ public function getFilename($filename, $extension = 'xlsx')
+ {
+ $originalExtension = pathinfo($filename, PATHINFO_EXTENSION);
+
+ return $this->getTemporaryFolder() . '/' . str_replace('.' . $originalExtension, '.' . $extension, basename($filename));
+ }
+
+ /**
+ * Return a random temporary file name.
+ *
+ * @param string $extension
+ *
+ * @return string
+ */
+ public function getTemporaryFilename($extension = 'xlsx')
+ {
+ $temporaryFilename = tempnam($this->getTemporaryFolder(), 'phpspreadsheet-');
+ unlink($temporaryFilename);
+
+ return $temporaryFilename . '.' . $extension;
+ }
+
+ public function log($message): void
+ {
+ $eol = $this->isCli() ? PHP_EOL : '<br />';
+ echo date('H:i:s ') . $message . $eol;
+ }
+
+ /**
+ * Log ending notes.
+ */
+ public function logEndingNotes(): void
+ {
+ // Do not show execution time for index
+ $this->log('Peak memory usage: ' . (memory_get_peak_usage(true) / 1024 / 1024) . 'MB');
+ }
+
+ /**
+ * Log a line about the write operation.
+ *
+ * @param string $path
+ * @param float $callStartTime
+ */
+ public function logWrite(IWriter $writer, $path, $callStartTime): void
+ {
+ $callEndTime = microtime(true);
+ $callTime = $callEndTime - $callStartTime;
+ $reflection = new ReflectionClass($writer);
+ $format = $reflection->getShortName();
+ $message = "Write {$format} format to <code>{$path}</code> in " . sprintf('%.4f', $callTime) . ' seconds';
+
+ $this->log($message);
+ }
+
+ /**
+ * Log a line about the read operation.
+ *
+ * @param string $format
+ * @param string $path
+ * @param float $callStartTime
+ */
+ public function logRead($format, $path, $callStartTime): void
+ {
+ $callEndTime = microtime(true);
+ $callTime = $callEndTime - $callStartTime;
+ $message = "Read {$format} format from <code>{$path}</code> in " . sprintf('%.4f', $callTime) . ' seconds';
+
+ $this->log($message);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php
new file mode 100755
index 0000000..c215847
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet;
+
+interface IComparable
+{
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode();
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IOFactory.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IOFactory.php
new file mode 100755
index 0000000..ab04e96
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IOFactory.php
@@ -0,0 +1,217 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet;
+
+use PhpOffice\PhpSpreadsheet\Shared\File;
+
+/**
+ * Factory to create readers and writers easily.
+ *
+ * It is not required to use this class, but it should make it easier to read and write files.
+ * Especially for reading files with an unknown format.
+ */
+abstract class IOFactory
+{
+ private static $readers = [
+ 'Xlsx' => Reader\Xlsx::class,
+ 'Xls' => Reader\Xls::class,
+ 'Xml' => Reader\Xml::class,
+ 'Ods' => Reader\Ods::class,
+ 'Slk' => Reader\Slk::class,
+ 'Gnumeric' => Reader\Gnumeric::class,
+ 'Html' => Reader\Html::class,
+ 'Csv' => Reader\Csv::class,
+ ];
+
+ private static $writers = [
+ 'Xls' => Writer\Xls::class,
+ 'Xlsx' => Writer\Xlsx::class,
+ 'Ods' => Writer\Ods::class,
+ 'Csv' => Writer\Csv::class,
+ 'Html' => Writer\Html::class,
+ 'Tcpdf' => Writer\Pdf\Tcpdf::class,
+ 'Dompdf' => Writer\Pdf\Dompdf::class,
+ 'Mpdf' => Writer\Pdf\Mpdf::class,
+ ];
+
+ /**
+ * Create Writer\IWriter.
+ *
+ * @param string $writerType Example: Xlsx
+ *
+ * @return Writer\IWriter
+ */
+ public static function createWriter(Spreadsheet $spreadsheet, $writerType)
+ {
+ if (!isset(self::$writers[$writerType])) {
+ throw new Writer\Exception("No writer found for type $writerType");
+ }
+
+ // Instantiate writer
+ $className = self::$writers[$writerType];
+
+ return new $className($spreadsheet);
+ }
+
+ /**
+ * Create Reader\IReader.
+ *
+ * @param string $readerType Example: Xlsx
+ *
+ * @return Reader\IReader
+ */
+ public static function createReader($readerType)
+ {
+ if (!isset(self::$readers[$readerType])) {
+ throw new Reader\Exception("No reader found for type $readerType");
+ }
+
+ // Instantiate reader
+ $className = self::$readers[$readerType];
+
+ return new $className();
+ }
+
+ /**
+ * Loads Spreadsheet from file using automatic Reader\IReader resolution.
+ *
+ * @param string $pFilename The name of the spreadsheet file
+ *
+ * @return Spreadsheet
+ */
+ public static function load($pFilename)
+ {
+ $reader = self::createReaderForFile($pFilename);
+
+ return $reader->load($pFilename);
+ }
+
+ /**
+ * Identify file type using automatic Reader\IReader resolution.
+ *
+ * @param string $pFilename The name of the spreadsheet file to identify
+ *
+ * @return string
+ */
+ public static function identify($pFilename)
+ {
+ $reader = self::createReaderForFile($pFilename);
+ $className = get_class($reader);
+ $classType = explode('\\', $className);
+ unset($reader);
+
+ return array_pop($classType);
+ }
+
+ /**
+ * Create Reader\IReader for file using automatic Reader\IReader resolution.
+ *
+ * @param string $filename The name of the spreadsheet file
+ *
+ * @return Reader\IReader
+ */
+ public static function createReaderForFile($filename)
+ {
+ File::assertFile($filename);
+
+ // First, lucky guess by inspecting file extension
+ $guessedReader = self::getReaderTypeFromExtension($filename);
+ if ($guessedReader !== null) {
+ $reader = self::createReader($guessedReader);
+
+ // Let's see if we are lucky
+ if (isset($reader) && $reader->canRead($filename)) {
+ return $reader;
+ }
+ }
+
+ // If we reach here then "lucky guess" didn't give any result
+ // Try walking through all the options in self::$autoResolveClasses
+ foreach (self::$readers as $type => $class) {
+ // Ignore our original guess, we know that won't work
+ if ($type !== $guessedReader) {
+ $reader = self::createReader($type);
+ if ($reader->canRead($filename)) {
+ return $reader;
+ }
+ }
+ }
+
+ throw new Reader\Exception('Unable to identify a reader for this file');
+ }
+
+ /**
+ * Guess a reader type from the file extension, if any.
+ *
+ * @param string $filename
+ *
+ * @return null|string
+ */
+ private static function getReaderTypeFromExtension($filename)
+ {
+ $pathinfo = pathinfo($filename);
+ if (!isset($pathinfo['extension'])) {
+ return null;
+ }
+
+ switch (strtolower($pathinfo['extension'])) {
+ case 'xlsx': // Excel (OfficeOpenXML) Spreadsheet
+ case 'xlsm': // Excel (OfficeOpenXML) Macro Spreadsheet (macros will be discarded)
+ case 'xltx': // Excel (OfficeOpenXML) Template
+ case 'xltm': // Excel (OfficeOpenXML) Macro Template (macros will be discarded)
+ return 'Xlsx';
+ case 'xls': // Excel (BIFF) Spreadsheet
+ case 'xlt': // Excel (BIFF) Template
+ return 'Xls';
+ case 'ods': // Open/Libre Offic Calc
+ case 'ots': // Open/Libre Offic Calc Template
+ return 'Ods';
+ case 'slk':
+ return 'Slk';
+ case 'xml': // Excel 2003 SpreadSheetML
+ return 'Xml';
+ case 'gnumeric':
+ return 'Gnumeric';
+ case 'htm':
+ case 'html':
+ return 'Html';
+ case 'csv':
+ // Do nothing
+ // We must not try to use CSV reader since it loads
+ // all files including Excel files etc.
+ return null;
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Register a writer with its type and class name.
+ *
+ * @param string $writerType
+ * @param string $writerClass
+ */
+ public static function registerWriter($writerType, $writerClass): void
+ {
+ if (!is_a($writerClass, Writer\IWriter::class, true)) {
+ throw new Writer\Exception('Registered writers must implement ' . Writer\IWriter::class);
+ }
+
+ self::$writers[$writerType] = $writerClass;
+ }
+
+ /**
+ * Register a reader with its type and class name.
+ *
+ * @param string $readerType
+ * @param string $readerClass
+ */
+ public static function registerReader($readerType, $readerClass): void
+ {
+ if (!is_a($readerClass, Reader\IReader::class, true)) {
+ throw new Reader\Exception('Registered readers must implement ' . Reader\IReader::class);
+ }
+
+ self::$readers[$readerType] = $readerClass;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php
new file mode 100755
index 0000000..94fe819
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php
@@ -0,0 +1,235 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet;
+
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class NamedRange
+{
+ /**
+ * Range name.
+ *
+ * @var string
+ */
+ private $name;
+
+ /**
+ * Worksheet on which the named range can be resolved.
+ *
+ * @var Worksheet
+ */
+ private $worksheet;
+
+ /**
+ * Range of the referenced cells.
+ *
+ * @var string
+ */
+ private $range;
+
+ /**
+ * Is the named range local? (i.e. can only be used on $this->worksheet).
+ *
+ * @var bool
+ */
+ private $localOnly;
+
+ /**
+ * Scope.
+ *
+ * @var Worksheet
+ */
+ private $scope;
+
+ /**
+ * Create a new NamedRange.
+ *
+ * @param string $pName
+ * @param string $pRange
+ * @param bool $pLocalOnly
+ * @param null|Worksheet $pScope Scope. Only applies when $pLocalOnly = true. Null for global scope.
+ */
+ public function __construct($pName, Worksheet $pWorksheet, $pRange = 'A1', $pLocalOnly = false, $pScope = null)
+ {
+ // Validate data
+ if (($pName === null) || ($pWorksheet === null) || ($pRange === null)) {
+ throw new Exception('Parameters can not be null.');
+ }
+
+ // Set local members
+ $this->name = $pName;
+ $this->worksheet = $pWorksheet;
+ $this->range = $pRange;
+ $this->localOnly = $pLocalOnly;
+ $this->scope = ($pLocalOnly == true) ? (($pScope == null) ? $pWorksheet : $pScope) : null;
+ }
+
+ /**
+ * Get name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set name.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setName($value)
+ {
+ if ($value !== null) {
+ // Old title
+ $oldTitle = $this->name;
+
+ // Re-attach
+ if ($this->worksheet !== null) {
+ $this->worksheet->getParent()->removeNamedRange($this->name, $this->worksheet);
+ }
+ $this->name = $value;
+
+ if ($this->worksheet !== null) {
+ $this->worksheet->getParent()->addNamedRange($this);
+ }
+
+ // New title
+ $newTitle = $this->name;
+ ReferenceHelper::getInstance()->updateNamedFormulas($this->worksheet->getParent(), $oldTitle, $newTitle);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get worksheet.
+ *
+ * @return Worksheet
+ */
+ public function getWorksheet()
+ {
+ return $this->worksheet;
+ }
+
+ /**
+ * Set worksheet.
+ *
+ * @param Worksheet $value
+ *
+ * @return $this
+ */
+ public function setWorksheet(?Worksheet $value = null)
+ {
+ if ($value !== null) {
+ $this->worksheet = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get range.
+ *
+ * @return string
+ */
+ public function getRange()
+ {
+ return $this->range;
+ }
+
+ /**
+ * Set range.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setRange($value)
+ {
+ if ($value !== null) {
+ $this->range = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get localOnly.
+ *
+ * @return bool
+ */
+ public function getLocalOnly()
+ {
+ return $this->localOnly;
+ }
+
+ /**
+ * Set localOnly.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setLocalOnly($value)
+ {
+ $this->localOnly = $value;
+ $this->scope = $value ? $this->worksheet : null;
+
+ return $this;
+ }
+
+ /**
+ * Get scope.
+ *
+ * @return null|Worksheet
+ */
+ public function getScope()
+ {
+ return $this->scope;
+ }
+
+ /**
+ * Set scope.
+ *
+ * @return $this
+ */
+ public function setScope(?Worksheet $value = null)
+ {
+ $this->scope = $value;
+ $this->localOnly = $value != null;
+
+ return $this;
+ }
+
+ /**
+ * Resolve a named range to a regular cell range.
+ *
+ * @param string $pNamedRange Named range
+ * @param null|Worksheet $pSheet Scope. Use null for global scope
+ *
+ * @return NamedRange
+ */
+ public static function resolveRange($pNamedRange, Worksheet $pSheet)
+ {
+ return $pSheet->getParent()->getNamedRange($pNamedRange, $pSheet);
+ }
+
+ /**
+ * 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php
new file mode 100755
index 0000000..77a6421
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php
@@ -0,0 +1,158 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader;
+
+use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
+use PhpOffice\PhpSpreadsheet\Shared\File;
+
+abstract class BaseReader implements IReader
+{
+ /**
+ * Read data only?
+ * Identifies whether the Reader should only read data values for cells, and ignore any formatting information;
+ * or whether it should read both data and formatting.
+ *
+ * @var bool
+ */
+ protected $readDataOnly = false;
+
+ /**
+ * Read empty cells?
+ * Identifies whether the Reader should read data values for cells all cells, or should ignore cells containing
+ * null value or empty string.
+ *
+ * @var bool
+ */
+ protected $readEmptyCells = true;
+
+ /**
+ * Read charts that are defined in the workbook?
+ * Identifies whether the Reader should read the definitions for any charts that exist in the workbook;.
+ *
+ * @var bool
+ */
+ protected $includeCharts = false;
+
+ /**
+ * Restrict which sheets should be loaded?
+ * This property holds an array of worksheet names to be loaded. If null, then all worksheets will be loaded.
+ *
+ * @var array of string
+ */
+ protected $loadSheetsOnly;
+
+ /**
+ * IReadFilter instance.
+ *
+ * @var IReadFilter
+ */
+ protected $readFilter;
+
+ protected $fileHandle;
+
+ /**
+ * @var XmlScanner
+ */
+ protected $securityScanner;
+
+ public function __construct()
+ {
+ $this->readFilter = new DefaultReadFilter();
+ }
+
+ public function getReadDataOnly()
+ {
+ return $this->readDataOnly;
+ }
+
+ public function setReadDataOnly($pValue)
+ {
+ $this->readDataOnly = (bool) $pValue;
+
+ return $this;
+ }
+
+ public function getReadEmptyCells()
+ {
+ return $this->readEmptyCells;
+ }
+
+ public function setReadEmptyCells($pValue)
+ {
+ $this->readEmptyCells = (bool) $pValue;
+
+ return $this;
+ }
+
+ public function getIncludeCharts()
+ {
+ return $this->includeCharts;
+ }
+
+ public function setIncludeCharts($pValue)
+ {
+ $this->includeCharts = (bool) $pValue;
+
+ return $this;
+ }
+
+ public function getLoadSheetsOnly()
+ {
+ return $this->loadSheetsOnly;
+ }
+
+ public function setLoadSheetsOnly($value)
+ {
+ if ($value === null) {
+ return $this->setLoadAllSheets();
+ }
+
+ $this->loadSheetsOnly = is_array($value) ? $value : [$value];
+
+ return $this;
+ }
+
+ public function setLoadAllSheets()
+ {
+ $this->loadSheetsOnly = null;
+
+ return $this;
+ }
+
+ public function getReadFilter()
+ {
+ return $this->readFilter;
+ }
+
+ public function setReadFilter(IReadFilter $pValue)
+ {
+ $this->readFilter = $pValue;
+
+ return $this;
+ }
+
+ public function getSecurityScanner()
+ {
+ if (property_exists($this, 'securityScanner')) {
+ return $this->securityScanner;
+ }
+
+ return null;
+ }
+
+ /**
+ * Open file for reading.
+ *
+ * @param string $pFilename
+ */
+ protected function openFile($pFilename): void
+ {
+ File::assertFile($pFilename);
+
+ // Open file
+ $this->fileHandle = fopen($pFilename, 'rb');
+ if ($this->fileHandle === false) {
+ throw new Exception('Could not open file ' . $pFilename . ' for reading.');
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php
new file mode 100755
index 0000000..db2b50b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php
@@ -0,0 +1,533 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader;
+
+use InvalidArgumentException;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Csv extends BaseReader
+{
+ /**
+ * Input encoding.
+ *
+ * @var string
+ */
+ private $inputEncoding = 'UTF-8';
+
+ /**
+ * Delimiter.
+ *
+ * @var string
+ */
+ private $delimiter;
+
+ /**
+ * Enclosure.
+ *
+ * @var string
+ */
+ private $enclosure = '"';
+
+ /**
+ * Sheet index to read.
+ *
+ * @var int
+ */
+ private $sheetIndex = 0;
+
+ /**
+ * Load rows contiguously.
+ *
+ * @var bool
+ */
+ private $contiguous = false;
+
+ /**
+ * The character that can escape the enclosure.
+ *
+ * @var string
+ */
+ private $escapeCharacter = '\\';
+
+ /**
+ * Create a new CSV Reader instance.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Set input encoding.
+ *
+ * @param string $pValue Input encoding, eg: 'UTF-8'
+ *
+ * @return $this
+ */
+ public function setInputEncoding($pValue)
+ {
+ $this->inputEncoding = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get input encoding.
+ *
+ * @return string
+ */
+ public function getInputEncoding()
+ {
+ return $this->inputEncoding;
+ }
+
+ /**
+ * Move filepointer past any BOM marker.
+ */
+ protected function skipBOM(): void
+ {
+ rewind($this->fileHandle);
+
+ switch ($this->inputEncoding) {
+ case 'UTF-8':
+ fgets($this->fileHandle, 4) == "\xEF\xBB\xBF" ?
+ fseek($this->fileHandle, 3) : fseek($this->fileHandle, 0);
+
+ break;
+ }
+ }
+
+ /**
+ * Identify any separator that is explicitly set in the file.
+ */
+ protected function checkSeparator(): void
+ {
+ $line = fgets($this->fileHandle);
+ if ($line === false) {
+ return;
+ }
+
+ if ((strlen(trim($line, "\r\n")) == 5) && (stripos($line, 'sep=') === 0)) {
+ $this->delimiter = substr($line, 4, 1);
+
+ return;
+ }
+
+ $this->skipBOM();
+ }
+
+ /**
+ * Infer the separator if it isn't explicitly set in the file or specified by the user.
+ */
+ protected function inferSeparator(): void
+ {
+ if ($this->delimiter !== null) {
+ return;
+ }
+
+ $potentialDelimiters = [',', ';', "\t", '|', ':', ' ', '~'];
+ $counts = [];
+ foreach ($potentialDelimiters as $delimiter) {
+ $counts[$delimiter] = [];
+ }
+
+ // Count how many times each of the potential delimiters appears in each line
+ $numberLines = 0;
+ while (($line = $this->getNextLine()) !== false && (++$numberLines < 1000)) {
+ $countLine = [];
+ for ($i = strlen($line) - 1; $i >= 0; --$i) {
+ $char = $line[$i];
+ if (isset($counts[$char])) {
+ if (!isset($countLine[$char])) {
+ $countLine[$char] = 0;
+ }
+ ++$countLine[$char];
+ }
+ }
+ foreach ($potentialDelimiters as $delimiter) {
+ $counts[$delimiter][] = $countLine[$delimiter]
+ ?? 0;
+ }
+ }
+
+ // If number of lines is 0, nothing to infer : fall back to the default
+ if ($numberLines === 0) {
+ $this->delimiter = reset($potentialDelimiters);
+ $this->skipBOM();
+
+ return;
+ }
+
+ // Calculate the mean square deviations for each delimiter (ignoring delimiters that haven't been found consistently)
+ $meanSquareDeviations = [];
+ $middleIdx = floor(($numberLines - 1) / 2);
+
+ foreach ($potentialDelimiters as $delimiter) {
+ $series = $counts[$delimiter];
+ sort($series);
+
+ $median = ($numberLines % 2)
+ ? $series[$middleIdx]
+ : ($series[$middleIdx] + $series[$middleIdx + 1]) / 2;
+
+ if ($median === 0) {
+ continue;
+ }
+
+ $meanSquareDeviations[$delimiter] = array_reduce(
+ $series,
+ function ($sum, $value) use ($median) {
+ return $sum + ($value - $median) ** 2;
+ }
+ ) / count($series);
+ }
+
+ // ... and pick the delimiter with the smallest mean square deviation (in case of ties, the order in potentialDelimiters is respected)
+ $min = INF;
+ foreach ($potentialDelimiters as $delimiter) {
+ if (!isset($meanSquareDeviations[$delimiter])) {
+ continue;
+ }
+
+ if ($meanSquareDeviations[$delimiter] < $min) {
+ $min = $meanSquareDeviations[$delimiter];
+ $this->delimiter = $delimiter;
+ }
+ }
+
+ // If no delimiter could be detected, fall back to the default
+ if ($this->delimiter === null) {
+ $this->delimiter = reset($potentialDelimiters);
+ }
+
+ $this->skipBOM();
+ }
+
+ /**
+ * Get the next full line from the file.
+ *
+ * @return false|string
+ */
+ private function getNextLine()
+ {
+ $line = '';
+ $enclosure = '(?<!' . preg_quote($this->escapeCharacter, '/') . ')' . preg_quote($this->enclosure, '/');
+
+ do {
+ // Get the next line in the file
+ $newLine = fgets($this->fileHandle);
+
+ // Return false if there is no next line
+ if ($newLine === false) {
+ return false;
+ }
+
+ // Add the new line to the line passed in
+ $line = $line . $newLine;
+
+ // Drop everything that is enclosed to avoid counting false positives in enclosures
+ $line = preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line);
+
+ // See if we have any enclosures left in the line
+ // if we still have an enclosure then we need to read the next line as well
+ } while (preg_match('/(' . $enclosure . ')/', $line) > 0);
+
+ return $line;
+ }
+
+ /**
+ * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
+ *
+ * @param string $pFilename
+ *
+ * @return array
+ */
+ public function listWorksheetInfo($pFilename)
+ {
+ // Open file
+ $this->openFileOrMemory($pFilename);
+ $fileHandle = $this->fileHandle;
+
+ // Skip BOM, if any
+ $this->skipBOM();
+ $this->checkSeparator();
+ $this->inferSeparator();
+
+ $worksheetInfo = [];
+ $worksheetInfo[0]['worksheetName'] = 'Worksheet';
+ $worksheetInfo[0]['lastColumnLetter'] = 'A';
+ $worksheetInfo[0]['lastColumnIndex'] = 0;
+ $worksheetInfo[0]['totalRows'] = 0;
+ $worksheetInfo[0]['totalColumns'] = 0;
+
+ // Loop through each line of the file in turn
+ while (($rowData = fgetcsv($fileHandle, 0, $this->delimiter, $this->enclosure, $this->escapeCharacter)) !== false) {
+ ++$worksheetInfo[0]['totalRows'];
+ $worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], count($rowData) - 1);
+ }
+
+ $worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1);
+ $worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1;
+
+ // Close file
+ fclose($fileHandle);
+
+ return $worksheetInfo;
+ }
+
+ /**
+ * Loads Spreadsheet from file.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function load($pFilename)
+ {
+ // Create new Spreadsheet
+ $spreadsheet = new Spreadsheet();
+
+ // Load into this instance
+ return $this->loadIntoExisting($pFilename, $spreadsheet);
+ }
+
+ private function openFileOrMemory($pFilename): void
+ {
+ // Open file
+ $fhandle = $this->canRead($pFilename);
+ if (!$fhandle) {
+ throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
+ }
+ $this->openFile($pFilename);
+ if ($this->inputEncoding !== 'UTF-8') {
+ fclose($this->fileHandle);
+ $entireFile = file_get_contents($pFilename);
+ $this->fileHandle = fopen('php://memory', 'r+b');
+ $data = StringHelper::convertEncoding($entireFile, 'UTF-8', $this->inputEncoding);
+ fwrite($this->fileHandle, $data);
+ rewind($this->fileHandle);
+ }
+ }
+
+ /**
+ * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
+ {
+ $lineEnding = ini_get('auto_detect_line_endings');
+ ini_set('auto_detect_line_endings', true);
+
+ // Open file
+ $this->openFileOrMemory($pFilename);
+ $fileHandle = $this->fileHandle;
+
+ // Skip BOM, if any
+ $this->skipBOM();
+ $this->checkSeparator();
+ $this->inferSeparator();
+
+ // Create new PhpSpreadsheet object
+ while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
+ $spreadsheet->createSheet();
+ }
+ $sheet = $spreadsheet->setActiveSheetIndex($this->sheetIndex);
+
+ // Set our starting row based on whether we're in contiguous mode or not
+ $currentRow = 1;
+ $outRow = 0;
+
+ // Loop through each line of the file in turn
+ while (($rowData = fgetcsv($fileHandle, 0, $this->delimiter, $this->enclosure, $this->escapeCharacter)) !== false) {
+ $noOutputYet = true;
+ $columnLetter = 'A';
+ foreach ($rowData as $rowDatum) {
+ if ($rowDatum != '' && $this->readFilter->readCell($columnLetter, $currentRow)) {
+ if ($this->contiguous) {
+ if ($noOutputYet) {
+ $noOutputYet = false;
+ ++$outRow;
+ }
+ } else {
+ $outRow = $currentRow;
+ }
+ // Set cell value
+ $sheet->getCell($columnLetter . $outRow)->setValue($rowDatum);
+ }
+ ++$columnLetter;
+ }
+ ++$currentRow;
+ }
+
+ // Close file
+ fclose($fileHandle);
+
+ ini_set('auto_detect_line_endings', $lineEnding);
+
+ // Return
+ return $spreadsheet;
+ }
+
+ /**
+ * Get delimiter.
+ *
+ * @return string
+ */
+ public function getDelimiter()
+ {
+ return $this->delimiter;
+ }
+
+ /**
+ * Set delimiter.
+ *
+ * @param string $delimiter Delimiter, eg: ','
+ *
+ * @return $this
+ */
+ public function setDelimiter($delimiter)
+ {
+ $this->delimiter = $delimiter;
+
+ return $this;
+ }
+
+ /**
+ * Get enclosure.
+ *
+ * @return string
+ */
+ public function getEnclosure()
+ {
+ return $this->enclosure;
+ }
+
+ /**
+ * Set enclosure.
+ *
+ * @param string $enclosure Enclosure, defaults to "
+ *
+ * @return $this
+ */
+ public function setEnclosure($enclosure)
+ {
+ if ($enclosure == '') {
+ $enclosure = '"';
+ }
+ $this->enclosure = $enclosure;
+
+ return $this;
+ }
+
+ /**
+ * Get sheet index.
+ *
+ * @return int
+ */
+ public function getSheetIndex()
+ {
+ return $this->sheetIndex;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param int $pValue Sheet index
+ *
+ * @return $this
+ */
+ public function setSheetIndex($pValue)
+ {
+ $this->sheetIndex = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Set Contiguous.
+ *
+ * @param bool $contiguous
+ *
+ * @return $this
+ */
+ public function setContiguous($contiguous)
+ {
+ $this->contiguous = (bool) $contiguous;
+
+ return $this;
+ }
+
+ /**
+ * Get Contiguous.
+ *
+ * @return bool
+ */
+ public function getContiguous()
+ {
+ return $this->contiguous;
+ }
+
+ /**
+ * Set escape backslashes.
+ *
+ * @param string $escapeCharacter
+ *
+ * @return $this
+ */
+ public function setEscapeCharacter($escapeCharacter)
+ {
+ $this->escapeCharacter = $escapeCharacter;
+
+ return $this;
+ }
+
+ /**
+ * Get escape backslashes.
+ *
+ * @return string
+ */
+ public function getEscapeCharacter()
+ {
+ return $this->escapeCharacter;
+ }
+
+ /**
+ * Can the current IReader read the file?
+ *
+ * @param string $pFilename
+ *
+ * @return bool
+ */
+ public function canRead($pFilename)
+ {
+ // Check if file exists
+ try {
+ $this->openFile($pFilename);
+ } catch (InvalidArgumentException $e) {
+ return false;
+ }
+
+ fclose($this->fileHandle);
+
+ // Trust file extension if any
+ $extension = strtolower(pathinfo($pFilename, PATHINFO_EXTENSION));
+ if (in_array($extension, ['csv', 'tsv'])) {
+ return true;
+ }
+
+ // Attempt to guess mimetype
+ $type = mime_content_type($pFilename);
+ $supportedTypes = [
+ 'text/csv',
+ 'text/plain',
+ 'inode/x-empty',
+ ];
+
+ return in_array($type, $supportedTypes, true);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php
new file mode 100755
index 0000000..e104186
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader;
+
+class DefaultReadFilter implements IReadFilter
+{
+ /**
+ * Should this cell be read?
+ *
+ * @param string $column Column address (as a string value like "A", or "IV")
+ * @param int $row Row number
+ * @param string $worksheetName Optional worksheet name
+ *
+ * @return bool
+ */
+ public function readCell($column, $row, $worksheetName = '')
+ {
+ return true;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php
new file mode 100755
index 0000000..ddbc9b5
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class Exception extends PhpSpreadsheetException
+{
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php
new file mode 100755
index 0000000..f976802
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php
@@ -0,0 +1,882 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\NamedRange;
+use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
+use PhpOffice\PhpSpreadsheet\ReferenceHelper;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Settings;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Borders;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use XMLReader;
+
+class Gnumeric extends BaseReader
+{
+ /**
+ * Shared Expressions.
+ *
+ * @var array
+ */
+ private $expressions = [];
+
+ private $referenceHelper;
+
+ /**
+ * Create a new Gnumeric.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->referenceHelper = ReferenceHelper::getInstance();
+ $this->securityScanner = XmlScanner::getInstance($this);
+ }
+
+ /**
+ * Can the current IReader read the file?
+ *
+ * @param string $pFilename
+ *
+ * @return bool
+ */
+ public function canRead($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ // Check if gzlib functions are available
+ if (!function_exists('gzread')) {
+ throw new Exception('gzlib library is not enabled');
+ }
+
+ // Read signature data (first 3 bytes)
+ $fh = fopen($pFilename, 'rb');
+ $data = fread($fh, 2);
+ fclose($fh);
+
+ return $data == chr(0x1F) . chr(0x8B);
+ }
+
+ /**
+ * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
+ *
+ * @param string $pFilename
+ *
+ * @return array
+ */
+ public function listWorksheetNames($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ $xml = new XMLReader();
+ $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($pFilename)), null, Settings::getLibXmlLoaderOptions());
+ $xml->setParserProperty(2, true);
+
+ $worksheetNames = [];
+ while ($xml->read()) {
+ if ($xml->name == 'gnm:SheetName' && $xml->nodeType == XMLReader::ELEMENT) {
+ $xml->read(); // Move onto the value node
+ $worksheetNames[] = (string) $xml->value;
+ } elseif ($xml->name == 'gnm:Sheets') {
+ // break out of the loop once we've got our sheet names rather than parse the entire file
+ break;
+ }
+ }
+
+ return $worksheetNames;
+ }
+
+ /**
+ * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
+ *
+ * @param string $pFilename
+ *
+ * @return array
+ */
+ public function listWorksheetInfo($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ $xml = new XMLReader();
+ $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($pFilename)), null, Settings::getLibXmlLoaderOptions());
+ $xml->setParserProperty(2, true);
+
+ $worksheetInfo = [];
+ while ($xml->read()) {
+ if ($xml->name == 'gnm:Sheet' && $xml->nodeType == XMLReader::ELEMENT) {
+ $tmpInfo = [
+ 'worksheetName' => '',
+ 'lastColumnLetter' => 'A',
+ 'lastColumnIndex' => 0,
+ 'totalRows' => 0,
+ 'totalColumns' => 0,
+ ];
+
+ while ($xml->read()) {
+ if ($xml->name == 'gnm:Name' && $xml->nodeType == XMLReader::ELEMENT) {
+ $xml->read(); // Move onto the value node
+ $tmpInfo['worksheetName'] = (string) $xml->value;
+ } elseif ($xml->name == 'gnm:MaxCol' && $xml->nodeType == XMLReader::ELEMENT) {
+ $xml->read(); // Move onto the value node
+ $tmpInfo['lastColumnIndex'] = (int) $xml->value;
+ $tmpInfo['totalColumns'] = (int) $xml->value + 1;
+ } elseif ($xml->name == 'gnm:MaxRow' && $xml->nodeType == XMLReader::ELEMENT) {
+ $xml->read(); // Move onto the value node
+ $tmpInfo['totalRows'] = (int) $xml->value + 1;
+
+ break;
+ }
+ }
+ $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
+ $worksheetInfo[] = $tmpInfo;
+ }
+ }
+
+ return $worksheetInfo;
+ }
+
+ /**
+ * @param string $filename
+ *
+ * @return string
+ */
+ private function gzfileGetContents($filename)
+ {
+ $file = @gzopen($filename, 'rb');
+ $data = '';
+ if ($file !== false) {
+ while (!gzeof($file)) {
+ $data .= gzread($file, 1024);
+ }
+ gzclose($file);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Loads Spreadsheet from file.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function load($pFilename)
+ {
+ // Create new Spreadsheet
+ $spreadsheet = new Spreadsheet();
+
+ // Load into this instance
+ return $this->loadIntoExisting($pFilename, $spreadsheet);
+ }
+
+ /**
+ * Loads from file into Spreadsheet instance.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
+ {
+ File::assertFile($pFilename);
+
+ $gFileData = $this->gzfileGetContents($pFilename);
+
+ $xml = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions());
+ $namespacesMeta = $xml->getNamespaces(true);
+
+ $gnmXML = $xml->children($namespacesMeta['gnm']);
+
+ $docProps = $spreadsheet->getProperties();
+ // Document Properties are held differently, depending on the version of Gnumeric
+ if (isset($namespacesMeta['office'])) {
+ $officeXML = $xml->children($namespacesMeta['office']);
+ $officeDocXML = $officeXML->{'document-meta'};
+ $officeDocMetaXML = $officeDocXML->meta;
+
+ foreach ($officeDocMetaXML as $officePropertyData) {
+ $officePropertyDC = [];
+ if (isset($namespacesMeta['dc'])) {
+ $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']);
+ }
+ foreach ($officePropertyDC as $propertyName => $propertyValue) {
+ $propertyValue = (string) $propertyValue;
+ switch ($propertyName) {
+ case 'title':
+ $docProps->setTitle(trim($propertyValue));
+
+ break;
+ case 'subject':
+ $docProps->setSubject(trim($propertyValue));
+
+ break;
+ case 'creator':
+ $docProps->setCreator(trim($propertyValue));
+ $docProps->setLastModifiedBy(trim($propertyValue));
+
+ break;
+ case 'date':
+ $creationDate = strtotime(trim($propertyValue));
+ $docProps->setCreated($creationDate);
+ $docProps->setModified($creationDate);
+
+ break;
+ case 'description':
+ $docProps->setDescription(trim($propertyValue));
+
+ break;
+ }
+ }
+ $officePropertyMeta = [];
+ if (isset($namespacesMeta['meta'])) {
+ $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']);
+ }
+ foreach ($officePropertyMeta as $propertyName => $propertyValue) {
+ $attributes = $propertyValue->attributes($namespacesMeta['meta']);
+ $propertyValue = (string) $propertyValue;
+ switch ($propertyName) {
+ case 'keyword':
+ $docProps->setKeywords(trim($propertyValue));
+
+ break;
+ case 'initial-creator':
+ $docProps->setCreator(trim($propertyValue));
+ $docProps->setLastModifiedBy(trim($propertyValue));
+
+ break;
+ case 'creation-date':
+ $creationDate = strtotime(trim($propertyValue));
+ $docProps->setCreated($creationDate);
+ $docProps->setModified($creationDate);
+
+ break;
+ case 'user-defined':
+ [, $attrName] = explode(':', $attributes['name']);
+ switch ($attrName) {
+ case 'publisher':
+ $docProps->setCompany(trim($propertyValue));
+
+ break;
+ case 'category':
+ $docProps->setCategory(trim($propertyValue));
+
+ break;
+ case 'manager':
+ $docProps->setManager(trim($propertyValue));
+
+ break;
+ }
+
+ break;
+ }
+ }
+ }
+ } elseif (isset($gnmXML->Summary)) {
+ foreach ($gnmXML->Summary->Item as $summaryItem) {
+ $propertyName = $summaryItem->name;
+ $propertyValue = $summaryItem->{'val-string'};
+ switch ($propertyName) {
+ case 'title':
+ $docProps->setTitle(trim($propertyValue));
+
+ break;
+ case 'comments':
+ $docProps->setDescription(trim($propertyValue));
+
+ break;
+ case 'keywords':
+ $docProps->setKeywords(trim($propertyValue));
+
+ break;
+ case 'category':
+ $docProps->setCategory(trim($propertyValue));
+
+ break;
+ case 'manager':
+ $docProps->setManager(trim($propertyValue));
+
+ break;
+ case 'author':
+ $docProps->setCreator(trim($propertyValue));
+ $docProps->setLastModifiedBy(trim($propertyValue));
+
+ break;
+ case 'company':
+ $docProps->setCompany(trim($propertyValue));
+
+ break;
+ }
+ }
+ }
+
+ $worksheetID = 0;
+ foreach ($gnmXML->Sheets->Sheet as $sheet) {
+ $worksheetName = (string) $sheet->Name;
+ if ((isset($this->loadSheetsOnly)) && (!in_array($worksheetName, $this->loadSheetsOnly))) {
+ continue;
+ }
+
+ $maxRow = $maxCol = 0;
+
+ // Create new Worksheet
+ $spreadsheet->createSheet();
+ $spreadsheet->setActiveSheetIndex($worksheetID);
+ // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
+ // cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
+ // name in line with the formula, not the reverse
+ $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
+
+ if ((!$this->readDataOnly) && (isset($sheet->PrintInformation))) {
+ if (isset($sheet->PrintInformation->Margins)) {
+ foreach ($sheet->PrintInformation->Margins->children('gnm', true) as $key => $margin) {
+ $marginAttributes = $margin->attributes();
+ $marginSize = 72 / 100; // Default
+ switch ($marginAttributes['PrefUnit']) {
+ case 'mm':
+ $marginSize = (int) ($marginAttributes['Points']) / 100;
+
+ break;
+ }
+ switch ($key) {
+ case 'top':
+ $spreadsheet->getActiveSheet()->getPageMargins()->setTop($marginSize);
+
+ break;
+ case 'bottom':
+ $spreadsheet->getActiveSheet()->getPageMargins()->setBottom($marginSize);
+
+ break;
+ case 'left':
+ $spreadsheet->getActiveSheet()->getPageMargins()->setLeft($marginSize);
+
+ break;
+ case 'right':
+ $spreadsheet->getActiveSheet()->getPageMargins()->setRight($marginSize);
+
+ break;
+ case 'header':
+ $spreadsheet->getActiveSheet()->getPageMargins()->setHeader($marginSize);
+
+ break;
+ case 'footer':
+ $spreadsheet->getActiveSheet()->getPageMargins()->setFooter($marginSize);
+
+ break;
+ }
+ }
+ }
+ }
+
+ foreach ($sheet->Cells->Cell as $cell) {
+ $cellAttributes = $cell->attributes();
+ $row = (int) $cellAttributes->Row + 1;
+ $column = (int) $cellAttributes->Col;
+
+ if ($row > $maxRow) {
+ $maxRow = $row;
+ }
+ if ($column > $maxCol) {
+ $maxCol = $column;
+ }
+
+ $column = Coordinate::stringFromColumnIndex($column + 1);
+
+ // Read cell?
+ if ($this->getReadFilter() !== null) {
+ if (!$this->getReadFilter()->readCell($column, $row, $worksheetName)) {
+ continue;
+ }
+ }
+
+ $ValueType = $cellAttributes->ValueType;
+ $ExprID = (string) $cellAttributes->ExprID;
+ $type = DataType::TYPE_FORMULA;
+ if ($ExprID > '') {
+ if (((string) $cell) > '') {
+ $this->expressions[$ExprID] = [
+ 'column' => $cellAttributes->Col,
+ 'row' => $cellAttributes->Row,
+ 'formula' => (string) $cell,
+ ];
+ } else {
+ $expression = $this->expressions[$ExprID];
+
+ $cell = $this->referenceHelper->updateFormulaReferences(
+ $expression['formula'],
+ 'A1',
+ $cellAttributes->Col - $expression['column'],
+ $cellAttributes->Row - $expression['row'],
+ $worksheetName
+ );
+ }
+ $type = DataType::TYPE_FORMULA;
+ } else {
+ switch ($ValueType) {
+ case '10': // NULL
+ $type = DataType::TYPE_NULL;
+
+ break;
+ case '20': // Boolean
+ $type = DataType::TYPE_BOOL;
+ $cell = $cell == 'TRUE';
+
+ break;
+ case '30': // Integer
+ $cell = (int) $cell;
+ // Excel 2007+ doesn't differentiate between integer and float, so set the value and dropthru to the next (numeric) case
+ // no break
+ case '40': // Float
+ $type = DataType::TYPE_NUMERIC;
+
+ break;
+ case '50': // Error
+ $type = DataType::TYPE_ERROR;
+
+ break;
+ case '60': // String
+ $type = DataType::TYPE_STRING;
+
+ break;
+ case '70': // Cell Range
+ case '80': // Array
+ }
+ }
+ $spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit($cell, $type);
+ }
+
+ if ((!$this->readDataOnly) && (isset($sheet->Objects))) {
+ foreach ($sheet->Objects->children('gnm', true) as $key => $comment) {
+ $commentAttributes = $comment->attributes();
+ // Only comment objects are handled at the moment
+ if ($commentAttributes->Text) {
+ $spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound)->setAuthor((string) $commentAttributes->Author)->setText($this->parseRichText((string) $commentAttributes->Text));
+ }
+ }
+ }
+ foreach ($sheet->Styles->StyleRegion as $styleRegion) {
+ $styleAttributes = $styleRegion->attributes();
+ if (($styleAttributes['startRow'] <= $maxRow) &&
+ ($styleAttributes['startCol'] <= $maxCol)) {
+ $startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1);
+ $startRow = $styleAttributes['startRow'] + 1;
+
+ $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol'];
+ $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1);
+ $endRow = ($styleAttributes['endRow'] > $maxRow) ? $maxRow : $styleAttributes['endRow'];
+ ++$endRow;
+ $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow;
+
+ $styleAttributes = $styleRegion->Style->attributes();
+
+ // We still set the number format mask for date/time values, even if readDataOnly is true
+ if ((!$this->readDataOnly) ||
+ (Date::isDateTimeFormatCode((string) $styleAttributes['Format']))) {
+ $styleArray = [];
+ $styleArray['numberFormat']['formatCode'] = (string) $styleAttributes['Format'];
+ // If readDataOnly is false, we set all formatting information
+ if (!$this->readDataOnly) {
+ switch ($styleAttributes['HAlign']) {
+ case '1':
+ $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_GENERAL;
+
+ break;
+ case '2':
+ $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_LEFT;
+
+ break;
+ case '4':
+ $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_RIGHT;
+
+ break;
+ case '8':
+ $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_CENTER;
+
+ break;
+ case '16':
+ case '64':
+ $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_CENTER_CONTINUOUS;
+
+ break;
+ case '32':
+ $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_JUSTIFY;
+
+ break;
+ }
+
+ switch ($styleAttributes['VAlign']) {
+ case '1':
+ $styleArray['alignment']['vertical'] = Alignment::VERTICAL_TOP;
+
+ break;
+ case '2':
+ $styleArray['alignment']['vertical'] = Alignment::VERTICAL_BOTTOM;
+
+ break;
+ case '4':
+ $styleArray['alignment']['vertical'] = Alignment::VERTICAL_CENTER;
+
+ break;
+ case '8':
+ $styleArray['alignment']['vertical'] = Alignment::VERTICAL_JUSTIFY;
+
+ break;
+ }
+
+ $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1';
+ $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1';
+ $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0;
+
+ $RGB = self::parseGnumericColour($styleAttributes['Fore']);
+ $styleArray['font']['color']['rgb'] = $RGB;
+ $RGB = self::parseGnumericColour($styleAttributes['Back']);
+ $shade = $styleAttributes['Shade'];
+ if (($RGB != '000000') || ($shade != '0')) {
+ $styleArray['fill']['color']['rgb'] = $styleArray['fill']['startColor']['rgb'] = $RGB;
+ $RGB2 = self::parseGnumericColour($styleAttributes['PatternColor']);
+ $styleArray['fill']['endColor']['rgb'] = $RGB2;
+ switch ($shade) {
+ case '1':
+ $styleArray['fill']['fillType'] = Fill::FILL_SOLID;
+
+ break;
+ case '2':
+ $styleArray['fill']['fillType'] = Fill::FILL_GRADIENT_LINEAR;
+
+ break;
+ case '3':
+ $styleArray['fill']['fillType'] = Fill::FILL_GRADIENT_PATH;
+
+ break;
+ case '4':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKDOWN;
+
+ break;
+ case '5':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKGRAY;
+
+ break;
+ case '6':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKGRID;
+
+ break;
+ case '7':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKHORIZONTAL;
+
+ break;
+ case '8':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKTRELLIS;
+
+ break;
+ case '9':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKUP;
+
+ break;
+ case '10':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKVERTICAL;
+
+ break;
+ case '11':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_GRAY0625;
+
+ break;
+ case '12':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_GRAY125;
+
+ break;
+ case '13':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTDOWN;
+
+ break;
+ case '14':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTGRAY;
+
+ break;
+ case '15':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTGRID;
+
+ break;
+ case '16':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTHORIZONTAL;
+
+ break;
+ case '17':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTTRELLIS;
+
+ break;
+ case '18':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTUP;
+
+ break;
+ case '19':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTVERTICAL;
+
+ break;
+ case '20':
+ $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_MEDIUMGRAY;
+
+ break;
+ }
+ }
+
+ $fontAttributes = $styleRegion->Style->Font->attributes();
+ $styleArray['font']['name'] = (string) $styleRegion->Style->Font;
+ $styleArray['font']['size'] = (int) ($fontAttributes['Unit']);
+ $styleArray['font']['bold'] = $fontAttributes['Bold'] == '1';
+ $styleArray['font']['italic'] = $fontAttributes['Italic'] == '1';
+ $styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1';
+ switch ($fontAttributes['Underline']) {
+ case '1':
+ $styleArray['font']['underline'] = Font::UNDERLINE_SINGLE;
+
+ break;
+ case '2':
+ $styleArray['font']['underline'] = Font::UNDERLINE_DOUBLE;
+
+ break;
+ case '3':
+ $styleArray['font']['underline'] = Font::UNDERLINE_SINGLEACCOUNTING;
+
+ break;
+ case '4':
+ $styleArray['font']['underline'] = Font::UNDERLINE_DOUBLEACCOUNTING;
+
+ break;
+ default:
+ $styleArray['font']['underline'] = Font::UNDERLINE_NONE;
+
+ break;
+ }
+ switch ($fontAttributes['Script']) {
+ case '1':
+ $styleArray['font']['superscript'] = true;
+
+ break;
+ case '-1':
+ $styleArray['font']['subscript'] = true;
+
+ break;
+ }
+
+ if (isset($styleRegion->Style->StyleBorder)) {
+ if (isset($styleRegion->Style->StyleBorder->Top)) {
+ $styleArray['borders']['top'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Top->attributes());
+ }
+ if (isset($styleRegion->Style->StyleBorder->Bottom)) {
+ $styleArray['borders']['bottom'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Bottom->attributes());
+ }
+ if (isset($styleRegion->Style->StyleBorder->Left)) {
+ $styleArray['borders']['left'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Left->attributes());
+ }
+ if (isset($styleRegion->Style->StyleBorder->Right)) {
+ $styleArray['borders']['right'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Right->attributes());
+ }
+ if ((isset($styleRegion->Style->StyleBorder->Diagonal)) && (isset($styleRegion->Style->StyleBorder->{'Rev-Diagonal'}))) {
+ $styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Diagonal->attributes());
+ $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH;
+ } elseif (isset($styleRegion->Style->StyleBorder->Diagonal)) {
+ $styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Diagonal->attributes());
+ $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP;
+ } elseif (isset($styleRegion->Style->StyleBorder->{'Rev-Diagonal'})) {
+ $styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->{'Rev-Diagonal'}->attributes());
+ $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN;
+ }
+ }
+ if (isset($styleRegion->Style->HyperLink)) {
+ // TO DO
+ $hyperlink = $styleRegion->Style->HyperLink->attributes();
+ }
+ }
+ $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray);
+ }
+ }
+ }
+
+ if ((!$this->readDataOnly) && (isset($sheet->Cols))) {
+ // Column Widths
+ $columnAttributes = $sheet->Cols->attributes();
+ $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4;
+ $c = 0;
+ foreach ($sheet->Cols->ColInfo as $columnOverride) {
+ $columnAttributes = $columnOverride->attributes();
+ $column = $columnAttributes['No'];
+ $columnWidth = $columnAttributes['Unit'] / 5.4;
+ $hidden = (isset($columnAttributes['Hidden'])) && ($columnAttributes['Hidden'] == '1');
+ $columnCount = (isset($columnAttributes['Count'])) ? $columnAttributes['Count'] : 1;
+ while ($c < $column) {
+ $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
+ ++$c;
+ }
+ while (($c < ($column + $columnCount)) && ($c <= $maxCol)) {
+ $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($columnWidth);
+ if ($hidden) {
+ $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setVisible(false);
+ }
+ ++$c;
+ }
+ }
+ while ($c <= $maxCol) {
+ $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
+ ++$c;
+ }
+ }
+
+ if ((!$this->readDataOnly) && (isset($sheet->Rows))) {
+ // Row Heights
+ $rowAttributes = $sheet->Rows->attributes();
+ $defaultHeight = $rowAttributes['DefaultSizePts'];
+ $r = 0;
+
+ foreach ($sheet->Rows->RowInfo as $rowOverride) {
+ $rowAttributes = $rowOverride->attributes();
+ $row = $rowAttributes['No'];
+ $rowHeight = $rowAttributes['Unit'];
+ $hidden = (isset($rowAttributes['Hidden'])) && ($rowAttributes['Hidden'] == '1');
+ $rowCount = (isset($rowAttributes['Count'])) ? $rowAttributes['Count'] : 1;
+ while ($r < $row) {
+ ++$r;
+ $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight);
+ }
+ while (($r < ($row + $rowCount)) && ($r < $maxRow)) {
+ ++$r;
+ $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($rowHeight);
+ if ($hidden) {
+ $spreadsheet->getActiveSheet()->getRowDimension($r)->setVisible(false);
+ }
+ }
+ }
+ while ($r < $maxRow) {
+ ++$r;
+ $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight);
+ }
+ }
+
+ // Handle Merged Cells in this worksheet
+ if (isset($sheet->MergedRegions)) {
+ foreach ($sheet->MergedRegions->Merge as $mergeCells) {
+ if (strpos($mergeCells, ':') !== false) {
+ $spreadsheet->getActiveSheet()->mergeCells($mergeCells);
+ }
+ }
+ }
+
+ ++$worksheetID;
+ }
+
+ // Loop through definedNames (global named ranges)
+ if (isset($gnmXML->Names)) {
+ foreach ($gnmXML->Names->Name as $namedRange) {
+ $name = (string) $namedRange->name;
+ $range = (string) $namedRange->value;
+ if (stripos($range, '#REF!') !== false) {
+ continue;
+ }
+
+ $range = Worksheet::extractSheetTitle($range, true);
+ $range[0] = trim($range[0], "'");
+ if ($worksheet = $spreadsheet->getSheetByName($range[0])) {
+ $extractedRange = str_replace('$', '', $range[1]);
+ $spreadsheet->addNamedRange(new NamedRange($name, $worksheet, $extractedRange));
+ }
+ }
+ }
+
+ // Return
+ return $spreadsheet;
+ }
+
+ private static function parseBorderAttributes($borderAttributes)
+ {
+ $styleArray = [];
+ if (isset($borderAttributes['Color'])) {
+ $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']);
+ }
+
+ switch ($borderAttributes['Style']) {
+ case '0':
+ $styleArray['borderStyle'] = Border::BORDER_NONE;
+
+ break;
+ case '1':
+ $styleArray['borderStyle'] = Border::BORDER_THIN;
+
+ break;
+ case '2':
+ $styleArray['borderStyle'] = Border::BORDER_MEDIUM;
+
+ break;
+ case '3':
+ $styleArray['borderStyle'] = Border::BORDER_SLANTDASHDOT;
+
+ break;
+ case '4':
+ $styleArray['borderStyle'] = Border::BORDER_DASHED;
+
+ break;
+ case '5':
+ $styleArray['borderStyle'] = Border::BORDER_THICK;
+
+ break;
+ case '6':
+ $styleArray['borderStyle'] = Border::BORDER_DOUBLE;
+
+ break;
+ case '7':
+ $styleArray['borderStyle'] = Border::BORDER_DOTTED;
+
+ break;
+ case '8':
+ $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHED;
+
+ break;
+ case '9':
+ $styleArray['borderStyle'] = Border::BORDER_DASHDOT;
+
+ break;
+ case '10':
+ $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOT;
+
+ break;
+ case '11':
+ $styleArray['borderStyle'] = Border::BORDER_DASHDOTDOT;
+
+ break;
+ case '12':
+ $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOTDOT;
+
+ break;
+ case '13':
+ $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOTDOT;
+
+ break;
+ }
+
+ return $styleArray;
+ }
+
+ private function parseRichText($is)
+ {
+ $value = new RichText();
+ $value->createText($is);
+
+ return $value;
+ }
+
+ private static function parseGnumericColour($gnmColour)
+ {
+ [$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour);
+ $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2);
+ $gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2);
+ $gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2);
+
+ return $gnmR . $gnmG . $gnmB;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php
new file mode 100755
index 0000000..2fe85b6
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php
@@ -0,0 +1,952 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader;
+
+use DOMDocument;
+use DOMElement;
+use DOMNode;
+use DOMText;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Color;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+/** PhpSpreadsheet root directory */
+class Html extends BaseReader
+{
+ /**
+ * Sample size to read to determine if it's HTML or not.
+ */
+ const TEST_SAMPLE_SIZE = 2048;
+
+ /**
+ * Input encoding.
+ *
+ * @var string
+ */
+ protected $inputEncoding = 'ANSI';
+
+ /**
+ * Sheet index to read.
+ *
+ * @var int
+ */
+ protected $sheetIndex = 0;
+
+ /**
+ * Formats.
+ *
+ * @var array
+ */
+ protected $formats = [
+ 'h1' => [
+ 'font' => [
+ 'bold' => true,
+ 'size' => 24,
+ ],
+ ], // Bold, 24pt
+ 'h2' => [
+ 'font' => [
+ 'bold' => true,
+ 'size' => 18,
+ ],
+ ], // Bold, 18pt
+ 'h3' => [
+ 'font' => [
+ 'bold' => true,
+ 'size' => 13.5,
+ ],
+ ], // Bold, 13.5pt
+ 'h4' => [
+ 'font' => [
+ 'bold' => true,
+ 'size' => 12,
+ ],
+ ], // Bold, 12pt
+ 'h5' => [
+ 'font' => [
+ 'bold' => true,
+ 'size' => 10,
+ ],
+ ], // Bold, 10pt
+ 'h6' => [
+ 'font' => [
+ 'bold' => true,
+ 'size' => 7.5,
+ ],
+ ], // Bold, 7.5pt
+ 'a' => [
+ 'font' => [
+ 'underline' => true,
+ 'color' => [
+ 'argb' => Color::COLOR_BLUE,
+ ],
+ ],
+ ], // Blue underlined
+ 'hr' => [
+ 'borders' => [
+ 'bottom' => [
+ 'borderStyle' => Border::BORDER_THIN,
+ 'color' => [
+ Color::COLOR_BLACK,
+ ],
+ ],
+ ],
+ ], // Bottom border
+ 'strong' => [
+ 'font' => [
+ 'bold' => true,
+ ],
+ ], // Bold
+ 'b' => [
+ 'font' => [
+ 'bold' => true,
+ ],
+ ], // Bold
+ 'i' => [
+ 'font' => [
+ 'italic' => true,
+ ],
+ ], // Italic
+ 'em' => [
+ 'font' => [
+ 'italic' => true,
+ ],
+ ], // Italic
+ ];
+
+ protected $rowspan = [];
+
+ /**
+ * Create a new HTML Reader instance.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->securityScanner = XmlScanner::getInstance($this);
+ }
+
+ /**
+ * Validate that the current file is an HTML file.
+ *
+ * @param string $pFilename
+ *
+ * @return bool
+ */
+ public function canRead($pFilename)
+ {
+ // Check if file exists
+ try {
+ $this->openFile($pFilename);
+ } catch (Exception $e) {
+ return false;
+ }
+
+ $beginning = $this->readBeginning();
+ $startWithTag = self::startsWithTag($beginning);
+ $containsTags = self::containsTags($beginning);
+ $endsWithTag = self::endsWithTag($this->readEnding());
+
+ fclose($this->fileHandle);
+
+ return $startWithTag && $containsTags && $endsWithTag;
+ }
+
+ private function readBeginning()
+ {
+ fseek($this->fileHandle, 0);
+
+ return fread($this->fileHandle, self::TEST_SAMPLE_SIZE);
+ }
+
+ private function readEnding()
+ {
+ $meta = stream_get_meta_data($this->fileHandle);
+ $filename = $meta['uri'];
+
+ $size = filesize($filename);
+ if ($size === 0) {
+ return '';
+ }
+
+ $blockSize = self::TEST_SAMPLE_SIZE;
+ if ($size < $blockSize) {
+ $blockSize = $size;
+ }
+
+ fseek($this->fileHandle, $size - $blockSize);
+
+ return fread($this->fileHandle, $blockSize);
+ }
+
+ private static function startsWithTag($data)
+ {
+ return '<' === substr(trim($data), 0, 1);
+ }
+
+ private static function endsWithTag($data)
+ {
+ return '>' === substr(trim($data), -1, 1);
+ }
+
+ private static function containsTags($data)
+ {
+ return strlen($data) !== strlen(strip_tags($data));
+ }
+
+ /**
+ * Loads Spreadsheet from file.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function load($pFilename)
+ {
+ // Create new Spreadsheet
+ $spreadsheet = new Spreadsheet();
+
+ // Load into this instance
+ return $this->loadIntoExisting($pFilename, $spreadsheet);
+ }
+
+ /**
+ * Set input encoding.
+ *
+ * @param string $pValue Input encoding, eg: 'ANSI'
+ *
+ * @return $this
+ */
+ public function setInputEncoding($pValue)
+ {
+ $this->inputEncoding = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get input encoding.
+ *
+ * @return string
+ */
+ public function getInputEncoding()
+ {
+ return $this->inputEncoding;
+ }
+
+ // Data Array used for testing only, should write to Spreadsheet object on completion of tests
+ protected $dataArray = [];
+
+ protected $tableLevel = 0;
+
+ protected $nestedColumn = ['A'];
+
+ protected function setTableStartColumn($column)
+ {
+ if ($this->tableLevel == 0) {
+ $column = 'A';
+ }
+ ++$this->tableLevel;
+ $this->nestedColumn[$this->tableLevel] = $column;
+
+ return $this->nestedColumn[$this->tableLevel];
+ }
+
+ protected function getTableStartColumn()
+ {
+ return $this->nestedColumn[$this->tableLevel];
+ }
+
+ protected function releaseTableStartColumn()
+ {
+ --$this->tableLevel;
+
+ return array_pop($this->nestedColumn);
+ }
+
+ protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent): void
+ {
+ if (is_string($cellContent)) {
+ // Simple String content
+ if (trim($cellContent) > '') {
+ // Only actually write it if there's content in the string
+ // Write to worksheet to be done here...
+ // ... we return the cell so we can mess about with styles more easily
+ $sheet->setCellValue($column . $row, $cellContent);
+ $this->dataArray[$row][$column] = $cellContent;
+ }
+ } else {
+ // We have a Rich Text run
+ // TODO
+ $this->dataArray[$row][$column] = 'RICH TEXT: ' . $cellContent;
+ }
+ $cellContent = (string) '';
+ }
+
+ /**
+ * @param int $row
+ * @param string $column
+ * @param string $cellContent
+ */
+ protected function processDomElement(DOMNode $element, Worksheet $sheet, &$row, &$column, &$cellContent): void
+ {
+ foreach ($element->childNodes as $child) {
+ if ($child instanceof DOMText) {
+ $domText = preg_replace('/\s+/u', ' ', trim($child->nodeValue));
+ if (is_string($cellContent)) {
+ // simply append the text if the cell content is a plain text string
+ $cellContent .= $domText;
+ }
+ // but if we have a rich text run instead, we need to append it correctly
+ // TODO
+ } elseif ($child instanceof DOMElement) {
+ $attributeArray = [];
+ foreach ($child->attributes as $attribute) {
+ $attributeArray[$attribute->name] = $attribute->value;
+ }
+
+ switch ($child->nodeName) {
+ case 'meta':
+ foreach ($attributeArray as $attributeName => $attributeValue) {
+ // Extract character set, so we can convert to UTF-8 if required
+ if ($attributeName === 'charset') {
+ $this->setInputEncoding($attributeValue);
+ }
+ }
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+
+ break;
+ case 'title':
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ $sheet->setTitle($cellContent, true, false);
+ $cellContent = '';
+
+ break;
+ case 'span':
+ case 'div':
+ case 'font':
+ case 'i':
+ case 'em':
+ case 'strong':
+ case 'b':
+ if (isset($attributeArray['class']) && $attributeArray['class'] === 'comment') {
+ $sheet->getComment($column . $row)
+ ->getText()
+ ->createTextRun($child->textContent);
+
+ break;
+ }
+
+ if ($cellContent > '') {
+ $cellContent .= ' ';
+ }
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ if ($cellContent > '') {
+ $cellContent .= ' ';
+ }
+
+ if (isset($this->formats[$child->nodeName])) {
+ $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
+ }
+
+ break;
+ case 'hr':
+ $this->flushCell($sheet, $column, $row, $cellContent);
+ ++$row;
+ if (isset($this->formats[$child->nodeName])) {
+ $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
+ } else {
+ $cellContent = '----------';
+ $this->flushCell($sheet, $column, $row, $cellContent);
+ }
+ ++$row;
+ // Add a break after a horizontal rule, simply by allowing the code to dropthru
+ // no break
+ case 'br':
+ if ($this->tableLevel > 0) {
+ // If we're inside a table, replace with a \n and set the cell to wrap
+ $cellContent .= "\n";
+ $sheet->getStyle($column . $row)->getAlignment()->setWrapText(true);
+ } else {
+ // Otherwise flush our existing content and move the row cursor on
+ $this->flushCell($sheet, $column, $row, $cellContent);
+ ++$row;
+ }
+
+ break;
+ case 'a':
+ foreach ($attributeArray as $attributeName => $attributeValue) {
+ switch ($attributeName) {
+ case 'href':
+ $sheet->getCell($column . $row)->getHyperlink()->setUrl($attributeValue);
+ if (isset($this->formats[$child->nodeName])) {
+ $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
+ }
+
+ break;
+ case 'class':
+ if ($attributeValue === 'comment-indicator') {
+ break; // Ignore - it's just a red square.
+ }
+ }
+ }
+ $cellContent .= ' ';
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+
+ break;
+ case 'h1':
+ case 'h2':
+ case 'h3':
+ case 'h4':
+ case 'h5':
+ case 'h6':
+ case 'ol':
+ case 'ul':
+ case 'p':
+ if ($this->tableLevel > 0) {
+ // If we're inside a table, replace with a \n
+ $cellContent .= "\n";
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ } else {
+ if ($cellContent > '') {
+ $this->flushCell($sheet, $column, $row, $cellContent);
+ ++$row;
+ }
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ $this->flushCell($sheet, $column, $row, $cellContent);
+
+ if (isset($this->formats[$child->nodeName])) {
+ $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
+ }
+
+ ++$row;
+ $column = 'A';
+ }
+
+ break;
+ case 'li':
+ if ($this->tableLevel > 0) {
+ // If we're inside a table, replace with a \n
+ $cellContent .= "\n";
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ } else {
+ if ($cellContent > '') {
+ $this->flushCell($sheet, $column, $row, $cellContent);
+ }
+ ++$row;
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ $this->flushCell($sheet, $column, $row, $cellContent);
+ $column = 'A';
+ }
+
+ break;
+ case 'img':
+ $this->insertImage($sheet, $column, $row, $attributeArray);
+
+ break;
+ case 'table':
+ $this->flushCell($sheet, $column, $row, $cellContent);
+ $column = $this->setTableStartColumn($column);
+ if ($this->tableLevel > 1) {
+ --$row;
+ }
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ $column = $this->releaseTableStartColumn();
+ if ($this->tableLevel > 1) {
+ ++$column;
+ } else {
+ ++$row;
+ }
+
+ break;
+ case 'thead':
+ case 'tbody':
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+
+ break;
+ case 'tr':
+ $column = $this->getTableStartColumn();
+ $cellContent = '';
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+
+ if (isset($attributeArray['height'])) {
+ $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
+ }
+
+ ++$row;
+
+ break;
+ case 'th':
+ case 'td':
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+
+ while (isset($this->rowspan[$column . $row])) {
+ ++$column;
+ }
+
+ // apply inline style
+ $this->applyInlineStyle($sheet, $row, $column, $attributeArray);
+
+ $this->flushCell($sheet, $column, $row, $cellContent);
+
+ if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
+ //create merging rowspan and colspan
+ $columnTo = $column;
+ for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
+ ++$columnTo;
+ }
+ $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1);
+ foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) {
+ $this->rowspan[$value] = true;
+ }
+ $sheet->mergeCells($range);
+ $column = $columnTo;
+ } elseif (isset($attributeArray['rowspan'])) {
+ //create merging rowspan
+ $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1);
+ foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) {
+ $this->rowspan[$value] = true;
+ }
+ $sheet->mergeCells($range);
+ } elseif (isset($attributeArray['colspan'])) {
+ //create merging colspan
+ $columnTo = $column;
+ for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
+ ++$columnTo;
+ }
+ $sheet->mergeCells($column . $row . ':' . $columnTo . $row);
+ $column = $columnTo;
+ } elseif (isset($attributeArray['bgcolor'])) {
+ $sheet->getStyle($column . $row)->applyFromArray(
+ [
+ 'fill' => [
+ 'fillType' => Fill::FILL_SOLID,
+ 'color' => ['rgb' => $attributeArray['bgcolor']],
+ ],
+ ]
+ );
+ }
+
+ if (isset($attributeArray['width'])) {
+ $sheet->getColumnDimension($column)->setWidth($attributeArray['width']);
+ }
+
+ if (isset($attributeArray['height'])) {
+ $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
+ }
+
+ if (isset($attributeArray['align'])) {
+ $sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']);
+ }
+
+ if (isset($attributeArray['valign'])) {
+ $sheet->getStyle($column . $row)->getAlignment()->setVertical($attributeArray['valign']);
+ }
+
+ if (isset($attributeArray['data-format'])) {
+ $sheet->getStyle($column . $row)->getNumberFormat()->setFormatCode($attributeArray['data-format']);
+ }
+
+ ++$column;
+
+ break;
+ case 'body':
+ $row = 1;
+ $column = 'A';
+ $cellContent = '';
+ $this->tableLevel = 0;
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+
+ break;
+ default:
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ }
+ }
+ }
+ }
+
+ /**
+ * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
+ {
+ // Validate
+ if (!$this->canRead($pFilename)) {
+ throw new Exception($pFilename . ' is an Invalid HTML file.');
+ }
+
+ // Create a new DOM object
+ $dom = new DOMDocument();
+ // Reload the HTML file into the DOM object
+ $loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scanFile($pFilename), 'HTML-ENTITIES', 'UTF-8'));
+ if ($loaded === false) {
+ throw new Exception('Failed to load ' . $pFilename . ' as a DOM Document');
+ }
+
+ return $this->loadDocument($dom, $spreadsheet);
+ }
+
+ /**
+ * Spreadsheet from content.
+ *
+ * @param string $content
+ */
+ public function loadFromString($content, ?Spreadsheet $spreadsheet = null): Spreadsheet
+ {
+ // Create a new DOM object
+ $dom = new DOMDocument();
+ // Reload the HTML file into the DOM object
+ $loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scan($content), 'HTML-ENTITIES', 'UTF-8'));
+ if ($loaded === false) {
+ throw new Exception('Failed to load content as a DOM Document');
+ }
+
+ return $this->loadDocument($dom, $spreadsheet ?? new Spreadsheet());
+ }
+
+ /**
+ * Loads PhpSpreadsheet from DOMDocument into PhpSpreadsheet instance.
+ */
+ private function loadDocument(DOMDocument $document, Spreadsheet $spreadsheet): Spreadsheet
+ {
+ while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
+ $spreadsheet->createSheet();
+ }
+ $spreadsheet->setActiveSheetIndex($this->sheetIndex);
+
+ // Discard white space
+ $document->preserveWhiteSpace = false;
+
+ $row = 0;
+ $column = 'A';
+ $content = '';
+ $this->rowspan = [];
+ $this->processDomElement($document, $spreadsheet->getActiveSheet(), $row, $column, $content);
+
+ // Return
+ return $spreadsheet;
+ }
+
+ /**
+ * Get sheet index.
+ *
+ * @return int
+ */
+ public function getSheetIndex()
+ {
+ return $this->sheetIndex;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param int $pValue Sheet index
+ *
+ * @return $this
+ */
+ public function setSheetIndex($pValue)
+ {
+ $this->sheetIndex = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Apply inline css inline style.
+ *
+ * NOTES :
+ * Currently only intended for td & th element,
+ * and only takes 'background-color' and 'color'; property with HEX color
+ *
+ * TODO :
+ * - Implement to other propertie, such as border
+ *
+ * @param Worksheet $sheet
+ * @param int $row
+ * @param string $column
+ * @param array $attributeArray
+ */
+ private function applyInlineStyle(&$sheet, $row, $column, $attributeArray): void
+ {
+ if (!isset($attributeArray['style'])) {
+ return;
+ }
+
+ $cellStyle = $sheet->getStyle($column . $row);
+
+ // add color styles (background & text) from dom element,currently support : td & th, using ONLY inline css style with RGB color
+ $styles = explode(';', $attributeArray['style']);
+ foreach ($styles as $st) {
+ $value = explode(':', $st);
+ $styleName = isset($value[0]) ? trim($value[0]) : null;
+ $styleValue = isset($value[1]) ? trim($value[1]) : null;
+
+ if (!$styleName) {
+ continue;
+ }
+
+ switch ($styleName) {
+ case 'background':
+ case 'background-color':
+ $styleColor = $this->getStyleColor($styleValue);
+
+ if (!$styleColor) {
+ continue 2;
+ }
+
+ $cellStyle->applyFromArray(['fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => $styleColor]]]);
+
+ break;
+ case 'color':
+ $styleColor = $this->getStyleColor($styleValue);
+
+ if (!$styleColor) {
+ continue 2;
+ }
+
+ $cellStyle->applyFromArray(['font' => ['color' => ['rgb' => $styleColor]]]);
+
+ break;
+
+ case 'border':
+ $this->setBorderStyle($cellStyle, $styleValue, 'allBorders');
+
+ break;
+
+ case 'border-top':
+ $this->setBorderStyle($cellStyle, $styleValue, 'top');
+
+ break;
+
+ case 'border-bottom':
+ $this->setBorderStyle($cellStyle, $styleValue, 'bottom');
+
+ break;
+
+ case 'border-left':
+ $this->setBorderStyle($cellStyle, $styleValue, 'left');
+
+ break;
+
+ case 'border-right':
+ $this->setBorderStyle($cellStyle, $styleValue, 'right');
+
+ break;
+
+ case 'font-size':
+ $cellStyle->getFont()->setSize(
+ (float) $styleValue
+ );
+
+ break;
+
+ case 'font-weight':
+ if ($styleValue === 'bold' || $styleValue >= 500) {
+ $cellStyle->getFont()->setBold(true);
+ }
+
+ break;
+
+ case 'font-style':
+ if ($styleValue === 'italic') {
+ $cellStyle->getFont()->setItalic(true);
+ }
+
+ break;
+
+ case 'font-family':
+ $cellStyle->getFont()->setName(str_replace('\'', '', $styleValue));
+
+ break;
+
+ case 'text-decoration':
+ switch ($styleValue) {
+ case 'underline':
+ $cellStyle->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
+
+ break;
+ case 'line-through':
+ $cellStyle->getFont()->setStrikethrough(true);
+
+ break;
+ }
+
+ break;
+
+ case 'text-align':
+ $cellStyle->getAlignment()->setHorizontal($styleValue);
+
+ break;
+
+ case 'vertical-align':
+ $cellStyle->getAlignment()->setVertical($styleValue);
+
+ break;
+
+ case 'width':
+ $sheet->getColumnDimension($column)->setWidth(
+ str_replace('px', '', $styleValue)
+ );
+
+ break;
+
+ case 'height':
+ $sheet->getRowDimension($row)->setRowHeight(
+ str_replace('px', '', $styleValue)
+ );
+
+ break;
+
+ case 'word-wrap':
+ $cellStyle->getAlignment()->setWrapText(
+ $styleValue === 'break-word'
+ );
+
+ break;
+
+ case 'text-indent':
+ $cellStyle->getAlignment()->setIndent(
+ (int) str_replace(['px'], '', $styleValue)
+ );
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * Check if has #, so we can get clean hex.
+ *
+ * @param $value
+ *
+ * @return null|string
+ */
+ public function getStyleColor($value)
+ {
+ if (strpos($value, '#') === 0) {
+ return substr($value, 1);
+ }
+
+ return null;
+ }
+
+ /**
+ * @param string $column
+ * @param int $row
+ */
+ private function insertImage(Worksheet $sheet, $column, $row, array $attributes): void
+ {
+ if (!isset($attributes['src'])) {
+ return;
+ }
+
+ $src = urldecode($attributes['src']);
+ $width = isset($attributes['width']) ? (float) $attributes['width'] : null;
+ $height = isset($attributes['height']) ? (float) $attributes['height'] : null;
+ $name = isset($attributes['alt']) ? (float) $attributes['alt'] : null;
+
+ $drawing = new Drawing();
+ $drawing->setPath($src);
+ $drawing->setWorksheet($sheet);
+ $drawing->setCoordinates($column . $row);
+ $drawing->setOffsetX(0);
+ $drawing->setOffsetY(10);
+ $drawing->setResizeProportional(true);
+
+ if ($name) {
+ $drawing->setName($name);
+ }
+
+ if ($width) {
+ $drawing->setWidth((int) $width);
+ }
+
+ if ($height) {
+ $drawing->setHeight((int) $height);
+ }
+
+ $sheet->getColumnDimension($column)->setWidth(
+ $drawing->getWidth() / 6
+ );
+
+ $sheet->getRowDimension($row)->setRowHeight(
+ $drawing->getHeight() * 0.9
+ );
+ }
+
+ /**
+ * Map html border style to PhpSpreadsheet border style.
+ *
+ * @param string $style
+ *
+ * @return null|string
+ */
+ public function getBorderStyle($style)
+ {
+ switch ($style) {
+ case 'solid':
+ return Border::BORDER_THIN;
+ case 'dashed':
+ return Border::BORDER_DASHED;
+ case 'dotted':
+ return Border::BORDER_DOTTED;
+ case 'medium':
+ return Border::BORDER_MEDIUM;
+ case 'thick':
+ return Border::BORDER_THICK;
+ case 'none':
+ return Border::BORDER_NONE;
+ case 'dash-dot':
+ return Border::BORDER_DASHDOT;
+ case 'dash-dot-dot':
+ return Border::BORDER_DASHDOTDOT;
+ case 'double':
+ return Border::BORDER_DOUBLE;
+ case 'hair':
+ return Border::BORDER_HAIR;
+ case 'medium-dash-dot':
+ return Border::BORDER_MEDIUMDASHDOT;
+ case 'medium-dash-dot-dot':
+ return Border::BORDER_MEDIUMDASHDOTDOT;
+ case 'medium-dashed':
+ return Border::BORDER_MEDIUMDASHED;
+ case 'slant-dash-dot':
+ return Border::BORDER_SLANTDASHDOT;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param string $styleValue
+ * @param string $type
+ */
+ private function setBorderStyle(Style $cellStyle, $styleValue, $type): void
+ {
+ if (trim($styleValue) === Border::BORDER_NONE) {
+ $borderStyle = Border::BORDER_NONE;
+ $color = null;
+ } else {
+ [, $borderStyle, $color] = explode(' ', $styleValue);
+ }
+
+ $cellStyle->applyFromArray([
+ 'borders' => [
+ $type => [
+ 'borderStyle' => $this->getBorderStyle($borderStyle),
+ 'color' => ['rgb' => $this->getStyleColor($color)],
+ ],
+ ],
+ ]);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php
new file mode 100755
index 0000000..ccfe05a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader;
+
+interface IReadFilter
+{
+ /**
+ * Should this cell be read?
+ *
+ * @param string $column Column address (as a string value like "A", or "IV")
+ * @param int $row Row number
+ * @param string $worksheetName Optional worksheet name
+ *
+ * @return bool
+ */
+ public function readCell($column, $row, $worksheetName = '');
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReader.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReader.php
new file mode 100755
index 0000000..a8bd360
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReader.php
@@ -0,0 +1,133 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader;
+
+interface IReader
+{
+ /**
+ * IReader constructor.
+ */
+ public function __construct();
+
+ /**
+ * Can the current IReader read the file?
+ *
+ * @param string $pFilename
+ *
+ * @return bool
+ */
+ public function canRead($pFilename);
+
+ /**
+ * Read data only?
+ * If this is true, then the Reader will only read data values for cells, it will not read any formatting information.
+ * If false (the default) it will read data and formatting.
+ *
+ * @return bool
+ */
+ public function getReadDataOnly();
+
+ /**
+ * Set read data only
+ * Set to true, to advise the Reader only to read data values for cells, and to ignore any formatting information.
+ * Set to false (the default) to advise the Reader to read both data and formatting for cells.
+ *
+ * @param bool $pValue
+ *
+ * @return IReader
+ */
+ public function setReadDataOnly($pValue);
+
+ /**
+ * Read empty cells?
+ * If this is true (the default), then the Reader will read data values for all cells, irrespective of value.
+ * If false it will not read data for cells containing a null value or an empty string.
+ *
+ * @return bool
+ */
+ public function getReadEmptyCells();
+
+ /**
+ * Set read empty cells
+ * Set to true (the default) to advise the Reader read data values for all cells, irrespective of value.
+ * Set to false to advise the Reader to ignore cells containing a null value or an empty string.
+ *
+ * @param bool $pValue
+ *
+ * @return IReader
+ */
+ public function setReadEmptyCells($pValue);
+
+ /**
+ * Read charts in workbook?
+ * If this is true, then the Reader will include any charts that exist in the workbook.
+ * Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value.
+ * If false (the default) it will ignore any charts defined in the workbook file.
+ *
+ * @return bool
+ */
+ public function getIncludeCharts();
+
+ /**
+ * Set read charts in workbook
+ * Set to true, to advise the Reader to include any charts that exist in the workbook.
+ * Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value.
+ * Set to false (the default) to discard charts.
+ *
+ * @param bool $pValue
+ *
+ * @return IReader
+ */
+ public function setIncludeCharts($pValue);
+
+ /**
+ * Get which sheets to load
+ * Returns either an array of worksheet names (the list of worksheets that should be loaded), or a null
+ * indicating that all worksheets in the workbook should be loaded.
+ *
+ * @return mixed
+ */
+ public function getLoadSheetsOnly();
+
+ /**
+ * Set which sheets to load.
+ *
+ * @param mixed $value
+ * This should be either an array of worksheet names to be loaded, or a string containing a single worksheet name.
+ * If NULL, then it tells the Reader to read all worksheets in the workbook
+ *
+ * @return IReader
+ */
+ public function setLoadSheetsOnly($value);
+
+ /**
+ * Set all sheets to load
+ * Tells the Reader to load all worksheets from the workbook.
+ *
+ * @return IReader
+ */
+ public function setLoadAllSheets();
+
+ /**
+ * Read filter.
+ *
+ * @return IReadFilter
+ */
+ public function getReadFilter();
+
+ /**
+ * Set read filter.
+ *
+ * @return IReader
+ */
+ public function setReadFilter(IReadFilter $pValue);
+
+ /**
+ * Loads PhpSpreadsheet from file.
+ *
+ * @param string $pFilename
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Spreadsheet
+ */
+ public function load($pFilename);
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php
new file mode 100755
index 0000000..d638d1f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php
@@ -0,0 +1,699 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader;
+
+use DateTime;
+use DateTimeZone;
+use DOMAttr;
+use DOMDocument;
+use DOMElement;
+use DOMNode;
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Reader\Ods\Properties as DocumentProperties;
+use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Settings;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use XMLReader;
+use ZipArchive;
+
+class Ods extends BaseReader
+{
+ /**
+ * Create a new Ods Reader instance.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->securityScanner = XmlScanner::getInstance($this);
+ }
+
+ /**
+ * Can the current IReader read the file?
+ *
+ * @param string $pFilename
+ *
+ * @return bool
+ */
+ public function canRead($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ $mimeType = 'UNKNOWN';
+
+ // Load file
+
+ $zip = new ZipArchive();
+ if ($zip->open($pFilename) === true) {
+ // check if it is an OOXML archive
+ $stat = $zip->statName('mimetype');
+ if ($stat && ($stat['size'] <= 255)) {
+ $mimeType = $zip->getFromName($stat['name']);
+ } elseif ($zip->statName('META-INF/manifest.xml')) {
+ $xml = simplexml_load_string(
+ $this->securityScanner->scan($zip->getFromName('META-INF/manifest.xml')),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $namespacesContent = $xml->getNamespaces(true);
+ if (isset($namespacesContent['manifest'])) {
+ $manifest = $xml->children($namespacesContent['manifest']);
+ foreach ($manifest as $manifestDataSet) {
+ $manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']);
+ if ($manifestAttributes->{'full-path'} == '/') {
+ $mimeType = (string) $manifestAttributes->{'media-type'};
+
+ break;
+ }
+ }
+ }
+ }
+
+ $zip->close();
+
+ return $mimeType === 'application/vnd.oasis.opendocument.spreadsheet';
+ }
+
+ return false;
+ }
+
+ /**
+ * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object.
+ *
+ * @param string $pFilename
+ *
+ * @return string[]
+ */
+ public function listWorksheetNames($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ $zip = new ZipArchive();
+ if (!$zip->open($pFilename)) {
+ throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.');
+ }
+
+ $worksheetNames = [];
+
+ $xml = new XMLReader();
+ $xml->xml(
+ $this->securityScanner->scanFile('zip://' . realpath($pFilename) . '#content.xml'),
+ null,
+ Settings::getLibXmlLoaderOptions()
+ );
+ $xml->setParserProperty(2, true);
+
+ // Step into the first level of content of the XML
+ $xml->read();
+ while ($xml->read()) {
+ // Quickly jump through to the office:body node
+ while ($xml->name !== 'office:body') {
+ if ($xml->isEmptyElement) {
+ $xml->read();
+ } else {
+ $xml->next();
+ }
+ }
+ // Now read each node until we find our first table:table node
+ while ($xml->read()) {
+ if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
+ // Loop through each table:table node reading the table:name attribute for each worksheet name
+ do {
+ $worksheetNames[] = $xml->getAttribute('table:name');
+ $xml->next();
+ } while ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT);
+ }
+ }
+ }
+
+ return $worksheetNames;
+ }
+
+ /**
+ * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
+ *
+ * @param string $pFilename
+ *
+ * @return array
+ */
+ public function listWorksheetInfo($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ $worksheetInfo = [];
+
+ $zip = new ZipArchive();
+ if (!$zip->open($pFilename)) {
+ throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.');
+ }
+
+ $xml = new XMLReader();
+ $xml->xml(
+ $this->securityScanner->scanFile('zip://' . realpath($pFilename) . '#content.xml'),
+ null,
+ Settings::getLibXmlLoaderOptions()
+ );
+ $xml->setParserProperty(2, true);
+
+ // Step into the first level of content of the XML
+ $xml->read();
+ while ($xml->read()) {
+ // Quickly jump through to the office:body node
+ while ($xml->name !== 'office:body') {
+ if ($xml->isEmptyElement) {
+ $xml->read();
+ } else {
+ $xml->next();
+ }
+ }
+ // Now read each node until we find our first table:table node
+ while ($xml->read()) {
+ if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
+ $worksheetNames[] = $xml->getAttribute('table:name');
+
+ $tmpInfo = [
+ 'worksheetName' => $xml->getAttribute('table:name'),
+ 'lastColumnLetter' => 'A',
+ 'lastColumnIndex' => 0,
+ 'totalRows' => 0,
+ 'totalColumns' => 0,
+ ];
+
+ // Loop through each child node of the table:table element reading
+ $currCells = 0;
+ do {
+ $xml->read();
+ if ($xml->name == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) {
+ $rowspan = $xml->getAttribute('table:number-rows-repeated');
+ $rowspan = empty($rowspan) ? 1 : $rowspan;
+ $tmpInfo['totalRows'] += $rowspan;
+ $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
+ $currCells = 0;
+ // Step into the row
+ $xml->read();
+ do {
+ if ($xml->name == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
+ if (!$xml->isEmptyElement) {
+ ++$currCells;
+ $xml->next();
+ } else {
+ $xml->read();
+ }
+ } elseif ($xml->name == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
+ $mergeSize = $xml->getAttribute('table:number-columns-repeated');
+ $currCells += (int) $mergeSize;
+ $xml->read();
+ } else {
+ $xml->read();
+ }
+ } while ($xml->name != 'table:table-row');
+ }
+ } while ($xml->name != 'table:table');
+
+ $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
+ $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
+ $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
+ $worksheetInfo[] = $tmpInfo;
+ }
+ }
+ }
+
+ return $worksheetInfo;
+ }
+
+ /**
+ * Loads PhpSpreadsheet from file.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function load($pFilename)
+ {
+ // Create new Spreadsheet
+ $spreadsheet = new Spreadsheet();
+
+ // Load into this instance
+ return $this->loadIntoExisting($pFilename, $spreadsheet);
+ }
+
+ /**
+ * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
+ {
+ File::assertFile($pFilename);
+
+ $timezoneObj = new DateTimeZone('Europe/London');
+ $GMT = new DateTimeZone('UTC');
+
+ $zip = new ZipArchive();
+ if (!$zip->open($pFilename)) {
+ throw new Exception("Could not open {$pFilename} for reading! Error opening file.");
+ }
+
+ // Meta
+
+ $xml = simplexml_load_string(
+ $this->securityScanner->scan($zip->getFromName('meta.xml')),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ if ($xml === false) {
+ throw new Exception('Unable to read data from {$pFilename}');
+ }
+
+ $namespacesMeta = $xml->getNamespaces(true);
+
+ (new DocumentProperties($spreadsheet))->load($xml, $namespacesMeta);
+
+ // Content
+
+ $dom = new DOMDocument('1.01', 'UTF-8');
+ $dom->loadXML(
+ $this->securityScanner->scan($zip->getFromName('content.xml')),
+ Settings::getLibXmlLoaderOptions()
+ );
+
+ $officeNs = $dom->lookupNamespaceUri('office');
+ $tableNs = $dom->lookupNamespaceUri('table');
+ $textNs = $dom->lookupNamespaceUri('text');
+ $xlinkNs = $dom->lookupNamespaceUri('xlink');
+
+ $spreadsheets = $dom->getElementsByTagNameNS($officeNs, 'body')
+ ->item(0)
+ ->getElementsByTagNameNS($officeNs, 'spreadsheet');
+
+ foreach ($spreadsheets as $workbookData) {
+ /** @var DOMElement $workbookData */
+ $tables = $workbookData->getElementsByTagNameNS($tableNs, 'table');
+
+ $worksheetID = 0;
+ foreach ($tables as $worksheetDataSet) {
+ /** @var DOMElement $worksheetDataSet */
+ $worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name');
+
+ // Check loadSheetsOnly
+ if (isset($this->loadSheetsOnly)
+ && $worksheetName
+ && !in_array($worksheetName, $this->loadSheetsOnly)) {
+ continue;
+ }
+
+ // Create sheet
+ if ($worksheetID > 0) {
+ $spreadsheet->createSheet(); // First sheet is added by default
+ }
+ $spreadsheet->setActiveSheetIndex($worksheetID);
+
+ if ($worksheetName) {
+ // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
+ // formula cells... during the load, all formulae should be correct, and we're simply
+ // bringing the worksheet name in line with the formula, not the reverse
+ $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
+ }
+
+ // Go through every child of table element
+ $rowID = 1;
+ foreach ($worksheetDataSet->childNodes as $childNode) {
+ /** @var DOMElement $childNode */
+
+ // Filter elements which are not under the "table" ns
+ if ($childNode->namespaceURI != $tableNs) {
+ continue;
+ }
+
+ $key = $childNode->nodeName;
+
+ // Remove ns from node name
+ if (strpos($key, ':') !== false) {
+ $keyChunks = explode(':', $key);
+ $key = array_pop($keyChunks);
+ }
+
+ switch ($key) {
+ case 'table-header-rows':
+ /// TODO :: Figure this out. This is only a partial implementation I guess.
+ // ($rowData it's not used at all and I'm not sure that PHPExcel
+ // has an API for this)
+
+// foreach ($rowData as $keyRowData => $cellData) {
+// $rowData = $cellData;
+// break;
+// }
+ break;
+ case 'table-row':
+ if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) {
+ $rowRepeats = $childNode->getAttributeNS($tableNs, 'number-rows-repeated');
+ } else {
+ $rowRepeats = 1;
+ }
+
+ $columnID = 'A';
+ foreach ($childNode->childNodes as $key => $cellData) {
+ // @var \DOMElement $cellData
+
+ if ($this->getReadFilter() !== null) {
+ if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
+ ++$columnID;
+
+ continue;
+ }
+ }
+
+ // Initialize variables
+ $formatting = $hyperlink = null;
+ $hasCalculatedValue = false;
+ $cellDataFormula = '';
+
+ if ($cellData->hasAttributeNS($tableNs, 'formula')) {
+ $cellDataFormula = $cellData->getAttributeNS($tableNs, 'formula');
+ $hasCalculatedValue = true;
+ }
+
+ // Annotations
+ $annotation = $cellData->getElementsByTagNameNS($officeNs, 'annotation');
+
+ if ($annotation->length > 0) {
+ $textNode = $annotation->item(0)->getElementsByTagNameNS($textNs, 'p');
+
+ if ($textNode->length > 0) {
+ $text = $this->scanElementForText($textNode->item(0));
+
+ $spreadsheet->getActiveSheet()
+ ->getComment($columnID . $rowID)
+ ->setText($this->parseRichText($text));
+// ->setAuthor( $author )
+ }
+ }
+
+ // Content
+
+ /** @var DOMElement[] $paragraphs */
+ $paragraphs = [];
+
+ foreach ($cellData->childNodes as $item) {
+ /** @var DOMElement $item */
+
+ // Filter text:p elements
+ if ($item->nodeName == 'text:p') {
+ $paragraphs[] = $item;
+ }
+ }
+
+ if (count($paragraphs) > 0) {
+ // Consolidate if there are multiple p records (maybe with spans as well)
+ $dataArray = [];
+
+ // Text can have multiple text:p and within those, multiple text:span.
+ // text:p newlines, but text:span does not.
+ // Also, here we assume there is no text data is span fields are specified, since
+ // we have no way of knowing proper positioning anyway.
+
+ foreach ($paragraphs as $pData) {
+ $dataArray[] = $this->scanElementForText($pData);
+ }
+ $allCellDataText = implode("\n", $dataArray);
+
+ $type = $cellData->getAttributeNS($officeNs, 'value-type');
+
+ switch ($type) {
+ case 'string':
+ $type = DataType::TYPE_STRING;
+ $dataValue = $allCellDataText;
+
+ foreach ($paragraphs as $paragraph) {
+ $link = $paragraph->getElementsByTagNameNS($textNs, 'a');
+ if ($link->length > 0) {
+ $hyperlink = $link->item(0)->getAttributeNS($xlinkNs, 'href');
+ }
+ }
+
+ break;
+ case 'boolean':
+ $type = DataType::TYPE_BOOL;
+ $dataValue = ($allCellDataText == 'TRUE') ? true : false;
+
+ break;
+ case 'percentage':
+ $type = DataType::TYPE_NUMERIC;
+ $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
+
+ if (floor($dataValue) == $dataValue) {
+ $dataValue = (int) $dataValue;
+ }
+ $formatting = NumberFormat::FORMAT_PERCENTAGE_00;
+
+ break;
+ case 'currency':
+ $type = DataType::TYPE_NUMERIC;
+ $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
+
+ if (floor($dataValue) == $dataValue) {
+ $dataValue = (int) $dataValue;
+ }
+ $formatting = NumberFormat::FORMAT_CURRENCY_USD_SIMPLE;
+
+ break;
+ case 'float':
+ $type = DataType::TYPE_NUMERIC;
+ $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
+
+ if (floor($dataValue) == $dataValue) {
+ if ($dataValue == (int) $dataValue) {
+ $dataValue = (int) $dataValue;
+ } else {
+ $dataValue = (float) $dataValue;
+ }
+ }
+
+ break;
+ case 'date':
+ $type = DataType::TYPE_NUMERIC;
+ $value = $cellData->getAttributeNS($officeNs, 'date-value');
+
+ $dateObj = new DateTime($value, $GMT);
+ $dateObj->setTimeZone($timezoneObj);
+ [$year, $month, $day, $hour, $minute, $second] = explode(
+ ' ',
+ $dateObj->format('Y m d H i s')
+ );
+
+ $dataValue = Date::formattedPHPToExcel(
+ (int) $year,
+ (int) $month,
+ (int) $day,
+ (int) $hour,
+ (int) $minute,
+ (int) $second
+ );
+
+ if ($dataValue != floor($dataValue)) {
+ $formatting = NumberFormat::FORMAT_DATE_XLSX15
+ . ' '
+ . NumberFormat::FORMAT_DATE_TIME4;
+ } else {
+ $formatting = NumberFormat::FORMAT_DATE_XLSX15;
+ }
+
+ break;
+ case 'time':
+ $type = DataType::TYPE_NUMERIC;
+
+ $timeValue = $cellData->getAttributeNS($officeNs, 'time-value');
+
+ $dataValue = Date::PHPToExcel(
+ strtotime(
+ '01-01-1970 ' . implode(':', sscanf($timeValue, 'PT%dH%dM%dS'))
+ )
+ );
+ $formatting = NumberFormat::FORMAT_DATE_TIME4;
+
+ break;
+ default:
+ $dataValue = null;
+ }
+ } else {
+ $type = DataType::TYPE_NULL;
+ $dataValue = null;
+ }
+
+ if ($hasCalculatedValue) {
+ $type = DataType::TYPE_FORMULA;
+ $cellDataFormula = substr($cellDataFormula, strpos($cellDataFormula, ':=') + 1);
+ $temp = explode('"', $cellDataFormula);
+ $tKey = false;
+ foreach ($temp as &$value) {
+ // Only replace in alternate array entries (i.e. non-quoted blocks)
+ if ($tKey = !$tKey) {
+ // Cell range reference in another sheet
+ $value = preg_replace('/\[([^\.]+)\.([^\.]+):\.([^\.]+)\]/U', '$1!$2:$3', $value);
+
+ // Cell reference in another sheet
+ $value = preg_replace('/\[([^\.]+)\.([^\.]+)\]/U', '$1!$2', $value);
+
+ // Cell range reference
+ $value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/U', '$1:$2', $value);
+
+ // Simple cell reference
+ $value = preg_replace('/\[\.([^\.]+)\]/U', '$1', $value);
+
+ $value = Calculation::translateSeparator(';', ',', $value, $inBraces);
+ }
+ }
+ unset($value);
+
+ // Then rebuild the formula string
+ $cellDataFormula = implode('"', $temp);
+ }
+
+ if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
+ $colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated');
+ } else {
+ $colRepeats = 1;
+ }
+
+ if ($type !== null) {
+ for ($i = 0; $i < $colRepeats; ++$i) {
+ if ($i > 0) {
+ ++$columnID;
+ }
+
+ if ($type !== DataType::TYPE_NULL) {
+ for ($rowAdjust = 0; $rowAdjust < $rowRepeats; ++$rowAdjust) {
+ $rID = $rowID + $rowAdjust;
+
+ $cell = $spreadsheet->getActiveSheet()
+ ->getCell($columnID . $rID);
+
+ // Set value
+ if ($hasCalculatedValue) {
+ $cell->setValueExplicit($cellDataFormula, $type);
+ } else {
+ $cell->setValueExplicit($dataValue, $type);
+ }
+
+ if ($hasCalculatedValue) {
+ $cell->setCalculatedValue($dataValue);
+ }
+
+ // Set other properties
+ if ($formatting !== null) {
+ $spreadsheet->getActiveSheet()
+ ->getStyle($columnID . $rID)
+ ->getNumberFormat()
+ ->setFormatCode($formatting);
+ } else {
+ $spreadsheet->getActiveSheet()
+ ->getStyle($columnID . $rID)
+ ->getNumberFormat()
+ ->setFormatCode(NumberFormat::FORMAT_GENERAL);
+ }
+
+ if ($hyperlink !== null) {
+ $cell->getHyperlink()
+ ->setUrl($hyperlink);
+ }
+ }
+ }
+ }
+ }
+
+ // Merged cells
+ if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')
+ || $cellData->hasAttributeNS($tableNs, 'number-rows-spanned')
+ ) {
+ if (($type !== DataType::TYPE_NULL) || (!$this->readDataOnly)) {
+ $columnTo = $columnID;
+
+ if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) {
+ $columnIndex = Coordinate::columnIndexFromString($columnID);
+ $columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned');
+ $columnIndex -= 2;
+
+ $columnTo = Coordinate::stringFromColumnIndex($columnIndex + 1);
+ }
+
+ $rowTo = $rowID;
+
+ if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) {
+ $rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1;
+ }
+
+ $cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo;
+ $spreadsheet->getActiveSheet()->mergeCells($cellRange);
+ }
+ }
+
+ ++$columnID;
+ }
+ $rowID += $rowRepeats;
+
+ break;
+ }
+ }
+ ++$worksheetID;
+ }
+ }
+
+ // Return
+ return $spreadsheet;
+ }
+
+ /**
+ * Recursively scan element.
+ *
+ * @return string
+ */
+ protected function scanElementForText(DOMNode $element)
+ {
+ $str = '';
+ foreach ($element->childNodes as $child) {
+ /** @var DOMNode $child */
+ if ($child->nodeType == XML_TEXT_NODE) {
+ $str .= $child->nodeValue;
+ } elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:s') {
+ // It's a space
+
+ // Multiple spaces?
+ /** @var DOMAttr $cAttr */
+ $cAttr = $child->attributes->getNamedItem('c');
+ if ($cAttr) {
+ $multiplier = (int) $cAttr->nodeValue;
+ } else {
+ $multiplier = 1;
+ }
+
+ $str .= str_repeat(' ', $multiplier);
+ }
+
+ if ($child->hasChildNodes()) {
+ $str .= $this->scanElementForText($child);
+ }
+ }
+
+ return $str;
+ }
+
+ /**
+ * @param string $is
+ *
+ * @return RichText
+ */
+ private function parseRichText($is)
+ {
+ $value = new RichText();
+ $value->createText($is);
+
+ return $value;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php
new file mode 100755
index 0000000..c0b309a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php
@@ -0,0 +1,136 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
+
+use PhpOffice\PhpSpreadsheet\Document\Properties as DocumentProperties;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use SimpleXMLElement;
+
+class Properties
+{
+ private $spreadsheet;
+
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ $this->spreadsheet = $spreadsheet;
+ }
+
+ public function load(SimpleXMLElement $xml, $namespacesMeta): void
+ {
+ $docProps = $this->spreadsheet->getProperties();
+ $officeProperty = $xml->children($namespacesMeta['office']);
+ foreach ($officeProperty as $officePropertyData) {
+ // @var \SimpleXMLElement $officePropertyData
+ if (isset($namespacesMeta['dc'])) {
+ $officePropertiesDC = $officePropertyData->children($namespacesMeta['dc']);
+ $this->setCoreProperties($docProps, $officePropertiesDC);
+ }
+
+ $officePropertyMeta = (object) [];
+ if (isset($namespacesMeta['dc'])) {
+ $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']);
+ }
+ foreach ($officePropertyMeta as $propertyName => $propertyValue) {
+ $this->setMetaProperties($namespacesMeta, $propertyValue, $propertyName, $docProps);
+ }
+ }
+ }
+
+ private function setCoreProperties(DocumentProperties $docProps, SimpleXMLElement $officePropertyDC): void
+ {
+ foreach ($officePropertyDC as $propertyName => $propertyValue) {
+ $propertyValue = (string) $propertyValue;
+ switch ($propertyName) {
+ case 'title':
+ $docProps->setTitle($propertyValue);
+
+ break;
+ case 'subject':
+ $docProps->setSubject($propertyValue);
+
+ break;
+ case 'creator':
+ $docProps->setCreator($propertyValue);
+ $docProps->setLastModifiedBy($propertyValue);
+
+ break;
+ case 'creation-date':
+ $creationDate = strtotime($propertyValue);
+ $docProps->setCreated($creationDate);
+ $docProps->setModified($creationDate);
+
+ break;
+ case 'keyword':
+ $docProps->setKeywords($propertyValue);
+
+ break;
+ case 'description':
+ $docProps->setDescription($propertyValue);
+
+ break;
+ }
+ }
+ }
+
+ private function setMetaProperties(
+ $namespacesMeta,
+ SimpleXMLElement $propertyValue,
+ $propertyName,
+ DocumentProperties $docProps
+ ): void {
+ $propertyValueAttributes = $propertyValue->attributes($namespacesMeta['meta']);
+ $propertyValue = (string) $propertyValue;
+ switch ($propertyName) {
+ case 'initial-creator':
+ $docProps->setCreator($propertyValue);
+
+ break;
+ case 'keyword':
+ $docProps->setKeywords($propertyValue);
+
+ break;
+ case 'creation-date':
+ $creationDate = strtotime($propertyValue);
+ $docProps->setCreated($creationDate);
+
+ break;
+ case 'user-defined':
+ $this->setUserDefinedProperty($propertyValueAttributes, $propertyValue, $docProps);
+
+ break;
+ }
+ }
+
+ private function setUserDefinedProperty($propertyValueAttributes, $propertyValue, DocumentProperties $docProps): void
+ {
+ $propertyValueName = '';
+ $propertyValueType = DocumentProperties::PROPERTY_TYPE_STRING;
+ foreach ($propertyValueAttributes as $key => $value) {
+ if ($key == 'name') {
+ $propertyValueName = (string) $value;
+ } elseif ($key == 'value-type') {
+ switch ($value) {
+ case 'date':
+ $propertyValue = DocumentProperties::convertProperty($propertyValue, 'date');
+ $propertyValueType = DocumentProperties::PROPERTY_TYPE_DATE;
+
+ break;
+ case 'boolean':
+ $propertyValue = DocumentProperties::convertProperty($propertyValue, 'bool');
+ $propertyValueType = DocumentProperties::PROPERTY_TYPE_BOOLEAN;
+
+ break;
+ case 'float':
+ $propertyValue = DocumentProperties::convertProperty($propertyValue, 'r4');
+ $propertyValueType = DocumentProperties::PROPERTY_TYPE_FLOAT;
+
+ break;
+ default:
+ $propertyValueType = DocumentProperties::PROPERTY_TYPE_STRING;
+ }
+ }
+ }
+
+ $docProps->setCustomProperty($propertyValueName, $propertyValue, $propertyValueType);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php
new file mode 100755
index 0000000..55bba63
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php
@@ -0,0 +1,150 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Security;
+
+use PhpOffice\PhpSpreadsheet\Reader;
+use PhpOffice\PhpSpreadsheet\Settings;
+
+class XmlScanner
+{
+ /**
+ * String used to identify risky xml elements.
+ *
+ * @var string
+ */
+ private $pattern;
+
+ private $callback;
+
+ private static $libxmlDisableEntityLoaderValue;
+
+ public function __construct($pattern = '<!DOCTYPE')
+ {
+ $this->pattern = $pattern;
+
+ $this->disableEntityLoaderCheck();
+
+ // A fatal error will bypass the destructor, so we register a shutdown here
+ register_shutdown_function([__CLASS__, 'shutdown']);
+ }
+
+ public static function getInstance(Reader\IReader $reader)
+ {
+ switch (true) {
+ case $reader instanceof Reader\Html:
+ return new self('<!ENTITY');
+ case $reader instanceof Reader\Xlsx:
+ case $reader instanceof Reader\Xml:
+ case $reader instanceof Reader\Ods:
+ case $reader instanceof Reader\Gnumeric:
+ return new self('<!DOCTYPE');
+ default:
+ return new self('<!DOCTYPE');
+ }
+ }
+
+ public static function threadSafeLibxmlDisableEntityLoaderAvailability()
+ {
+ if (PHP_MAJOR_VERSION == 7) {
+ switch (PHP_MINOR_VERSION) {
+ case 2:
+ return PHP_RELEASE_VERSION >= 1;
+ case 1:
+ return PHP_RELEASE_VERSION >= 13;
+ case 0:
+ return PHP_RELEASE_VERSION >= 27;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private function disableEntityLoaderCheck(): void
+ {
+ if (Settings::getLibXmlDisableEntityLoader()) {
+ $libxmlDisableEntityLoaderValue = libxml_disable_entity_loader(true);
+
+ if (self::$libxmlDisableEntityLoaderValue === null) {
+ self::$libxmlDisableEntityLoaderValue = $libxmlDisableEntityLoaderValue;
+ }
+ }
+ }
+
+ public static function shutdown(): void
+ {
+ if (self::$libxmlDisableEntityLoaderValue !== null) {
+ libxml_disable_entity_loader(self::$libxmlDisableEntityLoaderValue);
+ self::$libxmlDisableEntityLoaderValue = null;
+ }
+ }
+
+ public function __destruct()
+ {
+ self::shutdown();
+ }
+
+ public function setAdditionalCallback(callable $callback): void
+ {
+ $this->callback = $callback;
+ }
+
+ private function toUtf8($xml)
+ {
+ $pattern = '/encoding="(.*?)"/';
+ $result = preg_match($pattern, $xml, $matches);
+ $charset = strtoupper($result ? $matches[1] : 'UTF-8');
+
+ if ($charset !== 'UTF-8') {
+ $xml = mb_convert_encoding($xml, 'UTF-8', $charset);
+
+ $result = preg_match($pattern, $xml, $matches);
+ $charset = strtoupper($result ? $matches[1] : 'UTF-8');
+ if ($charset !== 'UTF-8') {
+ throw new Reader\Exception('Suspicious Double-encoded XML, spreadsheet file load() aborted to prevent XXE/XEE attacks');
+ }
+ }
+
+ return $xml;
+ }
+
+ /**
+ * Scan the XML for use of <!ENTITY to prevent XXE/XEE attacks.
+ *
+ * @param mixed $xml
+ *
+ * @return string
+ */
+ public function scan($xml)
+ {
+ $this->disableEntityLoaderCheck();
+
+ $xml = $this->toUtf8($xml);
+
+ // Don't rely purely on libxml_disable_entity_loader()
+ $pattern = '/\\0?' . implode('\\0?', str_split($this->pattern)) . '\\0?/';
+
+ if (preg_match($pattern, $xml)) {
+ throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks');
+ }
+
+ if ($this->callback !== null && is_callable($this->callback)) {
+ $xml = call_user_func($this->callback, $xml);
+ }
+
+ return $xml;
+ }
+
+ /**
+ * Scan theXML for use of <!ENTITY to prevent XXE/XEE attacks.
+ *
+ * @param string $filestream
+ *
+ * @return string
+ */
+ public function scanFile($filestream)
+ {
+ return $this->scan(file_get_contents($filestream));
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php
new file mode 100755
index 0000000..f40eba7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php
@@ -0,0 +1,489 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+
+class Slk extends BaseReader
+{
+ /**
+ * Input encoding.
+ *
+ * @var string
+ */
+ private $inputEncoding = 'ANSI';
+
+ /**
+ * Sheet index to read.
+ *
+ * @var int
+ */
+ private $sheetIndex = 0;
+
+ /**
+ * Formats.
+ *
+ * @var array
+ */
+ private $formats = [];
+
+ /**
+ * Format Count.
+ *
+ * @var int
+ */
+ private $format = 0;
+
+ /**
+ * Create a new SYLK Reader instance.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Validate that the current file is a SYLK file.
+ *
+ * @param string $pFilename
+ *
+ * @return bool
+ */
+ public function canRead($pFilename)
+ {
+ // Check if file exists
+ try {
+ $this->openFile($pFilename);
+ } catch (Exception $e) {
+ return false;
+ }
+
+ // Read sample data (first 2 KB will do)
+ $data = fread($this->fileHandle, 2048);
+
+ // Count delimiters in file
+ $delimiterCount = substr_count($data, ';');
+ $hasDelimiter = $delimiterCount > 0;
+
+ // Analyze first line looking for ID; signature
+ $lines = explode("\n", $data);
+ $hasId = substr($lines[0], 0, 4) === 'ID;P';
+
+ fclose($this->fileHandle);
+
+ return $hasDelimiter && $hasId;
+ }
+
+ /**
+ * Set input encoding.
+ *
+ * @param string $pValue Input encoding, eg: 'ANSI'
+ *
+ * @return $this
+ */
+ public function setInputEncoding($pValue)
+ {
+ $this->inputEncoding = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get input encoding.
+ *
+ * @return string
+ */
+ public function getInputEncoding()
+ {
+ return $this->inputEncoding;
+ }
+
+ /**
+ * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
+ *
+ * @param string $pFilename
+ *
+ * @return array
+ */
+ public function listWorksheetInfo($pFilename)
+ {
+ // Open file
+ if (!$this->canRead($pFilename)) {
+ throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
+ }
+ $this->openFile($pFilename);
+ $fileHandle = $this->fileHandle;
+ rewind($fileHandle);
+
+ $worksheetInfo = [];
+ $worksheetInfo[0]['worksheetName'] = 'Worksheet';
+ $worksheetInfo[0]['lastColumnLetter'] = 'A';
+ $worksheetInfo[0]['lastColumnIndex'] = 0;
+ $worksheetInfo[0]['totalRows'] = 0;
+ $worksheetInfo[0]['totalColumns'] = 0;
+
+ // loop through one row (line) at a time in the file
+ $rowIndex = 0;
+ while (($rowData = fgets($fileHandle)) !== false) {
+ $columnIndex = 0;
+
+ // convert SYLK encoded $rowData to UTF-8
+ $rowData = StringHelper::SYLKtoUTF8($rowData);
+
+ // explode each row at semicolons while taking into account that literal semicolon (;)
+ // is escaped like this (;;)
+ $rowData = explode("\t", str_replace('¤', ';', str_replace(';', "\t", str_replace(';;', '¤', rtrim($rowData)))));
+
+ $dataType = array_shift($rowData);
+ if ($dataType == 'C') {
+ // Read cell value data
+ foreach ($rowData as $rowDatum) {
+ switch ($rowDatum[0]) {
+ case 'C':
+ case 'X':
+ $columnIndex = substr($rowDatum, 1) - 1;
+
+ break;
+ case 'R':
+ case 'Y':
+ $rowIndex = substr($rowDatum, 1);
+
+ break;
+ }
+
+ $worksheetInfo[0]['totalRows'] = max($worksheetInfo[0]['totalRows'], $rowIndex);
+ $worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], $columnIndex);
+ }
+ }
+ }
+
+ $worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1);
+ $worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1;
+
+ // Close file
+ fclose($fileHandle);
+
+ return $worksheetInfo;
+ }
+
+ /**
+ * Loads PhpSpreadsheet from file.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function load($pFilename)
+ {
+ // Create new Spreadsheet
+ $spreadsheet = new Spreadsheet();
+
+ // Load into this instance
+ return $this->loadIntoExisting($pFilename, $spreadsheet);
+ }
+
+ /**
+ * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
+ {
+ // Open file
+ if (!$this->canRead($pFilename)) {
+ throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
+ }
+ $this->openFile($pFilename);
+ $fileHandle = $this->fileHandle;
+ rewind($fileHandle);
+
+ // Create new Worksheets
+ while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
+ $spreadsheet->createSheet();
+ }
+ $spreadsheet->setActiveSheetIndex($this->sheetIndex);
+
+ $fromFormats = ['\-', '\ '];
+ $toFormats = ['-', ' '];
+
+ // Loop through file
+ $column = $row = '';
+
+ // loop through one row (line) at a time in the file
+ while (($rowData = fgets($fileHandle)) !== false) {
+ // convert SYLK encoded $rowData to UTF-8
+ $rowData = StringHelper::SYLKtoUTF8($rowData);
+
+ // explode each row at semicolons while taking into account that literal semicolon (;)
+ // is escaped like this (;;)
+ $rowData = explode("\t", str_replace('¤', ';', str_replace(';', "\t", str_replace(';;', '¤', rtrim($rowData)))));
+
+ $dataType = array_shift($rowData);
+ // Read shared styles
+ if ($dataType == 'P') {
+ $formatArray = [];
+ foreach ($rowData as $rowDatum) {
+ switch ($rowDatum[0]) {
+ case 'P':
+ $formatArray['numberFormat']['formatCode'] = str_replace($fromFormats, $toFormats, substr($rowDatum, 1));
+
+ break;
+ case 'E':
+ case 'F':
+ $formatArray['font']['name'] = substr($rowDatum, 1);
+
+ break;
+ case 'L':
+ $formatArray['font']['size'] = substr($rowDatum, 1);
+
+ break;
+ case 'S':
+ $styleSettings = substr($rowDatum, 1);
+ $iMax = strlen($styleSettings);
+ for ($i = 0; $i < $iMax; ++$i) {
+ switch ($styleSettings[$i]) {
+ case 'I':
+ $formatArray['font']['italic'] = true;
+
+ break;
+ case 'D':
+ $formatArray['font']['bold'] = true;
+
+ break;
+ case 'T':
+ $formatArray['borders']['top']['borderStyle'] = Border::BORDER_THIN;
+
+ break;
+ case 'B':
+ $formatArray['borders']['bottom']['borderStyle'] = Border::BORDER_THIN;
+
+ break;
+ case 'L':
+ $formatArray['borders']['left']['borderStyle'] = Border::BORDER_THIN;
+
+ break;
+ case 'R':
+ $formatArray['borders']['right']['borderStyle'] = Border::BORDER_THIN;
+
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+ $this->formats['P' . $this->format++] = $formatArray;
+ // Read cell value data
+ } elseif ($dataType == 'C') {
+ $hasCalculatedValue = false;
+ $cellData = $cellDataFormula = '';
+ foreach ($rowData as $rowDatum) {
+ switch ($rowDatum[0]) {
+ case 'C':
+ case 'X':
+ $column = substr($rowDatum, 1);
+
+ break;
+ case 'R':
+ case 'Y':
+ $row = substr($rowDatum, 1);
+
+ break;
+ case 'K':
+ $cellData = substr($rowDatum, 1);
+
+ break;
+ case 'E':
+ $cellDataFormula = '=' . substr($rowDatum, 1);
+ // Convert R1C1 style references to A1 style references (but only when not quoted)
+ $temp = explode('"', $cellDataFormula);
+ $key = false;
+ foreach ($temp as &$value) {
+ // Only count/replace in alternate array entries
+ if ($key = !$key) {
+ preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
+ // Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
+ // through the formula from left to right. Reversing means that we work right to left.through
+ // the formula
+ $cellReferences = array_reverse($cellReferences);
+ // Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent,
+ // then modify the formula to use that new reference
+ foreach ($cellReferences as $cellReference) {
+ $rowReference = $cellReference[2][0];
+ // Empty R reference is the current row
+ if ($rowReference == '') {
+ $rowReference = $row;
+ }
+ // Bracketed R references are relative to the current row
+ if ($rowReference[0] == '[') {
+ $rowReference = $row + trim($rowReference, '[]');
+ }
+ $columnReference = $cellReference[4][0];
+ // Empty C reference is the current column
+ if ($columnReference == '') {
+ $columnReference = $column;
+ }
+ // Bracketed C references are relative to the current column
+ if ($columnReference[0] == '[') {
+ $columnReference = $column + trim($columnReference, '[]');
+ }
+ $A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference;
+
+ $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
+ }
+ }
+ }
+ unset($value);
+ // Then rebuild the formula string
+ $cellDataFormula = implode('"', $temp);
+ $hasCalculatedValue = true;
+
+ break;
+ }
+ }
+ $columnLetter = Coordinate::stringFromColumnIndex($column);
+ $cellData = Calculation::unwrapResult($cellData);
+
+ // Set cell value
+ $spreadsheet->getActiveSheet()->getCell($columnLetter . $row)->setValue(($hasCalculatedValue) ? $cellDataFormula : $cellData);
+ if ($hasCalculatedValue) {
+ $cellData = Calculation::unwrapResult($cellData);
+ $spreadsheet->getActiveSheet()->getCell($columnLetter . $row)->setCalculatedValue($cellData);
+ }
+ // Read cell formatting
+ } elseif ($dataType == 'F') {
+ $formatStyle = $columnWidth = $styleSettings = '';
+ $styleData = [];
+ foreach ($rowData as $rowDatum) {
+ switch ($rowDatum[0]) {
+ case 'C':
+ case 'X':
+ $column = substr($rowDatum, 1);
+
+ break;
+ case 'R':
+ case 'Y':
+ $row = substr($rowDatum, 1);
+
+ break;
+ case 'P':
+ $formatStyle = $rowDatum;
+
+ break;
+ case 'W':
+ [$startCol, $endCol, $columnWidth] = explode(' ', substr($rowDatum, 1));
+
+ break;
+ case 'S':
+ $styleSettings = substr($rowDatum, 1);
+ $iMax = strlen($styleSettings);
+ for ($i = 0; $i < $iMax; ++$i) {
+ switch ($styleSettings[$i]) {
+ case 'I':
+ $styleData['font']['italic'] = true;
+
+ break;
+ case 'D':
+ $styleData['font']['bold'] = true;
+
+ break;
+ case 'T':
+ $styleData['borders']['top']['borderStyle'] = Border::BORDER_THIN;
+
+ break;
+ case 'B':
+ $styleData['borders']['bottom']['borderStyle'] = Border::BORDER_THIN;
+
+ break;
+ case 'L':
+ $styleData['borders']['left']['borderStyle'] = Border::BORDER_THIN;
+
+ break;
+ case 'R':
+ $styleData['borders']['right']['borderStyle'] = Border::BORDER_THIN;
+
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+ if (($formatStyle > '') && ($column > '') && ($row > '')) {
+ $columnLetter = Coordinate::stringFromColumnIndex($column);
+ if (isset($this->formats[$formatStyle])) {
+ $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($this->formats[$formatStyle]);
+ }
+ }
+ if ((!empty($styleData)) && ($column > '') && ($row > '')) {
+ $columnLetter = Coordinate::stringFromColumnIndex($column);
+ $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($styleData);
+ }
+ if ($columnWidth > '') {
+ if ($startCol == $endCol) {
+ $startCol = Coordinate::stringFromColumnIndex($startCol);
+ $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth($columnWidth);
+ } else {
+ $startCol = Coordinate::stringFromColumnIndex($startCol);
+ $endCol = Coordinate::stringFromColumnIndex($endCol);
+ $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth($columnWidth);
+ do {
+ $spreadsheet->getActiveSheet()->getColumnDimension(++$startCol)->setWidth($columnWidth);
+ } while ($startCol != $endCol);
+ }
+ }
+ } else {
+ foreach ($rowData as $rowDatum) {
+ switch ($rowDatum[0]) {
+ case 'C':
+ case 'X':
+ $column = substr($rowDatum, 1);
+
+ break;
+ case 'R':
+ case 'Y':
+ $row = substr($rowDatum, 1);
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Close file
+ fclose($fileHandle);
+
+ // Return
+ return $spreadsheet;
+ }
+
+ /**
+ * Get sheet index.
+ *
+ * @return int
+ */
+ public function getSheetIndex()
+ {
+ return $this->sheetIndex;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param int $pValue Sheet index
+ *
+ * @return $this
+ */
+ public function setSheetIndex($pValue)
+ {
+ $this->sheetIndex = $pValue;
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php
new file mode 100755
index 0000000..b206f8a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php
@@ -0,0 +1,7933 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\NamedRange;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Shared\CodePage;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+use PhpOffice\PhpSpreadsheet\Shared\Escher;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Shared\OLE;
+use PhpOffice\PhpSpreadsheet\Shared\OLERead;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\Borders;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PhpOffice\PhpSpreadsheet\Style\Protection;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
+use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+// Original file header of ParseXL (used as the base for this class):
+// --------------------------------------------------------------------------------
+// Adapted from Excel_Spreadsheet_Reader developed by users bizon153,
+// trex005, and mmp11 (SourceForge.net)
+// https://sourceforge.net/projects/phpexcelreader/
+// Primary changes made by canyoncasa (dvc) for ParseXL 1.00 ...
+// Modelled moreso after Perl Excel Parse/Write modules
+// Added Parse_Excel_Spreadsheet object
+// Reads a whole worksheet or tab as row,column array or as
+// associated hash of indexed rows and named column fields
+// Added variables for worksheet (tab) indexes and names
+// Added an object call for loading individual woorksheets
+// Changed default indexing defaults to 0 based arrays
+// Fixed date/time and percent formats
+// Includes patches found at SourceForge...
+// unicode patch by nobody
+// unpack("d") machine depedency patch by matchy
+// boundsheet utf16 patch by bjaenichen
+// Renamed functions for shorter names
+// General code cleanup and rigor, including <80 column width
+// Included a testcase Excel file and PHP example calls
+// Code works for PHP 5.x
+
+// Primary changes made by canyoncasa (dvc) for ParseXL 1.10 ...
+// http://sourceforge.net/tracker/index.php?func=detail&aid=1466964&group_id=99160&atid=623334
+// Decoding of formula conditions, results, and tokens.
+// Support for user-defined named cells added as an array "namedcells"
+// Patch code for user-defined named cells supports single cells only.
+// NOTE: this patch only works for BIFF8 as BIFF5-7 use a different
+// external sheet reference structure
+class Xls extends BaseReader
+{
+ // ParseXL definitions
+ const XLS_BIFF8 = 0x0600;
+ const XLS_BIFF7 = 0x0500;
+ const XLS_WORKBOOKGLOBALS = 0x0005;
+ const XLS_WORKSHEET = 0x0010;
+
+ // record identifiers
+ const XLS_TYPE_FORMULA = 0x0006;
+ const XLS_TYPE_EOF = 0x000a;
+ const XLS_TYPE_PROTECT = 0x0012;
+ const XLS_TYPE_OBJECTPROTECT = 0x0063;
+ const XLS_TYPE_SCENPROTECT = 0x00dd;
+ const XLS_TYPE_PASSWORD = 0x0013;
+ const XLS_TYPE_HEADER = 0x0014;
+ const XLS_TYPE_FOOTER = 0x0015;
+ const XLS_TYPE_EXTERNSHEET = 0x0017;
+ const XLS_TYPE_DEFINEDNAME = 0x0018;
+ const XLS_TYPE_VERTICALPAGEBREAKS = 0x001a;
+ const XLS_TYPE_HORIZONTALPAGEBREAKS = 0x001b;
+ const XLS_TYPE_NOTE = 0x001c;
+ const XLS_TYPE_SELECTION = 0x001d;
+ const XLS_TYPE_DATEMODE = 0x0022;
+ const XLS_TYPE_EXTERNNAME = 0x0023;
+ const XLS_TYPE_LEFTMARGIN = 0x0026;
+ const XLS_TYPE_RIGHTMARGIN = 0x0027;
+ const XLS_TYPE_TOPMARGIN = 0x0028;
+ const XLS_TYPE_BOTTOMMARGIN = 0x0029;
+ const XLS_TYPE_PRINTGRIDLINES = 0x002b;
+ const XLS_TYPE_FILEPASS = 0x002f;
+ const XLS_TYPE_FONT = 0x0031;
+ const XLS_TYPE_CONTINUE = 0x003c;
+ const XLS_TYPE_PANE = 0x0041;
+ const XLS_TYPE_CODEPAGE = 0x0042;
+ const XLS_TYPE_DEFCOLWIDTH = 0x0055;
+ const XLS_TYPE_OBJ = 0x005d;
+ const XLS_TYPE_COLINFO = 0x007d;
+ const XLS_TYPE_IMDATA = 0x007f;
+ const XLS_TYPE_SHEETPR = 0x0081;
+ const XLS_TYPE_HCENTER = 0x0083;
+ const XLS_TYPE_VCENTER = 0x0084;
+ const XLS_TYPE_SHEET = 0x0085;
+ const XLS_TYPE_PALETTE = 0x0092;
+ const XLS_TYPE_SCL = 0x00a0;
+ const XLS_TYPE_PAGESETUP = 0x00a1;
+ const XLS_TYPE_MULRK = 0x00bd;
+ const XLS_TYPE_MULBLANK = 0x00be;
+ const XLS_TYPE_DBCELL = 0x00d7;
+ const XLS_TYPE_XF = 0x00e0;
+ const XLS_TYPE_MERGEDCELLS = 0x00e5;
+ const XLS_TYPE_MSODRAWINGGROUP = 0x00eb;
+ const XLS_TYPE_MSODRAWING = 0x00ec;
+ const XLS_TYPE_SST = 0x00fc;
+ const XLS_TYPE_LABELSST = 0x00fd;
+ const XLS_TYPE_EXTSST = 0x00ff;
+ const XLS_TYPE_EXTERNALBOOK = 0x01ae;
+ const XLS_TYPE_DATAVALIDATIONS = 0x01b2;
+ const XLS_TYPE_TXO = 0x01b6;
+ const XLS_TYPE_HYPERLINK = 0x01b8;
+ const XLS_TYPE_DATAVALIDATION = 0x01be;
+ const XLS_TYPE_DIMENSION = 0x0200;
+ const XLS_TYPE_BLANK = 0x0201;
+ const XLS_TYPE_NUMBER = 0x0203;
+ const XLS_TYPE_LABEL = 0x0204;
+ const XLS_TYPE_BOOLERR = 0x0205;
+ const XLS_TYPE_STRING = 0x0207;
+ const XLS_TYPE_ROW = 0x0208;
+ const XLS_TYPE_INDEX = 0x020b;
+ const XLS_TYPE_ARRAY = 0x0221;
+ const XLS_TYPE_DEFAULTROWHEIGHT = 0x0225;
+ const XLS_TYPE_WINDOW2 = 0x023e;
+ const XLS_TYPE_RK = 0x027e;
+ const XLS_TYPE_STYLE = 0x0293;
+ const XLS_TYPE_FORMAT = 0x041e;
+ const XLS_TYPE_SHAREDFMLA = 0x04bc;
+ const XLS_TYPE_BOF = 0x0809;
+ const XLS_TYPE_SHEETPROTECTION = 0x0867;
+ const XLS_TYPE_RANGEPROTECTION = 0x0868;
+ const XLS_TYPE_SHEETLAYOUT = 0x0862;
+ const XLS_TYPE_XFEXT = 0x087d;
+ const XLS_TYPE_PAGELAYOUTVIEW = 0x088b;
+ const XLS_TYPE_UNKNOWN = 0xffff;
+
+ // Encryption type
+ const MS_BIFF_CRYPTO_NONE = 0;
+ const MS_BIFF_CRYPTO_XOR = 1;
+ const MS_BIFF_CRYPTO_RC4 = 2;
+
+ // Size of stream blocks when using RC4 encryption
+ const REKEY_BLOCK = 0x400;
+
+ /**
+ * Summary Information stream data.
+ *
+ * @var string
+ */
+ private $summaryInformation;
+
+ /**
+ * Extended Summary Information stream data.
+ *
+ * @var string
+ */
+ private $documentSummaryInformation;
+
+ /**
+ * Workbook stream data. (Includes workbook globals substream as well as sheet substreams).
+ *
+ * @var string
+ */
+ private $data;
+
+ /**
+ * Size in bytes of $this->data.
+ *
+ * @var int
+ */
+ private $dataSize;
+
+ /**
+ * Current position in stream.
+ *
+ * @var int
+ */
+ private $pos;
+
+ /**
+ * Workbook to be returned by the reader.
+ *
+ * @var Spreadsheet
+ */
+ private $spreadsheet;
+
+ /**
+ * Worksheet that is currently being built by the reader.
+ *
+ * @var Worksheet
+ */
+ private $phpSheet;
+
+ /**
+ * BIFF version.
+ *
+ * @var int
+ */
+ private $version;
+
+ /**
+ * Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95)
+ * For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE'.
+ *
+ * @var string
+ */
+ private $codepage;
+
+ /**
+ * Shared formats.
+ *
+ * @var array
+ */
+ private $formats;
+
+ /**
+ * Shared fonts.
+ *
+ * @var array
+ */
+ private $objFonts;
+
+ /**
+ * Color palette.
+ *
+ * @var array
+ */
+ private $palette;
+
+ /**
+ * Worksheets.
+ *
+ * @var array
+ */
+ private $sheets;
+
+ /**
+ * External books.
+ *
+ * @var array
+ */
+ private $externalBooks;
+
+ /**
+ * REF structures. Only applies to BIFF8.
+ *
+ * @var array
+ */
+ private $ref;
+
+ /**
+ * External names.
+ *
+ * @var array
+ */
+ private $externalNames;
+
+ /**
+ * Defined names.
+ *
+ * @var array
+ */
+ private $definedname;
+
+ /**
+ * Shared strings. Only applies to BIFF8.
+ *
+ * @var array
+ */
+ private $sst;
+
+ /**
+ * Panes are frozen? (in sheet currently being read). See WINDOW2 record.
+ *
+ * @var bool
+ */
+ private $frozen;
+
+ /**
+ * Fit printout to number of pages? (in sheet currently being read). See SHEETPR record.
+ *
+ * @var bool
+ */
+ private $isFitToPages;
+
+ /**
+ * Objects. One OBJ record contributes with one entry.
+ *
+ * @var array
+ */
+ private $objs;
+
+ /**
+ * Text Objects. One TXO record corresponds with one entry.
+ *
+ * @var array
+ */
+ private $textObjects;
+
+ /**
+ * Cell Annotations (BIFF8).
+ *
+ * @var array
+ */
+ private $cellNotes;
+
+ /**
+ * The combined MSODRAWINGGROUP data.
+ *
+ * @var string
+ */
+ private $drawingGroupData;
+
+ /**
+ * The combined MSODRAWING data (per sheet).
+ *
+ * @var string
+ */
+ private $drawingData;
+
+ /**
+ * Keep track of XF index.
+ *
+ * @var int
+ */
+ private $xfIndex;
+
+ /**
+ * Mapping of XF index (that is a cell XF) to final index in cellXf collection.
+ *
+ * @var array
+ */
+ private $mapCellXfIndex;
+
+ /**
+ * Mapping of XF index (that is a style XF) to final index in cellStyleXf collection.
+ *
+ * @var array
+ */
+ private $mapCellStyleXfIndex;
+
+ /**
+ * The shared formulas in a sheet. One SHAREDFMLA record contributes with one value.
+ *
+ * @var array
+ */
+ private $sharedFormulas;
+
+ /**
+ * The shared formula parts in a sheet. One FORMULA record contributes with one value if it
+ * refers to a shared formula.
+ *
+ * @var array
+ */
+ private $sharedFormulaParts;
+
+ /**
+ * The type of encryption in use.
+ *
+ * @var int
+ */
+ private $encryption = 0;
+
+ /**
+ * The position in the stream after which contents are encrypted.
+ *
+ * @var int
+ */
+ private $encryptionStartPos = false;
+
+ /**
+ * The current RC4 decryption object.
+ *
+ * @var Xls\RC4
+ */
+ private $rc4Key;
+
+ /**
+ * The position in the stream that the RC4 decryption object was left at.
+ *
+ * @var int
+ */
+ private $rc4Pos = 0;
+
+ /**
+ * The current MD5 context state.
+ *
+ * @var string
+ */
+ private $md5Ctxt;
+
+ /**
+ * @var int
+ */
+ private $textObjRef;
+
+ /**
+ * @var string
+ */
+ private $baseCell;
+
+ /**
+ * Create a new Xls Reader instance.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Can the current IReader read the file?
+ *
+ * @param string $pFilename
+ *
+ * @return bool
+ */
+ public function canRead($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ try {
+ // Use ParseXL for the hard work.
+ $ole = new OLERead();
+
+ // get excel data
+ $ole->read($pFilename);
+
+ return true;
+ } catch (PhpSpreadsheetException $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object.
+ *
+ * @param string $pFilename
+ *
+ * @return array
+ */
+ public function listWorksheetNames($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ $worksheetNames = [];
+
+ // Read the OLE file
+ $this->loadOLE($pFilename);
+
+ // total byte size of Excel data (workbook global substream + sheet substreams)
+ $this->dataSize = strlen($this->data);
+
+ $this->pos = 0;
+ $this->sheets = [];
+
+ // Parse Workbook Global Substream
+ while ($this->pos < $this->dataSize) {
+ $code = self::getUInt2d($this->data, $this->pos);
+
+ switch ($code) {
+ case self::XLS_TYPE_BOF:
+ $this->readBof();
+
+ break;
+ case self::XLS_TYPE_SHEET:
+ $this->readSheet();
+
+ break;
+ case self::XLS_TYPE_EOF:
+ $this->readDefault();
+
+ break 2;
+ default:
+ $this->readDefault();
+
+ break;
+ }
+ }
+
+ foreach ($this->sheets as $sheet) {
+ if ($sheet['sheetType'] != 0x00) {
+ // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
+ continue;
+ }
+
+ $worksheetNames[] = $sheet['name'];
+ }
+
+ return $worksheetNames;
+ }
+
+ /**
+ * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
+ *
+ * @param string $pFilename
+ *
+ * @return array
+ */
+ public function listWorksheetInfo($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ $worksheetInfo = [];
+
+ // Read the OLE file
+ $this->loadOLE($pFilename);
+
+ // total byte size of Excel data (workbook global substream + sheet substreams)
+ $this->dataSize = strlen($this->data);
+
+ // initialize
+ $this->pos = 0;
+ $this->sheets = [];
+
+ // Parse Workbook Global Substream
+ while ($this->pos < $this->dataSize) {
+ $code = self::getUInt2d($this->data, $this->pos);
+
+ switch ($code) {
+ case self::XLS_TYPE_BOF:
+ $this->readBof();
+
+ break;
+ case self::XLS_TYPE_SHEET:
+ $this->readSheet();
+
+ break;
+ case self::XLS_TYPE_EOF:
+ $this->readDefault();
+
+ break 2;
+ default:
+ $this->readDefault();
+
+ break;
+ }
+ }
+
+ // Parse the individual sheets
+ foreach ($this->sheets as $sheet) {
+ if ($sheet['sheetType'] != 0x00) {
+ // 0x00: Worksheet
+ // 0x02: Chart
+ // 0x06: Visual Basic module
+ continue;
+ }
+
+ $tmpInfo = [];
+ $tmpInfo['worksheetName'] = $sheet['name'];
+ $tmpInfo['lastColumnLetter'] = 'A';
+ $tmpInfo['lastColumnIndex'] = 0;
+ $tmpInfo['totalRows'] = 0;
+ $tmpInfo['totalColumns'] = 0;
+
+ $this->pos = $sheet['offset'];
+
+ while ($this->pos <= $this->dataSize - 4) {
+ $code = self::getUInt2d($this->data, $this->pos);
+
+ switch ($code) {
+ case self::XLS_TYPE_RK:
+ case self::XLS_TYPE_LABELSST:
+ case self::XLS_TYPE_NUMBER:
+ case self::XLS_TYPE_FORMULA:
+ case self::XLS_TYPE_BOOLERR:
+ case self::XLS_TYPE_LABEL:
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ $rowIndex = self::getUInt2d($recordData, 0) + 1;
+ $columnIndex = self::getUInt2d($recordData, 2);
+
+ $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
+ $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
+
+ break;
+ case self::XLS_TYPE_BOF:
+ $this->readBof();
+
+ break;
+ case self::XLS_TYPE_EOF:
+ $this->readDefault();
+
+ break 2;
+ default:
+ $this->readDefault();
+
+ break;
+ }
+ }
+
+ $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
+ $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
+
+ $worksheetInfo[] = $tmpInfo;
+ }
+
+ return $worksheetInfo;
+ }
+
+ /**
+ * Loads PhpSpreadsheet from file.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function load($pFilename)
+ {
+ // Read the OLE file
+ $this->loadOLE($pFilename);
+
+ // Initialisations
+ $this->spreadsheet = new Spreadsheet();
+ $this->spreadsheet->removeSheetByIndex(0); // remove 1st sheet
+ if (!$this->readDataOnly) {
+ $this->spreadsheet->removeCellStyleXfByIndex(0); // remove the default style
+ $this->spreadsheet->removeCellXfByIndex(0); // remove the default style
+ }
+
+ // Read the summary information stream (containing meta data)
+ $this->readSummaryInformation();
+
+ // Read the Additional document summary information stream (containing application-specific meta data)
+ $this->readDocumentSummaryInformation();
+
+ // total byte size of Excel data (workbook global substream + sheet substreams)
+ $this->dataSize = strlen($this->data);
+
+ // initialize
+ $this->pos = 0;
+ $this->codepage = 'CP1252';
+ $this->formats = [];
+ $this->objFonts = [];
+ $this->palette = [];
+ $this->sheets = [];
+ $this->externalBooks = [];
+ $this->ref = [];
+ $this->definedname = [];
+ $this->sst = [];
+ $this->drawingGroupData = '';
+ $this->xfIndex = '';
+ $this->mapCellXfIndex = [];
+ $this->mapCellStyleXfIndex = [];
+
+ // Parse Workbook Global Substream
+ while ($this->pos < $this->dataSize) {
+ $code = self::getUInt2d($this->data, $this->pos);
+
+ switch ($code) {
+ case self::XLS_TYPE_BOF:
+ $this->readBof();
+
+ break;
+ case self::XLS_TYPE_FILEPASS:
+ $this->readFilepass();
+
+ break;
+ case self::XLS_TYPE_CODEPAGE:
+ $this->readCodepage();
+
+ break;
+ case self::XLS_TYPE_DATEMODE:
+ $this->readDateMode();
+
+ break;
+ case self::XLS_TYPE_FONT:
+ $this->readFont();
+
+ break;
+ case self::XLS_TYPE_FORMAT:
+ $this->readFormat();
+
+ break;
+ case self::XLS_TYPE_XF:
+ $this->readXf();
+
+ break;
+ case self::XLS_TYPE_XFEXT:
+ $this->readXfExt();
+
+ break;
+ case self::XLS_TYPE_STYLE:
+ $this->readStyle();
+
+ break;
+ case self::XLS_TYPE_PALETTE:
+ $this->readPalette();
+
+ break;
+ case self::XLS_TYPE_SHEET:
+ $this->readSheet();
+
+ break;
+ case self::XLS_TYPE_EXTERNALBOOK:
+ $this->readExternalBook();
+
+ break;
+ case self::XLS_TYPE_EXTERNNAME:
+ $this->readExternName();
+
+ break;
+ case self::XLS_TYPE_EXTERNSHEET:
+ $this->readExternSheet();
+
+ break;
+ case self::XLS_TYPE_DEFINEDNAME:
+ $this->readDefinedName();
+
+ break;
+ case self::XLS_TYPE_MSODRAWINGGROUP:
+ $this->readMsoDrawingGroup();
+
+ break;
+ case self::XLS_TYPE_SST:
+ $this->readSst();
+
+ break;
+ case self::XLS_TYPE_EOF:
+ $this->readDefault();
+
+ break 2;
+ default:
+ $this->readDefault();
+
+ break;
+ }
+ }
+
+ // Resolve indexed colors for font, fill, and border colors
+ // Cannot be resolved already in XF record, because PALETTE record comes afterwards
+ if (!$this->readDataOnly) {
+ foreach ($this->objFonts as $objFont) {
+ if (isset($objFont->colorIndex)) {
+ $color = Xls\Color::map($objFont->colorIndex, $this->palette, $this->version);
+ $objFont->getColor()->setRGB($color['rgb']);
+ }
+ }
+
+ foreach ($this->spreadsheet->getCellXfCollection() as $objStyle) {
+ // fill start and end color
+ $fill = $objStyle->getFill();
+
+ if (isset($fill->startcolorIndex)) {
+ $startColor = Xls\Color::map($fill->startcolorIndex, $this->palette, $this->version);
+ $fill->getStartColor()->setRGB($startColor['rgb']);
+ }
+ if (isset($fill->endcolorIndex)) {
+ $endColor = Xls\Color::map($fill->endcolorIndex, $this->palette, $this->version);
+ $fill->getEndColor()->setRGB($endColor['rgb']);
+ }
+
+ // border colors
+ $top = $objStyle->getBorders()->getTop();
+ $right = $objStyle->getBorders()->getRight();
+ $bottom = $objStyle->getBorders()->getBottom();
+ $left = $objStyle->getBorders()->getLeft();
+ $diagonal = $objStyle->getBorders()->getDiagonal();
+
+ if (isset($top->colorIndex)) {
+ $borderTopColor = Xls\Color::map($top->colorIndex, $this->palette, $this->version);
+ $top->getColor()->setRGB($borderTopColor['rgb']);
+ }
+ if (isset($right->colorIndex)) {
+ $borderRightColor = Xls\Color::map($right->colorIndex, $this->palette, $this->version);
+ $right->getColor()->setRGB($borderRightColor['rgb']);
+ }
+ if (isset($bottom->colorIndex)) {
+ $borderBottomColor = Xls\Color::map($bottom->colorIndex, $this->palette, $this->version);
+ $bottom->getColor()->setRGB($borderBottomColor['rgb']);
+ }
+ if (isset($left->colorIndex)) {
+ $borderLeftColor = Xls\Color::map($left->colorIndex, $this->palette, $this->version);
+ $left->getColor()->setRGB($borderLeftColor['rgb']);
+ }
+ if (isset($diagonal->colorIndex)) {
+ $borderDiagonalColor = Xls\Color::map($diagonal->colorIndex, $this->palette, $this->version);
+ $diagonal->getColor()->setRGB($borderDiagonalColor['rgb']);
+ }
+ }
+ }
+
+ // treat MSODRAWINGGROUP records, workbook-level Escher
+ if (!$this->readDataOnly && $this->drawingGroupData) {
+ $escherWorkbook = new Escher();
+ $reader = new Xls\Escher($escherWorkbook);
+ $escherWorkbook = $reader->load($this->drawingGroupData);
+ }
+
+ // Parse the individual sheets
+ foreach ($this->sheets as $sheet) {
+ if ($sheet['sheetType'] != 0x00) {
+ // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
+ continue;
+ }
+
+ // check if sheet should be skipped
+ if (isset($this->loadSheetsOnly) && !in_array($sheet['name'], $this->loadSheetsOnly)) {
+ continue;
+ }
+
+ // add sheet to PhpSpreadsheet object
+ $this->phpSheet = $this->spreadsheet->createSheet();
+ // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
+ // cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
+ // name in line with the formula, not the reverse
+ $this->phpSheet->setTitle($sheet['name'], false, false);
+ $this->phpSheet->setSheetState($sheet['sheetState']);
+
+ $this->pos = $sheet['offset'];
+
+ // Initialize isFitToPages. May change after reading SHEETPR record.
+ $this->isFitToPages = false;
+
+ // Initialize drawingData
+ $this->drawingData = '';
+
+ // Initialize objs
+ $this->objs = [];
+
+ // Initialize shared formula parts
+ $this->sharedFormulaParts = [];
+
+ // Initialize shared formulas
+ $this->sharedFormulas = [];
+
+ // Initialize text objs
+ $this->textObjects = [];
+
+ // Initialize cell annotations
+ $this->cellNotes = [];
+ $this->textObjRef = -1;
+
+ while ($this->pos <= $this->dataSize - 4) {
+ $code = self::getUInt2d($this->data, $this->pos);
+
+ switch ($code) {
+ case self::XLS_TYPE_BOF:
+ $this->readBof();
+
+ break;
+ case self::XLS_TYPE_PRINTGRIDLINES:
+ $this->readPrintGridlines();
+
+ break;
+ case self::XLS_TYPE_DEFAULTROWHEIGHT:
+ $this->readDefaultRowHeight();
+
+ break;
+ case self::XLS_TYPE_SHEETPR:
+ $this->readSheetPr();
+
+ break;
+ case self::XLS_TYPE_HORIZONTALPAGEBREAKS:
+ $this->readHorizontalPageBreaks();
+
+ break;
+ case self::XLS_TYPE_VERTICALPAGEBREAKS:
+ $this->readVerticalPageBreaks();
+
+ break;
+ case self::XLS_TYPE_HEADER:
+ $this->readHeader();
+
+ break;
+ case self::XLS_TYPE_FOOTER:
+ $this->readFooter();
+
+ break;
+ case self::XLS_TYPE_HCENTER:
+ $this->readHcenter();
+
+ break;
+ case self::XLS_TYPE_VCENTER:
+ $this->readVcenter();
+
+ break;
+ case self::XLS_TYPE_LEFTMARGIN:
+ $this->readLeftMargin();
+
+ break;
+ case self::XLS_TYPE_RIGHTMARGIN:
+ $this->readRightMargin();
+
+ break;
+ case self::XLS_TYPE_TOPMARGIN:
+ $this->readTopMargin();
+
+ break;
+ case self::XLS_TYPE_BOTTOMMARGIN:
+ $this->readBottomMargin();
+
+ break;
+ case self::XLS_TYPE_PAGESETUP:
+ $this->readPageSetup();
+
+ break;
+ case self::XLS_TYPE_PROTECT:
+ $this->readProtect();
+
+ break;
+ case self::XLS_TYPE_SCENPROTECT:
+ $this->readScenProtect();
+
+ break;
+ case self::XLS_TYPE_OBJECTPROTECT:
+ $this->readObjectProtect();
+
+ break;
+ case self::XLS_TYPE_PASSWORD:
+ $this->readPassword();
+
+ break;
+ case self::XLS_TYPE_DEFCOLWIDTH:
+ $this->readDefColWidth();
+
+ break;
+ case self::XLS_TYPE_COLINFO:
+ $this->readColInfo();
+
+ break;
+ case self::XLS_TYPE_DIMENSION:
+ $this->readDefault();
+
+ break;
+ case self::XLS_TYPE_ROW:
+ $this->readRow();
+
+ break;
+ case self::XLS_TYPE_DBCELL:
+ $this->readDefault();
+
+ break;
+ case self::XLS_TYPE_RK:
+ $this->readRk();
+
+ break;
+ case self::XLS_TYPE_LABELSST:
+ $this->readLabelSst();
+
+ break;
+ case self::XLS_TYPE_MULRK:
+ $this->readMulRk();
+
+ break;
+ case self::XLS_TYPE_NUMBER:
+ $this->readNumber();
+
+ break;
+ case self::XLS_TYPE_FORMULA:
+ $this->readFormula();
+
+ break;
+ case self::XLS_TYPE_SHAREDFMLA:
+ $this->readSharedFmla();
+
+ break;
+ case self::XLS_TYPE_BOOLERR:
+ $this->readBoolErr();
+
+ break;
+ case self::XLS_TYPE_MULBLANK:
+ $this->readMulBlank();
+
+ break;
+ case self::XLS_TYPE_LABEL:
+ $this->readLabel();
+
+ break;
+ case self::XLS_TYPE_BLANK:
+ $this->readBlank();
+
+ break;
+ case self::XLS_TYPE_MSODRAWING:
+ $this->readMsoDrawing();
+
+ break;
+ case self::XLS_TYPE_OBJ:
+ $this->readObj();
+
+ break;
+ case self::XLS_TYPE_WINDOW2:
+ $this->readWindow2();
+
+ break;
+ case self::XLS_TYPE_PAGELAYOUTVIEW:
+ $this->readPageLayoutView();
+
+ break;
+ case self::XLS_TYPE_SCL:
+ $this->readScl();
+
+ break;
+ case self::XLS_TYPE_PANE:
+ $this->readPane();
+
+ break;
+ case self::XLS_TYPE_SELECTION:
+ $this->readSelection();
+
+ break;
+ case self::XLS_TYPE_MERGEDCELLS:
+ $this->readMergedCells();
+
+ break;
+ case self::XLS_TYPE_HYPERLINK:
+ $this->readHyperLink();
+
+ break;
+ case self::XLS_TYPE_DATAVALIDATIONS:
+ $this->readDataValidations();
+
+ break;
+ case self::XLS_TYPE_DATAVALIDATION:
+ $this->readDataValidation();
+
+ break;
+ case self::XLS_TYPE_SHEETLAYOUT:
+ $this->readSheetLayout();
+
+ break;
+ case self::XLS_TYPE_SHEETPROTECTION:
+ $this->readSheetProtection();
+
+ break;
+ case self::XLS_TYPE_RANGEPROTECTION:
+ $this->readRangeProtection();
+
+ break;
+ case self::XLS_TYPE_NOTE:
+ $this->readNote();
+
+ break;
+ case self::XLS_TYPE_TXO:
+ $this->readTextObject();
+
+ break;
+ case self::XLS_TYPE_CONTINUE:
+ $this->readContinue();
+
+ break;
+ case self::XLS_TYPE_EOF:
+ $this->readDefault();
+
+ break 2;
+ default:
+ $this->readDefault();
+
+ break;
+ }
+ }
+
+ // treat MSODRAWING records, sheet-level Escher
+ if (!$this->readDataOnly && $this->drawingData) {
+ $escherWorksheet = new Escher();
+ $reader = new Xls\Escher($escherWorksheet);
+ $escherWorksheet = $reader->load($this->drawingData);
+
+ // get all spContainers in one long array, so they can be mapped to OBJ records
+ $allSpContainers = $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers();
+ }
+
+ // treat OBJ records
+ foreach ($this->objs as $n => $obj) {
+ // the first shape container never has a corresponding OBJ record, hence $n + 1
+ if (isset($allSpContainers[$n + 1]) && is_object($allSpContainers[$n + 1])) {
+ $spContainer = $allSpContainers[$n + 1];
+
+ // we skip all spContainers that are a part of a group shape since we cannot yet handle those
+ if ($spContainer->getNestingLevel() > 1) {
+ continue;
+ }
+
+ // calculate the width and height of the shape
+ [$startColumn, $startRow] = Coordinate::coordinateFromString($spContainer->getStartCoordinates());
+ [$endColumn, $endRow] = Coordinate::coordinateFromString($spContainer->getEndCoordinates());
+
+ $startOffsetX = $spContainer->getStartOffsetX();
+ $startOffsetY = $spContainer->getStartOffsetY();
+ $endOffsetX = $spContainer->getEndOffsetX();
+ $endOffsetY = $spContainer->getEndOffsetY();
+
+ $width = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceX($this->phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX);
+ $height = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceY($this->phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY);
+
+ // calculate offsetX and offsetY of the shape
+ $offsetX = $startOffsetX * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeCol($this->phpSheet, $startColumn) / 1024;
+ $offsetY = $startOffsetY * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeRow($this->phpSheet, $startRow) / 256;
+
+ switch ($obj['otObjType']) {
+ case 0x19:
+ // Note
+ if (isset($this->cellNotes[$obj['idObjID']])) {
+ $cellNote = $this->cellNotes[$obj['idObjID']];
+
+ if (isset($this->textObjects[$obj['idObjID']])) {
+ $textObject = $this->textObjects[$obj['idObjID']];
+ $this->cellNotes[$obj['idObjID']]['objTextData'] = $textObject;
+ }
+ }
+
+ break;
+ case 0x08:
+ // picture
+ // get index to BSE entry (1-based)
+ $BSEindex = $spContainer->getOPT(0x0104);
+
+ // If there is no BSE Index, we will fail here and other fields are not read.
+ // Fix by checking here.
+ // TODO: Why is there no BSE Index? Is this a new Office Version? Password protected field?
+ // More likely : a uncompatible picture
+ if (!$BSEindex) {
+ continue 2;
+ }
+
+ $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection();
+ $BSE = $BSECollection[$BSEindex - 1];
+ $blipType = $BSE->getBlipType();
+
+ // need check because some blip types are not supported by Escher reader such as EMF
+ if ($blip = $BSE->getBlip()) {
+ $ih = imagecreatefromstring($blip->getData());
+ $drawing = new MemoryDrawing();
+ $drawing->setImageResource($ih);
+
+ // width, height, offsetX, offsetY
+ $drawing->setResizeProportional(false);
+ $drawing->setWidth($width);
+ $drawing->setHeight($height);
+ $drawing->setOffsetX($offsetX);
+ $drawing->setOffsetY($offsetY);
+
+ switch ($blipType) {
+ case BSE::BLIPTYPE_JPEG:
+ $drawing->setRenderingFunction(MemoryDrawing::RENDERING_JPEG);
+ $drawing->setMimeType(MemoryDrawing::MIMETYPE_JPEG);
+
+ break;
+ case BSE::BLIPTYPE_PNG:
+ $drawing->setRenderingFunction(MemoryDrawing::RENDERING_PNG);
+ $drawing->setMimeType(MemoryDrawing::MIMETYPE_PNG);
+
+ break;
+ }
+
+ $drawing->setWorksheet($this->phpSheet);
+ $drawing->setCoordinates($spContainer->getStartCoordinates());
+ }
+
+ break;
+ default:
+ // other object type
+ break;
+ }
+ }
+ }
+
+ // treat SHAREDFMLA records
+ if ($this->version == self::XLS_BIFF8) {
+ foreach ($this->sharedFormulaParts as $cell => $baseCell) {
+ [$column, $row] = Coordinate::coordinateFromString($cell);
+ if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
+ $formula = $this->getFormulaFromStructure($this->sharedFormulas[$baseCell], $cell);
+ $this->phpSheet->getCell($cell)->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA);
+ }
+ }
+ }
+
+ if (!empty($this->cellNotes)) {
+ foreach ($this->cellNotes as $note => $noteDetails) {
+ if (!isset($noteDetails['objTextData'])) {
+ if (isset($this->textObjects[$note])) {
+ $textObject = $this->textObjects[$note];
+ $noteDetails['objTextData'] = $textObject;
+ } else {
+ $noteDetails['objTextData']['text'] = '';
+ }
+ }
+ $cellAddress = str_replace('$', '', $noteDetails['cellRef']);
+ $this->phpSheet->getComment($cellAddress)->setAuthor($noteDetails['author'])->setText($this->parseRichText($noteDetails['objTextData']['text']));
+ }
+ }
+ }
+
+ // add the named ranges (defined names)
+ foreach ($this->definedname as $definedName) {
+ if ($definedName['isBuiltInName']) {
+ switch ($definedName['name']) {
+ case pack('C', 0x06):
+ // print area
+ // in general, formula looks like this: Foo!$C$7:$J$66,Bar!$A$1:$IV$2
+ $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
+
+ $extractedRanges = [];
+ foreach ($ranges as $range) {
+ // $range should look like one of these
+ // Foo!$C$7:$J$66
+ // Bar!$A$1:$IV$2
+ $explodes = Worksheet::extractSheetTitle($range, true);
+ $sheetName = trim($explodes[0], "'");
+ if (count($explodes) == 2) {
+ if (strpos($explodes[1], ':') === false) {
+ $explodes[1] = $explodes[1] . ':' . $explodes[1];
+ }
+ $extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66
+ }
+ }
+ if ($docSheet = $this->spreadsheet->getSheetByName($sheetName)) {
+ $docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2
+ }
+
+ break;
+ case pack('C', 0x07):
+ // print titles (repeating rows)
+ // Assuming BIFF8, there are 3 cases
+ // 1. repeating rows
+ // formula looks like this: Sheet!$A$1:$IV$2
+ // rows 1-2 repeat
+ // 2. repeating columns
+ // formula looks like this: Sheet!$A$1:$B$65536
+ // columns A-B repeat
+ // 3. both repeating rows and repeating columns
+ // formula looks like this: Sheet!$A$1:$B$65536,Sheet!$A$1:$IV$2
+ $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
+ foreach ($ranges as $range) {
+ // $range should look like this one of these
+ // Sheet!$A$1:$B$65536
+ // Sheet!$A$1:$IV$2
+ if (strpos($range, '!') !== false) {
+ $explodes = Worksheet::extractSheetTitle($range, true);
+ if ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) {
+ $extractedRange = $explodes[1];
+ $extractedRange = str_replace('$', '', $extractedRange);
+
+ $coordinateStrings = explode(':', $extractedRange);
+ if (count($coordinateStrings) == 2) {
+ [$firstColumn, $firstRow] = Coordinate::coordinateFromString($coordinateStrings[0]);
+ [$lastColumn, $lastRow] = Coordinate::coordinateFromString($coordinateStrings[1]);
+
+ if ($firstColumn == 'A' && $lastColumn == 'IV') {
+ // then we have repeating rows
+ $docSheet->getPageSetup()->setRowsToRepeatAtTop([$firstRow, $lastRow]);
+ } elseif ($firstRow == 1 && $lastRow == 65536) {
+ // then we have repeating columns
+ $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$firstColumn, $lastColumn]);
+ }
+ }
+ }
+ }
+ }
+
+ break;
+ }
+ } else {
+ // Extract range
+ if (strpos($definedName['formula'], '!') !== false) {
+ $explodes = Worksheet::extractSheetTitle($definedName['formula'], true);
+ if (($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) ||
+ ($docSheet = $this->spreadsheet->getSheetByName(trim($explodes[0], "'")))) {
+ $extractedRange = $explodes[1];
+ $extractedRange = str_replace('$', '', $extractedRange);
+
+ $localOnly = ($definedName['scope'] == 0) ? false : true;
+
+ $scope = ($definedName['scope'] == 0) ? null : $this->spreadsheet->getSheetByName($this->sheets[$definedName['scope'] - 1]['name']);
+
+ $this->spreadsheet->addNamedRange(new NamedRange((string) $definedName['name'], $docSheet, $extractedRange, $localOnly, $scope));
+ }
+ }
+ // Named Value
+ // TODO Provide support for named values
+ }
+ }
+ $this->data = null;
+
+ return $this->spreadsheet;
+ }
+
+ /**
+ * Read record data from stream, decrypting as required.
+ *
+ * @param string $data Data stream to read from
+ * @param int $pos Position to start reading from
+ * @param int $len Record data length
+ *
+ * @return string Record data
+ */
+ private function readRecordData($data, $pos, $len)
+ {
+ $data = substr($data, $pos, $len);
+
+ // File not encrypted, or record before encryption start point
+ if ($this->encryption == self::MS_BIFF_CRYPTO_NONE || $pos < $this->encryptionStartPos) {
+ return $data;
+ }
+
+ $recordData = '';
+ if ($this->encryption == self::MS_BIFF_CRYPTO_RC4) {
+ $oldBlock = floor($this->rc4Pos / self::REKEY_BLOCK);
+ $block = floor($pos / self::REKEY_BLOCK);
+ $endBlock = floor(($pos + $len) / self::REKEY_BLOCK);
+
+ // Spin an RC4 decryptor to the right spot. If we have a decryptor sitting
+ // at a point earlier in the current block, re-use it as we can save some time.
+ if ($block != $oldBlock || $pos < $this->rc4Pos || !$this->rc4Key) {
+ $this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
+ $step = $pos % self::REKEY_BLOCK;
+ } else {
+ $step = $pos - $this->rc4Pos;
+ }
+ $this->rc4Key->RC4(str_repeat("\0", $step));
+
+ // Decrypt record data (re-keying at the end of every block)
+ while ($block != $endBlock) {
+ $step = self::REKEY_BLOCK - ($pos % self::REKEY_BLOCK);
+ $recordData .= $this->rc4Key->RC4(substr($data, 0, $step));
+ $data = substr($data, $step);
+ $pos += $step;
+ $len -= $step;
+ ++$block;
+ $this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
+ }
+ $recordData .= $this->rc4Key->RC4(substr($data, 0, $len));
+
+ // Keep track of the position of this decryptor.
+ // We'll try and re-use it later if we can to speed things up
+ $this->rc4Pos = $pos + $len;
+ } elseif ($this->encryption == self::MS_BIFF_CRYPTO_XOR) {
+ throw new Exception('XOr encryption not supported');
+ }
+
+ return $recordData;
+ }
+
+ /**
+ * Use OLE reader to extract the relevant data streams from the OLE file.
+ *
+ * @param string $pFilename
+ */
+ private function loadOLE($pFilename): void
+ {
+ // OLE reader
+ $ole = new OLERead();
+ // get excel data,
+ $ole->read($pFilename);
+ // Get workbook data: workbook stream + sheet streams
+ $this->data = $ole->getStream($ole->wrkbook);
+ // Get summary information data
+ $this->summaryInformation = $ole->getStream($ole->summaryInformation);
+ // Get additional document summary information data
+ $this->documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation);
+ }
+
+ /**
+ * Read summary information.
+ */
+ private function readSummaryInformation(): void
+ {
+ if (!isset($this->summaryInformation)) {
+ return;
+ }
+
+ // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
+ // offset: 2; size: 2;
+ // offset: 4; size: 2; OS version
+ // offset: 6; size: 2; OS indicator
+ // offset: 8; size: 16
+ // offset: 24; size: 4; section count
+ $secCount = self::getInt4d($this->summaryInformation, 24);
+
+ // offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9
+ // offset: 44; size: 4
+ $secOffset = self::getInt4d($this->summaryInformation, 44);
+
+ // section header
+ // offset: $secOffset; size: 4; section length
+ $secLength = self::getInt4d($this->summaryInformation, $secOffset);
+
+ // offset: $secOffset+4; size: 4; property count
+ $countProperties = self::getInt4d($this->summaryInformation, $secOffset + 4);
+
+ // initialize code page (used to resolve string values)
+ $codePage = 'CP1252';
+
+ // offset: ($secOffset+8); size: var
+ // loop through property decarations and properties
+ for ($i = 0; $i < $countProperties; ++$i) {
+ // offset: ($secOffset+8) + (8 * $i); size: 4; property ID
+ $id = self::getInt4d($this->summaryInformation, ($secOffset + 8) + (8 * $i));
+
+ // Use value of property id as appropriate
+ // offset: ($secOffset+12) + (8 * $i); size: 4; offset from beginning of section (48)
+ $offset = self::getInt4d($this->summaryInformation, ($secOffset + 12) + (8 * $i));
+
+ $type = self::getInt4d($this->summaryInformation, $secOffset + $offset);
+
+ // initialize property value
+ $value = null;
+
+ // extract property value based on property type
+ switch ($type) {
+ case 0x02: // 2 byte signed integer
+ $value = self::getUInt2d($this->summaryInformation, $secOffset + 4 + $offset);
+
+ break;
+ case 0x03: // 4 byte signed integer
+ $value = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
+
+ break;
+ case 0x13: // 4 byte unsigned integer
+ // not needed yet, fix later if necessary
+ break;
+ case 0x1E: // null-terminated string prepended by dword string length
+ $byteLength = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
+ $value = substr($this->summaryInformation, $secOffset + 8 + $offset, $byteLength);
+ $value = StringHelper::convertEncoding($value, 'UTF-8', $codePage);
+ $value = rtrim($value);
+
+ break;
+ case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
+ // PHP-time
+ $value = OLE::OLE2LocalDate(substr($this->summaryInformation, $secOffset + 4 + $offset, 8));
+
+ break;
+ case 0x47: // Clipboard format
+ // not needed yet, fix later if necessary
+ break;
+ }
+
+ switch ($id) {
+ case 0x01: // Code Page
+ $codePage = CodePage::numberToName($value);
+
+ break;
+ case 0x02: // Title
+ $this->spreadsheet->getProperties()->setTitle($value);
+
+ break;
+ case 0x03: // Subject
+ $this->spreadsheet->getProperties()->setSubject($value);
+
+ break;
+ case 0x04: // Author (Creator)
+ $this->spreadsheet->getProperties()->setCreator($value);
+
+ break;
+ case 0x05: // Keywords
+ $this->spreadsheet->getProperties()->setKeywords($value);
+
+ break;
+ case 0x06: // Comments (Description)
+ $this->spreadsheet->getProperties()->setDescription($value);
+
+ break;
+ case 0x07: // Template
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x08: // Last Saved By (LastModifiedBy)
+ $this->spreadsheet->getProperties()->setLastModifiedBy($value);
+
+ break;
+ case 0x09: // Revision
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x0A: // Total Editing Time
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x0B: // Last Printed
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x0C: // Created Date/Time
+ $this->spreadsheet->getProperties()->setCreated($value);
+
+ break;
+ case 0x0D: // Modified Date/Time
+ $this->spreadsheet->getProperties()->setModified($value);
+
+ break;
+ case 0x0E: // Number of Pages
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x0F: // Number of Words
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x10: // Number of Characters
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x11: // Thumbnail
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x12: // Name of creating application
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x13: // Security
+ // Not supported by PhpSpreadsheet
+ break;
+ }
+ }
+ }
+
+ /**
+ * Read additional document summary information.
+ */
+ private function readDocumentSummaryInformation(): void
+ {
+ if (!isset($this->documentSummaryInformation)) {
+ return;
+ }
+
+ // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
+ // offset: 2; size: 2;
+ // offset: 4; size: 2; OS version
+ // offset: 6; size: 2; OS indicator
+ // offset: 8; size: 16
+ // offset: 24; size: 4; section count
+ $secCount = self::getInt4d($this->documentSummaryInformation, 24);
+
+ // offset: 28; size: 16; first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae
+ // offset: 44; size: 4; first section offset
+ $secOffset = self::getInt4d($this->documentSummaryInformation, 44);
+
+ // section header
+ // offset: $secOffset; size: 4; section length
+ $secLength = self::getInt4d($this->documentSummaryInformation, $secOffset);
+
+ // offset: $secOffset+4; size: 4; property count
+ $countProperties = self::getInt4d($this->documentSummaryInformation, $secOffset + 4);
+
+ // initialize code page (used to resolve string values)
+ $codePage = 'CP1252';
+
+ // offset: ($secOffset+8); size: var
+ // loop through property decarations and properties
+ for ($i = 0; $i < $countProperties; ++$i) {
+ // offset: ($secOffset+8) + (8 * $i); size: 4; property ID
+ $id = self::getInt4d($this->documentSummaryInformation, ($secOffset + 8) + (8 * $i));
+
+ // Use value of property id as appropriate
+ // offset: 60 + 8 * $i; size: 4; offset from beginning of section (48)
+ $offset = self::getInt4d($this->documentSummaryInformation, ($secOffset + 12) + (8 * $i));
+
+ $type = self::getInt4d($this->documentSummaryInformation, $secOffset + $offset);
+
+ // initialize property value
+ $value = null;
+
+ // extract property value based on property type
+ switch ($type) {
+ case 0x02: // 2 byte signed integer
+ $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
+
+ break;
+ case 0x03: // 4 byte signed integer
+ $value = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
+
+ break;
+ case 0x0B: // Boolean
+ $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
+ $value = ($value == 0 ? false : true);
+
+ break;
+ case 0x13: // 4 byte unsigned integer
+ // not needed yet, fix later if necessary
+ break;
+ case 0x1E: // null-terminated string prepended by dword string length
+ $byteLength = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
+ $value = substr($this->documentSummaryInformation, $secOffset + 8 + $offset, $byteLength);
+ $value = StringHelper::convertEncoding($value, 'UTF-8', $codePage);
+ $value = rtrim($value);
+
+ break;
+ case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
+ // PHP-Time
+ $value = OLE::OLE2LocalDate(substr($this->documentSummaryInformation, $secOffset + 4 + $offset, 8));
+
+ break;
+ case 0x47: // Clipboard format
+ // not needed yet, fix later if necessary
+ break;
+ }
+
+ switch ($id) {
+ case 0x01: // Code Page
+ $codePage = CodePage::numberToName($value);
+
+ break;
+ case 0x02: // Category
+ $this->spreadsheet->getProperties()->setCategory($value);
+
+ break;
+ case 0x03: // Presentation Target
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x04: // Bytes
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x05: // Lines
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x06: // Paragraphs
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x07: // Slides
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x08: // Notes
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x09: // Hidden Slides
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x0A: // MM Clips
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x0B: // Scale Crop
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x0C: // Heading Pairs
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x0D: // Titles of Parts
+ // Not supported by PhpSpreadsheet
+ break;
+ case 0x0E: // Manager
+ $this->spreadsheet->getProperties()->setManager($value);
+
+ break;
+ case 0x0F: // Company
+ $this->spreadsheet->getProperties()->setCompany($value);
+
+ break;
+ case 0x10: // Links up-to-date
+ // Not supported by PhpSpreadsheet
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record.
+ */
+ private function readDefault(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+ }
+
+ /**
+ * The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions,
+ * this record stores a note (cell note). This feature was significantly enhanced in Excel 97.
+ */
+ private function readNote(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if ($this->readDataOnly) {
+ return;
+ }
+
+ $cellAddress = $this->readBIFF8CellAddress(substr($recordData, 0, 4));
+ if ($this->version == self::XLS_BIFF8) {
+ $noteObjID = self::getUInt2d($recordData, 6);
+ $noteAuthor = self::readUnicodeStringLong(substr($recordData, 8));
+ $noteAuthor = $noteAuthor['value'];
+ $this->cellNotes[$noteObjID] = [
+ 'cellRef' => $cellAddress,
+ 'objectID' => $noteObjID,
+ 'author' => $noteAuthor,
+ ];
+ } else {
+ $extension = false;
+ if ($cellAddress == '$B$65536') {
+ // If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation
+ // note from the previous cell annotation. We're not yet handling this, so annotations longer than the
+ // max 2048 bytes will probably throw a wobbly.
+ $row = self::getUInt2d($recordData, 0);
+ $extension = true;
+ $cellAddress = array_pop(array_keys($this->phpSheet->getComments()));
+ }
+
+ $cellAddress = str_replace('$', '', $cellAddress);
+ $noteLength = self::getUInt2d($recordData, 4);
+ $noteText = trim(substr($recordData, 6));
+
+ if ($extension) {
+ // Concatenate this extension with the currently set comment for the cell
+ $comment = $this->phpSheet->getComment($cellAddress);
+ $commentText = $comment->getText()->getPlainText();
+ $comment->setText($this->parseRichText($commentText . $noteText));
+ } else {
+ // Set comment for the cell
+ $this->phpSheet->getComment($cellAddress)->setText($this->parseRichText($noteText));
+// ->setAuthor($author)
+ }
+ }
+ }
+
+ /**
+ * The TEXT Object record contains the text associated with a cell annotation.
+ */
+ private function readTextObject(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if ($this->readDataOnly) {
+ return;
+ }
+
+ // recordData consists of an array of subrecords looking like this:
+ // grbit: 2 bytes; Option Flags
+ // rot: 2 bytes; rotation
+ // cchText: 2 bytes; length of the text (in the first continue record)
+ // cbRuns: 2 bytes; length of the formatting (in the second continue record)
+ // followed by the continuation records containing the actual text and formatting
+ $grbitOpts = self::getUInt2d($recordData, 0);
+ $rot = self::getUInt2d($recordData, 2);
+ $cchText = self::getUInt2d($recordData, 10);
+ $cbRuns = self::getUInt2d($recordData, 12);
+ $text = $this->getSplicedRecordData();
+
+ $textByte = $text['spliceOffsets'][1] - $text['spliceOffsets'][0] - 1;
+ $textStr = substr($text['recordData'], $text['spliceOffsets'][0] + 1, $textByte);
+ // get 1 byte
+ $is16Bit = ord($text['recordData'][0]);
+ // it is possible to use a compressed format,
+ // which omits the high bytes of all characters, if they are all zero
+ if (($is16Bit & 0x01) === 0) {
+ $textStr = StringHelper::ConvertEncoding($textStr, 'UTF-8', 'ISO-8859-1');
+ } else {
+ $textStr = $this->decodeCodepage($textStr);
+ }
+
+ $this->textObjects[$this->textObjRef] = [
+ 'text' => $textStr,
+ 'format' => substr($text['recordData'], $text['spliceOffsets'][1], $cbRuns),
+ 'alignment' => $grbitOpts,
+ 'rotation' => $rot,
+ ];
+ }
+
+ /**
+ * Read BOF.
+ */
+ private function readBof(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = substr($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 2; size: 2; type of the following data
+ $substreamType = self::getUInt2d($recordData, 2);
+
+ switch ($substreamType) {
+ case self::XLS_WORKBOOKGLOBALS:
+ $version = self::getUInt2d($recordData, 0);
+ if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) {
+ throw new Exception('Cannot read this Excel file. Version is too old.');
+ }
+ $this->version = $version;
+
+ break;
+ case self::XLS_WORKSHEET:
+ // do not use this version information for anything
+ // it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream
+ break;
+ default:
+ // substream, e.g. chart
+ // just skip the entire substream
+ do {
+ $code = self::getUInt2d($this->data, $this->pos);
+ $this->readDefault();
+ } while ($code != self::XLS_TYPE_EOF && $this->pos < $this->dataSize);
+
+ break;
+ }
+ }
+
+ /**
+ * FILEPASS.
+ *
+ * This record is part of the File Protection Block. It
+ * contains information about the read/write password of the
+ * file. All record contents following this record will be
+ * encrypted.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ *
+ * The decryption functions and objects used from here on in
+ * are based on the source of Spreadsheet-ParseExcel:
+ * https://metacpan.org/release/Spreadsheet-ParseExcel
+ */
+ private function readFilepass(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+
+ if ($length != 54) {
+ throw new Exception('Unexpected file pass record length');
+ }
+
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->verifyPassword('VelvetSweatshop', substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) {
+ throw new Exception('Decryption password incorrect');
+ }
+
+ $this->encryption = self::MS_BIFF_CRYPTO_RC4;
+
+ // Decryption required from the record after next onwards
+ $this->encryptionStartPos = $this->pos + self::getUInt2d($this->data, $this->pos + 2);
+ }
+
+ /**
+ * Make an RC4 decryptor for the given block.
+ *
+ * @param int $block Block for which to create decrypto
+ * @param string $valContext MD5 context state
+ *
+ * @return Xls\RC4
+ */
+ private function makeKey($block, $valContext)
+ {
+ $pwarray = str_repeat("\0", 64);
+
+ for ($i = 0; $i < 5; ++$i) {
+ $pwarray[$i] = $valContext[$i];
+ }
+
+ $pwarray[5] = chr($block & 0xff);
+ $pwarray[6] = chr(($block >> 8) & 0xff);
+ $pwarray[7] = chr(($block >> 16) & 0xff);
+ $pwarray[8] = chr(($block >> 24) & 0xff);
+
+ $pwarray[9] = "\x80";
+ $pwarray[56] = "\x48";
+
+ $md5 = new Xls\MD5();
+ $md5->add($pwarray);
+
+ $s = $md5->getContext();
+
+ return new Xls\RC4($s);
+ }
+
+ /**
+ * Verify RC4 file password.
+ *
+ * @param string $password Password to check
+ * @param string $docid Document id
+ * @param string $salt_data Salt data
+ * @param string $hashedsalt_data Hashed salt data
+ * @param string $valContext Set to the MD5 context of the value
+ *
+ * @return bool Success
+ */
+ private function verifyPassword($password, $docid, $salt_data, $hashedsalt_data, &$valContext)
+ {
+ $pwarray = str_repeat("\0", 64);
+
+ $iMax = strlen($password);
+ for ($i = 0; $i < $iMax; ++$i) {
+ $o = ord(substr($password, $i, 1));
+ $pwarray[2 * $i] = chr($o & 0xff);
+ $pwarray[2 * $i + 1] = chr(($o >> 8) & 0xff);
+ }
+ $pwarray[2 * $i] = chr(0x80);
+ $pwarray[56] = chr(($i << 4) & 0xff);
+
+ $md5 = new Xls\MD5();
+ $md5->add($pwarray);
+
+ $mdContext1 = $md5->getContext();
+
+ $offset = 0;
+ $keyoffset = 0;
+ $tocopy = 5;
+
+ $md5->reset();
+
+ while ($offset != 16) {
+ if ((64 - $offset) < 5) {
+ $tocopy = 64 - $offset;
+ }
+ for ($i = 0; $i <= $tocopy; ++$i) {
+ $pwarray[$offset + $i] = $mdContext1[$keyoffset + $i];
+ }
+ $offset += $tocopy;
+
+ if ($offset == 64) {
+ $md5->add($pwarray);
+ $keyoffset = $tocopy;
+ $tocopy = 5 - $tocopy;
+ $offset = 0;
+
+ continue;
+ }
+
+ $keyoffset = 0;
+ $tocopy = 5;
+ for ($i = 0; $i < 16; ++$i) {
+ $pwarray[$offset + $i] = $docid[$i];
+ }
+ $offset += 16;
+ }
+
+ $pwarray[16] = "\x80";
+ for ($i = 0; $i < 47; ++$i) {
+ $pwarray[17 + $i] = "\0";
+ }
+ $pwarray[56] = "\x80";
+ $pwarray[57] = "\x0a";
+
+ $md5->add($pwarray);
+ $valContext = $md5->getContext();
+
+ $key = $this->makeKey(0, $valContext);
+
+ $salt = $key->RC4($salt_data);
+ $hashedsalt = $key->RC4($hashedsalt_data);
+
+ $salt .= "\x80" . str_repeat("\0", 47);
+ $salt[56] = "\x80";
+
+ $md5->reset();
+ $md5->add($salt);
+ $mdContext2 = $md5->getContext();
+
+ return $mdContext2 == $hashedsalt;
+ }
+
+ /**
+ * CODEPAGE.
+ *
+ * This record stores the text encoding used to write byte
+ * strings, stored as MS Windows code page identifier.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readCodepage(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; code page identifier
+ $codepage = self::getUInt2d($recordData, 0);
+
+ $this->codepage = CodePage::numberToName($codepage);
+ }
+
+ /**
+ * DATEMODE.
+ *
+ * This record specifies the base date for displaying date
+ * values. All dates are stored as count of days past this
+ * base date. In BIFF2-BIFF4 this record is part of the
+ * Calculation Settings Block. In BIFF5-BIFF8 it is
+ * stored in the Workbook Globals Substream.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readDateMode(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; 0 = base 1900, 1 = base 1904
+ Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
+ if (ord($recordData[0]) == 1) {
+ Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
+ }
+ }
+
+ /**
+ * Read a FONT record.
+ */
+ private function readFont(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ $objFont = new Font();
+
+ // offset: 0; size: 2; height of the font (in twips = 1/20 of a point)
+ $size = self::getUInt2d($recordData, 0);
+ $objFont->setSize($size / 20);
+
+ // offset: 2; size: 2; option flags
+ // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8)
+ // bit: 1; mask 0x0002; italic
+ $isItalic = (0x0002 & self::getUInt2d($recordData, 2)) >> 1;
+ if ($isItalic) {
+ $objFont->setItalic(true);
+ }
+
+ // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8)
+ // bit: 3; mask 0x0008; strikethrough
+ $isStrike = (0x0008 & self::getUInt2d($recordData, 2)) >> 3;
+ if ($isStrike) {
+ $objFont->setStrikethrough(true);
+ }
+
+ // offset: 4; size: 2; colour index
+ $colorIndex = self::getUInt2d($recordData, 4);
+ $objFont->colorIndex = $colorIndex;
+
+ // offset: 6; size: 2; font weight
+ $weight = self::getUInt2d($recordData, 6);
+ switch ($weight) {
+ case 0x02BC:
+ $objFont->setBold(true);
+
+ break;
+ }
+
+ // offset: 8; size: 2; escapement type
+ $escapement = self::getUInt2d($recordData, 8);
+ switch ($escapement) {
+ case 0x0001:
+ $objFont->setSuperscript(true);
+
+ break;
+ case 0x0002:
+ $objFont->setSubscript(true);
+
+ break;
+ }
+
+ // offset: 10; size: 1; underline type
+ $underlineType = ord($recordData[10]);
+ switch ($underlineType) {
+ case 0x00:
+ break; // no underline
+ case 0x01:
+ $objFont->setUnderline(Font::UNDERLINE_SINGLE);
+
+ break;
+ case 0x02:
+ $objFont->setUnderline(Font::UNDERLINE_DOUBLE);
+
+ break;
+ case 0x21:
+ $objFont->setUnderline(Font::UNDERLINE_SINGLEACCOUNTING);
+
+ break;
+ case 0x22:
+ $objFont->setUnderline(Font::UNDERLINE_DOUBLEACCOUNTING);
+
+ break;
+ }
+
+ // offset: 11; size: 1; font family
+ // offset: 12; size: 1; character set
+ // offset: 13; size: 1; not used
+ // offset: 14; size: var; font name
+ if ($this->version == self::XLS_BIFF8) {
+ $string = self::readUnicodeStringShort(substr($recordData, 14));
+ } else {
+ $string = $this->readByteStringShort(substr($recordData, 14));
+ }
+ $objFont->setName($string['value']);
+
+ $this->objFonts[] = $objFont;
+ }
+ }
+
+ /**
+ * FORMAT.
+ *
+ * This record contains information about a number format.
+ * All FORMAT records occur together in a sequential list.
+ *
+ * In BIFF2-BIFF4 other records referencing a FORMAT record
+ * contain a zero-based index into this list. From BIFF5 on
+ * the FORMAT record contains the index itself that will be
+ * used by other records.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readFormat(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ $indexCode = self::getUInt2d($recordData, 0);
+
+ if ($this->version == self::XLS_BIFF8) {
+ $string = self::readUnicodeStringLong(substr($recordData, 2));
+ } else {
+ // BIFF7
+ $string = $this->readByteStringShort(substr($recordData, 2));
+ }
+
+ $formatString = $string['value'];
+ $this->formats[$indexCode] = $formatString;
+ }
+ }
+
+ /**
+ * XF - Extended Format.
+ *
+ * This record contains formatting information for cells, rows, columns or styles.
+ * According to https://support.microsoft.com/en-us/help/147732 there are always at least 15 cell style XF
+ * and 1 cell XF.
+ * Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF
+ * and XF record 15 is a cell XF
+ * We only read the first cell style XF and skip the remaining cell style XF records
+ * We read all cell XF records.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readXf(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ $objStyle = new Style();
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 2; Index to FONT record
+ if (self::getUInt2d($recordData, 0) < 4) {
+ $fontIndex = self::getUInt2d($recordData, 0);
+ } else {
+ // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
+ // check the OpenOffice documentation of the FONT record
+ $fontIndex = self::getUInt2d($recordData, 0) - 1;
+ }
+ $objStyle->setFont($this->objFonts[$fontIndex]);
+
+ // offset: 2; size: 2; Index to FORMAT record
+ $numberFormatIndex = self::getUInt2d($recordData, 2);
+ if (isset($this->formats[$numberFormatIndex])) {
+ // then we have user-defined format code
+ $numberFormat = ['formatCode' => $this->formats[$numberFormatIndex]];
+ } elseif (($code = NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') {
+ // then we have built-in format code
+ $numberFormat = ['formatCode' => $code];
+ } else {
+ // we set the general format code
+ $numberFormat = ['formatCode' => 'General'];
+ }
+ $objStyle->getNumberFormat()->setFormatCode($numberFormat['formatCode']);
+
+ // offset: 4; size: 2; XF type, cell protection, and parent style XF
+ // bit 2-0; mask 0x0007; XF_TYPE_PROT
+ $xfTypeProt = self::getUInt2d($recordData, 4);
+ // bit 0; mask 0x01; 1 = cell is locked
+ $isLocked = (0x01 & $xfTypeProt) >> 0;
+ $objStyle->getProtection()->setLocked($isLocked ? Protection::PROTECTION_INHERIT : Protection::PROTECTION_UNPROTECTED);
+
+ // bit 1; mask 0x02; 1 = Formula is hidden
+ $isHidden = (0x02 & $xfTypeProt) >> 1;
+ $objStyle->getProtection()->setHidden($isHidden ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED);
+
+ // bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF
+ $isCellStyleXf = (0x04 & $xfTypeProt) >> 2;
+
+ // offset: 6; size: 1; Alignment and text break
+ // bit 2-0, mask 0x07; horizontal alignment
+ $horAlign = (0x07 & ord($recordData[6])) >> 0;
+ switch ($horAlign) {
+ case 0:
+ $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_GENERAL);
+
+ break;
+ case 1:
+ $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT);
+
+ break;
+ case 2:
+ $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
+
+ break;
+ case 3:
+ $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
+
+ break;
+ case 4:
+ $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_FILL);
+
+ break;
+ case 5:
+ $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_JUSTIFY);
+
+ break;
+ case 6:
+ $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER_CONTINUOUS);
+
+ break;
+ }
+ // bit 3, mask 0x08; wrap text
+ $wrapText = (0x08 & ord($recordData[6])) >> 3;
+ switch ($wrapText) {
+ case 0:
+ $objStyle->getAlignment()->setWrapText(false);
+
+ break;
+ case 1:
+ $objStyle->getAlignment()->setWrapText(true);
+
+ break;
+ }
+ // bit 6-4, mask 0x70; vertical alignment
+ $vertAlign = (0x70 & ord($recordData[6])) >> 4;
+ switch ($vertAlign) {
+ case 0:
+ $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_TOP);
+
+ break;
+ case 1:
+ $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
+
+ break;
+ case 2:
+ $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_BOTTOM);
+
+ break;
+ case 3:
+ $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_JUSTIFY);
+
+ break;
+ }
+
+ if ($this->version == self::XLS_BIFF8) {
+ // offset: 7; size: 1; XF_ROTATION: Text rotation angle
+ $angle = ord($recordData[7]);
+ $rotation = 0;
+ if ($angle <= 90) {
+ $rotation = $angle;
+ } elseif ($angle <= 180) {
+ $rotation = 90 - $angle;
+ } elseif ($angle == 255) {
+ $rotation = -165;
+ }
+ $objStyle->getAlignment()->setTextRotation($rotation);
+
+ // offset: 8; size: 1; Indentation, shrink to cell size, and text direction
+ // bit: 3-0; mask: 0x0F; indent level
+ $indent = (0x0F & ord($recordData[8])) >> 0;
+ $objStyle->getAlignment()->setIndent($indent);
+
+ // bit: 4; mask: 0x10; 1 = shrink content to fit into cell
+ $shrinkToFit = (0x10 & ord($recordData[8])) >> 4;
+ switch ($shrinkToFit) {
+ case 0:
+ $objStyle->getAlignment()->setShrinkToFit(false);
+
+ break;
+ case 1:
+ $objStyle->getAlignment()->setShrinkToFit(true);
+
+ break;
+ }
+
+ // offset: 9; size: 1; Flags used for attribute groups
+
+ // offset: 10; size: 4; Cell border lines and background area
+ // bit: 3-0; mask: 0x0000000F; left style
+ if ($bordersLeftStyle = Xls\Style\Border::lookup((0x0000000F & self::getInt4d($recordData, 10)) >> 0)) {
+ $objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle);
+ }
+ // bit: 7-4; mask: 0x000000F0; right style
+ if ($bordersRightStyle = Xls\Style\Border::lookup((0x000000F0 & self::getInt4d($recordData, 10)) >> 4)) {
+ $objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle);
+ }
+ // bit: 11-8; mask: 0x00000F00; top style
+ if ($bordersTopStyle = Xls\Style\Border::lookup((0x00000F00 & self::getInt4d($recordData, 10)) >> 8)) {
+ $objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle);
+ }
+ // bit: 15-12; mask: 0x0000F000; bottom style
+ if ($bordersBottomStyle = Xls\Style\Border::lookup((0x0000F000 & self::getInt4d($recordData, 10)) >> 12)) {
+ $objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle);
+ }
+ // bit: 22-16; mask: 0x007F0000; left color
+ $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::getInt4d($recordData, 10)) >> 16;
+
+ // bit: 29-23; mask: 0x3F800000; right color
+ $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::getInt4d($recordData, 10)) >> 23;
+
+ // bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom
+ $diagonalDown = (0x40000000 & self::getInt4d($recordData, 10)) >> 30 ? true : false;
+
+ // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right
+ $diagonalUp = (0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false;
+
+ if ($diagonalUp == false && $diagonalDown == false) {
+ $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE);
+ } elseif ($diagonalUp == true && $diagonalDown == false) {
+ $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP);
+ } elseif ($diagonalUp == false && $diagonalDown == true) {
+ $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN);
+ } elseif ($diagonalUp == true && $diagonalDown == true) {
+ $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH);
+ }
+
+ // offset: 14; size: 4;
+ // bit: 6-0; mask: 0x0000007F; top color
+ $objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::getInt4d($recordData, 14)) >> 0;
+
+ // bit: 13-7; mask: 0x00003F80; bottom color
+ $objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::getInt4d($recordData, 14)) >> 7;
+
+ // bit: 20-14; mask: 0x001FC000; diagonal color
+ $objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::getInt4d($recordData, 14)) >> 14;
+
+ // bit: 24-21; mask: 0x01E00000; diagonal style
+ if ($bordersDiagonalStyle = Xls\Style\Border::lookup((0x01E00000 & self::getInt4d($recordData, 14)) >> 21)) {
+ $objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle);
+ }
+
+ // bit: 31-26; mask: 0xFC000000 fill pattern
+ if ($fillType = Xls\Style\FillPattern::lookup((0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) {
+ $objStyle->getFill()->setFillType($fillType);
+ }
+ // offset: 18; size: 2; pattern and background colour
+ // bit: 6-0; mask: 0x007F; color index for pattern color
+ $objStyle->getFill()->startcolorIndex = (0x007F & self::getUInt2d($recordData, 18)) >> 0;
+
+ // bit: 13-7; mask: 0x3F80; color index for pattern background
+ $objStyle->getFill()->endcolorIndex = (0x3F80 & self::getUInt2d($recordData, 18)) >> 7;
+ } else {
+ // BIFF5
+
+ // offset: 7; size: 1; Text orientation and flags
+ $orientationAndFlags = ord($recordData[7]);
+
+ // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation
+ $xfOrientation = (0x03 & $orientationAndFlags) >> 0;
+ switch ($xfOrientation) {
+ case 0:
+ $objStyle->getAlignment()->setTextRotation(0);
+
+ break;
+ case 1:
+ $objStyle->getAlignment()->setTextRotation(-165);
+
+ break;
+ case 2:
+ $objStyle->getAlignment()->setTextRotation(90);
+
+ break;
+ case 3:
+ $objStyle->getAlignment()->setTextRotation(-90);
+
+ break;
+ }
+
+ // offset: 8; size: 4; cell border lines and background area
+ $borderAndBackground = self::getInt4d($recordData, 8);
+
+ // bit: 6-0; mask: 0x0000007F; color index for pattern color
+ $objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0;
+
+ // bit: 13-7; mask: 0x00003F80; color index for pattern background
+ $objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7;
+
+ // bit: 21-16; mask: 0x003F0000; fill pattern
+ $objStyle->getFill()->setFillType(Xls\Style\FillPattern::lookup((0x003F0000 & $borderAndBackground) >> 16));
+
+ // bit: 24-22; mask: 0x01C00000; bottom line style
+ $objStyle->getBorders()->getBottom()->setBorderStyle(Xls\Style\Border::lookup((0x01C00000 & $borderAndBackground) >> 22));
+
+ // bit: 31-25; mask: 0xFE000000; bottom line color
+ $objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25;
+
+ // offset: 12; size: 4; cell border lines
+ $borderLines = self::getInt4d($recordData, 12);
+
+ // bit: 2-0; mask: 0x00000007; top line style
+ $objStyle->getBorders()->getTop()->setBorderStyle(Xls\Style\Border::lookup((0x00000007 & $borderLines) >> 0));
+
+ // bit: 5-3; mask: 0x00000038; left line style
+ $objStyle->getBorders()->getLeft()->setBorderStyle(Xls\Style\Border::lookup((0x00000038 & $borderLines) >> 3));
+
+ // bit: 8-6; mask: 0x000001C0; right line style
+ $objStyle->getBorders()->getRight()->setBorderStyle(Xls\Style\Border::lookup((0x000001C0 & $borderLines) >> 6));
+
+ // bit: 15-9; mask: 0x0000FE00; top line color index
+ $objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9;
+
+ // bit: 22-16; mask: 0x007F0000; left line color index
+ $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16;
+
+ // bit: 29-23; mask: 0x3F800000; right line color index
+ $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23;
+ }
+
+ // add cellStyleXf or cellXf and update mapping
+ if ($isCellStyleXf) {
+ // we only read one style XF record which is always the first
+ if ($this->xfIndex == 0) {
+ $this->spreadsheet->addCellStyleXf($objStyle);
+ $this->mapCellStyleXfIndex[$this->xfIndex] = 0;
+ }
+ } else {
+ // we read all cell XF records
+ $this->spreadsheet->addCellXf($objStyle);
+ $this->mapCellXfIndex[$this->xfIndex] = count($this->spreadsheet->getCellXfCollection()) - 1;
+ }
+
+ // update XF index for when we read next record
+ ++$this->xfIndex;
+ }
+ }
+
+ private function readXfExt(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 2; 0x087D = repeated header
+
+ // offset: 2; size: 2
+
+ // offset: 4; size: 8; not used
+
+ // offset: 12; size: 2; record version
+
+ // offset: 14; size: 2; index to XF record which this record modifies
+ $ixfe = self::getUInt2d($recordData, 14);
+
+ // offset: 16; size: 2; not used
+
+ // offset: 18; size: 2; number of extension properties that follow
+ $cexts = self::getUInt2d($recordData, 18);
+
+ // start reading the actual extension data
+ $offset = 20;
+ while ($offset < $length) {
+ // extension type
+ $extType = self::getUInt2d($recordData, $offset);
+
+ // extension length
+ $cb = self::getUInt2d($recordData, $offset + 2);
+
+ // extension data
+ $extData = substr($recordData, $offset + 4, $cb);
+
+ switch ($extType) {
+ case 4: // fill start color
+ $xclfType = self::getUInt2d($extData, 0); // color type
+ $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
+
+ if ($xclfType == 2) {
+ $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
+
+ // modify the relevant style property
+ if (isset($this->mapCellXfIndex[$ixfe])) {
+ $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
+ $fill->getStartColor()->setRGB($rgb);
+ $fill->startcolorIndex = null; // normal color index does not apply, discard
+ }
+ }
+
+ break;
+ case 5: // fill end color
+ $xclfType = self::getUInt2d($extData, 0); // color type
+ $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
+
+ if ($xclfType == 2) {
+ $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
+
+ // modify the relevant style property
+ if (isset($this->mapCellXfIndex[$ixfe])) {
+ $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
+ $fill->getEndColor()->setRGB($rgb);
+ $fill->endcolorIndex = null; // normal color index does not apply, discard
+ }
+ }
+
+ break;
+ case 7: // border color top
+ $xclfType = self::getUInt2d($extData, 0); // color type
+ $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
+
+ if ($xclfType == 2) {
+ $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
+
+ // modify the relevant style property
+ if (isset($this->mapCellXfIndex[$ixfe])) {
+ $top = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getTop();
+ $top->getColor()->setRGB($rgb);
+ $top->colorIndex = null; // normal color index does not apply, discard
+ }
+ }
+
+ break;
+ case 8: // border color bottom
+ $xclfType = self::getUInt2d($extData, 0); // color type
+ $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
+
+ if ($xclfType == 2) {
+ $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
+
+ // modify the relevant style property
+ if (isset($this->mapCellXfIndex[$ixfe])) {
+ $bottom = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getBottom();
+ $bottom->getColor()->setRGB($rgb);
+ $bottom->colorIndex = null; // normal color index does not apply, discard
+ }
+ }
+
+ break;
+ case 9: // border color left
+ $xclfType = self::getUInt2d($extData, 0); // color type
+ $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
+
+ if ($xclfType == 2) {
+ $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
+
+ // modify the relevant style property
+ if (isset($this->mapCellXfIndex[$ixfe])) {
+ $left = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getLeft();
+ $left->getColor()->setRGB($rgb);
+ $left->colorIndex = null; // normal color index does not apply, discard
+ }
+ }
+
+ break;
+ case 10: // border color right
+ $xclfType = self::getUInt2d($extData, 0); // color type
+ $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
+
+ if ($xclfType == 2) {
+ $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
+
+ // modify the relevant style property
+ if (isset($this->mapCellXfIndex[$ixfe])) {
+ $right = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getRight();
+ $right->getColor()->setRGB($rgb);
+ $right->colorIndex = null; // normal color index does not apply, discard
+ }
+ }
+
+ break;
+ case 11: // border color diagonal
+ $xclfType = self::getUInt2d($extData, 0); // color type
+ $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
+
+ if ($xclfType == 2) {
+ $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
+
+ // modify the relevant style property
+ if (isset($this->mapCellXfIndex[$ixfe])) {
+ $diagonal = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getDiagonal();
+ $diagonal->getColor()->setRGB($rgb);
+ $diagonal->colorIndex = null; // normal color index does not apply, discard
+ }
+ }
+
+ break;
+ case 13: // font color
+ $xclfType = self::getUInt2d($extData, 0); // color type
+ $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
+
+ if ($xclfType == 2) {
+ $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
+
+ // modify the relevant style property
+ if (isset($this->mapCellXfIndex[$ixfe])) {
+ $font = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFont();
+ $font->getColor()->setRGB($rgb);
+ $font->colorIndex = null; // normal color index does not apply, discard
+ }
+ }
+
+ break;
+ }
+
+ $offset += $cb;
+ }
+ }
+ }
+
+ /**
+ * Read STYLE record.
+ */
+ private function readStyle(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 2; index to XF record and flag for built-in style
+ $ixfe = self::getUInt2d($recordData, 0);
+
+ // bit: 11-0; mask 0x0FFF; index to XF record
+ $xfIndex = (0x0FFF & $ixfe) >> 0;
+
+ // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style
+ $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15);
+
+ if ($isBuiltIn) {
+ // offset: 2; size: 1; identifier for built-in style
+ $builtInId = ord($recordData[2]);
+
+ switch ($builtInId) {
+ case 0x00:
+ // currently, we are not using this for anything
+ break;
+ default:
+ break;
+ }
+ }
+ // user-defined; not supported by PhpSpreadsheet
+ }
+ }
+
+ /**
+ * Read PALETTE record.
+ */
+ private function readPalette(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 2; number of following colors
+ $nm = self::getUInt2d($recordData, 0);
+
+ // list of RGB colors
+ for ($i = 0; $i < $nm; ++$i) {
+ $rgb = substr($recordData, 2 + 4 * $i, 4);
+ $this->palette[] = self::readRGB($rgb);
+ }
+ }
+ }
+
+ /**
+ * SHEET.
+ *
+ * This record is located in the Workbook Globals
+ * Substream and represents a sheet inside the workbook.
+ * One SHEET record is written for each sheet. It stores the
+ * sheet name and a stream offset to the BOF record of the
+ * respective Sheet Substream within the Workbook Stream.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readSheet(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // offset: 0; size: 4; absolute stream position of the BOF record of the sheet
+ // NOTE: not encrypted
+ $rec_offset = self::getInt4d($this->data, $this->pos + 4);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 4; size: 1; sheet state
+ switch (ord($recordData[4])) {
+ case 0x00:
+ $sheetState = Worksheet::SHEETSTATE_VISIBLE;
+
+ break;
+ case 0x01:
+ $sheetState = Worksheet::SHEETSTATE_HIDDEN;
+
+ break;
+ case 0x02:
+ $sheetState = Worksheet::SHEETSTATE_VERYHIDDEN;
+
+ break;
+ default:
+ $sheetState = Worksheet::SHEETSTATE_VISIBLE;
+
+ break;
+ }
+
+ // offset: 5; size: 1; sheet type
+ $sheetType = ord($recordData[5]);
+
+ // offset: 6; size: var; sheet name
+ if ($this->version == self::XLS_BIFF8) {
+ $string = self::readUnicodeStringShort(substr($recordData, 6));
+ $rec_name = $string['value'];
+ } elseif ($this->version == self::XLS_BIFF7) {
+ $string = $this->readByteStringShort(substr($recordData, 6));
+ $rec_name = $string['value'];
+ }
+
+ $this->sheets[] = [
+ 'name' => $rec_name,
+ 'offset' => $rec_offset,
+ 'sheetState' => $sheetState,
+ 'sheetType' => $sheetType,
+ ];
+ }
+
+ /**
+ * Read EXTERNALBOOK record.
+ */
+ private function readExternalBook(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset within record data
+ $offset = 0;
+
+ // there are 4 types of records
+ if (strlen($recordData) > 4) {
+ // external reference
+ // offset: 0; size: 2; number of sheet names ($nm)
+ $nm = self::getUInt2d($recordData, 0);
+ $offset += 2;
+
+ // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length)
+ $encodedUrlString = self::readUnicodeStringLong(substr($recordData, 2));
+ $offset += $encodedUrlString['size'];
+
+ // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length)
+ $externalSheetNames = [];
+ for ($i = 0; $i < $nm; ++$i) {
+ $externalSheetNameString = self::readUnicodeStringLong(substr($recordData, $offset));
+ $externalSheetNames[] = $externalSheetNameString['value'];
+ $offset += $externalSheetNameString['size'];
+ }
+
+ // store the record data
+ $this->externalBooks[] = [
+ 'type' => 'external',
+ 'encodedUrl' => $encodedUrlString['value'],
+ 'externalSheetNames' => $externalSheetNames,
+ ];
+ } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) {
+ // internal reference
+ // offset: 0; size: 2; number of sheet in this document
+ // offset: 2; size: 2; 0x01 0x04
+ $this->externalBooks[] = [
+ 'type' => 'internal',
+ ];
+ } elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) {
+ // add-in function
+ // offset: 0; size: 2; 0x0001
+ $this->externalBooks[] = [
+ 'type' => 'addInFunction',
+ ];
+ } elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) {
+ // DDE links, OLE links
+ // offset: 0; size: 2; 0x0000
+ // offset: 2; size: var; encoded source document name
+ $this->externalBooks[] = [
+ 'type' => 'DDEorOLE',
+ ];
+ }
+ }
+
+ /**
+ * Read EXTERNNAME record.
+ */
+ private function readExternName(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // external sheet references provided for named cells
+ if ($this->version == self::XLS_BIFF8) {
+ // offset: 0; size: 2; options
+ $options = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2;
+
+ // offset: 4; size: 2; not used
+
+ // offset: 6; size: var
+ $nameString = self::readUnicodeStringShort(substr($recordData, 6));
+
+ // offset: var; size: var; formula data
+ $offset = 6 + $nameString['size'];
+ $formula = $this->getFormulaFromStructure(substr($recordData, $offset));
+
+ $this->externalNames[] = [
+ 'name' => $nameString['value'],
+ 'formula' => $formula,
+ ];
+ }
+ }
+
+ /**
+ * Read EXTERNSHEET record.
+ */
+ private function readExternSheet(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // external sheet references provided for named cells
+ if ($this->version == self::XLS_BIFF8) {
+ // offset: 0; size: 2; number of following ref structures
+ $nm = self::getUInt2d($recordData, 0);
+ for ($i = 0; $i < $nm; ++$i) {
+ $this->ref[] = [
+ // offset: 2 + 6 * $i; index to EXTERNALBOOK record
+ 'externalBookIndex' => self::getUInt2d($recordData, 2 + 6 * $i),
+ // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record
+ 'firstSheetIndex' => self::getUInt2d($recordData, 4 + 6 * $i),
+ // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record
+ 'lastSheetIndex' => self::getUInt2d($recordData, 6 + 6 * $i),
+ ];
+ }
+ }
+ }
+
+ /**
+ * DEFINEDNAME.
+ *
+ * This record is part of a Link Table. It contains the name
+ * and the token array of an internal defined name. Token
+ * arrays of defined names contain tokens with aberrant
+ * token classes.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readDefinedName(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if ($this->version == self::XLS_BIFF8) {
+ // retrieves named cells
+
+ // offset: 0; size: 2; option flags
+ $opts = self::getUInt2d($recordData, 0);
+
+ // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name
+ $isBuiltInName = (0x0020 & $opts) >> 5;
+
+ // offset: 2; size: 1; keyboard shortcut
+
+ // offset: 3; size: 1; length of the name (character count)
+ $nlen = ord($recordData[3]);
+
+ // offset: 4; size: 2; size of the formula data (it can happen that this is zero)
+ // note: there can also be additional data, this is not included in $flen
+ $flen = self::getUInt2d($recordData, 4);
+
+ // offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based)
+ $scope = self::getUInt2d($recordData, 8);
+
+ // offset: 14; size: var; Name (Unicode string without length field)
+ $string = self::readUnicodeString(substr($recordData, 14), $nlen);
+
+ // offset: var; size: $flen; formula data
+ $offset = 14 + $string['size'];
+ $formulaStructure = pack('v', $flen) . substr($recordData, $offset);
+
+ try {
+ $formula = $this->getFormulaFromStructure($formulaStructure);
+ } catch (PhpSpreadsheetException $e) {
+ $formula = '';
+ }
+
+ $this->definedname[] = [
+ 'isBuiltInName' => $isBuiltInName,
+ 'name' => $string['value'],
+ 'formula' => $formula,
+ 'scope' => $scope,
+ ];
+ }
+ }
+
+ /**
+ * Read MSODRAWINGGROUP record.
+ */
+ private function readMsoDrawingGroup(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+
+ // get spliced record data
+ $splicedRecordData = $this->getSplicedRecordData();
+ $recordData = $splicedRecordData['recordData'];
+
+ $this->drawingGroupData .= $recordData;
+ }
+
+ /**
+ * SST - Shared String Table.
+ *
+ * This record contains a list of all strings used anywhere
+ * in the workbook. Each string occurs only once. The
+ * workbook uses indexes into the list to reference the
+ * strings.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readSst(): void
+ {
+ // offset within (spliced) record data
+ $pos = 0;
+
+ // get spliced record data
+ $splicedRecordData = $this->getSplicedRecordData();
+
+ $recordData = $splicedRecordData['recordData'];
+ $spliceOffsets = $splicedRecordData['spliceOffsets'];
+
+ // offset: 0; size: 4; total number of strings in the workbook
+ $pos += 4;
+
+ // offset: 4; size: 4; number of following strings ($nm)
+ $nm = self::getInt4d($recordData, 4);
+ $pos += 4;
+
+ // loop through the Unicode strings (16-bit length)
+ for ($i = 0; $i < $nm; ++$i) {
+ // number of characters in the Unicode string
+ $numChars = self::getUInt2d($recordData, $pos);
+ $pos += 2;
+
+ // option flags
+ $optionFlags = ord($recordData[$pos]);
+ ++$pos;
+
+ // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed
+ $isCompressed = (($optionFlags & 0x01) == 0);
+
+ // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic
+ $hasAsian = (($optionFlags & 0x04) != 0);
+
+ // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text
+ $hasRichText = (($optionFlags & 0x08) != 0);
+
+ if ($hasRichText) {
+ // number of Rich-Text formatting runs
+ $formattingRuns = self::getUInt2d($recordData, $pos);
+ $pos += 2;
+ }
+
+ if ($hasAsian) {
+ // size of Asian phonetic setting
+ $extendedRunLength = self::getInt4d($recordData, $pos);
+ $pos += 4;
+ }
+
+ // expected byte length of character array if not split
+ $len = ($isCompressed) ? $numChars : $numChars * 2;
+
+ // look up limit position
+ foreach ($spliceOffsets as $spliceOffset) {
+ // it can happen that the string is empty, therefore we need
+ // <= and not just <
+ if ($pos <= $spliceOffset) {
+ $limitpos = $spliceOffset;
+
+ break;
+ }
+ }
+
+ if ($pos + $len <= $limitpos) {
+ // character array is not split between records
+
+ $retstr = substr($recordData, $pos, $len);
+ $pos += $len;
+ } else {
+ // character array is split between records
+
+ // first part of character array
+ $retstr = substr($recordData, $pos, $limitpos - $pos);
+
+ $bytesRead = $limitpos - $pos;
+
+ // remaining characters in Unicode string
+ $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2));
+
+ $pos = $limitpos;
+
+ // keep reading the characters
+ while ($charsLeft > 0) {
+ // look up next limit position, in case the string span more than one continue record
+ foreach ($spliceOffsets as $spliceOffset) {
+ if ($pos < $spliceOffset) {
+ $limitpos = $spliceOffset;
+
+ break;
+ }
+ }
+
+ // repeated option flags
+ // OpenOffice.org documentation 5.21
+ $option = ord($recordData[$pos]);
+ ++$pos;
+
+ if ($isCompressed && ($option == 0)) {
+ // 1st fragment compressed
+ // this fragment compressed
+ $len = min($charsLeft, $limitpos - $pos);
+ $retstr .= substr($recordData, $pos, $len);
+ $charsLeft -= $len;
+ $isCompressed = true;
+ } elseif (!$isCompressed && ($option != 0)) {
+ // 1st fragment uncompressed
+ // this fragment uncompressed
+ $len = min($charsLeft * 2, $limitpos - $pos);
+ $retstr .= substr($recordData, $pos, $len);
+ $charsLeft -= $len / 2;
+ $isCompressed = false;
+ } elseif (!$isCompressed && ($option == 0)) {
+ // 1st fragment uncompressed
+ // this fragment compressed
+ $len = min($charsLeft, $limitpos - $pos);
+ for ($j = 0; $j < $len; ++$j) {
+ $retstr .= $recordData[$pos + $j]
+ . chr(0);
+ }
+ $charsLeft -= $len;
+ $isCompressed = false;
+ } else {
+ // 1st fragment compressed
+ // this fragment uncompressed
+ $newstr = '';
+ $jMax = strlen($retstr);
+ for ($j = 0; $j < $jMax; ++$j) {
+ $newstr .= $retstr[$j] . chr(0);
+ }
+ $retstr = $newstr;
+ $len = min($charsLeft * 2, $limitpos - $pos);
+ $retstr .= substr($recordData, $pos, $len);
+ $charsLeft -= $len / 2;
+ $isCompressed = false;
+ }
+
+ $pos += $len;
+ }
+ }
+
+ // convert to UTF-8
+ $retstr = self::encodeUTF16($retstr, $isCompressed);
+
+ // read additional Rich-Text information, if any
+ $fmtRuns = [];
+ if ($hasRichText) {
+ // list of formatting runs
+ for ($j = 0; $j < $formattingRuns; ++$j) {
+ // first formatted character; zero-based
+ $charPos = self::getUInt2d($recordData, $pos + $j * 4);
+
+ // index to font record
+ $fontIndex = self::getUInt2d($recordData, $pos + 2 + $j * 4);
+
+ $fmtRuns[] = [
+ 'charPos' => $charPos,
+ 'fontIndex' => $fontIndex,
+ ];
+ }
+ $pos += 4 * $formattingRuns;
+ }
+
+ // read additional Asian phonetics information, if any
+ if ($hasAsian) {
+ // For Asian phonetic settings, we skip the extended string data
+ $pos += $extendedRunLength;
+ }
+
+ // store the shared sting
+ $this->sst[] = [
+ 'value' => $retstr,
+ 'fmtRuns' => $fmtRuns,
+ ];
+ }
+
+ // getSplicedRecordData() takes care of moving current position in data stream
+ }
+
+ /**
+ * Read PRINTGRIDLINES record.
+ */
+ private function readPrintGridlines(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
+ // offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines
+ $printGridlines = (bool) self::getUInt2d($recordData, 0);
+ $this->phpSheet->setPrintGridlines($printGridlines);
+ }
+ }
+
+ /**
+ * Read DEFAULTROWHEIGHT record.
+ */
+ private function readDefaultRowHeight(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; option flags
+ // offset: 2; size: 2; default height for unused rows, (twips 1/20 point)
+ $height = self::getUInt2d($recordData, 2);
+ $this->phpSheet->getDefaultRowDimension()->setRowHeight($height / 20);
+ }
+
+ /**
+ * Read SHEETPR record.
+ */
+ private function readSheetPr(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2
+
+ // bit: 6; mask: 0x0040; 0 = outline buttons above outline group
+ $isSummaryBelow = (0x0040 & self::getUInt2d($recordData, 0)) >> 6;
+ $this->phpSheet->setShowSummaryBelow($isSummaryBelow);
+
+ // bit: 7; mask: 0x0080; 0 = outline buttons left of outline group
+ $isSummaryRight = (0x0080 & self::getUInt2d($recordData, 0)) >> 7;
+ $this->phpSheet->setShowSummaryRight($isSummaryRight);
+
+ // bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages
+ // this corresponds to radio button setting in page setup dialog in Excel
+ $this->isFitToPages = (bool) ((0x0100 & self::getUInt2d($recordData, 0)) >> 8);
+ }
+
+ /**
+ * Read HORIZONTALPAGEBREAKS record.
+ */
+ private function readHorizontalPageBreaks(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
+ // offset: 0; size: 2; number of the following row index structures
+ $nm = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 6 * $nm; list of $nm row index structures
+ for ($i = 0; $i < $nm; ++$i) {
+ $r = self::getUInt2d($recordData, 2 + 6 * $i);
+ $cf = self::getUInt2d($recordData, 2 + 6 * $i + 2);
+ $cl = self::getUInt2d($recordData, 2 + 6 * $i + 4);
+
+ // not sure why two column indexes are necessary?
+ $this->phpSheet->setBreakByColumnAndRow($cf + 1, $r, Worksheet::BREAK_ROW);
+ }
+ }
+ }
+
+ /**
+ * Read VERTICALPAGEBREAKS record.
+ */
+ private function readVerticalPageBreaks(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
+ // offset: 0; size: 2; number of the following column index structures
+ $nm = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 6 * $nm; list of $nm row index structures
+ for ($i = 0; $i < $nm; ++$i) {
+ $c = self::getUInt2d($recordData, 2 + 6 * $i);
+ $rf = self::getUInt2d($recordData, 2 + 6 * $i + 2);
+ $rl = self::getUInt2d($recordData, 2 + 6 * $i + 4);
+
+ // not sure why two row indexes are necessary?
+ $this->phpSheet->setBreakByColumnAndRow($c + 1, $rf, Worksheet::BREAK_COLUMN);
+ }
+ }
+ }
+
+ /**
+ * Read HEADER record.
+ */
+ private function readHeader(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: var
+ // realized that $recordData can be empty even when record exists
+ if ($recordData) {
+ if ($this->version == self::XLS_BIFF8) {
+ $string = self::readUnicodeStringLong($recordData);
+ } else {
+ $string = $this->readByteStringShort($recordData);
+ }
+
+ $this->phpSheet->getHeaderFooter()->setOddHeader($string['value']);
+ $this->phpSheet->getHeaderFooter()->setEvenHeader($string['value']);
+ }
+ }
+ }
+
+ /**
+ * Read FOOTER record.
+ */
+ private function readFooter(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: var
+ // realized that $recordData can be empty even when record exists
+ if ($recordData) {
+ if ($this->version == self::XLS_BIFF8) {
+ $string = self::readUnicodeStringLong($recordData);
+ } else {
+ $string = $this->readByteStringShort($recordData);
+ }
+ $this->phpSheet->getHeaderFooter()->setOddFooter($string['value']);
+ $this->phpSheet->getHeaderFooter()->setEvenFooter($string['value']);
+ }
+ }
+ }
+
+ /**
+ * Read HCENTER record.
+ */
+ private function readHcenter(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally
+ $isHorizontalCentered = (bool) self::getUInt2d($recordData, 0);
+
+ $this->phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered);
+ }
+ }
+
+ /**
+ * Read VCENTER record.
+ */
+ private function readVcenter(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered
+ $isVerticalCentered = (bool) self::getUInt2d($recordData, 0);
+
+ $this->phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered);
+ }
+ }
+
+ /**
+ * Read LEFTMARGIN record.
+ */
+ private function readLeftMargin(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 8
+ $this->phpSheet->getPageMargins()->setLeft(self::extractNumber($recordData));
+ }
+ }
+
+ /**
+ * Read RIGHTMARGIN record.
+ */
+ private function readRightMargin(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 8
+ $this->phpSheet->getPageMargins()->setRight(self::extractNumber($recordData));
+ }
+ }
+
+ /**
+ * Read TOPMARGIN record.
+ */
+ private function readTopMargin(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 8
+ $this->phpSheet->getPageMargins()->setTop(self::extractNumber($recordData));
+ }
+ }
+
+ /**
+ * Read BOTTOMMARGIN record.
+ */
+ private function readBottomMargin(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 8
+ $this->phpSheet->getPageMargins()->setBottom(self::extractNumber($recordData));
+ }
+ }
+
+ /**
+ * Read PAGESETUP record.
+ */
+ private function readPageSetup(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 2; paper size
+ $paperSize = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; scaling factor
+ $scale = self::getUInt2d($recordData, 2);
+
+ // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed
+ $fitToWidth = self::getUInt2d($recordData, 6);
+
+ // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed
+ $fitToHeight = self::getUInt2d($recordData, 8);
+
+ // offset: 10; size: 2; option flags
+
+ // bit: 1; mask: 0x0002; 0=landscape, 1=portrait
+ $isPortrait = (0x0002 & self::getUInt2d($recordData, 10)) >> 1;
+
+ // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init
+ // when this bit is set, do not use flags for those properties
+ $isNotInit = (0x0004 & self::getUInt2d($recordData, 10)) >> 2;
+
+ if (!$isNotInit) {
+ $this->phpSheet->getPageSetup()->setPaperSize($paperSize);
+ switch ($isPortrait) {
+ case 0:
+ $this->phpSheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE);
+
+ break;
+ case 1:
+ $this->phpSheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_PORTRAIT);
+
+ break;
+ }
+
+ $this->phpSheet->getPageSetup()->setScale($scale, false);
+ $this->phpSheet->getPageSetup()->setFitToPage((bool) $this->isFitToPages);
+ $this->phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false);
+ $this->phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false);
+ }
+
+ // offset: 16; size: 8; header margin (IEEE 754 floating-point value)
+ $marginHeader = self::extractNumber(substr($recordData, 16, 8));
+ $this->phpSheet->getPageMargins()->setHeader($marginHeader);
+
+ // offset: 24; size: 8; footer margin (IEEE 754 floating-point value)
+ $marginFooter = self::extractNumber(substr($recordData, 24, 8));
+ $this->phpSheet->getPageMargins()->setFooter($marginFooter);
+ }
+ }
+
+ /**
+ * PROTECT - Sheet protection (BIFF2 through BIFF8)
+ * if this record is omitted, then it also means no sheet protection.
+ */
+ private function readProtect(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if ($this->readDataOnly) {
+ return;
+ }
+
+ // offset: 0; size: 2;
+
+ // bit 0, mask 0x01; 1 = sheet is protected
+ $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
+ $this->phpSheet->getProtection()->setSheet((bool) $bool);
+ }
+
+ /**
+ * SCENPROTECT.
+ */
+ private function readScenProtect(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if ($this->readDataOnly) {
+ return;
+ }
+
+ // offset: 0; size: 2;
+
+ // bit: 0, mask 0x01; 1 = scenarios are protected
+ $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
+
+ $this->phpSheet->getProtection()->setScenarios((bool) $bool);
+ }
+
+ /**
+ * OBJECTPROTECT.
+ */
+ private function readObjectProtect(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if ($this->readDataOnly) {
+ return;
+ }
+
+ // offset: 0; size: 2;
+
+ // bit: 0, mask 0x01; 1 = objects are protected
+ $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
+
+ $this->phpSheet->getProtection()->setObjects((bool) $bool);
+ }
+
+ /**
+ * PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8).
+ */
+ private function readPassword(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 2; 16-bit hash value of password
+ $password = strtoupper(dechex(self::getUInt2d($recordData, 0))); // the hashed password
+ $this->phpSheet->getProtection()->setPassword($password, true);
+ }
+ }
+
+ /**
+ * Read DEFCOLWIDTH record.
+ */
+ private function readDefColWidth(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; default column width
+ $width = self::getUInt2d($recordData, 0);
+ if ($width != 8) {
+ $this->phpSheet->getDefaultColumnDimension()->setWidth($width);
+ }
+ }
+
+ /**
+ * Read COLINFO record.
+ */
+ private function readColInfo(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 2; index to first column in range
+ $firstColumnIndex = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; index to last column in range
+ $lastColumnIndex = self::getUInt2d($recordData, 2);
+
+ // offset: 4; size: 2; width of the column in 1/256 of the width of the zero character
+ $width = self::getUInt2d($recordData, 4);
+
+ // offset: 6; size: 2; index to XF record for default column formatting
+ $xfIndex = self::getUInt2d($recordData, 6);
+
+ // offset: 8; size: 2; option flags
+ // bit: 0; mask: 0x0001; 1= columns are hidden
+ $isHidden = (0x0001 & self::getUInt2d($recordData, 8)) >> 0;
+
+ // bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline)
+ $level = (0x0700 & self::getUInt2d($recordData, 8)) >> 8;
+
+ // bit: 12; mask: 0x1000; 1 = collapsed
+ $isCollapsed = (0x1000 & self::getUInt2d($recordData, 8)) >> 12;
+
+ // offset: 10; size: 2; not used
+
+ for ($i = $firstColumnIndex + 1; $i <= $lastColumnIndex + 1; ++$i) {
+ if ($lastColumnIndex == 255 || $lastColumnIndex == 256) {
+ $this->phpSheet->getDefaultColumnDimension()->setWidth($width / 256);
+
+ break;
+ }
+ $this->phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256);
+ $this->phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden);
+ $this->phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level);
+ $this->phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed);
+ $this->phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
+ }
+ }
+ }
+
+ /**
+ * ROW.
+ *
+ * This record contains the properties of a single row in a
+ * sheet. Rows and cells in a sheet are divided into blocks
+ * of 32 rows.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readRow(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 2; index of this row
+ $r = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; index to column of the first cell which is described by a cell record
+
+ // offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1
+
+ // offset: 6; size: 2;
+
+ // bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point
+ $height = (0x7FFF & self::getUInt2d($recordData, 6)) >> 0;
+
+ // bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height
+ $useDefaultHeight = (0x8000 & self::getUInt2d($recordData, 6)) >> 15;
+
+ if (!$useDefaultHeight) {
+ $this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
+ }
+
+ // offset: 8; size: 2; not used
+
+ // offset: 10; size: 2; not used in BIFF5-BIFF8
+
+ // offset: 12; size: 4; option flags and default row formatting
+
+ // bit: 2-0: mask: 0x00000007; outline level of the row
+ $level = (0x00000007 & self::getInt4d($recordData, 12)) >> 0;
+ $this->phpSheet->getRowDimension($r + 1)->setOutlineLevel($level);
+
+ // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed
+ $isCollapsed = (0x00000010 & self::getInt4d($recordData, 12)) >> 4;
+ $this->phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed);
+
+ // bit: 5; mask: 0x00000020; 1 = row is hidden
+ $isHidden = (0x00000020 & self::getInt4d($recordData, 12)) >> 5;
+ $this->phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden);
+
+ // bit: 7; mask: 0x00000080; 1 = row has explicit format
+ $hasExplicitFormat = (0x00000080 & self::getInt4d($recordData, 12)) >> 7;
+
+ // bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record
+ $xfIndex = (0x0FFF0000 & self::getInt4d($recordData, 12)) >> 16;
+
+ if ($hasExplicitFormat && isset($this->mapCellXfIndex[$xfIndex])) {
+ $this->phpSheet->getRowDimension($r + 1)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
+ }
+ }
+ }
+
+ /**
+ * Read RK record
+ * This record represents a cell that contains an RK value
+ * (encoded integer or floating-point value). If a
+ * floating-point value cannot be encoded to an RK value,
+ * a NUMBER record will be written. This record replaces the
+ * record INTEGER written in BIFF2.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readRk(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; index to row
+ $row = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; index to column
+ $column = self::getUInt2d($recordData, 2);
+ $columnString = Coordinate::stringFromColumnIndex($column + 1);
+
+ // Read cell?
+ if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
+ // offset: 4; size: 2; index to XF record
+ $xfIndex = self::getUInt2d($recordData, 4);
+
+ // offset: 6; size: 4; RK value
+ $rknum = self::getInt4d($recordData, 6);
+ $numValue = self::getIEEE754($rknum);
+
+ $cell = $this->phpSheet->getCell($columnString . ($row + 1));
+ if (!$this->readDataOnly) {
+ // add style information
+ $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
+ }
+
+ // add cell
+ $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
+ }
+ }
+
+ /**
+ * Read LABELSST record
+ * This record represents a cell that contains a string. It
+ * replaces the LABEL record and RSTRING record used in
+ * BIFF2-BIFF5.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readLabelSst(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; index to row
+ $row = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; index to column
+ $column = self::getUInt2d($recordData, 2);
+ $columnString = Coordinate::stringFromColumnIndex($column + 1);
+
+ $emptyCell = true;
+ // Read cell?
+ if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
+ // offset: 4; size: 2; index to XF record
+ $xfIndex = self::getUInt2d($recordData, 4);
+
+ // offset: 6; size: 4; index to SST record
+ $index = self::getInt4d($recordData, 6);
+
+ // add cell
+ if (($fmtRuns = $this->sst[$index]['fmtRuns']) && !$this->readDataOnly) {
+ // then we should treat as rich text
+ $richText = new RichText();
+ $charPos = 0;
+ $sstCount = count($this->sst[$index]['fmtRuns']);
+ for ($i = 0; $i <= $sstCount; ++$i) {
+ if (isset($fmtRuns[$i])) {
+ $text = StringHelper::substring($this->sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos);
+ $charPos = $fmtRuns[$i]['charPos'];
+ } else {
+ $text = StringHelper::substring($this->sst[$index]['value'], $charPos, StringHelper::countCharacters($this->sst[$index]['value']));
+ }
+
+ if (StringHelper::countCharacters($text) > 0) {
+ if ($i == 0) { // first text run, no style
+ $richText->createText($text);
+ } else {
+ $textRun = $richText->createTextRun($text);
+ if (isset($fmtRuns[$i - 1])) {
+ if ($fmtRuns[$i - 1]['fontIndex'] < 4) {
+ $fontIndex = $fmtRuns[$i - 1]['fontIndex'];
+ } else {
+ // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
+ // check the OpenOffice documentation of the FONT record
+ $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1;
+ }
+ $textRun->setFont(clone $this->objFonts[$fontIndex]);
+ }
+ }
+ }
+ }
+ if ($this->readEmptyCells || trim($richText->getPlainText()) !== '') {
+ $cell = $this->phpSheet->getCell($columnString . ($row + 1));
+ $cell->setValueExplicit($richText, DataType::TYPE_STRING);
+ $emptyCell = false;
+ }
+ } else {
+ if ($this->readEmptyCells || trim($this->sst[$index]['value']) !== '') {
+ $cell = $this->phpSheet->getCell($columnString . ($row + 1));
+ $cell->setValueExplicit($this->sst[$index]['value'], DataType::TYPE_STRING);
+ $emptyCell = false;
+ }
+ }
+
+ if (!$this->readDataOnly && !$emptyCell && isset($this->mapCellXfIndex[$xfIndex])) {
+ // add style information
+ $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
+ }
+ }
+ }
+
+ /**
+ * Read MULRK record
+ * This record represents a cell range containing RK value
+ * cells. All cells are located in the same row.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readMulRk(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; index to row
+ $row = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; index to first column
+ $colFirst = self::getUInt2d($recordData, 2);
+
+ // offset: var; size: 2; index to last column
+ $colLast = self::getUInt2d($recordData, $length - 2);
+ $columns = $colLast - $colFirst + 1;
+
+ // offset within record data
+ $offset = 4;
+
+ for ($i = 1; $i <= $columns; ++$i) {
+ $columnString = Coordinate::stringFromColumnIndex($colFirst + $i);
+
+ // Read cell?
+ if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
+ // offset: var; size: 2; index to XF record
+ $xfIndex = self::getUInt2d($recordData, $offset);
+
+ // offset: var; size: 4; RK value
+ $numValue = self::getIEEE754(self::getInt4d($recordData, $offset + 2));
+ $cell = $this->phpSheet->getCell($columnString . ($row + 1));
+ if (!$this->readDataOnly) {
+ // add style
+ $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
+ }
+
+ // add cell value
+ $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
+ }
+
+ $offset += 6;
+ }
+ }
+
+ /**
+ * Read NUMBER record
+ * This record represents a cell that contains a
+ * floating-point value.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readNumber(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; index to row
+ $row = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size 2; index to column
+ $column = self::getUInt2d($recordData, 2);
+ $columnString = Coordinate::stringFromColumnIndex($column + 1);
+
+ // Read cell?
+ if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
+ // offset 4; size: 2; index to XF record
+ $xfIndex = self::getUInt2d($recordData, 4);
+
+ $numValue = self::extractNumber(substr($recordData, 6, 8));
+
+ $cell = $this->phpSheet->getCell($columnString . ($row + 1));
+ if (!$this->readDataOnly) {
+ // add cell style
+ $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
+ }
+
+ // add cell value
+ $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
+ }
+ }
+
+ /**
+ * Read FORMULA record + perhaps a following STRING record if formula result is a string
+ * This record contains the token array and the result of a
+ * formula cell.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readFormula(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; row index
+ $row = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; col index
+ $column = self::getUInt2d($recordData, 2);
+ $columnString = Coordinate::stringFromColumnIndex($column + 1);
+
+ // offset: 20: size: variable; formula structure
+ $formulaStructure = substr($recordData, 20);
+
+ // offset: 14: size: 2; option flags, recalculate always, recalculate on open etc.
+ $options = self::getUInt2d($recordData, 14);
+
+ // bit: 0; mask: 0x0001; 1 = recalculate always
+ // bit: 1; mask: 0x0002; 1 = calculate on open
+ // bit: 2; mask: 0x0008; 1 = part of a shared formula
+ $isPartOfSharedFormula = (bool) (0x0008 & $options);
+
+ // WARNING:
+ // We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true
+ // the formula data may be ordinary formula data, therefore we need to check
+ // explicitly for the tExp token (0x01)
+ $isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure[2]) == 0x01;
+
+ if ($isPartOfSharedFormula) {
+ // part of shared formula which means there will be a formula with a tExp token and nothing else
+ // get the base cell, grab tExp token
+ $baseRow = self::getUInt2d($formulaStructure, 3);
+ $baseCol = self::getUInt2d($formulaStructure, 5);
+ $this->baseCell = Coordinate::stringFromColumnIndex($baseCol + 1) . ($baseRow + 1);
+ }
+
+ // Read cell?
+ if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
+ if ($isPartOfSharedFormula) {
+ // formula is added to this cell after the sheet has been read
+ $this->sharedFormulaParts[$columnString . ($row + 1)] = $this->baseCell;
+ }
+
+ // offset: 16: size: 4; not used
+
+ // offset: 4; size: 2; XF index
+ $xfIndex = self::getUInt2d($recordData, 4);
+
+ // offset: 6; size: 8; result of the formula
+ if ((ord($recordData[6]) == 0) && (ord($recordData[12]) == 255) && (ord($recordData[13]) == 255)) {
+ // String formula. Result follows in appended STRING record
+ $dataType = DataType::TYPE_STRING;
+
+ // read possible SHAREDFMLA record
+ $code = self::getUInt2d($this->data, $this->pos);
+ if ($code == self::XLS_TYPE_SHAREDFMLA) {
+ $this->readSharedFmla();
+ }
+
+ // read STRING record
+ $value = $this->readString();
+ } elseif ((ord($recordData[6]) == 1)
+ && (ord($recordData[12]) == 255)
+ && (ord($recordData[13]) == 255)) {
+ // Boolean formula. Result is in +2; 0=false, 1=true
+ $dataType = DataType::TYPE_BOOL;
+ $value = (bool) ord($recordData[8]);
+ } elseif ((ord($recordData[6]) == 2)
+ && (ord($recordData[12]) == 255)
+ && (ord($recordData[13]) == 255)) {
+ // Error formula. Error code is in +2
+ $dataType = DataType::TYPE_ERROR;
+ $value = Xls\ErrorCode::lookup(ord($recordData[8]));
+ } elseif ((ord($recordData[6]) == 3)
+ && (ord($recordData[12]) == 255)
+ && (ord($recordData[13]) == 255)) {
+ // Formula result is a null string
+ $dataType = DataType::TYPE_NULL;
+ $value = '';
+ } else {
+ // forumla result is a number, first 14 bytes like _NUMBER record
+ $dataType = DataType::TYPE_NUMERIC;
+ $value = self::extractNumber(substr($recordData, 6, 8));
+ }
+
+ $cell = $this->phpSheet->getCell($columnString . ($row + 1));
+ if (!$this->readDataOnly) {
+ // add cell style
+ $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
+ }
+
+ // store the formula
+ if (!$isPartOfSharedFormula) {
+ // not part of shared formula
+ // add cell value. If we can read formula, populate with formula, otherwise just used cached value
+ try {
+ if ($this->version != self::XLS_BIFF8) {
+ throw new Exception('Not BIFF8. Can only read BIFF8 formulas');
+ }
+ $formula = $this->getFormulaFromStructure($formulaStructure); // get formula in human language
+ $cell->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA);
+ } catch (PhpSpreadsheetException $e) {
+ $cell->setValueExplicit($value, $dataType);
+ }
+ } else {
+ if ($this->version == self::XLS_BIFF8) {
+ // do nothing at this point, formula id added later in the code
+ } else {
+ $cell->setValueExplicit($value, $dataType);
+ }
+ }
+
+ // store the cached calculated value
+ $cell->setCalculatedValue($value);
+ }
+ }
+
+ /**
+ * Read a SHAREDFMLA record. This function just stores the binary shared formula in the reader,
+ * which usually contains relative references.
+ * These will be used to construct the formula in each shared formula part after the sheet is read.
+ */
+ private function readSharedFmla(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything
+ $cellRange = substr($recordData, 0, 6);
+ $cellRange = $this->readBIFF5CellRangeAddressFixed($cellRange); // note: even BIFF8 uses BIFF5 syntax
+
+ // offset: 6, size: 1; not used
+
+ // offset: 7, size: 1; number of existing FORMULA records for this shared formula
+ $no = ord($recordData[7]);
+
+ // offset: 8, size: var; Binary token array of the shared formula
+ $formula = substr($recordData, 8);
+
+ // at this point we only store the shared formula for later use
+ $this->sharedFormulas[$this->baseCell] = $formula;
+ }
+
+ /**
+ * Read a STRING record from current stream position and advance the stream pointer to next record
+ * This record is used for storing result from FORMULA record when it is a string, and
+ * it occurs directly after the FORMULA record.
+ *
+ * @return string The string contents as UTF-8
+ */
+ private function readString()
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if ($this->version == self::XLS_BIFF8) {
+ $string = self::readUnicodeStringLong($recordData);
+ $value = $string['value'];
+ } else {
+ $string = $this->readByteStringLong($recordData);
+ $value = $string['value'];
+ }
+
+ return $value;
+ }
+
+ /**
+ * Read BOOLERR record
+ * This record represents a Boolean value or error value
+ * cell.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readBoolErr(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; row index
+ $row = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; column index
+ $column = self::getUInt2d($recordData, 2);
+ $columnString = Coordinate::stringFromColumnIndex($column + 1);
+
+ // Read cell?
+ if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
+ // offset: 4; size: 2; index to XF record
+ $xfIndex = self::getUInt2d($recordData, 4);
+
+ // offset: 6; size: 1; the boolean value or error value
+ $boolErr = ord($recordData[6]);
+
+ // offset: 7; size: 1; 0=boolean; 1=error
+ $isError = ord($recordData[7]);
+
+ $cell = $this->phpSheet->getCell($columnString . ($row + 1));
+ switch ($isError) {
+ case 0: // boolean
+ $value = (bool) $boolErr;
+
+ // add cell value
+ $cell->setValueExplicit($value, DataType::TYPE_BOOL);
+
+ break;
+ case 1: // error type
+ $value = Xls\ErrorCode::lookup($boolErr);
+
+ // add cell value
+ $cell->setValueExplicit($value, DataType::TYPE_ERROR);
+
+ break;
+ }
+
+ if (!$this->readDataOnly) {
+ // add cell style
+ $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
+ }
+ }
+ }
+
+ /**
+ * Read MULBLANK record
+ * This record represents a cell range of empty cells. All
+ * cells are located in the same row.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readMulBlank(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; index to row
+ $row = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; index to first column
+ $fc = self::getUInt2d($recordData, 2);
+
+ // offset: 4; size: 2 x nc; list of indexes to XF records
+ // add style information
+ if (!$this->readDataOnly && $this->readEmptyCells) {
+ for ($i = 0; $i < $length / 2 - 3; ++$i) {
+ $columnString = Coordinate::stringFromColumnIndex($fc + $i + 1);
+
+ // Read cell?
+ if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
+ $xfIndex = self::getUInt2d($recordData, 4 + 2 * $i);
+ $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
+ }
+ }
+ }
+
+ // offset: 6; size 2; index to last column (not needed)
+ }
+
+ /**
+ * Read LABEL record
+ * This record represents a cell that contains a string. In
+ * BIFF8 it is usually replaced by the LABELSST record.
+ * Excel still uses this record, if it copies unformatted
+ * text cells to the clipboard.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readLabel(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; index to row
+ $row = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; index to column
+ $column = self::getUInt2d($recordData, 2);
+ $columnString = Coordinate::stringFromColumnIndex($column + 1);
+
+ // Read cell?
+ if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
+ // offset: 4; size: 2; XF index
+ $xfIndex = self::getUInt2d($recordData, 4);
+
+ // add cell value
+ // todo: what if string is very long? continue record
+ if ($this->version == self::XLS_BIFF8) {
+ $string = self::readUnicodeStringLong(substr($recordData, 6));
+ $value = $string['value'];
+ } else {
+ $string = $this->readByteStringLong(substr($recordData, 6));
+ $value = $string['value'];
+ }
+ if ($this->readEmptyCells || trim($value) !== '') {
+ $cell = $this->phpSheet->getCell($columnString . ($row + 1));
+ $cell->setValueExplicit($value, DataType::TYPE_STRING);
+
+ if (!$this->readDataOnly) {
+ // add cell style
+ $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Read BLANK record.
+ */
+ private function readBlank(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; row index
+ $row = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; col index
+ $col = self::getUInt2d($recordData, 2);
+ $columnString = Coordinate::stringFromColumnIndex($col + 1);
+
+ // Read cell?
+ if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
+ // offset: 4; size: 2; XF index
+ $xfIndex = self::getUInt2d($recordData, 4);
+
+ // add style information
+ if (!$this->readDataOnly && $this->readEmptyCells) {
+ $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
+ }
+ }
+ }
+
+ /**
+ * Read MSODRAWING record.
+ */
+ private function readMsoDrawing(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+
+ // get spliced record data
+ $splicedRecordData = $this->getSplicedRecordData();
+ $recordData = $splicedRecordData['recordData'];
+
+ $this->drawingData .= $recordData;
+ }
+
+ /**
+ * Read OBJ record.
+ */
+ private function readObj(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if ($this->readDataOnly || $this->version != self::XLS_BIFF8) {
+ return;
+ }
+
+ // recordData consists of an array of subrecords looking like this:
+ // ft: 2 bytes; ftCmo type (0x15)
+ // cb: 2 bytes; size in bytes of ftCmo data
+ // ot: 2 bytes; Object Type
+ // id: 2 bytes; Object id number
+ // grbit: 2 bytes; Option Flags
+ // data: var; subrecord data
+
+ // for now, we are just interested in the second subrecord containing the object type
+ $ftCmoType = self::getUInt2d($recordData, 0);
+ $cbCmoSize = self::getUInt2d($recordData, 2);
+ $otObjType = self::getUInt2d($recordData, 4);
+ $idObjID = self::getUInt2d($recordData, 6);
+ $grbitOpts = self::getUInt2d($recordData, 6);
+
+ $this->objs[] = [
+ 'ftCmoType' => $ftCmoType,
+ 'cbCmoSize' => $cbCmoSize,
+ 'otObjType' => $otObjType,
+ 'idObjID' => $idObjID,
+ 'grbitOpts' => $grbitOpts,
+ ];
+ $this->textObjRef = $idObjID;
+ }
+
+ /**
+ * Read WINDOW2 record.
+ */
+ private function readWindow2(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; option flags
+ $options = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; index to first visible row
+ $firstVisibleRow = self::getUInt2d($recordData, 2);
+
+ // offset: 4; size: 2; index to first visible colum
+ $firstVisibleColumn = self::getUInt2d($recordData, 4);
+ if ($this->version === self::XLS_BIFF8) {
+ // offset: 8; size: 2; not used
+ // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%)
+ // offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%)
+ // offset: 14; size: 4; not used
+ $zoomscaleInPageBreakPreview = self::getUInt2d($recordData, 10);
+ if ($zoomscaleInPageBreakPreview === 0) {
+ $zoomscaleInPageBreakPreview = 60;
+ }
+ $zoomscaleInNormalView = self::getUInt2d($recordData, 12);
+ if ($zoomscaleInNormalView === 0) {
+ $zoomscaleInNormalView = 100;
+ }
+ }
+
+ // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines
+ $showGridlines = (bool) ((0x0002 & $options) >> 1);
+ $this->phpSheet->setShowGridlines($showGridlines);
+
+ // bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers
+ $showRowColHeaders = (bool) ((0x0004 & $options) >> 2);
+ $this->phpSheet->setShowRowColHeaders($showRowColHeaders);
+
+ // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen
+ $this->frozen = (bool) ((0x0008 & $options) >> 3);
+
+ // bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left
+ $this->phpSheet->setRightToLeft((bool) ((0x0040 & $options) >> 6));
+
+ // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active
+ $isActive = (bool) ((0x0400 & $options) >> 10);
+ if ($isActive) {
+ $this->spreadsheet->setActiveSheetIndex($this->spreadsheet->getIndex($this->phpSheet));
+ }
+
+ // bit: 11; mask: 0x0800; 0 = normal view, 1 = page break view
+ $isPageBreakPreview = (bool) ((0x0800 & $options) >> 11);
+
+ //FIXME: set $firstVisibleRow and $firstVisibleColumn
+
+ if ($this->phpSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_PAGE_LAYOUT) {
+ //NOTE: this setting is inferior to page layout view(Excel2007-)
+ $view = $isPageBreakPreview ? SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW : SheetView::SHEETVIEW_NORMAL;
+ $this->phpSheet->getSheetView()->setView($view);
+ if ($this->version === self::XLS_BIFF8) {
+ $zoomScale = $isPageBreakPreview ? $zoomscaleInPageBreakPreview : $zoomscaleInNormalView;
+ $this->phpSheet->getSheetView()->setZoomScale($zoomScale);
+ $this->phpSheet->getSheetView()->setZoomScaleNormal($zoomscaleInNormalView);
+ }
+ }
+ }
+
+ /**
+ * Read PLV Record(Created by Excel2007 or upper).
+ */
+ private function readPageLayoutView(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; rt
+ //->ignore
+ $rt = self::getUInt2d($recordData, 0);
+ // offset: 2; size: 2; grbitfr
+ //->ignore
+ $grbitFrt = self::getUInt2d($recordData, 2);
+ // offset: 4; size: 8; reserved
+ //->ignore
+
+ // offset: 12; size 2; zoom scale
+ $wScalePLV = self::getUInt2d($recordData, 12);
+ // offset: 14; size 2; grbit
+ $grbit = self::getUInt2d($recordData, 14);
+
+ // decomprise grbit
+ $fPageLayoutView = $grbit & 0x01;
+ $fRulerVisible = ($grbit >> 1) & 0x01; //no support
+ $fWhitespaceHidden = ($grbit >> 3) & 0x01; //no support
+
+ if ($fPageLayoutView === 1) {
+ $this->phpSheet->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_LAYOUT);
+ $this->phpSheet->getSheetView()->setZoomScale($wScalePLV); //set by Excel2007 only if SHEETVIEW_PAGE_LAYOUT
+ }
+ //otherwise, we cannot know whether SHEETVIEW_PAGE_LAYOUT or SHEETVIEW_PAGE_BREAK_PREVIEW.
+ }
+
+ /**
+ * Read SCL record.
+ */
+ private function readScl(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // offset: 0; size: 2; numerator of the view magnification
+ $numerator = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; numerator of the view magnification
+ $denumerator = self::getUInt2d($recordData, 2);
+
+ // set the zoom scale (in percent)
+ $this->phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator);
+ }
+
+ /**
+ * Read PANE record.
+ */
+ private function readPane(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 2; position of vertical split
+ $px = self::getUInt2d($recordData, 0);
+
+ // offset: 2; size: 2; position of horizontal split
+ $py = self::getUInt2d($recordData, 2);
+
+ // offset: 4; size: 2; top most visible row in the bottom pane
+ $rwTop = self::getUInt2d($recordData, 4);
+
+ // offset: 6; size: 2; first visible left column in the right pane
+ $colLeft = self::getUInt2d($recordData, 6);
+
+ if ($this->frozen) {
+ // frozen panes
+ $cell = Coordinate::stringFromColumnIndex($px + 1) . ($py + 1);
+ $topLeftCell = Coordinate::stringFromColumnIndex($colLeft + 1) . ($rwTop + 1);
+ $this->phpSheet->freezePane($cell, $topLeftCell);
+ }
+ // unfrozen panes; split windows; not supported by PhpSpreadsheet core
+ }
+ }
+
+ /**
+ * Read SELECTION record. There is one such record for each pane in the sheet.
+ */
+ private function readSelection(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 1; pane identifier
+ $paneId = ord($recordData[0]);
+
+ // offset: 1; size: 2; index to row of the active cell
+ $r = self::getUInt2d($recordData, 1);
+
+ // offset: 3; size: 2; index to column of the active cell
+ $c = self::getUInt2d($recordData, 3);
+
+ // offset: 5; size: 2; index into the following cell range list to the
+ // entry that contains the active cell
+ $index = self::getUInt2d($recordData, 5);
+
+ // offset: 7; size: var; cell range address list containing all selected cell ranges
+ $data = substr($recordData, 7);
+ $cellRangeAddressList = $this->readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax
+
+ $selectedCells = $cellRangeAddressList['cellRangeAddresses'][0];
+
+ // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!)
+ if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) {
+ $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells);
+ }
+
+ // first row '1' + last row '65536' indicates that full column is selected
+ if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) {
+ $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells);
+ }
+
+ // first column 'A' + last column 'IV' indicates that full row is selected
+ if (preg_match('/^(A\d+\:)IV(\d+)$/', $selectedCells)) {
+ $selectedCells = preg_replace('/^(A\d+\:)IV(\d+)$/', '${1}XFD${2}', $selectedCells);
+ }
+
+ $this->phpSheet->setSelectedCells($selectedCells);
+ }
+ }
+
+ private function includeCellRangeFiltered($cellRangeAddress)
+ {
+ $includeCellRange = true;
+ if ($this->getReadFilter() !== null) {
+ $includeCellRange = false;
+ $rangeBoundaries = Coordinate::getRangeBoundaries($cellRangeAddress);
+ ++$rangeBoundaries[1][0];
+ for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; ++$row) {
+ for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; ++$column) {
+ if ($this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
+ $includeCellRange = true;
+
+ break 2;
+ }
+ }
+ }
+ }
+
+ return $includeCellRange;
+ }
+
+ /**
+ * MERGEDCELLS.
+ *
+ * This record contains the addresses of merged cell ranges
+ * in the current sheet.
+ *
+ * -- "OpenOffice.org's Documentation of the Microsoft
+ * Excel File Format"
+ */
+ private function readMergedCells(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
+ $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($recordData);
+ foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) {
+ if ((strpos($cellRangeAddress, ':') !== false) &&
+ ($this->includeCellRangeFiltered($cellRangeAddress))) {
+ $this->phpSheet->mergeCells($cellRangeAddress);
+ }
+ }
+ }
+ }
+
+ /**
+ * Read HYPERLINK record.
+ */
+ private function readHyperLink(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer forward to next record
+ $this->pos += 4 + $length;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 8; cell range address of all cells containing this hyperlink
+ try {
+ $cellRange = $this->readBIFF8CellRangeAddressFixed($recordData);
+ } catch (PhpSpreadsheetException $e) {
+ return;
+ }
+
+ // offset: 8, size: 16; GUID of StdLink
+
+ // offset: 24, size: 4; unknown value
+
+ // offset: 28, size: 4; option flags
+ // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL
+ $isFileLinkOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 0;
+
+ // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL
+ $isAbsPathOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 1;
+
+ // bit: 2 (and 4); mask: 0x00000014; 0 = no description
+ $hasDesc = (0x00000014 & self::getUInt2d($recordData, 28)) >> 2;
+
+ // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text
+ $hasText = (0x00000008 & self::getUInt2d($recordData, 28)) >> 3;
+
+ // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame
+ $hasFrame = (0x00000080 & self::getUInt2d($recordData, 28)) >> 7;
+
+ // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name)
+ $isUNC = (0x00000100 & self::getUInt2d($recordData, 28)) >> 8;
+
+ // offset within record data
+ $offset = 32;
+
+ if ($hasDesc) {
+ // offset: 32; size: var; character count of description text
+ $dl = self::getInt4d($recordData, 32);
+ // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated
+ $desc = self::encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false);
+ $offset += 4 + 2 * $dl;
+ }
+ if ($hasFrame) {
+ $fl = self::getInt4d($recordData, $offset);
+ $offset += 4 + 2 * $fl;
+ }
+
+ // detect type of hyperlink (there are 4 types)
+ $hyperlinkType = null;
+
+ if ($isUNC) {
+ $hyperlinkType = 'UNC';
+ } elseif (!$isFileLinkOrUrl) {
+ $hyperlinkType = 'workbook';
+ } elseif (ord($recordData[$offset]) == 0x03) {
+ $hyperlinkType = 'local';
+ } elseif (ord($recordData[$offset]) == 0xE0) {
+ $hyperlinkType = 'URL';
+ }
+
+ switch ($hyperlinkType) {
+ case 'URL':
+ // section 5.58.2: Hyperlink containing a URL
+ // e.g. http://example.org/index.php
+
+ // offset: var; size: 16; GUID of URL Moniker
+ $offset += 16;
+ // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word
+ $us = self::getInt4d($recordData, $offset);
+ $offset += 4;
+ // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated
+ $url = self::encodeUTF16(substr($recordData, $offset, $us - 2), false);
+ $nullOffset = strpos($url, chr(0x00));
+ if ($nullOffset) {
+ $url = substr($url, 0, $nullOffset);
+ }
+ $url .= $hasText ? '#' : '';
+ $offset += $us;
+
+ break;
+ case 'local':
+ // section 5.58.3: Hyperlink to local file
+ // examples:
+ // mydoc.txt
+ // ../../somedoc.xls#Sheet!A1
+
+ // offset: var; size: 16; GUI of File Moniker
+ $offset += 16;
+
+ // offset: var; size: 2; directory up-level count.
+ $upLevelCount = self::getUInt2d($recordData, $offset);
+ $offset += 2;
+
+ // offset: var; size: 4; character count of the shortened file path and name, including trailing zero word
+ $sl = self::getInt4d($recordData, $offset);
+ $offset += 4;
+
+ // offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string)
+ $shortenedFilePath = substr($recordData, $offset, $sl);
+ $shortenedFilePath = self::encodeUTF16($shortenedFilePath, true);
+ $shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero
+
+ $offset += $sl;
+
+ // offset: var; size: 24; unknown sequence
+ $offset += 24;
+
+ // extended file path
+ // offset: var; size: 4; size of the following file link field including string lenth mark
+ $sz = self::getInt4d($recordData, $offset);
+ $offset += 4;
+
+ // only present if $sz > 0
+ if ($sz > 0) {
+ // offset: var; size: 4; size of the character array of the extended file path and name
+ $xl = self::getInt4d($recordData, $offset);
+ $offset += 4;
+
+ // offset: var; size 2; unknown
+ $offset += 2;
+
+ // offset: var; size $xl; character array of the extended file path and name.
+ $extendedFilePath = substr($recordData, $offset, $xl);
+ $extendedFilePath = self::encodeUTF16($extendedFilePath, false);
+ $offset += $xl;
+ }
+
+ // construct the path
+ $url = str_repeat('..\\', $upLevelCount);
+ $url .= ($sz > 0) ? $extendedFilePath : $shortenedFilePath; // use extended path if available
+ $url .= $hasText ? '#' : '';
+
+ break;
+ case 'UNC':
+ // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path
+ // todo: implement
+ return;
+ case 'workbook':
+ // section 5.58.5: Hyperlink to the Current Workbook
+ // e.g. Sheet2!B1:C2, stored in text mark field
+ $url = 'sheet://';
+
+ break;
+ default:
+ return;
+ }
+
+ if ($hasText) {
+ // offset: var; size: 4; character count of text mark including trailing zero word
+ $tl = self::getInt4d($recordData, $offset);
+ $offset += 4;
+ // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated
+ $text = self::encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false);
+ $url .= $text;
+ }
+
+ // apply the hyperlink to all the relevant cells
+ foreach (Coordinate::extractAllCellReferencesInRange($cellRange) as $coordinate) {
+ $this->phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url);
+ }
+ }
+ }
+
+ /**
+ * Read DATAVALIDATIONS record.
+ */
+ private function readDataValidations(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer forward to next record
+ $this->pos += 4 + $length;
+ }
+
+ /**
+ * Read DATAVALIDATION record.
+ */
+ private function readDataValidation(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer forward to next record
+ $this->pos += 4 + $length;
+
+ if ($this->readDataOnly) {
+ return;
+ }
+
+ // offset: 0; size: 4; Options
+ $options = self::getInt4d($recordData, 0);
+
+ // bit: 0-3; mask: 0x0000000F; type
+ $type = (0x0000000F & $options) >> 0;
+ switch ($type) {
+ case 0x00:
+ $type = DataValidation::TYPE_NONE;
+
+ break;
+ case 0x01:
+ $type = DataValidation::TYPE_WHOLE;
+
+ break;
+ case 0x02:
+ $type = DataValidation::TYPE_DECIMAL;
+
+ break;
+ case 0x03:
+ $type = DataValidation::TYPE_LIST;
+
+ break;
+ case 0x04:
+ $type = DataValidation::TYPE_DATE;
+
+ break;
+ case 0x05:
+ $type = DataValidation::TYPE_TIME;
+
+ break;
+ case 0x06:
+ $type = DataValidation::TYPE_TEXTLENGTH;
+
+ break;
+ case 0x07:
+ $type = DataValidation::TYPE_CUSTOM;
+
+ break;
+ }
+
+ // bit: 4-6; mask: 0x00000070; error type
+ $errorStyle = (0x00000070 & $options) >> 4;
+ switch ($errorStyle) {
+ case 0x00:
+ $errorStyle = DataValidation::STYLE_STOP;
+
+ break;
+ case 0x01:
+ $errorStyle = DataValidation::STYLE_WARNING;
+
+ break;
+ case 0x02:
+ $errorStyle = DataValidation::STYLE_INFORMATION;
+
+ break;
+ }
+
+ // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list)
+ // I have only seen cases where this is 1
+ $explicitFormula = (0x00000080 & $options) >> 7;
+
+ // bit: 8; mask: 0x00000100; 1= empty cells allowed
+ $allowBlank = (0x00000100 & $options) >> 8;
+
+ // bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity
+ $suppressDropDown = (0x00000200 & $options) >> 9;
+
+ // bit: 18; mask: 0x00040000; 1= show prompt box if cell selected
+ $showInputMessage = (0x00040000 & $options) >> 18;
+
+ // bit: 19; mask: 0x00080000; 1= show error box if invalid values entered
+ $showErrorMessage = (0x00080000 & $options) >> 19;
+
+ // bit: 20-23; mask: 0x00F00000; condition operator
+ $operator = (0x00F00000 & $options) >> 20;
+ switch ($operator) {
+ case 0x00:
+ $operator = DataValidation::OPERATOR_BETWEEN;
+
+ break;
+ case 0x01:
+ $operator = DataValidation::OPERATOR_NOTBETWEEN;
+
+ break;
+ case 0x02:
+ $operator = DataValidation::OPERATOR_EQUAL;
+
+ break;
+ case 0x03:
+ $operator = DataValidation::OPERATOR_NOTEQUAL;
+
+ break;
+ case 0x04:
+ $operator = DataValidation::OPERATOR_GREATERTHAN;
+
+ break;
+ case 0x05:
+ $operator = DataValidation::OPERATOR_LESSTHAN;
+
+ break;
+ case 0x06:
+ $operator = DataValidation::OPERATOR_GREATERTHANOREQUAL;
+
+ break;
+ case 0x07:
+ $operator = DataValidation::OPERATOR_LESSTHANOREQUAL;
+
+ break;
+ }
+
+ // offset: 4; size: var; title of the prompt box
+ $offset = 4;
+ $string = self::readUnicodeStringLong(substr($recordData, $offset));
+ $promptTitle = $string['value'] !== chr(0) ? $string['value'] : '';
+ $offset += $string['size'];
+
+ // offset: var; size: var; title of the error box
+ $string = self::readUnicodeStringLong(substr($recordData, $offset));
+ $errorTitle = $string['value'] !== chr(0) ? $string['value'] : '';
+ $offset += $string['size'];
+
+ // offset: var; size: var; text of the prompt box
+ $string = self::readUnicodeStringLong(substr($recordData, $offset));
+ $prompt = $string['value'] !== chr(0) ? $string['value'] : '';
+ $offset += $string['size'];
+
+ // offset: var; size: var; text of the error box
+ $string = self::readUnicodeStringLong(substr($recordData, $offset));
+ $error = $string['value'] !== chr(0) ? $string['value'] : '';
+ $offset += $string['size'];
+
+ // offset: var; size: 2; size of the formula data for the first condition
+ $sz1 = self::getUInt2d($recordData, $offset);
+ $offset += 2;
+
+ // offset: var; size: 2; not used
+ $offset += 2;
+
+ // offset: var; size: $sz1; formula data for first condition (without size field)
+ $formula1 = substr($recordData, $offset, $sz1);
+ $formula1 = pack('v', $sz1) . $formula1; // prepend the length
+ try {
+ $formula1 = $this->getFormulaFromStructure($formula1);
+
+ // in list type validity, null characters are used as item separators
+ if ($type == DataValidation::TYPE_LIST) {
+ $formula1 = str_replace(chr(0), ',', $formula1);
+ }
+ } catch (PhpSpreadsheetException $e) {
+ return;
+ }
+ $offset += $sz1;
+
+ // offset: var; size: 2; size of the formula data for the first condition
+ $sz2 = self::getUInt2d($recordData, $offset);
+ $offset += 2;
+
+ // offset: var; size: 2; not used
+ $offset += 2;
+
+ // offset: var; size: $sz2; formula data for second condition (without size field)
+ $formula2 = substr($recordData, $offset, $sz2);
+ $formula2 = pack('v', $sz2) . $formula2; // prepend the length
+ try {
+ $formula2 = $this->getFormulaFromStructure($formula2);
+ } catch (PhpSpreadsheetException $e) {
+ return;
+ }
+ $offset += $sz2;
+
+ // offset: var; size: var; cell range address list with
+ $cellRangeAddressList = $this->readBIFF8CellRangeAddressList(substr($recordData, $offset));
+ $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
+
+ foreach ($cellRangeAddresses as $cellRange) {
+ $stRange = $this->phpSheet->shrinkRangeToFit($cellRange);
+ foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $coordinate) {
+ $objValidation = $this->phpSheet->getCell($coordinate)->getDataValidation();
+ $objValidation->setType($type);
+ $objValidation->setErrorStyle($errorStyle);
+ $objValidation->setAllowBlank((bool) $allowBlank);
+ $objValidation->setShowInputMessage((bool) $showInputMessage);
+ $objValidation->setShowErrorMessage((bool) $showErrorMessage);
+ $objValidation->setShowDropDown(!$suppressDropDown);
+ $objValidation->setOperator($operator);
+ $objValidation->setErrorTitle($errorTitle);
+ $objValidation->setError($error);
+ $objValidation->setPromptTitle($promptTitle);
+ $objValidation->setPrompt($prompt);
+ $objValidation->setFormula1($formula1);
+ $objValidation->setFormula2($formula2);
+ }
+ }
+ }
+
+ /**
+ * Read SHEETLAYOUT record. Stores sheet tab color information.
+ */
+ private function readSheetLayout(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // local pointer in record data
+ $offset = 0;
+
+ if (!$this->readDataOnly) {
+ // offset: 0; size: 2; repeated record identifier 0x0862
+
+ // offset: 2; size: 10; not used
+
+ // offset: 12; size: 4; size of record data
+ // Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?)
+ $sz = self::getInt4d($recordData, 12);
+
+ switch ($sz) {
+ case 0x14:
+ // offset: 16; size: 2; color index for sheet tab
+ $colorIndex = self::getUInt2d($recordData, 16);
+ $color = Xls\Color::map($colorIndex, $this->palette, $this->version);
+ $this->phpSheet->getTabColor()->setRGB($color['rgb']);
+
+ break;
+ case 0x28:
+ // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007
+ return;
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * Read SHEETPROTECTION record (FEATHEADR).
+ */
+ private function readSheetProtection(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ if ($this->readDataOnly) {
+ return;
+ }
+
+ // offset: 0; size: 2; repeated record header
+
+ // offset: 2; size: 2; FRT cell reference flag (=0 currently)
+
+ // offset: 4; size: 8; Currently not used and set to 0
+
+ // offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag)
+ $isf = self::getUInt2d($recordData, 12);
+ if ($isf != 2) {
+ return;
+ }
+
+ // offset: 14; size: 1; =1 since this is a feat header
+
+ // offset: 15; size: 4; size of rgbHdrSData
+
+ // rgbHdrSData, assume "Enhanced Protection"
+ // offset: 19; size: 2; option flags
+ $options = self::getUInt2d($recordData, 19);
+
+ // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects
+ $bool = (0x0001 & $options) >> 0;
+ $this->phpSheet->getProtection()->setObjects(!$bool);
+
+ // bit: 1; mask 0x0002; edit scenarios
+ $bool = (0x0002 & $options) >> 1;
+ $this->phpSheet->getProtection()->setScenarios(!$bool);
+
+ // bit: 2; mask 0x0004; format cells
+ $bool = (0x0004 & $options) >> 2;
+ $this->phpSheet->getProtection()->setFormatCells(!$bool);
+
+ // bit: 3; mask 0x0008; format columns
+ $bool = (0x0008 & $options) >> 3;
+ $this->phpSheet->getProtection()->setFormatColumns(!$bool);
+
+ // bit: 4; mask 0x0010; format rows
+ $bool = (0x0010 & $options) >> 4;
+ $this->phpSheet->getProtection()->setFormatRows(!$bool);
+
+ // bit: 5; mask 0x0020; insert columns
+ $bool = (0x0020 & $options) >> 5;
+ $this->phpSheet->getProtection()->setInsertColumns(!$bool);
+
+ // bit: 6; mask 0x0040; insert rows
+ $bool = (0x0040 & $options) >> 6;
+ $this->phpSheet->getProtection()->setInsertRows(!$bool);
+
+ // bit: 7; mask 0x0080; insert hyperlinks
+ $bool = (0x0080 & $options) >> 7;
+ $this->phpSheet->getProtection()->setInsertHyperlinks(!$bool);
+
+ // bit: 8; mask 0x0100; delete columns
+ $bool = (0x0100 & $options) >> 8;
+ $this->phpSheet->getProtection()->setDeleteColumns(!$bool);
+
+ // bit: 9; mask 0x0200; delete rows
+ $bool = (0x0200 & $options) >> 9;
+ $this->phpSheet->getProtection()->setDeleteRows(!$bool);
+
+ // bit: 10; mask 0x0400; select locked cells
+ $bool = (0x0400 & $options) >> 10;
+ $this->phpSheet->getProtection()->setSelectLockedCells(!$bool);
+
+ // bit: 11; mask 0x0800; sort cell range
+ $bool = (0x0800 & $options) >> 11;
+ $this->phpSheet->getProtection()->setSort(!$bool);
+
+ // bit: 12; mask 0x1000; auto filter
+ $bool = (0x1000 & $options) >> 12;
+ $this->phpSheet->getProtection()->setAutoFilter(!$bool);
+
+ // bit: 13; mask 0x2000; pivot tables
+ $bool = (0x2000 & $options) >> 13;
+ $this->phpSheet->getProtection()->setPivotTables(!$bool);
+
+ // bit: 14; mask 0x4000; select unlocked cells
+ $bool = (0x4000 & $options) >> 14;
+ $this->phpSheet->getProtection()->setSelectUnlockedCells(!$bool);
+
+ // offset: 21; size: 2; not used
+ }
+
+ /**
+ * Read RANGEPROTECTION record
+ * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification,
+ * where it is referred to as FEAT record.
+ */
+ private function readRangeProtection(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ // local pointer in record data
+ $offset = 0;
+
+ if (!$this->readDataOnly) {
+ $offset += 12;
+
+ // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag
+ $isf = self::getUInt2d($recordData, 12);
+ if ($isf != 2) {
+ // we only read FEAT records of type 2
+ return;
+ }
+ $offset += 2;
+
+ $offset += 5;
+
+ // offset: 19; size: 2; count of ref ranges this feature is on
+ $cref = self::getUInt2d($recordData, 19);
+ $offset += 2;
+
+ $offset += 6;
+
+ // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record)
+ $cellRanges = [];
+ for ($i = 0; $i < $cref; ++$i) {
+ try {
+ $cellRange = $this->readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8));
+ } catch (PhpSpreadsheetException $e) {
+ return;
+ }
+ $cellRanges[] = $cellRange;
+ $offset += 8;
+ }
+
+ // offset: var; size: var; variable length of feature specific data
+ $rgbFeat = substr($recordData, $offset);
+ $offset += 4;
+
+ // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit)
+ $wPassword = self::getInt4d($recordData, $offset);
+ $offset += 4;
+
+ // Apply range protection to sheet
+ if ($cellRanges) {
+ $this->phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true);
+ }
+ }
+ }
+
+ /**
+ * Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record
+ * When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented.
+ * In this case, we must treat the CONTINUE record as a MSODRAWING record.
+ */
+ private function readContinue(): void
+ {
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ // check if we are reading drawing data
+ // this is in case a free CONTINUE record occurs in other circumstances we are unaware of
+ if ($this->drawingData == '') {
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ return;
+ }
+
+ // check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data
+ if ($length < 4) {
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+
+ return;
+ }
+
+ // dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record
+ // look inside CONTINUE record to see if it looks like a part of an Escher stream
+ // we know that Escher stream may be split at least at
+ // 0xF003 MsofbtSpgrContainer
+ // 0xF004 MsofbtSpContainer
+ // 0xF00D MsofbtClientTextbox
+ $validSplitPoints = [0xF003, 0xF004, 0xF00D]; // add identifiers if we find more
+
+ $splitPoint = self::getUInt2d($recordData, 2);
+ if (in_array($splitPoint, $validSplitPoints)) {
+ // get spliced record data (and move pointer to next record)
+ $splicedRecordData = $this->getSplicedRecordData();
+ $this->drawingData .= $splicedRecordData['recordData'];
+
+ return;
+ }
+
+ // move stream pointer to next record
+ $this->pos += 4 + $length;
+ }
+
+ /**
+ * Reads a record from current position in data stream and continues reading data as long as CONTINUE
+ * records are found. Splices the record data pieces and returns the combined string as if record data
+ * is in one piece.
+ * Moves to next current position in data stream to start of next record different from a CONtINUE record.
+ *
+ * @return array
+ */
+ private function getSplicedRecordData()
+ {
+ $data = '';
+ $spliceOffsets = [];
+
+ $i = 0;
+ $spliceOffsets[0] = 0;
+
+ do {
+ ++$i;
+
+ // offset: 0; size: 2; identifier
+ $identifier = self::getUInt2d($this->data, $this->pos);
+ // offset: 2; size: 2; length
+ $length = self::getUInt2d($this->data, $this->pos + 2);
+ $data .= $this->readRecordData($this->data, $this->pos + 4, $length);
+
+ $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length;
+
+ $this->pos += 4 + $length;
+ $nextIdentifier = self::getUInt2d($this->data, $this->pos);
+ } while ($nextIdentifier == self::XLS_TYPE_CONTINUE);
+
+ return [
+ 'recordData' => $data,
+ 'spliceOffsets' => $spliceOffsets,
+ ];
+ }
+
+ /**
+ * Convert formula structure into human readable Excel formula like 'A3+A5*5'.
+ *
+ * @param string $formulaStructure The complete binary data for the formula
+ * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
+ *
+ * @return string Human readable formula
+ */
+ private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1')
+ {
+ // offset: 0; size: 2; size of the following formula data
+ $sz = self::getUInt2d($formulaStructure, 0);
+
+ // offset: 2; size: sz
+ $formulaData = substr($formulaStructure, 2, $sz);
+
+ // offset: 2 + sz; size: variable (optional)
+ if (strlen($formulaStructure) > 2 + $sz) {
+ $additionalData = substr($formulaStructure, 2 + $sz);
+ } else {
+ $additionalData = '';
+ }
+
+ return $this->getFormulaFromData($formulaData, $additionalData, $baseCell);
+ }
+
+ /**
+ * Take formula data and additional data for formula and return human readable formula.
+ *
+ * @param string $formulaData The binary data for the formula itself
+ * @param string $additionalData Additional binary data going with the formula
+ * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
+ *
+ * @return string Human readable formula
+ */
+ private function getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1')
+ {
+ // start parsing the formula data
+ $tokens = [];
+
+ while (strlen($formulaData) > 0 && $token = $this->getNextToken($formulaData, $baseCell)) {
+ $tokens[] = $token;
+ $formulaData = substr($formulaData, $token['size']);
+ }
+
+ $formulaString = $this->createFormulaFromTokens($tokens, $additionalData);
+
+ return $formulaString;
+ }
+
+ /**
+ * Take array of tokens together with additional data for formula and return human readable formula.
+ *
+ * @param array $tokens
+ * @param string $additionalData Additional binary data going with the formula
+ *
+ * @return string Human readable formula
+ */
+ private function createFormulaFromTokens($tokens, $additionalData)
+ {
+ // empty formula?
+ if (empty($tokens)) {
+ return '';
+ }
+
+ $formulaStrings = [];
+ foreach ($tokens as $token) {
+ // initialize spaces
+ $space0 = $space0 ?? ''; // spaces before next token, not tParen
+ $space1 = $space1 ?? ''; // carriage returns before next token, not tParen
+ $space2 = $space2 ?? ''; // spaces before opening parenthesis
+ $space3 = $space3 ?? ''; // carriage returns before opening parenthesis
+ $space4 = $space4 ?? ''; // spaces before closing parenthesis
+ $space5 = $space5 ?? ''; // carriage returns before closing parenthesis
+
+ switch ($token['name']) {
+ case 'tAdd': // addition
+ case 'tConcat': // addition
+ case 'tDiv': // division
+ case 'tEQ': // equality
+ case 'tGE': // greater than or equal
+ case 'tGT': // greater than
+ case 'tIsect': // intersection
+ case 'tLE': // less than or equal
+ case 'tList': // less than or equal
+ case 'tLT': // less than
+ case 'tMul': // multiplication
+ case 'tNE': // multiplication
+ case 'tPower': // power
+ case 'tRange': // range
+ case 'tSub': // subtraction
+ $op2 = array_pop($formulaStrings);
+ $op1 = array_pop($formulaStrings);
+ $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2";
+ unset($space0, $space1);
+
+ break;
+ case 'tUplus': // unary plus
+ case 'tUminus': // unary minus
+ $op = array_pop($formulaStrings);
+ $formulaStrings[] = "$space1$space0{$token['data']}$op";
+ unset($space0, $space1);
+
+ break;
+ case 'tPercent': // percent sign
+ $op = array_pop($formulaStrings);
+ $formulaStrings[] = "$op$space1$space0{$token['data']}";
+ unset($space0, $space1);
+
+ break;
+ case 'tAttrVolatile': // indicates volatile function
+ case 'tAttrIf':
+ case 'tAttrSkip':
+ case 'tAttrChoose':
+ // token is only important for Excel formula evaluator
+ // do nothing
+ break;
+ case 'tAttrSpace': // space / carriage return
+ // space will be used when next token arrives, do not alter formulaString stack
+ switch ($token['data']['spacetype']) {
+ case 'type0':
+ $space0 = str_repeat(' ', $token['data']['spacecount']);
+
+ break;
+ case 'type1':
+ $space1 = str_repeat("\n", $token['data']['spacecount']);
+
+ break;
+ case 'type2':
+ $space2 = str_repeat(' ', $token['data']['spacecount']);
+
+ break;
+ case 'type3':
+ $space3 = str_repeat("\n", $token['data']['spacecount']);
+
+ break;
+ case 'type4':
+ $space4 = str_repeat(' ', $token['data']['spacecount']);
+
+ break;
+ case 'type5':
+ $space5 = str_repeat("\n", $token['data']['spacecount']);
+
+ break;
+ }
+
+ break;
+ case 'tAttrSum': // SUM function with one parameter
+ $op = array_pop($formulaStrings);
+ $formulaStrings[] = "{$space1}{$space0}SUM($op)";
+ unset($space0, $space1);
+
+ break;
+ case 'tFunc': // function with fixed number of arguments
+ case 'tFuncV': // function with variable number of arguments
+ if ($token['data']['function'] != '') {
+ // normal function
+ $ops = []; // array of operators
+ for ($i = 0; $i < $token['data']['args']; ++$i) {
+ $ops[] = array_pop($formulaStrings);
+ }
+ $ops = array_reverse($ops);
+ $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ')';
+ unset($space0, $space1);
+ } else {
+ // add-in function
+ $ops = []; // array of operators
+ for ($i = 0; $i < $token['data']['args'] - 1; ++$i) {
+ $ops[] = array_pop($formulaStrings);
+ }
+ $ops = array_reverse($ops);
+ $function = array_pop($formulaStrings);
+ $formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ')';
+ unset($space0, $space1);
+ }
+
+ break;
+ case 'tParen': // parenthesis
+ $expression = array_pop($formulaStrings);
+ $formulaStrings[] = "$space3$space2($expression$space5$space4)";
+ unset($space2, $space3, $space4, $space5);
+
+ break;
+ case 'tArray': // array constant
+ $constantArray = self::readBIFF8ConstantArray($additionalData);
+ $formulaStrings[] = $space1 . $space0 . $constantArray['value'];
+ $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data
+ unset($space0, $space1);
+
+ break;
+ case 'tMemArea':
+ // bite off chunk of additional data
+ $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($additionalData);
+ $additionalData = substr($additionalData, $cellRangeAddressList['size']);
+ $formulaStrings[] = "$space1$space0{$token['data']}";
+ unset($space0, $space1);
+
+ break;
+ case 'tArea': // cell range address
+ case 'tBool': // boolean
+ case 'tErr': // error code
+ case 'tInt': // integer
+ case 'tMemErr':
+ case 'tMemFunc':
+ case 'tMissArg':
+ case 'tName':
+ case 'tNameX':
+ case 'tNum': // number
+ case 'tRef': // single cell reference
+ case 'tRef3d': // 3d cell reference
+ case 'tArea3d': // 3d cell range reference
+ case 'tRefN':
+ case 'tAreaN':
+ case 'tStr': // string
+ $formulaStrings[] = "$space1$space0{$token['data']}";
+ unset($space0, $space1);
+
+ break;
+ }
+ }
+ $formulaString = $formulaStrings[0];
+
+ return $formulaString;
+ }
+
+ /**
+ * Fetch next token from binary formula data.
+ *
+ * @param string $formulaData Formula data
+ * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
+ *
+ * @return array
+ */
+ private function getNextToken($formulaData, $baseCell = 'A1')
+ {
+ // offset: 0; size: 1; token id
+ $id = ord($formulaData[0]); // token id
+ $name = false; // initialize token name
+
+ switch ($id) {
+ case 0x03:
+ $name = 'tAdd';
+ $size = 1;
+ $data = '+';
+
+ break;
+ case 0x04:
+ $name = 'tSub';
+ $size = 1;
+ $data = '-';
+
+ break;
+ case 0x05:
+ $name = 'tMul';
+ $size = 1;
+ $data = '*';
+
+ break;
+ case 0x06:
+ $name = 'tDiv';
+ $size = 1;
+ $data = '/';
+
+ break;
+ case 0x07:
+ $name = 'tPower';
+ $size = 1;
+ $data = '^';
+
+ break;
+ case 0x08:
+ $name = 'tConcat';
+ $size = 1;
+ $data = '&';
+
+ break;
+ case 0x09:
+ $name = 'tLT';
+ $size = 1;
+ $data = '<';
+
+ break;
+ case 0x0A:
+ $name = 'tLE';
+ $size = 1;
+ $data = '<=';
+
+ break;
+ case 0x0B:
+ $name = 'tEQ';
+ $size = 1;
+ $data = '=';
+
+ break;
+ case 0x0C:
+ $name = 'tGE';
+ $size = 1;
+ $data = '>=';
+
+ break;
+ case 0x0D:
+ $name = 'tGT';
+ $size = 1;
+ $data = '>';
+
+ break;
+ case 0x0E:
+ $name = 'tNE';
+ $size = 1;
+ $data = '<>';
+
+ break;
+ case 0x0F:
+ $name = 'tIsect';
+ $size = 1;
+ $data = ' ';
+
+ break;
+ case 0x10:
+ $name = 'tList';
+ $size = 1;
+ $data = ',';
+
+ break;
+ case 0x11:
+ $name = 'tRange';
+ $size = 1;
+ $data = ':';
+
+ break;
+ case 0x12:
+ $name = 'tUplus';
+ $size = 1;
+ $data = '+';
+
+ break;
+ case 0x13:
+ $name = 'tUminus';
+ $size = 1;
+ $data = '-';
+
+ break;
+ case 0x14:
+ $name = 'tPercent';
+ $size = 1;
+ $data = '%';
+
+ break;
+ case 0x15: // parenthesis
+ $name = 'tParen';
+ $size = 1;
+ $data = null;
+
+ break;
+ case 0x16: // missing argument
+ $name = 'tMissArg';
+ $size = 1;
+ $data = '';
+
+ break;
+ case 0x17: // string
+ $name = 'tStr';
+ // offset: 1; size: var; Unicode string, 8-bit string length
+ $string = self::readUnicodeStringShort(substr($formulaData, 1));
+ $size = 1 + $string['size'];
+ $data = self::UTF8toExcelDoubleQuoted($string['value']);
+
+ break;
+ case 0x19: // Special attribute
+ // offset: 1; size: 1; attribute type flags:
+ switch (ord($formulaData[1])) {
+ case 0x01:
+ $name = 'tAttrVolatile';
+ $size = 4;
+ $data = null;
+
+ break;
+ case 0x02:
+ $name = 'tAttrIf';
+ $size = 4;
+ $data = null;
+
+ break;
+ case 0x04:
+ $name = 'tAttrChoose';
+ // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1)
+ $nc = self::getUInt2d($formulaData, 2);
+ // offset: 4; size: 2 * $nc
+ // offset: 4 + 2 * $nc; size: 2
+ $size = 2 * $nc + 6;
+ $data = null;
+
+ break;
+ case 0x08:
+ $name = 'tAttrSkip';
+ $size = 4;
+ $data = null;
+
+ break;
+ case 0x10:
+ $name = 'tAttrSum';
+ $size = 4;
+ $data = null;
+
+ break;
+ case 0x40:
+ case 0x41:
+ $name = 'tAttrSpace';
+ $size = 4;
+ // offset: 2; size: 2; space type and position
+ switch (ord($formulaData[2])) {
+ case 0x00:
+ $spacetype = 'type0';
+
+ break;
+ case 0x01:
+ $spacetype = 'type1';
+
+ break;
+ case 0x02:
+ $spacetype = 'type2';
+
+ break;
+ case 0x03:
+ $spacetype = 'type3';
+
+ break;
+ case 0x04:
+ $spacetype = 'type4';
+
+ break;
+ case 0x05:
+ $spacetype = 'type5';
+
+ break;
+ default:
+ throw new Exception('Unrecognized space type in tAttrSpace token');
+
+ break;
+ }
+ // offset: 3; size: 1; number of inserted spaces/carriage returns
+ $spacecount = ord($formulaData[3]);
+
+ $data = ['spacetype' => $spacetype, 'spacecount' => $spacecount];
+
+ break;
+ default:
+ throw new Exception('Unrecognized attribute flag in tAttr token');
+
+ break;
+ }
+
+ break;
+ case 0x1C: // error code
+ // offset: 1; size: 1; error code
+ $name = 'tErr';
+ $size = 2;
+ $data = Xls\ErrorCode::lookup(ord($formulaData[1]));
+
+ break;
+ case 0x1D: // boolean
+ // offset: 1; size: 1; 0 = false, 1 = true;
+ $name = 'tBool';
+ $size = 2;
+ $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE';
+
+ break;
+ case 0x1E: // integer
+ // offset: 1; size: 2; unsigned 16-bit integer
+ $name = 'tInt';
+ $size = 3;
+ $data = self::getUInt2d($formulaData, 1);
+
+ break;
+ case 0x1F: // number
+ // offset: 1; size: 8;
+ $name = 'tNum';
+ $size = 9;
+ $data = self::extractNumber(substr($formulaData, 1));
+ $data = str_replace(',', '.', (string) $data); // in case non-English locale
+ break;
+ case 0x20: // array constant
+ case 0x40:
+ case 0x60:
+ // offset: 1; size: 7; not used
+ $name = 'tArray';
+ $size = 8;
+ $data = null;
+
+ break;
+ case 0x21: // function with fixed number of arguments
+ case 0x41:
+ case 0x61:
+ $name = 'tFunc';
+ $size = 3;
+ // offset: 1; size: 2; index to built-in sheet function
+ switch (self::getUInt2d($formulaData, 1)) {
+ case 2:
+ $function = 'ISNA';
+ $args = 1;
+
+ break;
+ case 3:
+ $function = 'ISERROR';
+ $args = 1;
+
+ break;
+ case 10:
+ $function = 'NA';
+ $args = 0;
+
+ break;
+ case 15:
+ $function = 'SIN';
+ $args = 1;
+
+ break;
+ case 16:
+ $function = 'COS';
+ $args = 1;
+
+ break;
+ case 17:
+ $function = 'TAN';
+ $args = 1;
+
+ break;
+ case 18:
+ $function = 'ATAN';
+ $args = 1;
+
+ break;
+ case 19:
+ $function = 'PI';
+ $args = 0;
+
+ break;
+ case 20:
+ $function = 'SQRT';
+ $args = 1;
+
+ break;
+ case 21:
+ $function = 'EXP';
+ $args = 1;
+
+ break;
+ case 22:
+ $function = 'LN';
+ $args = 1;
+
+ break;
+ case 23:
+ $function = 'LOG10';
+ $args = 1;
+
+ break;
+ case 24:
+ $function = 'ABS';
+ $args = 1;
+
+ break;
+ case 25:
+ $function = 'INT';
+ $args = 1;
+
+ break;
+ case 26:
+ $function = 'SIGN';
+ $args = 1;
+
+ break;
+ case 27:
+ $function = 'ROUND';
+ $args = 2;
+
+ break;
+ case 30:
+ $function = 'REPT';
+ $args = 2;
+
+ break;
+ case 31:
+ $function = 'MID';
+ $args = 3;
+
+ break;
+ case 32:
+ $function = 'LEN';
+ $args = 1;
+
+ break;
+ case 33:
+ $function = 'VALUE';
+ $args = 1;
+
+ break;
+ case 34:
+ $function = 'TRUE';
+ $args = 0;
+
+ break;
+ case 35:
+ $function = 'FALSE';
+ $args = 0;
+
+ break;
+ case 38:
+ $function = 'NOT';
+ $args = 1;
+
+ break;
+ case 39:
+ $function = 'MOD';
+ $args = 2;
+
+ break;
+ case 40:
+ $function = 'DCOUNT';
+ $args = 3;
+
+ break;
+ case 41:
+ $function = 'DSUM';
+ $args = 3;
+
+ break;
+ case 42:
+ $function = 'DAVERAGE';
+ $args = 3;
+
+ break;
+ case 43:
+ $function = 'DMIN';
+ $args = 3;
+
+ break;
+ case 44:
+ $function = 'DMAX';
+ $args = 3;
+
+ break;
+ case 45:
+ $function = 'DSTDEV';
+ $args = 3;
+
+ break;
+ case 48:
+ $function = 'TEXT';
+ $args = 2;
+
+ break;
+ case 61:
+ $function = 'MIRR';
+ $args = 3;
+
+ break;
+ case 63:
+ $function = 'RAND';
+ $args = 0;
+
+ break;
+ case 65:
+ $function = 'DATE';
+ $args = 3;
+
+ break;
+ case 66:
+ $function = 'TIME';
+ $args = 3;
+
+ break;
+ case 67:
+ $function = 'DAY';
+ $args = 1;
+
+ break;
+ case 68:
+ $function = 'MONTH';
+ $args = 1;
+
+ break;
+ case 69:
+ $function = 'YEAR';
+ $args = 1;
+
+ break;
+ case 71:
+ $function = 'HOUR';
+ $args = 1;
+
+ break;
+ case 72:
+ $function = 'MINUTE';
+ $args = 1;
+
+ break;
+ case 73:
+ $function = 'SECOND';
+ $args = 1;
+
+ break;
+ case 74:
+ $function = 'NOW';
+ $args = 0;
+
+ break;
+ case 75:
+ $function = 'AREAS';
+ $args = 1;
+
+ break;
+ case 76:
+ $function = 'ROWS';
+ $args = 1;
+
+ break;
+ case 77:
+ $function = 'COLUMNS';
+ $args = 1;
+
+ break;
+ case 83:
+ $function = 'TRANSPOSE';
+ $args = 1;
+
+ break;
+ case 86:
+ $function = 'TYPE';
+ $args = 1;
+
+ break;
+ case 97:
+ $function = 'ATAN2';
+ $args = 2;
+
+ break;
+ case 98:
+ $function = 'ASIN';
+ $args = 1;
+
+ break;
+ case 99:
+ $function = 'ACOS';
+ $args = 1;
+
+ break;
+ case 105:
+ $function = 'ISREF';
+ $args = 1;
+
+ break;
+ case 111:
+ $function = 'CHAR';
+ $args = 1;
+
+ break;
+ case 112:
+ $function = 'LOWER';
+ $args = 1;
+
+ break;
+ case 113:
+ $function = 'UPPER';
+ $args = 1;
+
+ break;
+ case 114:
+ $function = 'PROPER';
+ $args = 1;
+
+ break;
+ case 117:
+ $function = 'EXACT';
+ $args = 2;
+
+ break;
+ case 118:
+ $function = 'TRIM';
+ $args = 1;
+
+ break;
+ case 119:
+ $function = 'REPLACE';
+ $args = 4;
+
+ break;
+ case 121:
+ $function = 'CODE';
+ $args = 1;
+
+ break;
+ case 126:
+ $function = 'ISERR';
+ $args = 1;
+
+ break;
+ case 127:
+ $function = 'ISTEXT';
+ $args = 1;
+
+ break;
+ case 128:
+ $function = 'ISNUMBER';
+ $args = 1;
+
+ break;
+ case 129:
+ $function = 'ISBLANK';
+ $args = 1;
+
+ break;
+ case 130:
+ $function = 'T';
+ $args = 1;
+
+ break;
+ case 131:
+ $function = 'N';
+ $args = 1;
+
+ break;
+ case 140:
+ $function = 'DATEVALUE';
+ $args = 1;
+
+ break;
+ case 141:
+ $function = 'TIMEVALUE';
+ $args = 1;
+
+ break;
+ case 142:
+ $function = 'SLN';
+ $args = 3;
+
+ break;
+ case 143:
+ $function = 'SYD';
+ $args = 4;
+
+ break;
+ case 162:
+ $function = 'CLEAN';
+ $args = 1;
+
+ break;
+ case 163:
+ $function = 'MDETERM';
+ $args = 1;
+
+ break;
+ case 164:
+ $function = 'MINVERSE';
+ $args = 1;
+
+ break;
+ case 165:
+ $function = 'MMULT';
+ $args = 2;
+
+ break;
+ case 184:
+ $function = 'FACT';
+ $args = 1;
+
+ break;
+ case 189:
+ $function = 'DPRODUCT';
+ $args = 3;
+
+ break;
+ case 190:
+ $function = 'ISNONTEXT';
+ $args = 1;
+
+ break;
+ case 195:
+ $function = 'DSTDEVP';
+ $args = 3;
+
+ break;
+ case 196:
+ $function = 'DVARP';
+ $args = 3;
+
+ break;
+ case 198:
+ $function = 'ISLOGICAL';
+ $args = 1;
+
+ break;
+ case 199:
+ $function = 'DCOUNTA';
+ $args = 3;
+
+ break;
+ case 207:
+ $function = 'REPLACEB';
+ $args = 4;
+
+ break;
+ case 210:
+ $function = 'MIDB';
+ $args = 3;
+
+ break;
+ case 211:
+ $function = 'LENB';
+ $args = 1;
+
+ break;
+ case 212:
+ $function = 'ROUNDUP';
+ $args = 2;
+
+ break;
+ case 213:
+ $function = 'ROUNDDOWN';
+ $args = 2;
+
+ break;
+ case 214:
+ $function = 'ASC';
+ $args = 1;
+
+ break;
+ case 215:
+ $function = 'DBCS';
+ $args = 1;
+
+ break;
+ case 221:
+ $function = 'TODAY';
+ $args = 0;
+
+ break;
+ case 229:
+ $function = 'SINH';
+ $args = 1;
+
+ break;
+ case 230:
+ $function = 'COSH';
+ $args = 1;
+
+ break;
+ case 231:
+ $function = 'TANH';
+ $args = 1;
+
+ break;
+ case 232:
+ $function = 'ASINH';
+ $args = 1;
+
+ break;
+ case 233:
+ $function = 'ACOSH';
+ $args = 1;
+
+ break;
+ case 234:
+ $function = 'ATANH';
+ $args = 1;
+
+ break;
+ case 235:
+ $function = 'DGET';
+ $args = 3;
+
+ break;
+ case 244:
+ $function = 'INFO';
+ $args = 1;
+
+ break;
+ case 252:
+ $function = 'FREQUENCY';
+ $args = 2;
+
+ break;
+ case 261:
+ $function = 'ERROR.TYPE';
+ $args = 1;
+
+ break;
+ case 271:
+ $function = 'GAMMALN';
+ $args = 1;
+
+ break;
+ case 273:
+ $function = 'BINOMDIST';
+ $args = 4;
+
+ break;
+ case 274:
+ $function = 'CHIDIST';
+ $args = 2;
+
+ break;
+ case 275:
+ $function = 'CHIINV';
+ $args = 2;
+
+ break;
+ case 276:
+ $function = 'COMBIN';
+ $args = 2;
+
+ break;
+ case 277:
+ $function = 'CONFIDENCE';
+ $args = 3;
+
+ break;
+ case 278:
+ $function = 'CRITBINOM';
+ $args = 3;
+
+ break;
+ case 279:
+ $function = 'EVEN';
+ $args = 1;
+
+ break;
+ case 280:
+ $function = 'EXPONDIST';
+ $args = 3;
+
+ break;
+ case 281:
+ $function = 'FDIST';
+ $args = 3;
+
+ break;
+ case 282:
+ $function = 'FINV';
+ $args = 3;
+
+ break;
+ case 283:
+ $function = 'FISHER';
+ $args = 1;
+
+ break;
+ case 284:
+ $function = 'FISHERINV';
+ $args = 1;
+
+ break;
+ case 285:
+ $function = 'FLOOR';
+ $args = 2;
+
+ break;
+ case 286:
+ $function = 'GAMMADIST';
+ $args = 4;
+
+ break;
+ case 287:
+ $function = 'GAMMAINV';
+ $args = 3;
+
+ break;
+ case 288:
+ $function = 'CEILING';
+ $args = 2;
+
+ break;
+ case 289:
+ $function = 'HYPGEOMDIST';
+ $args = 4;
+
+ break;
+ case 290:
+ $function = 'LOGNORMDIST';
+ $args = 3;
+
+ break;
+ case 291:
+ $function = 'LOGINV';
+ $args = 3;
+
+ break;
+ case 292:
+ $function = 'NEGBINOMDIST';
+ $args = 3;
+
+ break;
+ case 293:
+ $function = 'NORMDIST';
+ $args = 4;
+
+ break;
+ case 294:
+ $function = 'NORMSDIST';
+ $args = 1;
+
+ break;
+ case 295:
+ $function = 'NORMINV';
+ $args = 3;
+
+ break;
+ case 296:
+ $function = 'NORMSINV';
+ $args = 1;
+
+ break;
+ case 297:
+ $function = 'STANDARDIZE';
+ $args = 3;
+
+ break;
+ case 298:
+ $function = 'ODD';
+ $args = 1;
+
+ break;
+ case 299:
+ $function = 'PERMUT';
+ $args = 2;
+
+ break;
+ case 300:
+ $function = 'POISSON';
+ $args = 3;
+
+ break;
+ case 301:
+ $function = 'TDIST';
+ $args = 3;
+
+ break;
+ case 302:
+ $function = 'WEIBULL';
+ $args = 4;
+
+ break;
+ case 303:
+ $function = 'SUMXMY2';
+ $args = 2;
+
+ break;
+ case 304:
+ $function = 'SUMX2MY2';
+ $args = 2;
+
+ break;
+ case 305:
+ $function = 'SUMX2PY2';
+ $args = 2;
+
+ break;
+ case 306:
+ $function = 'CHITEST';
+ $args = 2;
+
+ break;
+ case 307:
+ $function = 'CORREL';
+ $args = 2;
+
+ break;
+ case 308:
+ $function = 'COVAR';
+ $args = 2;
+
+ break;
+ case 309:
+ $function = 'FORECAST';
+ $args = 3;
+
+ break;
+ case 310:
+ $function = 'FTEST';
+ $args = 2;
+
+ break;
+ case 311:
+ $function = 'INTERCEPT';
+ $args = 2;
+
+ break;
+ case 312:
+ $function = 'PEARSON';
+ $args = 2;
+
+ break;
+ case 313:
+ $function = 'RSQ';
+ $args = 2;
+
+ break;
+ case 314:
+ $function = 'STEYX';
+ $args = 2;
+
+ break;
+ case 315:
+ $function = 'SLOPE';
+ $args = 2;
+
+ break;
+ case 316:
+ $function = 'TTEST';
+ $args = 4;
+
+ break;
+ case 325:
+ $function = 'LARGE';
+ $args = 2;
+
+ break;
+ case 326:
+ $function = 'SMALL';
+ $args = 2;
+
+ break;
+ case 327:
+ $function = 'QUARTILE';
+ $args = 2;
+
+ break;
+ case 328:
+ $function = 'PERCENTILE';
+ $args = 2;
+
+ break;
+ case 331:
+ $function = 'TRIMMEAN';
+ $args = 2;
+
+ break;
+ case 332:
+ $function = 'TINV';
+ $args = 2;
+
+ break;
+ case 337:
+ $function = 'POWER';
+ $args = 2;
+
+ break;
+ case 342:
+ $function = 'RADIANS';
+ $args = 1;
+
+ break;
+ case 343:
+ $function = 'DEGREES';
+ $args = 1;
+
+ break;
+ case 346:
+ $function = 'COUNTIF';
+ $args = 2;
+
+ break;
+ case 347:
+ $function = 'COUNTBLANK';
+ $args = 1;
+
+ break;
+ case 350:
+ $function = 'ISPMT';
+ $args = 4;
+
+ break;
+ case 351:
+ $function = 'DATEDIF';
+ $args = 3;
+
+ break;
+ case 352:
+ $function = 'DATESTRING';
+ $args = 1;
+
+ break;
+ case 353:
+ $function = 'NUMBERSTRING';
+ $args = 2;
+
+ break;
+ case 360:
+ $function = 'PHONETIC';
+ $args = 1;
+
+ break;
+ case 368:
+ $function = 'BAHTTEXT';
+ $args = 1;
+
+ break;
+ default:
+ throw new Exception('Unrecognized function in formula');
+
+ break;
+ }
+ $data = ['function' => $function, 'args' => $args];
+
+ break;
+ case 0x22: // function with variable number of arguments
+ case 0x42:
+ case 0x62:
+ $name = 'tFuncV';
+ $size = 4;
+ // offset: 1; size: 1; number of arguments
+ $args = ord($formulaData[1]);
+ // offset: 2: size: 2; index to built-in sheet function
+ $index = self::getUInt2d($formulaData, 2);
+ switch ($index) {
+ case 0:
+ $function = 'COUNT';
+
+ break;
+ case 1:
+ $function = 'IF';
+
+ break;
+ case 4:
+ $function = 'SUM';
+
+ break;
+ case 5:
+ $function = 'AVERAGE';
+
+ break;
+ case 6:
+ $function = 'MIN';
+
+ break;
+ case 7:
+ $function = 'MAX';
+
+ break;
+ case 8:
+ $function = 'ROW';
+
+ break;
+ case 9:
+ $function = 'COLUMN';
+
+ break;
+ case 11:
+ $function = 'NPV';
+
+ break;
+ case 12:
+ $function = 'STDEV';
+
+ break;
+ case 13:
+ $function = 'DOLLAR';
+
+ break;
+ case 14:
+ $function = 'FIXED';
+
+ break;
+ case 28:
+ $function = 'LOOKUP';
+
+ break;
+ case 29:
+ $function = 'INDEX';
+
+ break;
+ case 36:
+ $function = 'AND';
+
+ break;
+ case 37:
+ $function = 'OR';
+
+ break;
+ case 46:
+ $function = 'VAR';
+
+ break;
+ case 49:
+ $function = 'LINEST';
+
+ break;
+ case 50:
+ $function = 'TREND';
+
+ break;
+ case 51:
+ $function = 'LOGEST';
+
+ break;
+ case 52:
+ $function = 'GROWTH';
+
+ break;
+ case 56:
+ $function = 'PV';
+
+ break;
+ case 57:
+ $function = 'FV';
+
+ break;
+ case 58:
+ $function = 'NPER';
+
+ break;
+ case 59:
+ $function = 'PMT';
+
+ break;
+ case 60:
+ $function = 'RATE';
+
+ break;
+ case 62:
+ $function = 'IRR';
+
+ break;
+ case 64:
+ $function = 'MATCH';
+
+ break;
+ case 70:
+ $function = 'WEEKDAY';
+
+ break;
+ case 78:
+ $function = 'OFFSET';
+
+ break;
+ case 82:
+ $function = 'SEARCH';
+
+ break;
+ case 100:
+ $function = 'CHOOSE';
+
+ break;
+ case 101:
+ $function = 'HLOOKUP';
+
+ break;
+ case 102:
+ $function = 'VLOOKUP';
+
+ break;
+ case 109:
+ $function = 'LOG';
+
+ break;
+ case 115:
+ $function = 'LEFT';
+
+ break;
+ case 116:
+ $function = 'RIGHT';
+
+ break;
+ case 120:
+ $function = 'SUBSTITUTE';
+
+ break;
+ case 124:
+ $function = 'FIND';
+
+ break;
+ case 125:
+ $function = 'CELL';
+
+ break;
+ case 144:
+ $function = 'DDB';
+
+ break;
+ case 148:
+ $function = 'INDIRECT';
+
+ break;
+ case 167:
+ $function = 'IPMT';
+
+ break;
+ case 168:
+ $function = 'PPMT';
+
+ break;
+ case 169:
+ $function = 'COUNTA';
+
+ break;
+ case 183:
+ $function = 'PRODUCT';
+
+ break;
+ case 193:
+ $function = 'STDEVP';
+
+ break;
+ case 194:
+ $function = 'VARP';
+
+ break;
+ case 197:
+ $function = 'TRUNC';
+
+ break;
+ case 204:
+ $function = 'USDOLLAR';
+
+ break;
+ case 205:
+ $function = 'FINDB';
+
+ break;
+ case 206:
+ $function = 'SEARCHB';
+
+ break;
+ case 208:
+ $function = 'LEFTB';
+
+ break;
+ case 209:
+ $function = 'RIGHTB';
+
+ break;
+ case 216:
+ $function = 'RANK';
+
+ break;
+ case 219:
+ $function = 'ADDRESS';
+
+ break;
+ case 220:
+ $function = 'DAYS360';
+
+ break;
+ case 222:
+ $function = 'VDB';
+
+ break;
+ case 227:
+ $function = 'MEDIAN';
+
+ break;
+ case 228:
+ $function = 'SUMPRODUCT';
+
+ break;
+ case 247:
+ $function = 'DB';
+
+ break;
+ case 255:
+ $function = '';
+
+ break;
+ case 269:
+ $function = 'AVEDEV';
+
+ break;
+ case 270:
+ $function = 'BETADIST';
+
+ break;
+ case 272:
+ $function = 'BETAINV';
+
+ break;
+ case 317:
+ $function = 'PROB';
+
+ break;
+ case 318:
+ $function = 'DEVSQ';
+
+ break;
+ case 319:
+ $function = 'GEOMEAN';
+
+ break;
+ case 320:
+ $function = 'HARMEAN';
+
+ break;
+ case 321:
+ $function = 'SUMSQ';
+
+ break;
+ case 322:
+ $function = 'KURT';
+
+ break;
+ case 323:
+ $function = 'SKEW';
+
+ break;
+ case 324:
+ $function = 'ZTEST';
+
+ break;
+ case 329:
+ $function = 'PERCENTRANK';
+
+ break;
+ case 330:
+ $function = 'MODE';
+
+ break;
+ case 336:
+ $function = 'CONCATENATE';
+
+ break;
+ case 344:
+ $function = 'SUBTOTAL';
+
+ break;
+ case 345:
+ $function = 'SUMIF';
+
+ break;
+ case 354:
+ $function = 'ROMAN';
+
+ break;
+ case 358:
+ $function = 'GETPIVOTDATA';
+
+ break;
+ case 359:
+ $function = 'HYPERLINK';
+
+ break;
+ case 361:
+ $function = 'AVERAGEA';
+
+ break;
+ case 362:
+ $function = 'MAXA';
+
+ break;
+ case 363:
+ $function = 'MINA';
+
+ break;
+ case 364:
+ $function = 'STDEVPA';
+
+ break;
+ case 365:
+ $function = 'VARPA';
+
+ break;
+ case 366:
+ $function = 'STDEVA';
+
+ break;
+ case 367:
+ $function = 'VARA';
+
+ break;
+ default:
+ throw new Exception('Unrecognized function in formula');
+
+ break;
+ }
+ $data = ['function' => $function, 'args' => $args];
+
+ break;
+ case 0x23: // index to defined name
+ case 0x43:
+ case 0x63:
+ $name = 'tName';
+ $size = 5;
+ // offset: 1; size: 2; one-based index to definedname record
+ $definedNameIndex = self::getUInt2d($formulaData, 1) - 1;
+ // offset: 2; size: 2; not used
+ $data = $this->definedname[$definedNameIndex]['name'];
+
+ break;
+ case 0x24: // single cell reference e.g. A5
+ case 0x44:
+ case 0x64:
+ $name = 'tRef';
+ $size = 5;
+ $data = $this->readBIFF8CellAddress(substr($formulaData, 1, 4));
+
+ break;
+ case 0x25: // cell range reference to cells in the same sheet (2d)
+ case 0x45:
+ case 0x65:
+ $name = 'tArea';
+ $size = 9;
+ $data = $this->readBIFF8CellRangeAddress(substr($formulaData, 1, 8));
+
+ break;
+ case 0x26: // Constant reference sub-expression
+ case 0x46:
+ case 0x66:
+ $name = 'tMemArea';
+ // offset: 1; size: 4; not used
+ // offset: 5; size: 2; size of the following subexpression
+ $subSize = self::getUInt2d($formulaData, 5);
+ $size = 7 + $subSize;
+ $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
+
+ break;
+ case 0x27: // Deleted constant reference sub-expression
+ case 0x47:
+ case 0x67:
+ $name = 'tMemErr';
+ // offset: 1; size: 4; not used
+ // offset: 5; size: 2; size of the following subexpression
+ $subSize = self::getUInt2d($formulaData, 5);
+ $size = 7 + $subSize;
+ $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
+
+ break;
+ case 0x29: // Variable reference sub-expression
+ case 0x49:
+ case 0x69:
+ $name = 'tMemFunc';
+ // offset: 1; size: 2; size of the following sub-expression
+ $subSize = self::getUInt2d($formulaData, 1);
+ $size = 3 + $subSize;
+ $data = $this->getFormulaFromData(substr($formulaData, 3, $subSize));
+
+ break;
+ case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places
+ case 0x4C:
+ case 0x6C:
+ $name = 'tRefN';
+ $size = 5;
+ $data = $this->readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell);
+
+ break;
+ case 0x2D: // Relative 2d range reference
+ case 0x4D:
+ case 0x6D:
+ $name = 'tAreaN';
+ $size = 9;
+ $data = $this->readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell);
+
+ break;
+ case 0x39: // External name
+ case 0x59:
+ case 0x79:
+ $name = 'tNameX';
+ $size = 7;
+ // offset: 1; size: 2; index to REF entry in EXTERNSHEET record
+ // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record
+ $index = self::getUInt2d($formulaData, 3);
+ // assume index is to EXTERNNAME record
+ $data = $this->externalNames[$index - 1]['name'];
+ // offset: 5; size: 2; not used
+ break;
+ case 0x3A: // 3d reference to cell
+ case 0x5A:
+ case 0x7A:
+ $name = 'tRef3d';
+ $size = 7;
+
+ try {
+ // offset: 1; size: 2; index to REF entry
+ $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1));
+ // offset: 3; size: 4; cell address
+ $cellAddress = $this->readBIFF8CellAddress(substr($formulaData, 3, 4));
+
+ $data = "$sheetRange!$cellAddress";
+ } catch (PhpSpreadsheetException $e) {
+ // deleted sheet reference
+ $data = '#REF!';
+ }
+
+ break;
+ case 0x3B: // 3d reference to cell range
+ case 0x5B:
+ case 0x7B:
+ $name = 'tArea3d';
+ $size = 11;
+
+ try {
+ // offset: 1; size: 2; index to REF entry
+ $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1));
+ // offset: 3; size: 8; cell address
+ $cellRangeAddress = $this->readBIFF8CellRangeAddress(substr($formulaData, 3, 8));
+
+ $data = "$sheetRange!$cellRangeAddress";
+ } catch (PhpSpreadsheetException $e) {
+ // deleted sheet reference
+ $data = '#REF!';
+ }
+
+ break;
+ // Unknown cases // don't know how to deal with
+ default:
+ throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula');
+
+ break;
+ }
+
+ return [
+ 'id' => $id,
+ 'name' => $name,
+ 'size' => $size,
+ 'data' => $data,
+ ];
+ }
+
+ /**
+ * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2'
+ * section 3.3.4.
+ *
+ * @param string $cellAddressStructure
+ *
+ * @return string
+ */
+ private function readBIFF8CellAddress($cellAddressStructure)
+ {
+ // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
+ $row = self::getUInt2d($cellAddressStructure, 0) + 1;
+
+ // offset: 2; size: 2; index to column or column offset + relative flags
+ // bit: 7-0; mask 0x00FF; column index
+ $column = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($cellAddressStructure, 2)) + 1);
+
+ // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
+ if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) {
+ $column = '$' . $column;
+ }
+ // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
+ if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
+ $row = '$' . $row;
+ }
+
+ return $column . $row;
+ }
+
+ /**
+ * Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column
+ * to indicate offsets from a base cell
+ * section 3.3.4.
+ *
+ * @param string $cellAddressStructure
+ * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
+ *
+ * @return string
+ */
+ private function readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1')
+ {
+ [$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell);
+ $baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
+
+ // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
+ $rowIndex = self::getUInt2d($cellAddressStructure, 0);
+ $row = self::getUInt2d($cellAddressStructure, 0) + 1;
+
+ // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
+ if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) {
+ // offset: 2; size: 2; index to column or column offset + relative flags
+ // bit: 7-0; mask 0x00FF; column index
+ $colIndex = 0x00FF & self::getUInt2d($cellAddressStructure, 2);
+
+ $column = Coordinate::stringFromColumnIndex($colIndex + 1);
+ $column = '$' . $column;
+ } else {
+ // offset: 2; size: 2; index to column or column offset + relative flags
+ // bit: 7-0; mask 0x00FF; column index
+ $relativeColIndex = 0x00FF & self::getInt2d($cellAddressStructure, 2);
+ $colIndex = $baseCol + $relativeColIndex;
+ $colIndex = ($colIndex < 256) ? $colIndex : $colIndex - 256;
+ $colIndex = ($colIndex >= 0) ? $colIndex : $colIndex + 256;
+ $column = Coordinate::stringFromColumnIndex($colIndex + 1);
+ }
+
+ // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
+ if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
+ $row = '$' . $row;
+ } else {
+ $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536;
+ $row = $baseRow + $rowIndex;
+ }
+
+ return $column . $row;
+ }
+
+ /**
+ * Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1'
+ * always fixed range
+ * section 2.5.14.
+ *
+ * @param string $subData
+ *
+ * @return string
+ */
+ private function readBIFF5CellRangeAddressFixed($subData)
+ {
+ // offset: 0; size: 2; index to first row
+ $fr = self::getUInt2d($subData, 0) + 1;
+
+ // offset: 2; size: 2; index to last row
+ $lr = self::getUInt2d($subData, 2) + 1;
+
+ // offset: 4; size: 1; index to first column
+ $fc = ord($subData[4]);
+
+ // offset: 5; size: 1; index to last column
+ $lc = ord($subData[5]);
+
+ // check values
+ if ($fr > $lr || $fc > $lc) {
+ throw new Exception('Not a cell range address');
+ }
+
+ // column index to letter
+ $fc = Coordinate::stringFromColumnIndex($fc + 1);
+ $lc = Coordinate::stringFromColumnIndex($lc + 1);
+
+ if ($fr == $lr && $fc == $lc) {
+ return "$fc$fr";
+ }
+
+ return "$fc$fr:$lc$lr";
+ }
+
+ /**
+ * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1'
+ * always fixed range
+ * section 2.5.14.
+ *
+ * @param string $subData
+ *
+ * @return string
+ */
+ private function readBIFF8CellRangeAddressFixed($subData)
+ {
+ // offset: 0; size: 2; index to first row
+ $fr = self::getUInt2d($subData, 0) + 1;
+
+ // offset: 2; size: 2; index to last row
+ $lr = self::getUInt2d($subData, 2) + 1;
+
+ // offset: 4; size: 2; index to first column
+ $fc = self::getUInt2d($subData, 4);
+
+ // offset: 6; size: 2; index to last column
+ $lc = self::getUInt2d($subData, 6);
+
+ // check values
+ if ($fr > $lr || $fc > $lc) {
+ throw new Exception('Not a cell range address');
+ }
+
+ // column index to letter
+ $fc = Coordinate::stringFromColumnIndex($fc + 1);
+ $lc = Coordinate::stringFromColumnIndex($lc + 1);
+
+ if ($fr == $lr && $fc == $lc) {
+ return "$fc$fr";
+ }
+
+ return "$fc$fr:$lc$lr";
+ }
+
+ /**
+ * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6'
+ * there are flags indicating whether column/row index is relative
+ * section 3.3.4.
+ *
+ * @param string $subData
+ *
+ * @return string
+ */
+ private function readBIFF8CellRangeAddress($subData)
+ {
+ // todo: if cell range is just a single cell, should this funciton
+ // not just return e.g. 'A1' and not 'A1:A1' ?
+
+ // offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767))
+ $fr = self::getUInt2d($subData, 0) + 1;
+
+ // offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767))
+ $lr = self::getUInt2d($subData, 2) + 1;
+
+ // offset: 4; size: 2; index to first column or column offset + relative flags
+
+ // bit: 7-0; mask 0x00FF; column index
+ $fc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 4)) + 1);
+
+ // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
+ if (!(0x4000 & self::getUInt2d($subData, 4))) {
+ $fc = '$' . $fc;
+ }
+
+ // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
+ if (!(0x8000 & self::getUInt2d($subData, 4))) {
+ $fr = '$' . $fr;
+ }
+
+ // offset: 6; size: 2; index to last column or column offset + relative flags
+
+ // bit: 7-0; mask 0x00FF; column index
+ $lc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 6)) + 1);
+
+ // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
+ if (!(0x4000 & self::getUInt2d($subData, 6))) {
+ $lc = '$' . $lc;
+ }
+
+ // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
+ if (!(0x8000 & self::getUInt2d($subData, 6))) {
+ $lr = '$' . $lr;
+ }
+
+ return "$fc$fr:$lc$lr";
+ }
+
+ /**
+ * Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column
+ * to indicate offsets from a base cell
+ * section 3.3.4.
+ *
+ * @param string $subData
+ * @param string $baseCell Base cell
+ *
+ * @return string Cell range address
+ */
+ private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1')
+ {
+ [$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell);
+ $baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
+
+ // TODO: if cell range is just a single cell, should this funciton
+ // not just return e.g. 'A1' and not 'A1:A1' ?
+
+ // offset: 0; size: 2; first row
+ $frIndex = self::getUInt2d($subData, 0); // adjust below
+
+ // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767)
+ $lrIndex = self::getUInt2d($subData, 2); // adjust below
+
+ // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
+ if (!(0x4000 & self::getUInt2d($subData, 4))) {
+ // absolute column index
+ // offset: 4; size: 2; first column with relative/absolute flags
+ // bit: 7-0; mask 0x00FF; column index
+ $fcIndex = 0x00FF & self::getUInt2d($subData, 4);
+ $fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
+ $fc = '$' . $fc;
+ } else {
+ // column offset
+ // offset: 4; size: 2; first column with relative/absolute flags
+ // bit: 7-0; mask 0x00FF; column index
+ $relativeFcIndex = 0x00FF & self::getInt2d($subData, 4);
+ $fcIndex = $baseCol + $relativeFcIndex;
+ $fcIndex = ($fcIndex < 256) ? $fcIndex : $fcIndex - 256;
+ $fcIndex = ($fcIndex >= 0) ? $fcIndex : $fcIndex + 256;
+ $fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
+ }
+
+ // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
+ if (!(0x8000 & self::getUInt2d($subData, 4))) {
+ // absolute row index
+ $fr = $frIndex + 1;
+ $fr = '$' . $fr;
+ } else {
+ // row offset
+ $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536;
+ $fr = $baseRow + $frIndex;
+ }
+
+ // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
+ if (!(0x4000 & self::getUInt2d($subData, 6))) {
+ // absolute column index
+ // offset: 6; size: 2; last column with relative/absolute flags
+ // bit: 7-0; mask 0x00FF; column index
+ $lcIndex = 0x00FF & self::getUInt2d($subData, 6);
+ $lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
+ $lc = '$' . $lc;
+ } else {
+ // column offset
+ // offset: 4; size: 2; first column with relative/absolute flags
+ // bit: 7-0; mask 0x00FF; column index
+ $relativeLcIndex = 0x00FF & self::getInt2d($subData, 4);
+ $lcIndex = $baseCol + $relativeLcIndex;
+ $lcIndex = ($lcIndex < 256) ? $lcIndex : $lcIndex - 256;
+ $lcIndex = ($lcIndex >= 0) ? $lcIndex : $lcIndex + 256;
+ $lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
+ }
+
+ // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
+ if (!(0x8000 & self::getUInt2d($subData, 6))) {
+ // absolute row index
+ $lr = $lrIndex + 1;
+ $lr = '$' . $lr;
+ } else {
+ // row offset
+ $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536;
+ $lr = $baseRow + $lrIndex;
+ }
+
+ return "$fc$fr:$lc$lr";
+ }
+
+ /**
+ * Read BIFF8 cell range address list
+ * section 2.5.15.
+ *
+ * @param string $subData
+ *
+ * @return array
+ */
+ private function readBIFF8CellRangeAddressList($subData)
+ {
+ $cellRangeAddresses = [];
+
+ // offset: 0; size: 2; number of the following cell range addresses
+ $nm = self::getUInt2d($subData, 0);
+
+ $offset = 2;
+ // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses
+ for ($i = 0; $i < $nm; ++$i) {
+ $cellRangeAddresses[] = $this->readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8));
+ $offset += 8;
+ }
+
+ return [
+ 'size' => 2 + 8 * $nm,
+ 'cellRangeAddresses' => $cellRangeAddresses,
+ ];
+ }
+
+ /**
+ * Read BIFF5 cell range address list
+ * section 2.5.15.
+ *
+ * @param string $subData
+ *
+ * @return array
+ */
+ private function readBIFF5CellRangeAddressList($subData)
+ {
+ $cellRangeAddresses = [];
+
+ // offset: 0; size: 2; number of the following cell range addresses
+ $nm = self::getUInt2d($subData, 0);
+
+ $offset = 2;
+ // offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses
+ for ($i = 0; $i < $nm; ++$i) {
+ $cellRangeAddresses[] = $this->readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6));
+ $offset += 6;
+ }
+
+ return [
+ 'size' => 2 + 6 * $nm,
+ 'cellRangeAddresses' => $cellRangeAddresses,
+ ];
+ }
+
+ /**
+ * Get a sheet range like Sheet1:Sheet3 from REF index
+ * Note: If there is only one sheet in the range, one gets e.g Sheet1
+ * It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets,
+ * in which case an Exception is thrown.
+ *
+ * @param int $index
+ *
+ * @return false|string
+ */
+ private function readSheetRangeByRefIndex($index)
+ {
+ if (isset($this->ref[$index])) {
+ $type = $this->externalBooks[$this->ref[$index]['externalBookIndex']]['type'];
+
+ switch ($type) {
+ case 'internal':
+ // check if we have a deleted 3d reference
+ if ($this->ref[$index]['firstSheetIndex'] == 0xFFFF || $this->ref[$index]['lastSheetIndex'] == 0xFFFF) {
+ throw new Exception('Deleted sheet reference');
+ }
+
+ // we have normal sheet range (collapsed or uncollapsed)
+ $firstSheetName = $this->sheets[$this->ref[$index]['firstSheetIndex']]['name'];
+ $lastSheetName = $this->sheets[$this->ref[$index]['lastSheetIndex']]['name'];
+
+ if ($firstSheetName == $lastSheetName) {
+ // collapsed sheet range
+ $sheetRange = $firstSheetName;
+ } else {
+ $sheetRange = "$firstSheetName:$lastSheetName";
+ }
+
+ // escape the single-quotes
+ $sheetRange = str_replace("'", "''", $sheetRange);
+
+ // if there are special characters, we need to enclose the range in single-quotes
+ // todo: check if we have identified the whole set of special characters
+ // it seems that the following characters are not accepted for sheet names
+ // and we may assume that they are not present: []*/:\?
+ if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/u", $sheetRange)) {
+ $sheetRange = "'$sheetRange'";
+ }
+
+ return $sheetRange;
+
+ break;
+ default:
+ // TODO: external sheet support
+ throw new Exception('Xls reader only supports internal sheets in formulas');
+
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * read BIFF8 constant value array from array data
+ * returns e.g. ['value' => '{1,2;3,4}', 'size' => 40]
+ * section 2.5.8.
+ *
+ * @param string $arrayData
+ *
+ * @return array
+ */
+ private static function readBIFF8ConstantArray($arrayData)
+ {
+ // offset: 0; size: 1; number of columns decreased by 1
+ $nc = ord($arrayData[0]);
+
+ // offset: 1; size: 2; number of rows decreased by 1
+ $nr = self::getUInt2d($arrayData, 1);
+ $size = 3; // initialize
+ $arrayData = substr($arrayData, 3);
+
+ // offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values
+ $matrixChunks = [];
+ for ($r = 1; $r <= $nr + 1; ++$r) {
+ $items = [];
+ for ($c = 1; $c <= $nc + 1; ++$c) {
+ $constant = self::readBIFF8Constant($arrayData);
+ $items[] = $constant['value'];
+ $arrayData = substr($arrayData, $constant['size']);
+ $size += $constant['size'];
+ }
+ $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"'
+ }
+ $matrix = '{' . implode(';', $matrixChunks) . '}';
+
+ return [
+ 'value' => $matrix,
+ 'size' => $size,
+ ];
+ }
+
+ /**
+ * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value'
+ * section 2.5.7
+ * returns e.g. ['value' => '5', 'size' => 9].
+ *
+ * @param string $valueData
+ *
+ * @return array
+ */
+ private static function readBIFF8Constant($valueData)
+ {
+ // offset: 0; size: 1; identifier for type of constant
+ $identifier = ord($valueData[0]);
+
+ switch ($identifier) {
+ case 0x00: // empty constant (what is this?)
+ $value = '';
+ $size = 9;
+
+ break;
+ case 0x01: // number
+ // offset: 1; size: 8; IEEE 754 floating-point value
+ $value = self::extractNumber(substr($valueData, 1, 8));
+ $size = 9;
+
+ break;
+ case 0x02: // string value
+ // offset: 1; size: var; Unicode string, 16-bit string length
+ $string = self::readUnicodeStringLong(substr($valueData, 1));
+ $value = '"' . $string['value'] . '"';
+ $size = 1 + $string['size'];
+
+ break;
+ case 0x04: // boolean
+ // offset: 1; size: 1; 0 = FALSE, 1 = TRUE
+ if (ord($valueData[1])) {
+ $value = 'TRUE';
+ } else {
+ $value = 'FALSE';
+ }
+ $size = 9;
+
+ break;
+ case 0x10: // error code
+ // offset: 1; size: 1; error code
+ $value = Xls\ErrorCode::lookup(ord($valueData[1]));
+ $size = 9;
+
+ break;
+ }
+
+ return [
+ 'value' => $value,
+ 'size' => $size,
+ ];
+ }
+
+ /**
+ * Extract RGB color
+ * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4.
+ *
+ * @param string $rgb Encoded RGB value (4 bytes)
+ *
+ * @return array
+ */
+ private static function readRGB($rgb)
+ {
+ // offset: 0; size 1; Red component
+ $r = ord($rgb[0]);
+
+ // offset: 1; size: 1; Green component
+ $g = ord($rgb[1]);
+
+ // offset: 2; size: 1; Blue component
+ $b = ord($rgb[2]);
+
+ // HEX notation, e.g. 'FF00FC'
+ $rgb = sprintf('%02X%02X%02X', $r, $g, $b);
+
+ return ['rgb' => $rgb];
+ }
+
+ /**
+ * Read byte string (8-bit string length)
+ * OpenOffice documentation: 2.5.2.
+ *
+ * @param string $subData
+ *
+ * @return array
+ */
+ private function readByteStringShort($subData)
+ {
+ // offset: 0; size: 1; length of the string (character count)
+ $ln = ord($subData[0]);
+
+ // offset: 1: size: var; character array (8-bit characters)
+ $value = $this->decodeCodepage(substr($subData, 1, $ln));
+
+ return [
+ 'value' => $value,
+ 'size' => 1 + $ln, // size in bytes of data structure
+ ];
+ }
+
+ /**
+ * Read byte string (16-bit string length)
+ * OpenOffice documentation: 2.5.2.
+ *
+ * @param string $subData
+ *
+ * @return array
+ */
+ private function readByteStringLong($subData)
+ {
+ // offset: 0; size: 2; length of the string (character count)
+ $ln = self::getUInt2d($subData, 0);
+
+ // offset: 2: size: var; character array (8-bit characters)
+ $value = $this->decodeCodepage(substr($subData, 2));
+
+ //return $string;
+ return [
+ 'value' => $value,
+ 'size' => 2 + $ln, // size in bytes of data structure
+ ];
+ }
+
+ /**
+ * Extracts an Excel Unicode short string (8-bit string length)
+ * OpenOffice documentation: 2.5.3
+ * function will automatically find out where the Unicode string ends.
+ *
+ * @param string $subData
+ *
+ * @return array
+ */
+ private static function readUnicodeStringShort($subData)
+ {
+ $value = '';
+
+ // offset: 0: size: 1; length of the string (character count)
+ $characterCount = ord($subData[0]);
+
+ $string = self::readUnicodeString(substr($subData, 1), $characterCount);
+
+ // add 1 for the string length
+ ++$string['size'];
+
+ return $string;
+ }
+
+ /**
+ * Extracts an Excel Unicode long string (16-bit string length)
+ * OpenOffice documentation: 2.5.3
+ * this function is under construction, needs to support rich text, and Asian phonetic settings.
+ *
+ * @param string $subData
+ *
+ * @return array
+ */
+ private static function readUnicodeStringLong($subData)
+ {
+ $value = '';
+
+ // offset: 0: size: 2; length of the string (character count)
+ $characterCount = self::getUInt2d($subData, 0);
+
+ $string = self::readUnicodeString(substr($subData, 2), $characterCount);
+
+ // add 2 for the string length
+ $string['size'] += 2;
+
+ return $string;
+ }
+
+ /**
+ * Read Unicode string with no string length field, but with known character count
+ * this function is under construction, needs to support rich text, and Asian phonetic settings
+ * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3.
+ *
+ * @param string $subData
+ * @param int $characterCount
+ *
+ * @return array
+ */
+ private static function readUnicodeString($subData, $characterCount)
+ {
+ $value = '';
+
+ // offset: 0: size: 1; option flags
+ // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit)
+ $isCompressed = !((0x01 & ord($subData[0])) >> 0);
+
+ // bit: 2; mask: 0x04; Asian phonetic settings
+ $hasAsian = (0x04) & ord($subData[0]) >> 2;
+
+ // bit: 3; mask: 0x08; Rich-Text settings
+ $hasRichText = (0x08) & ord($subData[0]) >> 3;
+
+ // offset: 1: size: var; character array
+ // this offset assumes richtext and Asian phonetic settings are off which is generally wrong
+ // needs to be fixed
+ $value = self::encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed);
+
+ return [
+ 'value' => $value,
+ 'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags
+ ];
+ }
+
+ /**
+ * Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas.
+ * Example: hello"world --> "hello""world".
+ *
+ * @param string $value UTF-8 encoded string
+ *
+ * @return string
+ */
+ private static function UTF8toExcelDoubleQuoted($value)
+ {
+ return '"' . str_replace('"', '""', $value) . '"';
+ }
+
+ /**
+ * Reads first 8 bytes of a string and return IEEE 754 float.
+ *
+ * @param string $data Binary string that is at least 8 bytes long
+ *
+ * @return float
+ */
+ private static function extractNumber($data)
+ {
+ $rknumhigh = self::getInt4d($data, 4);
+ $rknumlow = self::getInt4d($data, 0);
+ $sign = ($rknumhigh & 0x80000000) >> 31;
+ $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023;
+ $mantissa = (0x100000 | ($rknumhigh & 0x000fffff));
+ $mantissalow1 = ($rknumlow & 0x80000000) >> 31;
+ $mantissalow2 = ($rknumlow & 0x7fffffff);
+ $value = $mantissa / 2 ** (20 - $exp);
+
+ if ($mantissalow1 != 0) {
+ $value += 1 / 2 ** (21 - $exp);
+ }
+
+ $value += $mantissalow2 / 2 ** (52 - $exp);
+ if ($sign) {
+ $value *= -1;
+ }
+
+ return $value;
+ }
+
+ /**
+ * @param int $rknum
+ *
+ * @return float
+ */
+ private static function getIEEE754($rknum)
+ {
+ if (($rknum & 0x02) != 0) {
+ $value = $rknum >> 2;
+ } else {
+ // changes by mmp, info on IEEE754 encoding from
+ // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html
+ // The RK format calls for using only the most significant 30 bits
+ // of the 64 bit floating point value. The other 34 bits are assumed
+ // to be 0 so we use the upper 30 bits of $rknum as follows...
+ $sign = ($rknum & 0x80000000) >> 31;
+ $exp = ($rknum & 0x7ff00000) >> 20;
+ $mantissa = (0x100000 | ($rknum & 0x000ffffc));
+ $value = $mantissa / 2 ** (20 - ($exp - 1023));
+ if ($sign) {
+ $value = -1 * $value;
+ }
+ //end of changes by mmp
+ }
+ if (($rknum & 0x01) != 0) {
+ $value /= 100;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Get UTF-8 string from (compressed or uncompressed) UTF-16 string.
+ *
+ * @param string $string
+ * @param bool $compressed
+ *
+ * @return string
+ */
+ private static function encodeUTF16($string, $compressed = false)
+ {
+ if ($compressed) {
+ $string = self::uncompressByteString($string);
+ }
+
+ return StringHelper::convertEncoding($string, 'UTF-8', 'UTF-16LE');
+ }
+
+ /**
+ * Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8.
+ *
+ * @param string $string
+ *
+ * @return string
+ */
+ private static function uncompressByteString($string)
+ {
+ $uncompressedString = '';
+ $strLen = strlen($string);
+ for ($i = 0; $i < $strLen; ++$i) {
+ $uncompressedString .= $string[$i] . "\0";
+ }
+
+ return $uncompressedString;
+ }
+
+ /**
+ * Convert string to UTF-8. Only used for BIFF5.
+ *
+ * @param string $string
+ *
+ * @return string
+ */
+ private function decodeCodepage($string)
+ {
+ return StringHelper::convertEncoding($string, 'UTF-8', $this->codepage);
+ }
+
+ /**
+ * Read 16-bit unsigned integer.
+ *
+ * @param string $data
+ * @param int $pos
+ *
+ * @return int
+ */
+ public static function getUInt2d($data, $pos)
+ {
+ return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
+ }
+
+ /**
+ * Read 16-bit signed integer.
+ *
+ * @param string $data
+ * @param int $pos
+ *
+ * @return int
+ */
+ public static function getInt2d($data, $pos)
+ {
+ return unpack('s', $data[$pos] . $data[$pos + 1])[1];
+ }
+
+ /**
+ * Read 32-bit signed integer.
+ *
+ * @param string $data
+ * @param int $pos
+ *
+ * @return int
+ */
+ public static function getInt4d($data, $pos)
+ {
+ // FIX: represent numbers correctly on 64-bit system
+ // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
+ // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
+ $_or_24 = ord($data[$pos + 3]);
+ if ($_or_24 >= 128) {
+ // negative number
+ $_ord_24 = -abs((256 - $_or_24) << 24);
+ } else {
+ $_ord_24 = ($_or_24 & 127) << 24;
+ }
+
+ return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
+ }
+
+ private function parseRichText($is)
+ {
+ $value = new RichText();
+ $value->createText($is);
+
+ return $value;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color.php
new file mode 100755
index 0000000..c45f88c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
+
+use PhpOffice\PhpSpreadsheet\Reader\Xls;
+
+class Color
+{
+ /**
+ * Read color.
+ *
+ * @param int $color Indexed color
+ * @param array $palette Color palette
+ * @param int $version
+ *
+ * @return array RGB color value, example: ['rgb' => 'FF0000']
+ */
+ public static function map($color, $palette, $version)
+ {
+ if ($color <= 0x07 || $color >= 0x40) {
+ // special built-in color
+ return Color\BuiltIn::lookup($color);
+ } elseif (isset($palette, $palette[$color - 8])) {
+ // palette color, color index 0x08 maps to pallete index 0
+ return $palette[$color - 8];
+ }
+
+ // default color table
+ if ($version == Xls::XLS_BIFF8) {
+ return Color\BIFF8::lookup($color);
+ }
+
+ // BIFF5
+ return Color\BIFF5::lookup($color);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php
new file mode 100755
index 0000000..743d938
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color;
+
+class BIFF5
+{
+ protected static $map = [
+ 0x08 => '000000',
+ 0x09 => 'FFFFFF',
+ 0x0A => 'FF0000',
+ 0x0B => '00FF00',
+ 0x0C => '0000FF',
+ 0x0D => 'FFFF00',
+ 0x0E => 'FF00FF',
+ 0x0F => '00FFFF',
+ 0x10 => '800000',
+ 0x11 => '008000',
+ 0x12 => '000080',
+ 0x13 => '808000',
+ 0x14 => '800080',
+ 0x15 => '008080',
+ 0x16 => 'C0C0C0',
+ 0x17 => '808080',
+ 0x18 => '8080FF',
+ 0x19 => '802060',
+ 0x1A => 'FFFFC0',
+ 0x1B => 'A0E0F0',
+ 0x1C => '600080',
+ 0x1D => 'FF8080',
+ 0x1E => '0080C0',
+ 0x1F => 'C0C0FF',
+ 0x20 => '000080',
+ 0x21 => 'FF00FF',
+ 0x22 => 'FFFF00',
+ 0x23 => '00FFFF',
+ 0x24 => '800080',
+ 0x25 => '800000',
+ 0x26 => '008080',
+ 0x27 => '0000FF',
+ 0x28 => '00CFFF',
+ 0x29 => '69FFFF',
+ 0x2A => 'E0FFE0',
+ 0x2B => 'FFFF80',
+ 0x2C => 'A6CAF0',
+ 0x2D => 'DD9CB3',
+ 0x2E => 'B38FEE',
+ 0x2F => 'E3E3E3',
+ 0x30 => '2A6FF9',
+ 0x31 => '3FB8CD',
+ 0x32 => '488436',
+ 0x33 => '958C41',
+ 0x34 => '8E5E42',
+ 0x35 => 'A0627A',
+ 0x36 => '624FAC',
+ 0x37 => '969696',
+ 0x38 => '1D2FBE',
+ 0x39 => '286676',
+ 0x3A => '004500',
+ 0x3B => '453E01',
+ 0x3C => '6A2813',
+ 0x3D => '85396A',
+ 0x3E => '4A3285',
+ 0x3F => '424242',
+ ];
+
+ /**
+ * Map color array from BIFF5 built-in color index.
+ *
+ * @param int $color
+ *
+ * @return array
+ */
+ public static function lookup($color)
+ {
+ if (isset(self::$map[$color])) {
+ return ['rgb' => self::$map[$color]];
+ }
+
+ return ['rgb' => '000000'];
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php
new file mode 100755
index 0000000..5c109fb
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color;
+
+class BIFF8
+{
+ protected static $map = [
+ 0x08 => '000000',
+ 0x09 => 'FFFFFF',
+ 0x0A => 'FF0000',
+ 0x0B => '00FF00',
+ 0x0C => '0000FF',
+ 0x0D => 'FFFF00',
+ 0x0E => 'FF00FF',
+ 0x0F => '00FFFF',
+ 0x10 => '800000',
+ 0x11 => '008000',
+ 0x12 => '000080',
+ 0x13 => '808000',
+ 0x14 => '800080',
+ 0x15 => '008080',
+ 0x16 => 'C0C0C0',
+ 0x17 => '808080',
+ 0x18 => '9999FF',
+ 0x19 => '993366',
+ 0x1A => 'FFFFCC',
+ 0x1B => 'CCFFFF',
+ 0x1C => '660066',
+ 0x1D => 'FF8080',
+ 0x1E => '0066CC',
+ 0x1F => 'CCCCFF',
+ 0x20 => '000080',
+ 0x21 => 'FF00FF',
+ 0x22 => 'FFFF00',
+ 0x23 => '00FFFF',
+ 0x24 => '800080',
+ 0x25 => '800000',
+ 0x26 => '008080',
+ 0x27 => '0000FF',
+ 0x28 => '00CCFF',
+ 0x29 => 'CCFFFF',
+ 0x2A => 'CCFFCC',
+ 0x2B => 'FFFF99',
+ 0x2C => '99CCFF',
+ 0x2D => 'FF99CC',
+ 0x2E => 'CC99FF',
+ 0x2F => 'FFCC99',
+ 0x30 => '3366FF',
+ 0x31 => '33CCCC',
+ 0x32 => '99CC00',
+ 0x33 => 'FFCC00',
+ 0x34 => 'FF9900',
+ 0x35 => 'FF6600',
+ 0x36 => '666699',
+ 0x37 => '969696',
+ 0x38 => '003366',
+ 0x39 => '339966',
+ 0x3A => '003300',
+ 0x3B => '333300',
+ 0x3C => '993300',
+ 0x3D => '993366',
+ 0x3E => '333399',
+ 0x3F => '333333',
+ ];
+
+ /**
+ * Map color array from BIFF8 built-in color index.
+ *
+ * @param int $color
+ *
+ * @return array
+ */
+ public static function lookup($color)
+ {
+ if (isset(self::$map[$color])) {
+ return ['rgb' => self::$map[$color]];
+ }
+
+ return ['rgb' => '000000'];
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php
new file mode 100755
index 0000000..90d50e3
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color;
+
+class BuiltIn
+{
+ protected static $map = [
+ 0x00 => '000000',
+ 0x01 => 'FFFFFF',
+ 0x02 => 'FF0000',
+ 0x03 => '00FF00',
+ 0x04 => '0000FF',
+ 0x05 => 'FFFF00',
+ 0x06 => 'FF00FF',
+ 0x07 => '00FFFF',
+ 0x40 => '000000', // system window text color
+ 0x41 => 'FFFFFF', // system window background color
+ ];
+
+ /**
+ * Map built-in color to RGB value.
+ *
+ * @param int $color Indexed color
+ *
+ * @return array
+ */
+ public static function lookup($color)
+ {
+ if (isset(self::$map[$color])) {
+ return ['rgb' => self::$map[$color]];
+ }
+
+ return ['rgb' => '000000'];
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php
new file mode 100755
index 0000000..7daf723
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
+
+class ErrorCode
+{
+ protected static $map = [
+ 0x00 => '#NULL!',
+ 0x07 => '#DIV/0!',
+ 0x0F => '#VALUE!',
+ 0x17 => '#REF!',
+ 0x1D => '#NAME?',
+ 0x24 => '#NUM!',
+ 0x2A => '#N/A',
+ ];
+
+ /**
+ * Map error code, e.g. '#N/A'.
+ *
+ * @param int $code
+ *
+ * @return bool|string
+ */
+ public static function lookup($code)
+ {
+ if (isset(self::$map[$code])) {
+ return self::$map[$code];
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php
new file mode 100755
index 0000000..306fc8f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php
@@ -0,0 +1,677 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Reader\Xls;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip;
+
+class Escher
+{
+ const DGGCONTAINER = 0xF000;
+ const BSTORECONTAINER = 0xF001;
+ const DGCONTAINER = 0xF002;
+ const SPGRCONTAINER = 0xF003;
+ const SPCONTAINER = 0xF004;
+ const DGG = 0xF006;
+ const BSE = 0xF007;
+ const DG = 0xF008;
+ const SPGR = 0xF009;
+ const SP = 0xF00A;
+ const OPT = 0xF00B;
+ const CLIENTTEXTBOX = 0xF00D;
+ const CLIENTANCHOR = 0xF010;
+ const CLIENTDATA = 0xF011;
+ const BLIPJPEG = 0xF01D;
+ const BLIPPNG = 0xF01E;
+ const SPLITMENUCOLORS = 0xF11E;
+ const TERTIARYOPT = 0xF122;
+
+ /**
+ * Escher stream data (binary).
+ *
+ * @var string
+ */
+ private $data;
+
+ /**
+ * Size in bytes of the Escher stream data.
+ *
+ * @var int
+ */
+ private $dataSize;
+
+ /**
+ * Current position of stream pointer in Escher stream data.
+ *
+ * @var int
+ */
+ private $pos;
+
+ /**
+ * The object to be returned by the reader. Modified during load.
+ *
+ * @var BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer
+ */
+ private $object;
+
+ /**
+ * Create a new Escher instance.
+ *
+ * @param mixed $object
+ */
+ public function __construct($object)
+ {
+ $this->object = $object;
+ }
+
+ /**
+ * Load Escher stream data. May be a partial Escher stream.
+ *
+ * @param string $data
+ *
+ * @return BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer
+ */
+ public function load($data)
+ {
+ $this->data = $data;
+
+ // total byte size of Excel data (workbook global substream + sheet substreams)
+ $this->dataSize = strlen($this->data);
+
+ $this->pos = 0;
+
+ // Parse Escher stream
+ while ($this->pos < $this->dataSize) {
+ // offset: 2; size: 2: Record Type
+ $fbt = Xls::getUInt2d($this->data, $this->pos + 2);
+
+ switch ($fbt) {
+ case self::DGGCONTAINER:
+ $this->readDggContainer();
+
+ break;
+ case self::DGG:
+ $this->readDgg();
+
+ break;
+ case self::BSTORECONTAINER:
+ $this->readBstoreContainer();
+
+ break;
+ case self::BSE:
+ $this->readBSE();
+
+ break;
+ case self::BLIPJPEG:
+ $this->readBlipJPEG();
+
+ break;
+ case self::BLIPPNG:
+ $this->readBlipPNG();
+
+ break;
+ case self::OPT:
+ $this->readOPT();
+
+ break;
+ case self::TERTIARYOPT:
+ $this->readTertiaryOPT();
+
+ break;
+ case self::SPLITMENUCOLORS:
+ $this->readSplitMenuColors();
+
+ break;
+ case self::DGCONTAINER:
+ $this->readDgContainer();
+
+ break;
+ case self::DG:
+ $this->readDg();
+
+ break;
+ case self::SPGRCONTAINER:
+ $this->readSpgrContainer();
+
+ break;
+ case self::SPCONTAINER:
+ $this->readSpContainer();
+
+ break;
+ case self::SPGR:
+ $this->readSpgr();
+
+ break;
+ case self::SP:
+ $this->readSp();
+
+ break;
+ case self::CLIENTTEXTBOX:
+ $this->readClientTextbox();
+
+ break;
+ case self::CLIENTANCHOR:
+ $this->readClientAnchor();
+
+ break;
+ case self::CLIENTDATA:
+ $this->readClientData();
+
+ break;
+ default:
+ $this->readDefault();
+
+ break;
+ }
+ }
+
+ return $this->object;
+ }
+
+ /**
+ * Read a generic record.
+ */
+ private function readDefault(): void
+ {
+ // offset 0; size: 2; recVer and recInstance
+ $verInstance = Xls::getUInt2d($this->data, $this->pos);
+
+ // offset: 2; size: 2: Record Type
+ $fbt = Xls::getUInt2d($this->data, $this->pos + 2);
+
+ // bit: 0-3; mask: 0x000F; recVer
+ $recVer = (0x000F & $verInstance) >> 0;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read DggContainer record (Drawing Group Container).
+ */
+ private function readDggContainer(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // record is a container, read contents
+ $dggContainer = new DggContainer();
+ $this->object->setDggContainer($dggContainer);
+ $reader = new self($dggContainer);
+ $reader->load($recordData);
+ }
+
+ /**
+ * Read Dgg record (Drawing Group).
+ */
+ private function readDgg(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read BstoreContainer record (Blip Store Container).
+ */
+ private function readBstoreContainer(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // record is a container, read contents
+ $bstoreContainer = new BstoreContainer();
+ $this->object->setBstoreContainer($bstoreContainer);
+ $reader = new self($bstoreContainer);
+ $reader->load($recordData);
+ }
+
+ /**
+ * Read BSE record.
+ */
+ private function readBSE(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // add BSE to BstoreContainer
+ $BSE = new BSE();
+ $this->object->addBSE($BSE);
+
+ $BSE->setBLIPType($recInstance);
+
+ // offset: 0; size: 1; btWin32 (MSOBLIPTYPE)
+ $btWin32 = ord($recordData[0]);
+
+ // offset: 1; size: 1; btWin32 (MSOBLIPTYPE)
+ $btMacOS = ord($recordData[1]);
+
+ // offset: 2; size: 16; MD4 digest
+ $rgbUid = substr($recordData, 2, 16);
+
+ // offset: 18; size: 2; tag
+ $tag = Xls::getUInt2d($recordData, 18);
+
+ // offset: 20; size: 4; size of BLIP in bytes
+ $size = Xls::getInt4d($recordData, 20);
+
+ // offset: 24; size: 4; number of references to this BLIP
+ $cRef = Xls::getInt4d($recordData, 24);
+
+ // offset: 28; size: 4; MSOFO file offset
+ $foDelay = Xls::getInt4d($recordData, 28);
+
+ // offset: 32; size: 1; unused1
+ $unused1 = ord($recordData[32]);
+
+ // offset: 33; size: 1; size of nameData in bytes (including null terminator)
+ $cbName = ord($recordData[33]);
+
+ // offset: 34; size: 1; unused2
+ $unused2 = ord($recordData[34]);
+
+ // offset: 35; size: 1; unused3
+ $unused3 = ord($recordData[35]);
+
+ // offset: 36; size: $cbName; nameData
+ $nameData = substr($recordData, 36, $cbName);
+
+ // offset: 36 + $cbName, size: var; the BLIP data
+ $blipData = substr($recordData, 36 + $cbName);
+
+ // record is a container, read contents
+ $reader = new self($BSE);
+ $reader->load($blipData);
+ }
+
+ /**
+ * Read BlipJPEG record. Holds raw JPEG image data.
+ */
+ private function readBlipJPEG(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ $pos = 0;
+
+ // offset: 0; size: 16; rgbUid1 (MD4 digest of)
+ $rgbUid1 = substr($recordData, 0, 16);
+ $pos += 16;
+
+ // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3
+ if (in_array($recInstance, [0x046B, 0x06E3])) {
+ $rgbUid2 = substr($recordData, 16, 16);
+ $pos += 16;
+ }
+
+ // offset: var; size: 1; tag
+ $tag = ord($recordData[$pos]);
+ ++$pos;
+
+ // offset: var; size: var; the raw image data
+ $data = substr($recordData, $pos);
+
+ $blip = new Blip();
+ $blip->setData($data);
+
+ $this->object->setBlip($blip);
+ }
+
+ /**
+ * Read BlipPNG record. Holds raw PNG image data.
+ */
+ private function readBlipPNG(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ $pos = 0;
+
+ // offset: 0; size: 16; rgbUid1 (MD4 digest of)
+ $rgbUid1 = substr($recordData, 0, 16);
+ $pos += 16;
+
+ // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3
+ if ($recInstance == 0x06E1) {
+ $rgbUid2 = substr($recordData, 16, 16);
+ $pos += 16;
+ }
+
+ // offset: var; size: 1; tag
+ $tag = ord($recordData[$pos]);
+ ++$pos;
+
+ // offset: var; size: var; the raw image data
+ $data = substr($recordData, $pos);
+
+ $blip = new Blip();
+ $blip->setData($data);
+
+ $this->object->setBlip($blip);
+ }
+
+ /**
+ * Read OPT record. This record may occur within DggContainer record or SpContainer.
+ */
+ private function readOPT(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ $this->readOfficeArtRGFOPTE($recordData, $recInstance);
+ }
+
+ /**
+ * Read TertiaryOPT record.
+ */
+ private function readTertiaryOPT(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read SplitMenuColors record.
+ */
+ private function readSplitMenuColors(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read DgContainer record (Drawing Container).
+ */
+ private function readDgContainer(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // record is a container, read contents
+ $dgContainer = new DgContainer();
+ $this->object->setDgContainer($dgContainer);
+ $reader = new self($dgContainer);
+ $escher = $reader->load($recordData);
+ }
+
+ /**
+ * Read Dg record (Drawing).
+ */
+ private function readDg(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read SpgrContainer record (Shape Group Container).
+ */
+ private function readSpgrContainer(): void
+ {
+ // context is either context DgContainer or SpgrContainer
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // record is a container, read contents
+ $spgrContainer = new SpgrContainer();
+
+ if ($this->object instanceof DgContainer) {
+ // DgContainer
+ $this->object->setSpgrContainer($spgrContainer);
+ } else {
+ // SpgrContainer
+ $this->object->addChild($spgrContainer);
+ }
+
+ $reader = new self($spgrContainer);
+ $escher = $reader->load($recordData);
+ }
+
+ /**
+ * Read SpContainer record (Shape Container).
+ */
+ private function readSpContainer(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // add spContainer to spgrContainer
+ $spContainer = new SpContainer();
+ $this->object->addChild($spContainer);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // record is a container, read contents
+ $reader = new self($spContainer);
+ $escher = $reader->load($recordData);
+ }
+
+ /**
+ * Read Spgr record (Shape Group).
+ */
+ private function readSpgr(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read Sp record (Shape).
+ */
+ private function readSp(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read ClientTextbox record.
+ */
+ private function readClientTextbox(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read ClientAnchor record. This record holds information about where the shape is anchored in worksheet.
+ */
+ private function readClientAnchor(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // offset: 2; size: 2; upper-left corner column index (0-based)
+ $c1 = Xls::getUInt2d($recordData, 2);
+
+ // offset: 4; size: 2; upper-left corner horizontal offset in 1/1024 of column width
+ $startOffsetX = Xls::getUInt2d($recordData, 4);
+
+ // offset: 6; size: 2; upper-left corner row index (0-based)
+ $r1 = Xls::getUInt2d($recordData, 6);
+
+ // offset: 8; size: 2; upper-left corner vertical offset in 1/256 of row height
+ $startOffsetY = Xls::getUInt2d($recordData, 8);
+
+ // offset: 10; size: 2; bottom-right corner column index (0-based)
+ $c2 = Xls::getUInt2d($recordData, 10);
+
+ // offset: 12; size: 2; bottom-right corner horizontal offset in 1/1024 of column width
+ $endOffsetX = Xls::getUInt2d($recordData, 12);
+
+ // offset: 14; size: 2; bottom-right corner row index (0-based)
+ $r2 = Xls::getUInt2d($recordData, 14);
+
+ // offset: 16; size: 2; bottom-right corner vertical offset in 1/256 of row height
+ $endOffsetY = Xls::getUInt2d($recordData, 16);
+
+ // set the start coordinates
+ $this->object->setStartCoordinates(Coordinate::stringFromColumnIndex($c1 + 1) . ($r1 + 1));
+
+ // set the start offsetX
+ $this->object->setStartOffsetX($startOffsetX);
+
+ // set the start offsetY
+ $this->object->setStartOffsetY($startOffsetY);
+
+ // set the end coordinates
+ $this->object->setEndCoordinates(Coordinate::stringFromColumnIndex($c2 + 1) . ($r2 + 1));
+
+ // set the end offsetX
+ $this->object->setEndOffsetX($endOffsetX);
+
+ // set the end offsetY
+ $this->object->setEndOffsetY($endOffsetY);
+ }
+
+ /**
+ * Read ClientData record.
+ */
+ private function readClientData(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read OfficeArtRGFOPTE table of property-value pairs.
+ *
+ * @param string $data Binary data
+ * @param int $n Number of properties
+ */
+ private function readOfficeArtRGFOPTE($data, $n): void
+ {
+ $splicedComplexData = substr($data, 6 * $n);
+
+ // loop through property-value pairs
+ for ($i = 0; $i < $n; ++$i) {
+ // read 6 bytes at a time
+ $fopte = substr($data, 6 * $i, 6);
+
+ // offset: 0; size: 2; opid
+ $opid = Xls::getUInt2d($fopte, 0);
+
+ // bit: 0-13; mask: 0x3FFF; opid.opid
+ $opidOpid = (0x3FFF & $opid) >> 0;
+
+ // bit: 14; mask 0x4000; 1 = value in op field is BLIP identifier
+ $opidFBid = (0x4000 & $opid) >> 14;
+
+ // bit: 15; mask 0x8000; 1 = this is a complex property, op field specifies size of complex data
+ $opidFComplex = (0x8000 & $opid) >> 15;
+
+ // offset: 2; size: 4; the value for this property
+ $op = Xls::getInt4d($fopte, 2);
+
+ if ($opidFComplex) {
+ $complexData = substr($splicedComplexData, 0, $op);
+ $splicedComplexData = substr($splicedComplexData, $op);
+
+ // we store string value with complex data
+ $value = $complexData;
+ } else {
+ // we store integer value
+ $value = $op;
+ }
+
+ $this->object->setOPT($opidOpid, $value);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php
new file mode 100755
index 0000000..c0417ba
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php
@@ -0,0 +1,184 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
+
+class MD5
+{
+ // Context
+ private $a;
+
+ private $b;
+
+ private $c;
+
+ private $d;
+
+ /**
+ * MD5 stream constructor.
+ */
+ public function __construct()
+ {
+ $this->reset();
+ }
+
+ /**
+ * Reset the MD5 stream context.
+ */
+ public function reset(): void
+ {
+ $this->a = 0x67452301;
+ $this->b = 0xEFCDAB89;
+ $this->c = 0x98BADCFE;
+ $this->d = 0x10325476;
+ }
+
+ /**
+ * Get MD5 stream context.
+ *
+ * @return string
+ */
+ public function getContext()
+ {
+ $s = '';
+ foreach (['a', 'b', 'c', 'd'] as $i) {
+ $v = $this->{$i};
+ $s .= chr($v & 0xff);
+ $s .= chr(($v >> 8) & 0xff);
+ $s .= chr(($v >> 16) & 0xff);
+ $s .= chr(($v >> 24) & 0xff);
+ }
+
+ return $s;
+ }
+
+ /**
+ * Add data to context.
+ *
+ * @param string $data Data to add
+ */
+ public function add($data): void
+ {
+ $words = array_values(unpack('V16', $data));
+
+ $A = $this->a;
+ $B = $this->b;
+ $C = $this->c;
+ $D = $this->d;
+
+ $F = ['self', 'f'];
+ $G = ['self', 'g'];
+ $H = ['self', 'h'];
+ $I = ['self', 'i'];
+
+ // ROUND 1
+ self::step($F, $A, $B, $C, $D, $words[0], 7, 0xd76aa478);
+ self::step($F, $D, $A, $B, $C, $words[1], 12, 0xe8c7b756);
+ self::step($F, $C, $D, $A, $B, $words[2], 17, 0x242070db);
+ self::step($F, $B, $C, $D, $A, $words[3], 22, 0xc1bdceee);
+ self::step($F, $A, $B, $C, $D, $words[4], 7, 0xf57c0faf);
+ self::step($F, $D, $A, $B, $C, $words[5], 12, 0x4787c62a);
+ self::step($F, $C, $D, $A, $B, $words[6], 17, 0xa8304613);
+ self::step($F, $B, $C, $D, $A, $words[7], 22, 0xfd469501);
+ self::step($F, $A, $B, $C, $D, $words[8], 7, 0x698098d8);
+ self::step($F, $D, $A, $B, $C, $words[9], 12, 0x8b44f7af);
+ self::step($F, $C, $D, $A, $B, $words[10], 17, 0xffff5bb1);
+ self::step($F, $B, $C, $D, $A, $words[11], 22, 0x895cd7be);
+ self::step($F, $A, $B, $C, $D, $words[12], 7, 0x6b901122);
+ self::step($F, $D, $A, $B, $C, $words[13], 12, 0xfd987193);
+ self::step($F, $C, $D, $A, $B, $words[14], 17, 0xa679438e);
+ self::step($F, $B, $C, $D, $A, $words[15], 22, 0x49b40821);
+
+ // ROUND 2
+ self::step($G, $A, $B, $C, $D, $words[1], 5, 0xf61e2562);
+ self::step($G, $D, $A, $B, $C, $words[6], 9, 0xc040b340);
+ self::step($G, $C, $D, $A, $B, $words[11], 14, 0x265e5a51);
+ self::step($G, $B, $C, $D, $A, $words[0], 20, 0xe9b6c7aa);
+ self::step($G, $A, $B, $C, $D, $words[5], 5, 0xd62f105d);
+ self::step($G, $D, $A, $B, $C, $words[10], 9, 0x02441453);
+ self::step($G, $C, $D, $A, $B, $words[15], 14, 0xd8a1e681);
+ self::step($G, $B, $C, $D, $A, $words[4], 20, 0xe7d3fbc8);
+ self::step($G, $A, $B, $C, $D, $words[9], 5, 0x21e1cde6);
+ self::step($G, $D, $A, $B, $C, $words[14], 9, 0xc33707d6);
+ self::step($G, $C, $D, $A, $B, $words[3], 14, 0xf4d50d87);
+ self::step($G, $B, $C, $D, $A, $words[8], 20, 0x455a14ed);
+ self::step($G, $A, $B, $C, $D, $words[13], 5, 0xa9e3e905);
+ self::step($G, $D, $A, $B, $C, $words[2], 9, 0xfcefa3f8);
+ self::step($G, $C, $D, $A, $B, $words[7], 14, 0x676f02d9);
+ self::step($G, $B, $C, $D, $A, $words[12], 20, 0x8d2a4c8a);
+
+ // ROUND 3
+ self::step($H, $A, $B, $C, $D, $words[5], 4, 0xfffa3942);
+ self::step($H, $D, $A, $B, $C, $words[8], 11, 0x8771f681);
+ self::step($H, $C, $D, $A, $B, $words[11], 16, 0x6d9d6122);
+ self::step($H, $B, $C, $D, $A, $words[14], 23, 0xfde5380c);
+ self::step($H, $A, $B, $C, $D, $words[1], 4, 0xa4beea44);
+ self::step($H, $D, $A, $B, $C, $words[4], 11, 0x4bdecfa9);
+ self::step($H, $C, $D, $A, $B, $words[7], 16, 0xf6bb4b60);
+ self::step($H, $B, $C, $D, $A, $words[10], 23, 0xbebfbc70);
+ self::step($H, $A, $B, $C, $D, $words[13], 4, 0x289b7ec6);
+ self::step($H, $D, $A, $B, $C, $words[0], 11, 0xeaa127fa);
+ self::step($H, $C, $D, $A, $B, $words[3], 16, 0xd4ef3085);
+ self::step($H, $B, $C, $D, $A, $words[6], 23, 0x04881d05);
+ self::step($H, $A, $B, $C, $D, $words[9], 4, 0xd9d4d039);
+ self::step($H, $D, $A, $B, $C, $words[12], 11, 0xe6db99e5);
+ self::step($H, $C, $D, $A, $B, $words[15], 16, 0x1fa27cf8);
+ self::step($H, $B, $C, $D, $A, $words[2], 23, 0xc4ac5665);
+
+ // ROUND 4
+ self::step($I, $A, $B, $C, $D, $words[0], 6, 0xf4292244);
+ self::step($I, $D, $A, $B, $C, $words[7], 10, 0x432aff97);
+ self::step($I, $C, $D, $A, $B, $words[14], 15, 0xab9423a7);
+ self::step($I, $B, $C, $D, $A, $words[5], 21, 0xfc93a039);
+ self::step($I, $A, $B, $C, $D, $words[12], 6, 0x655b59c3);
+ self::step($I, $D, $A, $B, $C, $words[3], 10, 0x8f0ccc92);
+ self::step($I, $C, $D, $A, $B, $words[10], 15, 0xffeff47d);
+ self::step($I, $B, $C, $D, $A, $words[1], 21, 0x85845dd1);
+ self::step($I, $A, $B, $C, $D, $words[8], 6, 0x6fa87e4f);
+ self::step($I, $D, $A, $B, $C, $words[15], 10, 0xfe2ce6e0);
+ self::step($I, $C, $D, $A, $B, $words[6], 15, 0xa3014314);
+ self::step($I, $B, $C, $D, $A, $words[13], 21, 0x4e0811a1);
+ self::step($I, $A, $B, $C, $D, $words[4], 6, 0xf7537e82);
+ self::step($I, $D, $A, $B, $C, $words[11], 10, 0xbd3af235);
+ self::step($I, $C, $D, $A, $B, $words[2], 15, 0x2ad7d2bb);
+ self::step($I, $B, $C, $D, $A, $words[9], 21, 0xeb86d391);
+
+ $this->a = ($this->a + $A) & 0xffffffff;
+ $this->b = ($this->b + $B) & 0xffffffff;
+ $this->c = ($this->c + $C) & 0xffffffff;
+ $this->d = ($this->d + $D) & 0xffffffff;
+ }
+
+ private static function f($X, $Y, $Z)
+ {
+ return ($X & $Y) | ((~$X) & $Z); // X AND Y OR NOT X AND Z
+ }
+
+ private static function g($X, $Y, $Z)
+ {
+ return ($X & $Z) | ($Y & (~$Z)); // X AND Z OR Y AND NOT Z
+ }
+
+ private static function h($X, $Y, $Z)
+ {
+ return $X ^ $Y ^ $Z; // X XOR Y XOR Z
+ }
+
+ private static function i($X, $Y, $Z)
+ {
+ return $Y ^ ($X | (~$Z)); // Y XOR (X OR NOT Z)
+ }
+
+ private static function step($func, &$A, $B, $C, $D, $M, $s, $t): void
+ {
+ $A = ($A + call_user_func($func, $B, $C, $D) + $M + $t) & 0xffffffff;
+ $A = self::rotate($A, $s);
+ $A = ($B + $A) & 0xffffffff;
+ }
+
+ private static function rotate($decimal, $bits)
+ {
+ $binary = str_pad(decbin($decimal), 32, '0', STR_PAD_LEFT);
+
+ return bindec(substr($binary, $bits) . substr($binary, 0, $bits));
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/RC4.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/RC4.php
new file mode 100755
index 0000000..691aca7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/RC4.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
+
+class RC4
+{
+ // Context
+ protected $s = [];
+
+ protected $i = 0;
+
+ protected $j = 0;
+
+ /**
+ * RC4 stream decryption/encryption constrcutor.
+ *
+ * @param string $key Encryption key/passphrase
+ */
+ public function __construct($key)
+ {
+ $len = strlen($key);
+
+ for ($this->i = 0; $this->i < 256; ++$this->i) {
+ $this->s[$this->i] = $this->i;
+ }
+
+ $this->j = 0;
+ for ($this->i = 0; $this->i < 256; ++$this->i) {
+ $this->j = ($this->j + $this->s[$this->i] + ord($key[$this->i % $len])) % 256;
+ $t = $this->s[$this->i];
+ $this->s[$this->i] = $this->s[$this->j];
+ $this->s[$this->j] = $t;
+ }
+ $this->i = $this->j = 0;
+ }
+
+ /**
+ * Symmetric decryption/encryption function.
+ *
+ * @param string $data Data to encrypt/decrypt
+ *
+ * @return string
+ */
+ public function RC4($data)
+ {
+ $len = strlen($data);
+ for ($c = 0; $c < $len; ++$c) {
+ $this->i = ($this->i + 1) % 256;
+ $this->j = ($this->j + $this->s[$this->i]) % 256;
+ $t = $this->s[$this->i];
+ $this->s[$this->i] = $this->s[$this->j];
+ $this->s[$this->j] = $t;
+
+ $t = ($this->s[$this->i] + $this->s[$this->j]) % 256;
+
+ $data[$c] = chr(ord($data[$c]) ^ $this->s[$t]);
+ }
+
+ return $data;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/Border.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/Border.php
new file mode 100755
index 0000000..91cbe36
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/Border.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Style;
+
+use PhpOffice\PhpSpreadsheet\Style\Border as StyleBorder;
+
+class Border
+{
+ protected static $map = [
+ 0x00 => StyleBorder::BORDER_NONE,
+ 0x01 => StyleBorder::BORDER_THIN,
+ 0x02 => StyleBorder::BORDER_MEDIUM,
+ 0x03 => StyleBorder::BORDER_DASHED,
+ 0x04 => StyleBorder::BORDER_DOTTED,
+ 0x05 => StyleBorder::BORDER_THICK,
+ 0x06 => StyleBorder::BORDER_DOUBLE,
+ 0x07 => StyleBorder::BORDER_HAIR,
+ 0x08 => StyleBorder::BORDER_MEDIUMDASHED,
+ 0x09 => StyleBorder::BORDER_DASHDOT,
+ 0x0A => StyleBorder::BORDER_MEDIUMDASHDOT,
+ 0x0B => StyleBorder::BORDER_DASHDOTDOT,
+ 0x0C => StyleBorder::BORDER_MEDIUMDASHDOTDOT,
+ 0x0D => StyleBorder::BORDER_SLANTDASHDOT,
+ ];
+
+ /**
+ * Map border style
+ * OpenOffice documentation: 2.5.11.
+ *
+ * @param int $index
+ *
+ * @return string
+ */
+ public static function lookup($index)
+ {
+ if (isset(self::$map[$index])) {
+ return self::$map[$index];
+ }
+
+ return StyleBorder::BORDER_NONE;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php
new file mode 100755
index 0000000..7b85c08
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Style;
+
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+
+class FillPattern
+{
+ protected static $map = [
+ 0x00 => Fill::FILL_NONE,
+ 0x01 => Fill::FILL_SOLID,
+ 0x02 => Fill::FILL_PATTERN_MEDIUMGRAY,
+ 0x03 => Fill::FILL_PATTERN_DARKGRAY,
+ 0x04 => Fill::FILL_PATTERN_LIGHTGRAY,
+ 0x05 => Fill::FILL_PATTERN_DARKHORIZONTAL,
+ 0x06 => Fill::FILL_PATTERN_DARKVERTICAL,
+ 0x07 => Fill::FILL_PATTERN_DARKDOWN,
+ 0x08 => Fill::FILL_PATTERN_DARKUP,
+ 0x09 => Fill::FILL_PATTERN_DARKGRID,
+ 0x0A => Fill::FILL_PATTERN_DARKTRELLIS,
+ 0x0B => Fill::FILL_PATTERN_LIGHTHORIZONTAL,
+ 0x0C => Fill::FILL_PATTERN_LIGHTVERTICAL,
+ 0x0D => Fill::FILL_PATTERN_LIGHTDOWN,
+ 0x0E => Fill::FILL_PATTERN_LIGHTUP,
+ 0x0F => Fill::FILL_PATTERN_LIGHTGRID,
+ 0x10 => Fill::FILL_PATTERN_LIGHTTRELLIS,
+ 0x11 => Fill::FILL_PATTERN_GRAY125,
+ 0x12 => Fill::FILL_PATTERN_GRAY0625,
+ ];
+
+ /**
+ * Get fill pattern from index
+ * OpenOffice documentation: 2.5.12.
+ *
+ * @param int $index
+ *
+ * @return string
+ */
+ public static function lookup($index)
+ {
+ if (isset(self::$map[$index])) {
+ return self::$map[$index];
+ }
+
+ return Fill::FILL_NONE;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php
new file mode 100755
index 0000000..fb7f064
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php
@@ -0,0 +1,2054 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
+use PhpOffice\PhpSpreadsheet\NamedRange;
+use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\AutoFilter;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ColumnAndRowAttributes;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ConditionalStyles;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\DataValidations;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViews;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles;
+use PhpOffice\PhpSpreadsheet\ReferenceHelper;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Settings;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+use PhpOffice\PhpSpreadsheet\Shared\Drawing;
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Shared\Font;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Borders;
+use PhpOffice\PhpSpreadsheet\Style\Color;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PhpOffice\PhpSpreadsheet\Style\Protection;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use SimpleXMLElement;
+use stdClass;
+use Throwable;
+use XMLReader;
+use ZipArchive;
+
+class Xlsx extends BaseReader
+{
+ /**
+ * ReferenceHelper instance.
+ *
+ * @var ReferenceHelper
+ */
+ private $referenceHelper;
+
+ /**
+ * Xlsx\Theme instance.
+ *
+ * @var Xlsx\Theme
+ */
+ private static $theme = null;
+
+ /**
+ * Create a new Xlsx Reader instance.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->referenceHelper = ReferenceHelper::getInstance();
+ $this->securityScanner = XmlScanner::getInstance($this);
+ }
+
+ /**
+ * Can the current IReader read the file?
+ *
+ * @param string $pFilename
+ *
+ * @return bool
+ */
+ public function canRead($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ $result = false;
+ $zip = new ZipArchive();
+
+ if ($zip->open($pFilename) === true) {
+ $workbookBasename = $this->getWorkbookBaseName($zip);
+ $result = !empty($workbookBasename);
+
+ $zip->close();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
+ *
+ * @param string $pFilename
+ *
+ * @return array
+ */
+ public function listWorksheetNames($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ $worksheetNames = [];
+
+ $zip = new ZipArchive();
+ $zip->open($pFilename);
+
+ // The files we're looking at here are small enough that simpleXML is more efficient than XMLReader
+ //~ http://schemas.openxmlformats.org/package/2006/relationships");
+ $rels = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels'))
+ );
+ foreach ($rels->Relationship as $rel) {
+ switch ($rel['Type']) {
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument':
+ //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
+ $xmlWorkbook = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}"))
+ );
+
+ if ($xmlWorkbook->sheets) {
+ foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
+ // Check if sheet should be skipped
+ $worksheetNames[] = (string) $eleSheet['name'];
+ }
+ }
+ }
+ }
+
+ $zip->close();
+
+ return $worksheetNames;
+ }
+
+ /**
+ * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
+ *
+ * @param string $pFilename
+ *
+ * @return array
+ */
+ public function listWorksheetInfo($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ $worksheetInfo = [];
+
+ $zip = new ZipArchive();
+ $zip->open($pFilename);
+
+ //~ http://schemas.openxmlformats.org/package/2006/relationships"
+ $rels = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ foreach ($rels->Relationship as $rel) {
+ if ($rel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument') {
+ $dir = dirname($rel['Target']);
+
+ //~ http://schemas.openxmlformats.org/package/2006/relationships"
+ $relsWorkbook = simplexml_load_string(
+ $this->securityScanner->scan(
+ $this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels')
+ ),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ $worksheets = [];
+ foreach ($relsWorkbook->Relationship as $ele) {
+ if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet') {
+ $worksheets[(string) $ele['Id']] = $ele['Target'];
+ }
+ }
+
+ //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
+ $xmlWorkbook = simplexml_load_string(
+ $this->securityScanner->scan(
+ $this->getFromZipArchive($zip, "{$rel['Target']}")
+ ),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ if ($xmlWorkbook->sheets) {
+ $dir = dirname($rel['Target']);
+ /** @var SimpleXMLElement $eleSheet */
+ foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
+ $tmpInfo = [
+ 'worksheetName' => (string) $eleSheet['name'],
+ 'lastColumnLetter' => 'A',
+ 'lastColumnIndex' => 0,
+ 'totalRows' => 0,
+ 'totalColumns' => 0,
+ ];
+
+ $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];
+
+ $xml = new XMLReader();
+ $xml->xml(
+ $this->securityScanner->scanFile(
+ 'zip://' . File::realpath($pFilename) . '#' . "$dir/$fileWorksheet"
+ ),
+ null,
+ Settings::getLibXmlLoaderOptions()
+ );
+ $xml->setParserProperty(2, true);
+
+ $currCells = 0;
+ while ($xml->read()) {
+ if ($xml->name == 'row' && $xml->nodeType == XMLReader::ELEMENT) {
+ $row = $xml->getAttribute('r');
+ $tmpInfo['totalRows'] = $row;
+ $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
+ $currCells = 0;
+ } elseif ($xml->name == 'c' && $xml->nodeType == XMLReader::ELEMENT) {
+ ++$currCells;
+ }
+ }
+ $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
+ $xml->close();
+
+ $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
+ $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
+
+ $worksheetInfo[] = $tmpInfo;
+ }
+ }
+ }
+ }
+
+ $zip->close();
+
+ return $worksheetInfo;
+ }
+
+ private static function castToBoolean($c)
+ {
+ $value = isset($c->v) ? (string) $c->v : null;
+ if ($value == '0') {
+ return false;
+ } elseif ($value == '1') {
+ return true;
+ }
+
+ return (bool) $c->v;
+ }
+
+ private static function castToError($c)
+ {
+ return isset($c->v) ? (string) $c->v : null;
+ }
+
+ private static function castToString($c)
+ {
+ return isset($c->v) ? (string) $c->v : null;
+ }
+
+ private function castToFormula($c, $r, &$cellDataType, &$value, &$calculatedValue, &$sharedFormulas, $castBaseType): void
+ {
+ $cellDataType = 'f';
+ $value = "={$c->f}";
+ $calculatedValue = self::$castBaseType($c);
+
+ // Shared formula?
+ if (isset($c->f['t']) && strtolower((string) $c->f['t']) == 'shared') {
+ $instance = (string) $c->f['si'];
+
+ if (!isset($sharedFormulas[(string) $c->f['si']])) {
+ $sharedFormulas[$instance] = ['master' => $r, 'formula' => $value];
+ } else {
+ $master = Coordinate::coordinateFromString($sharedFormulas[$instance]['master']);
+ $current = Coordinate::coordinateFromString($r);
+
+ $difference = [0, 0];
+ $difference[0] = Coordinate::columnIndexFromString($current[0]) - Coordinate::columnIndexFromString($master[0]);
+ $difference[1] = $current[1] - $master[1];
+
+ $value = $this->referenceHelper->updateFormulaReferences($sharedFormulas[$instance]['formula'], 'A1', $difference[0], $difference[1]);
+ }
+ }
+ }
+
+ /**
+ * @param string $fileName
+ *
+ * @return string
+ */
+ private function getFromZipArchive(ZipArchive $archive, $fileName = '')
+ {
+ // Root-relative paths
+ if (strpos($fileName, '//') !== false) {
+ $fileName = substr($fileName, strpos($fileName, '//') + 1);
+ }
+ $fileName = File::realpath($fileName);
+
+ // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming
+ // so we need to load case-insensitively from the zip file
+
+ // Apache POI fixes
+ $contents = $archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE);
+ if ($contents === false) {
+ $contents = $archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE);
+ }
+
+ return $contents;
+ }
+
+ /**
+ * Loads Spreadsheet from file.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function load($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ // Initialisations
+ $excel = new Spreadsheet();
+ $excel->removeSheetByIndex(0);
+ if (!$this->readDataOnly) {
+ $excel->removeCellStyleXfByIndex(0); // remove the default style
+ $excel->removeCellXfByIndex(0); // remove the default style
+ }
+ $unparsedLoadedData = [];
+
+ $zip = new ZipArchive();
+ $zip->open($pFilename);
+
+ // Read the theme first, because we need the colour scheme when reading the styles
+ //~ http://schemas.openxmlformats.org/package/2006/relationships"
+ $workbookBasename = $this->getWorkbookBaseName($zip);
+ $wbRels = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/_rels/${workbookBasename}.rels")),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ foreach ($wbRels->Relationship as $rel) {
+ switch ($rel['Type']) {
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme':
+ $themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2'];
+ $themeOrderAdditional = count($themeOrderArray);
+
+ $xmlTheme = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/{$rel['Target']}")),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ if (is_object($xmlTheme)) {
+ $xmlThemeName = $xmlTheme->attributes();
+ $xmlTheme = $xmlTheme->children('http://schemas.openxmlformats.org/drawingml/2006/main');
+ $themeName = (string) $xmlThemeName['name'];
+
+ $colourScheme = $xmlTheme->themeElements->clrScheme->attributes();
+ $colourSchemeName = (string) $colourScheme['name'];
+ $colourScheme = $xmlTheme->themeElements->clrScheme->children('http://schemas.openxmlformats.org/drawingml/2006/main');
+
+ $themeColours = [];
+ foreach ($colourScheme as $k => $xmlColour) {
+ $themePos = array_search($k, $themeOrderArray);
+ if ($themePos === false) {
+ $themePos = $themeOrderAdditional++;
+ }
+ if (isset($xmlColour->sysClr)) {
+ $xmlColourData = $xmlColour->sysClr->attributes();
+ $themeColours[$themePos] = $xmlColourData['lastClr'];
+ } elseif (isset($xmlColour->srgbClr)) {
+ $xmlColourData = $xmlColour->srgbClr->attributes();
+ $themeColours[$themePos] = $xmlColourData['val'];
+ }
+ }
+ self::$theme = new Xlsx\Theme($themeName, $colourSchemeName, $themeColours);
+ }
+
+ break;
+ }
+ }
+
+ //~ http://schemas.openxmlformats.org/package/2006/relationships"
+ $rels = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+
+ $propertyReader = new PropertyReader($this->securityScanner, $excel->getProperties());
+ foreach ($rels->Relationship as $rel) {
+ switch ($rel['Type']) {
+ case 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties':
+ $propertyReader->readCoreProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
+
+ break;
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties':
+ $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
+
+ break;
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties':
+ $propertyReader->readCustomProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
+
+ break;
+ //Ribbon
+ case 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility':
+ $customUI = $rel['Target'];
+ if ($customUI !== null) {
+ $this->readRibbon($excel, $customUI, $zip);
+ }
+
+ break;
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument':
+ $dir = dirname($rel['Target']);
+ //~ http://schemas.openxmlformats.org/package/2006/relationships"
+ $relsWorkbook = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels')),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ $sharedStrings = [];
+ $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings']"));
+ if ($xpath) {
+ //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
+ $xmlStrings = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ if (isset($xmlStrings, $xmlStrings->si)) {
+ foreach ($xmlStrings->si as $val) {
+ if (isset($val->t)) {
+ $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t);
+ } elseif (isset($val->r)) {
+ $sharedStrings[] = $this->parseRichText($val);
+ }
+ }
+ }
+ }
+
+ $worksheets = [];
+ $macros = $customUI = null;
+ foreach ($relsWorkbook->Relationship as $ele) {
+ switch ($ele['Type']) {
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet':
+ $worksheets[(string) $ele['Id']] = $ele['Target'];
+
+ break;
+ // a vbaProject ? (: some macros)
+ case 'http://schemas.microsoft.com/office/2006/relationships/vbaProject':
+ $macros = $ele['Target'];
+
+ break;
+ }
+ }
+
+ if ($macros !== null) {
+ $macrosCode = $this->getFromZipArchive($zip, 'xl/vbaProject.bin'); //vbaProject.bin always in 'xl' dir and always named vbaProject.bin
+ if ($macrosCode !== false) {
+ $excel->setMacrosCode($macrosCode);
+ $excel->setHasMacros(true);
+ //short-circuit : not reading vbaProject.bin.rel to get Signature =>allways vbaProjectSignature.bin in 'xl' dir
+ $Certificate = $this->getFromZipArchive($zip, 'xl/vbaProjectSignature.bin');
+ if ($Certificate !== false) {
+ $excel->setMacrosCertificate($Certificate);
+ }
+ }
+ }
+
+ $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']"));
+ //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
+ $xmlStyles = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+
+ $styles = [];
+ $cellStyles = [];
+ $numFmts = null;
+ if ($xmlStyles && $xmlStyles->numFmts[0]) {
+ $numFmts = $xmlStyles->numFmts[0];
+ }
+ if (isset($numFmts) && ($numFmts !== null)) {
+ $numFmts->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+ }
+ if (!$this->readDataOnly && $xmlStyles) {
+ foreach ($xmlStyles->cellXfs->xf as $xf) {
+ $numFmt = NumberFormat::FORMAT_GENERAL;
+
+ if ($xf['numFmtId']) {
+ if (isset($numFmts)) {
+ $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]"));
+
+ if (isset($tmpNumFmt['formatCode'])) {
+ $numFmt = (string) $tmpNumFmt['formatCode'];
+ }
+ }
+
+ // We shouldn't override any of the built-in MS Excel values (values below id 164)
+ // But there's a lot of naughty homebrew xlsx writers that do use "reserved" id values that aren't actually used
+ // So we make allowance for them rather than lose formatting masks
+ if ((int) $xf['numFmtId'] < 164 &&
+ NumberFormat::builtInFormatCode((int) $xf['numFmtId']) !== '') {
+ $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
+ }
+ }
+ $quotePrefix = false;
+ if (isset($xf['quotePrefix'])) {
+ $quotePrefix = (bool) $xf['quotePrefix'];
+ }
+
+ $style = (object) [
+ 'numFmt' => $numFmt,
+ 'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])],
+ 'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])],
+ 'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])],
+ 'alignment' => $xf->alignment,
+ 'protection' => $xf->protection,
+ 'quotePrefix' => $quotePrefix,
+ ];
+ $styles[] = $style;
+
+ // add style to cellXf collection
+ $objStyle = new Style();
+ self::readStyle($objStyle, $style);
+ $excel->addCellXf($objStyle);
+ }
+
+ foreach (isset($xmlStyles->cellStyleXfs->xf) ? $xmlStyles->cellStyleXfs->xf : [] as $xf) {
+ $numFmt = NumberFormat::FORMAT_GENERAL;
+ if ($numFmts && $xf['numFmtId']) {
+ $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]"));
+ if (isset($tmpNumFmt['formatCode'])) {
+ $numFmt = (string) $tmpNumFmt['formatCode'];
+ } elseif ((int) $xf['numFmtId'] < 165) {
+ $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
+ }
+ }
+
+ $cellStyle = (object) [
+ 'numFmt' => $numFmt,
+ 'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])],
+ 'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])],
+ 'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])],
+ 'alignment' => $xf->alignment,
+ 'protection' => $xf->protection,
+ 'quotePrefix' => $quotePrefix,
+ ];
+ $cellStyles[] = $cellStyle;
+
+ // add style to cellStyleXf collection
+ $objStyle = new Style();
+ self::readStyle($objStyle, $cellStyle);
+ $excel->addCellStyleXf($objStyle);
+ }
+ }
+
+ $styleReader = new Styles($xmlStyles);
+ $styleReader->setStyleBaseData(self::$theme, $styles, $cellStyles);
+ $dxfs = $styleReader->dxfs($this->readDataOnly);
+ $styles = $styleReader->styles();
+
+ //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
+ $xmlWorkbook = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+
+ // Set base date
+ if ($xmlWorkbook->workbookPr) {
+ Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
+ if (isset($xmlWorkbook->workbookPr['date1904'])) {
+ if (self::boolean((string) $xmlWorkbook->workbookPr['date1904'])) {
+ Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
+ }
+ }
+ }
+
+ // Set protection
+ $this->readProtection($excel, $xmlWorkbook);
+
+ $sheetId = 0; // keep track of new sheet id in final workbook
+ $oldSheetId = -1; // keep track of old sheet id in final workbook
+ $countSkippedSheets = 0; // keep track of number of skipped sheets
+ $mapSheetId = []; // mapping of sheet ids from old to new
+
+ $charts = $chartDetails = [];
+
+ if ($xmlWorkbook->sheets) {
+ /** @var SimpleXMLElement $eleSheet */
+ foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
+ ++$oldSheetId;
+
+ // Check if sheet should be skipped
+ if (isset($this->loadSheetsOnly) && !in_array((string) $eleSheet['name'], $this->loadSheetsOnly)) {
+ ++$countSkippedSheets;
+ $mapSheetId[$oldSheetId] = null;
+
+ continue;
+ }
+
+ // Map old sheet id in original workbook to new sheet id.
+ // They will differ if loadSheetsOnly() is being used
+ $mapSheetId[$oldSheetId] = $oldSheetId - $countSkippedSheets;
+
+ // Load sheet
+ $docSheet = $excel->createSheet();
+ // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet
+ // references in formula cells... during the load, all formulae should be correct,
+ // and we're simply bringing the worksheet name in line with the formula, not the
+ // reverse
+ $docSheet->setTitle((string) $eleSheet['name'], false, false);
+ $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];
+ //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
+ $xmlSheet = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$fileWorksheet")),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+
+ $sharedFormulas = [];
+
+ if (isset($eleSheet['state']) && (string) $eleSheet['state'] != '') {
+ $docSheet->setSheetState((string) $eleSheet['state']);
+ }
+
+ if ($xmlSheet) {
+ if (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) {
+ $sheetViews = new SheetViews($xmlSheet->sheetViews->sheetView, $docSheet);
+ $sheetViews->load();
+ }
+
+ $sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheet);
+ $sheetViewOptions->load($this->getReadDataOnly());
+
+ (new ColumnAndRowAttributes($docSheet, $xmlSheet))
+ ->load($this->getReadFilter(), $this->getReadDataOnly());
+ }
+
+ if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) {
+ $cIndex = 1; // Cell Start from 1
+ foreach ($xmlSheet->sheetData->row as $row) {
+ $rowIndex = 1;
+ foreach ($row->c as $c) {
+ $r = (string) $c['r'];
+ if ($r == '') {
+ $r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex;
+ }
+ $cellDataType = (string) $c['t'];
+ $value = null;
+ $calculatedValue = null;
+
+ // Read cell?
+ if ($this->getReadFilter() !== null) {
+ $coordinates = Coordinate::coordinateFromString($r);
+
+ if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) {
+ ++$rowIndex;
+
+ continue;
+ }
+ }
+
+ // Read cell!
+ switch ($cellDataType) {
+ case 's':
+ if ((string) $c->v != '') {
+ $value = $sharedStrings[(int) ($c->v)];
+
+ if ($value instanceof RichText) {
+ $value = clone $value;
+ }
+ } else {
+ $value = '';
+ }
+
+ break;
+ case 'b':
+ if (!isset($c->f)) {
+ $value = self::castToBoolean($c);
+ } else {
+ // Formula
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToBoolean');
+ if (isset($c->f['t'])) {
+ $att = $c->f;
+ $docSheet->getCell($r)->setFormulaAttributes($att);
+ }
+ }
+
+ break;
+ case 'inlineStr':
+ if (isset($c->f)) {
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
+ } else {
+ $value = $this->parseRichText($c->is);
+ }
+
+ break;
+ case 'e':
+ if (!isset($c->f)) {
+ $value = self::castToError($c);
+ } else {
+ // Formula
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
+ }
+
+ break;
+ default:
+ if (!isset($c->f)) {
+ $value = self::castToString($c);
+ } else {
+ // Formula
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToString');
+ }
+
+ break;
+ }
+
+ // read empty cells or the cells are not empty
+ if ($this->readEmptyCells || ($value !== null && $value !== '')) {
+ // Rich text?
+ if ($value instanceof RichText && $this->readDataOnly) {
+ $value = $value->getPlainText();
+ }
+
+ $cell = $docSheet->getCell($r);
+ // Assign value
+ if ($cellDataType != '') {
+ $cell->setValueExplicit($value, $cellDataType);
+ } else {
+ $cell->setValue($value);
+ }
+ if ($calculatedValue !== null) {
+ $cell->setCalculatedValue($calculatedValue);
+ }
+
+ // Style information?
+ if ($c['s'] && !$this->readDataOnly) {
+ // no style index means 0, it seems
+ $cell->setXfIndex(isset($styles[(int) ($c['s'])]) ?
+ (int) ($c['s']) : 0);
+ }
+ }
+ ++$rowIndex;
+ }
+ ++$cIndex;
+ }
+ }
+
+ if (!$this->readDataOnly && $xmlSheet && $xmlSheet->conditionalFormatting) {
+ (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load();
+ }
+
+ $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'pivotTables', 'selectUnlockedCells'];
+ if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) {
+ foreach ($aKeys as $key) {
+ $method = 'set' . ucfirst($key);
+ $docSheet->getProtection()->$method(self::boolean((string) $xmlSheet->sheetProtection[$key]));
+ }
+ }
+
+ if ($xmlSheet) {
+ $this->readSheetProtection($docSheet, $xmlSheet);
+ }
+
+ if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) {
+ (new AutoFilter($docSheet, $xmlSheet))->load();
+ }
+
+ if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) {
+ foreach ($xmlSheet->mergeCells->mergeCell as $mergeCell) {
+ $mergeRef = (string) $mergeCell['ref'];
+ if (strpos($mergeRef, ':') !== false) {
+ $docSheet->mergeCells((string) $mergeCell['ref']);
+ }
+ }
+ }
+
+ if ($xmlSheet && !$this->readDataOnly) {
+ $unparsedLoadedData = (new PageSetup($docSheet, $xmlSheet))->load($unparsedLoadedData);
+ }
+
+ if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) {
+ (new DataValidations($docSheet, $xmlSheet))->load();
+ }
+
+ // unparsed sheet AlternateContent
+ if ($xmlSheet && !$this->readDataOnly) {
+ $mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006');
+ if ($mc->AlternateContent) {
+ foreach ($mc->AlternateContent as $alternateContent) {
+ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML();
+ }
+ }
+ }
+
+ // Add hyperlinks
+ if (!$this->readDataOnly) {
+ $hyperlinkReader = new Hyperlinks($docSheet);
+ // Locate hyperlink relations
+ $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
+ if ($zip->locateName($relationsFileName)) {
+ //~ http://schemas.openxmlformats.org/package/2006/relationships"
+ $relsWorksheet = simplexml_load_string(
+ $this->securityScanner->scan(
+ $this->getFromZipArchive($zip, $relationsFileName)
+ ),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $hyperlinkReader->readHyperlinks($relsWorksheet);
+ }
+
+ // Loop through hyperlinks
+ if ($xmlSheet && $xmlSheet->hyperlinks) {
+ $hyperlinkReader->setHyperlinks($xmlSheet->hyperlinks);
+ }
+ }
+
+ // Add comments
+ $comments = [];
+ $vmlComments = [];
+ if (!$this->readDataOnly) {
+ // Locate comment relations
+ if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
+ //~ http://schemas.openxmlformats.org/package/2006/relationships"
+ $relsWorksheet = simplexml_load_string(
+ $this->securityScanner->scan(
+ $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
+ ),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ foreach ($relsWorksheet->Relationship as $ele) {
+ if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments') {
+ $comments[(string) $ele['Id']] = (string) $ele['Target'];
+ }
+ if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') {
+ $vmlComments[(string) $ele['Id']] = (string) $ele['Target'];
+ }
+ }
+ }
+
+ // Loop through comments
+ foreach ($comments as $relName => $relPath) {
+ // Load comments file
+ $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);
+ $commentsFile = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+
+ // Utility variables
+ $authors = [];
+
+ // Loop through authors
+ foreach ($commentsFile->authors->author as $author) {
+ $authors[] = (string) $author;
+ }
+
+ // Loop through contents
+ foreach ($commentsFile->commentList->comment as $comment) {
+ if (!empty($comment['authorId'])) {
+ $docSheet->getComment((string) $comment['ref'])->setAuthor($authors[(string) $comment['authorId']]);
+ }
+ $docSheet->getComment((string) $comment['ref'])->setText($this->parseRichText($comment->text));
+ }
+ }
+
+ // later we will remove from it real vmlComments
+ $unparsedVmlDrawings = $vmlComments;
+
+ // Loop through VML comments
+ foreach ($vmlComments as $relName => $relPath) {
+ // Load VML comments file
+ $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);
+
+ try {
+ $vmlCommentsFile = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $vmlCommentsFile->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
+ } catch (Throwable $ex) {
+ //Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData
+ continue;
+ }
+
+ $shapes = $vmlCommentsFile->xpath('//v:shape');
+ foreach ($shapes as $shape) {
+ $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
+
+ if (isset($shape['style'])) {
+ $style = (string) $shape['style'];
+ $fillColor = strtoupper(substr((string) $shape['fillcolor'], 1));
+ $column = null;
+ $row = null;
+
+ $clientData = $shape->xpath('.//x:ClientData');
+ if (is_array($clientData) && !empty($clientData)) {
+ $clientData = $clientData[0];
+
+ if (isset($clientData['ObjectType']) && (string) $clientData['ObjectType'] == 'Note') {
+ $temp = $clientData->xpath('.//x:Row');
+ if (is_array($temp)) {
+ $row = $temp[0];
+ }
+
+ $temp = $clientData->xpath('.//x:Column');
+ if (is_array($temp)) {
+ $column = $temp[0];
+ }
+ }
+ }
+
+ if (($column !== null) && ($row !== null)) {
+ // Set comment properties
+ $comment = $docSheet->getCommentByColumnAndRow($column + 1, $row + 1);
+ $comment->getFillColor()->setRGB($fillColor);
+
+ // Parse style
+ $styleArray = explode(';', str_replace(' ', '', $style));
+ foreach ($styleArray as $stylePair) {
+ $stylePair = explode(':', $stylePair);
+
+ if ($stylePair[0] == 'margin-left') {
+ $comment->setMarginLeft($stylePair[1]);
+ }
+ if ($stylePair[0] == 'margin-top') {
+ $comment->setMarginTop($stylePair[1]);
+ }
+ if ($stylePair[0] == 'width') {
+ $comment->setWidth($stylePair[1]);
+ }
+ if ($stylePair[0] == 'height') {
+ $comment->setHeight($stylePair[1]);
+ }
+ if ($stylePair[0] == 'visibility') {
+ $comment->setVisible($stylePair[1] == 'visible');
+ }
+ }
+
+ unset($unparsedVmlDrawings[$relName]);
+ }
+ }
+ }
+ }
+
+ // unparsed vmlDrawing
+ if ($unparsedVmlDrawings) {
+ foreach ($unparsedVmlDrawings as $rId => $relPath) {
+ $rId = substr($rId, 3); // rIdXXX
+ $unparsedVmlDrawing = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['vmlDrawings'];
+ $unparsedVmlDrawing[$rId] = [];
+ $unparsedVmlDrawing[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $relPath);
+ $unparsedVmlDrawing[$rId]['relFilePath'] = $relPath;
+ $unparsedVmlDrawing[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedVmlDrawing[$rId]['filePath']));
+ unset($unparsedVmlDrawing);
+ }
+ }
+
+ // Header/footer images
+ if ($xmlSheet && $xmlSheet->legacyDrawingHF && !$this->readDataOnly) {
+ if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
+ //~ http://schemas.openxmlformats.org/package/2006/relationships"
+ $relsWorksheet = simplexml_load_string(
+ $this->securityScanner->scan(
+ $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
+ ),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $vmlRelationship = '';
+
+ foreach ($relsWorksheet->Relationship as $ele) {
+ if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') {
+ $vmlRelationship = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
+ }
+ }
+
+ if ($vmlRelationship != '') {
+ // Fetch linked images
+ //~ http://schemas.openxmlformats.org/package/2006/relationships"
+ $relsVML = simplexml_load_string(
+ $this->securityScanner->scan(
+ $this->getFromZipArchive($zip, dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels')
+ ),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $drawings = [];
+ if (isset($relsVML->Relationship)) {
+ foreach ($relsVML->Relationship as $ele) {
+ if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {
+ $drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']);
+ }
+ }
+ }
+ // Fetch VML document
+ $vmlDrawing = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, $vmlRelationship)),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $vmlDrawing->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
+
+ $hfImages = [];
+
+ $shapes = $vmlDrawing->xpath('//v:shape');
+ foreach ($shapes as $idx => $shape) {
+ $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
+ $imageData = $shape->xpath('//v:imagedata');
+
+ if (!$imageData) {
+ continue;
+ }
+
+ $imageData = $imageData[$idx];
+
+ $imageData = $imageData->attributes('urn:schemas-microsoft-com:office:office');
+ $style = self::toCSSArray((string) $shape['style']);
+
+ $hfImages[(string) $shape['id']] = new HeaderFooterDrawing();
+ if (isset($imageData['title'])) {
+ $hfImages[(string) $shape['id']]->setName((string) $imageData['title']);
+ }
+
+ $hfImages[(string) $shape['id']]->setPath('zip://' . File::realpath($pFilename) . '#' . $drawings[(string) $imageData['relid']], false);
+ $hfImages[(string) $shape['id']]->setResizeProportional(false);
+ $hfImages[(string) $shape['id']]->setWidth($style['width']);
+ $hfImages[(string) $shape['id']]->setHeight($style['height']);
+ if (isset($style['margin-left'])) {
+ $hfImages[(string) $shape['id']]->setOffsetX($style['margin-left']);
+ }
+ $hfImages[(string) $shape['id']]->setOffsetY($style['margin-top']);
+ $hfImages[(string) $shape['id']]->setResizeProportional(true);
+ }
+
+ $docSheet->getHeaderFooter()->setImages($hfImages);
+ }
+ }
+ }
+ }
+
+ // TODO: Autoshapes from twoCellAnchors!
+ if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
+ //~ http://schemas.openxmlformats.org/package/2006/relationships"
+ $relsWorksheet = simplexml_load_string(
+ $this->securityScanner->scan(
+ $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
+ ),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $drawings = [];
+ foreach ($relsWorksheet->Relationship as $ele) {
+ if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') {
+ $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
+ }
+ }
+ if ($xmlSheet->drawing && !$this->readDataOnly) {
+ $unparsedDrawings = [];
+ foreach ($xmlSheet->drawing as $drawing) {
+ $drawingRelId = (string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id');
+ $fileDrawing = $drawings[$drawingRelId];
+ //~ http://schemas.openxmlformats.org/package/2006/relationships"
+ $relsDrawing = simplexml_load_string(
+ $this->securityScanner->scan(
+ $this->getFromZipArchive($zip, dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels')
+ ),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $images = [];
+ $hyperlinks = [];
+ if ($relsDrawing && $relsDrawing->Relationship) {
+ foreach ($relsDrawing->Relationship as $ele) {
+ if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') {
+ $hyperlinks[(string) $ele['Id']] = (string) $ele['Target'];
+ }
+ if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {
+ $images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']);
+ } elseif ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart') {
+ if ($this->includeCharts) {
+ $charts[self::dirAdd($fileDrawing, $ele['Target'])] = [
+ 'id' => (string) $ele['Id'],
+ 'sheet' => $docSheet->getTitle(),
+ ];
+ }
+ }
+ }
+ }
+ $xmlDrawing = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $xmlDrawingChildren = $xmlDrawing->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
+
+ if ($xmlDrawingChildren->oneCellAnchor) {
+ foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) {
+ if ($oneCellAnchor->pic->blipFill) {
+ /** @var SimpleXMLElement $blip */
+ $blip = $oneCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
+ /** @var SimpleXMLElement $xfrm */
+ $xfrm = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;
+ /** @var SimpleXMLElement $outerShdw */
+ $outerShdw = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw;
+ /** @var SimpleXMLElement $hlinkClick */
+ $hlinkClick = $oneCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;
+
+ $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
+ $objDrawing->setName((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name'));
+ $objDrawing->setDescription((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr'));
+ $objDrawing->setPath(
+ 'zip://' . File::realpath($pFilename) . '#' .
+ $images[(string) self::getArrayItem(
+ $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'),
+ 'embed'
+ )],
+ false
+ );
+ $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1));
+ $objDrawing->setOffsetX(Drawing::EMUToPixels($oneCellAnchor->from->colOff));
+ $objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff));
+ $objDrawing->setResizeProportional(false);
+ $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx')));
+ $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy')));
+ if ($xfrm) {
+ $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot')));
+ }
+ if ($outerShdw) {
+ $shadow = $objDrawing->getShadow();
+ $shadow->setVisible(true);
+ $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad')));
+ $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist')));
+ $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir')));
+ $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn'));
+ $clr = isset($outerShdw->srgbClr) ? $outerShdw->srgbClr : $outerShdw->prstClr;
+ $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val'));
+ $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000);
+ }
+
+ $this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks);
+
+ $objDrawing->setWorksheet($docSheet);
+ } else {
+ // ? Can charts be positioned with a oneCellAnchor ?
+ $coordinates = Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1);
+ $offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff);
+ $offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff);
+ $width = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx'));
+ $height = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy'));
+ }
+ }
+ }
+ if ($xmlDrawingChildren->twoCellAnchor) {
+ foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) {
+ if ($twoCellAnchor->pic->blipFill) {
+ $blip = $twoCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
+ $xfrm = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;
+ $outerShdw = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw;
+ $hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;
+ $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
+ $objDrawing->setName((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name'));
+ $objDrawing->setDescription((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr'));
+ $objDrawing->setPath(
+ 'zip://' . File::realpath($pFilename) . '#' .
+ $images[(string) self::getArrayItem(
+ $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'),
+ 'embed'
+ )],
+ false
+ );
+ $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1));
+ $objDrawing->setOffsetX(Drawing::EMUToPixels($twoCellAnchor->from->colOff));
+ $objDrawing->setOffsetY(Drawing::EMUToPixels($twoCellAnchor->from->rowOff));
+ $objDrawing->setResizeProportional(false);
+
+ if ($xfrm) {
+ $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cx')));
+ $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cy')));
+ $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot')));
+ }
+ if ($outerShdw) {
+ $shadow = $objDrawing->getShadow();
+ $shadow->setVisible(true);
+ $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad')));
+ $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist')));
+ $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir')));
+ $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn'));
+ $clr = isset($outerShdw->srgbClr) ? $outerShdw->srgbClr : $outerShdw->prstClr;
+ $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val'));
+ $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000);
+ }
+
+ $this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks);
+
+ $objDrawing->setWorksheet($docSheet);
+ } elseif (($this->includeCharts) && ($twoCellAnchor->graphicFrame)) {
+ $fromCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1);
+ $fromOffsetX = Drawing::EMUToPixels($twoCellAnchor->from->colOff);
+ $fromOffsetY = Drawing::EMUToPixels($twoCellAnchor->from->rowOff);
+ $toCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1);
+ $toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff);
+ $toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff);
+ $graphic = $twoCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic;
+ /** @var SimpleXMLElement $chartRef */
+ $chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart;
+ $thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+
+ $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [
+ 'fromCoordinate' => $fromCoordinate,
+ 'fromOffsetX' => $fromOffsetX,
+ 'fromOffsetY' => $fromOffsetY,
+ 'toCoordinate' => $toCoordinate,
+ 'toOffsetX' => $toOffsetX,
+ 'toOffsetY' => $toOffsetY,
+ 'worksheetTitle' => $docSheet->getTitle(),
+ ];
+ }
+ }
+ }
+ if ($relsDrawing === false && $xmlDrawing->count() == 0) {
+ // Save Drawing without rels and children as unparsed
+ $unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML();
+ }
+ }
+
+ // store original rId of drawing files
+ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = [];
+ foreach ($relsWorksheet->Relationship as $ele) {
+ if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') {
+ $drawingRelId = (string) $ele['Id'];
+ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = $drawingRelId;
+ if (isset($unparsedDrawings[$drawingRelId])) {
+ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['Drawings'][$drawingRelId] = $unparsedDrawings[$drawingRelId];
+ }
+ }
+ }
+
+ // unparsed drawing AlternateContent
+ $xmlAltDrawing = simplexml_load_string(
+ $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ )->children('http://schemas.openxmlformats.org/markup-compatibility/2006');
+
+ if ($xmlAltDrawing->AlternateContent) {
+ foreach ($xmlAltDrawing->AlternateContent as $alternateContent) {
+ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML();
+ }
+ }
+ }
+ }
+
+ $this->readFormControlProperties($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
+ $this->readPrinterSettings($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
+
+ // Loop through definedNames
+ if ($xmlWorkbook->definedNames) {
+ foreach ($xmlWorkbook->definedNames->definedName as $definedName) {
+ // Extract range
+ $extractedRange = (string) $definedName;
+ if (($spos = strpos($extractedRange, '!')) !== false) {
+ $extractedRange = substr($extractedRange, 0, $spos) . str_replace('$', '', substr($extractedRange, $spos));
+ } else {
+ $extractedRange = str_replace('$', '', $extractedRange);
+ }
+
+ // Valid range?
+ if (stripos((string) $definedName, '#REF!') !== false || $extractedRange == '') {
+ continue;
+ }
+
+ // Some definedNames are only applicable if we are on the same sheet...
+ if ((string) $definedName['localSheetId'] != '' && (string) $definedName['localSheetId'] == $oldSheetId) {
+ // Switch on type
+ switch ((string) $definedName['name']) {
+ case '_xlnm._FilterDatabase':
+ if ((string) $definedName['hidden'] !== '1') {
+ $extractedRange = explode(',', $extractedRange);
+ foreach ($extractedRange as $range) {
+ $autoFilterRange = $range;
+ if (strpos($autoFilterRange, ':') !== false) {
+ $docSheet->getAutoFilter()->setRange($autoFilterRange);
+ }
+ }
+ }
+
+ break;
+ case '_xlnm.Print_Titles':
+ // Split $extractedRange
+ $extractedRange = explode(',', $extractedRange);
+
+ // Set print titles
+ foreach ($extractedRange as $range) {
+ $matches = [];
+ $range = str_replace('$', '', $range);
+
+ // check for repeating columns, e g. 'A:A' or 'A:D'
+ if (preg_match('/!?([A-Z]+)\:([A-Z]+)$/', $range, $matches)) {
+ $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$matches[1], $matches[2]]);
+ } elseif (preg_match('/!?(\d+)\:(\d+)$/', $range, $matches)) {
+ // check for repeating rows, e.g. '1:1' or '1:5'
+ $docSheet->getPageSetup()->setRowsToRepeatAtTop([$matches[1], $matches[2]]);
+ }
+ }
+
+ break;
+ case '_xlnm.Print_Area':
+ $rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+ $newRangeSets = [];
+ foreach ($rangeSets as $rangeSet) {
+ [$sheetName, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true);
+ if (strpos($rangeSet, ':') === false) {
+ $rangeSet = $rangeSet . ':' . $rangeSet;
+ }
+ $newRangeSets[] = str_replace('$', '', $rangeSet);
+ }
+ $docSheet->getPageSetup()->setPrintArea(implode(',', $newRangeSets));
+
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ // Next sheet id
+ ++$sheetId;
+ }
+
+ // Loop through definedNames
+ if ($xmlWorkbook->definedNames) {
+ foreach ($xmlWorkbook->definedNames->definedName as $definedName) {
+ // Extract range
+ $extractedRange = (string) $definedName;
+ if (($spos = strpos($extractedRange, '!')) !== false) {
+ $extractedRange = substr($extractedRange, 0, $spos) . str_replace('$', '', substr($extractedRange, $spos));
+ } else {
+ $extractedRange = str_replace('$', '', $extractedRange);
+ }
+
+ // Valid range?
+ if (stripos((string) $definedName, '#REF!') !== false || $extractedRange == '') {
+ continue;
+ }
+
+ // Some definedNames are only applicable if we are on the same sheet...
+ if ((string) $definedName['localSheetId'] != '') {
+ // Local defined name
+ // Switch on type
+ switch ((string) $definedName['name']) {
+ case '_xlnm._FilterDatabase':
+ case '_xlnm.Print_Titles':
+ case '_xlnm.Print_Area':
+ break;
+ default:
+ if ($mapSheetId[(int) $definedName['localSheetId']] !== null) {
+ if (strpos((string) $definedName, '!') !== false) {
+ $range = Worksheet::extractSheetTitle((string) $definedName, true);
+ $range[0] = str_replace("''", "'", $range[0]);
+ $range[0] = str_replace("'", '', $range[0]);
+ if ($worksheet = $docSheet->getParent()->getSheetByName($range[0])) {
+ $extractedRange = str_replace('$', '', $range[1]);
+ $scope = $docSheet->getParent()->getSheet($mapSheetId[(int) $definedName['localSheetId']]);
+ $excel->addNamedRange(new NamedRange((string) $definedName['name'], $worksheet, $extractedRange, true, $scope));
+ }
+ }
+ }
+
+ break;
+ }
+ } elseif (!isset($definedName['localSheetId'])) {
+ // "Global" definedNames
+ $locatedSheet = null;
+ $extractedSheetName = '';
+ if (strpos((string) $definedName, '!') !== false) {
+ // Extract sheet name
+ $extractedSheetName = Worksheet::extractSheetTitle((string) $definedName, true);
+ $extractedSheetName = trim($extractedSheetName[0], "'");
+
+ // Locate sheet
+ $locatedSheet = $excel->getSheetByName($extractedSheetName);
+
+ // Modify range
+ [$worksheetName, $extractedRange] = Worksheet::extractSheetTitle($extractedRange, true);
+ }
+
+ if ($locatedSheet !== null) {
+ $excel->addNamedRange(new NamedRange((string) $definedName['name'], $locatedSheet, $extractedRange, false));
+ }
+ }
+ }
+ }
+ }
+
+ if ((!$this->readDataOnly || !empty($this->loadSheetsOnly)) && isset($xmlWorkbook->bookViews->workbookView)) {
+ $workbookView = $xmlWorkbook->bookViews->workbookView;
+
+ // active sheet index
+ $activeTab = (int) ($workbookView['activeTab']); // refers to old sheet index
+
+ // keep active sheet index if sheet is still loaded, else first sheet is set as the active
+ if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) {
+ $excel->setActiveSheetIndex($mapSheetId[$activeTab]);
+ } else {
+ if ($excel->getSheetCount() == 0) {
+ $excel->createSheet();
+ }
+ $excel->setActiveSheetIndex(0);
+ }
+
+ if (isset($workbookView['showHorizontalScroll'])) {
+ $showHorizontalScroll = (string) $workbookView['showHorizontalScroll'];
+ $excel->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll));
+ }
+
+ if (isset($workbookView['showVerticalScroll'])) {
+ $showVerticalScroll = (string) $workbookView['showVerticalScroll'];
+ $excel->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll));
+ }
+
+ if (isset($workbookView['showSheetTabs'])) {
+ $showSheetTabs = (string) $workbookView['showSheetTabs'];
+ $excel->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs));
+ }
+
+ if (isset($workbookView['minimized'])) {
+ $minimized = (string) $workbookView['minimized'];
+ $excel->setMinimized($this->castXsdBooleanToBool($minimized));
+ }
+
+ if (isset($workbookView['autoFilterDateGrouping'])) {
+ $autoFilterDateGrouping = (string) $workbookView['autoFilterDateGrouping'];
+ $excel->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping));
+ }
+
+ if (isset($workbookView['firstSheet'])) {
+ $firstSheet = (string) $workbookView['firstSheet'];
+ $excel->setFirstSheetIndex((int) $firstSheet);
+ }
+
+ if (isset($workbookView['visibility'])) {
+ $visibility = (string) $workbookView['visibility'];
+ $excel->setVisibility($visibility);
+ }
+
+ if (isset($workbookView['tabRatio'])) {
+ $tabRatio = (string) $workbookView['tabRatio'];
+ $excel->setTabRatio((int) $tabRatio);
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (!$this->readDataOnly) {
+ $contentTypes = simplexml_load_string(
+ $this->securityScanner->scan(
+ $this->getFromZipArchive($zip, '[Content_Types].xml')
+ ),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+
+ // Default content types
+ foreach ($contentTypes->Default as $contentType) {
+ switch ($contentType['ContentType']) {
+ case 'application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings':
+ $unparsedLoadedData['default_content_types'][(string) $contentType['Extension']] = (string) $contentType['ContentType'];
+
+ break;
+ }
+ }
+
+ // Override content types
+ foreach ($contentTypes->Override as $contentType) {
+ switch ($contentType['ContentType']) {
+ case 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml':
+ if ($this->includeCharts) {
+ $chartEntryRef = ltrim($contentType['PartName'], '/');
+ $chartElements = simplexml_load_string(
+ $this->securityScanner->scan(
+ $this->getFromZipArchive($zip, $chartEntryRef)
+ ),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $objChart = Chart::readChart($chartElements, basename($chartEntryRef, '.xml'));
+
+ if (isset($charts[$chartEntryRef])) {
+ $chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id'];
+ if (isset($chartDetails[$chartPositionRef])) {
+ $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart);
+ $objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet']));
+ $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
+ $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']);
+ }
+ }
+ }
+
+ break;
+
+ // unparsed
+ case 'application/vnd.ms-excel.controlproperties+xml':
+ $unparsedLoadedData['override_content_types'][(string) $contentType['PartName']] = (string) $contentType['ContentType'];
+
+ break;
+ }
+ }
+ }
+
+ $excel->setUnparsedLoadedData($unparsedLoadedData);
+
+ $zip->close();
+
+ return $excel;
+ }
+
+ private static function readColor($color, $background = false)
+ {
+ if (isset($color['rgb'])) {
+ return (string) $color['rgb'];
+ } elseif (isset($color['indexed'])) {
+ return Color::indexedColor($color['indexed'] - 7, $background)->getARGB();
+ } elseif (isset($color['theme'])) {
+ if (self::$theme !== null) {
+ $returnColour = self::$theme->getColourByIndex((int) $color['theme']);
+ if (isset($color['tint'])) {
+ $tintAdjust = (float) $color['tint'];
+ $returnColour = Color::changeBrightness($returnColour, $tintAdjust);
+ }
+
+ return 'FF' . $returnColour;
+ }
+ }
+
+ if ($background) {
+ return 'FFFFFFFF';
+ }
+
+ return 'FF000000';
+ }
+
+ /**
+ * @param SimpleXMLElement|stdClass $style
+ */
+ private static function readStyle(Style $docStyle, $style): void
+ {
+ $docStyle->getNumberFormat()->setFormatCode($style->numFmt);
+
+ // font
+ if (isset($style->font)) {
+ $docStyle->getFont()->setName((string) $style->font->name['val']);
+ $docStyle->getFont()->setSize((string) $style->font->sz['val']);
+ if (isset($style->font->b)) {
+ $docStyle->getFont()->setBold(!isset($style->font->b['val']) || self::boolean((string) $style->font->b['val']));
+ }
+ if (isset($style->font->i)) {
+ $docStyle->getFont()->setItalic(!isset($style->font->i['val']) || self::boolean((string) $style->font->i['val']));
+ }
+ if (isset($style->font->strike)) {
+ $docStyle->getFont()->setStrikethrough(!isset($style->font->strike['val']) || self::boolean((string) $style->font->strike['val']));
+ }
+ $docStyle->getFont()->getColor()->setARGB(self::readColor($style->font->color));
+
+ if (isset($style->font->u) && !isset($style->font->u['val'])) {
+ $docStyle->getFont()->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE);
+ } elseif (isset($style->font->u, $style->font->u['val'])) {
+ $docStyle->getFont()->setUnderline((string) $style->font->u['val']);
+ }
+
+ if (isset($style->font->vertAlign, $style->font->vertAlign['val'])) {
+ $vertAlign = strtolower((string) $style->font->vertAlign['val']);
+ if ($vertAlign == 'superscript') {
+ $docStyle->getFont()->setSuperscript(true);
+ }
+ if ($vertAlign == 'subscript') {
+ $docStyle->getFont()->setSubscript(true);
+ }
+ }
+ }
+
+ // fill
+ if (isset($style->fill)) {
+ if ($style->fill->gradientFill) {
+ /** @var SimpleXMLElement $gradientFill */
+ $gradientFill = $style->fill->gradientFill[0];
+ if (!empty($gradientFill['type'])) {
+ $docStyle->getFill()->setFillType((string) $gradientFill['type']);
+ }
+ $docStyle->getFill()->setRotation((float) ($gradientFill['degree']));
+ $gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+ $docStyle->getFill()->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color));
+ $docStyle->getFill()->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color));
+ } elseif ($style->fill->patternFill) {
+ $patternType = (string) $style->fill->patternFill['patternType'] != '' ? (string) $style->fill->patternFill['patternType'] : 'solid';
+ $docStyle->getFill()->setFillType($patternType);
+ if ($style->fill->patternFill->fgColor) {
+ $docStyle->getFill()->getStartColor()->setARGB(self::readColor($style->fill->patternFill->fgColor, true));
+ }
+ if ($style->fill->patternFill->bgColor) {
+ $docStyle->getFill()->getEndColor()->setARGB(self::readColor($style->fill->patternFill->bgColor, true));
+ }
+ }
+ }
+
+ // border
+ if (isset($style->border)) {
+ $diagonalUp = self::boolean((string) $style->border['diagonalUp']);
+ $diagonalDown = self::boolean((string) $style->border['diagonalDown']);
+ if (!$diagonalUp && !$diagonalDown) {
+ $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE);
+ } elseif ($diagonalUp && !$diagonalDown) {
+ $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP);
+ } elseif (!$diagonalUp && $diagonalDown) {
+ $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN);
+ } else {
+ $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH);
+ }
+ self::readBorder($docStyle->getBorders()->getLeft(), $style->border->left);
+ self::readBorder($docStyle->getBorders()->getRight(), $style->border->right);
+ self::readBorder($docStyle->getBorders()->getTop(), $style->border->top);
+ self::readBorder($docStyle->getBorders()->getBottom(), $style->border->bottom);
+ self::readBorder($docStyle->getBorders()->getDiagonal(), $style->border->diagonal);
+ }
+
+ // alignment
+ if (isset($style->alignment)) {
+ $docStyle->getAlignment()->setHorizontal((string) $style->alignment['horizontal']);
+ $docStyle->getAlignment()->setVertical((string) $style->alignment['vertical']);
+
+ $textRotation = 0;
+ if ((int) $style->alignment['textRotation'] <= 90) {
+ $textRotation = (int) $style->alignment['textRotation'];
+ } elseif ((int) $style->alignment['textRotation'] > 90) {
+ $textRotation = 90 - (int) $style->alignment['textRotation'];
+ }
+
+ $docStyle->getAlignment()->setTextRotation((int) $textRotation);
+ $docStyle->getAlignment()->setWrapText(self::boolean((string) $style->alignment['wrapText']));
+ $docStyle->getAlignment()->setShrinkToFit(self::boolean((string) $style->alignment['shrinkToFit']));
+ $docStyle->getAlignment()->setIndent((int) ((string) $style->alignment['indent']) > 0 ? (int) ((string) $style->alignment['indent']) : 0);
+ $docStyle->getAlignment()->setReadOrder((int) ((string) $style->alignment['readingOrder']) > 0 ? (int) ((string) $style->alignment['readingOrder']) : 0);
+ }
+
+ // protection
+ if (isset($style->protection)) {
+ if (isset($style->protection['locked'])) {
+ if (self::boolean((string) $style->protection['locked'])) {
+ $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED);
+ } else {
+ $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED);
+ }
+ }
+
+ if (isset($style->protection['hidden'])) {
+ if (self::boolean((string) $style->protection['hidden'])) {
+ $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED);
+ } else {
+ $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED);
+ }
+ }
+ }
+
+ // top-level style settings
+ if (isset($style->quotePrefix)) {
+ $docStyle->setQuotePrefix($style->quotePrefix);
+ }
+ }
+
+ /**
+ * @param SimpleXMLElement $eleBorder
+ */
+ private static function readBorder(Border $docBorder, $eleBorder): void
+ {
+ if (isset($eleBorder['style'])) {
+ $docBorder->setBorderStyle((string) $eleBorder['style']);
+ }
+ if (isset($eleBorder->color)) {
+ $docBorder->getColor()->setARGB(self::readColor($eleBorder->color));
+ }
+ }
+
+ /**
+ * @param SimpleXMLElement | null $is
+ *
+ * @return RichText
+ */
+ private function parseRichText($is)
+ {
+ $value = new RichText();
+
+ if (isset($is->t)) {
+ $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $is->t));
+ } else {
+ if (is_object($is->r)) {
+ foreach ($is->r as $run) {
+ if (!isset($run->rPr)) {
+ $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $run->t));
+ } else {
+ $objText = $value->createTextRun(StringHelper::controlCharacterOOXML2PHP((string) $run->t));
+
+ if (isset($run->rPr->rFont['val'])) {
+ $objText->getFont()->setName((string) $run->rPr->rFont['val']);
+ }
+ if (isset($run->rPr->sz['val'])) {
+ $objText->getFont()->setSize((float) $run->rPr->sz['val']);
+ }
+ if (isset($run->rPr->color)) {
+ $objText->getFont()->setColor(new Color(self::readColor($run->rPr->color)));
+ }
+ if ((isset($run->rPr->b['val']) && self::boolean((string) $run->rPr->b['val'])) ||
+ (isset($run->rPr->b) && !isset($run->rPr->b['val']))) {
+ $objText->getFont()->setBold(true);
+ }
+ if ((isset($run->rPr->i['val']) && self::boolean((string) $run->rPr->i['val'])) ||
+ (isset($run->rPr->i) && !isset($run->rPr->i['val']))) {
+ $objText->getFont()->setItalic(true);
+ }
+ if (isset($run->rPr->vertAlign, $run->rPr->vertAlign['val'])) {
+ $vertAlign = strtolower((string) $run->rPr->vertAlign['val']);
+ if ($vertAlign == 'superscript') {
+ $objText->getFont()->setSuperscript(true);
+ }
+ if ($vertAlign == 'subscript') {
+ $objText->getFont()->setSubscript(true);
+ }
+ }
+ if (isset($run->rPr->u) && !isset($run->rPr->u['val'])) {
+ $objText->getFont()->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE);
+ } elseif (isset($run->rPr->u, $run->rPr->u['val'])) {
+ $objText->getFont()->setUnderline((string) $run->rPr->u['val']);
+ }
+ if ((isset($run->rPr->strike['val']) && self::boolean((string) $run->rPr->strike['val'])) ||
+ (isset($run->rPr->strike) && !isset($run->rPr->strike['val']))) {
+ $objText->getFont()->setStrikethrough(true);
+ }
+ }
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * @param mixed $customUITarget
+ * @param mixed $zip
+ */
+ private function readRibbon(Spreadsheet $excel, $customUITarget, $zip): void
+ {
+ $baseDir = dirname($customUITarget);
+ $nameCustomUI = basename($customUITarget);
+ // get the xml file (ribbon)
+ $localRibbon = $this->getFromZipArchive($zip, $customUITarget);
+ $customUIImagesNames = [];
+ $customUIImagesBinaries = [];
+ // something like customUI/_rels/customUI.xml.rels
+ $pathRels = $baseDir . '/_rels/' . $nameCustomUI . '.rels';
+ $dataRels = $this->getFromZipArchive($zip, $pathRels);
+ if ($dataRels) {
+ // exists and not empty if the ribbon have some pictures (other than internal MSO)
+ $UIRels = simplexml_load_string(
+ $this->securityScanner->scan($dataRels),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ if (false !== $UIRels) {
+ // we need to save id and target to avoid parsing customUI.xml and "guess" if it's a pseudo callback who load the image
+ foreach ($UIRels->Relationship as $ele) {
+ if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {
+ // an image ?
+ $customUIImagesNames[(string) $ele['Id']] = (string) $ele['Target'];
+ $customUIImagesBinaries[(string) $ele['Target']] = $this->getFromZipArchive($zip, $baseDir . '/' . (string) $ele['Target']);
+ }
+ }
+ }
+ }
+ if ($localRibbon) {
+ $excel->setRibbonXMLData($customUITarget, $localRibbon);
+ if (count($customUIImagesNames) > 0 && count($customUIImagesBinaries) > 0) {
+ $excel->setRibbonBinObjects($customUIImagesNames, $customUIImagesBinaries);
+ } else {
+ $excel->setRibbonBinObjects(null, null);
+ }
+ } else {
+ $excel->setRibbonXMLData(null, null);
+ $excel->setRibbonBinObjects(null, null);
+ }
+ }
+
+ private static function getArrayItem($array, $key = 0)
+ {
+ return $array[$key] ?? null;
+ }
+
+ private static function dirAdd($base, $add)
+ {
+ return preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add");
+ }
+
+ private static function toCSSArray($style)
+ {
+ $style = self::stripWhiteSpaceFromStyleString($style);
+
+ $temp = explode(';', $style);
+ $style = [];
+ foreach ($temp as $item) {
+ $item = explode(':', $item);
+
+ if (strpos($item[1], 'px') !== false) {
+ $item[1] = str_replace('px', '', $item[1]);
+ }
+ if (strpos($item[1], 'pt') !== false) {
+ $item[1] = str_replace('pt', '', $item[1]);
+ $item[1] = Font::fontSizeToPixels($item[1]);
+ }
+ if (strpos($item[1], 'in') !== false) {
+ $item[1] = str_replace('in', '', $item[1]);
+ $item[1] = Font::inchSizeToPixels($item[1]);
+ }
+ if (strpos($item[1], 'cm') !== false) {
+ $item[1] = str_replace('cm', '', $item[1]);
+ $item[1] = Font::centimeterSizeToPixels($item[1]);
+ }
+
+ $style[$item[0]] = $item[1];
+ }
+
+ return $style;
+ }
+
+ public static function stripWhiteSpaceFromStyleString($string)
+ {
+ return trim(str_replace(["\r", "\n", ' '], '', $string), ';');
+ }
+
+ private static function boolean($value)
+ {
+ if (is_object($value)) {
+ $value = (string) $value;
+ }
+ if (is_numeric($value)) {
+ return (bool) $value;
+ }
+
+ return $value === 'true' || $value === 'TRUE';
+ }
+
+ /**
+ * @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $objDrawing
+ * @param SimpleXMLElement $cellAnchor
+ * @param array $hyperlinks
+ */
+ private function readHyperLinkDrawing($objDrawing, $cellAnchor, $hyperlinks): void
+ {
+ $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;
+
+ if ($hlinkClick->count() === 0) {
+ return;
+ }
+
+ $hlinkId = (string) $hlinkClick->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships')['id'];
+ $hyperlink = new Hyperlink(
+ $hyperlinks[$hlinkId],
+ (string) self::getArrayItem($cellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')
+ );
+ $objDrawing->setHyperlink($hyperlink);
+ }
+
+ private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkbook): void
+ {
+ if (!$xmlWorkbook->workbookProtection) {
+ return;
+ }
+
+ if ($xmlWorkbook->workbookProtection['lockRevision']) {
+ $excel->getSecurity()->setLockRevision((bool) $xmlWorkbook->workbookProtection['lockRevision']);
+ }
+
+ if ($xmlWorkbook->workbookProtection['lockStructure']) {
+ $excel->getSecurity()->setLockStructure((bool) $xmlWorkbook->workbookProtection['lockStructure']);
+ }
+
+ if ($xmlWorkbook->workbookProtection['lockWindows']) {
+ $excel->getSecurity()->setLockWindows((bool) $xmlWorkbook->workbookProtection['lockWindows']);
+ }
+
+ if ($xmlWorkbook->workbookProtection['revisionsPassword']) {
+ $excel->getSecurity()->setRevisionsPassword((string) $xmlWorkbook->workbookProtection['revisionsPassword'], true);
+ }
+
+ if ($xmlWorkbook->workbookProtection['workbookPassword']) {
+ $excel->getSecurity()->setWorkbookPassword((string) $xmlWorkbook->workbookProtection['workbookPassword'], true);
+ }
+ }
+
+ private function readFormControlProperties(Spreadsheet $excel, ZipArchive $zip, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void
+ {
+ if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
+ return;
+ }
+
+ //~ http://schemas.openxmlformats.org/package/2006/relationships"
+ $relsWorksheet = simplexml_load_string(
+ $this->securityScanner->scan(
+ $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
+ ),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $ctrlProps = [];
+ foreach ($relsWorksheet->Relationship as $ele) {
+ if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp') {
+ $ctrlProps[(string) $ele['Id']] = $ele;
+ }
+ }
+
+ $unparsedCtrlProps = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['ctrlProps'];
+ foreach ($ctrlProps as $rId => $ctrlProp) {
+ $rId = substr($rId, 3); // rIdXXX
+ $unparsedCtrlProps[$rId] = [];
+ $unparsedCtrlProps[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $ctrlProp['Target']);
+ $unparsedCtrlProps[$rId]['relFilePath'] = (string) $ctrlProp['Target'];
+ $unparsedCtrlProps[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedCtrlProps[$rId]['filePath']));
+ }
+ unset($unparsedCtrlProps);
+ }
+
+ private function readPrinterSettings(Spreadsheet $excel, ZipArchive $zip, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData): void
+ {
+ if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
+ return;
+ }
+
+ //~ http://schemas.openxmlformats.org/package/2006/relationships"
+ $relsWorksheet = simplexml_load_string(
+ $this->securityScanner->scan(
+ $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')
+ ),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $sheetPrinterSettings = [];
+ foreach ($relsWorksheet->Relationship as $ele) {
+ if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings') {
+ $sheetPrinterSettings[(string) $ele['Id']] = $ele;
+ }
+ }
+
+ $unparsedPrinterSettings = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['printerSettings'];
+ foreach ($sheetPrinterSettings as $rId => $printerSettings) {
+ $rId = substr($rId, 3); // rIdXXX
+ $unparsedPrinterSettings[$rId] = [];
+ $unparsedPrinterSettings[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $printerSettings['Target']);
+ $unparsedPrinterSettings[$rId]['relFilePath'] = (string) $printerSettings['Target'];
+ $unparsedPrinterSettings[$rId]['content'] = $this->securityScanner->scan($this->getFromZipArchive($zip, $unparsedPrinterSettings[$rId]['filePath']));
+ }
+ unset($unparsedPrinterSettings);
+ }
+
+ /**
+ * 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($xsdBoolean)
+ {
+ if ($xsdBoolean === 'false') {
+ return false;
+ }
+
+ return (bool) $xsdBoolean;
+ }
+
+ /**
+ * @param ZipArchive $zip Opened zip archive
+ *
+ * @return string basename of the used excel workbook
+ */
+ private function getWorkbookBaseName(ZipArchive $zip)
+ {
+ $workbookBasename = '';
+
+ // check if it is an OOXML archive
+ $rels = simplexml_load_string(
+ $this->securityScanner->scan(
+ $this->getFromZipArchive($zip, '_rels/.rels')
+ ),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ if ($rels !== false) {
+ foreach ($rels->Relationship as $rel) {
+ switch ($rel['Type']) {
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument':
+ $basename = basename($rel['Target']);
+ if (preg_match('/workbook.*\.xml/', $basename)) {
+ $workbookBasename = $basename;
+ }
+
+ break;
+ }
+ }
+ }
+
+ return $workbookBasename;
+ }
+
+ private function readSheetProtection(Worksheet $docSheet, SimpleXMLElement $xmlSheet): void
+ {
+ if ($this->readDataOnly || !$xmlSheet->sheetProtection) {
+ return;
+ }
+
+ $algorithmName = (string) $xmlSheet->sheetProtection['algorithmName'];
+ $protection = $docSheet->getProtection();
+ $protection->setAlgorithm($algorithmName);
+
+ if ($algorithmName) {
+ $protection->setPassword((string) $xmlSheet->sheetProtection['hashValue'], true);
+ $protection->setSalt((string) $xmlSheet->sheetProtection['saltValue']);
+ $protection->setSpinCount((int) $xmlSheet->sheetProtection['spinCount']);
+ } else {
+ $protection->setPassword((string) $xmlSheet->sheetProtection['password'], true);
+ }
+
+ if ($xmlSheet->protectedRanges->protectedRange) {
+ foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) {
+ $docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true);
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
new file mode 100755
index 0000000..f52bfd4
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
@@ -0,0 +1,146 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
+use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use SimpleXMLElement;
+
+class AutoFilter
+{
+ private $worksheet;
+
+ private $worksheetXml;
+
+ public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml)
+ {
+ $this->worksheet = $workSheet;
+ $this->worksheetXml = $worksheetXml;
+ }
+
+ public function load(): void
+ {
+ // Remove all "$" in the auto filter range
+ $autoFilterRange = preg_replace('/\$/', '', $this->worksheetXml->autoFilter['ref']);
+ if (strpos($autoFilterRange, ':') !== false) {
+ $this->readAutoFilter($autoFilterRange, $this->worksheetXml);
+ }
+ }
+
+ private function readAutoFilter($autoFilterRange, $xmlSheet): void
+ {
+ $autoFilter = $this->worksheet->getAutoFilter();
+ $autoFilter->setRange($autoFilterRange);
+
+ foreach ($xmlSheet->autoFilter->filterColumn as $filterColumn) {
+ $column = $autoFilter->getColumnByOffset((int) $filterColumn['colId']);
+ // Check for standard filters
+ if ($filterColumn->filters) {
+ $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER);
+ $filters = $filterColumn->filters;
+ if ((isset($filters['blank'])) && ($filters['blank'] == 1)) {
+ // Operator is undefined, but always treated as EQUAL
+ $column->createRule()->setRule(null, '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
+ }
+ // Standard filters are always an OR join, so no join rule needs to be set
+ // Entries can be either filter elements
+ foreach ($filters->filter as $filterRule) {
+ // Operator is undefined, but always treated as EQUAL
+ $column->createRule()->setRule(null, (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
+ }
+
+ // Or Date Group elements
+ $this->readDateRangeAutoFilter($filters, $column);
+ }
+
+ // Check for custom filters
+ $this->readCustomAutoFilter($filterColumn, $column);
+ // Check for dynamic filters
+ $this->readDynamicAutoFilter($filterColumn, $column);
+ // Check for dynamic filters
+ $this->readTopTenAutoFilter($filterColumn, $column);
+ }
+ }
+
+ private function readDateRangeAutoFilter(SimpleXMLElement $filters, Column $column): void
+ {
+ foreach ($filters->dateGroupItem as $dateGroupItem) {
+ // Operator is undefined, but always treated as EQUAL
+ $column->createRule()->setRule(
+ null,
+ [
+ 'year' => (string) $dateGroupItem['year'],
+ 'month' => (string) $dateGroupItem['month'],
+ 'day' => (string) $dateGroupItem['day'],
+ 'hour' => (string) $dateGroupItem['hour'],
+ 'minute' => (string) $dateGroupItem['minute'],
+ 'second' => (string) $dateGroupItem['second'],
+ ],
+ (string) $dateGroupItem['dateTimeGrouping']
+ )->setRuleType(Rule::AUTOFILTER_RULETYPE_DATEGROUP);
+ }
+ }
+
+ private function readCustomAutoFilter(SimpleXMLElement $filterColumn, Column $column): void
+ {
+ if ($filterColumn->customFilters) {
+ $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER);
+ $customFilters = $filterColumn->customFilters;
+ // Custom filters can an AND or an OR join;
+ // and there should only ever be one or two entries
+ if ((isset($customFilters['and'])) && ($customFilters['and'] == 1)) {
+ $column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND);
+ }
+ foreach ($customFilters->customFilter as $filterRule) {
+ $column->createRule()->setRule(
+ (string) $filterRule['operator'],
+ (string) $filterRule['val']
+ )->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER);
+ }
+ }
+ }
+
+ private function readDynamicAutoFilter(SimpleXMLElement $filterColumn, Column $column): void
+ {
+ if ($filterColumn->dynamicFilter) {
+ $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER);
+ // We should only ever have one dynamic filter
+ foreach ($filterColumn->dynamicFilter as $filterRule) {
+ // Operator is undefined, but always treated as EQUAL
+ $column->createRule()->setRule(
+ null,
+ (string) $filterRule['val'],
+ (string) $filterRule['type']
+ )->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER);
+ if (isset($filterRule['val'])) {
+ $column->setAttribute('val', (string) $filterRule['val']);
+ }
+ if (isset($filterRule['maxVal'])) {
+ $column->setAttribute('maxVal', (string) $filterRule['maxVal']);
+ }
+ }
+ }
+ }
+
+ private function readTopTenAutoFilter(SimpleXMLElement $filterColumn, Column $column): void
+ {
+ if ($filterColumn->top10) {
+ $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER);
+ // We should only ever have one top10 filter
+ foreach ($filterColumn->top10 as $filterRule) {
+ $column->createRule()->setRule(
+ (((isset($filterRule['percent'])) && ($filterRule['percent'] == 1))
+ ? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT
+ : Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE
+ ),
+ (string) $filterRule['val'],
+ (((isset($filterRule['top'])) && ($filterRule['top'] == 1))
+ ? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP
+ : Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM
+ )
+ )->setRuleType(Rule::AUTOFILTER_RULETYPE_TOPTENFILTER);
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php
new file mode 100755
index 0000000..1679f01
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+class BaseParserClass
+{
+ protected static function boolean($value)
+ {
+ if (is_object($value)) {
+ $value = (string) $value;
+ }
+
+ if (is_numeric($value)) {
+ return (bool) $value;
+ }
+
+ return $value === strtolower('true');
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
new file mode 100755
index 0000000..c9a230c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
@@ -0,0 +1,567 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
+use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
+use PhpOffice\PhpSpreadsheet\Chart\Layout;
+use PhpOffice\PhpSpreadsheet\Chart\Legend;
+use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
+use PhpOffice\PhpSpreadsheet\Chart\Title;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Style\Color;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+use SimpleXMLElement;
+
+class Chart
+{
+ /**
+ * @param string $name
+ * @param string $format
+ *
+ * @return null|bool|float|int|string
+ */
+ private static function getAttribute(SimpleXMLElement $component, $name, $format)
+ {
+ $attributes = $component->attributes();
+ if (isset($attributes[$name])) {
+ if ($format == 'string') {
+ return (string) $attributes[$name];
+ } elseif ($format == 'integer') {
+ return (int) $attributes[$name];
+ } elseif ($format == 'boolean') {
+ return (bool) ($attributes[$name] === '0' || $attributes[$name] !== 'true') ? false : true;
+ }
+
+ return (float) $attributes[$name];
+ }
+
+ return null;
+ }
+
+ private static function readColor($color, $background = false)
+ {
+ if (isset($color['rgb'])) {
+ return (string) $color['rgb'];
+ } elseif (isset($color['indexed'])) {
+ return Color::indexedColor($color['indexed'] - 7, $background)->getARGB();
+ }
+ }
+
+ /**
+ * @param string $chartName
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Chart\Chart
+ */
+ public static function readChart(SimpleXMLElement $chartElements, $chartName)
+ {
+ $namespacesChartMeta = $chartElements->getNamespaces(true);
+ $chartElementsC = $chartElements->children($namespacesChartMeta['c']);
+
+ $XaxisLabel = $YaxisLabel = $legend = $title = null;
+ $dispBlanksAs = $plotVisOnly = null;
+
+ foreach ($chartElementsC as $chartElementKey => $chartElement) {
+ switch ($chartElementKey) {
+ case 'chart':
+ foreach ($chartElement as $chartDetailsKey => $chartDetails) {
+ $chartDetailsC = $chartDetails->children($namespacesChartMeta['c']);
+ switch ($chartDetailsKey) {
+ case 'plotArea':
+ $plotAreaLayout = $XaxisLable = $YaxisLable = null;
+ $plotSeries = $plotAttributes = [];
+ foreach ($chartDetails as $chartDetailKey => $chartDetail) {
+ switch ($chartDetailKey) {
+ case 'layout':
+ $plotAreaLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta);
+
+ break;
+ case 'catAx':
+ if (isset($chartDetail->title)) {
+ $XaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta);
+ }
+
+ break;
+ case 'dateAx':
+ if (isset($chartDetail->title)) {
+ $XaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta);
+ }
+
+ break;
+ case 'valAx':
+ if (isset($chartDetail->title)) {
+ $YaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta);
+ }
+
+ break;
+ case 'barChart':
+ case 'bar3DChart':
+ $barDirection = self::getAttribute($chartDetail->barDir, 'val', 'string');
+ $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
+ $plotSer->setPlotDirection($barDirection);
+ $plotSeries[] = $plotSer;
+ $plotAttributes = self::readChartAttributes($chartDetail);
+
+ break;
+ case 'lineChart':
+ case 'line3DChart':
+ $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
+ $plotAttributes = self::readChartAttributes($chartDetail);
+
+ break;
+ case 'areaChart':
+ case 'area3DChart':
+ $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
+ $plotAttributes = self::readChartAttributes($chartDetail);
+
+ break;
+ case 'doughnutChart':
+ case 'pieChart':
+ case 'pie3DChart':
+ $explosion = isset($chartDetail->ser->explosion);
+ $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
+ $plotSer->setPlotStyle($explosion);
+ $plotSeries[] = $plotSer;
+ $plotAttributes = self::readChartAttributes($chartDetail);
+
+ break;
+ case 'scatterChart':
+ $scatterStyle = self::getAttribute($chartDetail->scatterStyle, 'val', 'string');
+ $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
+ $plotSer->setPlotStyle($scatterStyle);
+ $plotSeries[] = $plotSer;
+ $plotAttributes = self::readChartAttributes($chartDetail);
+
+ break;
+ case 'bubbleChart':
+ $bubbleScale = self::getAttribute($chartDetail->bubbleScale, 'val', 'integer');
+ $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
+ $plotSer->setPlotStyle($bubbleScale);
+ $plotSeries[] = $plotSer;
+ $plotAttributes = self::readChartAttributes($chartDetail);
+
+ break;
+ case 'radarChart':
+ $radarStyle = self::getAttribute($chartDetail->radarStyle, 'val', 'string');
+ $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
+ $plotSer->setPlotStyle($radarStyle);
+ $plotSeries[] = $plotSer;
+ $plotAttributes = self::readChartAttributes($chartDetail);
+
+ break;
+ case 'surfaceChart':
+ case 'surface3DChart':
+ $wireFrame = self::getAttribute($chartDetail->wireframe, 'val', 'boolean');
+ $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
+ $plotSer->setPlotStyle($wireFrame);
+ $plotSeries[] = $plotSer;
+ $plotAttributes = self::readChartAttributes($chartDetail);
+
+ break;
+ case 'stockChart':
+ $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey);
+ $plotAttributes = self::readChartAttributes($plotAreaLayout);
+
+ break;
+ }
+ }
+ if ($plotAreaLayout == null) {
+ $plotAreaLayout = new Layout();
+ }
+ $plotArea = new PlotArea($plotAreaLayout, $plotSeries);
+ self::setChartAttributes($plotAreaLayout, $plotAttributes);
+
+ break;
+ case 'plotVisOnly':
+ $plotVisOnly = self::getAttribute($chartDetails, 'val', 'string');
+
+ break;
+ case 'dispBlanksAs':
+ $dispBlanksAs = self::getAttribute($chartDetails, 'val', 'string');
+
+ break;
+ case 'title':
+ $title = self::chartTitle($chartDetails, $namespacesChartMeta);
+
+ break;
+ case 'legend':
+ $legendPos = 'r';
+ $legendLayout = null;
+ $legendOverlay = false;
+ foreach ($chartDetails as $chartDetailKey => $chartDetail) {
+ switch ($chartDetailKey) {
+ case 'legendPos':
+ $legendPos = self::getAttribute($chartDetail, 'val', 'string');
+
+ break;
+ case 'overlay':
+ $legendOverlay = self::getAttribute($chartDetail, 'val', 'boolean');
+
+ break;
+ case 'layout':
+ $legendLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta);
+
+ break;
+ }
+ }
+ $legend = new Legend($legendPos, $legendLayout, $legendOverlay);
+
+ break;
+ }
+ }
+ }
+ }
+ $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, $dispBlanksAs, $XaxisLabel, $YaxisLabel);
+
+ return $chart;
+ }
+
+ private static function chartTitle(SimpleXMLElement $titleDetails, array $namespacesChartMeta)
+ {
+ $caption = [];
+ $titleLayout = null;
+ foreach ($titleDetails as $titleDetailKey => $chartDetail) {
+ switch ($titleDetailKey) {
+ case 'tx':
+ $titleDetails = $chartDetail->rich->children($namespacesChartMeta['a']);
+ foreach ($titleDetails as $titleKey => $titleDetail) {
+ switch ($titleKey) {
+ case 'p':
+ $titleDetailPart = $titleDetail->children($namespacesChartMeta['a']);
+ $caption[] = self::parseRichText($titleDetailPart);
+ }
+ }
+
+ break;
+ case 'layout':
+ $titleLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta);
+
+ break;
+ }
+ }
+
+ return new Title($caption, $titleLayout);
+ }
+
+ private static function chartLayoutDetails($chartDetail, $namespacesChartMeta)
+ {
+ if (!isset($chartDetail->manualLayout)) {
+ return null;
+ }
+ $details = $chartDetail->manualLayout->children($namespacesChartMeta['c']);
+ if ($details === null) {
+ return null;
+ }
+ $layout = [];
+ foreach ($details as $detailKey => $detail) {
+ $layout[$detailKey] = self::getAttribute($detail, 'val', 'string');
+ }
+
+ return new Layout($layout);
+ }
+
+ private static function chartDataSeries($chartDetail, $namespacesChartMeta, $plotType)
+ {
+ $multiSeriesType = null;
+ $smoothLine = false;
+ $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = [];
+
+ $seriesDetailSet = $chartDetail->children($namespacesChartMeta['c']);
+ foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) {
+ switch ($seriesDetailKey) {
+ case 'grouping':
+ $multiSeriesType = self::getAttribute($chartDetail->grouping, 'val', 'string');
+
+ break;
+ case 'ser':
+ $marker = null;
+ $seriesIndex = '';
+ foreach ($seriesDetails as $seriesKey => $seriesDetail) {
+ switch ($seriesKey) {
+ case 'idx':
+ $seriesIndex = self::getAttribute($seriesDetail, 'val', 'integer');
+
+ break;
+ case 'order':
+ $seriesOrder = self::getAttribute($seriesDetail, 'val', 'integer');
+ $plotOrder[$seriesIndex] = $seriesOrder;
+
+ break;
+ case 'tx':
+ $seriesLabel[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta);
+
+ break;
+ case 'marker':
+ $marker = self::getAttribute($seriesDetail->symbol, 'val', 'string');
+
+ break;
+ case 'smooth':
+ $smoothLine = self::getAttribute($seriesDetail, 'val', 'boolean');
+
+ break;
+ case 'cat':
+ $seriesCategory[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta);
+
+ break;
+ case 'val':
+ $seriesValues[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker);
+
+ break;
+ case 'xVal':
+ $seriesCategory[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker);
+
+ break;
+ case 'yVal':
+ $seriesValues[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker);
+
+ break;
+ }
+ }
+ }
+ }
+
+ return new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine);
+ }
+
+ private static function chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker = null)
+ {
+ if (isset($seriesDetail->strRef)) {
+ $seriesSource = (string) $seriesDetail->strRef->f;
+ $seriesData = self::chartDataSeriesValues($seriesDetail->strRef->strCache->children($namespacesChartMeta['c']), 's');
+
+ return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker);
+ } elseif (isset($seriesDetail->numRef)) {
+ $seriesSource = (string) $seriesDetail->numRef->f;
+ $seriesData = self::chartDataSeriesValues($seriesDetail->numRef->numCache->children($namespacesChartMeta['c']));
+
+ return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker);
+ } elseif (isset($seriesDetail->multiLvlStrRef)) {
+ $seriesSource = (string) $seriesDetail->multiLvlStrRef->f;
+ $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($namespacesChartMeta['c']), 's');
+ $seriesData['pointCount'] = count($seriesData['dataValues']);
+
+ return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker);
+ } elseif (isset($seriesDetail->multiLvlNumRef)) {
+ $seriesSource = (string) $seriesDetail->multiLvlNumRef->f;
+ $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($namespacesChartMeta['c']), 's');
+ $seriesData['pointCount'] = count($seriesData['dataValues']);
+
+ return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker);
+ }
+
+ return null;
+ }
+
+ private static function chartDataSeriesValues($seriesValueSet, $dataType = 'n')
+ {
+ $seriesVal = [];
+ $formatCode = '';
+ $pointCount = 0;
+
+ foreach ($seriesValueSet as $seriesValueIdx => $seriesValue) {
+ switch ($seriesValueIdx) {
+ case 'ptCount':
+ $pointCount = self::getAttribute($seriesValue, 'val', 'integer');
+
+ break;
+ case 'formatCode':
+ $formatCode = (string) $seriesValue;
+
+ break;
+ case 'pt':
+ $pointVal = self::getAttribute($seriesValue, 'idx', 'integer');
+ if ($dataType == 's') {
+ $seriesVal[$pointVal] = (string) $seriesValue->v;
+ } elseif ($seriesValue->v === Functions::NA()) {
+ $seriesVal[$pointVal] = null;
+ } else {
+ $seriesVal[$pointVal] = (float) $seriesValue->v;
+ }
+
+ break;
+ }
+ }
+
+ return [
+ 'formatCode' => $formatCode,
+ 'pointCount' => $pointCount,
+ 'dataValues' => $seriesVal,
+ ];
+ }
+
+ private static function chartDataSeriesValuesMultiLevel($seriesValueSet, $dataType = 'n')
+ {
+ $seriesVal = [];
+ $formatCode = '';
+ $pointCount = 0;
+
+ foreach ($seriesValueSet->lvl as $seriesLevelIdx => $seriesLevel) {
+ foreach ($seriesLevel as $seriesValueIdx => $seriesValue) {
+ switch ($seriesValueIdx) {
+ case 'ptCount':
+ $pointCount = self::getAttribute($seriesValue, 'val', 'integer');
+
+ break;
+ case 'formatCode':
+ $formatCode = (string) $seriesValue;
+
+ break;
+ case 'pt':
+ $pointVal = self::getAttribute($seriesValue, 'idx', 'integer');
+ if ($dataType == 's') {
+ $seriesVal[$pointVal][] = (string) $seriesValue->v;
+ } elseif ($seriesValue->v === Functions::NA()) {
+ $seriesVal[$pointVal] = null;
+ } else {
+ $seriesVal[$pointVal][] = (float) $seriesValue->v;
+ }
+
+ break;
+ }
+ }
+ }
+
+ return [
+ 'formatCode' => $formatCode,
+ 'pointCount' => $pointCount,
+ 'dataValues' => $seriesVal,
+ ];
+ }
+
+ private static function parseRichText(SimpleXMLElement $titleDetailPart)
+ {
+ $value = new RichText();
+ $objText = null;
+ foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) {
+ if (isset($titleDetailElement->t)) {
+ $objText = $value->createTextRun((string) $titleDetailElement->t);
+ }
+ if (isset($titleDetailElement->rPr)) {
+ if (isset($titleDetailElement->rPr->rFont['val'])) {
+ $objText->getFont()->setName((string) $titleDetailElement->rPr->rFont['val']);
+ }
+
+ $fontSize = (self::getAttribute($titleDetailElement->rPr, 'sz', 'integer'));
+ if ($fontSize !== null) {
+ $objText->getFont()->setSize(floor($fontSize / 100));
+ }
+
+ $fontColor = (self::getAttribute($titleDetailElement->rPr, 'color', 'string'));
+ if ($fontColor !== null) {
+ $objText->getFont()->setColor(new Color(self::readColor($fontColor)));
+ }
+
+ $bold = self::getAttribute($titleDetailElement->rPr, 'b', 'boolean');
+ if ($bold !== null) {
+ $objText->getFont()->setBold($bold);
+ }
+
+ $italic = self::getAttribute($titleDetailElement->rPr, 'i', 'boolean');
+ if ($italic !== null) {
+ $objText->getFont()->setItalic($italic);
+ }
+
+ $baseline = self::getAttribute($titleDetailElement->rPr, 'baseline', 'integer');
+ if ($baseline !== null) {
+ if ($baseline > 0) {
+ $objText->getFont()->setSuperscript(true);
+ } elseif ($baseline < 0) {
+ $objText->getFont()->setSubscript(true);
+ }
+ }
+
+ $underscore = (self::getAttribute($titleDetailElement->rPr, 'u', 'string'));
+ if ($underscore !== null) {
+ if ($underscore == 'sng') {
+ $objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
+ } elseif ($underscore == 'dbl') {
+ $objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE);
+ } else {
+ $objText->getFont()->setUnderline(Font::UNDERLINE_NONE);
+ }
+ }
+
+ $strikethrough = (self::getAttribute($titleDetailElement->rPr, 's', 'string'));
+ if ($strikethrough !== null) {
+ if ($strikethrough == 'noStrike') {
+ $objText->getFont()->setStrikethrough(false);
+ } else {
+ $objText->getFont()->setStrikethrough(true);
+ }
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ private static function readChartAttributes($chartDetail)
+ {
+ $plotAttributes = [];
+ if (isset($chartDetail->dLbls)) {
+ if (isset($chartDetail->dLbls->howLegendKey)) {
+ $plotAttributes['showLegendKey'] = self::getAttribute($chartDetail->dLbls->showLegendKey, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->showVal)) {
+ $plotAttributes['showVal'] = self::getAttribute($chartDetail->dLbls->showVal, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->showCatName)) {
+ $plotAttributes['showCatName'] = self::getAttribute($chartDetail->dLbls->showCatName, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->showSerName)) {
+ $plotAttributes['showSerName'] = self::getAttribute($chartDetail->dLbls->showSerName, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->showPercent)) {
+ $plotAttributes['showPercent'] = self::getAttribute($chartDetail->dLbls->showPercent, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->showBubbleSize)) {
+ $plotAttributes['showBubbleSize'] = self::getAttribute($chartDetail->dLbls->showBubbleSize, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->showLeaderLines)) {
+ $plotAttributes['showLeaderLines'] = self::getAttribute($chartDetail->dLbls->showLeaderLines, 'val', 'string');
+ }
+ }
+
+ return $plotAttributes;
+ }
+
+ /**
+ * @param mixed $plotAttributes
+ */
+ private static function setChartAttributes(Layout $plotArea, $plotAttributes): void
+ {
+ foreach ($plotAttributes as $plotAttributeKey => $plotAttributeValue) {
+ switch ($plotAttributeKey) {
+ case 'showLegendKey':
+ $plotArea->setShowLegendKey($plotAttributeValue);
+
+ break;
+ case 'showVal':
+ $plotArea->setShowVal($plotAttributeValue);
+
+ break;
+ case 'showCatName':
+ $plotArea->setShowCatName($plotAttributeValue);
+
+ break;
+ case 'showSerName':
+ $plotArea->setShowSerName($plotAttributeValue);
+
+ break;
+ case 'showPercent':
+ $plotArea->setShowPercent($plotAttributeValue);
+
+ break;
+ case 'showBubbleSize':
+ $plotArea->setShowBubbleSize($plotAttributeValue);
+
+ break;
+ case 'showLeaderLines':
+ $plotArea->setShowLeaderLines($plotAttributeValue);
+
+ break;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php
new file mode 100755
index 0000000..339c058
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php
@@ -0,0 +1,205 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use SimpleXMLElement;
+
+class ColumnAndRowAttributes extends BaseParserClass
+{
+ private $worksheet;
+
+ private $worksheetXml;
+
+ public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null)
+ {
+ $this->worksheet = $workSheet;
+ $this->worksheetXml = $worksheetXml;
+ }
+
+ /**
+ * Set Worksheet column attributes by attributes array passed.
+ *
+ * @param string $columnAddress A, B, ... DX, ...
+ * @param array $columnAttributes array of attributes (indexes are attribute name, values are value)
+ * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ?
+ */
+ private function setColumnAttributes($columnAddress, array $columnAttributes): void
+ {
+ if (isset($columnAttributes['xfIndex'])) {
+ $this->worksheet->getColumnDimension($columnAddress)->setXfIndex($columnAttributes['xfIndex']);
+ }
+ if (isset($columnAttributes['visible'])) {
+ $this->worksheet->getColumnDimension($columnAddress)->setVisible($columnAttributes['visible']);
+ }
+ if (isset($columnAttributes['collapsed'])) {
+ $this->worksheet->getColumnDimension($columnAddress)->setCollapsed($columnAttributes['collapsed']);
+ }
+ if (isset($columnAttributes['outlineLevel'])) {
+ $this->worksheet->getColumnDimension($columnAddress)->setOutlineLevel($columnAttributes['outlineLevel']);
+ }
+ if (isset($columnAttributes['width'])) {
+ $this->worksheet->getColumnDimension($columnAddress)->setWidth($columnAttributes['width']);
+ }
+ }
+
+ /**
+ * Set Worksheet row attributes by attributes array passed.
+ *
+ * @param int $rowNumber 1, 2, 3, ... 99, ...
+ * @param array $rowAttributes array of attributes (indexes are attribute name, values are value)
+ * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ?
+ */
+ private function setRowAttributes($rowNumber, array $rowAttributes): void
+ {
+ if (isset($rowAttributes['xfIndex'])) {
+ $this->worksheet->getRowDimension($rowNumber)->setXfIndex($rowAttributes['xfIndex']);
+ }
+ if (isset($rowAttributes['visible'])) {
+ $this->worksheet->getRowDimension($rowNumber)->setVisible($rowAttributes['visible']);
+ }
+ if (isset($rowAttributes['collapsed'])) {
+ $this->worksheet->getRowDimension($rowNumber)->setCollapsed($rowAttributes['collapsed']);
+ }
+ if (isset($rowAttributes['outlineLevel'])) {
+ $this->worksheet->getRowDimension($rowNumber)->setOutlineLevel($rowAttributes['outlineLevel']);
+ }
+ if (isset($rowAttributes['rowHeight'])) {
+ $this->worksheet->getRowDimension($rowNumber)->setRowHeight($rowAttributes['rowHeight']);
+ }
+ }
+
+ /**
+ * @param IReadFilter $readFilter
+ * @param bool $readDataOnly
+ */
+ public function load(?IReadFilter $readFilter = null, $readDataOnly = false): void
+ {
+ if ($this->worksheetXml === null) {
+ return;
+ }
+
+ $columnsAttributes = [];
+ $rowsAttributes = [];
+ if (isset($this->worksheetXml->cols)) {
+ $columnsAttributes = $this->readColumnAttributes($this->worksheetXml->cols, $readDataOnly);
+ }
+
+ if ($this->worksheetXml->sheetData && $this->worksheetXml->sheetData->row) {
+ $rowsAttributes = $this->readRowAttributes($this->worksheetXml->sheetData->row, $readDataOnly);
+ }
+
+ // set columns/rows attributes
+ $columnsAttributesAreSet = [];
+ foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) {
+ if ($readFilter === null ||
+ !$this->isFilteredColumn($readFilter, $columnCoordinate, $rowsAttributes)) {
+ if (!isset($columnsAttributesAreSet[$columnCoordinate])) {
+ $this->setColumnAttributes($columnCoordinate, $columnAttributes);
+ $columnsAttributesAreSet[$columnCoordinate] = true;
+ }
+ }
+ }
+
+ $rowsAttributesAreSet = [];
+ foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) {
+ if ($readFilter === null ||
+ !$this->isFilteredRow($readFilter, $rowCoordinate, $columnsAttributes)) {
+ if (!isset($rowsAttributesAreSet[$rowCoordinate])) {
+ $this->setRowAttributes($rowCoordinate, $rowAttributes);
+ $rowsAttributesAreSet[$rowCoordinate] = true;
+ }
+ }
+ }
+ }
+
+ private function isFilteredColumn(IReadFilter $readFilter, $columnCoordinate, array $rowsAttributes)
+ {
+ foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) {
+ if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private function readColumnAttributes(SimpleXMLElement $worksheetCols, $readDataOnly)
+ {
+ $columnAttributes = [];
+
+ foreach ($worksheetCols->col as $column) {
+ $startColumn = Coordinate::stringFromColumnIndex((int) $column['min']);
+ $endColumn = Coordinate::stringFromColumnIndex((int) $column['max']);
+ ++$endColumn;
+ for ($columnAddress = $startColumn; $columnAddress !== $endColumn; ++$columnAddress) {
+ $columnAttributes[$columnAddress] = $this->readColumnRangeAttributes($column, $readDataOnly);
+
+ if ((int) ($column['max']) == 16384) {
+ break;
+ }
+ }
+ }
+
+ return $columnAttributes;
+ }
+
+ private function readColumnRangeAttributes(SimpleXMLElement $column, $readDataOnly)
+ {
+ $columnAttributes = [];
+
+ if ($column['style'] && !$readDataOnly) {
+ $columnAttributes['xfIndex'] = (int) $column['style'];
+ }
+ if (self::boolean($column['hidden'])) {
+ $columnAttributes['visible'] = false;
+ }
+ if (self::boolean($column['collapsed'])) {
+ $columnAttributes['collapsed'] = true;
+ }
+ if (((int) $column['outlineLevel']) > 0) {
+ $columnAttributes['outlineLevel'] = (int) $column['outlineLevel'];
+ }
+ $columnAttributes['width'] = (float) $column['width'];
+
+ return $columnAttributes;
+ }
+
+ private function isFilteredRow(IReadFilter $readFilter, $rowCoordinate, array $columnsAttributes)
+ {
+ foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) {
+ if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private function readRowAttributes(SimpleXMLElement $worksheetRow, $readDataOnly)
+ {
+ $rowAttributes = [];
+
+ foreach ($worksheetRow as $row) {
+ if ($row['ht'] && !$readDataOnly) {
+ $rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht'];
+ }
+ if (self::boolean($row['hidden'])) {
+ $rowAttributes[(int) $row['r']]['visible'] = false;
+ }
+ if (self::boolean($row['collapsed'])) {
+ $rowAttributes[(int) $row['r']]['collapsed'] = true;
+ }
+ if ((int) $row['outlineLevel'] > 0) {
+ $rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel'];
+ }
+ if ($row['s'] && !$readDataOnly) {
+ $rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s'];
+ }
+ }
+
+ return $rowAttributes;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php
new file mode 100755
index 0000000..668bfd1
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use SimpleXMLElement;
+
+class ConditionalStyles
+{
+ private $worksheet;
+
+ private $worksheetXml;
+
+ private $dxfs;
+
+ public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml, array $dxfs = [])
+ {
+ $this->worksheet = $workSheet;
+ $this->worksheetXml = $worksheetXml;
+ $this->dxfs = $dxfs;
+ }
+
+ public function load(): void
+ {
+ $this->setConditionalStyles(
+ $this->worksheet,
+ $this->readConditionalStyles($this->worksheetXml)
+ );
+ }
+
+ private function readConditionalStyles($xmlSheet)
+ {
+ $conditionals = [];
+ foreach ($xmlSheet->conditionalFormatting as $conditional) {
+ foreach ($conditional->cfRule as $cfRule) {
+ if (((string) $cfRule['type'] == Conditional::CONDITION_NONE
+ || (string) $cfRule['type'] == Conditional::CONDITION_CELLIS
+ || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSTEXT
+ || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSBLANKS
+ || (string) $cfRule['type'] == Conditional::CONDITION_NOTCONTAINSBLANKS
+ || (string) $cfRule['type'] == Conditional::CONDITION_EXPRESSION)
+ && isset($this->dxfs[(int) ($cfRule['dxfId'])])) {
+ $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule;
+ }
+ }
+ }
+
+ return $conditionals;
+ }
+
+ private function setConditionalStyles(Worksheet $worksheet, array $conditionals): void
+ {
+ foreach ($conditionals as $ref => $cfRules) {
+ ksort($cfRules);
+ $conditionalStyles = $this->readStyleRules($cfRules);
+
+ // Extract all cell references in $ref
+ $cellBlocks = explode(' ', str_replace('$', '', strtoupper($ref)));
+ foreach ($cellBlocks as $cellBlock) {
+ $worksheet->getStyle($cellBlock)->setConditionalStyles($conditionalStyles);
+ }
+ }
+ }
+
+ private function readStyleRules($cfRules)
+ {
+ $conditionalStyles = [];
+ foreach ($cfRules as $cfRule) {
+ $objConditional = new Conditional();
+ $objConditional->setConditionType((string) $cfRule['type']);
+ $objConditional->setOperatorType((string) $cfRule['operator']);
+
+ if ((string) $cfRule['text'] != '') {
+ $objConditional->setText((string) $cfRule['text']);
+ }
+
+ if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) {
+ $objConditional->setStopIfTrue(true);
+ }
+
+ if (count($cfRule->formula) > 1) {
+ foreach ($cfRule->formula as $formula) {
+ $objConditional->addCondition((string) $formula);
+ }
+ } else {
+ $objConditional->addCondition((string) $cfRule->formula);
+ }
+ $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]);
+ $conditionalStyles[] = $objConditional;
+ }
+
+ return $conditionalStyles;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php
new file mode 100755
index 0000000..41a8c9f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use SimpleXMLElement;
+
+class DataValidations
+{
+ private $worksheet;
+
+ private $worksheetXml;
+
+ public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml)
+ {
+ $this->worksheet = $workSheet;
+ $this->worksheetXml = $worksheetXml;
+ }
+
+ public function load(): void
+ {
+ foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) {
+ // Uppercase coordinate
+ $range = strtoupper($dataValidation['sqref']);
+ $rangeSet = explode(' ', $range);
+ foreach ($rangeSet as $range) {
+ $stRange = $this->worksheet->shrinkRangeToFit($range);
+
+ // Extract all cell references in $range
+ foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $reference) {
+ // Create validation
+ $docValidation = $this->worksheet->getCell($reference)->getDataValidation();
+ $docValidation->setType((string) $dataValidation['type']);
+ $docValidation->setErrorStyle((string) $dataValidation['errorStyle']);
+ $docValidation->setOperator((string) $dataValidation['operator']);
+ $docValidation->setAllowBlank($dataValidation['allowBlank'] != 0);
+ $docValidation->setShowDropDown($dataValidation['showDropDown'] == 0);
+ $docValidation->setShowInputMessage($dataValidation['showInputMessage'] != 0);
+ $docValidation->setShowErrorMessage($dataValidation['showErrorMessage'] != 0);
+ $docValidation->setErrorTitle((string) $dataValidation['errorTitle']);
+ $docValidation->setError((string) $dataValidation['error']);
+ $docValidation->setPromptTitle((string) $dataValidation['promptTitle']);
+ $docValidation->setPrompt((string) $dataValidation['prompt']);
+ $docValidation->setFormula1((string) $dataValidation->formula1);
+ $docValidation->setFormula2((string) $dataValidation->formula2);
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php
new file mode 100755
index 0000000..9e6aeaf
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use SimpleXMLElement;
+
+class Hyperlinks
+{
+ private $worksheet;
+
+ private $hyperlinks = [];
+
+ public function __construct(Worksheet $workSheet)
+ {
+ $this->worksheet = $workSheet;
+ }
+
+ public function readHyperlinks(SimpleXMLElement $relsWorksheet): void
+ {
+ foreach ($relsWorksheet->Relationship as $element) {
+ if ($element['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') {
+ $this->hyperlinks[(string) $element['Id']] = (string) $element['Target'];
+ }
+ }
+ }
+
+ public function setHyperlinks(SimpleXMLElement $worksheetXml): void
+ {
+ foreach ($worksheetXml->hyperlink as $hyperlink) {
+ $this->setHyperlink($hyperlink, $this->worksheet);
+ }
+ }
+
+ private function setHyperlink(SimpleXMLElement $hyperlink, Worksheet $worksheet): void
+ {
+ // Link url
+ $linkRel = $hyperlink->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+
+ foreach (Coordinate::extractAllCellReferencesInRange($hyperlink['ref']) as $cellReference) {
+ $cell = $worksheet->getCell($cellReference);
+ if (isset($linkRel['id'])) {
+ $hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']];
+ if (isset($hyperlink['location'])) {
+ $hyperlinkUrl .= '#' . (string) $hyperlink['location'];
+ }
+ $cell->getHyperlink()->setUrl($hyperlinkUrl);
+ } elseif (isset($hyperlink['location'])) {
+ $cell->getHyperlink()->setUrl('sheet://' . (string) $hyperlink['location']);
+ }
+
+ // Tooltip
+ if (isset($hyperlink['tooltip'])) {
+ $cell->getHyperlink()->setTooltip((string) $hyperlink['tooltip']);
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php
new file mode 100755
index 0000000..bfb7a1f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use SimpleXMLElement;
+
+class PageSetup extends BaseParserClass
+{
+ private $worksheet;
+
+ private $worksheetXml;
+
+ public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null)
+ {
+ $this->worksheet = $workSheet;
+ $this->worksheetXml = $worksheetXml;
+ }
+
+ public function load(array $unparsedLoadedData)
+ {
+ if (!$this->worksheetXml) {
+ return $unparsedLoadedData;
+ }
+
+ $this->margins($this->worksheetXml, $this->worksheet);
+ $unparsedLoadedData = $this->pageSetup($this->worksheetXml, $this->worksheet, $unparsedLoadedData);
+ $this->headerFooter($this->worksheetXml, $this->worksheet);
+ $this->pageBreaks($this->worksheetXml, $this->worksheet);
+
+ return $unparsedLoadedData;
+ }
+
+ private function margins(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ if ($xmlSheet->pageMargins) {
+ $docPageMargins = $worksheet->getPageMargins();
+ $docPageMargins->setLeft((float) ($xmlSheet->pageMargins['left']));
+ $docPageMargins->setRight((float) ($xmlSheet->pageMargins['right']));
+ $docPageMargins->setTop((float) ($xmlSheet->pageMargins['top']));
+ $docPageMargins->setBottom((float) ($xmlSheet->pageMargins['bottom']));
+ $docPageMargins->setHeader((float) ($xmlSheet->pageMargins['header']));
+ $docPageMargins->setFooter((float) ($xmlSheet->pageMargins['footer']));
+ }
+ }
+
+ private function pageSetup(SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData)
+ {
+ if ($xmlSheet->pageSetup) {
+ $docPageSetup = $worksheet->getPageSetup();
+
+ if (isset($xmlSheet->pageSetup['orientation'])) {
+ $docPageSetup->setOrientation((string) $xmlSheet->pageSetup['orientation']);
+ }
+ if (isset($xmlSheet->pageSetup['paperSize'])) {
+ $docPageSetup->setPaperSize((int) ($xmlSheet->pageSetup['paperSize']));
+ }
+ if (isset($xmlSheet->pageSetup['scale'])) {
+ $docPageSetup->setScale((int) ($xmlSheet->pageSetup['scale']), false);
+ }
+ if (isset($xmlSheet->pageSetup['fitToHeight']) && (int) ($xmlSheet->pageSetup['fitToHeight']) >= 0) {
+ $docPageSetup->setFitToHeight((int) ($xmlSheet->pageSetup['fitToHeight']), false);
+ }
+ if (isset($xmlSheet->pageSetup['fitToWidth']) && (int) ($xmlSheet->pageSetup['fitToWidth']) >= 0) {
+ $docPageSetup->setFitToWidth((int) ($xmlSheet->pageSetup['fitToWidth']), false);
+ }
+ if (isset($xmlSheet->pageSetup['firstPageNumber'], $xmlSheet->pageSetup['useFirstPageNumber']) &&
+ self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber'])) {
+ $docPageSetup->setFirstPageNumber((int) ($xmlSheet->pageSetup['firstPageNumber']));
+ }
+
+ $relAttributes = $xmlSheet->pageSetup->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+ if (isset($relAttributes['id'])) {
+ $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id'];
+ }
+ }
+
+ return $unparsedLoadedData;
+ }
+
+ private function headerFooter(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ if ($xmlSheet->headerFooter) {
+ $docHeaderFooter = $worksheet->getHeaderFooter();
+
+ if (isset($xmlSheet->headerFooter['differentOddEven']) &&
+ self::boolean((string) $xmlSheet->headerFooter['differentOddEven'])) {
+ $docHeaderFooter->setDifferentOddEven(true);
+ } else {
+ $docHeaderFooter->setDifferentOddEven(false);
+ }
+ if (isset($xmlSheet->headerFooter['differentFirst']) &&
+ self::boolean((string) $xmlSheet->headerFooter['differentFirst'])) {
+ $docHeaderFooter->setDifferentFirst(true);
+ } else {
+ $docHeaderFooter->setDifferentFirst(false);
+ }
+ if (isset($xmlSheet->headerFooter['scaleWithDoc']) &&
+ !self::boolean((string) $xmlSheet->headerFooter['scaleWithDoc'])) {
+ $docHeaderFooter->setScaleWithDocument(false);
+ } else {
+ $docHeaderFooter->setScaleWithDocument(true);
+ }
+ if (isset($xmlSheet->headerFooter['alignWithMargins']) &&
+ !self::boolean((string) $xmlSheet->headerFooter['alignWithMargins'])) {
+ $docHeaderFooter->setAlignWithMargins(false);
+ } else {
+ $docHeaderFooter->setAlignWithMargins(true);
+ }
+
+ $docHeaderFooter->setOddHeader((string) $xmlSheet->headerFooter->oddHeader);
+ $docHeaderFooter->setOddFooter((string) $xmlSheet->headerFooter->oddFooter);
+ $docHeaderFooter->setEvenHeader((string) $xmlSheet->headerFooter->evenHeader);
+ $docHeaderFooter->setEvenFooter((string) $xmlSheet->headerFooter->evenFooter);
+ $docHeaderFooter->setFirstHeader((string) $xmlSheet->headerFooter->firstHeader);
+ $docHeaderFooter->setFirstFooter((string) $xmlSheet->headerFooter->firstFooter);
+ }
+ }
+
+ private function pageBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ if ($xmlSheet->rowBreaks && $xmlSheet->rowBreaks->brk) {
+ $this->rowBreaks($xmlSheet, $worksheet);
+ }
+ if ($xmlSheet->colBreaks && $xmlSheet->colBreaks->brk) {
+ $this->columnBreaks($xmlSheet, $worksheet);
+ }
+ }
+
+ private function rowBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ foreach ($xmlSheet->rowBreaks->brk as $brk) {
+ if ($brk['man']) {
+ $worksheet->setBreak("A{$brk['id']}", Worksheet::BREAK_ROW);
+ }
+ }
+ }
+
+ private function columnBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ foreach ($xmlSheet->colBreaks->brk as $brk) {
+ if ($brk['man']) {
+ $worksheet->setBreak(
+ Coordinate::stringFromColumnIndex(((int) $brk['id']) + 1) . '1',
+ Worksheet::BREAK_COLUMN
+ );
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php
new file mode 100755
index 0000000..b6f3c61
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Document\Properties as DocumentProperties;
+use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
+use PhpOffice\PhpSpreadsheet\Settings;
+use SimpleXMLElement;
+
+class Properties
+{
+ private $securityScanner;
+
+ private $docProps;
+
+ public function __construct(XmlScanner $securityScanner, DocumentProperties $docProps)
+ {
+ $this->securityScanner = $securityScanner;
+ $this->docProps = $docProps;
+ }
+
+ private function extractPropertyData($propertyData)
+ {
+ return simplexml_load_string(
+ $this->securityScanner->scan($propertyData),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ }
+
+ public function readCoreProperties($propertyData): void
+ {
+ $xmlCore = $this->extractPropertyData($propertyData);
+
+ if (is_object($xmlCore)) {
+ $xmlCore->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/');
+ $xmlCore->registerXPathNamespace('dcterms', 'http://purl.org/dc/terms/');
+ $xmlCore->registerXPathNamespace('cp', 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties');
+
+ $this->docProps->setCreator((string) self::getArrayItem($xmlCore->xpath('dc:creator')));
+ $this->docProps->setLastModifiedBy((string) self::getArrayItem($xmlCore->xpath('cp:lastModifiedBy')));
+ $this->docProps->setCreated(strtotime(self::getArrayItem($xmlCore->xpath('dcterms:created')))); //! respect xsi:type
+ $this->docProps->setModified(strtotime(self::getArrayItem($xmlCore->xpath('dcterms:modified')))); //! respect xsi:type
+ $this->docProps->setTitle((string) self::getArrayItem($xmlCore->xpath('dc:title')));
+ $this->docProps->setDescription((string) self::getArrayItem($xmlCore->xpath('dc:description')));
+ $this->docProps->setSubject((string) self::getArrayItem($xmlCore->xpath('dc:subject')));
+ $this->docProps->setKeywords((string) self::getArrayItem($xmlCore->xpath('cp:keywords')));
+ $this->docProps->setCategory((string) self::getArrayItem($xmlCore->xpath('cp:category')));
+ }
+ }
+
+ public function readExtendedProperties($propertyData): void
+ {
+ $xmlCore = $this->extractPropertyData($propertyData);
+
+ if (is_object($xmlCore)) {
+ if (isset($xmlCore->Company)) {
+ $this->docProps->setCompany((string) $xmlCore->Company);
+ }
+ if (isset($xmlCore->Manager)) {
+ $this->docProps->setManager((string) $xmlCore->Manager);
+ }
+ }
+ }
+
+ public function readCustomProperties($propertyData): void
+ {
+ $xmlCore = $this->extractPropertyData($propertyData);
+
+ if (is_object($xmlCore)) {
+ foreach ($xmlCore as $xmlProperty) {
+ /** @var SimpleXMLElement $xmlProperty */
+ $cellDataOfficeAttributes = $xmlProperty->attributes();
+ if (isset($cellDataOfficeAttributes['name'])) {
+ $propertyName = (string) $cellDataOfficeAttributes['name'];
+ $cellDataOfficeChildren = $xmlProperty->children('http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes');
+
+ $attributeType = $cellDataOfficeChildren->getName();
+ $attributeValue = (string) $cellDataOfficeChildren->{$attributeType};
+ $attributeValue = DocumentProperties::convertProperty($attributeValue, $attributeType);
+ $attributeType = DocumentProperties::convertPropertyType($attributeType);
+ $this->docProps->setCustomProperty($propertyName, $attributeValue, $attributeType);
+ }
+ }
+ }
+ }
+
+ private static function getArrayItem(array $array, $key = 0)
+ {
+ return $array[$key] ?? null;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php
new file mode 100755
index 0000000..b3bee89
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php
@@ -0,0 +1,125 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use SimpleXMLElement;
+
+class SheetViewOptions extends BaseParserClass
+{
+ private $worksheet;
+
+ private $worksheetXml;
+
+ public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null)
+ {
+ $this->worksheet = $workSheet;
+ $this->worksheetXml = $worksheetXml;
+ }
+
+ /**
+ * @param bool $readDataOnly
+ */
+ public function load($readDataOnly = false): void
+ {
+ if ($this->worksheetXml === null) {
+ return;
+ }
+
+ if (isset($this->worksheetXml->sheetPr)) {
+ $this->tabColor($this->worksheetXml->sheetPr);
+ $this->codeName($this->worksheetXml->sheetPr);
+ $this->outlines($this->worksheetXml->sheetPr);
+ $this->pageSetup($this->worksheetXml->sheetPr);
+ }
+
+ if (isset($this->worksheetXml->sheetFormatPr)) {
+ $this->sheetFormat($this->worksheetXml->sheetFormatPr);
+ }
+
+ if (!$readDataOnly && isset($this->worksheetXml->printOptions)) {
+ $this->printOptions($this->worksheetXml->printOptions);
+ }
+ }
+
+ private function tabColor(SimpleXMLElement $sheetPr): void
+ {
+ if (isset($sheetPr->tabColor, $sheetPr->tabColor['rgb'])) {
+ $this->worksheet->getTabColor()->setARGB((string) $sheetPr->tabColor['rgb']);
+ }
+ }
+
+ private function codeName(SimpleXMLElement $sheetPr): void
+ {
+ if (isset($sheetPr['codeName'])) {
+ $this->worksheet->setCodeName((string) $sheetPr['codeName'], false);
+ }
+ }
+
+ private function outlines(SimpleXMLElement $sheetPr): void
+ {
+ if (isset($sheetPr->outlinePr)) {
+ if (isset($sheetPr->outlinePr['summaryRight']) &&
+ !self::boolean((string) $sheetPr->outlinePr['summaryRight'])) {
+ $this->worksheet->setShowSummaryRight(false);
+ } else {
+ $this->worksheet->setShowSummaryRight(true);
+ }
+
+ if (isset($sheetPr->outlinePr['summaryBelow']) &&
+ !self::boolean((string) $sheetPr->outlinePr['summaryBelow'])) {
+ $this->worksheet->setShowSummaryBelow(false);
+ } else {
+ $this->worksheet->setShowSummaryBelow(true);
+ }
+ }
+ }
+
+ private function pageSetup(SimpleXMLElement $sheetPr): void
+ {
+ if (isset($sheetPr->pageSetUpPr)) {
+ if (isset($sheetPr->pageSetUpPr['fitToPage']) &&
+ !self::boolean((string) $sheetPr->pageSetUpPr['fitToPage'])) {
+ $this->worksheet->getPageSetup()->setFitToPage(false);
+ } else {
+ $this->worksheet->getPageSetup()->setFitToPage(true);
+ }
+ }
+ }
+
+ private function sheetFormat(SimpleXMLElement $sheetFormatPr): void
+ {
+ if (isset($sheetFormatPr['customHeight']) &&
+ self::boolean((string) $sheetFormatPr['customHeight']) &&
+ isset($sheetFormatPr['defaultRowHeight'])) {
+ $this->worksheet->getDefaultRowDimension()
+ ->setRowHeight((float) $sheetFormatPr['defaultRowHeight']);
+ }
+
+ if (isset($sheetFormatPr['defaultColWidth'])) {
+ $this->worksheet->getDefaultColumnDimension()
+ ->setWidth((float) $sheetFormatPr['defaultColWidth']);
+ }
+
+ if (isset($sheetFormatPr['zeroHeight']) &&
+ ((string) $sheetFormatPr['zeroHeight'] === '1')) {
+ $this->worksheet->getDefaultRowDimension()->setZeroHeight(true);
+ }
+ }
+
+ private function printOptions(SimpleXMLElement $printOptions): void
+ {
+ if (self::boolean((string) $printOptions['gridLinesSet'])) {
+ $this->worksheet->setShowGridlines(true);
+ }
+ if (self::boolean((string) $printOptions['gridLines'])) {
+ $this->worksheet->setPrintGridlines(true);
+ }
+ if (self::boolean((string) $printOptions['horizontalCentered'])) {
+ $this->worksheet->getPageSetup()->setHorizontalCentered(true);
+ }
+ if (self::boolean((string) $printOptions['verticalCentered'])) {
+ $this->worksheet->getPageSetup()->setVerticalCentered(true);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php
new file mode 100755
index 0000000..f6c4792
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use SimpleXMLElement;
+
+class SheetViews extends BaseParserClass
+{
+ private $sheetViewXml;
+
+ private $worksheet;
+
+ public function __construct(SimpleXMLElement $sheetViewXml, Worksheet $workSheet)
+ {
+ $this->sheetViewXml = $sheetViewXml;
+ $this->worksheet = $workSheet;
+ }
+
+ public function load(): void
+ {
+ $this->zoomScale();
+ $this->view();
+ $this->gridLines();
+ $this->headers();
+ $this->direction();
+ $this->showZeros();
+
+ if (isset($this->sheetViewXml->pane)) {
+ $this->pane();
+ }
+ if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection['sqref'])) {
+ $this->selection();
+ }
+ }
+
+ private function zoomScale(): void
+ {
+ if (isset($this->sheetViewXml['zoomScale'])) {
+ $zoomScale = (int) ($this->sheetViewXml['zoomScale']);
+ if ($zoomScale <= 0) {
+ // setZoomScale will throw an Exception if the scale is less than or equals 0
+ // that is OK when manually creating documents, but we should be able to read all documents
+ $zoomScale = 100;
+ }
+
+ $this->worksheet->getSheetView()->setZoomScale($zoomScale);
+ }
+
+ if (isset($this->sheetViewXml['zoomScaleNormal'])) {
+ $zoomScaleNormal = (int) ($this->sheetViewXml['zoomScaleNormal']);
+ if ($zoomScaleNormal <= 0) {
+ // setZoomScaleNormal will throw an Exception if the scale is less than or equals 0
+ // that is OK when manually creating documents, but we should be able to read all documents
+ $zoomScaleNormal = 100;
+ }
+
+ $this->worksheet->getSheetView()->setZoomScaleNormal($zoomScaleNormal);
+ }
+ }
+
+ private function view(): void
+ {
+ if (isset($this->sheetViewXml['view'])) {
+ $this->worksheet->getSheetView()->setView((string) $this->sheetViewXml['view']);
+ }
+ }
+
+ private function gridLines(): void
+ {
+ if (isset($this->sheetViewXml['showGridLines'])) {
+ $this->worksheet->setShowGridLines(
+ self::boolean((string) $this->sheetViewXml['showGridLines'])
+ );
+ }
+ }
+
+ private function headers(): void
+ {
+ if (isset($this->sheetViewXml['showRowColHeaders'])) {
+ $this->worksheet->setShowRowColHeaders(
+ self::boolean((string) $this->sheetViewXml['showRowColHeaders'])
+ );
+ }
+ }
+
+ private function direction(): void
+ {
+ if (isset($this->sheetViewXml['rightToLeft'])) {
+ $this->worksheet->setRightToLeft(
+ self::boolean((string) $this->sheetViewXml['rightToLeft'])
+ );
+ }
+ }
+
+ private function showZeros(): void
+ {
+ if (isset($this->sheetViewXml['showZeros'])) {
+ $this->worksheet->getSheetView()->setShowZeros(
+ self::boolean((string) $this->sheetViewXml['showZeros'])
+ );
+ }
+ }
+
+ private function pane(): void
+ {
+ $xSplit = 0;
+ $ySplit = 0;
+ $topLeftCell = null;
+
+ if (isset($this->sheetViewXml->pane['xSplit'])) {
+ $xSplit = (int) ($this->sheetViewXml->pane['xSplit']);
+ }
+
+ if (isset($this->sheetViewXml->pane['ySplit'])) {
+ $ySplit = (int) ($this->sheetViewXml->pane['ySplit']);
+ }
+
+ if (isset($this->sheetViewXml->pane['topLeftCell'])) {
+ $topLeftCell = (string) $this->sheetViewXml->pane['topLeftCell'];
+ }
+
+ $this->worksheet->freezePane(
+ Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1),
+ $topLeftCell
+ );
+ }
+
+ private function selection(): void
+ {
+ $sqref = (string) $this->sheetViewXml->selection['sqref'];
+ $sqref = explode(' ', $sqref);
+ $sqref = $sqref[0];
+
+ $this->worksheet->setSelectedCells($sqref);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php
new file mode 100755
index 0000000..43de878
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php
@@ -0,0 +1,282 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Borders;
+use PhpOffice\PhpSpreadsheet\Style\Color;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PhpOffice\PhpSpreadsheet\Style\Protection;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+use SimpleXMLElement;
+
+class Styles extends BaseParserClass
+{
+ /**
+ * Theme instance.
+ *
+ * @var Theme
+ */
+ private static $theme = null;
+
+ private $styles = [];
+
+ private $cellStyles = [];
+
+ private $styleXml;
+
+ public function __construct(SimpleXMLElement $styleXml)
+ {
+ $this->styleXml = $styleXml;
+ }
+
+ public function setStyleBaseData(?Theme $theme = null, $styles = [], $cellStyles = []): void
+ {
+ self::$theme = $theme;
+ $this->styles = $styles;
+ $this->cellStyles = $cellStyles;
+ }
+
+ private static function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml): void
+ {
+ $fontStyle->setName((string) $fontStyleXml->name['val']);
+ $fontStyle->setSize((float) $fontStyleXml->sz['val']);
+
+ if (isset($fontStyleXml->b)) {
+ $fontStyle->setBold(!isset($fontStyleXml->b['val']) || self::boolean((string) $fontStyleXml->b['val']));
+ }
+ if (isset($fontStyleXml->i)) {
+ $fontStyle->setItalic(!isset($fontStyleXml->i['val']) || self::boolean((string) $fontStyleXml->i['val']));
+ }
+ if (isset($fontStyleXml->strike)) {
+ $fontStyle->setStrikethrough(!isset($fontStyleXml->strike['val']) || self::boolean((string) $fontStyleXml->strike['val']));
+ }
+ $fontStyle->getColor()->setARGB(self::readColor($fontStyleXml->color));
+
+ if (isset($fontStyleXml->u) && !isset($fontStyleXml->u['val'])) {
+ $fontStyle->setUnderline(Font::UNDERLINE_SINGLE);
+ } elseif (isset($fontStyleXml->u, $fontStyleXml->u['val'])) {
+ $fontStyle->setUnderline((string) $fontStyleXml->u['val']);
+ }
+
+ if (isset($fontStyleXml->vertAlign, $fontStyleXml->vertAlign['val'])) {
+ $verticalAlign = strtolower((string) $fontStyleXml->vertAlign['val']);
+ if ($verticalAlign === 'superscript') {
+ $fontStyle->setSuperscript(true);
+ }
+ if ($verticalAlign === 'subscript') {
+ $fontStyle->setSubscript(true);
+ }
+ }
+ }
+
+ private static function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void
+ {
+ if ($numfmtStyleXml->count() === 0) {
+ return;
+ }
+ $numfmt = $numfmtStyleXml->attributes();
+ if ($numfmt->count() > 0 && isset($numfmt['formatCode'])) {
+ $numfmtStyle->setFormatCode((string) $numfmt['formatCode']);
+ }
+ }
+
+ private static function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyleXml): void
+ {
+ if ($fillStyleXml->gradientFill) {
+ /** @var SimpleXMLElement $gradientFill */
+ $gradientFill = $fillStyleXml->gradientFill[0];
+ if (!empty($gradientFill['type'])) {
+ $fillStyle->setFillType((string) $gradientFill['type']);
+ }
+ $fillStyle->setRotation((float) ($gradientFill['degree']));
+ $gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+ $fillStyle->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color));
+ $fillStyle->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color));
+ } elseif ($fillStyleXml->patternFill) {
+ $patternType = (string) $fillStyleXml->patternFill['patternType'] != '' ? (string) $fillStyleXml->patternFill['patternType'] : 'solid';
+ $fillStyle->setFillType($patternType);
+ if ($fillStyleXml->patternFill->fgColor) {
+ $fillStyle->getStartColor()->setARGB(self::readColor($fillStyleXml->patternFill->fgColor, true));
+ } else {
+ $fillStyle->getStartColor()->setARGB('FF000000');
+ }
+ if ($fillStyleXml->patternFill->bgColor) {
+ $fillStyle->getEndColor()->setARGB(self::readColor($fillStyleXml->patternFill->bgColor, true));
+ }
+ }
+ }
+
+ private static function readBorderStyle(Borders $borderStyle, SimpleXMLElement $borderStyleXml): void
+ {
+ $diagonalUp = self::boolean((string) $borderStyleXml['diagonalUp']);
+ $diagonalDown = self::boolean((string) $borderStyleXml['diagonalDown']);
+ if (!$diagonalUp && !$diagonalDown) {
+ $borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE);
+ } elseif ($diagonalUp && !$diagonalDown) {
+ $borderStyle->setDiagonalDirection(Borders::DIAGONAL_UP);
+ } elseif (!$diagonalUp && $diagonalDown) {
+ $borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN);
+ } else {
+ $borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH);
+ }
+
+ self::readBorder($borderStyle->getLeft(), $borderStyleXml->left);
+ self::readBorder($borderStyle->getRight(), $borderStyleXml->right);
+ self::readBorder($borderStyle->getTop(), $borderStyleXml->top);
+ self::readBorder($borderStyle->getBottom(), $borderStyleXml->bottom);
+ self::readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal);
+ }
+
+ private static function readBorder(Border $border, SimpleXMLElement $borderXml): void
+ {
+ if (isset($borderXml['style'])) {
+ $border->setBorderStyle((string) $borderXml['style']);
+ }
+ if (isset($borderXml->color)) {
+ $border->getColor()->setARGB(self::readColor($borderXml->color));
+ }
+ }
+
+ private static function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void
+ {
+ $alignment->setHorizontal((string) $alignmentXml->alignment['horizontal']);
+ $alignment->setVertical((string) $alignmentXml->alignment['vertical']);
+
+ $textRotation = 0;
+ if ((int) $alignmentXml->alignment['textRotation'] <= 90) {
+ $textRotation = (int) $alignmentXml->alignment['textRotation'];
+ } elseif ((int) $alignmentXml->alignment['textRotation'] > 90) {
+ $textRotation = 90 - (int) $alignmentXml->alignment['textRotation'];
+ }
+
+ $alignment->setTextRotation((int) $textRotation);
+ $alignment->setWrapText(self::boolean((string) $alignmentXml->alignment['wrapText']));
+ $alignment->setShrinkToFit(self::boolean((string) $alignmentXml->alignment['shrinkToFit']));
+ $alignment->setIndent((int) ((string) $alignmentXml->alignment['indent']) > 0 ? (int) ((string) $alignmentXml->alignment['indent']) : 0);
+ $alignment->setReadOrder((int) ((string) $alignmentXml->alignment['readingOrder']) > 0 ? (int) ((string) $alignmentXml->alignment['readingOrder']) : 0);
+ }
+
+ private function readStyle(Style $docStyle, $style): void
+ {
+ if ($style->numFmt instanceof SimpleXMLElement) {
+ self::readNumberFormat($docStyle->getNumberFormat(), $style->numFmt);
+ } else {
+ $docStyle->getNumberFormat()->setFormatCode($style->numFmt);
+ }
+
+ if (isset($style->font)) {
+ self::readFontStyle($docStyle->getFont(), $style->font);
+ }
+
+ if (isset($style->fill)) {
+ self::readFillStyle($docStyle->getFill(), $style->fill);
+ }
+
+ if (isset($style->border)) {
+ self::readBorderStyle($docStyle->getBorders(), $style->border);
+ }
+
+ if (isset($style->alignment->alignment)) {
+ self::readAlignmentStyle($docStyle->getAlignment(), $style->alignment);
+ }
+
+ // protection
+ if (isset($style->protection)) {
+ $this->readProtectionLocked($docStyle, $style);
+ $this->readProtectionHidden($docStyle, $style);
+ }
+
+ // top-level style settings
+ if (isset($style->quotePrefix)) {
+ $docStyle->setQuotePrefix(true);
+ }
+ }
+
+ private function readProtectionLocked(Style $docStyle, $style): void
+ {
+ if (isset($style->protection['locked'])) {
+ if (self::boolean((string) $style->protection['locked'])) {
+ $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED);
+ } else {
+ $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED);
+ }
+ }
+ }
+
+ private function readProtectionHidden(Style $docStyle, $style): void
+ {
+ if (isset($style->protection['hidden'])) {
+ if (self::boolean((string) $style->protection['hidden'])) {
+ $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED);
+ } else {
+ $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED);
+ }
+ }
+ }
+
+ private static function readColor($color, $background = false)
+ {
+ if (isset($color['rgb'])) {
+ return (string) $color['rgb'];
+ } elseif (isset($color['indexed'])) {
+ return Color::indexedColor($color['indexed'] - 7, $background)->getARGB();
+ } elseif (isset($color['theme'])) {
+ if (self::$theme !== null) {
+ $returnColour = self::$theme->getColourByIndex((int) $color['theme']);
+ if (isset($color['tint'])) {
+ $tintAdjust = (float) $color['tint'];
+ $returnColour = Color::changeBrightness($returnColour, $tintAdjust);
+ }
+
+ return 'FF' . $returnColour;
+ }
+ }
+
+ return ($background) ? 'FFFFFFFF' : 'FF000000';
+ }
+
+ public function dxfs($readDataOnly = false)
+ {
+ $dxfs = [];
+ if (!$readDataOnly && $this->styleXml) {
+ // Conditional Styles
+ if ($this->styleXml->dxfs) {
+ foreach ($this->styleXml->dxfs->dxf as $dxf) {
+ $style = new Style(false, true);
+ $this->readStyle($style, $dxf);
+ $dxfs[] = $style;
+ }
+ }
+ // Cell Styles
+ if ($this->styleXml->cellStyles) {
+ foreach ($this->styleXml->cellStyles->cellStyle as $cellStyle) {
+ if ((int) ($cellStyle['builtinId']) == 0) {
+ if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) {
+ // Set default style
+ $style = new Style();
+ $this->readStyle($style, $this->cellStyles[(int) ($cellStyle['xfId'])]);
+
+ // normal style, currently not using it for anything
+ }
+ }
+ }
+ }
+ }
+
+ return $dxfs;
+ }
+
+ public function styles()
+ {
+ return $this->styles;
+ }
+
+ private static function getArrayItem($array, $key = 0)
+ {
+ return $array[$key] ?? null;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php
new file mode 100755
index 0000000..c105f3c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
+
+class Theme
+{
+ /**
+ * Theme Name.
+ *
+ * @var string
+ */
+ private $themeName;
+
+ /**
+ * Colour Scheme Name.
+ *
+ * @var string
+ */
+ private $colourSchemeName;
+
+ /**
+ * Colour Map.
+ *
+ * @var array of string
+ */
+ private $colourMap;
+
+ /**
+ * Create a new Theme.
+ *
+ * @param mixed $themeName
+ * @param mixed $colourSchemeName
+ * @param mixed $colourMap
+ */
+ public function __construct($themeName, $colourSchemeName, $colourMap)
+ {
+ // Initialise values
+ $this->themeName = $themeName;
+ $this->colourSchemeName = $colourSchemeName;
+ $this->colourMap = $colourMap;
+ }
+
+ /**
+ * Get Theme Name.
+ *
+ * @return string
+ */
+ public function getThemeName()
+ {
+ return $this->themeName;
+ }
+
+ /**
+ * Get colour Scheme Name.
+ *
+ * @return string
+ */
+ public function getColourSchemeName()
+ {
+ return $this->colourSchemeName;
+ }
+
+ /**
+ * Get colour Map Value by Position.
+ *
+ * @param mixed $index
+ *
+ * @return string
+ */
+ public function getColourByIndex($index)
+ {
+ if (isset($this->colourMap[$index])) {
+ return $this->colourMap[$index];
+ }
+
+ return null;
+ }
+
+ /**
+ * 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)) && ($key != '_parent')) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php
new file mode 100755
index 0000000..15fb34a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php
@@ -0,0 +1,858 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Reader;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Document\Properties;
+use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Settings;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+use SimpleXMLElement;
+
+/**
+ * Reader for SpreadsheetML, the XML schema for Microsoft Office Excel 2003.
+ */
+class Xml extends BaseReader
+{
+ /**
+ * Formats.
+ *
+ * @var array
+ */
+ protected $styles = [];
+
+ /**
+ * Character set used in the file.
+ *
+ * @var string
+ */
+ protected $charSet = 'UTF-8';
+
+ /**
+ * Create a new Excel2003XML Reader instance.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->securityScanner = XmlScanner::getInstance($this);
+ }
+
+ /**
+ * Can the current IReader read the file?
+ *
+ * @param string $pFilename
+ *
+ * @return bool
+ */
+ public function canRead($pFilename)
+ {
+ // Office xmlns:o="urn:schemas-microsoft-com:office:office"
+ // Excel xmlns:x="urn:schemas-microsoft-com:office:excel"
+ // XML Spreadsheet xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
+ // Spreadsheet component xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet"
+ // XML schema xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
+ // XML data type xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
+ // MS-persist recordset xmlns:rs="urn:schemas-microsoft-com:rowset"
+ // Rowset xmlns:z="#RowsetSchema"
+ //
+
+ $signature = [
+ '<?xml version="1.0"',
+ '<?mso-application progid="Excel.Sheet"?>',
+ ];
+
+ // Open file
+ $this->openFile($pFilename);
+ $fileHandle = $this->fileHandle;
+
+ // Read sample data (first 2 KB will do)
+ $data = fread($fileHandle, 2048);
+ fclose($fileHandle);
+ $data = str_replace("'", '"', $data); // fix headers with single quote
+
+ $valid = true;
+ foreach ($signature as $match) {
+ // every part of the signature must be present
+ if (strpos($data, $match) === false) {
+ $valid = false;
+
+ break;
+ }
+ }
+
+ // Retrieve charset encoding
+ if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/um', $data, $matches)) {
+ $this->charSet = strtoupper($matches[1]);
+ }
+
+ return $valid;
+ }
+
+ /**
+ * Check if the file is a valid SimpleXML.
+ *
+ * @param string $pFilename
+ *
+ * @return false|SimpleXMLElement
+ */
+ public function trySimpleXMLLoadString($pFilename)
+ {
+ try {
+ $xml = simplexml_load_string(
+ $this->securityScanner->scan(file_get_contents($pFilename)),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ } catch (\Exception $e) {
+ throw new Exception('Cannot load invalid XML file: ' . $pFilename, 0, $e);
+ }
+
+ return $xml;
+ }
+
+ /**
+ * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
+ *
+ * @param string $pFilename
+ *
+ * @return array
+ */
+ public function listWorksheetNames($pFilename)
+ {
+ File::assertFile($pFilename);
+ if (!$this->canRead($pFilename)) {
+ throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
+ }
+
+ $worksheetNames = [];
+
+ $xml = $this->trySimpleXMLLoadString($pFilename);
+
+ $namespaces = $xml->getNamespaces(true);
+
+ $xml_ss = $xml->children($namespaces['ss']);
+ foreach ($xml_ss->Worksheet as $worksheet) {
+ $worksheet_ss = $worksheet->attributes($namespaces['ss']);
+ $worksheetNames[] = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet);
+ }
+
+ return $worksheetNames;
+ }
+
+ /**
+ * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
+ *
+ * @param string $pFilename
+ *
+ * @return array
+ */
+ public function listWorksheetInfo($pFilename)
+ {
+ File::assertFile($pFilename);
+
+ $worksheetInfo = [];
+
+ $xml = $this->trySimpleXMLLoadString($pFilename);
+
+ $namespaces = $xml->getNamespaces(true);
+
+ $worksheetID = 1;
+ $xml_ss = $xml->children($namespaces['ss']);
+ foreach ($xml_ss->Worksheet as $worksheet) {
+ $worksheet_ss = $worksheet->attributes($namespaces['ss']);
+
+ $tmpInfo = [];
+ $tmpInfo['worksheetName'] = '';
+ $tmpInfo['lastColumnLetter'] = 'A';
+ $tmpInfo['lastColumnIndex'] = 0;
+ $tmpInfo['totalRows'] = 0;
+ $tmpInfo['totalColumns'] = 0;
+
+ if (isset($worksheet_ss['Name'])) {
+ $tmpInfo['worksheetName'] = (string) $worksheet_ss['Name'];
+ } else {
+ $tmpInfo['worksheetName'] = "Worksheet_{$worksheetID}";
+ }
+
+ if (isset($worksheet->Table->Row)) {
+ $rowIndex = 0;
+
+ foreach ($worksheet->Table->Row as $rowData) {
+ $columnIndex = 0;
+ $rowHasData = false;
+
+ foreach ($rowData->Cell as $cell) {
+ if (isset($cell->Data)) {
+ $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
+ $rowHasData = true;
+ }
+
+ ++$columnIndex;
+ }
+
+ ++$rowIndex;
+
+ if ($rowHasData) {
+ $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
+ }
+ }
+ }
+
+ $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
+ $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
+
+ $worksheetInfo[] = $tmpInfo;
+ ++$worksheetID;
+ }
+
+ return $worksheetInfo;
+ }
+
+ /**
+ * Loads Spreadsheet from file.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function load($pFilename)
+ {
+ // Create new Spreadsheet
+ $spreadsheet = new Spreadsheet();
+ $spreadsheet->removeSheetByIndex(0);
+
+ // Load into this instance
+ return $this->loadIntoExisting($pFilename, $spreadsheet);
+ }
+
+ private static function identifyFixedStyleValue($styleList, &$styleAttributeValue)
+ {
+ $styleAttributeValue = strtolower($styleAttributeValue);
+ foreach ($styleList as $style) {
+ if ($styleAttributeValue == strtolower($style)) {
+ $styleAttributeValue = $style;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * pixel units to excel width units(units of 1/256th of a character width).
+ *
+ * @param float $pxs
+ *
+ * @return float
+ */
+ protected static function pixel2WidthUnits($pxs)
+ {
+ $UNIT_OFFSET_MAP = [0, 36, 73, 109, 146, 182, 219];
+
+ $widthUnits = 256 * ($pxs / 7);
+ $widthUnits += $UNIT_OFFSET_MAP[($pxs % 7)];
+
+ return $widthUnits;
+ }
+
+ /**
+ * excel width units(units of 1/256th of a character width) to pixel units.
+ *
+ * @param float $widthUnits
+ *
+ * @return float
+ */
+ protected static function widthUnits2Pixel($widthUnits)
+ {
+ $pixels = ($widthUnits / 256) * 7;
+ $offsetWidthUnits = $widthUnits % 256;
+
+ return $pixels + round($offsetWidthUnits / (256 / 7));
+ }
+
+ protected static function hex2str($hex)
+ {
+ return chr(hexdec($hex[1]));
+ }
+
+ /**
+ * Loads from file into Spreadsheet instance.
+ *
+ * @param string $pFilename
+ *
+ * @return Spreadsheet
+ */
+ public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
+ {
+ File::assertFile($pFilename);
+ if (!$this->canRead($pFilename)) {
+ throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
+ }
+
+ $xml = $this->trySimpleXMLLoadString($pFilename);
+
+ $namespaces = $xml->getNamespaces(true);
+
+ $docProps = $spreadsheet->getProperties();
+ if (isset($xml->DocumentProperties[0])) {
+ foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) {
+ switch ($propertyName) {
+ case 'Title':
+ $docProps->setTitle(self::convertStringEncoding($propertyValue, $this->charSet));
+
+ break;
+ case 'Subject':
+ $docProps->setSubject(self::convertStringEncoding($propertyValue, $this->charSet));
+
+ break;
+ case 'Author':
+ $docProps->setCreator(self::convertStringEncoding($propertyValue, $this->charSet));
+
+ break;
+ case 'Created':
+ $creationDate = strtotime($propertyValue);
+ $docProps->setCreated($creationDate);
+
+ break;
+ case 'LastAuthor':
+ $docProps->setLastModifiedBy(self::convertStringEncoding($propertyValue, $this->charSet));
+
+ break;
+ case 'LastSaved':
+ $lastSaveDate = strtotime($propertyValue);
+ $docProps->setModified($lastSaveDate);
+
+ break;
+ case 'Company':
+ $docProps->setCompany(self::convertStringEncoding($propertyValue, $this->charSet));
+
+ break;
+ case 'Category':
+ $docProps->setCategory(self::convertStringEncoding($propertyValue, $this->charSet));
+
+ break;
+ case 'Manager':
+ $docProps->setManager(self::convertStringEncoding($propertyValue, $this->charSet));
+
+ break;
+ case 'Keywords':
+ $docProps->setKeywords(self::convertStringEncoding($propertyValue, $this->charSet));
+
+ break;
+ case 'Description':
+ $docProps->setDescription(self::convertStringEncoding($propertyValue, $this->charSet));
+
+ break;
+ }
+ }
+ }
+ if (isset($xml->CustomDocumentProperties)) {
+ foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) {
+ $propertyAttributes = $propertyValue->attributes($namespaces['dt']);
+ $propertyName = preg_replace_callback('/_x([0-9a-z]{4})_/', ['self', 'hex2str'], $propertyName);
+ $propertyType = Properties::PROPERTY_TYPE_UNKNOWN;
+ switch ((string) $propertyAttributes) {
+ case 'string':
+ $propertyType = Properties::PROPERTY_TYPE_STRING;
+ $propertyValue = trim($propertyValue);
+
+ break;
+ case 'boolean':
+ $propertyType = Properties::PROPERTY_TYPE_BOOLEAN;
+ $propertyValue = (bool) $propertyValue;
+
+ break;
+ case 'integer':
+ $propertyType = Properties::PROPERTY_TYPE_INTEGER;
+ $propertyValue = (int) $propertyValue;
+
+ break;
+ case 'float':
+ $propertyType = Properties::PROPERTY_TYPE_FLOAT;
+ $propertyValue = (float) $propertyValue;
+
+ break;
+ case 'dateTime.tz':
+ $propertyType = Properties::PROPERTY_TYPE_DATE;
+ $propertyValue = strtotime(trim($propertyValue));
+
+ break;
+ }
+ $docProps->setCustomProperty($propertyName, $propertyValue, $propertyType);
+ }
+ }
+
+ $this->parseStyles($xml, $namespaces);
+
+ $worksheetID = 0;
+ $xml_ss = $xml->children($namespaces['ss']);
+
+ foreach ($xml_ss->Worksheet as $worksheet) {
+ $worksheet_ss = $worksheet->attributes($namespaces['ss']);
+
+ if ((isset($this->loadSheetsOnly)) && (isset($worksheet_ss['Name'])) &&
+ (!in_array($worksheet_ss['Name'], $this->loadSheetsOnly))) {
+ continue;
+ }
+
+ // Create new Worksheet
+ $spreadsheet->createSheet();
+ $spreadsheet->setActiveSheetIndex($worksheetID);
+ if (isset($worksheet_ss['Name'])) {
+ $worksheetName = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet);
+ // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
+ // formula cells... during the load, all formulae should be correct, and we're simply bringing
+ // the worksheet name in line with the formula, not the reverse
+ $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
+ }
+
+ $columnID = 'A';
+ if (isset($worksheet->Table->Column)) {
+ foreach ($worksheet->Table->Column as $columnData) {
+ $columnData_ss = $columnData->attributes($namespaces['ss']);
+ if (isset($columnData_ss['Index'])) {
+ $columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']);
+ }
+ if (isset($columnData_ss['Width'])) {
+ $columnWidth = $columnData_ss['Width'];
+ $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
+ }
+ ++$columnID;
+ }
+ }
+
+ $rowID = 1;
+ if (isset($worksheet->Table->Row)) {
+ $additionalMergedCells = 0;
+ foreach ($worksheet->Table->Row as $rowData) {
+ $rowHasData = false;
+ $row_ss = $rowData->attributes($namespaces['ss']);
+ if (isset($row_ss['Index'])) {
+ $rowID = (int) $row_ss['Index'];
+ }
+
+ $columnID = 'A';
+ foreach ($rowData->Cell as $cell) {
+ $cell_ss = $cell->attributes($namespaces['ss']);
+ if (isset($cell_ss['Index'])) {
+ $columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']);
+ }
+ $cellRange = $columnID . $rowID;
+
+ if ($this->getReadFilter() !== null) {
+ if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
+ ++$columnID;
+
+ continue;
+ }
+ }
+
+ if (isset($cell_ss['HRef'])) {
+ $spreadsheet->getActiveSheet()->getCell($cellRange)->getHyperlink()->setUrl($cell_ss['HRef']);
+ }
+
+ if ((isset($cell_ss['MergeAcross'])) || (isset($cell_ss['MergeDown']))) {
+ $columnTo = $columnID;
+ if (isset($cell_ss['MergeAcross'])) {
+ $additionalMergedCells += (int) $cell_ss['MergeAcross'];
+ $columnTo = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($columnID) + $cell_ss['MergeAcross']);
+ }
+ $rowTo = $rowID;
+ if (isset($cell_ss['MergeDown'])) {
+ $rowTo = $rowTo + $cell_ss['MergeDown'];
+ }
+ $cellRange .= ':' . $columnTo . $rowTo;
+ $spreadsheet->getActiveSheet()->mergeCells($cellRange);
+ }
+
+ $cellIsSet = $hasCalculatedValue = false;
+ $cellDataFormula = '';
+ if (isset($cell_ss['Formula'])) {
+ $cellDataFormula = $cell_ss['Formula'];
+ $hasCalculatedValue = true;
+ }
+ if (isset($cell->Data)) {
+ $cellValue = $cellData = $cell->Data;
+ $type = DataType::TYPE_NULL;
+ $cellData_ss = $cellData->attributes($namespaces['ss']);
+ if (isset($cellData_ss['Type'])) {
+ $cellDataType = $cellData_ss['Type'];
+ switch ($cellDataType) {
+ /*
+ const TYPE_STRING = 's';
+ const TYPE_FORMULA = 'f';
+ const TYPE_NUMERIC = 'n';
+ const TYPE_BOOL = 'b';
+ const TYPE_NULL = 'null';
+ const TYPE_INLINE = 'inlineStr';
+ const TYPE_ERROR = 'e';
+ */
+ case 'String':
+ $cellValue = self::convertStringEncoding($cellValue, $this->charSet);
+ $type = DataType::TYPE_STRING;
+
+ break;
+ case 'Number':
+ $type = DataType::TYPE_NUMERIC;
+ $cellValue = (float) $cellValue;
+ if (floor($cellValue) == $cellValue) {
+ $cellValue = (int) $cellValue;
+ }
+
+ break;
+ case 'Boolean':
+ $type = DataType::TYPE_BOOL;
+ $cellValue = ($cellValue != 0);
+
+ break;
+ case 'DateTime':
+ $type = DataType::TYPE_NUMERIC;
+ $cellValue = Date::PHPToExcel(strtotime($cellValue));
+
+ break;
+ case 'Error':
+ $type = DataType::TYPE_ERROR;
+
+ break;
+ }
+ }
+
+ if ($hasCalculatedValue) {
+ $type = DataType::TYPE_FORMULA;
+ $columnNumber = Coordinate::columnIndexFromString($columnID);
+ if (substr($cellDataFormula, 0, 3) == 'of:') {
+ $cellDataFormula = substr($cellDataFormula, 3);
+ $temp = explode('"', $cellDataFormula);
+ $key = false;
+ foreach ($temp as &$value) {
+ // Only replace in alternate array entries (i.e. non-quoted blocks)
+ if ($key = !$key) {
+ $value = str_replace(['[.', '.', ']'], '', $value);
+ }
+ }
+ } else {
+ // Convert R1C1 style references to A1 style references (but only when not quoted)
+ $temp = explode('"', $cellDataFormula);
+ $key = false;
+ foreach ($temp as &$value) {
+ // Only replace in alternate array entries (i.e. non-quoted blocks)
+ if ($key = !$key) {
+ preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
+ // Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
+ // through the formula from left to right. Reversing means that we work right to left.through
+ // the formula
+ $cellReferences = array_reverse($cellReferences);
+ // Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent,
+ // then modify the formula to use that new reference
+ foreach ($cellReferences as $cellReference) {
+ $rowReference = $cellReference[2][0];
+ // Empty R reference is the current row
+ if ($rowReference == '') {
+ $rowReference = $rowID;
+ }
+ // Bracketed R references are relative to the current row
+ if ($rowReference[0] == '[') {
+ $rowReference = $rowID + trim($rowReference, '[]');
+ }
+ $columnReference = $cellReference[4][0];
+ // Empty C reference is the current column
+ if ($columnReference == '') {
+ $columnReference = $columnNumber;
+ }
+ // Bracketed C references are relative to the current column
+ if ($columnReference[0] == '[') {
+ $columnReference = $columnNumber + trim($columnReference, '[]');
+ }
+ $A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference;
+ $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
+ }
+ }
+ }
+ }
+ unset($value);
+ // Then rebuild the formula string
+ $cellDataFormula = implode('"', $temp);
+ }
+
+ $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValueExplicit((($hasCalculatedValue) ? $cellDataFormula : $cellValue), $type);
+ if ($hasCalculatedValue) {
+ $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setCalculatedValue($cellValue);
+ }
+ $cellIsSet = $rowHasData = true;
+ }
+
+ if (isset($cell->Comment)) {
+ $commentAttributes = $cell->Comment->attributes($namespaces['ss']);
+ $author = 'unknown';
+ if (isset($commentAttributes->Author)) {
+ $author = (string) $commentAttributes->Author;
+ }
+ $node = $cell->Comment->Data->asXML();
+ $annotation = strip_tags($node);
+ $spreadsheet->getActiveSheet()->getComment($columnID . $rowID)->setAuthor(self::convertStringEncoding($author, $this->charSet))->setText($this->parseRichText($annotation));
+ }
+
+ if (($cellIsSet) && (isset($cell_ss['StyleID']))) {
+ $style = (string) $cell_ss['StyleID'];
+ if ((isset($this->styles[$style])) && (!empty($this->styles[$style]))) {
+ if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) {
+ $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null);
+ }
+ $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($this->styles[$style]);
+ }
+ }
+ ++$columnID;
+ while ($additionalMergedCells > 0) {
+ ++$columnID;
+ --$additionalMergedCells;
+ }
+ }
+
+ if ($rowHasData) {
+ if (isset($row_ss['Height'])) {
+ $rowHeight = $row_ss['Height'];
+ $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight($rowHeight);
+ }
+ }
+
+ ++$rowID;
+ }
+ }
+ ++$worksheetID;
+ }
+
+ // Return
+ return $spreadsheet;
+ }
+
+ protected static function convertStringEncoding($string, $charset)
+ {
+ if ($charset != 'UTF-8') {
+ return StringHelper::convertEncoding($string, 'UTF-8', $charset);
+ }
+
+ return $string;
+ }
+
+ protected function parseRichText($is)
+ {
+ $value = new RichText();
+
+ $value->createText(self::convertStringEncoding($is, $this->charSet));
+
+ return $value;
+ }
+
+ private function parseStyles(SimpleXMLElement $xml, array $namespaces): void
+ {
+ if (!isset($xml->Styles)) {
+ return;
+ }
+
+ foreach ($xml->Styles[0] as $style) {
+ $style_ss = $style->attributes($namespaces['ss']);
+ $styleID = (string) $style_ss['ID'];
+ $this->styles[$styleID] = (isset($this->styles['Default'])) ? $this->styles['Default'] : [];
+ foreach ($style as $styleType => $styleData) {
+ $styleAttributes = $styleData->attributes($namespaces['ss']);
+ switch ($styleType) {
+ case 'Alignment':
+ $this->parseStyleAlignment($styleID, $styleAttributes);
+
+ break;
+ case 'Borders':
+ $this->parseStyleBorders($styleID, $styleData, $namespaces);
+
+ break;
+ case 'Font':
+ $this->parseStyleFont($styleID, $styleAttributes);
+
+ break;
+ case 'Interior':
+ $this->parseStyleInterior($styleID, $styleAttributes);
+
+ break;
+ case 'NumberFormat':
+ $this->parseStyleNumberFormat($styleID, $styleAttributes);
+
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param string $styleID
+ */
+ private function parseStyleAlignment($styleID, SimpleXMLElement $styleAttributes): void
+ {
+ $verticalAlignmentStyles = [
+ Alignment::VERTICAL_BOTTOM,
+ Alignment::VERTICAL_TOP,
+ Alignment::VERTICAL_CENTER,
+ Alignment::VERTICAL_JUSTIFY,
+ ];
+ $horizontalAlignmentStyles = [
+ Alignment::HORIZONTAL_GENERAL,
+ Alignment::HORIZONTAL_LEFT,
+ Alignment::HORIZONTAL_RIGHT,
+ Alignment::HORIZONTAL_CENTER,
+ Alignment::HORIZONTAL_CENTER_CONTINUOUS,
+ Alignment::HORIZONTAL_JUSTIFY,
+ ];
+
+ foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
+ $styleAttributeValue = (string) $styleAttributeValue;
+ switch ($styleAttributeKey) {
+ case 'Vertical':
+ if (self::identifyFixedStyleValue($verticalAlignmentStyles, $styleAttributeValue)) {
+ $this->styles[$styleID]['alignment']['vertical'] = $styleAttributeValue;
+ }
+
+ break;
+ case 'Horizontal':
+ if (self::identifyFixedStyleValue($horizontalAlignmentStyles, $styleAttributeValue)) {
+ $this->styles[$styleID]['alignment']['horizontal'] = $styleAttributeValue;
+ }
+
+ break;
+ case 'WrapText':
+ $this->styles[$styleID]['alignment']['wrapText'] = true;
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param $styleID
+ */
+ private function parseStyleBorders($styleID, SimpleXMLElement $styleData, array $namespaces): void
+ {
+ foreach ($styleData->Border as $borderStyle) {
+ $borderAttributes = $borderStyle->attributes($namespaces['ss']);
+ $thisBorder = [];
+ foreach ($borderAttributes as $borderStyleKey => $borderStyleValue) {
+ switch ($borderStyleKey) {
+ case 'LineStyle':
+ $thisBorder['borderStyle'] = Border::BORDER_MEDIUM;
+
+ break;
+ case 'Weight':
+ break;
+ case 'Position':
+ $borderPosition = strtolower($borderStyleValue);
+
+ break;
+ case 'Color':
+ $borderColour = substr($borderStyleValue, 1);
+ $thisBorder['color']['rgb'] = $borderColour;
+
+ break;
+ }
+ }
+ if (!empty($thisBorder)) {
+ if (($borderPosition == 'left') || ($borderPosition == 'right') || ($borderPosition == 'top') || ($borderPosition == 'bottom')) {
+ $this->styles[$styleID]['borders'][$borderPosition] = $thisBorder;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param $styleID
+ */
+ private function parseStyleFont($styleID, SimpleXMLElement $styleAttributes): void
+ {
+ $underlineStyles = [
+ Font::UNDERLINE_NONE,
+ Font::UNDERLINE_DOUBLE,
+ Font::UNDERLINE_DOUBLEACCOUNTING,
+ Font::UNDERLINE_SINGLE,
+ Font::UNDERLINE_SINGLEACCOUNTING,
+ ];
+
+ foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
+ $styleAttributeValue = (string) $styleAttributeValue;
+ switch ($styleAttributeKey) {
+ case 'FontName':
+ $this->styles[$styleID]['font']['name'] = $styleAttributeValue;
+
+ break;
+ case 'Size':
+ $this->styles[$styleID]['font']['size'] = $styleAttributeValue;
+
+ break;
+ case 'Color':
+ $this->styles[$styleID]['font']['color']['rgb'] = substr($styleAttributeValue, 1);
+
+ break;
+ case 'Bold':
+ $this->styles[$styleID]['font']['bold'] = true;
+
+ break;
+ case 'Italic':
+ $this->styles[$styleID]['font']['italic'] = true;
+
+ break;
+ case 'Underline':
+ if (self::identifyFixedStyleValue($underlineStyles, $styleAttributeValue)) {
+ $this->styles[$styleID]['font']['underline'] = $styleAttributeValue;
+ }
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param $styleID
+ */
+ private function parseStyleInterior($styleID, SimpleXMLElement $styleAttributes): void
+ {
+ foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
+ switch ($styleAttributeKey) {
+ case 'Color':
+ $this->styles[$styleID]['fill']['color']['rgb'] = substr($styleAttributeValue, 1);
+
+ break;
+ case 'Pattern':
+ $this->styles[$styleID]['fill']['fillType'] = strtolower($styleAttributeValue);
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param $styleID
+ */
+ private function parseStyleNumberFormat($styleID, SimpleXMLElement $styleAttributes): void
+ {
+ $fromFormats = ['\-', '\ '];
+ $toFormats = ['-', ' '];
+
+ foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
+ $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue);
+ switch ($styleAttributeValue) {
+ case 'Short Date':
+ $styleAttributeValue = 'dd/mm/yyyy';
+
+ break;
+ }
+
+ if ($styleAttributeValue > '') {
+ $this->styles[$styleID]['numberFormat']['formatCode'] = $styleAttributeValue;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php
new file mode 100755
index 0000000..0b0f6dd
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php
@@ -0,0 +1,898 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class ReferenceHelper
+{
+ /** Constants */
+ /** Regular Expressions */
+ const REFHELPER_REGEXP_CELLREF = '((\w*|\'[^!]*\')!)?(?<![:a-z\$])(\$?[a-z]{1,3}\$?\d+)(?=[^:!\d\'])';
+ const REFHELPER_REGEXP_CELLRANGE = '((\w*|\'[^!]*\')!)?(\$?[a-z]{1,3}\$?\d+):(\$?[a-z]{1,3}\$?\d+)';
+ const REFHELPER_REGEXP_ROWRANGE = '((\w*|\'[^!]*\')!)?(\$?\d+):(\$?\d+)';
+ const REFHELPER_REGEXP_COLRANGE = '((\w*|\'[^!]*\')!)?(\$?[a-z]{1,3}):(\$?[a-z]{1,3})';
+
+ /**
+ * Instance of this class.
+ *
+ * @var ReferenceHelper
+ */
+ private static $instance;
+
+ /**
+ * Get an instance of this class.
+ *
+ * @return ReferenceHelper
+ */
+ public static function getInstance()
+ {
+ if (!isset(self::$instance) || (self::$instance === null)) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Create a new ReferenceHelper.
+ */
+ protected function __construct()
+ {
+ }
+
+ /**
+ * Compare two column addresses
+ * Intended for use as a Callback function for sorting column addresses by column.
+ *
+ * @param string $a First column to test (e.g. 'AA')
+ * @param string $b Second column to test (e.g. 'Z')
+ *
+ * @return int
+ */
+ public static function columnSort($a, $b)
+ {
+ return strcasecmp(strlen($a) . $a, strlen($b) . $b);
+ }
+
+ /**
+ * Compare two column addresses
+ * Intended for use as a Callback function for reverse sorting column addresses by column.
+ *
+ * @param string $a First column to test (e.g. 'AA')
+ * @param string $b Second column to test (e.g. 'Z')
+ *
+ * @return int
+ */
+ public static function columnReverseSort($a, $b)
+ {
+ return 1 - strcasecmp(strlen($a) . $a, strlen($b) . $b);
+ }
+
+ /**
+ * Compare two cell addresses
+ * Intended for use as a Callback function for sorting cell addresses by column and row.
+ *
+ * @param string $a First cell to test (e.g. 'AA1')
+ * @param string $b Second cell to test (e.g. 'Z1')
+ *
+ * @return int
+ */
+ public static function cellSort($a, $b)
+ {
+ [$ac, $ar] = sscanf($a, '%[A-Z]%d');
+ [$bc, $br] = sscanf($b, '%[A-Z]%d');
+
+ if ($ar === $br) {
+ return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
+ }
+
+ return ($ar < $br) ? -1 : 1;
+ }
+
+ /**
+ * Compare two cell addresses
+ * Intended for use as a Callback function for sorting cell addresses by column and row.
+ *
+ * @param string $a First cell to test (e.g. 'AA1')
+ * @param string $b Second cell to test (e.g. 'Z1')
+ *
+ * @return int
+ */
+ public static function cellReverseSort($a, $b)
+ {
+ [$ac, $ar] = sscanf($a, '%[A-Z]%d');
+ [$bc, $br] = sscanf($b, '%[A-Z]%d');
+
+ if ($ar === $br) {
+ return 1 - strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
+ }
+
+ return ($ar < $br) ? 1 : -1;
+ }
+
+ /**
+ * Test whether a cell address falls within a defined range of cells.
+ *
+ * @param string $cellAddress Address of the cell we're testing
+ * @param int $beforeRow Number of the row we're inserting/deleting before
+ * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
+ * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
+ * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
+ *
+ * @return bool
+ */
+ private static function cellAddressInDeleteRange($cellAddress, $beforeRow, $pNumRows, $beforeColumnIndex, $pNumCols)
+ {
+ [$cellColumn, $cellRow] = Coordinate::coordinateFromString($cellAddress);
+ $cellColumnIndex = Coordinate::columnIndexFromString($cellColumn);
+ // Is cell within the range of rows/columns if we're deleting
+ if ($pNumRows < 0 &&
+ ($cellRow >= ($beforeRow + $pNumRows)) &&
+ ($cellRow < $beforeRow)) {
+ return true;
+ } elseif ($pNumCols < 0 &&
+ ($cellColumnIndex >= ($beforeColumnIndex + $pNumCols)) &&
+ ($cellColumnIndex < $beforeColumnIndex)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Update page breaks when inserting/deleting rows/columns.
+ *
+ * @param Worksheet $pSheet The worksheet that we're editing
+ * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
+ * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
+ * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
+ * @param int $beforeRow Number of the row we're inserting/deleting before
+ * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
+ */
+ protected function adjustPageBreaks(Worksheet $pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows): void
+ {
+ $aBreaks = $pSheet->getBreaks();
+ ($pNumCols > 0 || $pNumRows > 0) ?
+ uksort($aBreaks, ['self', 'cellReverseSort']) : uksort($aBreaks, ['self', 'cellSort']);
+
+ foreach ($aBreaks as $key => $value) {
+ if (self::cellAddressInDeleteRange($key, $beforeRow, $pNumRows, $beforeColumnIndex, $pNumCols)) {
+ // If we're deleting, then clear any defined breaks that are within the range
+ // of rows/columns that we're deleting
+ $pSheet->setBreak($key, Worksheet::BREAK_NONE);
+ } else {
+ // Otherwise update any affected breaks by inserting a new break at the appropriate point
+ // and removing the old affected break
+ $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
+ if ($key != $newReference) {
+ $pSheet->setBreak($newReference, $value)
+ ->setBreak($key, Worksheet::BREAK_NONE);
+ }
+ }
+ }
+ }
+
+ /**
+ * Update cell comments when inserting/deleting rows/columns.
+ *
+ * @param Worksheet $pSheet The worksheet that we're editing
+ * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
+ * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
+ * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
+ * @param int $beforeRow Number of the row we're inserting/deleting before
+ * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
+ */
+ protected function adjustComments($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows): void
+ {
+ $aComments = $pSheet->getComments();
+ $aNewComments = []; // the new array of all comments
+
+ foreach ($aComments as $key => &$value) {
+ // Any comments inside a deleted range will be ignored
+ if (!self::cellAddressInDeleteRange($key, $beforeRow, $pNumRows, $beforeColumnIndex, $pNumCols)) {
+ // Otherwise build a new array of comments indexed by the adjusted cell reference
+ $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
+ $aNewComments[$newReference] = $value;
+ }
+ }
+ // Replace the comments array with the new set of comments
+ $pSheet->setComments($aNewComments);
+ }
+
+ /**
+ * Update hyperlinks when inserting/deleting rows/columns.
+ *
+ * @param Worksheet $pSheet The worksheet that we're editing
+ * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
+ * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
+ * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
+ * @param int $beforeRow Number of the row we're inserting/deleting before
+ * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
+ */
+ protected function adjustHyperlinks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows): void
+ {
+ $aHyperlinkCollection = $pSheet->getHyperlinkCollection();
+ ($pNumCols > 0 || $pNumRows > 0) ?
+ uksort($aHyperlinkCollection, ['self', 'cellReverseSort']) : uksort($aHyperlinkCollection, ['self', 'cellSort']);
+
+ foreach ($aHyperlinkCollection as $key => $value) {
+ $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
+ if ($key != $newReference) {
+ $pSheet->setHyperlink($newReference, $value);
+ $pSheet->setHyperlink($key, null);
+ }
+ }
+ }
+
+ /**
+ * Update data validations when inserting/deleting rows/columns.
+ *
+ * @param Worksheet $pSheet The worksheet that we're editing
+ * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
+ * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
+ * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
+ * @param int $beforeRow Number of the row we're inserting/deleting before
+ * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
+ */
+ protected function adjustDataValidations($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows): void
+ {
+ $aDataValidationCollection = $pSheet->getDataValidationCollection();
+ ($pNumCols > 0 || $pNumRows > 0) ?
+ uksort($aDataValidationCollection, ['self', 'cellReverseSort']) : uksort($aDataValidationCollection, ['self', 'cellSort']);
+
+ foreach ($aDataValidationCollection as $key => $value) {
+ $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
+ if ($key != $newReference) {
+ $pSheet->setDataValidation($newReference, $value);
+ $pSheet->setDataValidation($key, null);
+ }
+ }
+ }
+
+ /**
+ * Update merged cells when inserting/deleting rows/columns.
+ *
+ * @param Worksheet $pSheet The worksheet that we're editing
+ * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
+ * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
+ * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
+ * @param int $beforeRow Number of the row we're inserting/deleting before
+ * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
+ */
+ protected function adjustMergeCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows): void
+ {
+ $aMergeCells = $pSheet->getMergeCells();
+ $aNewMergeCells = []; // the new array of all merge cells
+ foreach ($aMergeCells as $key => &$value) {
+ $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
+ $aNewMergeCells[$newReference] = $newReference;
+ }
+ $pSheet->setMergeCells($aNewMergeCells); // replace the merge cells array
+ }
+
+ /**
+ * Update protected cells when inserting/deleting rows/columns.
+ *
+ * @param Worksheet $pSheet The worksheet that we're editing
+ * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
+ * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
+ * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
+ * @param int $beforeRow Number of the row we're inserting/deleting before
+ * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
+ */
+ protected function adjustProtectedCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows): void
+ {
+ $aProtectedCells = $pSheet->getProtectedCells();
+ ($pNumCols > 0 || $pNumRows > 0) ?
+ uksort($aProtectedCells, ['self', 'cellReverseSort']) : uksort($aProtectedCells, ['self', 'cellSort']);
+ foreach ($aProtectedCells as $key => $value) {
+ $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
+ if ($key != $newReference) {
+ $pSheet->protectCells($newReference, $value, true);
+ $pSheet->unprotectCells($key);
+ }
+ }
+ }
+
+ /**
+ * Update column dimensions when inserting/deleting rows/columns.
+ *
+ * @param Worksheet $pSheet The worksheet that we're editing
+ * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
+ * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
+ * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
+ * @param int $beforeRow Number of the row we're inserting/deleting before
+ * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
+ */
+ protected function adjustColumnDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows): void
+ {
+ $aColumnDimensions = array_reverse($pSheet->getColumnDimensions(), true);
+ if (!empty($aColumnDimensions)) {
+ foreach ($aColumnDimensions as $objColumnDimension) {
+ $newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1', $pBefore, $pNumCols, $pNumRows);
+ [$newReference] = Coordinate::coordinateFromString($newReference);
+ if ($objColumnDimension->getColumnIndex() != $newReference) {
+ $objColumnDimension->setColumnIndex($newReference);
+ }
+ }
+ $pSheet->refreshColumnDimensions();
+ }
+ }
+
+ /**
+ * Update row dimensions when inserting/deleting rows/columns.
+ *
+ * @param Worksheet $pSheet The worksheet that we're editing
+ * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
+ * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before
+ * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
+ * @param int $beforeRow Number of the row we're inserting/deleting before
+ * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
+ */
+ protected function adjustRowDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows): void
+ {
+ $aRowDimensions = array_reverse($pSheet->getRowDimensions(), true);
+ if (!empty($aRowDimensions)) {
+ foreach ($aRowDimensions as $objRowDimension) {
+ $newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex(), $pBefore, $pNumCols, $pNumRows);
+ [, $newReference] = Coordinate::coordinateFromString($newReference);
+ if ($objRowDimension->getRowIndex() != $newReference) {
+ $objRowDimension->setRowIndex($newReference);
+ }
+ }
+ $pSheet->refreshRowDimensions();
+
+ $copyDimension = $pSheet->getRowDimension($beforeRow - 1);
+ for ($i = $beforeRow; $i <= $beforeRow - 1 + $pNumRows; ++$i) {
+ $newDimension = $pSheet->getRowDimension($i);
+ $newDimension->setRowHeight($copyDimension->getRowHeight());
+ $newDimension->setVisible($copyDimension->getVisible());
+ $newDimension->setOutlineLevel($copyDimension->getOutlineLevel());
+ $newDimension->setCollapsed($copyDimension->getCollapsed());
+ }
+ }
+ }
+
+ /**
+ * Insert a new column or row, updating all possible related data.
+ *
+ * @param string $pBefore Insert before this cell address (e.g. 'A1')
+ * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion)
+ * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion)
+ * @param Worksheet $pSheet The worksheet that we're editing
+ */
+ public function insertNewBefore($pBefore, $pNumCols, $pNumRows, Worksheet $pSheet): void
+ {
+ $remove = ($pNumCols < 0 || $pNumRows < 0);
+ $allCoordinates = $pSheet->getCoordinates();
+
+ // Get coordinate of $pBefore
+ [$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($pBefore);
+ $beforeColumnIndex = Coordinate::columnIndexFromString($beforeColumn);
+
+ // Clear cells if we are removing columns or rows
+ $highestColumn = $pSheet->getHighestColumn();
+ $highestRow = $pSheet->getHighestRow();
+
+ // 1. Clear column strips if we are removing columns
+ if ($pNumCols < 0 && $beforeColumnIndex - 2 + $pNumCols > 0) {
+ for ($i = 1; $i <= $highestRow - 1; ++$i) {
+ for ($j = $beforeColumnIndex - 1 + $pNumCols; $j <= $beforeColumnIndex - 2; ++$j) {
+ $coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i;
+ $pSheet->removeConditionalStyles($coordinate);
+ if ($pSheet->cellExists($coordinate)) {
+ $pSheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
+ $pSheet->getCell($coordinate)->setXfIndex(0);
+ }
+ }
+ }
+ }
+
+ // 2. Clear row strips if we are removing rows
+ if ($pNumRows < 0 && $beforeRow - 1 + $pNumRows > 0) {
+ for ($i = $beforeColumnIndex - 1; $i <= Coordinate::columnIndexFromString($highestColumn) - 1; ++$i) {
+ for ($j = $beforeRow + $pNumRows; $j <= $beforeRow - 1; ++$j) {
+ $coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j;
+ $pSheet->removeConditionalStyles($coordinate);
+ if ($pSheet->cellExists($coordinate)) {
+ $pSheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
+ $pSheet->getCell($coordinate)->setXfIndex(0);
+ }
+ }
+ }
+ }
+
+ // Loop through cells, bottom-up, and change cell coordinate
+ if ($remove) {
+ // It's faster to reverse and pop than to use unshift, especially with large cell collections
+ $allCoordinates = array_reverse($allCoordinates);
+ }
+ while ($coordinate = array_pop($allCoordinates)) {
+ $cell = $pSheet->getCell($coordinate);
+ $cellIndex = Coordinate::columnIndexFromString($cell->getColumn());
+
+ if ($cellIndex - 1 + $pNumCols < 0) {
+ continue;
+ }
+
+ // New coordinate
+ $newCoordinate = Coordinate::stringFromColumnIndex($cellIndex + $pNumCols) . ($cell->getRow() + $pNumRows);
+
+ // Should the cell be updated? Move value and cellXf index from one cell to another.
+ if (($cellIndex >= $beforeColumnIndex) && ($cell->getRow() >= $beforeRow)) {
+ // Update cell styles
+ $pSheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex());
+
+ // Insert this cell at its new location
+ if ($cell->getDataType() == DataType::TYPE_FORMULA) {
+ // Formula should be adjusted
+ $pSheet->getCell($newCoordinate)
+ ->setValue($this->updateFormulaReferences($cell->getValue(), $pBefore, $pNumCols, $pNumRows, $pSheet->getTitle()));
+ } else {
+ // Formula should not be adjusted
+ $pSheet->getCell($newCoordinate)->setValue($cell->getValue());
+ }
+
+ // Clear the original cell
+ $pSheet->getCellCollection()->delete($coordinate);
+ } else {
+ /* We don't need to update styles for rows/columns before our insertion position,
+ but we do still need to adjust any formulae in those cells */
+ if ($cell->getDataType() == DataType::TYPE_FORMULA) {
+ // Formula should be adjusted
+ $cell->setValue($this->updateFormulaReferences($cell->getValue(), $pBefore, $pNumCols, $pNumRows, $pSheet->getTitle()));
+ }
+ }
+ }
+
+ // Duplicate styles for the newly inserted cells
+ $highestColumn = $pSheet->getHighestColumn();
+ $highestRow = $pSheet->getHighestRow();
+
+ if ($pNumCols > 0 && $beforeColumnIndex - 2 > 0) {
+ for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) {
+ // Style
+ $coordinate = Coordinate::stringFromColumnIndex($beforeColumnIndex - 1) . $i;
+ if ($pSheet->cellExists($coordinate)) {
+ $xfIndex = $pSheet->getCell($coordinate)->getXfIndex();
+ $conditionalStyles = $pSheet->conditionalStylesExists($coordinate) ?
+ $pSheet->getConditionalStyles($coordinate) : false;
+ for ($j = $beforeColumnIndex; $j <= $beforeColumnIndex - 1 + $pNumCols; ++$j) {
+ $pSheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex);
+ if ($conditionalStyles) {
+ $cloned = [];
+ foreach ($conditionalStyles as $conditionalStyle) {
+ $cloned[] = clone $conditionalStyle;
+ }
+ $pSheet->setConditionalStyles(Coordinate::stringFromColumnIndex($j) . $i, $cloned);
+ }
+ }
+ }
+ }
+ }
+
+ if ($pNumRows > 0 && $beforeRow - 1 > 0) {
+ for ($i = $beforeColumnIndex; $i <= Coordinate::columnIndexFromString($highestColumn); ++$i) {
+ // Style
+ $coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1);
+ if ($pSheet->cellExists($coordinate)) {
+ $xfIndex = $pSheet->getCell($coordinate)->getXfIndex();
+ $conditionalStyles = $pSheet->conditionalStylesExists($coordinate) ?
+ $pSheet->getConditionalStyles($coordinate) : false;
+ for ($j = $beforeRow; $j <= $beforeRow - 1 + $pNumRows; ++$j) {
+ $pSheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex);
+ if ($conditionalStyles) {
+ $cloned = [];
+ foreach ($conditionalStyles as $conditionalStyle) {
+ $cloned[] = clone $conditionalStyle;
+ }
+ $pSheet->setConditionalStyles(Coordinate::stringFromColumnIndex($i) . $j, $cloned);
+ }
+ }
+ }
+ }
+ }
+
+ // Update worksheet: column dimensions
+ $this->adjustColumnDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
+
+ // Update worksheet: row dimensions
+ $this->adjustRowDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
+
+ // Update worksheet: page breaks
+ $this->adjustPageBreaks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
+
+ // Update worksheet: comments
+ $this->adjustComments($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
+
+ // Update worksheet: hyperlinks
+ $this->adjustHyperlinks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
+
+ // Update worksheet: data validations
+ $this->adjustDataValidations($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
+
+ // Update worksheet: merge cells
+ $this->adjustMergeCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
+
+ // Update worksheet: protected cells
+ $this->adjustProtectedCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
+
+ // Update worksheet: autofilter
+ $autoFilter = $pSheet->getAutoFilter();
+ $autoFilterRange = $autoFilter->getRange();
+ if (!empty($autoFilterRange)) {
+ if ($pNumCols != 0) {
+ $autoFilterColumns = $autoFilter->getColumns();
+ if (count($autoFilterColumns) > 0) {
+ $column = '';
+ $row = 0;
+ sscanf($pBefore, '%[A-Z]%d', $column, $row);
+ $columnIndex = Coordinate::columnIndexFromString($column);
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange);
+ if ($columnIndex <= $rangeEnd[0]) {
+ if ($pNumCols < 0) {
+ // If we're actually deleting any columns that fall within the autofilter range,
+ // then we delete any rules for those columns
+ $deleteColumn = $columnIndex + $pNumCols - 1;
+ $deleteCount = abs($pNumCols);
+ for ($i = 1; $i <= $deleteCount; ++$i) {
+ if (isset($autoFilterColumns[Coordinate::stringFromColumnIndex($deleteColumn + 1)])) {
+ $autoFilter->clearColumn(Coordinate::stringFromColumnIndex($deleteColumn + 1));
+ }
+ ++$deleteColumn;
+ }
+ }
+ $startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
+
+ // Shuffle columns in autofilter range
+ if ($pNumCols > 0) {
+ $startColRef = $startCol;
+ $endColRef = $rangeEnd[0];
+ $toColRef = $rangeEnd[0] + $pNumCols;
+
+ do {
+ $autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
+ --$endColRef;
+ --$toColRef;
+ } while ($startColRef <= $endColRef);
+ } else {
+ // For delete, we shuffle from beginning to end to avoid overwriting
+ $startColID = Coordinate::stringFromColumnIndex($startCol);
+ $toColID = Coordinate::stringFromColumnIndex($startCol + $pNumCols);
+ $endColID = Coordinate::stringFromColumnIndex($rangeEnd[0] + 1);
+ do {
+ $autoFilter->shiftColumn($startColID, $toColID);
+ ++$startColID;
+ ++$toColID;
+ } while ($startColID != $endColID);
+ }
+ }
+ }
+ }
+ $pSheet->setAutoFilter($this->updateCellReference($autoFilterRange, $pBefore, $pNumCols, $pNumRows));
+ }
+
+ // Update worksheet: freeze pane
+ if ($pSheet->getFreezePane()) {
+ $splitCell = $pSheet->getFreezePane();
+ $topLeftCell = $pSheet->getTopLeftCell();
+
+ $splitCell = $this->updateCellReference($splitCell, $pBefore, $pNumCols, $pNumRows);
+ $topLeftCell = $this->updateCellReference($topLeftCell, $pBefore, $pNumCols, $pNumRows);
+
+ $pSheet->freezePane($splitCell, $topLeftCell);
+ }
+
+ // Page setup
+ if ($pSheet->getPageSetup()->isPrintAreaSet()) {
+ $pSheet->getPageSetup()->setPrintArea($this->updateCellReference($pSheet->getPageSetup()->getPrintArea(), $pBefore, $pNumCols, $pNumRows));
+ }
+
+ // Update worksheet: drawings
+ $aDrawings = $pSheet->getDrawingCollection();
+ foreach ($aDrawings as $objDrawing) {
+ $newReference = $this->updateCellReference($objDrawing->getCoordinates(), $pBefore, $pNumCols, $pNumRows);
+ if ($objDrawing->getCoordinates() != $newReference) {
+ $objDrawing->setCoordinates($newReference);
+ }
+ }
+
+ // Update workbook: named ranges
+ if (count($pSheet->getParent()->getNamedRanges()) > 0) {
+ foreach ($pSheet->getParent()->getNamedRanges() as $namedRange) {
+ if ($namedRange->getWorksheet()->getHashCode() == $pSheet->getHashCode()) {
+ $namedRange->setRange($this->updateCellReference($namedRange->getRange(), $pBefore, $pNumCols, $pNumRows));
+ }
+ }
+ }
+
+ // Garbage collect
+ $pSheet->garbageCollect();
+ }
+
+ /**
+ * Update references within formulas.
+ *
+ * @param string $pFormula Formula to update
+ * @param string $pBefore Insert before this one
+ * @param int $pNumCols Number of columns to insert
+ * @param int $pNumRows Number of rows to insert
+ * @param string $sheetName Worksheet name/title
+ *
+ * @return string Updated formula
+ */
+ public function updateFormulaReferences($pFormula = '', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0, $sheetName = '')
+ {
+ // Update cell references in the formula
+ $formulaBlocks = explode('"', $pFormula);
+ $i = false;
+ foreach ($formulaBlocks as &$formulaBlock) {
+ // Ignore blocks that were enclosed in quotes (alternating entries in the $formulaBlocks array after the explode)
+ if ($i = !$i) {
+ $adjustCount = 0;
+ $newCellTokens = $cellTokens = [];
+ // Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5)
+ $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
+ if ($matchCount > 0) {
+ foreach ($matches as $match) {
+ $fromString = ($match[2] > '') ? $match[2] . '!' : '';
+ $fromString .= $match[3] . ':' . $match[4];
+ $modified3 = substr($this->updateCellReference('$A' . $match[3], $pBefore, $pNumCols, $pNumRows), 2);
+ $modified4 = substr($this->updateCellReference('$A' . $match[4], $pBefore, $pNumCols, $pNumRows), 2);
+
+ if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
+ if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
+ $toString = ($match[2] > '') ? $match[2] . '!' : '';
+ $toString .= $modified3 . ':' . $modified4;
+ // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
+ $column = 100000;
+ $row = 10000000 + trim($match[3], '$');
+ $cellIndex = $column . $row;
+
+ $newCellTokens[$cellIndex] = preg_quote($toString, '/');
+ $cellTokens[$cellIndex] = '/(?<!\d\$\!)' . preg_quote($fromString, '/') . '(?!\d)/i';
+ ++$adjustCount;
+ }
+ }
+ }
+ }
+ // Search for column ranges (e.g. 'Sheet1'!C:E or C:E) with or without $ absolutes (e.g. $C:E)
+ $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_COLRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
+ if ($matchCount > 0) {
+ foreach ($matches as $match) {
+ $fromString = ($match[2] > '') ? $match[2] . '!' : '';
+ $fromString .= $match[3] . ':' . $match[4];
+ $modified3 = substr($this->updateCellReference($match[3] . '$1', $pBefore, $pNumCols, $pNumRows), 0, -2);
+ $modified4 = substr($this->updateCellReference($match[4] . '$1', $pBefore, $pNumCols, $pNumRows), 0, -2);
+
+ if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
+ if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
+ $toString = ($match[2] > '') ? $match[2] . '!' : '';
+ $toString .= $modified3 . ':' . $modified4;
+ // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
+ $column = Coordinate::columnIndexFromString(trim($match[3], '$')) + 100000;
+ $row = 10000000;
+ $cellIndex = $column . $row;
+
+ $newCellTokens[$cellIndex] = preg_quote($toString, '/');
+ $cellTokens[$cellIndex] = '/(?<![A-Z\$\!])' . preg_quote($fromString, '/') . '(?![A-Z])/i';
+ ++$adjustCount;
+ }
+ }
+ }
+ }
+ // Search for cell ranges (e.g. 'Sheet1'!A3:C5 or A3:C5) with or without $ absolutes (e.g. $A1:C$5)
+ $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
+ if ($matchCount > 0) {
+ foreach ($matches as $match) {
+ $fromString = ($match[2] > '') ? $match[2] . '!' : '';
+ $fromString .= $match[3] . ':' . $match[4];
+ $modified3 = $this->updateCellReference($match[3], $pBefore, $pNumCols, $pNumRows);
+ $modified4 = $this->updateCellReference($match[4], $pBefore, $pNumCols, $pNumRows);
+
+ if ($match[3] . $match[4] !== $modified3 . $modified4) {
+ if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
+ $toString = ($match[2] > '') ? $match[2] . '!' : '';
+ $toString .= $modified3 . ':' . $modified4;
+ [$column, $row] = Coordinate::coordinateFromString($match[3]);
+ // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
+ $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000;
+ $row = trim($row, '$') + 10000000;
+ $cellIndex = $column . $row;
+
+ $newCellTokens[$cellIndex] = preg_quote($toString, '/');
+ $cellTokens[$cellIndex] = '/(?<![A-Z]\$\!)' . preg_quote($fromString, '/') . '(?!\d)/i';
+ ++$adjustCount;
+ }
+ }
+ }
+ }
+ // Search for cell references (e.g. 'Sheet1'!A3 or C5) with or without $ absolutes (e.g. $A1 or C$5)
+ $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLREF . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
+
+ if ($matchCount > 0) {
+ foreach ($matches as $match) {
+ $fromString = ($match[2] > '') ? $match[2] . '!' : '';
+ $fromString .= $match[3];
+
+ $modified3 = $this->updateCellReference($match[3], $pBefore, $pNumCols, $pNumRows);
+ if ($match[3] !== $modified3) {
+ if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
+ $toString = ($match[2] > '') ? $match[2] . '!' : '';
+ $toString .= $modified3;
+ [$column, $row] = Coordinate::coordinateFromString($match[3]);
+ // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
+ $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000;
+ $row = trim($row, '$') + 10000000;
+ $cellIndex = $row . $column;
+
+ $newCellTokens[$cellIndex] = preg_quote($toString, '/');
+ $cellTokens[$cellIndex] = '/(?<![A-Z\$\!])' . preg_quote($fromString, '/') . '(?!\d)/i';
+ ++$adjustCount;
+ }
+ }
+ }
+ }
+ if ($adjustCount > 0) {
+ if ($pNumCols > 0 || $pNumRows > 0) {
+ krsort($cellTokens);
+ krsort($newCellTokens);
+ } else {
+ ksort($cellTokens);
+ ksort($newCellTokens);
+ } // Update cell references in the formula
+ $formulaBlock = str_replace('\\', '', preg_replace($cellTokens, $newCellTokens, $formulaBlock));
+ }
+ }
+ }
+ unset($formulaBlock);
+
+ // Then rebuild the formula string
+ return implode('"', $formulaBlocks);
+ }
+
+ /**
+ * Update cell reference.
+ *
+ * @param string $pCellRange Cell range
+ * @param string $pBefore Insert before this one
+ * @param int $pNumCols Number of columns to increment
+ * @param int $pNumRows Number of rows to increment
+ *
+ * @return string Updated cell range
+ */
+ public function updateCellReference($pCellRange = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
+ {
+ // Is it in another worksheet? Will not have to update anything.
+ if (strpos($pCellRange, '!') !== false) {
+ return $pCellRange;
+ // Is it a range or a single cell?
+ } elseif (!Coordinate::coordinateIsRange($pCellRange)) {
+ // Single cell
+ return $this->updateSingleCellReference($pCellRange, $pBefore, $pNumCols, $pNumRows);
+ } elseif (Coordinate::coordinateIsRange($pCellRange)) {
+ // Range
+ return $this->updateCellRange($pCellRange, $pBefore, $pNumCols, $pNumRows);
+ }
+
+ // Return original
+ return $pCellRange;
+ }
+
+ /**
+ * Update named formulas (i.e. containing worksheet references / named ranges).
+ *
+ * @param Spreadsheet $spreadsheet Object to update
+ * @param string $oldName Old name (name to replace)
+ * @param string $newName New name
+ */
+ public function updateNamedFormulas(Spreadsheet $spreadsheet, $oldName = '', $newName = ''): void
+ {
+ if ($oldName == '') {
+ return;
+ }
+
+ foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
+ foreach ($sheet->getCoordinates(false) as $coordinate) {
+ $cell = $sheet->getCell($coordinate);
+ if (($cell !== null) && ($cell->getDataType() == DataType::TYPE_FORMULA)) {
+ $formula = $cell->getValue();
+ if (strpos($formula, $oldName) !== false) {
+ $formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula);
+ $formula = str_replace($oldName . '!', $newName . '!', $formula);
+ $cell->setValueExplicit($formula, DataType::TYPE_FORMULA);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Update cell range.
+ *
+ * @param string $pCellRange Cell range (e.g. 'B2:D4', 'B:C' or '2:3')
+ * @param string $pBefore Insert before this one
+ * @param int $pNumCols Number of columns to increment
+ * @param int $pNumRows Number of rows to increment
+ *
+ * @return string Updated cell range
+ */
+ private function updateCellRange($pCellRange = 'A1:A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
+ {
+ if (!Coordinate::coordinateIsRange($pCellRange)) {
+ throw new Exception('Only cell ranges may be passed to this method.');
+ }
+
+ // Update range
+ $range = Coordinate::splitRange($pCellRange);
+ $ic = count($range);
+ for ($i = 0; $i < $ic; ++$i) {
+ $jc = count($range[$i]);
+ for ($j = 0; $j < $jc; ++$j) {
+ if (ctype_alpha($range[$i][$j])) {
+ $r = Coordinate::coordinateFromString($this->updateSingleCellReference($range[$i][$j] . '1', $pBefore, $pNumCols, $pNumRows));
+ $range[$i][$j] = $r[0];
+ } elseif (ctype_digit($range[$i][$j])) {
+ $r = Coordinate::coordinateFromString($this->updateSingleCellReference('A' . $range[$i][$j], $pBefore, $pNumCols, $pNumRows));
+ $range[$i][$j] = $r[1];
+ } else {
+ $range[$i][$j] = $this->updateSingleCellReference($range[$i][$j], $pBefore, $pNumCols, $pNumRows);
+ }
+ }
+ }
+
+ // Recreate range string
+ return Coordinate::buildRange($range);
+ }
+
+ /**
+ * Update single cell reference.
+ *
+ * @param string $pCellReference Single cell reference
+ * @param string $pBefore Insert before this one
+ * @param int $pNumCols Number of columns to increment
+ * @param int $pNumRows Number of rows to increment
+ *
+ * @return string Updated cell reference
+ */
+ private function updateSingleCellReference($pCellReference = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
+ {
+ if (Coordinate::coordinateIsRange($pCellReference)) {
+ throw new Exception('Only single cell references may be passed to this method.');
+ }
+
+ // Get coordinate of $pBefore
+ [$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($pBefore);
+
+ // Get coordinate of $pCellReference
+ [$newColumn, $newRow] = Coordinate::coordinateFromString($pCellReference);
+
+ // Verify which parts should be updated
+ $updateColumn = (($newColumn[0] != '$') && ($beforeColumn[0] != '$') && (Coordinate::columnIndexFromString($newColumn) >= Coordinate::columnIndexFromString($beforeColumn)));
+ $updateRow = (($newRow[0] != '$') && ($beforeRow[0] != '$') && $newRow >= $beforeRow);
+
+ // Create new column reference
+ if ($updateColumn) {
+ $newColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($newColumn) + $pNumCols);
+ }
+
+ // Create new row reference
+ if ($updateRow) {
+ $newRow = $newRow + $pNumRows;
+ }
+
+ // Return new reference
+ return $newColumn . $newRow;
+ }
+
+ /**
+ * __clone implementation. Cloning should not be allowed in a Singleton!
+ */
+ final public function __clone()
+ {
+ throw new Exception('Cloning a Singleton is not allowed!');
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php
new file mode 100755
index 0000000..6995467
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\RichText;
+
+interface ITextElement
+{
+ /**
+ * Get text.
+ *
+ * @return string Text
+ */
+ public function getText();
+
+ /**
+ * Set text.
+ *
+ * @param $text string Text
+ *
+ * @return ITextElement
+ */
+ public function setText($text);
+
+ /**
+ * Get font.
+ *
+ * @return null|\PhpOffice\PhpSpreadsheet\Style\Font
+ */
+ public function getFont();
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode();
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/RichText.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/RichText.php
new file mode 100755
index 0000000..104177b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/RichText.php
@@ -0,0 +1,170 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\RichText;
+
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\IComparable;
+
+class RichText implements IComparable
+{
+ /**
+ * Rich text elements.
+ *
+ * @var ITextElement[]
+ */
+ private $richTextElements;
+
+ /**
+ * Create a new RichText instance.
+ *
+ * @param Cell $pCell
+ */
+ public function __construct(?Cell $pCell = null)
+ {
+ // Initialise variables
+ $this->richTextElements = [];
+
+ // Rich-Text string attached to cell?
+ if ($pCell !== null) {
+ // Add cell text and style
+ if ($pCell->getValue() != '') {
+ $objRun = new Run($pCell->getValue());
+ $objRun->setFont(clone $pCell->getWorksheet()->getStyle($pCell->getCoordinate())->getFont());
+ $this->addText($objRun);
+ }
+
+ // Set parent value
+ $pCell->setValueExplicit($this, DataType::TYPE_STRING);
+ }
+ }
+
+ /**
+ * Add text.
+ *
+ * @param ITextElement $pText Rich text element
+ *
+ * @return $this
+ */
+ public function addText(ITextElement $pText)
+ {
+ $this->richTextElements[] = $pText;
+
+ return $this;
+ }
+
+ /**
+ * Create text.
+ *
+ * @param string $pText Text
+ *
+ * @return TextElement
+ */
+ public function createText($pText)
+ {
+ $objText = new TextElement($pText);
+ $this->addText($objText);
+
+ return $objText;
+ }
+
+ /**
+ * Create text run.
+ *
+ * @param string $pText Text
+ *
+ * @return Run
+ */
+ public function createTextRun($pText)
+ {
+ $objText = new Run($pText);
+ $this->addText($objText);
+
+ return $objText;
+ }
+
+ /**
+ * Get plain text.
+ *
+ * @return string
+ */
+ public function getPlainText()
+ {
+ // Return value
+ $returnValue = '';
+
+ // Loop through all ITextElements
+ foreach ($this->richTextElements as $text) {
+ $returnValue .= $text->getText();
+ }
+
+ return $returnValue;
+ }
+
+ /**
+ * Convert to string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getPlainText();
+ }
+
+ /**
+ * Get Rich Text elements.
+ *
+ * @return ITextElement[]
+ */
+ public function getRichTextElements()
+ {
+ return $this->richTextElements;
+ }
+
+ /**
+ * Set Rich Text elements.
+ *
+ * @param ITextElement[] $textElements Array of elements
+ *
+ * @return $this
+ */
+ public function setRichTextElements(array $textElements)
+ {
+ $this->richTextElements = $textElements;
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ $hashElements = '';
+ foreach ($this->richTextElements as $element) {
+ $hashElements .= $element->getHashCode();
+ }
+
+ return md5(
+ $hashElements .
+ __CLASS__
+ );
+ }
+
+ /**
+ * 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/Run.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/Run.php
new file mode 100755
index 0000000..592d0e3
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/Run.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\RichText;
+
+use PhpOffice\PhpSpreadsheet\Style\Font;
+
+class Run extends TextElement implements ITextElement
+{
+ /**
+ * Font.
+ *
+ * @var Font
+ */
+ private $font;
+
+ /**
+ * Create a new Run instance.
+ *
+ * @param string $pText Text
+ */
+ public function __construct($pText = '')
+ {
+ parent::__construct($pText);
+ // Initialise variables
+ $this->font = new Font();
+ }
+
+ /**
+ * Get font.
+ *
+ * @return null|\PhpOffice\PhpSpreadsheet\Style\Font
+ */
+ public function getFont()
+ {
+ return $this->font;
+ }
+
+ /**
+ * Set font.
+ *
+ * @param Font $pFont Font
+ *
+ * @return $this
+ */
+ public function setFont(?Font $pFont = null)
+ {
+ $this->font = $pFont;
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->getText() .
+ $this->font->getHashCode() .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php
new file mode 100755
index 0000000..f8be5d5
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\RichText;
+
+class TextElement implements ITextElement
+{
+ /**
+ * Text.
+ *
+ * @var string
+ */
+ private $text;
+
+ /**
+ * Create a new TextElement instance.
+ *
+ * @param string $pText Text
+ */
+ public function __construct($pText = '')
+ {
+ // Initialise variables
+ $this->text = $pText;
+ }
+
+ /**
+ * Get text.
+ *
+ * @return string Text
+ */
+ public function getText()
+ {
+ return $this->text;
+ }
+
+ /**
+ * Set text.
+ *
+ * @param $text string Text
+ *
+ * @return $this
+ */
+ public function setText($text)
+ {
+ $this->text = $text;
+
+ return $this;
+ }
+
+ /**
+ * Get font.
+ *
+ * @return null|\PhpOffice\PhpSpreadsheet\Style\Font
+ */
+ public function getFont()
+ {
+ return null;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->text .
+ __CLASS__
+ );
+ }
+
+ /**
+ * 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php
new file mode 100755
index 0000000..4e0c91e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php
@@ -0,0 +1,159 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Chart\Renderer\IRenderer;
+use PhpOffice\PhpSpreadsheet\Collection\Memory;
+use Psr\SimpleCache\CacheInterface;
+
+class Settings
+{
+ /**
+ * Class name of the chart renderer used for rendering charts
+ * eg: PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph.
+ *
+ * @var string
+ */
+ private static $chartRenderer;
+
+ /**
+ * Default options for libxml loader.
+ *
+ * @var int
+ */
+ private static $libXmlLoaderOptions = null;
+
+ /**
+ * Allow/disallow libxml_disable_entity_loader() call when not thread safe.
+ * Default behaviour is to do the check, but if you're running PHP versions
+ * 7.2 < 7.2.1
+ * then you may need to disable this check to prevent unwanted behaviour in other threads
+ * SECURITY WARNING: Changing this flag is not recommended.
+ *
+ * @var bool
+ */
+ private static $libXmlDisableEntityLoader = true;
+
+ /**
+ * The cache implementation to be used for cell collection.
+ *
+ * @var CacheInterface
+ */
+ private static $cache;
+
+ /**
+ * Set the locale code to use for formula translations and any special formatting.
+ *
+ * @param string $locale The locale code to use (e.g. "fr" or "pt_br" or "en_uk")
+ *
+ * @return bool Success or failure
+ */
+ public static function setLocale($locale)
+ {
+ return Calculation::getInstance()->setLocale($locale);
+ }
+
+ /**
+ * Identify to PhpSpreadsheet the external library to use for rendering charts.
+ *
+ * @param string $rendererClass Class name of the chart renderer
+ * eg: PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph
+ */
+ public static function setChartRenderer($rendererClass): void
+ {
+ if (!is_a($rendererClass, IRenderer::class, true)) {
+ throw new Exception('Chart renderer must implement ' . IRenderer::class);
+ }
+
+ self::$chartRenderer = $rendererClass;
+ }
+
+ /**
+ * Return the Chart Rendering Library that PhpSpreadsheet is currently configured to use.
+ *
+ * @return null|string Class name of the chart renderer
+ * eg: PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph
+ */
+ public static function getChartRenderer()
+ {
+ return self::$chartRenderer;
+ }
+
+ /**
+ * Set default options for libxml loader.
+ *
+ * @param int $options Default options for libxml loader
+ */
+ public static function setLibXmlLoaderOptions($options): void
+ {
+ if ($options === null && defined('LIBXML_DTDLOAD')) {
+ $options = LIBXML_DTDLOAD | LIBXML_DTDATTR;
+ }
+ self::$libXmlLoaderOptions = $options;
+ }
+
+ /**
+ * Get default options for libxml loader.
+ * Defaults to LIBXML_DTDLOAD | LIBXML_DTDATTR when not set explicitly.
+ *
+ * @return int Default options for libxml loader
+ */
+ public static function getLibXmlLoaderOptions()
+ {
+ if (self::$libXmlLoaderOptions === null && defined('LIBXML_DTDLOAD')) {
+ self::setLibXmlLoaderOptions(LIBXML_DTDLOAD | LIBXML_DTDATTR);
+ } elseif (self::$libXmlLoaderOptions === null) {
+ self::$libXmlLoaderOptions = true;
+ }
+
+ return self::$libXmlLoaderOptions;
+ }
+
+ /**
+ * Enable/Disable the entity loader for libxml loader.
+ * Allow/disallow libxml_disable_entity_loader() call when not thread safe.
+ * Default behaviour is to do the check, but if you're running PHP versions
+ * 7.2 < 7.2.1
+ * then you may need to disable this check to prevent unwanted behaviour in other threads
+ * SECURITY WARNING: Changing this flag to false is not recommended.
+ *
+ * @param bool $state
+ */
+ public static function setLibXmlDisableEntityLoader($state): void
+ {
+ self::$libXmlDisableEntityLoader = (bool) $state;
+ }
+
+ /**
+ * Return the state of the entity loader (disabled/enabled) for libxml loader.
+ *
+ * @return bool $state
+ */
+ public static function getLibXmlDisableEntityLoader()
+ {
+ return self::$libXmlDisableEntityLoader;
+ }
+
+ /**
+ * Sets the implementation of cache that should be used for cell collection.
+ */
+ public static function setCache(CacheInterface $cache): void
+ {
+ self::$cache = $cache;
+ }
+
+ /**
+ * Gets the implementation of cache that should be used for cell collection.
+ *
+ * @return CacheInterface
+ */
+ public static function getCache()
+ {
+ if (!self::$cache) {
+ self::$cache = new Memory();
+ }
+
+ return self::$cache;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php
new file mode 100755
index 0000000..97cbfbb
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class CodePage
+{
+ private static $pageArray = [
+ 0 => 'CP1252', // CodePage is not always correctly set when the xls file was saved by Apple's Numbers program
+ 367 => 'ASCII', // ASCII
+ 437 => 'CP437', // OEM US
+ //720 => 'notsupported', // OEM Arabic
+ 737 => 'CP737', // OEM Greek
+ 775 => 'CP775', // OEM Baltic
+ 850 => 'CP850', // OEM Latin I
+ 852 => 'CP852', // OEM Latin II (Central European)
+ 855 => 'CP855', // OEM Cyrillic
+ 857 => 'CP857', // OEM Turkish
+ 858 => 'CP858', // OEM Multilingual Latin I with Euro
+ 860 => 'CP860', // OEM Portugese
+ 861 => 'CP861', // OEM Icelandic
+ 862 => 'CP862', // OEM Hebrew
+ 863 => 'CP863', // OEM Canadian (French)
+ 864 => 'CP864', // OEM Arabic
+ 865 => 'CP865', // OEM Nordic
+ 866 => 'CP866', // OEM Cyrillic (Russian)
+ 869 => 'CP869', // OEM Greek (Modern)
+ 874 => 'CP874', // ANSI Thai
+ 932 => 'CP932', // ANSI Japanese Shift-JIS
+ 936 => 'CP936', // ANSI Chinese Simplified GBK
+ 949 => 'CP949', // ANSI Korean (Wansung)
+ 950 => 'CP950', // ANSI Chinese Traditional BIG5
+ 1200 => 'UTF-16LE', // UTF-16 (BIFF8)
+ 1250 => 'CP1250', // ANSI Latin II (Central European)
+ 1251 => 'CP1251', // ANSI Cyrillic
+ 1252 => 'CP1252', // ANSI Latin I (BIFF4-BIFF7)
+ 1253 => 'CP1253', // ANSI Greek
+ 1254 => 'CP1254', // ANSI Turkish
+ 1255 => 'CP1255', // ANSI Hebrew
+ 1256 => 'CP1256', // ANSI Arabic
+ 1257 => 'CP1257', // ANSI Baltic
+ 1258 => 'CP1258', // ANSI Vietnamese
+ 1361 => 'CP1361', // ANSI Korean (Johab)
+ 10000 => 'MAC', // Apple Roman
+ 10001 => 'CP932', // Macintosh Japanese
+ 10002 => 'CP950', // Macintosh Chinese Traditional
+ 10003 => 'CP1361', // Macintosh Korean
+ 10004 => 'MACARABIC', // Apple Arabic
+ 10005 => 'MACHEBREW', // Apple Hebrew
+ 10006 => 'MACGREEK', // Macintosh Greek
+ 10007 => 'MACCYRILLIC', // Macintosh Cyrillic
+ 10008 => 'CP936', // Macintosh - Simplified Chinese (GB 2312)
+ 10010 => 'MACROMANIA', // Macintosh Romania
+ 10017 => 'MACUKRAINE', // Macintosh Ukraine
+ 10021 => 'MACTHAI', // Macintosh Thai
+ 10029 => 'MACCENTRALEUROPE', // Macintosh Central Europe
+ 10079 => 'MACICELAND', // Macintosh Icelandic
+ 10081 => 'MACTURKISH', // Macintosh Turkish
+ 10082 => 'MACCROATIAN', // Macintosh Croatian
+ 21010 => 'UTF-16LE', // UTF-16 (BIFF8) This isn't correct, but some Excel writer libraries erroneously use Codepage 21010 for UTF-16LE
+ 32768 => 'MAC', // Apple Roman
+ //32769 => 'unsupported', // ANSI Latin I (BIFF2-BIFF3)
+ 65000 => 'UTF-7', // Unicode (UTF-7)
+ 65001 => 'UTF-8', // Unicode (UTF-8)
+ ];
+
+ /**
+ * Convert Microsoft Code Page Identifier to Code Page Name which iconv
+ * and mbstring understands.
+ *
+ * @param int $codePage Microsoft Code Page Indentifier
+ *
+ * @return string Code Page Name
+ */
+ public static function numberToName(int $codePage): string
+ {
+ if (array_key_exists($codePage, self::$pageArray)) {
+ return self::$pageArray[$codePage];
+ }
+ if ($codePage == 720 || $codePage == 32769) {
+ throw new PhpSpreadsheetException("Code page $codePage not supported."); // OEM Arabic
+ }
+
+ throw new PhpSpreadsheetException('Unknown codepage: ' . $codePage);
+ }
+
+ public static function getEncodings(): array
+ {
+ return self::$pageArray;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php
new file mode 100755
index 0000000..9dc9929
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php
@@ -0,0 +1,483 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared;
+
+use DateTimeInterface;
+use DateTimeZone;
+use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+
+class Date
+{
+ /** constants */
+ const CALENDAR_WINDOWS_1900 = 1900; // Base date of 1st Jan 1900 = 1.0
+ const CALENDAR_MAC_1904 = 1904; // Base date of 2nd Jan 1904 = 1.0
+
+ /**
+ * Names of the months of the year, indexed by shortname
+ * Planned usage for locale settings.
+ *
+ * @var string[]
+ */
+ public static $monthNames = [
+ 'Jan' => 'January',
+ 'Feb' => 'February',
+ 'Mar' => 'March',
+ 'Apr' => 'April',
+ 'May' => 'May',
+ 'Jun' => 'June',
+ 'Jul' => 'July',
+ 'Aug' => 'August',
+ 'Sep' => 'September',
+ 'Oct' => 'October',
+ 'Nov' => 'November',
+ 'Dec' => 'December',
+ ];
+
+ /**
+ * @var string[]
+ */
+ public static $numberSuffixes = [
+ 'st',
+ 'nd',
+ 'rd',
+ 'th',
+ ];
+
+ /**
+ * Base calendar year to use for calculations
+ * Value is either CALENDAR_WINDOWS_1900 (1900) or CALENDAR_MAC_1904 (1904).
+ *
+ * @var int
+ */
+ protected static $excelCalendar = self::CALENDAR_WINDOWS_1900;
+
+ /**
+ * Default timezone to use for DateTime objects.
+ *
+ * @var null|DateTimeZone
+ */
+ protected static $defaultTimeZone;
+
+ /**
+ * Set the Excel calendar (Windows 1900 or Mac 1904).
+ *
+ * @param int $baseDate Excel base date (1900 or 1904)
+ *
+ * @return bool Success or failure
+ */
+ public static function setExcelCalendar($baseDate)
+ {
+ if (($baseDate == self::CALENDAR_WINDOWS_1900) ||
+ ($baseDate == self::CALENDAR_MAC_1904)) {
+ self::$excelCalendar = $baseDate;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return the Excel calendar (Windows 1900 or Mac 1904).
+ *
+ * @return int Excel base date (1900 or 1904)
+ */
+ public static function getExcelCalendar()
+ {
+ return self::$excelCalendar;
+ }
+
+ /**
+ * Set the Default timezone to use for dates.
+ *
+ * @param DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions
+ *
+ * @return bool Success or failure
+ */
+ public static function setDefaultTimezone($timeZone)
+ {
+ try {
+ $timeZone = self::validateTimeZone($timeZone);
+ self::$defaultTimeZone = $timeZone;
+ $retval = true;
+ } catch (PhpSpreadsheetException $e) {
+ $retval = false;
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Return the Default timezone being used for dates.
+ *
+ * @return DateTimeZone The timezone being used as default for Excel timestamp to PHP DateTime object
+ */
+ public static function getDefaultTimezone()
+ {
+ if (self::$defaultTimeZone === null) {
+ self::$defaultTimeZone = new DateTimeZone('UTC');
+ }
+
+ return self::$defaultTimeZone;
+ }
+
+ /**
+ * Validate a timezone.
+ *
+ * @param DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object
+ *
+ * @return DateTimeZone The timezone as a timezone object
+ */
+ private static function validateTimeZone($timeZone)
+ {
+ if ($timeZone instanceof DateTimeZone) {
+ return $timeZone;
+ }
+ if (in_array($timeZone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC))) {
+ return new DateTimeZone($timeZone);
+ }
+
+ throw new PhpSpreadsheetException('Invalid timezone');
+ }
+
+ /**
+ * Convert a MS serialized datetime value from Excel to a PHP Date/Time object.
+ *
+ * @param float|int $excelTimestamp MS Excel serialized date/time value
+ * @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp,
+ * if you don't want to treat it as a UTC value
+ * Use the default (UST) unless you absolutely need a conversion
+ *
+ * @return \DateTime PHP date/time object
+ */
+ public static function excelToDateTimeObject($excelTimestamp, $timeZone = null)
+ {
+ $timeZone = ($timeZone === null) ? self::getDefaultTimezone() : self::validateTimeZone($timeZone);
+ if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) {
+ if ($excelTimestamp < 1.0) {
+ // Unix timestamp base date
+ $baseDate = new \DateTime('1970-01-01', $timeZone);
+ } else {
+ // MS Excel calendar base dates
+ if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) {
+ // Allow adjustment for 1900 Leap Year in MS Excel
+ $baseDate = ($excelTimestamp < 60) ? new \DateTime('1899-12-31', $timeZone) : new \DateTime('1899-12-30', $timeZone);
+ } else {
+ $baseDate = new \DateTime('1904-01-01', $timeZone);
+ }
+ }
+ } else {
+ $baseDate = new \DateTime('1899-12-30', $timeZone);
+ }
+
+ $days = floor($excelTimestamp);
+ $partDay = $excelTimestamp - $days;
+ $hours = floor($partDay * 24);
+ $partDay = $partDay * 24 - $hours;
+ $minutes = floor($partDay * 60);
+ $partDay = $partDay * 60 - $minutes;
+ $seconds = round($partDay * 60);
+
+ if ($days >= 0) {
+ $days = '+' . $days;
+ }
+ $interval = $days . ' days';
+
+ return $baseDate->modify($interval)
+ ->setTime((int) $hours, (int) $minutes, (int) $seconds);
+ }
+
+ /**
+ * Convert a MS serialized datetime value from Excel to a unix timestamp.
+ *
+ * @param float|int $excelTimestamp MS Excel serialized date/time value
+ * @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp,
+ * if you don't want to treat it as a UTC value
+ * Use the default (UST) unless you absolutely need a conversion
+ *
+ * @return int Unix timetamp for this date/time
+ */
+ public static function excelToTimestamp($excelTimestamp, $timeZone = null)
+ {
+ return (int) self::excelToDateTimeObject($excelTimestamp, $timeZone)
+ ->format('U');
+ }
+
+ /**
+ * Convert a date from PHP to an MS Excel serialized date/time value.
+ *
+ * @param mixed $dateValue Unix Timestamp or PHP DateTime object or a string
+ *
+ * @return bool|float Excel date/time value
+ * or boolean FALSE on failure
+ */
+ public static function PHPToExcel($dateValue)
+ {
+ if ((is_object($dateValue)) && ($dateValue instanceof DateTimeInterface)) {
+ return self::dateTimeToExcel($dateValue);
+ } elseif (is_numeric($dateValue)) {
+ return self::timestampToExcel($dateValue);
+ } elseif (is_string($dateValue)) {
+ return self::stringToExcel($dateValue);
+ }
+
+ return false;
+ }
+
+ /**
+ * Convert a PHP DateTime object to an MS Excel serialized date/time value.
+ *
+ * @param DateTimeInterface $dateValue PHP DateTime object
+ *
+ * @return float MS Excel serialized date/time value
+ */
+ public static function dateTimeToExcel(DateTimeInterface $dateValue)
+ {
+ return self::formattedPHPToExcel(
+ (int) $dateValue->format('Y'),
+ (int) $dateValue->format('m'),
+ (int) $dateValue->format('d'),
+ (int) $dateValue->format('H'),
+ (int) $dateValue->format('i'),
+ (int) $dateValue->format('s')
+ );
+ }
+
+ /**
+ * Convert a Unix timestamp to an MS Excel serialized date/time value.
+ *
+ * @param int $dateValue Unix Timestamp
+ *
+ * @return float MS Excel serialized date/time value
+ */
+ public static function timestampToExcel($dateValue)
+ {
+ if (!is_numeric($dateValue)) {
+ return false;
+ }
+
+ return self::dateTimeToExcel(new \DateTime('@' . $dateValue));
+ }
+
+ /**
+ * formattedPHPToExcel.
+ *
+ * @param int $year
+ * @param int $month
+ * @param int $day
+ * @param int $hours
+ * @param int $minutes
+ * @param int $seconds
+ *
+ * @return float Excel date/time value
+ */
+ public static function formattedPHPToExcel($year, $month, $day, $hours = 0, $minutes = 0, $seconds = 0)
+ {
+ if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) {
+ //
+ // Fudge factor for the erroneous fact that the year 1900 is treated as a Leap Year in MS Excel
+ // This affects every date following 28th February 1900
+ //
+ $excel1900isLeapYear = true;
+ if (($year == 1900) && ($month <= 2)) {
+ $excel1900isLeapYear = false;
+ }
+ $myexcelBaseDate = 2415020;
+ } else {
+ $myexcelBaseDate = 2416481;
+ $excel1900isLeapYear = false;
+ }
+
+ // Julian base date Adjustment
+ if ($month > 2) {
+ $month -= 3;
+ } else {
+ $month += 9;
+ --$year;
+ }
+
+ // Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0)
+ $century = substr($year, 0, 2);
+ $decade = substr($year, 2, 2);
+ $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myexcelBaseDate + $excel1900isLeapYear;
+
+ $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400;
+
+ return (float) $excelDate + $excelTime;
+ }
+
+ /**
+ * Is a given cell a date/time?
+ *
+ * @return bool
+ */
+ public static function isDateTime(Cell $pCell)
+ {
+ return is_numeric($pCell->getCalculatedValue()) &&
+ self::isDateTimeFormat(
+ $pCell->getWorksheet()->getStyle(
+ $pCell->getCoordinate()
+ )->getNumberFormat()
+ );
+ }
+
+ /**
+ * Is a given number format a date/time?
+ *
+ * @return bool
+ */
+ public static function isDateTimeFormat(NumberFormat $pFormat)
+ {
+ return self::isDateTimeFormatCode($pFormat->getFormatCode());
+ }
+
+ private static $possibleDateFormatCharacters = 'eymdHs';
+
+ /**
+ * Is a given number format code a date/time?
+ *
+ * @param string $pFormatCode
+ *
+ * @return bool
+ */
+ public static function isDateTimeFormatCode($pFormatCode)
+ {
+ if (strtolower($pFormatCode) === strtolower(NumberFormat::FORMAT_GENERAL)) {
+ // "General" contains an epoch letter 'e', so we trap for it explicitly here (case-insensitive check)
+ return false;
+ }
+ if (preg_match('/[0#]E[+-]0/i', $pFormatCode)) {
+ // Scientific format
+ return false;
+ }
+
+ // Switch on formatcode
+ switch ($pFormatCode) {
+ // Explicitly defined date formats
+ case NumberFormat::FORMAT_DATE_YYYYMMDD:
+ case NumberFormat::FORMAT_DATE_YYYYMMDD2:
+ case NumberFormat::FORMAT_DATE_DDMMYYYY:
+ case NumberFormat::FORMAT_DATE_DMYSLASH:
+ case NumberFormat::FORMAT_DATE_DMYMINUS:
+ case NumberFormat::FORMAT_DATE_DMMINUS:
+ case NumberFormat::FORMAT_DATE_MYMINUS:
+ case NumberFormat::FORMAT_DATE_DATETIME:
+ case NumberFormat::FORMAT_DATE_TIME1:
+ case NumberFormat::FORMAT_DATE_TIME2:
+ case NumberFormat::FORMAT_DATE_TIME3:
+ case NumberFormat::FORMAT_DATE_TIME4:
+ case NumberFormat::FORMAT_DATE_TIME5:
+ case NumberFormat::FORMAT_DATE_TIME6:
+ case NumberFormat::FORMAT_DATE_TIME7:
+ case NumberFormat::FORMAT_DATE_TIME8:
+ case NumberFormat::FORMAT_DATE_YYYYMMDDSLASH:
+ case NumberFormat::FORMAT_DATE_XLSX14:
+ case NumberFormat::FORMAT_DATE_XLSX15:
+ case NumberFormat::FORMAT_DATE_XLSX16:
+ case NumberFormat::FORMAT_DATE_XLSX17:
+ case NumberFormat::FORMAT_DATE_XLSX22:
+ return true;
+ }
+
+ // Typically number, currency or accounting (or occasionally fraction) formats
+ if ((substr($pFormatCode, 0, 1) == '_') || (substr($pFormatCode, 0, 2) == '0 ')) {
+ return false;
+ }
+ // Try checking for any of the date formatting characters that don't appear within square braces
+ if (preg_match('/(^|\])[^\[]*[' . self::$possibleDateFormatCharacters . ']/i', $pFormatCode)) {
+ // We might also have a format mask containing quoted strings...
+ // we don't want to test for any of our characters within the quoted blocks
+ if (strpos($pFormatCode, '"') !== false) {
+ $segMatcher = false;
+ foreach (explode('"', $pFormatCode) as $subVal) {
+ // Only test in alternate array entries (the non-quoted blocks)
+ if (($segMatcher = !$segMatcher) &&
+ (preg_match('/(^|\])[^\[]*[' . self::$possibleDateFormatCharacters . ']/i', $subVal))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ // No date...
+ return false;
+ }
+
+ /**
+ * Convert a date/time string to Excel time.
+ *
+ * @param string $dateValue Examples: '2009-12-31', '2009-12-31 15:59', '2009-12-31 15:59:10'
+ *
+ * @return false|float Excel date/time serial value
+ */
+ public static function stringToExcel($dateValue)
+ {
+ if (strlen($dateValue) < 2) {
+ return false;
+ }
+ if (!preg_match('/^(\d{1,4}[ \.\/\-][A-Z]{3,9}([ \.\/\-]\d{1,4})?|[A-Z]{3,9}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?|\d{1,4}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?)( \d{1,2}:\d{1,2}(:\d{1,2})?)?$/iu', $dateValue)) {
+ return false;
+ }
+
+ $dateValueNew = DateTime::DATEVALUE($dateValue);
+
+ if ($dateValueNew === Functions::VALUE()) {
+ return false;
+ }
+
+ if (strpos($dateValue, ':') !== false) {
+ $timeValue = DateTime::TIMEVALUE($dateValue);
+ if ($timeValue === Functions::VALUE()) {
+ return false;
+ }
+ $dateValueNew += $timeValue;
+ }
+
+ return $dateValueNew;
+ }
+
+ /**
+ * Converts a month name (either a long or a short name) to a month number.
+ *
+ * @param string $month Month name or abbreviation
+ *
+ * @return int|string Month number (1 - 12), or the original string argument if it isn't a valid month name
+ */
+ public static function monthStringToNumber($month)
+ {
+ $monthIndex = 1;
+ foreach (self::$monthNames as $shortMonthName => $longMonthName) {
+ if (($month === $longMonthName) || ($month === $shortMonthName)) {
+ return $monthIndex;
+ }
+ ++$monthIndex;
+ }
+
+ return $month;
+ }
+
+ /**
+ * Strips an ordinal from a numeric value.
+ *
+ * @param string $day Day number with an ordinal
+ *
+ * @return int|string The integer value with any ordinal stripped, or the original string argument if it isn't a valid numeric
+ */
+ public static function dayStringToNumber($day)
+ {
+ $strippedDayValue = (str_replace(self::$numberSuffixes, '', $day));
+ if (is_numeric($strippedDayValue)) {
+ return (int) $strippedDayValue;
+ }
+
+ return $day;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php
new file mode 100755
index 0000000..25d6910
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php
@@ -0,0 +1,249 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared;
+
+class Drawing
+{
+ /**
+ * Convert pixels to EMU.
+ *
+ * @param int $pValue Value in pixels
+ *
+ * @return int Value in EMU
+ */
+ public static function pixelsToEMU($pValue)
+ {
+ return round($pValue * 9525);
+ }
+
+ /**
+ * Convert EMU to pixels.
+ *
+ * @param int $pValue Value in EMU
+ *
+ * @return int Value in pixels
+ */
+ public static function EMUToPixels($pValue)
+ {
+ if ($pValue != 0) {
+ return round($pValue / 9525);
+ }
+
+ return 0;
+ }
+
+ /**
+ * Convert pixels to column width. Exact algorithm not known.
+ * By inspection of a real Excel file using Calibri 11, one finds 1000px ~ 142.85546875
+ * This gives a conversion factor of 7. Also, we assume that pixels and font size are proportional.
+ *
+ * @param int $pValue Value in pixels
+ * @param \PhpOffice\PhpSpreadsheet\Style\Font $pDefaultFont Default font of the workbook
+ *
+ * @return int Value in cell dimension
+ */
+ public static function pixelsToCellDimension($pValue, \PhpOffice\PhpSpreadsheet\Style\Font $pDefaultFont)
+ {
+ // Font name and size
+ $name = $pDefaultFont->getName();
+ $size = $pDefaultFont->getSize();
+
+ if (isset(Font::$defaultColumnWidths[$name][$size])) {
+ // Exact width can be determined
+ $colWidth = $pValue * Font::$defaultColumnWidths[$name][$size]['width'] / Font::$defaultColumnWidths[$name][$size]['px'];
+ } else {
+ // We don't have data for this particular font and size, use approximation by
+ // extrapolating from Calibri 11
+ $colWidth = $pValue * 11 * Font::$defaultColumnWidths['Calibri'][11]['width'] / Font::$defaultColumnWidths['Calibri'][11]['px'] / $size;
+ }
+
+ return $colWidth;
+ }
+
+ /**
+ * Convert column width from (intrinsic) Excel units to pixels.
+ *
+ * @param float $pValue Value in cell dimension
+ * @param \PhpOffice\PhpSpreadsheet\Style\Font $pDefaultFont Default font of the workbook
+ *
+ * @return int Value in pixels
+ */
+ public static function cellDimensionToPixels($pValue, \PhpOffice\PhpSpreadsheet\Style\Font $pDefaultFont)
+ {
+ // Font name and size
+ $name = $pDefaultFont->getName();
+ $size = $pDefaultFont->getSize();
+
+ if (isset(Font::$defaultColumnWidths[$name][$size])) {
+ // Exact width can be determined
+ $colWidth = $pValue * Font::$defaultColumnWidths[$name][$size]['px'] / Font::$defaultColumnWidths[$name][$size]['width'];
+ } else {
+ // We don't have data for this particular font and size, use approximation by
+ // extrapolating from Calibri 11
+ $colWidth = $pValue * $size * Font::$defaultColumnWidths['Calibri'][11]['px'] / Font::$defaultColumnWidths['Calibri'][11]['width'] / 11;
+ }
+
+ // Round pixels to closest integer
+ $colWidth = (int) round($colWidth);
+
+ return $colWidth;
+ }
+
+ /**
+ * Convert pixels to points.
+ *
+ * @param int $pValue Value in pixels
+ *
+ * @return float Value in points
+ */
+ public static function pixelsToPoints($pValue)
+ {
+ return $pValue * 0.67777777;
+ }
+
+ /**
+ * Convert points to pixels.
+ *
+ * @param int $pValue Value in points
+ *
+ * @return int Value in pixels
+ */
+ public static function pointsToPixels($pValue)
+ {
+ if ($pValue != 0) {
+ return (int) ceil($pValue * 1.333333333);
+ }
+
+ return 0;
+ }
+
+ /**
+ * Convert degrees to angle.
+ *
+ * @param int $pValue Degrees
+ *
+ * @return int Angle
+ */
+ public static function degreesToAngle($pValue)
+ {
+ return (int) round($pValue * 60000);
+ }
+
+ /**
+ * Convert angle to degrees.
+ *
+ * @param int $pValue Angle
+ *
+ * @return int Degrees
+ */
+ public static function angleToDegrees($pValue)
+ {
+ if ($pValue != 0) {
+ return round($pValue / 60000);
+ }
+
+ return 0;
+ }
+
+ /**
+ * Create a new image from file. By alexander at alexauto dot nl.
+ *
+ * @see http://www.php.net/manual/en/function.imagecreatefromwbmp.php#86214
+ *
+ * @param string $p_sFile Path to Windows DIB (BMP) image
+ *
+ * @return resource
+ */
+ public static function imagecreatefrombmp($p_sFile)
+ {
+ // Load the image into a string
+ $file = fopen($p_sFile, 'rb');
+ $read = fread($file, 10);
+ while (!feof($file) && ($read != '')) {
+ $read .= fread($file, 1024);
+ }
+
+ $temp = unpack('H*', $read);
+ $hex = $temp[1];
+ $header = substr($hex, 0, 108);
+
+ // Process the header
+ // Structure: http://www.fastgraph.com/help/bmp_header_format.html
+ if (substr($header, 0, 4) == '424d') {
+ // Cut it in parts of 2 bytes
+ $header_parts = str_split($header, 2);
+
+ // Get the width 4 bytes
+ $width = hexdec($header_parts[19] . $header_parts[18]);
+
+ // Get the height 4 bytes
+ $height = hexdec($header_parts[23] . $header_parts[22]);
+
+ // Unset the header params
+ unset($header_parts);
+ }
+
+ // Define starting X and Y
+ $x = 0;
+ $y = 1;
+
+ // Create newimage
+ $image = imagecreatetruecolor($width, $height);
+
+ // Grab the body from the image
+ $body = substr($hex, 108);
+
+ // Calculate if padding at the end-line is needed
+ // Divided by two to keep overview.
+ // 1 byte = 2 HEX-chars
+ $body_size = (strlen($body) / 2);
+ $header_size = ($width * $height);
+
+ // Use end-line padding? Only when needed
+ $usePadding = ($body_size > ($header_size * 3) + 4);
+
+ // Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption
+ // Calculate the next DWORD-position in the body
+ for ($i = 0; $i < $body_size; $i += 3) {
+ // Calculate line-ending and padding
+ if ($x >= $width) {
+ // If padding needed, ignore image-padding
+ // Shift i to the ending of the current 32-bit-block
+ if ($usePadding) {
+ $i += $width % 4;
+ }
+
+ // Reset horizontal position
+ $x = 0;
+
+ // Raise the height-position (bottom-up)
+ ++$y;
+
+ // Reached the image-height? Break the for-loop
+ if ($y > $height) {
+ break;
+ }
+ }
+
+ // Calculation of the RGB-pixel (defined as BGR in image-data)
+ // Define $i_pos as absolute position in the body
+ $i_pos = $i * 2;
+ $r = hexdec($body[$i_pos + 4] . $body[$i_pos + 5]);
+ $g = hexdec($body[$i_pos + 2] . $body[$i_pos + 3]);
+ $b = hexdec($body[$i_pos] . $body[$i_pos + 1]);
+
+ // Calculate and draw the pixel
+ $color = imagecolorallocate($image, $r, $g, $b);
+ imagesetpixel($image, $x, $height - $y, $color);
+
+ // Raise the horizontal position
+ ++$x;
+ }
+
+ // Unset the body / free the memory
+ unset($body);
+
+ // Return image-object
+ return $image;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php
new file mode 100755
index 0000000..c6d0a6f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared;
+
+class Escher
+{
+ /**
+ * Drawing Group Container.
+ *
+ * @var Escher\DggContainer
+ */
+ private $dggContainer;
+
+ /**
+ * Drawing Container.
+ *
+ * @var Escher\DgContainer
+ */
+ private $dgContainer;
+
+ /**
+ * Get Drawing Group Container.
+ *
+ * @return Escher\DggContainer
+ */
+ public function getDggContainer()
+ {
+ return $this->dggContainer;
+ }
+
+ /**
+ * Set Drawing Group Container.
+ *
+ * @param Escher\DggContainer $dggContainer
+ *
+ * @return Escher\DggContainer
+ */
+ public function setDggContainer($dggContainer)
+ {
+ return $this->dggContainer = $dggContainer;
+ }
+
+ /**
+ * Get Drawing Container.
+ *
+ * @return Escher\DgContainer
+ */
+ public function getDgContainer()
+ {
+ return $this->dgContainer;
+ }
+
+ /**
+ * Set Drawing Container.
+ *
+ * @param Escher\DgContainer $dgContainer
+ *
+ * @return Escher\DgContainer
+ */
+ public function setDgContainer($dgContainer)
+ {
+ return $this->dgContainer = $dgContainer;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php
new file mode 100755
index 0000000..b0d75d7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Escher;
+
+class DgContainer
+{
+ /**
+ * Drawing index, 1-based.
+ *
+ * @var int
+ */
+ private $dgId;
+
+ /**
+ * Last shape index in this drawing.
+ *
+ * @var int
+ */
+ private $lastSpId;
+
+ private $spgrContainer;
+
+ public function getDgId()
+ {
+ return $this->dgId;
+ }
+
+ public function setDgId($value): void
+ {
+ $this->dgId = $value;
+ }
+
+ public function getLastSpId()
+ {
+ return $this->lastSpId;
+ }
+
+ public function setLastSpId($value): void
+ {
+ $this->lastSpId = $value;
+ }
+
+ public function getSpgrContainer()
+ {
+ return $this->spgrContainer;
+ }
+
+ public function setSpgrContainer($spgrContainer)
+ {
+ return $this->spgrContainer = $spgrContainer;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php
new file mode 100755
index 0000000..1da8772
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
+
+class SpgrContainer
+{
+ /**
+ * Parent Shape Group Container.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer
+ */
+ private $parent;
+
+ /**
+ * Shape Container collection.
+ *
+ * @var array
+ */
+ private $children = [];
+
+ /**
+ * Set parent Shape Group Container.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer $parent
+ */
+ public function setParent($parent): void
+ {
+ $this->parent = $parent;
+ }
+
+ /**
+ * Get the parent Shape Group Container if any.
+ *
+ * @return null|\PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Add a child. This will be either spgrContainer or spContainer.
+ *
+ * @param mixed $child
+ */
+ public function addChild($child): void
+ {
+ $this->children[] = $child;
+ $child->setParent($this);
+ }
+
+ /**
+ * Get collection of Shape Containers.
+ */
+ public function getChildren()
+ {
+ return $this->children;
+ }
+
+ /**
+ * Recursively get all spContainers within this spgrContainer.
+ *
+ * @return SpgrContainer\SpContainer[]
+ */
+ public function getAllSpContainers()
+ {
+ $allSpContainers = [];
+
+ foreach ($this->children as $child) {
+ if ($child instanceof self) {
+ $allSpContainers = array_merge($allSpContainers, $child->getAllSpContainers());
+ } else {
+ $allSpContainers[] = $child;
+ }
+ }
+
+ return $allSpContainers;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php
new file mode 100755
index 0000000..8a81ff5
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php
@@ -0,0 +1,369 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
+
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
+
+class SpContainer
+{
+ /**
+ * Parent Shape Group Container.
+ *
+ * @var SpgrContainer
+ */
+ private $parent;
+
+ /**
+ * Is this a group shape?
+ *
+ * @var bool
+ */
+ private $spgr = false;
+
+ /**
+ * Shape type.
+ *
+ * @var int
+ */
+ private $spType;
+
+ /**
+ * Shape flag.
+ *
+ * @var int
+ */
+ private $spFlag;
+
+ /**
+ * Shape index (usually group shape has index 0, and the rest: 1,2,3...).
+ *
+ * @var int
+ */
+ private $spId;
+
+ /**
+ * Array of options.
+ *
+ * @var array
+ */
+ private $OPT;
+
+ /**
+ * Cell coordinates of upper-left corner of shape, e.g. 'A1'.
+ *
+ * @var string
+ */
+ private $startCoordinates;
+
+ /**
+ * Horizontal offset of upper-left corner of shape measured in 1/1024 of column width.
+ *
+ * @var int
+ */
+ private $startOffsetX;
+
+ /**
+ * Vertical offset of upper-left corner of shape measured in 1/256 of row height.
+ *
+ * @var int
+ */
+ private $startOffsetY;
+
+ /**
+ * Cell coordinates of bottom-right corner of shape, e.g. 'B2'.
+ *
+ * @var string
+ */
+ private $endCoordinates;
+
+ /**
+ * Horizontal offset of bottom-right corner of shape measured in 1/1024 of column width.
+ *
+ * @var int
+ */
+ private $endOffsetX;
+
+ /**
+ * Vertical offset of bottom-right corner of shape measured in 1/256 of row height.
+ *
+ * @var int
+ */
+ private $endOffsetY;
+
+ /**
+ * Set parent Shape Group Container.
+ *
+ * @param SpgrContainer $parent
+ */
+ public function setParent($parent): void
+ {
+ $this->parent = $parent;
+ }
+
+ /**
+ * Get the parent Shape Group Container.
+ *
+ * @return SpgrContainer
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Set whether this is a group shape.
+ *
+ * @param bool $value
+ */
+ public function setSpgr($value): void
+ {
+ $this->spgr = $value;
+ }
+
+ /**
+ * Get whether this is a group shape.
+ *
+ * @return bool
+ */
+ public function getSpgr()
+ {
+ return $this->spgr;
+ }
+
+ /**
+ * Set the shape type.
+ *
+ * @param int $value
+ */
+ public function setSpType($value): void
+ {
+ $this->spType = $value;
+ }
+
+ /**
+ * Get the shape type.
+ *
+ * @return int
+ */
+ public function getSpType()
+ {
+ return $this->spType;
+ }
+
+ /**
+ * Set the shape flag.
+ *
+ * @param int $value
+ */
+ public function setSpFlag($value): void
+ {
+ $this->spFlag = $value;
+ }
+
+ /**
+ * Get the shape flag.
+ *
+ * @return int
+ */
+ public function getSpFlag()
+ {
+ return $this->spFlag;
+ }
+
+ /**
+ * Set the shape index.
+ *
+ * @param int $value
+ */
+ public function setSpId($value): void
+ {
+ $this->spId = $value;
+ }
+
+ /**
+ * Get the shape index.
+ *
+ * @return int
+ */
+ public function getSpId()
+ {
+ return $this->spId;
+ }
+
+ /**
+ * Set an option for the Shape Group Container.
+ *
+ * @param int $property The number specifies the option
+ * @param mixed $value
+ */
+ public function setOPT($property, $value): void
+ {
+ $this->OPT[$property] = $value;
+ }
+
+ /**
+ * Get an option for the Shape Group Container.
+ *
+ * @param int $property The number specifies the option
+ *
+ * @return mixed
+ */
+ public function getOPT($property)
+ {
+ if (isset($this->OPT[$property])) {
+ return $this->OPT[$property];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the collection of options.
+ *
+ * @return array
+ */
+ public function getOPTCollection()
+ {
+ return $this->OPT;
+ }
+
+ /**
+ * Set cell coordinates of upper-left corner of shape.
+ *
+ * @param string $value eg: 'A1'
+ */
+ public function setStartCoordinates($value): void
+ {
+ $this->startCoordinates = $value;
+ }
+
+ /**
+ * Get cell coordinates of upper-left corner of shape.
+ *
+ * @return string
+ */
+ public function getStartCoordinates()
+ {
+ return $this->startCoordinates;
+ }
+
+ /**
+ * Set offset in x-direction of upper-left corner of shape measured in 1/1024 of column width.
+ *
+ * @param int $startOffsetX
+ */
+ public function setStartOffsetX($startOffsetX): void
+ {
+ $this->startOffsetX = $startOffsetX;
+ }
+
+ /**
+ * Get offset in x-direction of upper-left corner of shape measured in 1/1024 of column width.
+ *
+ * @return int
+ */
+ public function getStartOffsetX()
+ {
+ return $this->startOffsetX;
+ }
+
+ /**
+ * Set offset in y-direction of upper-left corner of shape measured in 1/256 of row height.
+ *
+ * @param int $startOffsetY
+ */
+ public function setStartOffsetY($startOffsetY): void
+ {
+ $this->startOffsetY = $startOffsetY;
+ }
+
+ /**
+ * Get offset in y-direction of upper-left corner of shape measured in 1/256 of row height.
+ *
+ * @return int
+ */
+ public function getStartOffsetY()
+ {
+ return $this->startOffsetY;
+ }
+
+ /**
+ * Set cell coordinates of bottom-right corner of shape.
+ *
+ * @param string $value eg: 'A1'
+ */
+ public function setEndCoordinates($value): void
+ {
+ $this->endCoordinates = $value;
+ }
+
+ /**
+ * Get cell coordinates of bottom-right corner of shape.
+ *
+ * @return string
+ */
+ public function getEndCoordinates()
+ {
+ return $this->endCoordinates;
+ }
+
+ /**
+ * Set offset in x-direction of bottom-right corner of shape measured in 1/1024 of column width.
+ *
+ * @param int $endOffsetX
+ */
+ public function setEndOffsetX($endOffsetX): void
+ {
+ $this->endOffsetX = $endOffsetX;
+ }
+
+ /**
+ * Get offset in x-direction of bottom-right corner of shape measured in 1/1024 of column width.
+ *
+ * @return int
+ */
+ public function getEndOffsetX()
+ {
+ return $this->endOffsetX;
+ }
+
+ /**
+ * Set offset in y-direction of bottom-right corner of shape measured in 1/256 of row height.
+ *
+ * @param int $endOffsetY
+ */
+ public function setEndOffsetY($endOffsetY): void
+ {
+ $this->endOffsetY = $endOffsetY;
+ }
+
+ /**
+ * Get offset in y-direction of bottom-right corner of shape measured in 1/256 of row height.
+ *
+ * @return int
+ */
+ public function getEndOffsetY()
+ {
+ return $this->endOffsetY;
+ }
+
+ /**
+ * Get the nesting level of this spContainer. This is the number of spgrContainers between this spContainer and
+ * the dgContainer. A value of 1 = immediately within first spgrContainer
+ * Higher nesting level occurs if and only if spContainer is part of a shape group.
+ *
+ * @return int Nesting level
+ */
+ public function getNestingLevel()
+ {
+ $nestingLevel = 0;
+
+ $parent = $this->getParent();
+ while ($parent instanceof SpgrContainer) {
+ ++$nestingLevel;
+ $parent = $parent->getParent();
+ }
+
+ return $nestingLevel;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php
new file mode 100755
index 0000000..1bd15b9
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php
@@ -0,0 +1,175 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Escher;
+
+class DggContainer
+{
+ /**
+ * Maximum shape index of all shapes in all drawings increased by one.
+ *
+ * @var int
+ */
+ private $spIdMax;
+
+ /**
+ * Total number of drawings saved.
+ *
+ * @var int
+ */
+ private $cDgSaved;
+
+ /**
+ * Total number of shapes saved (including group shapes).
+ *
+ * @var int
+ */
+ private $cSpSaved;
+
+ /**
+ * BLIP Store Container.
+ *
+ * @var DggContainer\BstoreContainer
+ */
+ private $bstoreContainer;
+
+ /**
+ * Array of options for the drawing group.
+ *
+ * @var array
+ */
+ private $OPT = [];
+
+ /**
+ * Array of identifier clusters containg information about the maximum shape identifiers.
+ *
+ * @var array
+ */
+ private $IDCLs = [];
+
+ /**
+ * Get maximum shape index of all shapes in all drawings (plus one).
+ *
+ * @return int
+ */
+ public function getSpIdMax()
+ {
+ return $this->spIdMax;
+ }
+
+ /**
+ * Set maximum shape index of all shapes in all drawings (plus one).
+ *
+ * @param int $value
+ */
+ public function setSpIdMax($value): void
+ {
+ $this->spIdMax = $value;
+ }
+
+ /**
+ * Get total number of drawings saved.
+ *
+ * @return int
+ */
+ public function getCDgSaved()
+ {
+ return $this->cDgSaved;
+ }
+
+ /**
+ * Set total number of drawings saved.
+ *
+ * @param int $value
+ */
+ public function setCDgSaved($value): void
+ {
+ $this->cDgSaved = $value;
+ }
+
+ /**
+ * Get total number of shapes saved (including group shapes).
+ *
+ * @return int
+ */
+ public function getCSpSaved()
+ {
+ return $this->cSpSaved;
+ }
+
+ /**
+ * Set total number of shapes saved (including group shapes).
+ *
+ * @param int $value
+ */
+ public function setCSpSaved($value): void
+ {
+ $this->cSpSaved = $value;
+ }
+
+ /**
+ * Get BLIP Store Container.
+ *
+ * @return DggContainer\BstoreContainer
+ */
+ public function getBstoreContainer()
+ {
+ return $this->bstoreContainer;
+ }
+
+ /**
+ * Set BLIP Store Container.
+ *
+ * @param DggContainer\BstoreContainer $bstoreContainer
+ */
+ public function setBstoreContainer($bstoreContainer): void
+ {
+ $this->bstoreContainer = $bstoreContainer;
+ }
+
+ /**
+ * Set an option for the drawing group.
+ *
+ * @param int $property The number specifies the option
+ * @param mixed $value
+ */
+ public function setOPT($property, $value): void
+ {
+ $this->OPT[$property] = $value;
+ }
+
+ /**
+ * Get an option for the drawing group.
+ *
+ * @param int $property The number specifies the option
+ *
+ * @return mixed
+ */
+ public function getOPT($property)
+ {
+ if (isset($this->OPT[$property])) {
+ return $this->OPT[$property];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get identifier clusters.
+ *
+ * @return array
+ */
+ public function getIDCLs()
+ {
+ return $this->IDCLs;
+ }
+
+ /**
+ * Set identifier clusters. [<drawingId> => <max shape id>, ...].
+ *
+ * @param array $pValue
+ */
+ public function setIDCLs($pValue): void
+ {
+ $this->IDCLs = $pValue;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php
new file mode 100755
index 0000000..b07786f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
+
+class BstoreContainer
+{
+ /**
+ * BLIP Store Entries. Each of them holds one BLIP (Big Large Image or Picture).
+ *
+ * @var array
+ */
+ private $BSECollection = [];
+
+ /**
+ * Add a BLIP Store Entry.
+ *
+ * @param BstoreContainer\BSE $BSE
+ */
+ public function addBSE($BSE): void
+ {
+ $this->BSECollection[] = $BSE;
+ $BSE->setParent($this);
+ }
+
+ /**
+ * Get the collection of BLIP Store Entries.
+ *
+ * @return BstoreContainer\BSE[]
+ */
+ public function getBSECollection()
+ {
+ return $this->BSECollection;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php
new file mode 100755
index 0000000..e885146
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
+
+class BSE
+{
+ const BLIPTYPE_ERROR = 0x00;
+ const BLIPTYPE_UNKNOWN = 0x01;
+ const BLIPTYPE_EMF = 0x02;
+ const BLIPTYPE_WMF = 0x03;
+ const BLIPTYPE_PICT = 0x04;
+ const BLIPTYPE_JPEG = 0x05;
+ const BLIPTYPE_PNG = 0x06;
+ const BLIPTYPE_DIB = 0x07;
+ const BLIPTYPE_TIFF = 0x11;
+ const BLIPTYPE_CMYKJPEG = 0x12;
+
+ /**
+ * The parent BLIP Store Entry Container.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer
+ */
+ private $parent;
+
+ /**
+ * The BLIP (Big Large Image or Picture).
+ *
+ * @var BSE\Blip
+ */
+ private $blip;
+
+ /**
+ * The BLIP type.
+ *
+ * @var int
+ */
+ private $blipType;
+
+ /**
+ * Set parent BLIP Store Entry Container.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer $parent
+ */
+ public function setParent($parent): void
+ {
+ $this->parent = $parent;
+ }
+
+ /**
+ * Get the BLIP.
+ *
+ * @return BSE\Blip
+ */
+ public function getBlip()
+ {
+ return $this->blip;
+ }
+
+ /**
+ * Set the BLIP.
+ *
+ * @param BSE\Blip $blip
+ */
+ public function setBlip($blip): void
+ {
+ $this->blip = $blip;
+ $blip->setParent($this);
+ }
+
+ /**
+ * Get the BLIP type.
+ *
+ * @return int
+ */
+ public function getBlipType()
+ {
+ return $this->blipType;
+ }
+
+ /**
+ * Set the BLIP type.
+ *
+ * @param int $blipType
+ */
+ public function setBlipType($blipType): void
+ {
+ $this->blipType = $blipType;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php
new file mode 100755
index 0000000..500d7ea
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
+
+class Blip
+{
+ /**
+ * The parent BSE.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE
+ */
+ private $parent;
+
+ /**
+ * Raw image data.
+ *
+ * @var string
+ */
+ private $data;
+
+ /**
+ * Get the raw image data.
+ *
+ * @return string
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Set the raw image data.
+ *
+ * @param string $data
+ */
+ public function setData($data): void
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * Set parent BSE.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE $parent
+ */
+ public function setParent($parent): void
+ {
+ $this->parent = $parent;
+ }
+
+ /**
+ * Get parent BSE.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE $parent
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php
new file mode 100755
index 0000000..7525df8
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared;
+
+use InvalidArgumentException;
+use ZipArchive;
+
+class File
+{
+ /**
+ * Use Temp or File Upload Temp for temporary files.
+ *
+ * @var bool
+ */
+ protected static $useUploadTempDirectory = false;
+
+ /**
+ * Set the flag indicating whether the File Upload Temp directory should be used for temporary files.
+ *
+ * @param bool $useUploadTempDir Use File Upload Temporary directory (true or false)
+ */
+ public static function setUseUploadTempDirectory($useUploadTempDir): void
+ {
+ self::$useUploadTempDirectory = (bool) $useUploadTempDir;
+ }
+
+ /**
+ * Get the flag indicating whether the File Upload Temp directory should be used for temporary files.
+ *
+ * @return bool Use File Upload Temporary directory (true or false)
+ */
+ public static function getUseUploadTempDirectory()
+ {
+ return self::$useUploadTempDirectory;
+ }
+
+ /**
+ * Verify if a file exists.
+ *
+ * @param string $pFilename Filename
+ *
+ * @return bool
+ */
+ public static function fileExists($pFilename)
+ {
+ // Sick construction, but it seems that
+ // file_exists returns strange values when
+ // doing the original file_exists on ZIP archives...
+ if (strtolower(substr($pFilename, 0, 3)) == 'zip') {
+ // Open ZIP file and verify if the file exists
+ $zipFile = substr($pFilename, 6, strpos($pFilename, '#') - 6);
+ $archiveFile = substr($pFilename, strpos($pFilename, '#') + 1);
+
+ $zip = new ZipArchive();
+ if ($zip->open($zipFile) === true) {
+ $returnValue = ($zip->getFromName($archiveFile) !== false);
+ $zip->close();
+
+ return $returnValue;
+ }
+
+ return false;
+ }
+
+ return file_exists($pFilename);
+ }
+
+ /**
+ * Returns canonicalized absolute pathname, also for ZIP archives.
+ *
+ * @param string $pFilename
+ *
+ * @return string
+ */
+ public static function realpath($pFilename)
+ {
+ // Returnvalue
+ $returnValue = '';
+
+ // Try using realpath()
+ if (file_exists($pFilename)) {
+ $returnValue = realpath($pFilename);
+ }
+
+ // Found something?
+ if ($returnValue == '' || ($returnValue === null)) {
+ $pathArray = explode('/', $pFilename);
+ while (in_array('..', $pathArray) && $pathArray[0] != '..') {
+ $iMax = count($pathArray);
+ for ($i = 0; $i < $iMax; ++$i) {
+ if ($pathArray[$i] == '..' && $i > 0) {
+ unset($pathArray[$i], $pathArray[$i - 1]);
+
+ break;
+ }
+ }
+ }
+ $returnValue = implode('/', $pathArray);
+ }
+
+ // Return
+ return $returnValue;
+ }
+
+ /**
+ * Get the systems temporary directory.
+ *
+ * @return string
+ */
+ public static function sysGetTempDir()
+ {
+ if (self::$useUploadTempDirectory) {
+ // use upload-directory when defined to allow running on environments having very restricted
+ // open_basedir configs
+ if (ini_get('upload_tmp_dir') !== false) {
+ if ($temp = ini_get('upload_tmp_dir')) {
+ if (file_exists($temp)) {
+ return realpath($temp);
+ }
+ }
+ }
+ }
+
+ return realpath(sys_get_temp_dir());
+ }
+
+ /**
+ * Assert that given path is an existing file and is readable, otherwise throw exception.
+ *
+ * @param string $filename
+ */
+ public static function assertFile($filename): void
+ {
+ if (!is_file($filename)) {
+ throw new InvalidArgumentException('File "' . $filename . '" does not exist.');
+ }
+
+ if (!is_readable($filename)) {
+ throw new InvalidArgumentException('Could not open "' . $filename . '" for reading.');
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php
new file mode 100755
index 0000000..ca94529
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php
@@ -0,0 +1,759 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+
+class Font
+{
+ // Methods for resolving autosize value
+ const AUTOSIZE_METHOD_APPROX = 'approx';
+ const AUTOSIZE_METHOD_EXACT = 'exact';
+
+ private static $autoSizeMethods = [
+ self::AUTOSIZE_METHOD_APPROX,
+ self::AUTOSIZE_METHOD_EXACT,
+ ];
+
+ /** Character set codes used by BIFF5-8 in Font records */
+ const CHARSET_ANSI_LATIN = 0x00;
+ const CHARSET_SYSTEM_DEFAULT = 0x01;
+ const CHARSET_SYMBOL = 0x02;
+ const CHARSET_APPLE_ROMAN = 0x4D;
+ const CHARSET_ANSI_JAPANESE_SHIFTJIS = 0x80;
+ const CHARSET_ANSI_KOREAN_HANGUL = 0x81;
+ const CHARSET_ANSI_KOREAN_JOHAB = 0x82;
+ const CHARSET_ANSI_CHINESE_SIMIPLIFIED = 0x86; // gb2312
+ const CHARSET_ANSI_CHINESE_TRADITIONAL = 0x88; // big5
+ const CHARSET_ANSI_GREEK = 0xA1;
+ const CHARSET_ANSI_TURKISH = 0xA2;
+ const CHARSET_ANSI_VIETNAMESE = 0xA3;
+ const CHARSET_ANSI_HEBREW = 0xB1;
+ const CHARSET_ANSI_ARABIC = 0xB2;
+ const CHARSET_ANSI_BALTIC = 0xBA;
+ const CHARSET_ANSI_CYRILLIC = 0xCC;
+ const CHARSET_ANSI_THAI = 0xDD;
+ const CHARSET_ANSI_LATIN_II = 0xEE;
+ const CHARSET_OEM_LATIN_I = 0xFF;
+
+ // XXX: Constants created!
+ /** Font filenames */
+ const ARIAL = 'arial.ttf';
+ const ARIAL_BOLD = 'arialbd.ttf';
+ const ARIAL_ITALIC = 'ariali.ttf';
+ const ARIAL_BOLD_ITALIC = 'arialbi.ttf';
+
+ const CALIBRI = 'CALIBRI.TTF';
+ const CALIBRI_BOLD = 'CALIBRIB.TTF';
+ const CALIBRI_ITALIC = 'CALIBRII.TTF';
+ const CALIBRI_BOLD_ITALIC = 'CALIBRIZ.TTF';
+
+ const COMIC_SANS_MS = 'comic.ttf';
+ const COMIC_SANS_MS_BOLD = 'comicbd.ttf';
+
+ const COURIER_NEW = 'cour.ttf';
+ const COURIER_NEW_BOLD = 'courbd.ttf';
+ const COURIER_NEW_ITALIC = 'couri.ttf';
+ const COURIER_NEW_BOLD_ITALIC = 'courbi.ttf';
+
+ const GEORGIA = 'georgia.ttf';
+ const GEORGIA_BOLD = 'georgiab.ttf';
+ const GEORGIA_ITALIC = 'georgiai.ttf';
+ const GEORGIA_BOLD_ITALIC = 'georgiaz.ttf';
+
+ const IMPACT = 'impact.ttf';
+
+ const LIBERATION_SANS = 'LiberationSans-Regular.ttf';
+ const LIBERATION_SANS_BOLD = 'LiberationSans-Bold.ttf';
+ const LIBERATION_SANS_ITALIC = 'LiberationSans-Italic.ttf';
+ const LIBERATION_SANS_BOLD_ITALIC = 'LiberationSans-BoldItalic.ttf';
+
+ const LUCIDA_CONSOLE = 'lucon.ttf';
+ const LUCIDA_SANS_UNICODE = 'l_10646.ttf';
+
+ const MICROSOFT_SANS_SERIF = 'micross.ttf';
+
+ const PALATINO_LINOTYPE = 'pala.ttf';
+ const PALATINO_LINOTYPE_BOLD = 'palab.ttf';
+ const PALATINO_LINOTYPE_ITALIC = 'palai.ttf';
+ const PALATINO_LINOTYPE_BOLD_ITALIC = 'palabi.ttf';
+
+ const SYMBOL = 'symbol.ttf';
+
+ const TAHOMA = 'tahoma.ttf';
+ const TAHOMA_BOLD = 'tahomabd.ttf';
+
+ const TIMES_NEW_ROMAN = 'times.ttf';
+ const TIMES_NEW_ROMAN_BOLD = 'timesbd.ttf';
+ const TIMES_NEW_ROMAN_ITALIC = 'timesi.ttf';
+ const TIMES_NEW_ROMAN_BOLD_ITALIC = 'timesbi.ttf';
+
+ const TREBUCHET_MS = 'trebuc.ttf';
+ const TREBUCHET_MS_BOLD = 'trebucbd.ttf';
+ const TREBUCHET_MS_ITALIC = 'trebucit.ttf';
+ const TREBUCHET_MS_BOLD_ITALIC = 'trebucbi.ttf';
+
+ const VERDANA = 'verdana.ttf';
+ const VERDANA_BOLD = 'verdanab.ttf';
+ const VERDANA_ITALIC = 'verdanai.ttf';
+ const VERDANA_BOLD_ITALIC = 'verdanaz.ttf';
+
+ /**
+ * AutoSize method.
+ *
+ * @var string
+ */
+ private static $autoSizeMethod = self::AUTOSIZE_METHOD_APPROX;
+
+ /**
+ * Path to folder containing TrueType font .ttf files.
+ *
+ * @var string
+ */
+ private static $trueTypeFontPath = null;
+
+ /**
+ * How wide is a default column for a given default font and size?
+ * Empirical data found by inspecting real Excel files and reading off the pixel width
+ * in Microsoft Office Excel 2007.
+ *
+ * @var array
+ */
+ public static $defaultColumnWidths = [
+ 'Arial' => [
+ 1 => ['px' => 24, 'width' => 12.00000000],
+ 2 => ['px' => 24, 'width' => 12.00000000],
+ 3 => ['px' => 32, 'width' => 10.66406250],
+ 4 => ['px' => 32, 'width' => 10.66406250],
+ 5 => ['px' => 40, 'width' => 10.00000000],
+ 6 => ['px' => 48, 'width' => 9.59765625],
+ 7 => ['px' => 48, 'width' => 9.59765625],
+ 8 => ['px' => 56, 'width' => 9.33203125],
+ 9 => ['px' => 64, 'width' => 9.14062500],
+ 10 => ['px' => 64, 'width' => 9.14062500],
+ ],
+ 'Calibri' => [
+ 1 => ['px' => 24, 'width' => 12.00000000],
+ 2 => ['px' => 24, 'width' => 12.00000000],
+ 3 => ['px' => 32, 'width' => 10.66406250],
+ 4 => ['px' => 32, 'width' => 10.66406250],
+ 5 => ['px' => 40, 'width' => 10.00000000],
+ 6 => ['px' => 48, 'width' => 9.59765625],
+ 7 => ['px' => 48, 'width' => 9.59765625],
+ 8 => ['px' => 56, 'width' => 9.33203125],
+ 9 => ['px' => 56, 'width' => 9.33203125],
+ 10 => ['px' => 64, 'width' => 9.14062500],
+ 11 => ['px' => 64, 'width' => 9.14062500],
+ ],
+ 'Verdana' => [
+ 1 => ['px' => 24, 'width' => 12.00000000],
+ 2 => ['px' => 24, 'width' => 12.00000000],
+ 3 => ['px' => 32, 'width' => 10.66406250],
+ 4 => ['px' => 32, 'width' => 10.66406250],
+ 5 => ['px' => 40, 'width' => 10.00000000],
+ 6 => ['px' => 48, 'width' => 9.59765625],
+ 7 => ['px' => 48, 'width' => 9.59765625],
+ 8 => ['px' => 64, 'width' => 9.14062500],
+ 9 => ['px' => 72, 'width' => 9.00000000],
+ 10 => ['px' => 72, 'width' => 9.00000000],
+ ],
+ ];
+
+ /**
+ * Set autoSize method.
+ *
+ * @param string $pValue see self::AUTOSIZE_METHOD_*
+ *
+ * @return bool Success or failure
+ */
+ public static function setAutoSizeMethod($pValue)
+ {
+ if (!in_array($pValue, self::$autoSizeMethods)) {
+ return false;
+ }
+ self::$autoSizeMethod = $pValue;
+
+ return true;
+ }
+
+ /**
+ * Get autoSize method.
+ *
+ * @return string
+ */
+ public static function getAutoSizeMethod()
+ {
+ return self::$autoSizeMethod;
+ }
+
+ /**
+ * Set the path to the folder containing .ttf files. There should be a trailing slash.
+ * Typical locations on variout some platforms:
+ * <ul>
+ * <li>C:/Windows/Fonts/</li>
+ * <li>/usr/share/fonts/truetype/</li>
+ * <li>~/.fonts/</li>
+ * </ul>.
+ *
+ * @param string $pValue
+ */
+ public static function setTrueTypeFontPath($pValue): void
+ {
+ self::$trueTypeFontPath = $pValue;
+ }
+
+ /**
+ * Get the path to the folder containing .ttf files.
+ *
+ * @return string
+ */
+ public static function getTrueTypeFontPath()
+ {
+ return self::$trueTypeFontPath;
+ }
+
+ /**
+ * Calculate an (approximate) OpenXML column width, based on font size and text contained.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Style\Font $font Font object
+ * @param RichText|string $cellText Text to calculate width
+ * @param int $rotation Rotation angle
+ * @param null|\PhpOffice\PhpSpreadsheet\Style\Font $defaultFont Font object
+ *
+ * @return int Column width
+ */
+ public static function calculateColumnWidth(\PhpOffice\PhpSpreadsheet\Style\Font $font, $cellText = '', $rotation = 0, ?\PhpOffice\PhpSpreadsheet\Style\Font $defaultFont = null)
+ {
+ // If it is rich text, use plain text
+ if ($cellText instanceof RichText) {
+ $cellText = $cellText->getPlainText();
+ }
+
+ // Special case if there are one or more newline characters ("\n")
+ if (strpos($cellText, "\n") !== false) {
+ $lineTexts = explode("\n", $cellText);
+ $lineWidths = [];
+ foreach ($lineTexts as $lineText) {
+ $lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont);
+ }
+
+ return max($lineWidths); // width of longest line in cell
+ }
+
+ // Try to get the exact text width in pixels
+ $approximate = self::$autoSizeMethod == self::AUTOSIZE_METHOD_APPROX;
+ if (!$approximate) {
+ $columnWidthAdjust = ceil(self::getTextWidthPixelsExact('n', $font, 0) * 1.07);
+
+ try {
+ // Width of text in pixels excl. padding
+ // and addition because Excel adds some padding, just use approx width of 'n' glyph
+ $columnWidth = self::getTextWidthPixelsExact($cellText, $font, $rotation) + $columnWidthAdjust;
+ } catch (PhpSpreadsheetException $e) {
+ $approximate = true;
+ }
+ }
+
+ if ($approximate) {
+ $columnWidthAdjust = self::getTextWidthPixelsApprox('n', $font, 0);
+ // Width of text in pixels excl. padding, approximation
+ // and addition because Excel adds some padding, just use approx width of 'n' glyph
+ $columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust;
+ }
+
+ // Convert from pixel width to column width
+ $columnWidth = Drawing::pixelsToCellDimension($columnWidth, $defaultFont);
+
+ // Return
+ return round($columnWidth, 6);
+ }
+
+ /**
+ * Get GD text width in pixels for a string of text in a certain font at a certain rotation angle.
+ *
+ * @param string $text
+ * @param \PhpOffice\PhpSpreadsheet\Style\Font
+ * @param int $rotation
+ *
+ * @return int
+ */
+ public static function getTextWidthPixelsExact($text, \PhpOffice\PhpSpreadsheet\Style\Font $font, $rotation = 0)
+ {
+ if (!function_exists('imagettfbbox')) {
+ throw new PhpSpreadsheetException('GD library needs to be enabled');
+ }
+
+ // font size should really be supplied in pixels in GD2,
+ // but since GD2 seems to assume 72dpi, pixels and points are the same
+ $fontFile = self::getTrueTypeFontFileFromFont($font);
+ $textBox = imagettfbbox($font->getSize(), $rotation, $fontFile, $text);
+
+ // Get corners positions
+ $lowerLeftCornerX = $textBox[0];
+ $lowerRightCornerX = $textBox[2];
+ $upperRightCornerX = $textBox[4];
+ $upperLeftCornerX = $textBox[6];
+
+ // Consider the rotation when calculating the width
+ return max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX);
+ }
+
+ /**
+ * Get approximate width in pixels for a string of text in a certain font at a certain rotation angle.
+ *
+ * @param string $columnText
+ * @param int $rotation
+ *
+ * @return int Text width in pixels (no padding added)
+ */
+ public static function getTextWidthPixelsApprox($columnText, \PhpOffice\PhpSpreadsheet\Style\Font $font, $rotation = 0)
+ {
+ $fontName = $font->getName();
+ $fontSize = $font->getSize();
+
+ // Calculate column width in pixels. We assume fixed glyph width. Result varies with font name and size.
+ switch ($fontName) {
+ case 'Calibri':
+ // value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
+ $columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText));
+ $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
+ break;
+ case 'Arial':
+ // value 8 was set because of experience in different exports at Arial 10 font.
+ $columnWidth = (int) (8 * StringHelper::countCharacters($columnText));
+ $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
+ break;
+ case 'Verdana':
+ // value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font.
+ $columnWidth = (int) (8 * StringHelper::countCharacters($columnText));
+ $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
+ break;
+ default:
+ // just assume Calibri
+ $columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText));
+ $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
+ break;
+ }
+
+ // Calculate approximate rotated column width
+ if ($rotation !== 0) {
+ if ($rotation == -165) {
+ // stacked text
+ $columnWidth = 4; // approximation
+ } else {
+ // rotated text
+ $columnWidth = $columnWidth * cos(deg2rad($rotation))
+ + $fontSize * abs(sin(deg2rad($rotation))) / 5; // approximation
+ }
+ }
+
+ // pixel width is an integer
+ return (int) $columnWidth;
+ }
+
+ /**
+ * Calculate an (approximate) pixel size, based on a font points size.
+ *
+ * @param int $fontSizeInPoints Font size (in points)
+ *
+ * @return int Font size (in pixels)
+ */
+ public static function fontSizeToPixels($fontSizeInPoints)
+ {
+ return (int) ((4 / 3) * $fontSizeInPoints);
+ }
+
+ /**
+ * Calculate an (approximate) pixel size, based on inch size.
+ *
+ * @param int $sizeInInch Font size (in inch)
+ *
+ * @return int Size (in pixels)
+ */
+ public static function inchSizeToPixels($sizeInInch)
+ {
+ return $sizeInInch * 96;
+ }
+
+ /**
+ * Calculate an (approximate) pixel size, based on centimeter size.
+ *
+ * @param int $sizeInCm Font size (in centimeters)
+ *
+ * @return float Size (in pixels)
+ */
+ public static function centimeterSizeToPixels($sizeInCm)
+ {
+ return $sizeInCm * 37.795275591;
+ }
+
+ /**
+ * Returns the font path given the font.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Style\Font $font
+ *
+ * @return string Path to TrueType font file
+ */
+ public static function getTrueTypeFontFileFromFont($font)
+ {
+ if (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath)) {
+ throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified');
+ }
+
+ $name = $font->getName();
+ $bold = $font->getBold();
+ $italic = $font->getItalic();
+
+ // Check if we can map font to true type font file
+ switch ($name) {
+ case 'Arial':
+ $fontFile = (
+ $bold ? ($italic ? self::ARIAL_BOLD_ITALIC : self::ARIAL_BOLD)
+ : ($italic ? self::ARIAL_ITALIC : self::ARIAL)
+ );
+
+ break;
+ case 'Calibri':
+ $fontFile = (
+ $bold ? ($italic ? self::CALIBRI_BOLD_ITALIC : self::CALIBRI_BOLD)
+ : ($italic ? self::CALIBRI_ITALIC : self::CALIBRI)
+ );
+
+ break;
+ case 'Courier New':
+ $fontFile = (
+ $bold ? ($italic ? self::COURIER_NEW_BOLD_ITALIC : self::COURIER_NEW_BOLD)
+ : ($italic ? self::COURIER_NEW_ITALIC : self::COURIER_NEW)
+ );
+
+ break;
+ case 'Comic Sans MS':
+ $fontFile = (
+ $bold ? self::COMIC_SANS_MS_BOLD : self::COMIC_SANS_MS
+ );
+
+ break;
+ case 'Georgia':
+ $fontFile = (
+ $bold ? ($italic ? self::GEORGIA_BOLD_ITALIC : self::GEORGIA_BOLD)
+ : ($italic ? self::GEORGIA_ITALIC : self::GEORGIA)
+ );
+
+ break;
+ case 'Impact':
+ $fontFile = self::IMPACT;
+
+ break;
+ case 'Liberation Sans':
+ $fontFile = (
+ $bold ? ($italic ? self::LIBERATION_SANS_BOLD_ITALIC : self::LIBERATION_SANS_BOLD)
+ : ($italic ? self::LIBERATION_SANS_ITALIC : self::LIBERATION_SANS)
+ );
+
+ break;
+ case 'Lucida Console':
+ $fontFile = self::LUCIDA_CONSOLE;
+
+ break;
+ case 'Lucida Sans Unicode':
+ $fontFile = self::LUCIDA_SANS_UNICODE;
+
+ break;
+ case 'Microsoft Sans Serif':
+ $fontFile = self::MICROSOFT_SANS_SERIF;
+
+ break;
+ case 'Palatino Linotype':
+ $fontFile = (
+ $bold ? ($italic ? self::PALATINO_LINOTYPE_BOLD_ITALIC : self::PALATINO_LINOTYPE_BOLD)
+ : ($italic ? self::PALATINO_LINOTYPE_ITALIC : self::PALATINO_LINOTYPE)
+ );
+
+ break;
+ case 'Symbol':
+ $fontFile = self::SYMBOL;
+
+ break;
+ case 'Tahoma':
+ $fontFile = (
+ $bold ? self::TAHOMA_BOLD : self::TAHOMA
+ );
+
+ break;
+ case 'Times New Roman':
+ $fontFile = (
+ $bold ? ($italic ? self::TIMES_NEW_ROMAN_BOLD_ITALIC : self::TIMES_NEW_ROMAN_BOLD)
+ : ($italic ? self::TIMES_NEW_ROMAN_ITALIC : self::TIMES_NEW_ROMAN)
+ );
+
+ break;
+ case 'Trebuchet MS':
+ $fontFile = (
+ $bold ? ($italic ? self::TREBUCHET_MS_BOLD_ITALIC : self::TREBUCHET_MS_BOLD)
+ : ($italic ? self::TREBUCHET_MS_ITALIC : self::TREBUCHET_MS)
+ );
+
+ break;
+ case 'Verdana':
+ $fontFile = (
+ $bold ? ($italic ? self::VERDANA_BOLD_ITALIC : self::VERDANA_BOLD)
+ : ($italic ? self::VERDANA_ITALIC : self::VERDANA)
+ );
+
+ break;
+ default:
+ throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file');
+
+ break;
+ }
+
+ $fontFile = self::$trueTypeFontPath . $fontFile;
+
+ // Check if file actually exists
+ if (!file_exists($fontFile)) {
+ throw new PhpSpreadsheetException('TrueType Font file not found');
+ }
+
+ return $fontFile;
+ }
+
+ /**
+ * Returns the associated charset for the font name.
+ *
+ * @param string $name Font name
+ *
+ * @return int Character set code
+ */
+ public static function getCharsetFromFontName($name)
+ {
+ switch ($name) {
+ // Add more cases. Check FONT records in real Excel files.
+ case 'EucrosiaUPC':
+ return self::CHARSET_ANSI_THAI;
+ case 'Wingdings':
+ return self::CHARSET_SYMBOL;
+ case 'Wingdings 2':
+ return self::CHARSET_SYMBOL;
+ case 'Wingdings 3':
+ return self::CHARSET_SYMBOL;
+ default:
+ return self::CHARSET_ANSI_LATIN;
+ }
+ }
+
+ /**
+ * Get the effective column width for columns without a column dimension or column with width -1
+ * For example, for Calibri 11 this is 9.140625 (64 px).
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Style\Font $font The workbooks default font
+ * @param bool $pPixels true = return column width in pixels, false = return in OOXML units
+ *
+ * @return mixed Column width
+ */
+ public static function getDefaultColumnWidthByFont(\PhpOffice\PhpSpreadsheet\Style\Font $font, $pPixels = false)
+ {
+ if (isset(self::$defaultColumnWidths[$font->getName()][$font->getSize()])) {
+ // Exact width can be determined
+ $columnWidth = $pPixels ?
+ self::$defaultColumnWidths[$font->getName()][$font->getSize()]['px']
+ : self::$defaultColumnWidths[$font->getName()][$font->getSize()]['width'];
+ } else {
+ // We don't have data for this particular font and size, use approximation by
+ // extrapolating from Calibri 11
+ $columnWidth = $pPixels ?
+ self::$defaultColumnWidths['Calibri'][11]['px']
+ : self::$defaultColumnWidths['Calibri'][11]['width'];
+ $columnWidth = $columnWidth * $font->getSize() / 11;
+
+ // Round pixels to closest integer
+ if ($pPixels) {
+ $columnWidth = (int) round($columnWidth);
+ }
+ }
+
+ return $columnWidth;
+ }
+
+ /**
+ * Get the effective row height for rows without a row dimension or rows with height -1
+ * For example, for Calibri 11 this is 15 points.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Style\Font $font The workbooks default font
+ *
+ * @return float Row height in points
+ */
+ public static function getDefaultRowHeightByFont(\PhpOffice\PhpSpreadsheet\Style\Font $font)
+ {
+ switch ($font->getName()) {
+ case 'Arial':
+ switch ($font->getSize()) {
+ case 10:
+ // inspection of Arial 10 workbook says 12.75pt ~17px
+ $rowHeight = 12.75;
+
+ break;
+ case 9:
+ // inspection of Arial 9 workbook says 12.00pt ~16px
+ $rowHeight = 12;
+
+ break;
+ case 8:
+ // inspection of Arial 8 workbook says 11.25pt ~15px
+ $rowHeight = 11.25;
+
+ break;
+ case 7:
+ // inspection of Arial 7 workbook says 9.00pt ~12px
+ $rowHeight = 9;
+
+ break;
+ case 6:
+ case 5:
+ // inspection of Arial 5,6 workbook says 8.25pt ~11px
+ $rowHeight = 8.25;
+
+ break;
+ case 4:
+ // inspection of Arial 4 workbook says 6.75pt ~9px
+ $rowHeight = 6.75;
+
+ break;
+ case 3:
+ // inspection of Arial 3 workbook says 6.00pt ~8px
+ $rowHeight = 6;
+
+ break;
+ case 2:
+ case 1:
+ // inspection of Arial 1,2 workbook says 5.25pt ~7px
+ $rowHeight = 5.25;
+
+ break;
+ default:
+ // use Arial 10 workbook as an approximation, extrapolation
+ $rowHeight = 12.75 * $font->getSize() / 10;
+
+ break;
+ }
+
+ break;
+ case 'Calibri':
+ switch ($font->getSize()) {
+ case 11:
+ // inspection of Calibri 11 workbook says 15.00pt ~20px
+ $rowHeight = 15;
+
+ break;
+ case 10:
+ // inspection of Calibri 10 workbook says 12.75pt ~17px
+ $rowHeight = 12.75;
+
+ break;
+ case 9:
+ // inspection of Calibri 9 workbook says 12.00pt ~16px
+ $rowHeight = 12;
+
+ break;
+ case 8:
+ // inspection of Calibri 8 workbook says 11.25pt ~15px
+ $rowHeight = 11.25;
+
+ break;
+ case 7:
+ // inspection of Calibri 7 workbook says 9.00pt ~12px
+ $rowHeight = 9;
+
+ break;
+ case 6:
+ case 5:
+ // inspection of Calibri 5,6 workbook says 8.25pt ~11px
+ $rowHeight = 8.25;
+
+ break;
+ case 4:
+ // inspection of Calibri 4 workbook says 6.75pt ~9px
+ $rowHeight = 6.75;
+
+ break;
+ case 3:
+ // inspection of Calibri 3 workbook says 6.00pt ~8px
+ $rowHeight = 6.00;
+
+ break;
+ case 2:
+ case 1:
+ // inspection of Calibri 1,2 workbook says 5.25pt ~7px
+ $rowHeight = 5.25;
+
+ break;
+ default:
+ // use Calibri 11 workbook as an approximation, extrapolation
+ $rowHeight = 15 * $font->getSize() / 11;
+
+ break;
+ }
+
+ break;
+ case 'Verdana':
+ switch ($font->getSize()) {
+ case 10:
+ // inspection of Verdana 10 workbook says 12.75pt ~17px
+ $rowHeight = 12.75;
+
+ break;
+ case 9:
+ // inspection of Verdana 9 workbook says 11.25pt ~15px
+ $rowHeight = 11.25;
+
+ break;
+ case 8:
+ // inspection of Verdana 8 workbook says 10.50pt ~14px
+ $rowHeight = 10.50;
+
+ break;
+ case 7:
+ // inspection of Verdana 7 workbook says 9.00pt ~12px
+ $rowHeight = 9.00;
+
+ break;
+ case 6:
+ case 5:
+ // inspection of Verdana 5,6 workbook says 8.25pt ~11px
+ $rowHeight = 8.25;
+
+ break;
+ case 4:
+ // inspection of Verdana 4 workbook says 6.75pt ~9px
+ $rowHeight = 6.75;
+
+ break;
+ case 3:
+ // inspection of Verdana 3 workbook says 6.00pt ~8px
+ $rowHeight = 6;
+
+ break;
+ case 2:
+ case 1:
+ // inspection of Verdana 1,2 workbook says 5.25pt ~7px
+ $rowHeight = 5.25;
+
+ break;
+ default:
+ // use Verdana 10 workbook as an approximation, extrapolation
+ $rowHeight = 12.75 * $font->getSize() / 10;
+
+ break;
+ }
+
+ break;
+ default:
+ // just use Calibri as an approximation
+ $rowHeight = 15 * $font->getSize() / 11;
+
+ break;
+ }
+
+ return $rowHeight;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CHANGELOG.TXT b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CHANGELOG.TXT
new file mode 100755
index 0000000..1c18a5d
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CHANGELOG.TXT
@@ -0,0 +1,16 @@
+Mar 1, 2005 11:15 AST by PM
+
++ For consistency, renamed Math.php to Maths.java, utils to util,
+ tests to test, docs to doc -
+
++ Removed conditional logic from top of Matrix class.
+
++ Switched to using hypo function in Maths.php for all php-hypot calls.
+ NOTE TO SELF: Need to make sure that all decompositions have been
+ switched over to using the bundled hypo.
+
+Feb 25, 2005 at 10:00 AST by PM
+
++ Recommend using simpler Error.php instead of JAMA_Error.php but
+ can be persuaded otherwise.
+
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CholeskyDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CholeskyDecomposition.php
new file mode 100755
index 0000000..2b241d5
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CholeskyDecomposition.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
+
+/**
+ * Cholesky decomposition class.
+ *
+ * For a symmetric, positive definite matrix A, the Cholesky decomposition
+ * is an lower triangular matrix L so that A = L*L'.
+ *
+ * If the matrix is not symmetric or positive definite, the constructor
+ * returns a partial decomposition and sets an internal flag that may
+ * be queried by the isSPD() method.
+ *
+ * @author Paul Meagher
+ * @author Michael Bommarito
+ *
+ * @version 1.2
+ */
+class CholeskyDecomposition
+{
+ /**
+ * Decomposition storage.
+ *
+ * @var array
+ */
+ private $L = [];
+
+ /**
+ * Matrix row and column dimension.
+ *
+ * @var int
+ */
+ private $m;
+
+ /**
+ * Symmetric positive definite flag.
+ *
+ * @var bool
+ */
+ private $isspd = true;
+
+ /**
+ * CholeskyDecomposition.
+ *
+ * Class constructor - decomposes symmetric positive definite matrix
+ *
+ * @param Matrix $A Matrix square symmetric positive definite matrix
+ */
+ public function __construct(Matrix $A)
+ {
+ $this->L = $A->getArray();
+ $this->m = $A->getRowDimension();
+
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = $i; $j < $this->m; ++$j) {
+ for ($sum = $this->L[$i][$j], $k = $i - 1; $k >= 0; --$k) {
+ $sum -= $this->L[$i][$k] * $this->L[$j][$k];
+ }
+ if ($i == $j) {
+ if ($sum >= 0) {
+ $this->L[$i][$i] = sqrt($sum);
+ } else {
+ $this->isspd = false;
+ }
+ } else {
+ if ($this->L[$i][$i] != 0) {
+ $this->L[$j][$i] = $sum / $this->L[$i][$i];
+ }
+ }
+ }
+
+ for ($k = $i + 1; $k < $this->m; ++$k) {
+ $this->L[$i][$k] = 0.0;
+ }
+ }
+ }
+
+ /**
+ * Is the matrix symmetric and positive definite?
+ *
+ * @return bool
+ */
+ public function isSPD()
+ {
+ return $this->isspd;
+ }
+
+ /**
+ * getL.
+ *
+ * Return triangular factor.
+ *
+ * @return Matrix Lower triangular matrix
+ */
+ public function getL()
+ {
+ return new Matrix($this->L);
+ }
+
+ /**
+ * Solve A*X = B.
+ *
+ * @param $B Row-equal matrix
+ *
+ * @return Matrix L * L' * X = B
+ */
+ public function solve(Matrix $B)
+ {
+ if ($B->getRowDimension() == $this->m) {
+ if ($this->isspd) {
+ $X = $B->getArrayCopy();
+ $nx = $B->getColumnDimension();
+
+ for ($k = 0; $k < $this->m; ++$k) {
+ for ($i = $k + 1; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X[$i][$j] -= $X[$k][$j] * $this->L[$i][$k];
+ }
+ }
+ for ($j = 0; $j < $nx; ++$j) {
+ $X[$k][$j] /= $this->L[$k][$k];
+ }
+ }
+
+ for ($k = $this->m - 1; $k >= 0; --$k) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X[$k][$j] /= $this->L[$k][$k];
+ }
+ for ($i = 0; $i < $k; ++$i) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X[$i][$j] -= $X[$k][$j] * $this->L[$k][$i];
+ }
+ }
+ }
+
+ return new Matrix($X, $this->m, $nx);
+ }
+
+ throw new CalculationException(Matrix::MATRIX_SPD_EXCEPTION);
+ }
+
+ throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php
new file mode 100755
index 0000000..f6fbccb
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php
@@ -0,0 +1,861 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
+
+/**
+ * Class to obtain eigenvalues and eigenvectors of a real matrix.
+ *
+ * If A is symmetric, then A = V*D*V' where the eigenvalue matrix D
+ * is diagonal and the eigenvector matrix V is orthogonal (i.e.
+ * A = V.times(D.times(V.transpose())) and V.times(V.transpose())
+ * equals the identity matrix).
+ *
+ * If A is not symmetric, then the eigenvalue matrix D is block diagonal
+ * with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues,
+ * lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The
+ * columns of V represent the eigenvectors in the sense that A*V = V*D,
+ * i.e. A.times(V) equals V.times(D). The matrix V may be badly
+ * conditioned, or even singular, so the validity of the equation
+ * A = V*D*inverse(V) depends upon V.cond().
+ *
+ * @author Paul Meagher
+ *
+ * @version 1.1
+ */
+class EigenvalueDecomposition
+{
+ /**
+ * Row and column dimension (square matrix).
+ *
+ * @var int
+ */
+ private $n;
+
+ /**
+ * Arrays for internal storage of eigenvalues.
+ *
+ * @var array
+ */
+ private $d = [];
+
+ private $e = [];
+
+ /**
+ * Array for internal storage of eigenvectors.
+ *
+ * @var array
+ */
+ private $V = [];
+
+ /**
+ * Array for internal storage of nonsymmetric Hessenberg form.
+ *
+ * @var array
+ */
+ private $H = [];
+
+ /**
+ * Working storage for nonsymmetric algorithm.
+ *
+ * @var array
+ */
+ private $ort;
+
+ /**
+ * Used for complex scalar division.
+ *
+ * @var float
+ */
+ private $cdivr;
+
+ private $cdivi;
+
+ /**
+ * Symmetric Householder reduction to tridiagonal form.
+ */
+ private function tred2(): void
+ {
+ // This is derived from the Algol procedures tred2 by
+ // Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
+ // Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
+ // Fortran subroutine in EISPACK.
+ $this->d = $this->V[$this->n - 1];
+ // Householder reduction to tridiagonal form.
+ for ($i = $this->n - 1; $i > 0; --$i) {
+ $i_ = $i - 1;
+ // Scale to avoid under/overflow.
+ $h = $scale = 0.0;
+ $scale += array_sum(array_map('abs', $this->d));
+ if ($scale == 0.0) {
+ $this->e[$i] = $this->d[$i_];
+ $this->d = array_slice($this->V[$i_], 0, $i_);
+ for ($j = 0; $j < $i; ++$j) {
+ $this->V[$j][$i] = $this->V[$i][$j] = 0.0;
+ }
+ } else {
+ // Generate Householder vector.
+ for ($k = 0; $k < $i; ++$k) {
+ $this->d[$k] /= $scale;
+ $h += $this->d[$k] ** 2;
+ }
+ $f = $this->d[$i_];
+ $g = sqrt($h);
+ if ($f > 0) {
+ $g = -$g;
+ }
+ $this->e[$i] = $scale * $g;
+ $h = $h - $f * $g;
+ $this->d[$i_] = $f - $g;
+ for ($j = 0; $j < $i; ++$j) {
+ $this->e[$j] = 0.0;
+ }
+ // Apply similarity transformation to remaining columns.
+ for ($j = 0; $j < $i; ++$j) {
+ $f = $this->d[$j];
+ $this->V[$j][$i] = $f;
+ $g = $this->e[$j] + $this->V[$j][$j] * $f;
+ for ($k = $j + 1; $k <= $i_; ++$k) {
+ $g += $this->V[$k][$j] * $this->d[$k];
+ $this->e[$k] += $this->V[$k][$j] * $f;
+ }
+ $this->e[$j] = $g;
+ }
+ $f = 0.0;
+ for ($j = 0; $j < $i; ++$j) {
+ $this->e[$j] /= $h;
+ $f += $this->e[$j] * $this->d[$j];
+ }
+ $hh = $f / (2 * $h);
+ for ($j = 0; $j < $i; ++$j) {
+ $this->e[$j] -= $hh * $this->d[$j];
+ }
+ for ($j = 0; $j < $i; ++$j) {
+ $f = $this->d[$j];
+ $g = $this->e[$j];
+ for ($k = $j; $k <= $i_; ++$k) {
+ $this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]);
+ }
+ $this->d[$j] = $this->V[$i - 1][$j];
+ $this->V[$i][$j] = 0.0;
+ }
+ }
+ $this->d[$i] = $h;
+ }
+
+ // Accumulate transformations.
+ for ($i = 0; $i < $this->n - 1; ++$i) {
+ $this->V[$this->n - 1][$i] = $this->V[$i][$i];
+ $this->V[$i][$i] = 1.0;
+ $h = $this->d[$i + 1];
+ if ($h != 0.0) {
+ for ($k = 0; $k <= $i; ++$k) {
+ $this->d[$k] = $this->V[$k][$i + 1] / $h;
+ }
+ for ($j = 0; $j <= $i; ++$j) {
+ $g = 0.0;
+ for ($k = 0; $k <= $i; ++$k) {
+ $g += $this->V[$k][$i + 1] * $this->V[$k][$j];
+ }
+ for ($k = 0; $k <= $i; ++$k) {
+ $this->V[$k][$j] -= $g * $this->d[$k];
+ }
+ }
+ }
+ for ($k = 0; $k <= $i; ++$k) {
+ $this->V[$k][$i + 1] = 0.0;
+ }
+ }
+
+ $this->d = $this->V[$this->n - 1];
+ $this->V[$this->n - 1] = array_fill(0, $j, 0.0);
+ $this->V[$this->n - 1][$this->n - 1] = 1.0;
+ $this->e[0] = 0.0;
+ }
+
+ /**
+ * Symmetric tridiagonal QL algorithm.
+ *
+ * This is derived from the Algol procedures tql2, by
+ * Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
+ * Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
+ * Fortran subroutine in EISPACK.
+ */
+ private function tql2(): void
+ {
+ for ($i = 1; $i < $this->n; ++$i) {
+ $this->e[$i - 1] = $this->e[$i];
+ }
+ $this->e[$this->n - 1] = 0.0;
+ $f = 0.0;
+ $tst1 = 0.0;
+ $eps = 2.0 ** (-52.0);
+
+ for ($l = 0; $l < $this->n; ++$l) {
+ // Find small subdiagonal element
+ $tst1 = max($tst1, abs($this->d[$l]) + abs($this->e[$l]));
+ $m = $l;
+ while ($m < $this->n) {
+ if (abs($this->e[$m]) <= $eps * $tst1) {
+ break;
+ }
+ ++$m;
+ }
+ // If m == l, $this->d[l] is an eigenvalue,
+ // otherwise, iterate.
+ if ($m > $l) {
+ $iter = 0;
+ do {
+ // Could check iteration count here.
+ ++$iter;
+ // Compute implicit shift
+ $g = $this->d[$l];
+ $p = ($this->d[$l + 1] - $g) / (2.0 * $this->e[$l]);
+ $r = hypo($p, 1.0);
+ if ($p < 0) {
+ $r *= -1;
+ }
+ $this->d[$l] = $this->e[$l] / ($p + $r);
+ $this->d[$l + 1] = $this->e[$l] * ($p + $r);
+ $dl1 = $this->d[$l + 1];
+ $h = $g - $this->d[$l];
+ for ($i = $l + 2; $i < $this->n; ++$i) {
+ $this->d[$i] -= $h;
+ }
+ $f += $h;
+ // Implicit QL transformation.
+ $p = $this->d[$m];
+ $c = 1.0;
+ $c2 = $c3 = $c;
+ $el1 = $this->e[$l + 1];
+ $s = $s2 = 0.0;
+ for ($i = $m - 1; $i >= $l; --$i) {
+ $c3 = $c2;
+ $c2 = $c;
+ $s2 = $s;
+ $g = $c * $this->e[$i];
+ $h = $c * $p;
+ $r = hypo($p, $this->e[$i]);
+ $this->e[$i + 1] = $s * $r;
+ $s = $this->e[$i] / $r;
+ $c = $p / $r;
+ $p = $c * $this->d[$i] - $s * $g;
+ $this->d[$i + 1] = $h + $s * ($c * $g + $s * $this->d[$i]);
+ // Accumulate transformation.
+ for ($k = 0; $k < $this->n; ++$k) {
+ $h = $this->V[$k][$i + 1];
+ $this->V[$k][$i + 1] = $s * $this->V[$k][$i] + $c * $h;
+ $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h;
+ }
+ }
+ $p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1;
+ $this->e[$l] = $s * $p;
+ $this->d[$l] = $c * $p;
+ // Check for convergence.
+ } while (abs($this->e[$l]) > $eps * $tst1);
+ }
+ $this->d[$l] = $this->d[$l] + $f;
+ $this->e[$l] = 0.0;
+ }
+
+ // Sort eigenvalues and corresponding vectors.
+ for ($i = 0; $i < $this->n - 1; ++$i) {
+ $k = $i;
+ $p = $this->d[$i];
+ for ($j = $i + 1; $j < $this->n; ++$j) {
+ if ($this->d[$j] < $p) {
+ $k = $j;
+ $p = $this->d[$j];
+ }
+ }
+ if ($k != $i) {
+ $this->d[$k] = $this->d[$i];
+ $this->d[$i] = $p;
+ for ($j = 0; $j < $this->n; ++$j) {
+ $p = $this->V[$j][$i];
+ $this->V[$j][$i] = $this->V[$j][$k];
+ $this->V[$j][$k] = $p;
+ }
+ }
+ }
+ }
+
+ /**
+ * Nonsymmetric reduction to Hessenberg form.
+ *
+ * This is derived from the Algol procedures orthes and ortran,
+ * by Martin and Wilkinson, Handbook for Auto. Comp.,
+ * Vol.ii-Linear Algebra, and the corresponding
+ * Fortran subroutines in EISPACK.
+ */
+ private function orthes(): void
+ {
+ $low = 0;
+ $high = $this->n - 1;
+
+ for ($m = $low + 1; $m <= $high - 1; ++$m) {
+ // Scale column.
+ $scale = 0.0;
+ for ($i = $m; $i <= $high; ++$i) {
+ $scale = $scale + abs($this->H[$i][$m - 1]);
+ }
+ if ($scale != 0.0) {
+ // Compute Householder transformation.
+ $h = 0.0;
+ for ($i = $high; $i >= $m; --$i) {
+ $this->ort[$i] = $this->H[$i][$m - 1] / $scale;
+ $h += $this->ort[$i] * $this->ort[$i];
+ }
+ $g = sqrt($h);
+ if ($this->ort[$m] > 0) {
+ $g *= -1;
+ }
+ $h -= $this->ort[$m] * $g;
+ $this->ort[$m] -= $g;
+ // Apply Householder similarity transformation
+ // H = (I -u * u' / h) * H * (I -u * u') / h)
+ for ($j = $m; $j < $this->n; ++$j) {
+ $f = 0.0;
+ for ($i = $high; $i >= $m; --$i) {
+ $f += $this->ort[$i] * $this->H[$i][$j];
+ }
+ $f /= $h;
+ for ($i = $m; $i <= $high; ++$i) {
+ $this->H[$i][$j] -= $f * $this->ort[$i];
+ }
+ }
+ for ($i = 0; $i <= $high; ++$i) {
+ $f = 0.0;
+ for ($j = $high; $j >= $m; --$j) {
+ $f += $this->ort[$j] * $this->H[$i][$j];
+ }
+ $f = $f / $h;
+ for ($j = $m; $j <= $high; ++$j) {
+ $this->H[$i][$j] -= $f * $this->ort[$j];
+ }
+ }
+ $this->ort[$m] = $scale * $this->ort[$m];
+ $this->H[$m][$m - 1] = $scale * $g;
+ }
+ }
+
+ // Accumulate transformations (Algol's ortran).
+ for ($i = 0; $i < $this->n; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $this->V[$i][$j] = ($i == $j ? 1.0 : 0.0);
+ }
+ }
+ for ($m = $high - 1; $m >= $low + 1; --$m) {
+ if ($this->H[$m][$m - 1] != 0.0) {
+ for ($i = $m + 1; $i <= $high; ++$i) {
+ $this->ort[$i] = $this->H[$i][$m - 1];
+ }
+ for ($j = $m; $j <= $high; ++$j) {
+ $g = 0.0;
+ for ($i = $m; $i <= $high; ++$i) {
+ $g += $this->ort[$i] * $this->V[$i][$j];
+ }
+ // Double division avoids possible underflow
+ $g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1];
+ for ($i = $m; $i <= $high; ++$i) {
+ $this->V[$i][$j] += $g * $this->ort[$i];
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Performs complex division.
+ *
+ * @param mixed $xr
+ * @param mixed $xi
+ * @param mixed $yr
+ * @param mixed $yi
+ */
+ private function cdiv($xr, $xi, $yr, $yi): void
+ {
+ if (abs($yr) > abs($yi)) {
+ $r = $yi / $yr;
+ $d = $yr + $r * $yi;
+ $this->cdivr = ($xr + $r * $xi) / $d;
+ $this->cdivi = ($xi - $r * $xr) / $d;
+ } else {
+ $r = $yr / $yi;
+ $d = $yi + $r * $yr;
+ $this->cdivr = ($r * $xr + $xi) / $d;
+ $this->cdivi = ($r * $xi - $xr) / $d;
+ }
+ }
+
+ /**
+ * Nonsymmetric reduction from Hessenberg to real Schur form.
+ *
+ * Code is derived from the Algol procedure hqr2,
+ * by Martin and Wilkinson, Handbook for Auto. Comp.,
+ * Vol.ii-Linear Algebra, and the corresponding
+ * Fortran subroutine in EISPACK.
+ */
+ private function hqr2(): void
+ {
+ // Initialize
+ $nn = $this->n;
+ $n = $nn - 1;
+ $low = 0;
+ $high = $nn - 1;
+ $eps = 2.0 ** (-52.0);
+ $exshift = 0.0;
+ $p = $q = $r = $s = $z = 0;
+ // Store roots isolated by balanc and compute matrix norm
+ $norm = 0.0;
+
+ for ($i = 0; $i < $nn; ++$i) {
+ if (($i < $low) || ($i > $high)) {
+ $this->d[$i] = $this->H[$i][$i];
+ $this->e[$i] = 0.0;
+ }
+ for ($j = max($i - 1, 0); $j < $nn; ++$j) {
+ $norm = $norm + abs($this->H[$i][$j]);
+ }
+ }
+
+ // Outer loop over eigenvalue index
+ $iter = 0;
+ while ($n >= $low) {
+ // Look for single small sub-diagonal element
+ $l = $n;
+ while ($l > $low) {
+ $s = abs($this->H[$l - 1][$l - 1]) + abs($this->H[$l][$l]);
+ if ($s == 0.0) {
+ $s = $norm;
+ }
+ if (abs($this->H[$l][$l - 1]) < $eps * $s) {
+ break;
+ }
+ --$l;
+ }
+ // Check for convergence
+ // One root found
+ if ($l == $n) {
+ $this->H[$n][$n] = $this->H[$n][$n] + $exshift;
+ $this->d[$n] = $this->H[$n][$n];
+ $this->e[$n] = 0.0;
+ --$n;
+ $iter = 0;
+ // Two roots found
+ } elseif ($l == $n - 1) {
+ $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n];
+ $p = ($this->H[$n - 1][$n - 1] - $this->H[$n][$n]) / 2.0;
+ $q = $p * $p + $w;
+ $z = sqrt(abs($q));
+ $this->H[$n][$n] = $this->H[$n][$n] + $exshift;
+ $this->H[$n - 1][$n - 1] = $this->H[$n - 1][$n - 1] + $exshift;
+ $x = $this->H[$n][$n];
+ // Real pair
+ if ($q >= 0) {
+ if ($p >= 0) {
+ $z = $p + $z;
+ } else {
+ $z = $p - $z;
+ }
+ $this->d[$n - 1] = $x + $z;
+ $this->d[$n] = $this->d[$n - 1];
+ if ($z != 0.0) {
+ $this->d[$n] = $x - $w / $z;
+ }
+ $this->e[$n - 1] = 0.0;
+ $this->e[$n] = 0.0;
+ $x = $this->H[$n][$n - 1];
+ $s = abs($x) + abs($z);
+ $p = $x / $s;
+ $q = $z / $s;
+ $r = sqrt($p * $p + $q * $q);
+ $p = $p / $r;
+ $q = $q / $r;
+ // Row modification
+ for ($j = $n - 1; $j < $nn; ++$j) {
+ $z = $this->H[$n - 1][$j];
+ $this->H[$n - 1][$j] = $q * $z + $p * $this->H[$n][$j];
+ $this->H[$n][$j] = $q * $this->H[$n][$j] - $p * $z;
+ }
+ // Column modification
+ for ($i = 0; $i <= $n; ++$i) {
+ $z = $this->H[$i][$n - 1];
+ $this->H[$i][$n - 1] = $q * $z + $p * $this->H[$i][$n];
+ $this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z;
+ }
+ // Accumulate transformations
+ for ($i = $low; $i <= $high; ++$i) {
+ $z = $this->V[$i][$n - 1];
+ $this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n];
+ $this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z;
+ }
+ // Complex pair
+ } else {
+ $this->d[$n - 1] = $x + $p;
+ $this->d[$n] = $x + $p;
+ $this->e[$n - 1] = $z;
+ $this->e[$n] = -$z;
+ }
+ $n = $n - 2;
+ $iter = 0;
+ // No convergence yet
+ } else {
+ // Form shift
+ $x = $this->H[$n][$n];
+ $y = 0.0;
+ $w = 0.0;
+ if ($l < $n) {
+ $y = $this->H[$n - 1][$n - 1];
+ $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n];
+ }
+ // Wilkinson's original ad hoc shift
+ if ($iter == 10) {
+ $exshift += $x;
+ for ($i = $low; $i <= $n; ++$i) {
+ $this->H[$i][$i] -= $x;
+ }
+ $s = abs($this->H[$n][$n - 1]) + abs($this->H[$n - 1][$n - 2]);
+ $x = $y = 0.75 * $s;
+ $w = -0.4375 * $s * $s;
+ }
+ // MATLAB's new ad hoc shift
+ if ($iter == 30) {
+ $s = ($y - $x) / 2.0;
+ $s = $s * $s + $w;
+ if ($s > 0) {
+ $s = sqrt($s);
+ if ($y < $x) {
+ $s = -$s;
+ }
+ $s = $x - $w / (($y - $x) / 2.0 + $s);
+ for ($i = $low; $i <= $n; ++$i) {
+ $this->H[$i][$i] -= $s;
+ }
+ $exshift += $s;
+ $x = $y = $w = 0.964;
+ }
+ }
+ // Could check iteration count here.
+ $iter = $iter + 1;
+ // Look for two consecutive small sub-diagonal elements
+ $m = $n - 2;
+ while ($m >= $l) {
+ $z = $this->H[$m][$m];
+ $r = $x - $z;
+ $s = $y - $z;
+ $p = ($r * $s - $w) / $this->H[$m + 1][$m] + $this->H[$m][$m + 1];
+ $q = $this->H[$m + 1][$m + 1] - $z - $r - $s;
+ $r = $this->H[$m + 2][$m + 1];
+ $s = abs($p) + abs($q) + abs($r);
+ $p = $p / $s;
+ $q = $q / $s;
+ $r = $r / $s;
+ if ($m == $l) {
+ break;
+ }
+ if (abs($this->H[$m][$m - 1]) * (abs($q) + abs($r)) <
+ $eps * (abs($p) * (abs($this->H[$m - 1][$m - 1]) + abs($z) + abs($this->H[$m + 1][$m + 1])))) {
+ break;
+ }
+ --$m;
+ }
+ for ($i = $m + 2; $i <= $n; ++$i) {
+ $this->H[$i][$i - 2] = 0.0;
+ if ($i > $m + 2) {
+ $this->H[$i][$i - 3] = 0.0;
+ }
+ }
+ // Double QR step involving rows l:n and columns m:n
+ for ($k = $m; $k <= $n - 1; ++$k) {
+ $notlast = ($k != $n - 1);
+ if ($k != $m) {
+ $p = $this->H[$k][$k - 1];
+ $q = $this->H[$k + 1][$k - 1];
+ $r = ($notlast ? $this->H[$k + 2][$k - 1] : 0.0);
+ $x = abs($p) + abs($q) + abs($r);
+ if ($x != 0.0) {
+ $p = $p / $x;
+ $q = $q / $x;
+ $r = $r / $x;
+ }
+ }
+ if ($x == 0.0) {
+ break;
+ }
+ $s = sqrt($p * $p + $q * $q + $r * $r);
+ if ($p < 0) {
+ $s = -$s;
+ }
+ if ($s != 0) {
+ if ($k != $m) {
+ $this->H[$k][$k - 1] = -$s * $x;
+ } elseif ($l != $m) {
+ $this->H[$k][$k - 1] = -$this->H[$k][$k - 1];
+ }
+ $p = $p + $s;
+ $x = $p / $s;
+ $y = $q / $s;
+ $z = $r / $s;
+ $q = $q / $p;
+ $r = $r / $p;
+ // Row modification
+ for ($j = $k; $j < $nn; ++$j) {
+ $p = $this->H[$k][$j] + $q * $this->H[$k + 1][$j];
+ if ($notlast) {
+ $p = $p + $r * $this->H[$k + 2][$j];
+ $this->H[$k + 2][$j] = $this->H[$k + 2][$j] - $p * $z;
+ }
+ $this->H[$k][$j] = $this->H[$k][$j] - $p * $x;
+ $this->H[$k + 1][$j] = $this->H[$k + 1][$j] - $p * $y;
+ }
+ // Column modification
+ $iMax = min($n, $k + 3);
+ for ($i = 0; $i <= $iMax; ++$i) {
+ $p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k + 1];
+ if ($notlast) {
+ $p = $p + $z * $this->H[$i][$k + 2];
+ $this->H[$i][$k + 2] = $this->H[$i][$k + 2] - $p * $r;
+ }
+ $this->H[$i][$k] = $this->H[$i][$k] - $p;
+ $this->H[$i][$k + 1] = $this->H[$i][$k + 1] - $p * $q;
+ }
+ // Accumulate transformations
+ for ($i = $low; $i <= $high; ++$i) {
+ $p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k + 1];
+ if ($notlast) {
+ $p = $p + $z * $this->V[$i][$k + 2];
+ $this->V[$i][$k + 2] = $this->V[$i][$k + 2] - $p * $r;
+ }
+ $this->V[$i][$k] = $this->V[$i][$k] - $p;
+ $this->V[$i][$k + 1] = $this->V[$i][$k + 1] - $p * $q;
+ }
+ } // ($s != 0)
+ } // k loop
+ } // check convergence
+ } // while ($n >= $low)
+
+ // Backsubstitute to find vectors of upper triangular form
+ if ($norm == 0.0) {
+ return;
+ }
+
+ for ($n = $nn - 1; $n >= 0; --$n) {
+ $p = $this->d[$n];
+ $q = $this->e[$n];
+ // Real vector
+ if ($q == 0) {
+ $l = $n;
+ $this->H[$n][$n] = 1.0;
+ for ($i = $n - 1; $i >= 0; --$i) {
+ $w = $this->H[$i][$i] - $p;
+ $r = 0.0;
+ for ($j = $l; $j <= $n; ++$j) {
+ $r = $r + $this->H[$i][$j] * $this->H[$j][$n];
+ }
+ if ($this->e[$i] < 0.0) {
+ $z = $w;
+ $s = $r;
+ } else {
+ $l = $i;
+ if ($this->e[$i] == 0.0) {
+ if ($w != 0.0) {
+ $this->H[$i][$n] = -$r / $w;
+ } else {
+ $this->H[$i][$n] = -$r / ($eps * $norm);
+ }
+ // Solve real equations
+ } else {
+ $x = $this->H[$i][$i + 1];
+ $y = $this->H[$i + 1][$i];
+ $q = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i];
+ $t = ($x * $s - $z * $r) / $q;
+ $this->H[$i][$n] = $t;
+ if (abs($x) > abs($z)) {
+ $this->H[$i + 1][$n] = (-$r - $w * $t) / $x;
+ } else {
+ $this->H[$i + 1][$n] = (-$s - $y * $t) / $z;
+ }
+ }
+ // Overflow control
+ $t = abs($this->H[$i][$n]);
+ if (($eps * $t) * $t > 1) {
+ for ($j = $i; $j <= $n; ++$j) {
+ $this->H[$j][$n] = $this->H[$j][$n] / $t;
+ }
+ }
+ }
+ }
+ // Complex vector
+ } elseif ($q < 0) {
+ $l = $n - 1;
+ // Last vector component imaginary so matrix is triangular
+ if (abs($this->H[$n][$n - 1]) > abs($this->H[$n - 1][$n])) {
+ $this->H[$n - 1][$n - 1] = $q / $this->H[$n][$n - 1];
+ $this->H[$n - 1][$n] = -($this->H[$n][$n] - $p) / $this->H[$n][$n - 1];
+ } else {
+ $this->cdiv(0.0, -$this->H[$n - 1][$n], $this->H[$n - 1][$n - 1] - $p, $q);
+ $this->H[$n - 1][$n - 1] = $this->cdivr;
+ $this->H[$n - 1][$n] = $this->cdivi;
+ }
+ $this->H[$n][$n - 1] = 0.0;
+ $this->H[$n][$n] = 1.0;
+ for ($i = $n - 2; $i >= 0; --$i) {
+ // double ra,sa,vr,vi;
+ $ra = 0.0;
+ $sa = 0.0;
+ for ($j = $l; $j <= $n; ++$j) {
+ $ra = $ra + $this->H[$i][$j] * $this->H[$j][$n - 1];
+ $sa = $sa + $this->H[$i][$j] * $this->H[$j][$n];
+ }
+ $w = $this->H[$i][$i] - $p;
+ if ($this->e[$i] < 0.0) {
+ $z = $w;
+ $r = $ra;
+ $s = $sa;
+ } else {
+ $l = $i;
+ if ($this->e[$i] == 0) {
+ $this->cdiv(-$ra, -$sa, $w, $q);
+ $this->H[$i][$n - 1] = $this->cdivr;
+ $this->H[$i][$n] = $this->cdivi;
+ } else {
+ // Solve complex equations
+ $x = $this->H[$i][$i + 1];
+ $y = $this->H[$i + 1][$i];
+ $vr = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i] - $q * $q;
+ $vi = ($this->d[$i] - $p) * 2.0 * $q;
+ if ($vr == 0.0 & $vi == 0.0) {
+ $vr = $eps * $norm * (abs($w) + abs($q) + abs($x) + abs($y) + abs($z));
+ }
+ $this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi);
+ $this->H[$i][$n - 1] = $this->cdivr;
+ $this->H[$i][$n] = $this->cdivi;
+ if (abs($x) > (abs($z) + abs($q))) {
+ $this->H[$i + 1][$n - 1] = (-$ra - $w * $this->H[$i][$n - 1] + $q * $this->H[$i][$n]) / $x;
+ $this->H[$i + 1][$n] = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n - 1]) / $x;
+ } else {
+ $this->cdiv(-$r - $y * $this->H[$i][$n - 1], -$s - $y * $this->H[$i][$n], $z, $q);
+ $this->H[$i + 1][$n - 1] = $this->cdivr;
+ $this->H[$i + 1][$n] = $this->cdivi;
+ }
+ }
+ // Overflow control
+ $t = max(abs($this->H[$i][$n - 1]), abs($this->H[$i][$n]));
+ if (($eps * $t) * $t > 1) {
+ for ($j = $i; $j <= $n; ++$j) {
+ $this->H[$j][$n - 1] = $this->H[$j][$n - 1] / $t;
+ $this->H[$j][$n] = $this->H[$j][$n] / $t;
+ }
+ }
+ } // end else
+ } // end for
+ } // end else for complex case
+ } // end for
+
+ // Vectors of isolated roots
+ for ($i = 0; $i < $nn; ++$i) {
+ if ($i < $low | $i > $high) {
+ for ($j = $i; $j < $nn; ++$j) {
+ $this->V[$i][$j] = $this->H[$i][$j];
+ }
+ }
+ }
+
+ // Back transformation to get eigenvectors of original matrix
+ for ($j = $nn - 1; $j >= $low; --$j) {
+ for ($i = $low; $i <= $high; ++$i) {
+ $z = 0.0;
+ $kMax = min($j, $high);
+ for ($k = $low; $k <= $kMax; ++$k) {
+ $z = $z + $this->V[$i][$k] * $this->H[$k][$j];
+ }
+ $this->V[$i][$j] = $z;
+ }
+ }
+ }
+
+ // end hqr2
+
+ /**
+ * Constructor: Check for symmetry, then construct the eigenvalue decomposition.
+ *
+ * @param mixed $Arg A Square matrix
+ */
+ public function __construct($Arg)
+ {
+ $this->A = $Arg->getArray();
+ $this->n = $Arg->getColumnDimension();
+
+ $issymmetric = true;
+ for ($j = 0; ($j < $this->n) & $issymmetric; ++$j) {
+ for ($i = 0; ($i < $this->n) & $issymmetric; ++$i) {
+ $issymmetric = ($this->A[$i][$j] == $this->A[$j][$i]);
+ }
+ }
+
+ if ($issymmetric) {
+ $this->V = $this->A;
+ // Tridiagonalize.
+ $this->tred2();
+ // Diagonalize.
+ $this->tql2();
+ } else {
+ $this->H = $this->A;
+ $this->ort = [];
+ // Reduce to Hessenberg form.
+ $this->orthes();
+ // Reduce Hessenberg to real Schur form.
+ $this->hqr2();
+ }
+ }
+
+ /**
+ * Return the eigenvector matrix.
+ *
+ * @return Matrix V
+ */
+ public function getV()
+ {
+ return new Matrix($this->V, $this->n, $this->n);
+ }
+
+ /**
+ * Return the real parts of the eigenvalues.
+ *
+ * @return array real(diag(D))
+ */
+ public function getRealEigenvalues()
+ {
+ return $this->d;
+ }
+
+ /**
+ * Return the imaginary parts of the eigenvalues.
+ *
+ * @return array imag(diag(D))
+ */
+ public function getImagEigenvalues()
+ {
+ return $this->e;
+ }
+
+ /**
+ * Return the block diagonal eigenvalue matrix.
+ *
+ * @return Matrix D
+ */
+ public function getD()
+ {
+ for ($i = 0; $i < $this->n; ++$i) {
+ $D[$i] = array_fill(0, $this->n, 0.0);
+ $D[$i][$i] = $this->d[$i];
+ if ($this->e[$i] == 0) {
+ continue;
+ }
+ $o = ($this->e[$i] > 0) ? $i + 1 : $i - 1;
+ $D[$i][$o] = $this->e[$i];
+ }
+
+ return new Matrix($D);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php
new file mode 100755
index 0000000..4aecff7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php
@@ -0,0 +1,282 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
+
+/**
+ * For an m-by-n matrix A with m >= n, the LU decomposition is an m-by-n
+ * unit lower triangular matrix L, an n-by-n upper triangular matrix U,
+ * and a permutation vector piv of length m so that A(piv,:) = L*U.
+ * If m < n, then L is m-by-m and U is m-by-n.
+ *
+ * The LU decompostion with pivoting always exists, even if the matrix is
+ * singular, so the constructor will never fail. The primary use of the
+ * LU decomposition is in the solution of square systems of simultaneous
+ * linear equations. This will fail if isNonsingular() returns false.
+ *
+ * @author Paul Meagher
+ * @author Bartosz Matosiuk
+ * @author Michael Bommarito
+ *
+ * @version 1.1
+ */
+class LUDecomposition
+{
+ const MATRIX_SINGULAR_EXCEPTION = 'Can only perform operation on singular matrix.';
+ const MATRIX_SQUARE_EXCEPTION = 'Mismatched Row dimension';
+
+ /**
+ * Decomposition storage.
+ *
+ * @var array
+ */
+ private $LU = [];
+
+ /**
+ * Row dimension.
+ *
+ * @var int
+ */
+ private $m;
+
+ /**
+ * Column dimension.
+ *
+ * @var int
+ */
+ private $n;
+
+ /**
+ * Pivot sign.
+ *
+ * @var int
+ */
+ private $pivsign;
+
+ /**
+ * Internal storage of pivot vector.
+ *
+ * @var array
+ */
+ private $piv = [];
+
+ /**
+ * LU Decomposition constructor.
+ *
+ * @param Matrix $A Rectangular matrix
+ */
+ public function __construct($A)
+ {
+ if ($A instanceof Matrix) {
+ // Use a "left-looking", dot-product, Crout/Doolittle algorithm.
+ $this->LU = $A->getArray();
+ $this->m = $A->getRowDimension();
+ $this->n = $A->getColumnDimension();
+ for ($i = 0; $i < $this->m; ++$i) {
+ $this->piv[$i] = $i;
+ }
+ $this->pivsign = 1;
+ $LUrowi = $LUcolj = [];
+
+ // Outer loop.
+ for ($j = 0; $j < $this->n; ++$j) {
+ // Make a copy of the j-th column to localize references.
+ for ($i = 0; $i < $this->m; ++$i) {
+ $LUcolj[$i] = &$this->LU[$i][$j];
+ }
+ // Apply previous transformations.
+ for ($i = 0; $i < $this->m; ++$i) {
+ $LUrowi = $this->LU[$i];
+ // Most of the time is spent in the following dot product.
+ $kmax = min($i, $j);
+ $s = 0.0;
+ for ($k = 0; $k < $kmax; ++$k) {
+ $s += $LUrowi[$k] * $LUcolj[$k];
+ }
+ $LUrowi[$j] = $LUcolj[$i] -= $s;
+ }
+ // Find pivot and exchange if necessary.
+ $p = $j;
+ for ($i = $j + 1; $i < $this->m; ++$i) {
+ if (abs($LUcolj[$i]) > abs($LUcolj[$p])) {
+ $p = $i;
+ }
+ }
+ if ($p != $j) {
+ for ($k = 0; $k < $this->n; ++$k) {
+ $t = $this->LU[$p][$k];
+ $this->LU[$p][$k] = $this->LU[$j][$k];
+ $this->LU[$j][$k] = $t;
+ }
+ $k = $this->piv[$p];
+ $this->piv[$p] = $this->piv[$j];
+ $this->piv[$j] = $k;
+ $this->pivsign = $this->pivsign * -1;
+ }
+ // Compute multipliers.
+ if (($j < $this->m) && ($this->LU[$j][$j] != 0.0)) {
+ for ($i = $j + 1; $i < $this->m; ++$i) {
+ $this->LU[$i][$j] /= $this->LU[$j][$j];
+ }
+ }
+ }
+ } else {
+ throw new CalculationException(Matrix::ARGUMENT_TYPE_EXCEPTION);
+ }
+ }
+
+ // function __construct()
+
+ /**
+ * Get lower triangular factor.
+ *
+ * @return Matrix Lower triangular factor
+ */
+ public function getL()
+ {
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ if ($i > $j) {
+ $L[$i][$j] = $this->LU[$i][$j];
+ } elseif ($i == $j) {
+ $L[$i][$j] = 1.0;
+ } else {
+ $L[$i][$j] = 0.0;
+ }
+ }
+ }
+
+ return new Matrix($L);
+ }
+
+ // function getL()
+
+ /**
+ * Get upper triangular factor.
+ *
+ * @return Matrix Upper triangular factor
+ */
+ public function getU()
+ {
+ for ($i = 0; $i < $this->n; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ if ($i <= $j) {
+ $U[$i][$j] = $this->LU[$i][$j];
+ } else {
+ $U[$i][$j] = 0.0;
+ }
+ }
+ }
+
+ return new Matrix($U);
+ }
+
+ // function getU()
+
+ /**
+ * Return pivot permutation vector.
+ *
+ * @return array Pivot vector
+ */
+ public function getPivot()
+ {
+ return $this->piv;
+ }
+
+ // function getPivot()
+
+ /**
+ * Alias for getPivot.
+ *
+ * @see getPivot
+ */
+ public function getDoublePivot()
+ {
+ return $this->getPivot();
+ }
+
+ // function getDoublePivot()
+
+ /**
+ * Is the matrix nonsingular?
+ *
+ * @return bool true if U, and hence A, is nonsingular
+ */
+ public function isNonsingular()
+ {
+ for ($j = 0; $j < $this->n; ++$j) {
+ if ($this->LU[$j][$j] == 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // function isNonsingular()
+
+ /**
+ * Count determinants.
+ *
+ * @return array d matrix deterninat
+ */
+ public function det()
+ {
+ if ($this->m == $this->n) {
+ $d = $this->pivsign;
+ for ($j = 0; $j < $this->n; ++$j) {
+ $d *= $this->LU[$j][$j];
+ }
+
+ return $d;
+ }
+
+ throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION);
+ }
+
+ // function det()
+
+ /**
+ * Solve A*X = B.
+ *
+ * @param mixed $B a Matrix with as many rows as A and any number of columns
+ *
+ * @return Matrix X so that L*U*X = B(piv,:)
+ */
+ public function solve($B)
+ {
+ if ($B->getRowDimension() == $this->m) {
+ if ($this->isNonsingular()) {
+ // Copy right hand side with pivoting
+ $nx = $B->getColumnDimension();
+ $X = $B->getMatrix($this->piv, 0, $nx - 1);
+ // Solve L*Y = B(piv,:)
+ for ($k = 0; $k < $this->n; ++$k) {
+ for ($i = $k + 1; $i < $this->n; ++$i) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X->A[$i][$j] -= $X->A[$k][$j] * $this->LU[$i][$k];
+ }
+ }
+ }
+ // Solve U*X = Y;
+ for ($k = $this->n - 1; $k >= 0; --$k) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X->A[$k][$j] /= $this->LU[$k][$k];
+ }
+ for ($i = 0; $i < $k; ++$i) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X->A[$i][$j] -= $X->A[$k][$j] * $this->LU[$i][$k];
+ }
+ }
+ }
+
+ return $X;
+ }
+
+ throw new CalculationException(self::MATRIX_SINGULAR_EXCEPTION);
+ }
+
+ throw new CalculationException(self::MATRIX_SQUARE_EXCEPTION);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/Matrix.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/Matrix.php
new file mode 100755
index 0000000..a5cb6de
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/Matrix.php
@@ -0,0 +1,1202 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+/**
+ * Matrix class.
+ *
+ * @author Paul Meagher
+ * @author Michael Bommarito
+ * @author Lukasz Karapuda
+ * @author Bartek Matosiuk
+ *
+ * @version 1.8
+ *
+ * @see https://math.nist.gov/javanumerics/jama/
+ */
+class Matrix
+{
+ const POLYMORPHIC_ARGUMENT_EXCEPTION = 'Invalid argument pattern for polymorphic function.';
+ const ARGUMENT_TYPE_EXCEPTION = 'Invalid argument type.';
+ const ARGUMENT_BOUNDS_EXCEPTION = 'Invalid argument range.';
+ const MATRIX_DIMENSION_EXCEPTION = 'Matrix dimensions are not equal.';
+ const ARRAY_LENGTH_EXCEPTION = 'Array length must be a multiple of m.';
+ const MATRIX_SPD_EXCEPTION = 'Can only perform operation on symmetric positive definite matrix.';
+
+ /**
+ * Matrix storage.
+ *
+ * @var array
+ */
+ public $A = [];
+
+ /**
+ * Matrix row dimension.
+ *
+ * @var int
+ */
+ private $m;
+
+ /**
+ * Matrix column dimension.
+ *
+ * @var int
+ */
+ private $n;
+
+ /**
+ * Polymorphic constructor.
+ *
+ * As PHP has no support for polymorphic constructors, we use tricks to make our own sort of polymorphism using func_num_args, func_get_arg, and gettype. In essence, we're just implementing a simple RTTI filter and calling the appropriate constructor.
+ */
+ public function __construct(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ //Rectangular matrix - m x n initialized from 2D array
+ case 'array':
+ $this->m = count($args[0]);
+ $this->n = count($args[0][0]);
+ $this->A = $args[0];
+
+ break;
+ //Square matrix - n x n
+ case 'integer':
+ $this->m = $args[0];
+ $this->n = $args[0];
+ $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));
+
+ break;
+ //Rectangular matrix - m x n
+ case 'integer,integer':
+ $this->m = $args[0];
+ $this->n = $args[1];
+ $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));
+
+ break;
+ //Rectangular matrix - m x n initialized from packed array
+ case 'array,integer':
+ $this->m = $args[1];
+ if ($this->m != 0) {
+ $this->n = count($args[0]) / $this->m;
+ } else {
+ $this->n = 0;
+ }
+ if (($this->m * $this->n) == count($args[0])) {
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $this->A[$i][$j] = $args[0][$i + $j * $this->m];
+ }
+ }
+ } else {
+ throw new CalculationException(self::ARRAY_LENGTH_EXCEPTION);
+ }
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ } else {
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+ }
+
+ /**
+ * getArray.
+ *
+ * @return array Matrix array
+ */
+ public function getArray()
+ {
+ return $this->A;
+ }
+
+ /**
+ * getRowDimension.
+ *
+ * @return int Row dimension
+ */
+ public function getRowDimension()
+ {
+ return $this->m;
+ }
+
+ /**
+ * getColumnDimension.
+ *
+ * @return int Column dimension
+ */
+ public function getColumnDimension()
+ {
+ return $this->n;
+ }
+
+ /**
+ * get.
+ *
+ * Get the i,j-th element of the matrix.
+ *
+ * @param int $i Row position
+ * @param int $j Column position
+ *
+ * @return mixed Element (int/float/double)
+ */
+ public function get($i = null, $j = null)
+ {
+ return $this->A[$i][$j];
+ }
+
+ /**
+ * getMatrix.
+ *
+ * Get a submatrix
+ *
+ * @return Matrix Submatrix
+ */
+ public function getMatrix(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ //A($i0...; $j0...)
+ case 'integer,integer':
+ [$i0, $j0] = $args;
+ if ($i0 >= 0) {
+ $m = $this->m - $i0;
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ if ($j0 >= 0) {
+ $n = $this->n - $j0;
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ $R = new self($m, $n);
+ for ($i = $i0; $i < $this->m; ++$i) {
+ for ($j = $j0; $j < $this->n; ++$j) {
+ $R->set($i, $j, $this->A[$i][$j]);
+ }
+ }
+
+ return $R;
+
+ break;
+ //A($i0...$iF; $j0...$jF)
+ case 'integer,integer,integer,integer':
+ [$i0, $iF, $j0, $jF] = $args;
+ if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
+ $m = $iF - $i0;
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ if (($jF > $j0) && ($this->n >= $jF) && ($j0 >= 0)) {
+ $n = $jF - $j0;
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ $R = new self($m + 1, $n + 1);
+ for ($i = $i0; $i <= $iF; ++$i) {
+ for ($j = $j0; $j <= $jF; ++$j) {
+ $R->set($i - $i0, $j - $j0, $this->A[$i][$j]);
+ }
+ }
+
+ return $R;
+
+ break;
+ //$R = array of row indices; $C = array of column indices
+ case 'array,array':
+ [$RL, $CL] = $args;
+ if (count($RL) > 0) {
+ $m = count($RL);
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ if (count($CL) > 0) {
+ $n = count($CL);
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ $R = new self($m, $n);
+ for ($i = 0; $i < $m; ++$i) {
+ for ($j = 0; $j < $n; ++$j) {
+ $R->set($i, $j, $this->A[$RL[$i]][$CL[$j]]);
+ }
+ }
+
+ return $R;
+
+ break;
+ //A($i0...$iF); $CL = array of column indices
+ case 'integer,integer,array':
+ [$i0, $iF, $CL] = $args;
+ if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
+ $m = $iF - $i0;
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ if (count($CL) > 0) {
+ $n = count($CL);
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ $R = new self($m, $n);
+ for ($i = $i0; $i < $iF; ++$i) {
+ for ($j = 0; $j < $n; ++$j) {
+ $R->set($i - $i0, $j, $this->A[$i][$CL[$j]]);
+ }
+ }
+
+ return $R;
+
+ break;
+ //$RL = array of row indices
+ case 'array,integer,integer':
+ [$RL, $j0, $jF] = $args;
+ if (count($RL) > 0) {
+ $m = count($RL);
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ if (($jF >= $j0) && ($this->n >= $jF) && ($j0 >= 0)) {
+ $n = $jF - $j0;
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ $R = new self($m, $n + 1);
+ for ($i = 0; $i < $m; ++$i) {
+ for ($j = $j0; $j <= $jF; ++$j) {
+ $R->set($i, $j - $j0, $this->A[$RL[$i]][$j]);
+ }
+ }
+
+ return $R;
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ } else {
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+ }
+
+ /**
+ * checkMatrixDimensions.
+ *
+ * Is matrix B the same size?
+ *
+ * @param Matrix $B Matrix B
+ *
+ * @return bool
+ */
+ public function checkMatrixDimensions($B = null)
+ {
+ if ($B instanceof self) {
+ if (($this->m == $B->getRowDimension()) && ($this->n == $B->getColumnDimension())) {
+ return true;
+ }
+
+ throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
+ }
+
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ // function checkMatrixDimensions()
+
+ /**
+ * set.
+ *
+ * Set the i,j-th element of the matrix.
+ *
+ * @param int $i Row position
+ * @param int $j Column position
+ * @param mixed $c Int/float/double value
+ *
+ * @return mixed Element (int/float/double)
+ */
+ public function set($i = null, $j = null, $c = null)
+ {
+ // Optimized set version just has this
+ $this->A[$i][$j] = $c;
+ }
+
+ // function set()
+
+ /**
+ * identity.
+ *
+ * Generate an identity matrix.
+ *
+ * @param int $m Row dimension
+ * @param int $n Column dimension
+ *
+ * @return Matrix Identity matrix
+ */
+ public function identity($m = null, $n = null)
+ {
+ return $this->diagonal($m, $n, 1);
+ }
+
+ /**
+ * diagonal.
+ *
+ * Generate a diagonal matrix
+ *
+ * @param int $m Row dimension
+ * @param int $n Column dimension
+ * @param mixed $c Diagonal value
+ *
+ * @return Matrix Diagonal matrix
+ */
+ public function diagonal($m = null, $n = null, $c = 1)
+ {
+ $R = new self($m, $n);
+ for ($i = 0; $i < $m; ++$i) {
+ $R->set($i, $i, $c);
+ }
+
+ return $R;
+ }
+
+ /**
+ * getMatrixByRow.
+ *
+ * Get a submatrix by row index/range
+ *
+ * @param int $i0 Initial row index
+ * @param int $iF Final row index
+ *
+ * @return Matrix Submatrix
+ */
+ public function getMatrixByRow($i0 = null, $iF = null)
+ {
+ if (is_int($i0)) {
+ if (is_int($iF)) {
+ return $this->getMatrix($i0, 0, $iF + 1, $this->n);
+ }
+
+ return $this->getMatrix($i0, 0, $i0 + 1, $this->n);
+ }
+
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ /**
+ * getMatrixByCol.
+ *
+ * Get a submatrix by column index/range
+ *
+ * @param int $j0 Initial column index
+ * @param int $jF Final column index
+ *
+ * @return Matrix Submatrix
+ */
+ public function getMatrixByCol($j0 = null, $jF = null)
+ {
+ if (is_int($j0)) {
+ if (is_int($jF)) {
+ return $this->getMatrix(0, $j0, $this->m, $jF + 1);
+ }
+
+ return $this->getMatrix(0, $j0, $this->m, $j0 + 1);
+ }
+
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ /**
+ * transpose.
+ *
+ * Tranpose matrix
+ *
+ * @return Matrix Transposed matrix
+ */
+ public function transpose()
+ {
+ $R = new self($this->n, $this->m);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $R->set($j, $i, $this->A[$i][$j]);
+ }
+ }
+
+ return $R;
+ }
+
+ // function transpose()
+
+ /**
+ * trace.
+ *
+ * Sum of diagonal elements
+ *
+ * @return float Sum of diagonal elements
+ */
+ public function trace()
+ {
+ $s = 0;
+ $n = min($this->m, $this->n);
+ for ($i = 0; $i < $n; ++$i) {
+ $s += $this->A[$i][$i];
+ }
+
+ return $s;
+ }
+
+ /**
+ * uminus.
+ *
+ * Unary minus matrix -A
+ *
+ * @return Matrix Unary minus matrix
+ */
+ public function uminus()
+ {
+ }
+
+ /**
+ * plus.
+ *
+ * A + B
+ *
+ * @return Matrix Sum
+ */
+ public function plus(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $M->set($i, $j, $M->get($i, $j) + $this->A[$i][$j]);
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * plusEquals.
+ *
+ * A = A + B
+ *
+ * @return $this
+ */
+ public function plusEquals(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $validValues = true;
+ $value = $M->get($i, $j);
+ if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
+ $this->A[$i][$j] = trim($this->A[$i][$j], '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
+ }
+ if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
+ $value = trim($value, '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($value);
+ }
+ if ($validValues) {
+ $this->A[$i][$j] += $value;
+ } else {
+ $this->A[$i][$j] = Functions::NAN();
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * minus.
+ *
+ * A - B
+ *
+ * @return Matrix Sum
+ */
+ public function minus(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $M->set($i, $j, $M->get($i, $j) - $this->A[$i][$j]);
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * minusEquals.
+ *
+ * A = A - B
+ *
+ * @return $this
+ */
+ public function minusEquals(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $validValues = true;
+ $value = $M->get($i, $j);
+ if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
+ $this->A[$i][$j] = trim($this->A[$i][$j], '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
+ }
+ if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
+ $value = trim($value, '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($value);
+ }
+ if ($validValues) {
+ $this->A[$i][$j] -= $value;
+ } else {
+ $this->A[$i][$j] = Functions::NAN();
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * arrayTimes.
+ *
+ * Element-by-element multiplication
+ * Cij = Aij * Bij
+ *
+ * @return Matrix Matrix Cij
+ */
+ public function arrayTimes(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $M->set($i, $j, $M->get($i, $j) * $this->A[$i][$j]);
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * arrayTimesEquals.
+ *
+ * Element-by-element multiplication
+ * Aij = Aij * Bij
+ *
+ * @return $this
+ */
+ public function arrayTimesEquals(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $validValues = true;
+ $value = $M->get($i, $j);
+ if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
+ $this->A[$i][$j] = trim($this->A[$i][$j], '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
+ }
+ if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
+ $value = trim($value, '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($value);
+ }
+ if ($validValues) {
+ $this->A[$i][$j] *= $value;
+ } else {
+ $this->A[$i][$j] = Functions::NAN();
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * arrayRightDivide.
+ *
+ * Element-by-element right division
+ * A / B
+ *
+ * @return Matrix Division result
+ */
+ public function arrayRightDivide(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $validValues = true;
+ $value = $M->get($i, $j);
+ if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
+ $this->A[$i][$j] = trim($this->A[$i][$j], '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
+ }
+ if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
+ $value = trim($value, '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($value);
+ }
+ if ($validValues) {
+ if ($value == 0) {
+ // Trap for Divide by Zero error
+ $M->set($i, $j, '#DIV/0!');
+ } else {
+ $M->set($i, $j, $this->A[$i][$j] / $value);
+ }
+ } else {
+ $M->set($i, $j, Functions::NAN());
+ }
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * arrayRightDivideEquals.
+ *
+ * Element-by-element right division
+ * Aij = Aij / Bij
+ *
+ * @return Matrix Matrix Aij
+ */
+ public function arrayRightDivideEquals(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $this->A[$i][$j] = $this->A[$i][$j] / $M->get($i, $j);
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * arrayLeftDivide.
+ *
+ * Element-by-element Left division
+ * A / B
+ *
+ * @return Matrix Division result
+ */
+ public function arrayLeftDivide(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $M->set($i, $j, $M->get($i, $j) / $this->A[$i][$j]);
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * arrayLeftDivideEquals.
+ *
+ * Element-by-element Left division
+ * Aij = Aij / Bij
+ *
+ * @return Matrix Matrix Aij
+ */
+ public function arrayLeftDivideEquals(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $this->A[$i][$j] = $M->get($i, $j) / $this->A[$i][$j];
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * times.
+ *
+ * Matrix multiplication
+ *
+ * @return Matrix Product
+ */
+ public function times(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $B = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+ if ($this->n == $B->m) {
+ $C = new self($this->m, $B->n);
+ for ($j = 0; $j < $B->n; ++$j) {
+ $Bcolj = [];
+ for ($k = 0; $k < $this->n; ++$k) {
+ $Bcolj[$k] = $B->A[$k][$j];
+ }
+ for ($i = 0; $i < $this->m; ++$i) {
+ $Arowi = $this->A[$i];
+ $s = 0;
+ for ($k = 0; $k < $this->n; ++$k) {
+ $s += $Arowi[$k] * $Bcolj[$k];
+ }
+ $C->A[$i][$j] = $s;
+ }
+ }
+
+ return $C;
+ }
+
+ throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
+ case 'array':
+ $B = new self($args[0]);
+ if ($this->n == $B->m) {
+ $C = new self($this->m, $B->n);
+ for ($i = 0; $i < $C->m; ++$i) {
+ for ($j = 0; $j < $C->n; ++$j) {
+ $s = '0';
+ for ($k = 0; $k < $C->n; ++$k) {
+ $s += $this->A[$i][$k] * $B->A[$k][$j];
+ }
+ $C->A[$i][$j] = $s;
+ }
+ }
+
+ return $C;
+ }
+
+ throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
+ case 'integer':
+ $C = new self($this->A);
+ for ($i = 0; $i < $C->m; ++$i) {
+ for ($j = 0; $j < $C->n; ++$j) {
+ $C->A[$i][$j] *= $args[0];
+ }
+ }
+
+ return $C;
+ case 'double':
+ $C = new self($this->m, $this->n);
+ for ($i = 0; $i < $C->m; ++$i) {
+ for ($j = 0; $j < $C->n; ++$j) {
+ $C->A[$i][$j] = $args[0] * $this->A[$i][$j];
+ }
+ }
+
+ return $C;
+ case 'float':
+ $C = new self($this->A);
+ for ($i = 0; $i < $C->m; ++$i) {
+ for ($j = 0; $j < $C->n; ++$j) {
+ $C->A[$i][$j] *= $args[0];
+ }
+ }
+
+ return $C;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+ } else {
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+ }
+
+ /**
+ * power.
+ *
+ * A = A ^ B
+ *
+ * @return $this
+ */
+ public function power(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $validValues = true;
+ $value = $M->get($i, $j);
+ if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
+ $this->A[$i][$j] = trim($this->A[$i][$j], '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
+ }
+ if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
+ $value = trim($value, '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($value);
+ }
+ if ($validValues) {
+ $this->A[$i][$j] = $this->A[$i][$j] ** $value;
+ } else {
+ $this->A[$i][$j] = Functions::NAN();
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * concat.
+ *
+ * A = A & B
+ *
+ * @return $this
+ */
+ public function concat(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $this->A[$i][$j] = trim($this->A[$i][$j], '"') . trim($M->get($i, $j), '"');
+ }
+ }
+
+ return $this;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * Solve A*X = B.
+ *
+ * @param Matrix $B Right hand side
+ *
+ * @return Matrix ... Solution if A is square, least squares solution otherwise
+ */
+ public function solve($B)
+ {
+ if ($this->m == $this->n) {
+ $LU = new LUDecomposition($this);
+
+ return $LU->solve($B);
+ }
+ $QR = new QRDecomposition($this);
+
+ return $QR->solve($B);
+ }
+
+ /**
+ * Matrix inverse or pseudoinverse.
+ *
+ * @return Matrix ... Inverse(A) if A is square, pseudoinverse otherwise.
+ */
+ public function inverse()
+ {
+ return $this->solve($this->identity($this->m, $this->m));
+ }
+
+ /**
+ * det.
+ *
+ * Calculate determinant
+ *
+ * @return float Determinant
+ */
+ public function det()
+ {
+ $L = new LUDecomposition($this);
+
+ return $L->det();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php
new file mode 100755
index 0000000..3bb8a10
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php
@@ -0,0 +1,249 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
+
+/**
+ * For an m-by-n matrix A with m >= n, the QR decomposition is an m-by-n
+ * orthogonal matrix Q and an n-by-n upper triangular matrix R so that
+ * A = Q*R.
+ *
+ * The QR decompostion always exists, even if the matrix does not have
+ * full rank, so the constructor will never fail. The primary use of the
+ * QR decomposition is in the least squares solution of nonsquare systems
+ * of simultaneous linear equations. This will fail if isFullRank()
+ * returns false.
+ *
+ * @author Paul Meagher
+ *
+ * @version 1.1
+ */
+class QRDecomposition
+{
+ const MATRIX_RANK_EXCEPTION = 'Can only perform operation on full-rank matrix.';
+
+ /**
+ * Array for internal storage of decomposition.
+ *
+ * @var array
+ */
+ private $QR = [];
+
+ /**
+ * Row dimension.
+ *
+ * @var int
+ */
+ private $m;
+
+ /**
+ * Column dimension.
+ *
+ * @var int
+ */
+ private $n;
+
+ /**
+ * Array for internal storage of diagonal of R.
+ *
+ * @var array
+ */
+ private $Rdiag = [];
+
+ /**
+ * QR Decomposition computed by Householder reflections.
+ *
+ * @param matrix $A Rectangular matrix
+ */
+ public function __construct($A)
+ {
+ if ($A instanceof Matrix) {
+ // Initialize.
+ $this->QR = $A->getArray();
+ $this->m = $A->getRowDimension();
+ $this->n = $A->getColumnDimension();
+ // Main loop.
+ for ($k = 0; $k < $this->n; ++$k) {
+ // Compute 2-norm of k-th column without under/overflow.
+ $nrm = 0.0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $nrm = hypo($nrm, $this->QR[$i][$k]);
+ }
+ if ($nrm != 0.0) {
+ // Form k-th Householder vector.
+ if ($this->QR[$k][$k] < 0) {
+ $nrm = -$nrm;
+ }
+ for ($i = $k; $i < $this->m; ++$i) {
+ $this->QR[$i][$k] /= $nrm;
+ }
+ $this->QR[$k][$k] += 1.0;
+ // Apply transformation to remaining columns.
+ for ($j = $k + 1; $j < $this->n; ++$j) {
+ $s = 0.0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $s += $this->QR[$i][$k] * $this->QR[$i][$j];
+ }
+ $s = -$s / $this->QR[$k][$k];
+ for ($i = $k; $i < $this->m; ++$i) {
+ $this->QR[$i][$j] += $s * $this->QR[$i][$k];
+ }
+ }
+ }
+ $this->Rdiag[$k] = -$nrm;
+ }
+ } else {
+ throw new CalculationException(Matrix::ARGUMENT_TYPE_EXCEPTION);
+ }
+ }
+
+ // function __construct()
+
+ /**
+ * Is the matrix full rank?
+ *
+ * @return bool true if R, and hence A, has full rank, else false
+ */
+ public function isFullRank()
+ {
+ for ($j = 0; $j < $this->n; ++$j) {
+ if ($this->Rdiag[$j] == 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // function isFullRank()
+
+ /**
+ * Return the Householder vectors.
+ *
+ * @return Matrix Lower trapezoidal matrix whose columns define the reflections
+ */
+ public function getH()
+ {
+ $H = [];
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ if ($i >= $j) {
+ $H[$i][$j] = $this->QR[$i][$j];
+ } else {
+ $H[$i][$j] = 0.0;
+ }
+ }
+ }
+
+ return new Matrix($H);
+ }
+
+ // function getH()
+
+ /**
+ * Return the upper triangular factor.
+ *
+ * @return Matrix upper triangular factor
+ */
+ public function getR()
+ {
+ $R = [];
+ for ($i = 0; $i < $this->n; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ if ($i < $j) {
+ $R[$i][$j] = $this->QR[$i][$j];
+ } elseif ($i == $j) {
+ $R[$i][$j] = $this->Rdiag[$i];
+ } else {
+ $R[$i][$j] = 0.0;
+ }
+ }
+ }
+
+ return new Matrix($R);
+ }
+
+ // function getR()
+
+ /**
+ * Generate and return the (economy-sized) orthogonal factor.
+ *
+ * @return Matrix orthogonal factor
+ */
+ public function getQ()
+ {
+ $Q = [];
+ for ($k = $this->n - 1; $k >= 0; --$k) {
+ for ($i = 0; $i < $this->m; ++$i) {
+ $Q[$i][$k] = 0.0;
+ }
+ $Q[$k][$k] = 1.0;
+ for ($j = $k; $j < $this->n; ++$j) {
+ if ($this->QR[$k][$k] != 0) {
+ $s = 0.0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $s += $this->QR[$i][$k] * $Q[$i][$j];
+ }
+ $s = -$s / $this->QR[$k][$k];
+ for ($i = $k; $i < $this->m; ++$i) {
+ $Q[$i][$j] += $s * $this->QR[$i][$k];
+ }
+ }
+ }
+ }
+
+ return new Matrix($Q);
+ }
+
+ // function getQ()
+
+ /**
+ * Least squares solution of A*X = B.
+ *
+ * @param Matrix $B a Matrix with as many rows as A and any number of columns
+ *
+ * @return Matrix matrix that minimizes the two norm of Q*R*X-B
+ */
+ public function solve($B)
+ {
+ if ($B->getRowDimension() == $this->m) {
+ if ($this->isFullRank()) {
+ // Copy right hand side
+ $nx = $B->getColumnDimension();
+ $X = $B->getArrayCopy();
+ // Compute Y = transpose(Q)*B
+ for ($k = 0; $k < $this->n; ++$k) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $s = 0.0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $s += $this->QR[$i][$k] * $X[$i][$j];
+ }
+ $s = -$s / $this->QR[$k][$k];
+ for ($i = $k; $i < $this->m; ++$i) {
+ $X[$i][$j] += $s * $this->QR[$i][$k];
+ }
+ }
+ }
+ // Solve R*X = Y;
+ for ($k = $this->n - 1; $k >= 0; --$k) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X[$k][$j] /= $this->Rdiag[$k];
+ }
+ for ($i = 0; $i < $k; ++$i) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X[$i][$j] -= $X[$k][$j] * $this->QR[$i][$k];
+ }
+ }
+ }
+ $X = new Matrix($X);
+
+ return $X->getMatrix(0, $this->n - 1, 0, $nx);
+ }
+
+ throw new CalculationException(self::MATRIX_RANK_EXCEPTION);
+ }
+
+ throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php
new file mode 100755
index 0000000..b997fb7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php
@@ -0,0 +1,528 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
+
+/**
+ * For an m-by-n matrix A with m >= n, the singular value decomposition is
+ * an m-by-n orthogonal matrix U, an n-by-n diagonal matrix S, and
+ * an n-by-n orthogonal matrix V so that A = U*S*V'.
+ *
+ * The singular values, sigma[$k] = S[$k][$k], are ordered so that
+ * sigma[0] >= sigma[1] >= ... >= sigma[n-1].
+ *
+ * The singular value decompostion always exists, so the constructor will
+ * never fail. The matrix condition number and the effective numerical
+ * rank can be computed from this decomposition.
+ *
+ * @author Paul Meagher
+ *
+ * @version 1.1
+ */
+class SingularValueDecomposition
+{
+ /**
+ * Internal storage of U.
+ *
+ * @var array
+ */
+ private $U = [];
+
+ /**
+ * Internal storage of V.
+ *
+ * @var array
+ */
+ private $V = [];
+
+ /**
+ * Internal storage of singular values.
+ *
+ * @var array
+ */
+ private $s = [];
+
+ /**
+ * Row dimension.
+ *
+ * @var int
+ */
+ private $m;
+
+ /**
+ * Column dimension.
+ *
+ * @var int
+ */
+ private $n;
+
+ /**
+ * Construct the singular value decomposition.
+ *
+ * Derived from LINPACK code.
+ *
+ * @param mixed $Arg Rectangular matrix
+ */
+ public function __construct($Arg)
+ {
+ // Initialize.
+ $A = $Arg->getArrayCopy();
+ $this->m = $Arg->getRowDimension();
+ $this->n = $Arg->getColumnDimension();
+ $nu = min($this->m, $this->n);
+ $e = [];
+ $work = [];
+ $wantu = true;
+ $wantv = true;
+ $nct = min($this->m - 1, $this->n);
+ $nrt = max(0, min($this->n - 2, $this->m));
+
+ // Reduce A to bidiagonal form, storing the diagonal elements
+ // in s and the super-diagonal elements in e.
+ $kMax = max($nct, $nrt);
+ for ($k = 0; $k < $kMax; ++$k) {
+ if ($k < $nct) {
+ // Compute the transformation for the k-th column and
+ // place the k-th diagonal in s[$k].
+ // Compute 2-norm of k-th column without under/overflow.
+ $this->s[$k] = 0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $this->s[$k] = hypo($this->s[$k], $A[$i][$k]);
+ }
+ if ($this->s[$k] != 0.0) {
+ if ($A[$k][$k] < 0.0) {
+ $this->s[$k] = -$this->s[$k];
+ }
+ for ($i = $k; $i < $this->m; ++$i) {
+ $A[$i][$k] /= $this->s[$k];
+ }
+ $A[$k][$k] += 1.0;
+ }
+ $this->s[$k] = -$this->s[$k];
+ }
+
+ for ($j = $k + 1; $j < $this->n; ++$j) {
+ if (($k < $nct) & ($this->s[$k] != 0.0)) {
+ // Apply the transformation.
+ $t = 0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $t += $A[$i][$k] * $A[$i][$j];
+ }
+ $t = -$t / $A[$k][$k];
+ for ($i = $k; $i < $this->m; ++$i) {
+ $A[$i][$j] += $t * $A[$i][$k];
+ }
+ // Place the k-th row of A into e for the
+ // subsequent calculation of the row transformation.
+ $e[$j] = $A[$k][$j];
+ }
+ }
+
+ if ($wantu && ($k < $nct)) {
+ // Place the transformation in U for subsequent back
+ // multiplication.
+ for ($i = $k; $i < $this->m; ++$i) {
+ $this->U[$i][$k] = $A[$i][$k];
+ }
+ }
+
+ if ($k < $nrt) {
+ // Compute the k-th row transformation and place the
+ // k-th super-diagonal in e[$k].
+ // Compute 2-norm without under/overflow.
+ $e[$k] = 0;
+ for ($i = $k + 1; $i < $this->n; ++$i) {
+ $e[$k] = hypo($e[$k], $e[$i]);
+ }
+ if ($e[$k] != 0.0) {
+ if ($e[$k + 1] < 0.0) {
+ $e[$k] = -$e[$k];
+ }
+ for ($i = $k + 1; $i < $this->n; ++$i) {
+ $e[$i] /= $e[$k];
+ }
+ $e[$k + 1] += 1.0;
+ }
+ $e[$k] = -$e[$k];
+ if (($k + 1 < $this->m) && ($e[$k] != 0.0)) {
+ // Apply the transformation.
+ for ($i = $k + 1; $i < $this->m; ++$i) {
+ $work[$i] = 0.0;
+ }
+ for ($j = $k + 1; $j < $this->n; ++$j) {
+ for ($i = $k + 1; $i < $this->m; ++$i) {
+ $work[$i] += $e[$j] * $A[$i][$j];
+ }
+ }
+ for ($j = $k + 1; $j < $this->n; ++$j) {
+ $t = -$e[$j] / $e[$k + 1];
+ for ($i = $k + 1; $i < $this->m; ++$i) {
+ $A[$i][$j] += $t * $work[$i];
+ }
+ }
+ }
+ if ($wantv) {
+ // Place the transformation in V for subsequent
+ // back multiplication.
+ for ($i = $k + 1; $i < $this->n; ++$i) {
+ $this->V[$i][$k] = $e[$i];
+ }
+ }
+ }
+ }
+
+ // Set up the final bidiagonal matrix or order p.
+ $p = min($this->n, $this->m + 1);
+ if ($nct < $this->n) {
+ $this->s[$nct] = $A[$nct][$nct];
+ }
+ if ($this->m < $p) {
+ $this->s[$p - 1] = 0.0;
+ }
+ if ($nrt + 1 < $p) {
+ $e[$nrt] = $A[$nrt][$p - 1];
+ }
+ $e[$p - 1] = 0.0;
+ // If required, generate U.
+ if ($wantu) {
+ for ($j = $nct; $j < $nu; ++$j) {
+ for ($i = 0; $i < $this->m; ++$i) {
+ $this->U[$i][$j] = 0.0;
+ }
+ $this->U[$j][$j] = 1.0;
+ }
+ for ($k = $nct - 1; $k >= 0; --$k) {
+ if ($this->s[$k] != 0.0) {
+ for ($j = $k + 1; $j < $nu; ++$j) {
+ $t = 0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $t += $this->U[$i][$k] * $this->U[$i][$j];
+ }
+ $t = -$t / $this->U[$k][$k];
+ for ($i = $k; $i < $this->m; ++$i) {
+ $this->U[$i][$j] += $t * $this->U[$i][$k];
+ }
+ }
+ for ($i = $k; $i < $this->m; ++$i) {
+ $this->U[$i][$k] = -$this->U[$i][$k];
+ }
+ $this->U[$k][$k] = 1.0 + $this->U[$k][$k];
+ for ($i = 0; $i < $k - 1; ++$i) {
+ $this->U[$i][$k] = 0.0;
+ }
+ } else {
+ for ($i = 0; $i < $this->m; ++$i) {
+ $this->U[$i][$k] = 0.0;
+ }
+ $this->U[$k][$k] = 1.0;
+ }
+ }
+ }
+
+ // If required, generate V.
+ if ($wantv) {
+ for ($k = $this->n - 1; $k >= 0; --$k) {
+ if (($k < $nrt) && ($e[$k] != 0.0)) {
+ for ($j = $k + 1; $j < $nu; ++$j) {
+ $t = 0;
+ for ($i = $k + 1; $i < $this->n; ++$i) {
+ $t += $this->V[$i][$k] * $this->V[$i][$j];
+ }
+ $t = -$t / $this->V[$k + 1][$k];
+ for ($i = $k + 1; $i < $this->n; ++$i) {
+ $this->V[$i][$j] += $t * $this->V[$i][$k];
+ }
+ }
+ }
+ for ($i = 0; $i < $this->n; ++$i) {
+ $this->V[$i][$k] = 0.0;
+ }
+ $this->V[$k][$k] = 1.0;
+ }
+ }
+
+ // Main iteration loop for the singular values.
+ $pp = $p - 1;
+ $iter = 0;
+ $eps = 2.0 ** (-52.0);
+
+ while ($p > 0) {
+ // Here is where a test for too many iterations would go.
+ // This section of the program inspects for negligible
+ // elements in the s and e arrays. On completion the
+ // variables kase and k are set as follows:
+ // kase = 1 if s(p) and e[k-1] are negligible and k<p
+ // kase = 2 if s(k) is negligible and k<p
+ // kase = 3 if e[k-1] is negligible, k<p, and
+ // s(k), ..., s(p) are not negligible (qr step).
+ // kase = 4 if e(p-1) is negligible (convergence).
+ for ($k = $p - 2; $k >= -1; --$k) {
+ if ($k == -1) {
+ break;
+ }
+ if (abs($e[$k]) <= $eps * (abs($this->s[$k]) + abs($this->s[$k + 1]))) {
+ $e[$k] = 0.0;
+
+ break;
+ }
+ }
+ if ($k == $p - 2) {
+ $kase = 4;
+ } else {
+ for ($ks = $p - 1; $ks >= $k; --$ks) {
+ if ($ks == $k) {
+ break;
+ }
+ $t = ($ks != $p ? abs($e[$ks]) : 0.) + ($ks != $k + 1 ? abs($e[$ks - 1]) : 0.);
+ if (abs($this->s[$ks]) <= $eps * $t) {
+ $this->s[$ks] = 0.0;
+
+ break;
+ }
+ }
+ if ($ks == $k) {
+ $kase = 3;
+ } elseif ($ks == $p - 1) {
+ $kase = 1;
+ } else {
+ $kase = 2;
+ $k = $ks;
+ }
+ }
+ ++$k;
+
+ // Perform the task indicated by kase.
+ switch ($kase) {
+ // Deflate negligible s(p).
+ case 1:
+ $f = $e[$p - 2];
+ $e[$p - 2] = 0.0;
+ for ($j = $p - 2; $j >= $k; --$j) {
+ $t = hypo($this->s[$j], $f);
+ $cs = $this->s[$j] / $t;
+ $sn = $f / $t;
+ $this->s[$j] = $t;
+ if ($j != $k) {
+ $f = -$sn * $e[$j - 1];
+ $e[$j - 1] = $cs * $e[$j - 1];
+ }
+ if ($wantv) {
+ for ($i = 0; $i < $this->n; ++$i) {
+ $t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$p - 1];
+ $this->V[$i][$p - 1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$p - 1];
+ $this->V[$i][$j] = $t;
+ }
+ }
+ }
+
+ break;
+ // Split at negligible s(k).
+ case 2:
+ $f = $e[$k - 1];
+ $e[$k - 1] = 0.0;
+ for ($j = $k; $j < $p; ++$j) {
+ $t = hypo($this->s[$j], $f);
+ $cs = $this->s[$j] / $t;
+ $sn = $f / $t;
+ $this->s[$j] = $t;
+ $f = -$sn * $e[$j];
+ $e[$j] = $cs * $e[$j];
+ if ($wantu) {
+ for ($i = 0; $i < $this->m; ++$i) {
+ $t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$k - 1];
+ $this->U[$i][$k - 1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$k - 1];
+ $this->U[$i][$j] = $t;
+ }
+ }
+ }
+
+ break;
+ // Perform one qr step.
+ case 3:
+ // Calculate the shift.
+ $scale = max(max(max(max(abs($this->s[$p - 1]), abs($this->s[$p - 2])), abs($e[$p - 2])), abs($this->s[$k])), abs($e[$k]));
+ $sp = $this->s[$p - 1] / $scale;
+ $spm1 = $this->s[$p - 2] / $scale;
+ $epm1 = $e[$p - 2] / $scale;
+ $sk = $this->s[$k] / $scale;
+ $ek = $e[$k] / $scale;
+ $b = (($spm1 + $sp) * ($spm1 - $sp) + $epm1 * $epm1) / 2.0;
+ $c = ($sp * $epm1) * ($sp * $epm1);
+ $shift = 0.0;
+ if (($b != 0.0) || ($c != 0.0)) {
+ $shift = sqrt($b * $b + $c);
+ if ($b < 0.0) {
+ $shift = -$shift;
+ }
+ $shift = $c / ($b + $shift);
+ }
+ $f = ($sk + $sp) * ($sk - $sp) + $shift;
+ $g = $sk * $ek;
+ // Chase zeros.
+ for ($j = $k; $j < $p - 1; ++$j) {
+ $t = hypo($f, $g);
+ $cs = $f / $t;
+ $sn = $g / $t;
+ if ($j != $k) {
+ $e[$j - 1] = $t;
+ }
+ $f = $cs * $this->s[$j] + $sn * $e[$j];
+ $e[$j] = $cs * $e[$j] - $sn * $this->s[$j];
+ $g = $sn * $this->s[$j + 1];
+ $this->s[$j + 1] = $cs * $this->s[$j + 1];
+ if ($wantv) {
+ for ($i = 0; $i < $this->n; ++$i) {
+ $t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$j + 1];
+ $this->V[$i][$j + 1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$j + 1];
+ $this->V[$i][$j] = $t;
+ }
+ }
+ $t = hypo($f, $g);
+ $cs = $f / $t;
+ $sn = $g / $t;
+ $this->s[$j] = $t;
+ $f = $cs * $e[$j] + $sn * $this->s[$j + 1];
+ $this->s[$j + 1] = -$sn * $e[$j] + $cs * $this->s[$j + 1];
+ $g = $sn * $e[$j + 1];
+ $e[$j + 1] = $cs * $e[$j + 1];
+ if ($wantu && ($j < $this->m - 1)) {
+ for ($i = 0; $i < $this->m; ++$i) {
+ $t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$j + 1];
+ $this->U[$i][$j + 1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$j + 1];
+ $this->U[$i][$j] = $t;
+ }
+ }
+ }
+ $e[$p - 2] = $f;
+ $iter = $iter + 1;
+
+ break;
+ // Convergence.
+ case 4:
+ // Make the singular values positive.
+ if ($this->s[$k] <= 0.0) {
+ $this->s[$k] = ($this->s[$k] < 0.0 ? -$this->s[$k] : 0.0);
+ if ($wantv) {
+ for ($i = 0; $i <= $pp; ++$i) {
+ $this->V[$i][$k] = -$this->V[$i][$k];
+ }
+ }
+ }
+ // Order the singular values.
+ while ($k < $pp) {
+ if ($this->s[$k] >= $this->s[$k + 1]) {
+ break;
+ }
+ $t = $this->s[$k];
+ $this->s[$k] = $this->s[$k + 1];
+ $this->s[$k + 1] = $t;
+ if ($wantv && ($k < $this->n - 1)) {
+ for ($i = 0; $i < $this->n; ++$i) {
+ $t = $this->V[$i][$k + 1];
+ $this->V[$i][$k + 1] = $this->V[$i][$k];
+ $this->V[$i][$k] = $t;
+ }
+ }
+ if ($wantu && ($k < $this->m - 1)) {
+ for ($i = 0; $i < $this->m; ++$i) {
+ $t = $this->U[$i][$k + 1];
+ $this->U[$i][$k + 1] = $this->U[$i][$k];
+ $this->U[$i][$k] = $t;
+ }
+ }
+ ++$k;
+ }
+ $iter = 0;
+ --$p;
+
+ break;
+ } // end switch
+ } // end while
+ }
+
+ /**
+ * Return the left singular vectors.
+ *
+ * @return Matrix U
+ */
+ public function getU()
+ {
+ return new Matrix($this->U, $this->m, min($this->m + 1, $this->n));
+ }
+
+ /**
+ * Return the right singular vectors.
+ *
+ * @return Matrix V
+ */
+ public function getV()
+ {
+ return new Matrix($this->V);
+ }
+
+ /**
+ * Return the one-dimensional array of singular values.
+ *
+ * @return array diagonal of S
+ */
+ public function getSingularValues()
+ {
+ return $this->s;
+ }
+
+ /**
+ * Return the diagonal matrix of singular values.
+ *
+ * @return Matrix S
+ */
+ public function getS()
+ {
+ for ($i = 0; $i < $this->n; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $S[$i][$j] = 0.0;
+ }
+ $S[$i][$i] = $this->s[$i];
+ }
+
+ return new Matrix($S);
+ }
+
+ /**
+ * Two norm.
+ *
+ * @return float max(S)
+ */
+ public function norm2()
+ {
+ return $this->s[0];
+ }
+
+ /**
+ * Two norm condition number.
+ *
+ * @return float max(S)/min(S)
+ */
+ public function cond()
+ {
+ return $this->s[0] / $this->s[min($this->m, $this->n) - 1];
+ }
+
+ /**
+ * Effective numerical matrix rank.
+ *
+ * @return int Number of nonnegligible singular values
+ */
+ public function rank()
+ {
+ $eps = 2.0 ** (-52.0);
+ $tol = max($this->m, $this->n) * $this->s[0] * $eps;
+ $r = 0;
+ $iMax = count($this->s);
+ for ($i = 0; $i < $iMax; ++$i) {
+ if ($this->s[$i] > $tol) {
+ ++$r;
+ }
+ }
+
+ return $r;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/utils/Maths.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/utils/Maths.php
new file mode 100755
index 0000000..68c3864
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/utils/Maths.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Pythagorean Theorem:.
+ *
+ * a = 3
+ * b = 4
+ * r = sqrt(square(a) + square(b))
+ * r = 5
+ *
+ * r = sqrt(a^2 + b^2) without under/overflow.
+ *
+ * @param mixed $a
+ * @param mixed $b
+ *
+ * @return float
+ */
+function hypo($a, $b)
+{
+ if (abs($a) > abs($b)) {
+ $r = $b / $a;
+ $r = abs($a) * sqrt(1 + $r * $r);
+ } elseif ($b != 0) {
+ $r = $a / $b;
+ $r = abs($b) * sqrt(1 + $r * $r);
+ } else {
+ $r = 0.0;
+ }
+
+ return $r;
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php
new file mode 100755
index 0000000..32b56e1
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php
@@ -0,0 +1,567 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared;
+
+// vim: set expandtab tabstop=4 shiftwidth=4:
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2002 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Xavier Noguer <xnoguer@php.net> |
+// | Based on OLE::Storage_Lite by Kawai, Takanori |
+// +----------------------------------------------------------------------+
+//
+
+use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root;
+
+/*
+ * Array for storing OLE instances that are accessed from
+ * OLE_ChainedBlockStream::stream_open().
+ *
+ * @var array
+ */
+$GLOBALS['_OLE_INSTANCES'] = [];
+
+/**
+ * OLE package base class.
+ *
+ * @author Xavier Noguer <xnoguer@php.net>
+ * @author Christian Schmidt <schmidt@php.net>
+ */
+class OLE
+{
+ const OLE_PPS_TYPE_ROOT = 5;
+ const OLE_PPS_TYPE_DIR = 1;
+ const OLE_PPS_TYPE_FILE = 2;
+ const OLE_DATA_SIZE_SMALL = 0x1000;
+ const OLE_LONG_INT_SIZE = 4;
+ const OLE_PPS_SIZE = 0x80;
+
+ /**
+ * The file handle for reading an OLE container.
+ *
+ * @var resource
+ */
+ public $_file_handle;
+
+ /**
+ * Array of PPS's found on the OLE container.
+ *
+ * @var array
+ */
+ public $_list = [];
+
+ /**
+ * Root directory of OLE container.
+ *
+ * @var Root
+ */
+ public $root;
+
+ /**
+ * Big Block Allocation Table.
+ *
+ * @var array (blockId => nextBlockId)
+ */
+ public $bbat;
+
+ /**
+ * Short Block Allocation Table.
+ *
+ * @var array (blockId => nextBlockId)
+ */
+ public $sbat;
+
+ /**
+ * Size of big blocks. This is usually 512.
+ *
+ * @var int number of octets per block
+ */
+ public $bigBlockSize;
+
+ /**
+ * Size of small blocks. This is usually 64.
+ *
+ * @var int number of octets per block
+ */
+ public $smallBlockSize;
+
+ /**
+ * Threshold for big blocks.
+ *
+ * @var int
+ */
+ public $bigBlockThreshold;
+
+ /**
+ * Reads an OLE container from the contents of the file given.
+ *
+ * @acces public
+ *
+ * @param string $file
+ *
+ * @return bool true on success, PEAR_Error on failure
+ */
+ public function read($file)
+ {
+ $fh = fopen($file, 'rb');
+ if (!$fh) {
+ throw new ReaderException("Can't open file $file");
+ }
+ $this->_file_handle = $fh;
+
+ $signature = fread($fh, 8);
+ if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) {
+ throw new ReaderException("File doesn't seem to be an OLE container.");
+ }
+ fseek($fh, 28);
+ if (fread($fh, 2) != "\xFE\xFF") {
+ // This shouldn't be a problem in practice
+ throw new ReaderException('Only Little-Endian encoding is supported.');
+ }
+ // Size of blocks and short blocks in bytes
+ $this->bigBlockSize = 2 ** self::_readInt2($fh);
+ $this->smallBlockSize = 2 ** self::_readInt2($fh);
+
+ // Skip UID, revision number and version number
+ fseek($fh, 44);
+ // Number of blocks in Big Block Allocation Table
+ $bbatBlockCount = self::_readInt4($fh);
+
+ // Root chain 1st block
+ $directoryFirstBlockId = self::_readInt4($fh);
+
+ // Skip unused bytes
+ fseek($fh, 56);
+ // Streams shorter than this are stored using small blocks
+ $this->bigBlockThreshold = self::_readInt4($fh);
+ // Block id of first sector in Short Block Allocation Table
+ $sbatFirstBlockId = self::_readInt4($fh);
+ // Number of blocks in Short Block Allocation Table
+ $sbbatBlockCount = self::_readInt4($fh);
+ // Block id of first sector in Master Block Allocation Table
+ $mbatFirstBlockId = self::_readInt4($fh);
+ // Number of blocks in Master Block Allocation Table
+ $mbbatBlockCount = self::_readInt4($fh);
+ $this->bbat = [];
+
+ // Remaining 4 * 109 bytes of current block is beginning of Master
+ // Block Allocation Table
+ $mbatBlocks = [];
+ for ($i = 0; $i < 109; ++$i) {
+ $mbatBlocks[] = self::_readInt4($fh);
+ }
+
+ // Read rest of Master Block Allocation Table (if any is left)
+ $pos = $this->_getBlockOffset($mbatFirstBlockId);
+ for ($i = 0; $i < $mbbatBlockCount; ++$i) {
+ fseek($fh, $pos);
+ for ($j = 0; $j < $this->bigBlockSize / 4 - 1; ++$j) {
+ $mbatBlocks[] = self::_readInt4($fh);
+ }
+ // Last block id in each block points to next block
+ $pos = $this->_getBlockOffset(self::_readInt4($fh));
+ }
+
+ // Read Big Block Allocation Table according to chain specified by $mbatBlocks
+ for ($i = 0; $i < $bbatBlockCount; ++$i) {
+ $pos = $this->_getBlockOffset($mbatBlocks[$i]);
+ fseek($fh, $pos);
+ for ($j = 0; $j < $this->bigBlockSize / 4; ++$j) {
+ $this->bbat[] = self::_readInt4($fh);
+ }
+ }
+
+ // Read short block allocation table (SBAT)
+ $this->sbat = [];
+ $shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4;
+ $sbatFh = $this->getStream($sbatFirstBlockId);
+ for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) {
+ $this->sbat[$blockId] = self::_readInt4($sbatFh);
+ }
+ fclose($sbatFh);
+
+ $this->_readPpsWks($directoryFirstBlockId);
+
+ return true;
+ }
+
+ /**
+ * @param int $blockId byte offset from beginning of file
+ *
+ * @return int
+ */
+ public function _getBlockOffset($blockId)
+ {
+ return 512 + $blockId * $this->bigBlockSize;
+ }
+
+ /**
+ * Returns a stream for use with fread() etc. External callers should
+ * use \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File::getStream().
+ *
+ * @param int|OLE\PPS $blockIdOrPps block id or PPS
+ *
+ * @return resource read-only stream
+ */
+ public function getStream($blockIdOrPps)
+ {
+ static $isRegistered = false;
+ if (!$isRegistered) {
+ stream_wrapper_register('ole-chainedblockstream', ChainedBlockStream::class);
+ $isRegistered = true;
+ }
+
+ // Store current instance in global array, so that it can be accessed
+ // in OLE_ChainedBlockStream::stream_open().
+ // Object is removed from self::$instances in OLE_Stream::close().
+ $GLOBALS['_OLE_INSTANCES'][] = $this;
+ $instanceId = end(array_keys($GLOBALS['_OLE_INSTANCES']));
+
+ $path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId;
+ if ($blockIdOrPps instanceof OLE\PPS) {
+ $path .= '&blockId=' . $blockIdOrPps->startBlock;
+ $path .= '&size=' . $blockIdOrPps->Size;
+ } else {
+ $path .= '&blockId=' . $blockIdOrPps;
+ }
+
+ return fopen($path, 'rb');
+ }
+
+ /**
+ * Reads a signed char.
+ *
+ * @param resource $fh file handle
+ *
+ * @return int
+ */
+ private static function _readInt1($fh)
+ {
+ [, $tmp] = unpack('c', fread($fh, 1));
+
+ return $tmp;
+ }
+
+ /**
+ * Reads an unsigned short (2 octets).
+ *
+ * @param resource $fh file handle
+ *
+ * @return int
+ */
+ private static function _readInt2($fh)
+ {
+ [, $tmp] = unpack('v', fread($fh, 2));
+
+ return $tmp;
+ }
+
+ /**
+ * Reads an unsigned long (4 octets).
+ *
+ * @param resource $fh file handle
+ *
+ * @return int
+ */
+ private static function _readInt4($fh)
+ {
+ [, $tmp] = unpack('V', fread($fh, 4));
+
+ return $tmp;
+ }
+
+ /**
+ * Gets information about all PPS's on the OLE container from the PPS WK's
+ * creates an OLE_PPS object for each one.
+ *
+ * @param int $blockId the block id of the first block
+ *
+ * @return bool true on success, PEAR_Error on failure
+ */
+ public function _readPpsWks($blockId)
+ {
+ $fh = $this->getStream($blockId);
+ for ($pos = 0; true; $pos += 128) {
+ fseek($fh, $pos, SEEK_SET);
+ $nameUtf16 = fread($fh, 64);
+ $nameLength = self::_readInt2($fh);
+ $nameUtf16 = substr($nameUtf16, 0, $nameLength - 2);
+ // Simple conversion from UTF-16LE to ISO-8859-1
+ $name = str_replace("\x00", '', $nameUtf16);
+ $type = self::_readInt1($fh);
+ switch ($type) {
+ case self::OLE_PPS_TYPE_ROOT:
+ $pps = new OLE\PPS\Root(null, null, []);
+ $this->root = $pps;
+
+ break;
+ case self::OLE_PPS_TYPE_DIR:
+ $pps = new OLE\PPS(null, null, null, null, null, null, null, null, null, []);
+
+ break;
+ case self::OLE_PPS_TYPE_FILE:
+ $pps = new OLE\PPS\File($name);
+
+ break;
+ default:
+ break;
+ }
+ fseek($fh, 1, SEEK_CUR);
+ $pps->Type = $type;
+ $pps->Name = $name;
+ $pps->PrevPps = self::_readInt4($fh);
+ $pps->NextPps = self::_readInt4($fh);
+ $pps->DirPps = self::_readInt4($fh);
+ fseek($fh, 20, SEEK_CUR);
+ $pps->Time1st = self::OLE2LocalDate(fread($fh, 8));
+ $pps->Time2nd = self::OLE2LocalDate(fread($fh, 8));
+ $pps->startBlock = self::_readInt4($fh);
+ $pps->Size = self::_readInt4($fh);
+ $pps->No = count($this->_list);
+ $this->_list[] = $pps;
+
+ // check if the PPS tree (starting from root) is complete
+ if (isset($this->root) && $this->_ppsTreeComplete($this->root->No)) {
+ break;
+ }
+ }
+ fclose($fh);
+
+ // Initialize $pps->children on directories
+ foreach ($this->_list as $pps) {
+ if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) {
+ $nos = [$pps->DirPps];
+ $pps->children = [];
+ while ($nos) {
+ $no = array_pop($nos);
+ if ($no != -1) {
+ $childPps = $this->_list[$no];
+ $nos[] = $childPps->PrevPps;
+ $nos[] = $childPps->NextPps;
+ $pps->children[] = $childPps;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * It checks whether the PPS tree is complete (all PPS's read)
+ * starting with the given PPS (not necessarily root).
+ *
+ * @param int $index The index of the PPS from which we are checking
+ *
+ * @return bool Whether the PPS tree for the given PPS is complete
+ */
+ public function _ppsTreeComplete($index)
+ {
+ return isset($this->_list[$index]) &&
+ ($pps = $this->_list[$index]) &&
+ ($pps->PrevPps == -1 ||
+ $this->_ppsTreeComplete($pps->PrevPps)) &&
+ ($pps->NextPps == -1 ||
+ $this->_ppsTreeComplete($pps->NextPps)) &&
+ ($pps->DirPps == -1 ||
+ $this->_ppsTreeComplete($pps->DirPps));
+ }
+
+ /**
+ * Checks whether a PPS is a File PPS or not.
+ * If there is no PPS for the index given, it will return false.
+ *
+ * @param int $index The index for the PPS
+ *
+ * @return bool true if it's a File PPS, false otherwise
+ */
+ public function isFile($index)
+ {
+ if (isset($this->_list[$index])) {
+ return $this->_list[$index]->Type == self::OLE_PPS_TYPE_FILE;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether a PPS is a Root PPS or not.
+ * If there is no PPS for the index given, it will return false.
+ *
+ * @param int $index the index for the PPS
+ *
+ * @return bool true if it's a Root PPS, false otherwise
+ */
+ public function isRoot($index)
+ {
+ if (isset($this->_list[$index])) {
+ return $this->_list[$index]->Type == self::OLE_PPS_TYPE_ROOT;
+ }
+
+ return false;
+ }
+
+ /**
+ * Gives the total number of PPS's found in the OLE container.
+ *
+ * @return int The total number of PPS's found in the OLE container
+ */
+ public function ppsTotal()
+ {
+ return count($this->_list);
+ }
+
+ /**
+ * Gets data from a PPS
+ * If there is no PPS for the index given, it will return an empty string.
+ *
+ * @param int $index The index for the PPS
+ * @param int $position The position from which to start reading
+ * (relative to the PPS)
+ * @param int $length The amount of bytes to read (at most)
+ *
+ * @return string The binary string containing the data requested
+ *
+ * @see OLE_PPS_File::getStream()
+ */
+ public function getData($index, $position, $length)
+ {
+ // if position is not valid return empty string
+ if (!isset($this->_list[$index]) || ($position >= $this->_list[$index]->Size) || ($position < 0)) {
+ return '';
+ }
+ $fh = $this->getStream($this->_list[$index]);
+ $data = stream_get_contents($fh, $length, $position);
+ fclose($fh);
+
+ return $data;
+ }
+
+ /**
+ * Gets the data length from a PPS
+ * If there is no PPS for the index given, it will return 0.
+ *
+ * @param int $index The index for the PPS
+ *
+ * @return int The amount of bytes in data the PPS has
+ */
+ public function getDataLength($index)
+ {
+ if (isset($this->_list[$index])) {
+ return $this->_list[$index]->Size;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Utility function to transform ASCII text to Unicode.
+ *
+ * @param string $ascii The ASCII string to transform
+ *
+ * @return string The string in Unicode
+ */
+ public static function ascToUcs($ascii)
+ {
+ $rawname = '';
+ $iMax = strlen($ascii);
+ for ($i = 0; $i < $iMax; ++$i) {
+ $rawname .= $ascii[$i]
+ . "\x00";
+ }
+
+ return $rawname;
+ }
+
+ /**
+ * Utility function
+ * Returns a string for the OLE container with the date given.
+ *
+ * @param int $date A timestamp
+ *
+ * @return string The string for the OLE container
+ */
+ public static function localDateToOLE($date)
+ {
+ if (!isset($date)) {
+ return "\x00\x00\x00\x00\x00\x00\x00\x00";
+ }
+
+ // factor used for separating numbers into 4 bytes parts
+ $factor = 2 ** 32;
+
+ // days from 1-1-1601 until the beggining of UNIX era
+ $days = 134774;
+ // calculate seconds
+ $big_date = $days * 24 * 3600 + gmmktime(date('H', $date), date('i', $date), date('s', $date), date('m', $date), date('d', $date), date('Y', $date));
+ // multiply just to make MS happy
+ $big_date *= 10000000;
+
+ $high_part = floor($big_date / $factor);
+ // lower 4 bytes
+ $low_part = floor((($big_date / $factor) - $high_part) * $factor);
+
+ // Make HEX string
+ $res = '';
+
+ for ($i = 0; $i < 4; ++$i) {
+ $hex = $low_part % 0x100;
+ $res .= pack('c', $hex);
+ $low_part /= 0x100;
+ }
+ for ($i = 0; $i < 4; ++$i) {
+ $hex = $high_part % 0x100;
+ $res .= pack('c', $hex);
+ $high_part /= 0x100;
+ }
+
+ return $res;
+ }
+
+ /**
+ * Returns a timestamp from an OLE container's date.
+ *
+ * @param string $oleTimestamp A binary string with the encoded date
+ *
+ * @return int The Unix timestamp corresponding to the string
+ */
+ public static function OLE2LocalDate($oleTimestamp)
+ {
+ if (strlen($oleTimestamp) != 8) {
+ throw new ReaderException('Expecting 8 byte string');
+ }
+
+ // convert to units of 100 ns since 1601:
+ $unpackedTimestamp = unpack('v4', $oleTimestamp);
+ $timestampHigh = (float) $unpackedTimestamp[4] * 65536 + (float) $unpackedTimestamp[3];
+ $timestampLow = (float) $unpackedTimestamp[2] * 65536 + (float) $unpackedTimestamp[1];
+
+ // translate to seconds since 1601:
+ $timestampHigh /= 10000000;
+ $timestampLow /= 10000000;
+
+ // days from 1601 to 1970:
+ $days = 134774;
+
+ // translate to seconds since 1970:
+ $unixTimestamp = floor(65536.0 * 65536.0 * $timestampHigh + $timestampLow - $days * 24 * 3600 + 0.5);
+
+ if ((int) $unixTimestamp == $unixTimestamp) {
+ return (int) $unixTimestamp;
+ }
+
+ return $unixTimestamp >= 0.0 ? PHP_INT_MAX : PHP_INT_MIN;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php
new file mode 100755
index 0000000..ecaa97e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php
@@ -0,0 +1,196 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\OLE;
+
+use PhpOffice\PhpSpreadsheet\Shared\OLE;
+
+class ChainedBlockStream
+{
+ /**
+ * The OLE container of the file that is being read.
+ *
+ * @var OLE
+ */
+ public $ole;
+
+ /**
+ * Parameters specified by fopen().
+ *
+ * @var array
+ */
+ public $params;
+
+ /**
+ * The binary data of the file.
+ *
+ * @var string
+ */
+ public $data;
+
+ /**
+ * The file pointer.
+ *
+ * @var int byte offset
+ */
+ public $pos;
+
+ /**
+ * Implements support for fopen().
+ * For creating streams using this wrapper, use OLE_PPS_File::getStream().
+ *
+ * @param string $path resource name including scheme, e.g.
+ * ole-chainedblockstream://oleInstanceId=1
+ * @param string $mode only "r" is supported
+ * @param int $options mask of STREAM_REPORT_ERRORS and STREAM_USE_PATH
+ * @param string &$openedPath absolute path of the opened stream (out parameter)
+ *
+ * @return bool true on success
+ */
+ public function stream_open($path, $mode, $options, &$openedPath) // @codingStandardsIgnoreLine
+ {
+ if ($mode != 'r') {
+ if ($options & STREAM_REPORT_ERRORS) {
+ trigger_error('Only reading is supported', E_USER_WARNING);
+ }
+
+ return false;
+ }
+
+ // 25 is length of "ole-chainedblockstream://"
+ parse_str(substr($path, 25), $this->params);
+ if (!isset($this->params['oleInstanceId'], $this->params['blockId'], $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']])) {
+ if ($options & STREAM_REPORT_ERRORS) {
+ trigger_error('OLE stream not found', E_USER_WARNING);
+ }
+
+ return false;
+ }
+ $this->ole = $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']];
+
+ $blockId = $this->params['blockId'];
+ $this->data = '';
+ if (isset($this->params['size']) && $this->params['size'] < $this->ole->bigBlockThreshold && $blockId != $this->ole->root->startBlock) {
+ // Block id refers to small blocks
+ $rootPos = $this->ole->_getBlockOffset($this->ole->root->startBlock);
+ while ($blockId != -2) {
+ $pos = $rootPos + $blockId * $this->ole->bigBlockSize;
+ $blockId = $this->ole->sbat[$blockId];
+ fseek($this->ole->_file_handle, $pos);
+ $this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize);
+ }
+ } else {
+ // Block id refers to big blocks
+ while ($blockId != -2) {
+ $pos = $this->ole->_getBlockOffset($blockId);
+ fseek($this->ole->_file_handle, $pos);
+ $this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize);
+ $blockId = $this->ole->bbat[$blockId];
+ }
+ }
+ if (isset($this->params['size'])) {
+ $this->data = substr($this->data, 0, $this->params['size']);
+ }
+
+ if ($options & STREAM_USE_PATH) {
+ $openedPath = $path;
+ }
+
+ return true;
+ }
+
+ /**
+ * Implements support for fclose().
+ */
+ public function stream_close(): void // @codingStandardsIgnoreLine
+ {
+ $this->ole = null;
+ unset($GLOBALS['_OLE_INSTANCES']);
+ }
+
+ /**
+ * Implements support for fread(), fgets() etc.
+ *
+ * @param int $count maximum number of bytes to read
+ *
+ * @return string
+ */
+ public function stream_read($count) // @codingStandardsIgnoreLine
+ {
+ if ($this->stream_eof()) {
+ return false;
+ }
+ $s = substr($this->data, $this->pos, $count);
+ $this->pos += $count;
+
+ return $s;
+ }
+
+ /**
+ * Implements support for feof().
+ *
+ * @return bool TRUE if the file pointer is at EOF; otherwise FALSE
+ */
+ public function stream_eof() // @codingStandardsIgnoreLine
+ {
+ return $this->pos >= strlen($this->data);
+ }
+
+ /**
+ * Returns the position of the file pointer, i.e. its offset into the file
+ * stream. Implements support for ftell().
+ *
+ * @return int
+ */
+ public function stream_tell() // @codingStandardsIgnoreLine
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Implements support for fseek().
+ *
+ * @param int $offset byte offset
+ * @param int $whence SEEK_SET, SEEK_CUR or SEEK_END
+ *
+ * @return bool
+ */
+ public function stream_seek($offset, $whence) // @codingStandardsIgnoreLine
+ {
+ if ($whence == SEEK_SET && $offset >= 0) {
+ $this->pos = $offset;
+ } elseif ($whence == SEEK_CUR && -$offset <= $this->pos) {
+ $this->pos += $offset;
+ } elseif ($whence == SEEK_END && -$offset <= count($this->data)) {
+ $this->pos = strlen($this->data) + $offset;
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Implements support for fstat(). Currently the only supported field is
+ * "size".
+ *
+ * @return array
+ */
+ public function stream_stat() // @codingStandardsIgnoreLine
+ {
+ return [
+ 'size' => strlen($this->data),
+ ];
+ }
+
+ // Methods used by stream_wrapper_register() that are not implemented:
+ // bool stream_flush ( void )
+ // int stream_write ( string data )
+ // bool rename ( string path_from, string path_to )
+ // bool mkdir ( string path, int mode, int options )
+ // bool rmdir ( string path, int options )
+ // bool dir_opendir ( string path, int options )
+ // array url_stat ( string path, int flags )
+ // string dir_readdir ( void )
+ // bool dir_rewinddir ( void )
+ // bool dir_closedir ( void )
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php
new file mode 100755
index 0000000..0407e0d
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php
@@ -0,0 +1,236 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\OLE;
+
+// vim: set expandtab tabstop=4 shiftwidth=4:
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2002 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Xavier Noguer <xnoguer@php.net> |
+// | Based on OLE::Storage_Lite by Kawai, Takanori |
+// +----------------------------------------------------------------------+
+//
+use PhpOffice\PhpSpreadsheet\Shared\OLE;
+
+/**
+ * Class for creating PPS's for OLE containers.
+ *
+ * @author Xavier Noguer <xnoguer@php.net>
+ */
+class PPS
+{
+ /**
+ * The PPS index.
+ *
+ * @var int
+ */
+ public $No;
+
+ /**
+ * The PPS name (in Unicode).
+ *
+ * @var string
+ */
+ public $Name;
+
+ /**
+ * The PPS type. Dir, Root or File.
+ *
+ * @var int
+ */
+ public $Type;
+
+ /**
+ * The index of the previous PPS.
+ *
+ * @var int
+ */
+ public $PrevPps;
+
+ /**
+ * The index of the next PPS.
+ *
+ * @var int
+ */
+ public $NextPps;
+
+ /**
+ * The index of it's first child if this is a Dir or Root PPS.
+ *
+ * @var int
+ */
+ public $DirPps;
+
+ /**
+ * A timestamp.
+ *
+ * @var int
+ */
+ public $Time1st;
+
+ /**
+ * A timestamp.
+ *
+ * @var int
+ */
+ public $Time2nd;
+
+ /**
+ * Starting block (small or big) for this PPS's data inside the container.
+ *
+ * @var int
+ */
+ public $startBlock;
+
+ /**
+ * The size of the PPS's data (in bytes).
+ *
+ * @var int
+ */
+ public $Size;
+
+ /**
+ * The PPS's data (only used if it's not using a temporary file).
+ *
+ * @var string
+ */
+ public $_data;
+
+ /**
+ * Array of child PPS's (only used by Root and Dir PPS's).
+ *
+ * @var array
+ */
+ public $children = [];
+
+ /**
+ * Pointer to OLE container.
+ *
+ * @var OLE
+ */
+ public $ole;
+
+ /**
+ * The constructor.
+ *
+ * @param int $No The PPS index
+ * @param string $name The PPS name
+ * @param int $type The PPS type. Dir, Root or File
+ * @param int $prev The index of the previous PPS
+ * @param int $next The index of the next PPS
+ * @param int $dir The index of it's first child if this is a Dir or Root PPS
+ * @param int $time_1st A timestamp
+ * @param int $time_2nd A timestamp
+ * @param string $data The (usually binary) source data of the PPS
+ * @param array $children Array containing children PPS for this PPS
+ */
+ public function __construct($No, $name, $type, $prev, $next, $dir, $time_1st, $time_2nd, $data, $children)
+ {
+ $this->No = $No;
+ $this->Name = $name;
+ $this->Type = $type;
+ $this->PrevPps = $prev;
+ $this->NextPps = $next;
+ $this->DirPps = $dir;
+ $this->Time1st = $time_1st;
+ $this->Time2nd = $time_2nd;
+ $this->_data = $data;
+ $this->children = $children;
+ if ($data != '') {
+ $this->Size = strlen($data);
+ } else {
+ $this->Size = 0;
+ }
+ }
+
+ /**
+ * Returns the amount of data saved for this PPS.
+ *
+ * @return int The amount of data (in bytes)
+ */
+ public function getDataLen()
+ {
+ if (!isset($this->_data)) {
+ return 0;
+ }
+
+ return strlen($this->_data);
+ }
+
+ /**
+ * Returns a string with the PPS's WK (What is a WK?).
+ *
+ * @return string The binary string
+ */
+ public function _getPpsWk()
+ {
+ $ret = str_pad($this->Name, 64, "\x00");
+
+ $ret .= pack('v', strlen($this->Name) + 2) // 66
+ . pack('c', $this->Type) // 67
+ . pack('c', 0x00) //UK // 68
+ . pack('V', $this->PrevPps) //Prev // 72
+ . pack('V', $this->NextPps) //Next // 76
+ . pack('V', $this->DirPps) //Dir // 80
+ . "\x00\x09\x02\x00" // 84
+ . "\x00\x00\x00\x00" // 88
+ . "\xc0\x00\x00\x00" // 92
+ . "\x00\x00\x00\x46" // 96 // Seems to be ok only for Root
+ . "\x00\x00\x00\x00" // 100
+ . OLE::localDateToOLE($this->Time1st) // 108
+ . OLE::localDateToOLE($this->Time2nd) // 116
+ . pack('V', isset($this->startBlock) ? $this->startBlock : 0) // 120
+ . pack('V', $this->Size) // 124
+ . pack('V', 0); // 128
+ return $ret;
+ }
+
+ /**
+ * Updates index and pointers to previous, next and children PPS's for this
+ * PPS. I don't think it'll work with Dir PPS's.
+ *
+ * @param array &$raList Reference to the array of PPS's for the whole OLE
+ * container
+ * @param mixed $to_save
+ * @param mixed $depth
+ *
+ * @return int The index for this PPS
+ */
+ public static function _savePpsSetPnt(&$raList, $to_save, $depth = 0)
+ {
+ if (!is_array($to_save) || (empty($to_save))) {
+ return 0xFFFFFFFF;
+ } elseif (count($to_save) == 1) {
+ $cnt = count($raList);
+ // If the first entry, it's the root... Don't clone it!
+ $raList[$cnt] = ($depth == 0) ? $to_save[0] : clone $to_save[0];
+ $raList[$cnt]->No = $cnt;
+ $raList[$cnt]->PrevPps = 0xFFFFFFFF;
+ $raList[$cnt]->NextPps = 0xFFFFFFFF;
+ $raList[$cnt]->DirPps = self::_savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++);
+ } else {
+ $iPos = floor(count($to_save) / 2);
+ $aPrev = array_slice($to_save, 0, $iPos);
+ $aNext = array_slice($to_save, $iPos + 1);
+ $cnt = count($raList);
+ // If the first entry, it's the root... Don't clone it!
+ $raList[$cnt] = ($depth == 0) ? $to_save[$iPos] : clone $to_save[$iPos];
+ $raList[$cnt]->No = $cnt;
+ $raList[$cnt]->PrevPps = self::_savePpsSetPnt($raList, $aPrev, $depth++);
+ $raList[$cnt]->NextPps = self::_savePpsSetPnt($raList, $aNext, $depth++);
+ $raList[$cnt]->DirPps = self::_savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++);
+ }
+
+ return $cnt;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php
new file mode 100755
index 0000000..dd1cda2
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
+
+// vim: set expandtab tabstop=4 shiftwidth=4:
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2002 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Xavier Noguer <xnoguer@php.net> |
+// | Based on OLE::Storage_Lite by Kawai, Takanori |
+// +----------------------------------------------------------------------+
+//
+use PhpOffice\PhpSpreadsheet\Shared\OLE;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
+
+/**
+ * Class for creating File PPS's for OLE containers.
+ *
+ * @author Xavier Noguer <xnoguer@php.net>
+ */
+class File extends PPS
+{
+ /**
+ * The constructor.
+ *
+ * @param string $name The name of the file (in Unicode)
+ *
+ * @see OLE::ascToUcs()
+ */
+ public function __construct($name)
+ {
+ parent::__construct(null, $name, OLE::OLE_PPS_TYPE_FILE, null, null, null, null, null, '', []);
+ }
+
+ /**
+ * Initialization method. Has to be called right after OLE_PPS_File().
+ *
+ * @return mixed true on success
+ */
+ public function init()
+ {
+ return true;
+ }
+
+ /**
+ * Append data to PPS.
+ *
+ * @param string $data The data to append
+ */
+ public function append($data): void
+ {
+ $this->_data .= $data;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php
new file mode 100755
index 0000000..11655c6
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php
@@ -0,0 +1,426 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
+
+// vim: set expandtab tabstop=4 shiftwidth=4:
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2002 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Xavier Noguer <xnoguer@php.net> |
+// | Based on OLE::Storage_Lite by Kawai, Takanori |
+// +----------------------------------------------------------------------+
+//
+use PhpOffice\PhpSpreadsheet\Shared\OLE;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
+
+/**
+ * Class for creating Root PPS's for OLE containers.
+ *
+ * @author Xavier Noguer <xnoguer@php.net>
+ */
+class Root extends PPS
+{
+ /**
+ * @var resource
+ */
+ private $fileHandle;
+
+ /**
+ * @var int
+ */
+ private $smallBlockSize;
+
+ /**
+ * @var int
+ */
+ private $bigBlockSize;
+
+ /**
+ * @param int $time_1st A timestamp
+ * @param int $time_2nd A timestamp
+ * @param File[] $raChild
+ */
+ public function __construct($time_1st, $time_2nd, $raChild)
+ {
+ parent::__construct(null, OLE::ascToUcs('Root Entry'), OLE::OLE_PPS_TYPE_ROOT, null, null, null, $time_1st, $time_2nd, null, $raChild);
+ }
+
+ /**
+ * Method for saving the whole OLE container (including files).
+ * In fact, if called with an empty argument (or '-'), it saves to a
+ * temporary file and then outputs it's contents to stdout.
+ * If a resource pointer to a stream created by fopen() is passed
+ * it will be used, but you have to close such stream by yourself.
+ *
+ * @param resource $fileHandle the name of the file or stream where to save the OLE container
+ *
+ * @return bool true on success
+ */
+ public function save($fileHandle)
+ {
+ $this->fileHandle = $fileHandle;
+
+ // Initial Setting for saving
+ $this->bigBlockSize = 2 ** (
+ (isset($this->bigBlockSize)) ? self::adjust2($this->bigBlockSize) : 9
+ );
+ $this->smallBlockSize = 2 ** (
+ (isset($this->smallBlockSize)) ? self::adjust2($this->smallBlockSize) : 6
+ );
+
+ // Make an array of PPS's (for Save)
+ $aList = [];
+ PPS::_savePpsSetPnt($aList, [$this]);
+ // calculate values for header
+ [$iSBDcnt, $iBBcnt, $iPPScnt] = $this->_calcSize($aList); //, $rhInfo);
+ // Save Header
+ $this->_saveHeader($iSBDcnt, $iBBcnt, $iPPScnt);
+
+ // Make Small Data string (write SBD)
+ $this->_data = $this->_makeSmallData($aList);
+
+ // Write BB
+ $this->_saveBigData($iSBDcnt, $aList);
+ // Write PPS
+ $this->_savePps($aList);
+ // Write Big Block Depot and BDList and Adding Header informations
+ $this->_saveBbd($iSBDcnt, $iBBcnt, $iPPScnt);
+
+ return true;
+ }
+
+ /**
+ * Calculate some numbers.
+ *
+ * @param array $raList Reference to an array of PPS's
+ *
+ * @return float[] The array of numbers
+ */
+ public function _calcSize(&$raList)
+ {
+ // Calculate Basic Setting
+ [$iSBDcnt, $iBBcnt, $iPPScnt] = [0, 0, 0];
+ $iSmallLen = 0;
+ $iSBcnt = 0;
+ $iCount = count($raList);
+ for ($i = 0; $i < $iCount; ++$i) {
+ if ($raList[$i]->Type == OLE::OLE_PPS_TYPE_FILE) {
+ $raList[$i]->Size = $raList[$i]->getDataLen();
+ if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) {
+ $iSBcnt += floor($raList[$i]->Size / $this->smallBlockSize)
+ + (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0);
+ } else {
+ $iBBcnt += (floor($raList[$i]->Size / $this->bigBlockSize) +
+ (($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0));
+ }
+ }
+ }
+ $iSmallLen = $iSBcnt * $this->smallBlockSize;
+ $iSlCnt = floor($this->bigBlockSize / OLE::OLE_LONG_INT_SIZE);
+ $iSBDcnt = floor($iSBcnt / $iSlCnt) + (($iSBcnt % $iSlCnt) ? 1 : 0);
+ $iBBcnt += (floor($iSmallLen / $this->bigBlockSize) +
+ (($iSmallLen % $this->bigBlockSize) ? 1 : 0));
+ $iCnt = count($raList);
+ $iBdCnt = $this->bigBlockSize / OLE::OLE_PPS_SIZE;
+ $iPPScnt = (floor($iCnt / $iBdCnt) + (($iCnt % $iBdCnt) ? 1 : 0));
+
+ return [$iSBDcnt, $iBBcnt, $iPPScnt];
+ }
+
+ /**
+ * Helper function for caculating a magic value for block sizes.
+ *
+ * @param int $i2 The argument
+ *
+ * @return float
+ *
+ * @see save()
+ */
+ private static function adjust2($i2)
+ {
+ $iWk = log($i2) / log(2);
+
+ return ($iWk > floor($iWk)) ? floor($iWk) + 1 : $iWk;
+ }
+
+ /**
+ * Save OLE header.
+ *
+ * @param int $iSBDcnt
+ * @param int $iBBcnt
+ * @param int $iPPScnt
+ */
+ public function _saveHeader($iSBDcnt, $iBBcnt, $iPPScnt): void
+ {
+ $FILE = $this->fileHandle;
+
+ // Calculate Basic Setting
+ $iBlCnt = $this->bigBlockSize / OLE::OLE_LONG_INT_SIZE;
+ $i1stBdL = ($this->bigBlockSize - 0x4C) / OLE::OLE_LONG_INT_SIZE;
+
+ $iBdExL = 0;
+ $iAll = $iBBcnt + $iPPScnt + $iSBDcnt;
+ $iAllW = $iAll;
+ $iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt) ? 1 : 0);
+ $iBdCnt = floor(($iAll + $iBdCntW) / $iBlCnt) + ((($iAllW + $iBdCntW) % $iBlCnt) ? 1 : 0);
+
+ // Calculate BD count
+ if ($iBdCnt > $i1stBdL) {
+ while (1) {
+ ++$iBdExL;
+ ++$iAllW;
+ $iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt) ? 1 : 0);
+ $iBdCnt = floor(($iAllW + $iBdCntW) / $iBlCnt) + ((($iAllW + $iBdCntW) % $iBlCnt) ? 1 : 0);
+ if ($iBdCnt <= ($iBdExL * $iBlCnt + $i1stBdL)) {
+ break;
+ }
+ }
+ }
+
+ // Save Header
+ fwrite(
+ $FILE,
+ "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"
+ . "\x00\x00\x00\x00"
+ . "\x00\x00\x00\x00"
+ . "\x00\x00\x00\x00"
+ . "\x00\x00\x00\x00"
+ . pack('v', 0x3b)
+ . pack('v', 0x03)
+ . pack('v', -2)
+ . pack('v', 9)
+ . pack('v', 6)
+ . pack('v', 0)
+ . "\x00\x00\x00\x00"
+ . "\x00\x00\x00\x00"
+ . pack('V', $iBdCnt)
+ . pack('V', $iBBcnt + $iSBDcnt) //ROOT START
+ . pack('V', 0)
+ . pack('V', 0x1000)
+ . pack('V', $iSBDcnt ? 0 : -2) //Small Block Depot
+ . pack('V', $iSBDcnt)
+ );
+ // Extra BDList Start, Count
+ if ($iBdCnt < $i1stBdL) {
+ fwrite(
+ $FILE,
+ pack('V', -2) // Extra BDList Start
+ . pack('V', 0)// Extra BDList Count
+ );
+ } else {
+ fwrite($FILE, pack('V', $iAll + $iBdCnt) . pack('V', $iBdExL));
+ }
+
+ // BDList
+ for ($i = 0; $i < $i1stBdL && $i < $iBdCnt; ++$i) {
+ fwrite($FILE, pack('V', $iAll + $i));
+ }
+ if ($i < $i1stBdL) {
+ $jB = $i1stBdL - $i;
+ for ($j = 0; $j < $jB; ++$j) {
+ fwrite($FILE, (pack('V', -1)));
+ }
+ }
+ }
+
+ /**
+ * Saving big data (PPS's with data bigger than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL).
+ *
+ * @param int $iStBlk
+ * @param array &$raList Reference to array of PPS's
+ */
+ public function _saveBigData($iStBlk, &$raList): void
+ {
+ $FILE = $this->fileHandle;
+
+ // cycle through PPS's
+ $iCount = count($raList);
+ for ($i = 0; $i < $iCount; ++$i) {
+ if ($raList[$i]->Type != OLE::OLE_PPS_TYPE_DIR) {
+ $raList[$i]->Size = $raList[$i]->getDataLen();
+ if (($raList[$i]->Size >= OLE::OLE_DATA_SIZE_SMALL) || (($raList[$i]->Type == OLE::OLE_PPS_TYPE_ROOT) && isset($raList[$i]->_data))) {
+ fwrite($FILE, $raList[$i]->_data);
+
+ if ($raList[$i]->Size % $this->bigBlockSize) {
+ fwrite($FILE, str_repeat("\x00", $this->bigBlockSize - ($raList[$i]->Size % $this->bigBlockSize)));
+ }
+ // Set For PPS
+ $raList[$i]->startBlock = $iStBlk;
+ $iStBlk +=
+ (floor($raList[$i]->Size / $this->bigBlockSize) +
+ (($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0));
+ }
+ }
+ }
+ }
+
+ /**
+ * get small data (PPS's with data smaller than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL).
+ *
+ * @param array &$raList Reference to array of PPS's
+ *
+ * @return string
+ */
+ public function _makeSmallData(&$raList)
+ {
+ $sRes = '';
+ $FILE = $this->fileHandle;
+ $iSmBlk = 0;
+
+ $iCount = count($raList);
+ for ($i = 0; $i < $iCount; ++$i) {
+ // Make SBD, small data string
+ if ($raList[$i]->Type == OLE::OLE_PPS_TYPE_FILE) {
+ if ($raList[$i]->Size <= 0) {
+ continue;
+ }
+ if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) {
+ $iSmbCnt = floor($raList[$i]->Size / $this->smallBlockSize)
+ + (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0);
+ // Add to SBD
+ $jB = $iSmbCnt - 1;
+ for ($j = 0; $j < $jB; ++$j) {
+ fwrite($FILE, pack('V', $j + $iSmBlk + 1));
+ }
+ fwrite($FILE, pack('V', -2));
+
+ // Add to Data String(this will be written for RootEntry)
+ $sRes .= $raList[$i]->_data;
+ if ($raList[$i]->Size % $this->smallBlockSize) {
+ $sRes .= str_repeat("\x00", $this->smallBlockSize - ($raList[$i]->Size % $this->smallBlockSize));
+ }
+ // Set for PPS
+ $raList[$i]->startBlock = $iSmBlk;
+ $iSmBlk += $iSmbCnt;
+ }
+ }
+ }
+ $iSbCnt = floor($this->bigBlockSize / OLE::OLE_LONG_INT_SIZE);
+ if ($iSmBlk % $iSbCnt) {
+ $iB = $iSbCnt - ($iSmBlk % $iSbCnt);
+ for ($i = 0; $i < $iB; ++$i) {
+ fwrite($FILE, pack('V', -1));
+ }
+ }
+
+ return $sRes;
+ }
+
+ /**
+ * Saves all the PPS's WKs.
+ *
+ * @param array $raList Reference to an array with all PPS's
+ */
+ public function _savePps(&$raList): void
+ {
+ // Save each PPS WK
+ $iC = count($raList);
+ for ($i = 0; $i < $iC; ++$i) {
+ fwrite($this->fileHandle, $raList[$i]->_getPpsWk());
+ }
+ // Adjust for Block
+ $iCnt = count($raList);
+ $iBCnt = $this->bigBlockSize / OLE::OLE_PPS_SIZE;
+ if ($iCnt % $iBCnt) {
+ fwrite($this->fileHandle, str_repeat("\x00", ($iBCnt - ($iCnt % $iBCnt)) * OLE::OLE_PPS_SIZE));
+ }
+ }
+
+ /**
+ * Saving Big Block Depot.
+ *
+ * @param int $iSbdSize
+ * @param int $iBsize
+ * @param int $iPpsCnt
+ */
+ public function _saveBbd($iSbdSize, $iBsize, $iPpsCnt): void
+ {
+ $FILE = $this->fileHandle;
+ // Calculate Basic Setting
+ $iBbCnt = $this->bigBlockSize / OLE::OLE_LONG_INT_SIZE;
+ $i1stBdL = ($this->bigBlockSize - 0x4C) / OLE::OLE_LONG_INT_SIZE;
+
+ $iBdExL = 0;
+ $iAll = $iBsize + $iPpsCnt + $iSbdSize;
+ $iAllW = $iAll;
+ $iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt) ? 1 : 0);
+ $iBdCnt = floor(($iAll + $iBdCntW) / $iBbCnt) + ((($iAllW + $iBdCntW) % $iBbCnt) ? 1 : 0);
+ // Calculate BD count
+ if ($iBdCnt > $i1stBdL) {
+ while (1) {
+ ++$iBdExL;
+ ++$iAllW;
+ $iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt) ? 1 : 0);
+ $iBdCnt = floor(($iAllW + $iBdCntW) / $iBbCnt) + ((($iAllW + $iBdCntW) % $iBbCnt) ? 1 : 0);
+ if ($iBdCnt <= ($iBdExL * $iBbCnt + $i1stBdL)) {
+ break;
+ }
+ }
+ }
+
+ // Making BD
+ // Set for SBD
+ if ($iSbdSize > 0) {
+ for ($i = 0; $i < ($iSbdSize - 1); ++$i) {
+ fwrite($FILE, pack('V', $i + 1));
+ }
+ fwrite($FILE, pack('V', -2));
+ }
+ // Set for B
+ for ($i = 0; $i < ($iBsize - 1); ++$i) {
+ fwrite($FILE, pack('V', $i + $iSbdSize + 1));
+ }
+ fwrite($FILE, pack('V', -2));
+
+ // Set for PPS
+ for ($i = 0; $i < ($iPpsCnt - 1); ++$i) {
+ fwrite($FILE, pack('V', $i + $iSbdSize + $iBsize + 1));
+ }
+ fwrite($FILE, pack('V', -2));
+ // Set for BBD itself ( 0xFFFFFFFD : BBD)
+ for ($i = 0; $i < $iBdCnt; ++$i) {
+ fwrite($FILE, pack('V', 0xFFFFFFFD));
+ }
+ // Set for ExtraBDList
+ for ($i = 0; $i < $iBdExL; ++$i) {
+ fwrite($FILE, pack('V', 0xFFFFFFFC));
+ }
+ // Adjust for Block
+ if (($iAllW + $iBdCnt) % $iBbCnt) {
+ $iBlock = ($iBbCnt - (($iAllW + $iBdCnt) % $iBbCnt));
+ for ($i = 0; $i < $iBlock; ++$i) {
+ fwrite($FILE, pack('V', -1));
+ }
+ }
+ // Extra BDList
+ if ($iBdCnt > $i1stBdL) {
+ $iN = 0;
+ $iNb = 0;
+ for ($i = $i1stBdL; $i < $iBdCnt; $i++, ++$iN) {
+ if ($iN >= ($iBbCnt - 1)) {
+ $iN = 0;
+ ++$iNb;
+ fwrite($FILE, pack('V', $iAll + $iBdCnt + $iNb));
+ }
+ fwrite($FILE, pack('V', $iBsize + $iSbdSize + $iPpsCnt + $i));
+ }
+ if (($iBdCnt - $i1stBdL) % ($iBbCnt - 1)) {
+ $iB = ($iBbCnt - 1) - (($iBdCnt - $i1stBdL) % ($iBbCnt - 1));
+ for ($i = 0; $i < $iB; ++$i) {
+ fwrite($FILE, pack('V', -1));
+ }
+ }
+ fwrite($FILE, pack('V', -2));
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php
new file mode 100755
index 0000000..b6d5422
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php
@@ -0,0 +1,350 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared;
+
+use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
+
+class OLERead
+{
+ private $data = '';
+
+ // Size of a sector = 512 bytes
+ const BIG_BLOCK_SIZE = 0x200;
+
+ // Size of a short sector = 64 bytes
+ const SMALL_BLOCK_SIZE = 0x40;
+
+ // Size of a directory entry always = 128 bytes
+ const PROPERTY_STORAGE_BLOCK_SIZE = 0x80;
+
+ // Minimum size of a standard stream = 4096 bytes, streams smaller than this are stored as short streams
+ const SMALL_BLOCK_THRESHOLD = 0x1000;
+
+ // header offsets
+ const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS = 0x2c;
+ const ROOT_START_BLOCK_POS = 0x30;
+ const SMALL_BLOCK_DEPOT_BLOCK_POS = 0x3c;
+ const EXTENSION_BLOCK_POS = 0x44;
+ const NUM_EXTENSION_BLOCK_POS = 0x48;
+ const BIG_BLOCK_DEPOT_BLOCKS_POS = 0x4c;
+
+ // property storage offsets (directory offsets)
+ const SIZE_OF_NAME_POS = 0x40;
+ const TYPE_POS = 0x42;
+ const START_BLOCK_POS = 0x74;
+ const SIZE_POS = 0x78;
+
+ public $wrkbook;
+
+ public $summaryInformation;
+
+ public $documentSummaryInformation;
+
+ /**
+ * @var int
+ */
+ private $numBigBlockDepotBlocks;
+
+ /**
+ * @var int
+ */
+ private $rootStartBlock;
+
+ /**
+ * @var int
+ */
+ private $sbdStartBlock;
+
+ /**
+ * @var int
+ */
+ private $extensionBlock;
+
+ /**
+ * @var int
+ */
+ private $numExtensionBlocks;
+
+ /**
+ * @var string
+ */
+ private $bigBlockChain;
+
+ /**
+ * @var string
+ */
+ private $smallBlockChain;
+
+ /**
+ * @var string
+ */
+ private $entry;
+
+ /**
+ * @var int
+ */
+ private $rootentry;
+
+ /**
+ * @var array
+ */
+ private $props = [];
+
+ /**
+ * Read the file.
+ *
+ * @param $pFilename string Filename
+ */
+ public function read($pFilename): void
+ {
+ File::assertFile($pFilename);
+
+ // Get the file identifier
+ // Don't bother reading the whole file until we know it's a valid OLE file
+ $this->data = file_get_contents($pFilename, false, null, 0, 8);
+
+ // Check OLE identifier
+ $identifierOle = pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1);
+ if ($this->data != $identifierOle) {
+ throw new ReaderException('The filename ' . $pFilename . ' is not recognised as an OLE file');
+ }
+
+ // Get the file data
+ $this->data = file_get_contents($pFilename);
+
+ // Total number of sectors used for the SAT
+ $this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);
+
+ // SecID of the first sector of the directory stream
+ $this->rootStartBlock = self::getInt4d($this->data, self::ROOT_START_BLOCK_POS);
+
+ // SecID of the first sector of the SSAT (or -2 if not extant)
+ $this->sbdStartBlock = self::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS);
+
+ // SecID of the first sector of the MSAT (or -2 if no additional sectors are used)
+ $this->extensionBlock = self::getInt4d($this->data, self::EXTENSION_BLOCK_POS);
+
+ // Total number of sectors used by MSAT
+ $this->numExtensionBlocks = self::getInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS);
+
+ $bigBlockDepotBlocks = [];
+ $pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS;
+
+ $bbdBlocks = $this->numBigBlockDepotBlocks;
+
+ if ($this->numExtensionBlocks != 0) {
+ $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS) / 4;
+ }
+
+ for ($i = 0; $i < $bbdBlocks; ++$i) {
+ $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
+ $pos += 4;
+ }
+
+ for ($j = 0; $j < $this->numExtensionBlocks; ++$j) {
+ $pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE;
+ $blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1);
+
+ for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) {
+ $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
+ $pos += 4;
+ }
+
+ $bbdBlocks += $blocksToRead;
+ if ($bbdBlocks < $this->numBigBlockDepotBlocks) {
+ $this->extensionBlock = self::getInt4d($this->data, $pos);
+ }
+ }
+
+ $pos = 0;
+ $this->bigBlockChain = '';
+ $bbs = self::BIG_BLOCK_SIZE / 4;
+ for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) {
+ $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE;
+
+ $this->bigBlockChain .= substr($this->data, $pos, 4 * $bbs);
+ $pos += 4 * $bbs;
+ }
+
+ $pos = 0;
+ $sbdBlock = $this->sbdStartBlock;
+ $this->smallBlockChain = '';
+ while ($sbdBlock != -2) {
+ $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;
+
+ $this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs);
+ $pos += 4 * $bbs;
+
+ $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4);
+ }
+
+ // read the directory stream
+ $block = $this->rootStartBlock;
+ $this->entry = $this->_readData($block);
+
+ $this->readPropertySets();
+ }
+
+ /**
+ * Extract binary stream data.
+ *
+ * @param int $stream
+ *
+ * @return string
+ */
+ public function getStream($stream)
+ {
+ if ($stream === null) {
+ return null;
+ }
+
+ $streamData = '';
+
+ if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) {
+ $rootdata = $this->_readData($this->props[$this->rootentry]['startBlock']);
+
+ $block = $this->props[$stream]['startBlock'];
+
+ while ($block != -2) {
+ $pos = $block * self::SMALL_BLOCK_SIZE;
+ $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE);
+
+ $block = self::getInt4d($this->smallBlockChain, $block * 4);
+ }
+
+ return $streamData;
+ }
+ $numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE;
+ if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) {
+ ++$numBlocks;
+ }
+
+ if ($numBlocks == 0) {
+ return '';
+ }
+
+ $block = $this->props[$stream]['startBlock'];
+
+ while ($block != -2) {
+ $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
+ $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
+ $block = self::getInt4d($this->bigBlockChain, $block * 4);
+ }
+
+ return $streamData;
+ }
+
+ /**
+ * Read a standard stream (by joining sectors using information from SAT).
+ *
+ * @param int $bl Sector ID where the stream starts
+ *
+ * @return string Data for standard stream
+ */
+ private function _readData($bl)
+ {
+ $block = $bl;
+ $data = '';
+
+ while ($block != -2) {
+ $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
+ $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
+ $block = self::getInt4d($this->bigBlockChain, $block * 4);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Read entries in the directory stream.
+ */
+ private function readPropertySets(): void
+ {
+ $offset = 0;
+
+ // loop through entires, each entry is 128 bytes
+ $entryLen = strlen($this->entry);
+ while ($offset < $entryLen) {
+ // entry data (128 bytes)
+ $d = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE);
+
+ // size in bytes of name
+ $nameSize = ord($d[self::SIZE_OF_NAME_POS]) | (ord($d[self::SIZE_OF_NAME_POS + 1]) << 8);
+
+ // type of entry
+ $type = ord($d[self::TYPE_POS]);
+
+ // sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook)
+ // sectorID of first sector of the short-stream container stream, if this entry is root entry
+ $startBlock = self::getInt4d($d, self::START_BLOCK_POS);
+
+ $size = self::getInt4d($d, self::SIZE_POS);
+
+ $name = str_replace("\x00", '', substr($d, 0, $nameSize));
+
+ $this->props[] = [
+ 'name' => $name,
+ 'type' => $type,
+ 'startBlock' => $startBlock,
+ 'size' => $size,
+ ];
+
+ // tmp helper to simplify checks
+ $upName = strtoupper($name);
+
+ // Workbook directory entry (BIFF5 uses Book, BIFF8 uses Workbook)
+ if (($upName === 'WORKBOOK') || ($upName === 'BOOK')) {
+ $this->wrkbook = count($this->props) - 1;
+ } elseif ($upName === 'ROOT ENTRY' || $upName === 'R') {
+ // Root entry
+ $this->rootentry = count($this->props) - 1;
+ }
+
+ // Summary information
+ if ($name == chr(5) . 'SummaryInformation') {
+ $this->summaryInformation = count($this->props) - 1;
+ }
+
+ // Additional Document Summary information
+ if ($name == chr(5) . 'DocumentSummaryInformation') {
+ $this->documentSummaryInformation = count($this->props) - 1;
+ }
+
+ $offset += self::PROPERTY_STORAGE_BLOCK_SIZE;
+ }
+ }
+
+ /**
+ * Read 4 bytes of data at specified position.
+ *
+ * @param string $data
+ * @param int $pos
+ *
+ * @return int
+ */
+ private static function getInt4d($data, $pos)
+ {
+ if ($pos < 0) {
+ // Invalid position
+ throw new ReaderException('Parameter pos=' . $pos . ' is invalid.');
+ }
+
+ $len = strlen($data);
+ if ($len < $pos + 4) {
+ $data .= str_repeat("\0", $pos + 4 - $len);
+ }
+
+ // FIX: represent numbers correctly on 64-bit system
+ // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
+ // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
+ $_or_24 = ord($data[$pos + 3]);
+ if ($_or_24 >= 128) {
+ // negative number
+ $_ord_24 = -abs((256 - $_or_24) << 24);
+ } else {
+ $_ord_24 = ($_or_24 & 127) << 24;
+ }
+
+ return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php
new file mode 100755
index 0000000..9fefe88
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared;
+
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Worksheet\Protection;
+
+class PasswordHasher
+{
+ /**
+ * Get algorithm name for PHP.
+ */
+ private static function getAlgorithm(string $algorithmName): string
+ {
+ if (!$algorithmName) {
+ return '';
+ }
+
+ // Mapping between algorithm name in Excel and algorithm name in PHP
+ $mapping = [
+ Protection::ALGORITHM_MD2 => 'md2',
+ Protection::ALGORITHM_MD4 => 'md4',
+ Protection::ALGORITHM_MD5 => 'md5',
+ Protection::ALGORITHM_SHA_1 => 'sha1',
+ Protection::ALGORITHM_SHA_256 => 'sha256',
+ Protection::ALGORITHM_SHA_384 => 'sha384',
+ Protection::ALGORITHM_SHA_512 => 'sha512',
+ Protection::ALGORITHM_RIPEMD_128 => 'ripemd128',
+ Protection::ALGORITHM_RIPEMD_160 => 'ripemd160',
+ Protection::ALGORITHM_WHIRLPOOL => 'whirlpool',
+ ];
+
+ if (array_key_exists($algorithmName, $mapping)) {
+ return $mapping[$algorithmName];
+ }
+
+ throw new Exception('Unsupported password algorithm: ' . $algorithmName);
+ }
+
+ /**
+ * Create a password hash from a given string.
+ *
+ * This method is based on the algorithm provided by
+ * Daniel Rentz of OpenOffice and the PEAR package
+ * Spreadsheet_Excel_Writer by Xavier Noguer <xnoguer@rezebra.com>.
+ *
+ * @param string $pPassword Password to hash
+ */
+ private static function defaultHashPassword(string $pPassword): string
+ {
+ $password = 0x0000;
+ $charPos = 1; // char position
+
+ // split the plain text password in its component characters
+ $chars = preg_split('//', $pPassword, -1, PREG_SPLIT_NO_EMPTY);
+ foreach ($chars as $char) {
+ $value = ord($char) << $charPos++; // shifted ASCII value
+ $rotated_bits = $value >> 15; // rotated bits beyond bit 15
+ $value &= 0x7fff; // first 15 bits
+ $password ^= ($value | $rotated_bits);
+ }
+
+ $password ^= strlen($pPassword);
+ $password ^= 0xCE4B;
+
+ return strtoupper(dechex($password));
+ }
+
+ /**
+ * Create a password hash from a given string by a specific algorithm.
+ *
+ * 2.4.2.4 ISO Write Protection Method
+ *
+ * @see https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/1357ea58-646e-4483-92ef-95d718079d6f
+ *
+ * @param string $password Password to hash
+ * @param string $algorithm Hash algorithm used to compute the password hash value
+ * @param string $salt Pseudorandom string
+ * @param int $spinCount Number of times to iterate on a hash of a password
+ *
+ * @return string Hashed password
+ */
+ public static function hashPassword(string $password, string $algorithm = '', string $salt = '', int $spinCount = 10000): string
+ {
+ $phpAlgorithm = self::getAlgorithm($algorithm);
+ if (!$phpAlgorithm) {
+ return self::defaultHashPassword($password);
+ }
+
+ $saltValue = base64_decode($salt);
+ $encodedPassword = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8');
+
+ $hashValue = hash($phpAlgorithm, $saltValue . $encodedPassword, true);
+ for ($i = 0; $i < $spinCount; ++$i) {
+ $hashValue = hash($phpAlgorithm, $hashValue . pack('L', $i), true);
+ }
+
+ return base64_encode($hashValue);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php
new file mode 100755
index 0000000..9ae3241
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php
@@ -0,0 +1,722 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+
+class StringHelper
+{
+ /** Constants */
+ /** Regular Expressions */
+ // Fraction
+ const STRING_REGEXP_FRACTION = '(-?)(\d+)\s+(\d+\/\d+)';
+
+ /**
+ * Control characters array.
+ *
+ * @var string[]
+ */
+ private static $controlCharacters = [];
+
+ /**
+ * SYLK Characters array.
+ *
+ * @var array
+ */
+ private static $SYLKCharacters = [];
+
+ /**
+ * Decimal separator.
+ *
+ * @var string
+ */
+ private static $decimalSeparator;
+
+ /**
+ * Thousands separator.
+ *
+ * @var string
+ */
+ private static $thousandsSeparator;
+
+ /**
+ * Currency code.
+ *
+ * @var string
+ */
+ private static $currencyCode;
+
+ /**
+ * Is iconv extension avalable?
+ *
+ * @var bool
+ */
+ private static $isIconvEnabled;
+
+ /**
+ * iconv options.
+ *
+ * @var string
+ */
+ private static $iconvOptions = '//IGNORE//TRANSLIT';
+
+ /**
+ * Build control characters array.
+ */
+ private static function buildControlCharacters(): void
+ {
+ for ($i = 0; $i <= 31; ++$i) {
+ if ($i != 9 && $i != 10 && $i != 13) {
+ $find = '_x' . sprintf('%04s', strtoupper(dechex($i))) . '_';
+ $replace = chr($i);
+ self::$controlCharacters[$find] = $replace;
+ }
+ }
+ }
+
+ /**
+ * Build SYLK characters array.
+ */
+ private static function buildSYLKCharacters(): void
+ {
+ self::$SYLKCharacters = [
+ "\x1B 0" => chr(0),
+ "\x1B 1" => chr(1),
+ "\x1B 2" => chr(2),
+ "\x1B 3" => chr(3),
+ "\x1B 4" => chr(4),
+ "\x1B 5" => chr(5),
+ "\x1B 6" => chr(6),
+ "\x1B 7" => chr(7),
+ "\x1B 8" => chr(8),
+ "\x1B 9" => chr(9),
+ "\x1B :" => chr(10),
+ "\x1B ;" => chr(11),
+ "\x1B <" => chr(12),
+ "\x1B =" => chr(13),
+ "\x1B >" => chr(14),
+ "\x1B ?" => chr(15),
+ "\x1B!0" => chr(16),
+ "\x1B!1" => chr(17),
+ "\x1B!2" => chr(18),
+ "\x1B!3" => chr(19),
+ "\x1B!4" => chr(20),
+ "\x1B!5" => chr(21),
+ "\x1B!6" => chr(22),
+ "\x1B!7" => chr(23),
+ "\x1B!8" => chr(24),
+ "\x1B!9" => chr(25),
+ "\x1B!:" => chr(26),
+ "\x1B!;" => chr(27),
+ "\x1B!<" => chr(28),
+ "\x1B!=" => chr(29),
+ "\x1B!>" => chr(30),
+ "\x1B!?" => chr(31),
+ "\x1B'?" => chr(127),
+ "\x1B(0" => '€', // 128 in CP1252
+ "\x1B(2" => '‚', // 130 in CP1252
+ "\x1B(3" => 'ƒ', // 131 in CP1252
+ "\x1B(4" => '„', // 132 in CP1252
+ "\x1B(5" => '…', // 133 in CP1252
+ "\x1B(6" => '†', // 134 in CP1252
+ "\x1B(7" => '‡', // 135 in CP1252
+ "\x1B(8" => 'ˆ', // 136 in CP1252
+ "\x1B(9" => '‰', // 137 in CP1252
+ "\x1B(:" => 'Š', // 138 in CP1252
+ "\x1B(;" => '‹', // 139 in CP1252
+ "\x1BNj" => 'Œ', // 140 in CP1252
+ "\x1B(>" => 'Ž', // 142 in CP1252
+ "\x1B)1" => '‘', // 145 in CP1252
+ "\x1B)2" => '’', // 146 in CP1252
+ "\x1B)3" => '“', // 147 in CP1252
+ "\x1B)4" => '”', // 148 in CP1252
+ "\x1B)5" => '•', // 149 in CP1252
+ "\x1B)6" => '–', // 150 in CP1252
+ "\x1B)7" => '—', // 151 in CP1252
+ "\x1B)8" => '˜', // 152 in CP1252
+ "\x1B)9" => '™', // 153 in CP1252
+ "\x1B):" => 'š', // 154 in CP1252
+ "\x1B);" => '›', // 155 in CP1252
+ "\x1BNz" => 'œ', // 156 in CP1252
+ "\x1B)>" => 'ž', // 158 in CP1252
+ "\x1B)?" => 'Ÿ', // 159 in CP1252
+ "\x1B*0" => ' ', // 160 in CP1252
+ "\x1BN!" => '¡', // 161 in CP1252
+ "\x1BN\"" => '¢', // 162 in CP1252
+ "\x1BN#" => '£', // 163 in CP1252
+ "\x1BN(" => '¤', // 164 in CP1252
+ "\x1BN%" => '¥', // 165 in CP1252
+ "\x1B*6" => '¦', // 166 in CP1252
+ "\x1BN'" => '§', // 167 in CP1252
+ "\x1BNH " => '¨', // 168 in CP1252
+ "\x1BNS" => '©', // 169 in CP1252
+ "\x1BNc" => 'ª', // 170 in CP1252
+ "\x1BN+" => '«', // 171 in CP1252
+ "\x1B*<" => '¬', // 172 in CP1252
+ "\x1B*=" => '', // 173 in CP1252
+ "\x1BNR" => '®', // 174 in CP1252
+ "\x1B*?" => '¯', // 175 in CP1252
+ "\x1BN0" => '°', // 176 in CP1252
+ "\x1BN1" => '±', // 177 in CP1252
+ "\x1BN2" => '²', // 178 in CP1252
+ "\x1BN3" => '³', // 179 in CP1252
+ "\x1BNB " => '´', // 180 in CP1252
+ "\x1BN5" => 'µ', // 181 in CP1252
+ "\x1BN6" => '¶', // 182 in CP1252
+ "\x1BN7" => '·', // 183 in CP1252
+ "\x1B+8" => '¸', // 184 in CP1252
+ "\x1BNQ" => '¹', // 185 in CP1252
+ "\x1BNk" => 'º', // 186 in CP1252
+ "\x1BN;" => '»', // 187 in CP1252
+ "\x1BN<" => '¼', // 188 in CP1252
+ "\x1BN=" => '½', // 189 in CP1252
+ "\x1BN>" => '¾', // 190 in CP1252
+ "\x1BN?" => '¿', // 191 in CP1252
+ "\x1BNAA" => 'À', // 192 in CP1252
+ "\x1BNBA" => 'Á', // 193 in CP1252
+ "\x1BNCA" => 'Â', // 194 in CP1252
+ "\x1BNDA" => 'Ã', // 195 in CP1252
+ "\x1BNHA" => 'Ä', // 196 in CP1252
+ "\x1BNJA" => 'Å', // 197 in CP1252
+ "\x1BNa" => 'Æ', // 198 in CP1252
+ "\x1BNKC" => 'Ç', // 199 in CP1252
+ "\x1BNAE" => 'È', // 200 in CP1252
+ "\x1BNBE" => 'É', // 201 in CP1252
+ "\x1BNCE" => 'Ê', // 202 in CP1252
+ "\x1BNHE" => 'Ë', // 203 in CP1252
+ "\x1BNAI" => 'Ì', // 204 in CP1252
+ "\x1BNBI" => 'Í', // 205 in CP1252
+ "\x1BNCI" => 'Î', // 206 in CP1252
+ "\x1BNHI" => 'Ï', // 207 in CP1252
+ "\x1BNb" => 'Ð', // 208 in CP1252
+ "\x1BNDN" => 'Ñ', // 209 in CP1252
+ "\x1BNAO" => 'Ò', // 210 in CP1252
+ "\x1BNBO" => 'Ó', // 211 in CP1252
+ "\x1BNCO" => 'Ô', // 212 in CP1252
+ "\x1BNDO" => 'Õ', // 213 in CP1252
+ "\x1BNHO" => 'Ö', // 214 in CP1252
+ "\x1B-7" => '×', // 215 in CP1252
+ "\x1BNi" => 'Ø', // 216 in CP1252
+ "\x1BNAU" => 'Ù', // 217 in CP1252
+ "\x1BNBU" => 'Ú', // 218 in CP1252
+ "\x1BNCU" => 'Û', // 219 in CP1252
+ "\x1BNHU" => 'Ü', // 220 in CP1252
+ "\x1B-=" => 'Ý', // 221 in CP1252
+ "\x1BNl" => 'Þ', // 222 in CP1252
+ "\x1BN{" => 'ß', // 223 in CP1252
+ "\x1BNAa" => 'à', // 224 in CP1252
+ "\x1BNBa" => 'á', // 225 in CP1252
+ "\x1BNCa" => 'â', // 226 in CP1252
+ "\x1BNDa" => 'ã', // 227 in CP1252
+ "\x1BNHa" => 'ä', // 228 in CP1252
+ "\x1BNJa" => 'å', // 229 in CP1252
+ "\x1BNq" => 'æ', // 230 in CP1252
+ "\x1BNKc" => 'ç', // 231 in CP1252
+ "\x1BNAe" => 'è', // 232 in CP1252
+ "\x1BNBe" => 'é', // 233 in CP1252
+ "\x1BNCe" => 'ê', // 234 in CP1252
+ "\x1BNHe" => 'ë', // 235 in CP1252
+ "\x1BNAi" => 'ì', // 236 in CP1252
+ "\x1BNBi" => 'í', // 237 in CP1252
+ "\x1BNCi" => 'î', // 238 in CP1252
+ "\x1BNHi" => 'ï', // 239 in CP1252
+ "\x1BNs" => 'ð', // 240 in CP1252
+ "\x1BNDn" => 'ñ', // 241 in CP1252
+ "\x1BNAo" => 'ò', // 242 in CP1252
+ "\x1BNBo" => 'ó', // 243 in CP1252
+ "\x1BNCo" => 'ô', // 244 in CP1252
+ "\x1BNDo" => 'õ', // 245 in CP1252
+ "\x1BNHo" => 'ö', // 246 in CP1252
+ "\x1B/7" => '÷', // 247 in CP1252
+ "\x1BNy" => 'ø', // 248 in CP1252
+ "\x1BNAu" => 'ù', // 249 in CP1252
+ "\x1BNBu" => 'ú', // 250 in CP1252
+ "\x1BNCu" => 'û', // 251 in CP1252
+ "\x1BNHu" => 'ü', // 252 in CP1252
+ "\x1B/=" => 'ý', // 253 in CP1252
+ "\x1BN|" => 'þ', // 254 in CP1252
+ "\x1BNHy" => 'ÿ', // 255 in CP1252
+ ];
+ }
+
+ /**
+ * Get whether iconv extension is available.
+ *
+ * @return bool
+ */
+ public static function getIsIconvEnabled()
+ {
+ if (isset(self::$isIconvEnabled)) {
+ return self::$isIconvEnabled;
+ }
+
+ // Assume no problems with iconv
+ self::$isIconvEnabled = true;
+
+ // Fail if iconv doesn't exist
+ if (!function_exists('iconv')) {
+ self::$isIconvEnabled = false;
+ } elseif (!@iconv('UTF-8', 'UTF-16LE', 'x')) {
+ // Sometimes iconv is not working, and e.g. iconv('UTF-8', 'UTF-16LE', 'x') just returns false,
+ self::$isIconvEnabled = false;
+ } elseif (defined('PHP_OS') && @stristr(PHP_OS, 'AIX') && defined('ICONV_IMPL') && (@strcasecmp(ICONV_IMPL, 'unknown') == 0) && defined('ICONV_VERSION') && (@strcasecmp(ICONV_VERSION, 'unknown') == 0)) {
+ // CUSTOM: IBM AIX iconv() does not work
+ self::$isIconvEnabled = false;
+ }
+
+ // Deactivate iconv default options if they fail (as seen on IMB i)
+ if (self::$isIconvEnabled && !@iconv('UTF-8', 'UTF-16LE' . self::$iconvOptions, 'x')) {
+ self::$iconvOptions = '';
+ }
+
+ return self::$isIconvEnabled;
+ }
+
+ private static function buildCharacterSets(): void
+ {
+ if (empty(self::$controlCharacters)) {
+ self::buildControlCharacters();
+ }
+
+ if (empty(self::$SYLKCharacters)) {
+ self::buildSYLKCharacters();
+ }
+ }
+
+ /**
+ * Convert from OpenXML escaped control character to PHP control character.
+ *
+ * Excel 2007 team:
+ * ----------------
+ * That's correct, control characters are stored directly in the shared-strings table.
+ * We do encode characters that cannot be represented in XML using the following escape sequence:
+ * _xHHHH_ where H represents a hexadecimal character in the character's value...
+ * So you could end up with something like _x0008_ in a string (either in a cell value (<v>)
+ * element or in the shared string <t> element.
+ *
+ * @param string $value Value to unescape
+ *
+ * @return string
+ */
+ public static function controlCharacterOOXML2PHP($value)
+ {
+ self::buildCharacterSets();
+
+ return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $value);
+ }
+
+ /**
+ * Convert from PHP control character to OpenXML escaped control character.
+ *
+ * Excel 2007 team:
+ * ----------------
+ * That's correct, control characters are stored directly in the shared-strings table.
+ * We do encode characters that cannot be represented in XML using the following escape sequence:
+ * _xHHHH_ where H represents a hexadecimal character in the character's value...
+ * So you could end up with something like _x0008_ in a string (either in a cell value (<v>)
+ * element or in the shared string <t> element.
+ *
+ * @param string $value Value to escape
+ *
+ * @return string
+ */
+ public static function controlCharacterPHP2OOXML($value)
+ {
+ self::buildCharacterSets();
+
+ return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $value);
+ }
+
+ /**
+ * Try to sanitize UTF8, stripping invalid byte sequences. Not perfect. Does not surrogate characters.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public static function sanitizeUTF8($value)
+ {
+ if (self::getIsIconvEnabled()) {
+ $value = @iconv('UTF-8', 'UTF-8', $value);
+
+ return $value;
+ }
+
+ $value = mb_convert_encoding($value, 'UTF-8', 'UTF-8');
+
+ return $value;
+ }
+
+ /**
+ * Check if a string contains UTF8 data.
+ *
+ * @param string $value
+ *
+ * @return bool
+ */
+ public static function isUTF8($value)
+ {
+ return $value === '' || preg_match('/^./su', $value) === 1;
+ }
+
+ /**
+ * Formats a numeric value as a string for output in various output writers forcing
+ * point as decimal separator in case locale is other than English.
+ *
+ * @param mixed $value
+ *
+ * @return string
+ */
+ public static function formatNumber($value)
+ {
+ if (is_float($value)) {
+ return str_replace(',', '.', $value);
+ }
+
+ return (string) $value;
+ }
+
+ /**
+ * Converts a UTF-8 string into BIFF8 Unicode string data (8-bit string length)
+ * Writes the string using uncompressed notation, no rich text, no Asian phonetics
+ * If mbstring extension is not available, ASCII is assumed, and compressed notation is used
+ * although this will give wrong results for non-ASCII strings
+ * see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3.
+ *
+ * @param string $value UTF-8 encoded string
+ * @param mixed[] $arrcRuns Details of rich text runs in $value
+ *
+ * @return string
+ */
+ public static function UTF8toBIFF8UnicodeShort($value, $arrcRuns = [])
+ {
+ // character count
+ $ln = self::countCharacters($value, 'UTF-8');
+ // option flags
+ if (empty($arrcRuns)) {
+ $data = pack('CC', $ln, 0x0001);
+ // characters
+ $data .= self::convertEncoding($value, 'UTF-16LE', 'UTF-8');
+ } else {
+ $data = pack('vC', $ln, 0x09);
+ $data .= pack('v', count($arrcRuns));
+ // characters
+ $data .= self::convertEncoding($value, 'UTF-16LE', 'UTF-8');
+ foreach ($arrcRuns as $cRun) {
+ $data .= pack('v', $cRun['strlen']);
+ $data .= pack('v', $cRun['fontidx']);
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Converts a UTF-8 string into BIFF8 Unicode string data (16-bit string length)
+ * Writes the string using uncompressed notation, no rich text, no Asian phonetics
+ * If mbstring extension is not available, ASCII is assumed, and compressed notation is used
+ * although this will give wrong results for non-ASCII strings
+ * see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3.
+ *
+ * @param string $value UTF-8 encoded string
+ *
+ * @return string
+ */
+ public static function UTF8toBIFF8UnicodeLong($value)
+ {
+ // character count
+ $ln = self::countCharacters($value, 'UTF-8');
+
+ // characters
+ $chars = self::convertEncoding($value, 'UTF-16LE', 'UTF-8');
+
+ return pack('vC', $ln, 0x0001) . $chars;
+ }
+
+ /**
+ * Convert string from one encoding to another.
+ *
+ * @param string $value
+ * @param string $to Encoding to convert to, e.g. 'UTF-8'
+ * @param string $from Encoding to convert from, e.g. 'UTF-16LE'
+ *
+ * @return string
+ */
+ public static function convertEncoding($value, $to, $from)
+ {
+ if (self::getIsIconvEnabled()) {
+ $result = iconv($from, $to . self::$iconvOptions, $value);
+ if (false !== $result) {
+ return $result;
+ }
+ }
+
+ return mb_convert_encoding($value, $to, $from);
+ }
+
+ /**
+ * Get character count.
+ *
+ * @param string $value
+ * @param string $enc Encoding
+ *
+ * @return int Character count
+ */
+ public static function countCharacters($value, $enc = 'UTF-8')
+ {
+ return mb_strlen($value, $enc);
+ }
+
+ /**
+ * Get a substring of a UTF-8 encoded string.
+ *
+ * @param string $pValue UTF-8 encoded string
+ * @param int $pStart Start offset
+ * @param int $pLength Maximum number of characters in substring
+ *
+ * @return string
+ */
+ public static function substring($pValue, $pStart, $pLength = 0)
+ {
+ return mb_substr($pValue, $pStart, $pLength, 'UTF-8');
+ }
+
+ /**
+ * Convert a UTF-8 encoded string to upper case.
+ *
+ * @param string $pValue UTF-8 encoded string
+ *
+ * @return string
+ */
+ public static function strToUpper($pValue)
+ {
+ return mb_convert_case($pValue, MB_CASE_UPPER, 'UTF-8');
+ }
+
+ /**
+ * Convert a UTF-8 encoded string to lower case.
+ *
+ * @param string $pValue UTF-8 encoded string
+ *
+ * @return string
+ */
+ public static function strToLower($pValue)
+ {
+ return mb_convert_case($pValue, MB_CASE_LOWER, 'UTF-8');
+ }
+
+ /**
+ * Convert a UTF-8 encoded string to title/proper case
+ * (uppercase every first character in each word, lower case all other characters).
+ *
+ * @param string $pValue UTF-8 encoded string
+ *
+ * @return string
+ */
+ public static function strToTitle($pValue)
+ {
+ return mb_convert_case($pValue, MB_CASE_TITLE, 'UTF-8');
+ }
+
+ public static function mbIsUpper($char)
+ {
+ return mb_strtolower($char, 'UTF-8') != $char;
+ }
+
+ public static function mbStrSplit($string)
+ {
+ // Split at all position not after the start: ^
+ // and not before the end: $
+ return preg_split('/(?<!^)(?!$)/u', $string);
+ }
+
+ /**
+ * Reverse the case of a string, so that all uppercase characters become lowercase
+ * and all lowercase characters become uppercase.
+ *
+ * @param string $pValue UTF-8 encoded string
+ *
+ * @return string
+ */
+ public static function strCaseReverse($pValue)
+ {
+ $characters = self::mbStrSplit($pValue);
+ foreach ($characters as &$character) {
+ if (self::mbIsUpper($character)) {
+ $character = mb_strtolower($character, 'UTF-8');
+ } else {
+ $character = mb_strtoupper($character, 'UTF-8');
+ }
+ }
+
+ return implode('', $characters);
+ }
+
+ /**
+ * 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
+ *
+ * @return bool
+ */
+ public static function convertToNumberIfFraction(&$operand)
+ {
+ if (preg_match('/^' . self::STRING_REGEXP_FRACTION . '$/i', $operand, $match)) {
+ $sign = ($match[1] == '-') ? '-' : '+';
+ $fractionFormula = '=' . $sign . $match[2] . $sign . $match[3];
+ $operand = Calculation::getInstance()->_calculateFormulaValue($fractionFormula);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ // function convertToNumberIfFraction()
+
+ /**
+ * Get the decimal separator. If it has not yet been set explicitly, try to obtain number
+ * formatting information from locale.
+ *
+ * @return string
+ */
+ public static function getDecimalSeparator()
+ {
+ if (!isset(self::$decimalSeparator)) {
+ $localeconv = localeconv();
+ self::$decimalSeparator = ($localeconv['decimal_point'] != '')
+ ? $localeconv['decimal_point'] : $localeconv['mon_decimal_point'];
+
+ if (self::$decimalSeparator == '') {
+ // Default to .
+ self::$decimalSeparator = '.';
+ }
+ }
+
+ return self::$decimalSeparator;
+ }
+
+ /**
+ * Set the decimal separator. Only used by NumberFormat::toFormattedString()
+ * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
+ *
+ * @param string $pValue Character for decimal separator
+ */
+ public static function setDecimalSeparator($pValue): void
+ {
+ self::$decimalSeparator = $pValue;
+ }
+
+ /**
+ * Get the thousands separator. If it has not yet been set explicitly, try to obtain number
+ * formatting information from locale.
+ *
+ * @return string
+ */
+ public static function getThousandsSeparator()
+ {
+ if (!isset(self::$thousandsSeparator)) {
+ $localeconv = localeconv();
+ self::$thousandsSeparator = ($localeconv['thousands_sep'] != '')
+ ? $localeconv['thousands_sep'] : $localeconv['mon_thousands_sep'];
+
+ if (self::$thousandsSeparator == '') {
+ // Default to .
+ self::$thousandsSeparator = ',';
+ }
+ }
+
+ return self::$thousandsSeparator;
+ }
+
+ /**
+ * Set the thousands separator. Only used by NumberFormat::toFormattedString()
+ * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
+ *
+ * @param string $pValue Character for thousands separator
+ */
+ public static function setThousandsSeparator($pValue): void
+ {
+ self::$thousandsSeparator = $pValue;
+ }
+
+ /**
+ * Get the currency code. If it has not yet been set explicitly, try to obtain the
+ * symbol information from locale.
+ *
+ * @return string
+ */
+ public static function getCurrencyCode()
+ {
+ if (!empty(self::$currencyCode)) {
+ return self::$currencyCode;
+ }
+ self::$currencyCode = '$';
+ $localeconv = localeconv();
+ if (!empty($localeconv['currency_symbol'])) {
+ self::$currencyCode = $localeconv['currency_symbol'];
+
+ return self::$currencyCode;
+ }
+ if (!empty($localeconv['int_curr_symbol'])) {
+ self::$currencyCode = $localeconv['int_curr_symbol'];
+
+ return self::$currencyCode;
+ }
+
+ return self::$currencyCode;
+ }
+
+ /**
+ * Set the currency code. Only used by NumberFormat::toFormattedString()
+ * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
+ *
+ * @param string $pValue Character for currency code
+ */
+ public static function setCurrencyCode($pValue): void
+ {
+ self::$currencyCode = $pValue;
+ }
+
+ /**
+ * Convert SYLK encoded string to UTF-8.
+ *
+ * @param string $pValue
+ *
+ * @return string UTF-8 encoded string
+ */
+ public static function SYLKtoUTF8($pValue)
+ {
+ self::buildCharacterSets();
+
+ // If there is no escape character in the string there is nothing to do
+ if (strpos($pValue, '') === false) {
+ return $pValue;
+ }
+
+ foreach (self::$SYLKCharacters as $k => $v) {
+ $pValue = str_replace($k, $v, $pValue);
+ }
+
+ return $pValue;
+ }
+
+ /**
+ * Retrieve any leading numeric part of a string, or return the full string if no leading numeric
+ * (handles basic integer or float, but not exponent or non decimal).
+ *
+ * @param string $value
+ *
+ * @return mixed string or only the leading numeric part of the string
+ */
+ public static function testStringAsNumeric($value)
+ {
+ if (is_numeric($value)) {
+ return $value;
+ }
+ $v = (float) $value;
+
+ return (is_numeric(substr($value, 0, strlen($v)))) ? $v : $value;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php
new file mode 100755
index 0000000..43fd365
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared;
+
+use DateTimeZone;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class TimeZone
+{
+ /**
+ * Default Timezone used for date/time conversions.
+ *
+ * @var string
+ */
+ protected static $timezone = 'UTC';
+
+ /**
+ * Validate a Timezone name.
+ *
+ * @param string $timezone Time zone (e.g. 'Europe/London')
+ *
+ * @return bool Success or failure
+ */
+ private static function validateTimeZone($timezone)
+ {
+ return in_array($timezone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC));
+ }
+
+ /**
+ * Set the Default Timezone used for date/time conversions.
+ *
+ * @param string $timezone Time zone (e.g. 'Europe/London')
+ *
+ * @return bool Success or failure
+ */
+ public static function setTimeZone($timezone)
+ {
+ if (self::validateTimezone($timezone)) {
+ self::$timezone = $timezone;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return the Default Timezone used for date/time conversions.
+ *
+ * @return string Timezone (e.g. 'Europe/London')
+ */
+ public static function getTimeZone()
+ {
+ return self::$timezone;
+ }
+
+ /**
+ * Return the Timezone offset used for date/time conversions to/from UST
+ * This requires both the timezone and the calculated date/time to allow for local DST.
+ *
+ * @param string $timezone The timezone for finding the adjustment to UST
+ * @param int $timestamp PHP date/time value
+ *
+ * @return int Number of seconds for timezone adjustment
+ */
+ public static function getTimeZoneAdjustment($timezone, $timestamp)
+ {
+ if ($timezone !== null) {
+ if (!self::validateTimezone($timezone)) {
+ throw new PhpSpreadsheetException('Invalid timezone ' . $timezone);
+ }
+ } else {
+ $timezone = self::$timezone;
+ }
+
+ $objTimezone = new DateTimeZone($timezone);
+ $transitions = $objTimezone->getTransitions($timestamp, $timestamp);
+
+ return (count($transitions) > 0) ? $transitions[0]['offset'] : 0;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php
new file mode 100755
index 0000000..c949972
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php
@@ -0,0 +1,463 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
+
+class BestFit
+{
+ /**
+ * Indicator flag for a calculation error.
+ *
+ * @var bool
+ */
+ protected $error = false;
+
+ /**
+ * Algorithm type to use for best-fit.
+ *
+ * @var string
+ */
+ protected $bestFitType = 'undetermined';
+
+ /**
+ * Number of entries in the sets of x- and y-value arrays.
+ *
+ * @var int
+ */
+ protected $valueCount = 0;
+
+ /**
+ * X-value dataseries of values.
+ *
+ * @var float[]
+ */
+ protected $xValues = [];
+
+ /**
+ * Y-value dataseries of values.
+ *
+ * @var float[]
+ */
+ protected $yValues = [];
+
+ /**
+ * Flag indicating whether values should be adjusted to Y=0.
+ *
+ * @var bool
+ */
+ protected $adjustToZero = false;
+
+ /**
+ * Y-value series of best-fit values.
+ *
+ * @var float[]
+ */
+ protected $yBestFitValues = [];
+
+ protected $goodnessOfFit = 1;
+
+ protected $stdevOfResiduals = 0;
+
+ protected $covariance = 0;
+
+ protected $correlation = 0;
+
+ protected $SSRegression = 0;
+
+ protected $SSResiduals = 0;
+
+ protected $DFResiduals = 0;
+
+ protected $f = 0;
+
+ protected $slope = 0;
+
+ protected $slopeSE = 0;
+
+ protected $intersect = 0;
+
+ protected $intersectSE = 0;
+
+ protected $xOffset = 0;
+
+ protected $yOffset = 0;
+
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ public function getBestFitType()
+ {
+ return $this->bestFitType;
+ }
+
+ /**
+ * Return the Y-Value for a specified value of X.
+ *
+ * @param float $xValue X-Value
+ *
+ * @return bool Y-Value
+ */
+ public function getValueOfYForX($xValue)
+ {
+ return false;
+ }
+
+ /**
+ * Return the X-Value for a specified value of Y.
+ *
+ * @param float $yValue Y-Value
+ *
+ * @return bool X-Value
+ */
+ public function getValueOfXForY($yValue)
+ {
+ return false;
+ }
+
+ /**
+ * Return the original set of X-Values.
+ *
+ * @return float[] X-Values
+ */
+ public function getXValues()
+ {
+ return $this->xValues;
+ }
+
+ /**
+ * Return the Equation of the best-fit line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return bool
+ */
+ public function getEquation($dp = 0)
+ {
+ return false;
+ }
+
+ /**
+ * Return the Slope of the line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return float
+ */
+ public function getSlope($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->slope, $dp);
+ }
+
+ return $this->slope;
+ }
+
+ /**
+ * Return the standard error of the Slope.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return float
+ */
+ public function getSlopeSE($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->slopeSE, $dp);
+ }
+
+ return $this->slopeSE;
+ }
+
+ /**
+ * Return the Value of X where it intersects Y = 0.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return float
+ */
+ public function getIntersect($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->intersect, $dp);
+ }
+
+ return $this->intersect;
+ }
+
+ /**
+ * Return the standard error of the Intersect.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return float
+ */
+ public function getIntersectSE($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->intersectSE, $dp);
+ }
+
+ return $this->intersectSE;
+ }
+
+ /**
+ * Return the goodness of fit for this regression.
+ *
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getGoodnessOfFit($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->goodnessOfFit, $dp);
+ }
+
+ return $this->goodnessOfFit;
+ }
+
+ /**
+ * Return the goodness of fit for this regression.
+ *
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getGoodnessOfFitPercent($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->goodnessOfFit * 100, $dp);
+ }
+
+ return $this->goodnessOfFit * 100;
+ }
+
+ /**
+ * Return the standard deviation of the residuals for this regression.
+ *
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getStdevOfResiduals($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->stdevOfResiduals, $dp);
+ }
+
+ return $this->stdevOfResiduals;
+ }
+
+ /**
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getSSRegression($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->SSRegression, $dp);
+ }
+
+ return $this->SSRegression;
+ }
+
+ /**
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getSSResiduals($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->SSResiduals, $dp);
+ }
+
+ return $this->SSResiduals;
+ }
+
+ /**
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getDFResiduals($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->DFResiduals, $dp);
+ }
+
+ return $this->DFResiduals;
+ }
+
+ /**
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getF($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->f, $dp);
+ }
+
+ return $this->f;
+ }
+
+ /**
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getCovariance($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->covariance, $dp);
+ }
+
+ return $this->covariance;
+ }
+
+ /**
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getCorrelation($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->correlation, $dp);
+ }
+
+ return $this->correlation;
+ }
+
+ /**
+ * @return float[]
+ */
+ public function getYBestFitValues()
+ {
+ return $this->yBestFitValues;
+ }
+
+ protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const): void
+ {
+ $SSres = $SScov = $SScor = $SStot = $SSsex = 0.0;
+ foreach ($this->xValues as $xKey => $xValue) {
+ $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
+
+ $SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY);
+ if ($const) {
+ $SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY);
+ } else {
+ $SStot += $this->yValues[$xKey] * $this->yValues[$xKey];
+ }
+ $SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY);
+ if ($const) {
+ $SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX);
+ } else {
+ $SSsex += $this->xValues[$xKey] * $this->xValues[$xKey];
+ }
+ }
+
+ $this->SSResiduals = $SSres;
+ $this->DFResiduals = $this->valueCount - 1 - $const;
+
+ if ($this->DFResiduals == 0.0) {
+ $this->stdevOfResiduals = 0.0;
+ } else {
+ $this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals);
+ }
+ if (($SStot == 0.0) || ($SSres == $SStot)) {
+ $this->goodnessOfFit = 1;
+ } else {
+ $this->goodnessOfFit = 1 - ($SSres / $SStot);
+ }
+
+ $this->SSRegression = $this->goodnessOfFit * $SStot;
+ $this->covariance = $SScov / $this->valueCount;
+ $this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - $sumX ** 2) * ($this->valueCount * $sumY2 - $sumY ** 2));
+ $this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex);
+ $this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2));
+ if ($this->SSResiduals != 0.0) {
+ if ($this->DFResiduals == 0.0) {
+ $this->f = 0.0;
+ } else {
+ $this->f = $this->SSRegression / ($this->SSResiduals / $this->DFResiduals);
+ }
+ } else {
+ if ($this->DFResiduals == 0.0) {
+ $this->f = 0.0;
+ } else {
+ $this->f = $this->SSRegression / $this->DFResiduals;
+ }
+ }
+ }
+
+ /**
+ * @param float[] $yValues
+ * @param float[] $xValues
+ * @param bool $const
+ */
+ protected function leastSquareFit(array $yValues, array $xValues, $const): void
+ {
+ // calculate sums
+ $x_sum = array_sum($xValues);
+ $y_sum = array_sum($yValues);
+ $meanX = $x_sum / $this->valueCount;
+ $meanY = $y_sum / $this->valueCount;
+ $mBase = $mDivisor = $xx_sum = $xy_sum = $yy_sum = 0.0;
+ for ($i = 0; $i < $this->valueCount; ++$i) {
+ $xy_sum += $xValues[$i] * $yValues[$i];
+ $xx_sum += $xValues[$i] * $xValues[$i];
+ $yy_sum += $yValues[$i] * $yValues[$i];
+
+ if ($const) {
+ $mBase += ($xValues[$i] - $meanX) * ($yValues[$i] - $meanY);
+ $mDivisor += ($xValues[$i] - $meanX) * ($xValues[$i] - $meanX);
+ } else {
+ $mBase += $xValues[$i] * $yValues[$i];
+ $mDivisor += $xValues[$i] * $xValues[$i];
+ }
+ }
+
+ // calculate slope
+ $this->slope = $mBase / $mDivisor;
+
+ // calculate intersect
+ if ($const) {
+ $this->intersect = $meanY - ($this->slope * $meanX);
+ } else {
+ $this->intersect = 0;
+ }
+
+ $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, $meanX, $meanY, $const);
+ }
+
+ /**
+ * Define the regression.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ * @param bool $const
+ */
+ public function __construct($yValues, $xValues = [], $const = true)
+ {
+ // Calculate number of points
+ $nY = count($yValues);
+ $nX = count($xValues);
+
+ // Define X Values if necessary
+ if ($nX == 0) {
+ $xValues = range(1, $nY);
+ } elseif ($nY != $nX) {
+ // Ensure both arrays of points are the same size
+ $this->error = true;
+ }
+
+ $this->valueCount = $nY;
+ $this->xValues = $xValues;
+ $this->yValues = $yValues;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php
new file mode 100755
index 0000000..82866de
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
+
+class ExponentialBestFit extends BestFit
+{
+ /**
+ * Algorithm type to use for best-fit
+ * (Name of this Trend class).
+ *
+ * @var string
+ */
+ protected $bestFitType = 'exponential';
+
+ /**
+ * Return the Y-Value for a specified value of X.
+ *
+ * @param float $xValue X-Value
+ *
+ * @return float Y-Value
+ */
+ public function getValueOfYForX($xValue)
+ {
+ return $this->getIntersect() * $this->getSlope() ** ($xValue - $this->xOffset);
+ }
+
+ /**
+ * Return the X-Value for a specified value of Y.
+ *
+ * @param float $yValue Y-Value
+ *
+ * @return float X-Value
+ */
+ public function getValueOfXForY($yValue)
+ {
+ return log(($yValue + $this->yOffset) / $this->getIntersect()) / log($this->getSlope());
+ }
+
+ /**
+ * Return the Equation of the best-fit line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return string
+ */
+ public function getEquation($dp = 0)
+ {
+ $slope = $this->getSlope($dp);
+ $intersect = $this->getIntersect($dp);
+
+ return 'Y = ' . $intersect . ' * ' . $slope . '^X';
+ }
+
+ /**
+ * Return the Slope of the line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return float
+ */
+ public function getSlope($dp = 0)
+ {
+ if ($dp != 0) {
+ return round(exp($this->slope), $dp);
+ }
+
+ return exp($this->slope);
+ }
+
+ /**
+ * Return the Value of X where it intersects Y = 0.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return float
+ */
+ public function getIntersect($dp = 0)
+ {
+ if ($dp != 0) {
+ return round(exp($this->intersect), $dp);
+ }
+
+ return exp($this->intersect);
+ }
+
+ /**
+ * Execute the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ * @param bool $const
+ */
+ private function exponentialRegression($yValues, $xValues, $const): void
+ {
+ foreach ($yValues as &$value) {
+ if ($value < 0.0) {
+ $value = 0 - log(abs($value));
+ } elseif ($value > 0.0) {
+ $value = log($value);
+ }
+ }
+ unset($value);
+
+ $this->leastSquareFit($yValues, $xValues, $const);
+ }
+
+ /**
+ * Define the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ * @param bool $const
+ */
+ public function __construct($yValues, $xValues = [], $const = true)
+ {
+ parent::__construct($yValues, $xValues);
+
+ if (!$this->error) {
+ $this->exponentialRegression($yValues, $xValues, $const);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php
new file mode 100755
index 0000000..26a562c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
+
+class LinearBestFit extends BestFit
+{
+ /**
+ * Algorithm type to use for best-fit
+ * (Name of this Trend class).
+ *
+ * @var string
+ */
+ protected $bestFitType = 'linear';
+
+ /**
+ * Return the Y-Value for a specified value of X.
+ *
+ * @param float $xValue X-Value
+ *
+ * @return float Y-Value
+ */
+ public function getValueOfYForX($xValue)
+ {
+ return $this->getIntersect() + $this->getSlope() * $xValue;
+ }
+
+ /**
+ * Return the X-Value for a specified value of Y.
+ *
+ * @param float $yValue Y-Value
+ *
+ * @return float X-Value
+ */
+ public function getValueOfXForY($yValue)
+ {
+ return ($yValue - $this->getIntersect()) / $this->getSlope();
+ }
+
+ /**
+ * Return the Equation of the best-fit line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return string
+ */
+ public function getEquation($dp = 0)
+ {
+ $slope = $this->getSlope($dp);
+ $intersect = $this->getIntersect($dp);
+
+ return 'Y = ' . $intersect . ' + ' . $slope . ' * X';
+ }
+
+ /**
+ * Execute the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ * @param bool $const
+ */
+ private function linearRegression($yValues, $xValues, $const): void
+ {
+ $this->leastSquareFit($yValues, $xValues, $const);
+ }
+
+ /**
+ * Define the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ * @param bool $const
+ */
+ public function __construct($yValues, $xValues = [], $const = true)
+ {
+ parent::__construct($yValues, $xValues);
+
+ if (!$this->error) {
+ $this->linearRegression($yValues, $xValues, $const);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php
new file mode 100755
index 0000000..c469067
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
+
+class LogarithmicBestFit extends BestFit
+{
+ /**
+ * Algorithm type to use for best-fit
+ * (Name of this Trend class).
+ *
+ * @var string
+ */
+ protected $bestFitType = 'logarithmic';
+
+ /**
+ * Return the Y-Value for a specified value of X.
+ *
+ * @param float $xValue X-Value
+ *
+ * @return float Y-Value
+ */
+ public function getValueOfYForX($xValue)
+ {
+ return $this->getIntersect() + $this->getSlope() * log($xValue - $this->xOffset);
+ }
+
+ /**
+ * Return the X-Value for a specified value of Y.
+ *
+ * @param float $yValue Y-Value
+ *
+ * @return float X-Value
+ */
+ public function getValueOfXForY($yValue)
+ {
+ return exp(($yValue - $this->getIntersect()) / $this->getSlope());
+ }
+
+ /**
+ * Return the Equation of the best-fit line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return string
+ */
+ public function getEquation($dp = 0)
+ {
+ $slope = $this->getSlope($dp);
+ $intersect = $this->getIntersect($dp);
+
+ return 'Y = ' . $intersect . ' + ' . $slope . ' * log(X)';
+ }
+
+ /**
+ * Execute the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ * @param bool $const
+ */
+ private function logarithmicRegression($yValues, $xValues, $const): void
+ {
+ foreach ($xValues as &$value) {
+ if ($value < 0.0) {
+ $value = 0 - log(abs($value));
+ } elseif ($value > 0.0) {
+ $value = log($value);
+ }
+ }
+ unset($value);
+
+ $this->leastSquareFit($yValues, $xValues, $const);
+ }
+
+ /**
+ * Define the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ * @param bool $const
+ */
+ public function __construct($yValues, $xValues = [], $const = true)
+ {
+ parent::__construct($yValues, $xValues);
+
+ if (!$this->error) {
+ $this->logarithmicRegression($yValues, $xValues, $const);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php
new file mode 100755
index 0000000..d959edd
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php
@@ -0,0 +1,200 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
+
+use PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix;
+
+class PolynomialBestFit extends BestFit
+{
+ /**
+ * Algorithm type to use for best-fit
+ * (Name of this Trend class).
+ *
+ * @var string
+ */
+ protected $bestFitType = 'polynomial';
+
+ /**
+ * Polynomial order.
+ *
+ * @var int
+ */
+ protected $order = 0;
+
+ /**
+ * Return the order of this polynomial.
+ *
+ * @return int
+ */
+ public function getOrder()
+ {
+ return $this->order;
+ }
+
+ /**
+ * Return the Y-Value for a specified value of X.
+ *
+ * @param float $xValue X-Value
+ *
+ * @return float Y-Value
+ */
+ public function getValueOfYForX($xValue)
+ {
+ $retVal = $this->getIntersect();
+ $slope = $this->getSlope();
+ foreach ($slope as $key => $value) {
+ if ($value != 0.0) {
+ $retVal += $value * $xValue ** ($key + 1);
+ }
+ }
+
+ return $retVal;
+ }
+
+ /**
+ * Return the X-Value for a specified value of Y.
+ *
+ * @param float $yValue Y-Value
+ *
+ * @return float X-Value
+ */
+ public function getValueOfXForY($yValue)
+ {
+ return ($yValue - $this->getIntersect()) / $this->getSlope();
+ }
+
+ /**
+ * Return the Equation of the best-fit line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return string
+ */
+ public function getEquation($dp = 0)
+ {
+ $slope = $this->getSlope($dp);
+ $intersect = $this->getIntersect($dp);
+
+ $equation = 'Y = ' . $intersect;
+ foreach ($slope as $key => $value) {
+ if ($value != 0.0) {
+ $equation .= ' + ' . $value . ' * X';
+ if ($key > 0) {
+ $equation .= '^' . ($key + 1);
+ }
+ }
+ }
+
+ return $equation;
+ }
+
+ /**
+ * Return the Slope of the line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return string
+ */
+ public function getSlope($dp = 0)
+ {
+ if ($dp != 0) {
+ $coefficients = [];
+ foreach ($this->slope as $coefficient) {
+ $coefficients[] = round($coefficient, $dp);
+ }
+
+ return $coefficients;
+ }
+
+ return $this->slope;
+ }
+
+ public function getCoefficients($dp = 0)
+ {
+ return array_merge([$this->getIntersect($dp)], $this->getSlope($dp));
+ }
+
+ /**
+ * Execute the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param int $order Order of Polynomial for this regression
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ */
+ private function polynomialRegression($order, $yValues, $xValues): void
+ {
+ // calculate sums
+ $x_sum = array_sum($xValues);
+ $y_sum = array_sum($yValues);
+ $xx_sum = $xy_sum = $yy_sum = 0;
+ for ($i = 0; $i < $this->valueCount; ++$i) {
+ $xy_sum += $xValues[$i] * $yValues[$i];
+ $xx_sum += $xValues[$i] * $xValues[$i];
+ $yy_sum += $yValues[$i] * $yValues[$i];
+ }
+ /*
+ * This routine uses logic from the PHP port of polyfit version 0.1
+ * written by Michael Bommarito and Paul Meagher
+ *
+ * The function fits a polynomial function of order $order through
+ * a series of x-y data points using least squares.
+ *
+ */
+ $A = [];
+ $B = [];
+ for ($i = 0; $i < $this->valueCount; ++$i) {
+ for ($j = 0; $j <= $order; ++$j) {
+ $A[$i][$j] = $xValues[$i] ** $j;
+ }
+ }
+ for ($i = 0; $i < $this->valueCount; ++$i) {
+ $B[$i] = [$yValues[$i]];
+ }
+ $matrixA = new Matrix($A);
+ $matrixB = new Matrix($B);
+ $C = $matrixA->solve($matrixB);
+
+ $coefficients = [];
+ for ($i = 0; $i < $C->getRowDimension(); ++$i) {
+ $r = $C->get($i, 0);
+ if (abs($r) <= 10 ** (-9)) {
+ $r = 0;
+ }
+ $coefficients[] = $r;
+ }
+
+ $this->intersect = array_shift($coefficients);
+ $this->slope = $coefficients;
+
+ $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, 0, 0, 0);
+ foreach ($this->xValues as $xKey => $xValue) {
+ $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
+ }
+ }
+
+ /**
+ * Define the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param int $order Order of Polynomial for this regression
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ * @param bool $const
+ */
+ public function __construct($order, $yValues, $xValues = [], $const = true)
+ {
+ parent::__construct($yValues, $xValues);
+
+ if (!$this->error) {
+ if ($order < $this->valueCount) {
+ $this->bestFitType .= '_' . $order;
+ $this->order = $order;
+ $this->polynomialRegression($order, $yValues, $xValues);
+ if (($this->getGoodnessOfFit() < 0.0) || ($this->getGoodnessOfFit() > 1.0)) {
+ $this->error = true;
+ }
+ } else {
+ $this->error = true;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php
new file mode 100755
index 0000000..c53eab6
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
+
+class PowerBestFit extends BestFit
+{
+ /**
+ * Algorithm type to use for best-fit
+ * (Name of this Trend class).
+ *
+ * @var string
+ */
+ protected $bestFitType = 'power';
+
+ /**
+ * Return the Y-Value for a specified value of X.
+ *
+ * @param float $xValue X-Value
+ *
+ * @return float Y-Value
+ */
+ public function getValueOfYForX($xValue)
+ {
+ return $this->getIntersect() * ($xValue - $this->xOffset) ** $this->getSlope();
+ }
+
+ /**
+ * Return the X-Value for a specified value of Y.
+ *
+ * @param float $yValue Y-Value
+ *
+ * @return float X-Value
+ */
+ public function getValueOfXForY($yValue)
+ {
+ return (($yValue + $this->yOffset) / $this->getIntersect()) ** (1 / $this->getSlope());
+ }
+
+ /**
+ * Return the Equation of the best-fit line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return string
+ */
+ public function getEquation($dp = 0)
+ {
+ $slope = $this->getSlope($dp);
+ $intersect = $this->getIntersect($dp);
+
+ return 'Y = ' . $intersect . ' * X^' . $slope;
+ }
+
+ /**
+ * Return the Value of X where it intersects Y = 0.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return float
+ */
+ public function getIntersect($dp = 0)
+ {
+ if ($dp != 0) {
+ return round(exp($this->intersect), $dp);
+ }
+
+ return exp($this->intersect);
+ }
+
+ /**
+ * Execute the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ * @param bool $const
+ */
+ private function powerRegression($yValues, $xValues, $const): void
+ {
+ foreach ($xValues as &$value) {
+ if ($value < 0.0) {
+ $value = 0 - log(abs($value));
+ } elseif ($value > 0.0) {
+ $value = log($value);
+ }
+ }
+ unset($value);
+ foreach ($yValues as &$value) {
+ if ($value < 0.0) {
+ $value = 0 - log(abs($value));
+ } elseif ($value > 0.0) {
+ $value = log($value);
+ }
+ }
+ unset($value);
+
+ $this->leastSquareFit($yValues, $xValues, $const);
+ }
+
+ /**
+ * Define the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ * @param bool $const
+ */
+ public function __construct($yValues, $xValues = [], $const = true)
+ {
+ parent::__construct($yValues, $xValues);
+
+ if (!$this->error) {
+ $this->powerRegression($yValues, $xValues, $const);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php
new file mode 100755
index 0000000..1b7b390
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
+
+class Trend
+{
+ const TREND_LINEAR = 'Linear';
+ const TREND_LOGARITHMIC = 'Logarithmic';
+ const TREND_EXPONENTIAL = 'Exponential';
+ const TREND_POWER = 'Power';
+ const TREND_POLYNOMIAL_2 = 'Polynomial_2';
+ const TREND_POLYNOMIAL_3 = 'Polynomial_3';
+ const TREND_POLYNOMIAL_4 = 'Polynomial_4';
+ const TREND_POLYNOMIAL_5 = 'Polynomial_5';
+ const TREND_POLYNOMIAL_6 = 'Polynomial_6';
+ const TREND_BEST_FIT = 'Bestfit';
+ const TREND_BEST_FIT_NO_POLY = 'Bestfit_no_Polynomials';
+
+ /**
+ * Names of the best-fit Trend analysis methods.
+ *
+ * @var string[]
+ */
+ private static $trendTypes = [
+ self::TREND_LINEAR,
+ self::TREND_LOGARITHMIC,
+ self::TREND_EXPONENTIAL,
+ self::TREND_POWER,
+ ];
+
+ /**
+ * Names of the best-fit Trend polynomial orders.
+ *
+ * @var string[]
+ */
+ private static $trendTypePolynomialOrders = [
+ self::TREND_POLYNOMIAL_2,
+ self::TREND_POLYNOMIAL_3,
+ self::TREND_POLYNOMIAL_4,
+ self::TREND_POLYNOMIAL_5,
+ self::TREND_POLYNOMIAL_6,
+ ];
+
+ /**
+ * Cached results for each method when trying to identify which provides the best fit.
+ *
+ * @var bestFit[]
+ */
+ private static $trendCache = [];
+
+ public static function calculate($trendType = self::TREND_BEST_FIT, $yValues = [], $xValues = [], $const = true)
+ {
+ // Calculate number of points in each dataset
+ $nY = count($yValues);
+ $nX = count($xValues);
+
+ // Define X Values if necessary
+ if ($nX == 0) {
+ $xValues = range(1, $nY);
+ $nX = $nY;
+ } elseif ($nY != $nX) {
+ // Ensure both arrays of points are the same size
+ trigger_error('Trend(): Number of elements in coordinate arrays do not match.', E_USER_ERROR);
+ }
+
+ $key = md5($trendType . $const . serialize($yValues) . serialize($xValues));
+ // Determine which Trend method has been requested
+ switch ($trendType) {
+ // Instantiate and return the class for the requested Trend method
+ case self::TREND_LINEAR:
+ case self::TREND_LOGARITHMIC:
+ case self::TREND_EXPONENTIAL:
+ case self::TREND_POWER:
+ if (!isset(self::$trendCache[$key])) {
+ $className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit';
+ self::$trendCache[$key] = new $className($yValues, $xValues, $const);
+ }
+
+ return self::$trendCache[$key];
+ case self::TREND_POLYNOMIAL_2:
+ case self::TREND_POLYNOMIAL_3:
+ case self::TREND_POLYNOMIAL_4:
+ case self::TREND_POLYNOMIAL_5:
+ case self::TREND_POLYNOMIAL_6:
+ if (!isset(self::$trendCache[$key])) {
+ $order = substr($trendType, -1);
+ self::$trendCache[$key] = new PolynomialBestFit($order, $yValues, $xValues, $const);
+ }
+
+ return self::$trendCache[$key];
+ case self::TREND_BEST_FIT:
+ case self::TREND_BEST_FIT_NO_POLY:
+ // If the request is to determine the best fit regression, then we test each Trend line in turn
+ // Start by generating an instance of each available Trend method
+ foreach (self::$trendTypes as $trendMethod) {
+ $className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit';
+ $bestFit[$trendMethod] = new $className($yValues, $xValues, $const);
+ $bestFitValue[$trendMethod] = $bestFit[$trendMethod]->getGoodnessOfFit();
+ }
+ if ($trendType != self::TREND_BEST_FIT_NO_POLY) {
+ foreach (self::$trendTypePolynomialOrders as $trendMethod) {
+ $order = substr($trendMethod, -1);
+ $bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues, $const);
+ if ($bestFit[$trendMethod]->getError()) {
+ unset($bestFit[$trendMethod]);
+ } else {
+ $bestFitValue[$trendMethod] = $bestFit[$trendMethod]->getGoodnessOfFit();
+ }
+ }
+ }
+ // Determine which of our Trend lines is the best fit, and then we return the instance of that Trend class
+ arsort($bestFitValue);
+ $bestFitType = key($bestFitValue);
+
+ return $bestFit[$bestFitType];
+ default:
+ return false;
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php
new file mode 100755
index 0000000..4f7a6a0
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared;
+
+class XMLWriter extends \XMLWriter
+{
+ public static $debugEnabled = false;
+
+ /** Temporary storage method */
+ const STORAGE_MEMORY = 1;
+ const STORAGE_DISK = 2;
+
+ /**
+ * Temporary filename.
+ *
+ * @var string
+ */
+ private $tempFileName = '';
+
+ /**
+ * Create a new XMLWriter instance.
+ *
+ * @param int $pTemporaryStorage Temporary storage location
+ * @param string $pTemporaryStorageFolder Temporary storage folder
+ */
+ public function __construct($pTemporaryStorage = self::STORAGE_MEMORY, $pTemporaryStorageFolder = null)
+ {
+ // Open temporary storage
+ if ($pTemporaryStorage == self::STORAGE_MEMORY) {
+ $this->openMemory();
+ } else {
+ // Create temporary filename
+ if ($pTemporaryStorageFolder === null) {
+ $pTemporaryStorageFolder = File::sysGetTempDir();
+ }
+ $this->tempFileName = @tempnam($pTemporaryStorageFolder, 'xml');
+
+ // Open storage
+ if ($this->openUri($this->tempFileName) === false) {
+ // Fallback to memory...
+ $this->openMemory();
+ }
+ }
+
+ // Set default values
+ if (self::$debugEnabled) {
+ $this->setIndent(true);
+ }
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ // Unlink temporary files
+ if ($this->tempFileName != '') {
+ @unlink($this->tempFileName);
+ }
+ }
+
+ /**
+ * Get written data.
+ *
+ * @return string
+ */
+ public function getData()
+ {
+ if ($this->tempFileName == '') {
+ return $this->outputMemory(true);
+ }
+ $this->flush();
+
+ return file_get_contents($this->tempFileName);
+ }
+
+ /**
+ * Wrapper method for writeRaw.
+ *
+ * @param string|string[] $text
+ *
+ * @return bool
+ */
+ public function writeRawData($text)
+ {
+ if (is_array($text)) {
+ $text = implode("\n", $text);
+ }
+
+ return $this->writeRaw(htmlspecialchars($text));
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php
new file mode 100755
index 0000000..c9eaf37
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php
@@ -0,0 +1,279 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Shared;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class Xls
+{
+ /**
+ * Get the width of a column in pixels. We use the relationship y = ceil(7x) where
+ * x is the width in intrinsic Excel units (measuring width in number of normal characters)
+ * This holds for Arial 10.
+ *
+ * @param Worksheet $sheet The sheet
+ * @param string $col The column
+ *
+ * @return int The width in pixels
+ */
+ public static function sizeCol($sheet, $col = 'A')
+ {
+ // default font of the workbook
+ $font = $sheet->getParent()->getDefaultStyle()->getFont();
+
+ $columnDimensions = $sheet->getColumnDimensions();
+
+ // first find the true column width in pixels (uncollapsed and unhidden)
+ if (isset($columnDimensions[$col]) && $columnDimensions[$col]->getWidth() != -1) {
+ // then we have column dimension with explicit width
+ $columnDimension = $columnDimensions[$col];
+ $width = $columnDimension->getWidth();
+ $pixelWidth = Drawing::cellDimensionToPixels($width, $font);
+ } elseif ($sheet->getDefaultColumnDimension()->getWidth() != -1) {
+ // then we have default column dimension with explicit width
+ $defaultColumnDimension = $sheet->getDefaultColumnDimension();
+ $width = $defaultColumnDimension->getWidth();
+ $pixelWidth = Drawing::cellDimensionToPixels($width, $font);
+ } else {
+ // we don't even have any default column dimension. Width depends on default font
+ $pixelWidth = Font::getDefaultColumnWidthByFont($font, true);
+ }
+
+ // now find the effective column width in pixels
+ if (isset($columnDimensions[$col]) && !$columnDimensions[$col]->getVisible()) {
+ $effectivePixelWidth = 0;
+ } else {
+ $effectivePixelWidth = $pixelWidth;
+ }
+
+ return $effectivePixelWidth;
+ }
+
+ /**
+ * Convert the height of a cell from user's units to pixels. By interpolation
+ * the relationship is: y = 4/3x. If the height hasn't been set by the user we
+ * use the default value. If the row is hidden we use a value of zero.
+ *
+ * @param Worksheet $sheet The sheet
+ * @param int $row The row index (1-based)
+ *
+ * @return int The width in pixels
+ */
+ public static function sizeRow($sheet, $row = 1)
+ {
+ // default font of the workbook
+ $font = $sheet->getParent()->getDefaultStyle()->getFont();
+
+ $rowDimensions = $sheet->getRowDimensions();
+
+ // first find the true row height in pixels (uncollapsed and unhidden)
+ if (isset($rowDimensions[$row]) && $rowDimensions[$row]->getRowHeight() != -1) {
+ // then we have a row dimension
+ $rowDimension = $rowDimensions[$row];
+ $rowHeight = $rowDimension->getRowHeight();
+ $pixelRowHeight = (int) ceil(4 * $rowHeight / 3); // here we assume Arial 10
+ } elseif ($sheet->getDefaultRowDimension()->getRowHeight() != -1) {
+ // then we have a default row dimension with explicit height
+ $defaultRowDimension = $sheet->getDefaultRowDimension();
+ $rowHeight = $defaultRowDimension->getRowHeight();
+ $pixelRowHeight = Drawing::pointsToPixels($rowHeight);
+ } else {
+ // we don't even have any default row dimension. Height depends on default font
+ $pointRowHeight = Font::getDefaultRowHeightByFont($font);
+ $pixelRowHeight = Font::fontSizeToPixels($pointRowHeight);
+ }
+
+ // now find the effective row height in pixels
+ if (isset($rowDimensions[$row]) && !$rowDimensions[$row]->getVisible()) {
+ $effectivePixelRowHeight = 0;
+ } else {
+ $effectivePixelRowHeight = $pixelRowHeight;
+ }
+
+ return $effectivePixelRowHeight;
+ }
+
+ /**
+ * Get the horizontal distance in pixels between two anchors
+ * The distanceX is found as sum of all the spanning columns widths minus correction for the two offsets.
+ *
+ * @param string $startColumn
+ * @param int $startOffsetX Offset within start cell measured in 1/1024 of the cell width
+ * @param string $endColumn
+ * @param int $endOffsetX Offset within end cell measured in 1/1024 of the cell width
+ *
+ * @return int Horizontal measured in pixels
+ */
+ public static function getDistanceX(Worksheet $sheet, $startColumn = 'A', $startOffsetX = 0, $endColumn = 'A', $endOffsetX = 0)
+ {
+ $distanceX = 0;
+
+ // add the widths of the spanning columns
+ $startColumnIndex = Coordinate::columnIndexFromString($startColumn);
+ $endColumnIndex = Coordinate::columnIndexFromString($endColumn);
+ for ($i = $startColumnIndex; $i <= $endColumnIndex; ++$i) {
+ $distanceX += self::sizeCol($sheet, Coordinate::stringFromColumnIndex($i));
+ }
+
+ // correct for offsetX in startcell
+ $distanceX -= (int) floor(self::sizeCol($sheet, $startColumn) * $startOffsetX / 1024);
+
+ // correct for offsetX in endcell
+ $distanceX -= (int) floor(self::sizeCol($sheet, $endColumn) * (1 - $endOffsetX / 1024));
+
+ return $distanceX;
+ }
+
+ /**
+ * Get the vertical distance in pixels between two anchors
+ * The distanceY is found as sum of all the spanning rows minus two offsets.
+ *
+ * @param int $startRow (1-based)
+ * @param int $startOffsetY Offset within start cell measured in 1/256 of the cell height
+ * @param int $endRow (1-based)
+ * @param int $endOffsetY Offset within end cell measured in 1/256 of the cell height
+ *
+ * @return int Vertical distance measured in pixels
+ */
+ public static function getDistanceY(Worksheet $sheet, $startRow = 1, $startOffsetY = 0, $endRow = 1, $endOffsetY = 0)
+ {
+ $distanceY = 0;
+
+ // add the widths of the spanning rows
+ for ($row = $startRow; $row <= $endRow; ++$row) {
+ $distanceY += self::sizeRow($sheet, $row);
+ }
+
+ // correct for offsetX in startcell
+ $distanceY -= (int) floor(self::sizeRow($sheet, $startRow) * $startOffsetY / 256);
+
+ // correct for offsetX in endcell
+ $distanceY -= (int) floor(self::sizeRow($sheet, $endRow) * (1 - $endOffsetY / 256));
+
+ return $distanceY;
+ }
+
+ /**
+ * Convert 1-cell anchor coordinates to 2-cell anchor coordinates
+ * This function is ported from PEAR Spreadsheet_Writer_Excel with small modifications.
+ *
+ * Calculate the vertices that define the position of the image as required by
+ * the OBJ record.
+ *
+ * +------------+------------+
+ * | A | B |
+ * +-----+------------+------------+
+ * | |(x1,y1) | |
+ * | 1 |(A1)._______|______ |
+ * | | | | |
+ * | | | | |
+ * +-----+----| BITMAP |-----+
+ * | | | | |
+ * | 2 | |______________. |
+ * | | | (B2)|
+ * | | | (x2,y2)|
+ * +---- +------------+------------+
+ *
+ * Example of a bitmap that covers some of the area from cell A1 to cell B2.
+ *
+ * Based on the width and height of the bitmap we need to calculate 8 vars:
+ * $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
+ * The width and height of the cells are also variable and have to be taken into
+ * account.
+ * The values of $col_start and $row_start are passed in from the calling
+ * function. The values of $col_end and $row_end are calculated by subtracting
+ * the width and height of the bitmap from the width and height of the
+ * underlying cells.
+ * The vertices are expressed as a percentage of the underlying cell width as
+ * follows (rhs values are in pixels):
+ *
+ * x1 = X / W *1024
+ * y1 = Y / H *256
+ * x2 = (X-1) / W *1024
+ * y2 = (Y-1) / H *256
+ *
+ * Where: X is distance from the left side of the underlying cell
+ * Y is distance from the top of the underlying cell
+ * W is the width of the cell
+ * H is the height of the cell
+ *
+ * @param Worksheet $sheet
+ * @param string $coordinates E.g. 'A1'
+ * @param int $offsetX Horizontal offset in pixels
+ * @param int $offsetY Vertical offset in pixels
+ * @param int $width Width in pixels
+ * @param int $height Height in pixels
+ *
+ * @return array
+ */
+ public static function oneAnchor2twoAnchor($sheet, $coordinates, $offsetX, $offsetY, $width, $height)
+ {
+ [$column, $row] = Coordinate::coordinateFromString($coordinates);
+ $col_start = Coordinate::columnIndexFromString($column);
+ $row_start = $row - 1;
+
+ $x1 = $offsetX;
+ $y1 = $offsetY;
+
+ // Initialise end cell to the same as the start cell
+ $col_end = $col_start; // Col containing lower right corner of object
+ $row_end = $row_start; // Row containing bottom right corner of object
+
+ // Zero the specified offset if greater than the cell dimensions
+ if ($x1 >= self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_start))) {
+ $x1 = 0;
+ }
+ if ($y1 >= self::sizeRow($sheet, $row_start + 1)) {
+ $y1 = 0;
+ }
+
+ $width = $width + $x1 - 1;
+ $height = $height + $y1 - 1;
+
+ // Subtract the underlying cell widths to find the end cell of the image
+ while ($width >= self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end))) {
+ $width -= self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end));
+ ++$col_end;
+ }
+
+ // Subtract the underlying cell heights to find the end cell of the image
+ while ($height >= self::sizeRow($sheet, $row_end + 1)) {
+ $height -= self::sizeRow($sheet, $row_end + 1);
+ ++$row_end;
+ }
+
+ // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
+ // with zero height or width.
+ if (self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_start)) == 0) {
+ return;
+ }
+ if (self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end)) == 0) {
+ return;
+ }
+ if (self::sizeRow($sheet, $row_start + 1) == 0) {
+ return;
+ }
+ if (self::sizeRow($sheet, $row_end + 1) == 0) {
+ return;
+ }
+
+ // Convert the pixel values to the percentage value expected by Excel
+ $x1 = $x1 / self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_start)) * 1024;
+ $y1 = $y1 / self::sizeRow($sheet, $row_start + 1) * 256;
+ $x2 = ($width + 1) / self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end)) * 1024; // Distance to right side of object
+ $y2 = ($height + 1) / self::sizeRow($sheet, $row_end + 1) * 256; // Distance to bottom of object
+
+ $startCoordinates = Coordinate::stringFromColumnIndex($col_start) . ($row_start + 1);
+ $endCoordinates = Coordinate::stringFromColumnIndex($col_end) . ($row_end + 1);
+
+ return [
+ 'startCoordinates' => $startCoordinates,
+ 'startOffsetX' => $x1,
+ 'startOffsetY' => $y1,
+ 'endCoordinates' => $endCoordinates,
+ 'endOffsetX' => $x2,
+ 'endOffsetY' => $y2,
+ ];
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php
new file mode 100755
index 0000000..322d5f7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php
@@ -0,0 +1,1443 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+use PhpOffice\PhpSpreadsheet\Worksheet\Iterator;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class Spreadsheet
+{
+ // Allowable values for workbook window visilbity
+ const VISIBILITY_VISIBLE = 'visible';
+ const VISIBILITY_HIDDEN = 'hidden';
+ const VISIBILITY_VERY_HIDDEN = 'veryHidden';
+
+ private static $workbookViewVisibilityValues = [
+ self::VISIBILITY_VISIBLE,
+ self::VISIBILITY_HIDDEN,
+ self::VISIBILITY_VERY_HIDDEN,
+ ];
+
+ /**
+ * Unique ID.
+ *
+ * @var string
+ */
+ private $uniqueID;
+
+ /**
+ * Document properties.
+ *
+ * @var Document\Properties
+ */
+ private $properties;
+
+ /**
+ * Document security.
+ *
+ * @var Document\Security
+ */
+ private $security;
+
+ /**
+ * Collection of Worksheet objects.
+ *
+ * @var Worksheet[]
+ */
+ private $workSheetCollection = [];
+
+ /**
+ * Calculation Engine.
+ *
+ * @var Calculation
+ */
+ private $calculationEngine;
+
+ /**
+ * Active sheet index.
+ *
+ * @var int
+ */
+ private $activeSheetIndex = 0;
+
+ /**
+ * Named ranges.
+ *
+ * @var NamedRange[]
+ */
+ private $namedRanges = [];
+
+ /**
+ * CellXf supervisor.
+ *
+ * @var Style
+ */
+ private $cellXfSupervisor;
+
+ /**
+ * CellXf collection.
+ *
+ * @var Style[]
+ */
+ private $cellXfCollection = [];
+
+ /**
+ * CellStyleXf collection.
+ *
+ * @var Style[]
+ */
+ private $cellStyleXfCollection = [];
+
+ /**
+ * hasMacros : this workbook have macros ?
+ *
+ * @var bool
+ */
+ private $hasMacros = false;
+
+ /**
+ * macrosCode : all macros code as binary data (the vbaProject.bin file, this include form, code, etc.), null if no macro.
+ *
+ * @var string
+ */
+ private $macrosCode;
+
+ /**
+ * macrosCertificate : if macros are signed, contains binary data vbaProjectSignature.bin file, null if not signed.
+ *
+ * @var string
+ */
+ private $macrosCertificate;
+
+ /**
+ * ribbonXMLData : null if workbook is'nt Excel 2007 or not contain a customized UI.
+ *
+ * @var null|string
+ */
+ private $ribbonXMLData;
+
+ /**
+ * ribbonBinObjects : null if workbook is'nt Excel 2007 or not contain embedded objects (picture(s)) for Ribbon Elements
+ * ignored if $ribbonXMLData is null.
+ *
+ * @var null|array
+ */
+ private $ribbonBinObjects;
+
+ /**
+ * List of unparsed loaded data for export to same format with better compatibility.
+ * It has to be minimized when the library start to support currently unparsed data.
+ *
+ * @var array
+ */
+ private $unparsedLoadedData = [];
+
+ /**
+ * Controls visibility of the horizonal scroll bar in the application.
+ *
+ * @var bool
+ */
+ private $showHorizontalScroll = true;
+
+ /**
+ * Controls visibility of the horizonal scroll bar in the application.
+ *
+ * @var bool
+ */
+ private $showVerticalScroll = true;
+
+ /**
+ * Controls visibility of the sheet tabs in the application.
+ *
+ * @var bool
+ */
+ private $showSheetTabs = true;
+
+ /**
+ * Specifies a boolean value that indicates whether the workbook window
+ * is minimized.
+ *
+ * @var bool
+ */
+ private $minimized = false;
+
+ /**
+ * Specifies a boolean value that indicates whether to group dates
+ * when presenting the user with filtering optiomd in the user
+ * interface.
+ *
+ * @var bool
+ */
+ private $autoFilterDateGrouping = true;
+
+ /**
+ * Specifies the index to the first sheet in the book view.
+ *
+ * @var int
+ */
+ private $firstSheetIndex = 0;
+
+ /**
+ * Specifies the visible status of the workbook.
+ *
+ * @var string
+ */
+ private $visibility = self::VISIBILITY_VISIBLE;
+
+ /**
+ * Specifies the ratio between the workbook tabs bar and the horizontal
+ * scroll bar. TabRatio is assumed to be out of 1000 of the horizontal
+ * window width.
+ *
+ * @var int
+ */
+ private $tabRatio = 600;
+
+ /**
+ * The workbook has macros ?
+ *
+ * @return bool
+ */
+ public function hasMacros()
+ {
+ return $this->hasMacros;
+ }
+
+ /**
+ * Define if a workbook has macros.
+ *
+ * @param bool $hasMacros true|false
+ */
+ public function setHasMacros($hasMacros): void
+ {
+ $this->hasMacros = (bool) $hasMacros;
+ }
+
+ /**
+ * Set the macros code.
+ *
+ * @param string $macroCode string|null
+ */
+ public function setMacrosCode($macroCode): void
+ {
+ $this->macrosCode = $macroCode;
+ $this->setHasMacros($macroCode !== null);
+ }
+
+ /**
+ * Return the macros code.
+ *
+ * @return null|string
+ */
+ public function getMacrosCode()
+ {
+ return $this->macrosCode;
+ }
+
+ /**
+ * Set the macros certificate.
+ *
+ * @param null|string $certificate
+ */
+ public function setMacrosCertificate($certificate): void
+ {
+ $this->macrosCertificate = $certificate;
+ }
+
+ /**
+ * Is the project signed ?
+ *
+ * @return bool true|false
+ */
+ public function hasMacrosCertificate()
+ {
+ return $this->macrosCertificate !== null;
+ }
+
+ /**
+ * Return the macros certificate.
+ *
+ * @return null|string
+ */
+ public function getMacrosCertificate()
+ {
+ return $this->macrosCertificate;
+ }
+
+ /**
+ * Remove all macros, certificate from spreadsheet.
+ */
+ public function discardMacros(): void
+ {
+ $this->hasMacros = false;
+ $this->macrosCode = null;
+ $this->macrosCertificate = null;
+ }
+
+ /**
+ * set ribbon XML data.
+ *
+ * @param null|mixed $target
+ * @param null|mixed $xmlData
+ */
+ public function setRibbonXMLData($target, $xmlData): void
+ {
+ if ($target !== null && $xmlData !== null) {
+ $this->ribbonXMLData = ['target' => $target, 'data' => $xmlData];
+ } else {
+ $this->ribbonXMLData = null;
+ }
+ }
+
+ /**
+ * retrieve ribbon XML Data.
+ *
+ * return string|null|array
+ *
+ * @param string $what
+ *
+ * @return string
+ */
+ public function getRibbonXMLData($what = 'all') //we need some constants here...
+ {
+ $returnData = null;
+ $what = strtolower($what);
+ switch ($what) {
+ case 'all':
+ $returnData = $this->ribbonXMLData;
+
+ break;
+ case 'target':
+ case 'data':
+ if (is_array($this->ribbonXMLData) && isset($this->ribbonXMLData[$what])) {
+ $returnData = $this->ribbonXMLData[$what];
+ }
+
+ break;
+ }
+
+ return $returnData;
+ }
+
+ /**
+ * store binaries ribbon objects (pictures).
+ *
+ * @param null|mixed $BinObjectsNames
+ * @param null|mixed $BinObjectsData
+ */
+ public function setRibbonBinObjects($BinObjectsNames, $BinObjectsData): void
+ {
+ if ($BinObjectsNames !== null && $BinObjectsData !== null) {
+ $this->ribbonBinObjects = ['names' => $BinObjectsNames, 'data' => $BinObjectsData];
+ } else {
+ $this->ribbonBinObjects = null;
+ }
+ }
+
+ /**
+ * List of unparsed loaded data for export to same format with better compatibility.
+ * It has to be minimized when the library start to support currently unparsed data.
+ *
+ * @internal
+ *
+ * @return array
+ */
+ public function getUnparsedLoadedData()
+ {
+ return $this->unparsedLoadedData;
+ }
+
+ /**
+ * List of unparsed loaded data for export to same format with better compatibility.
+ * It has to be minimized when the library start to support currently unparsed data.
+ *
+ * @internal
+ */
+ public function setUnparsedLoadedData(array $unparsedLoadedData): void
+ {
+ $this->unparsedLoadedData = $unparsedLoadedData;
+ }
+
+ /**
+ * return the extension of a filename. Internal use for a array_map callback (php<5.3 don't like lambda function).
+ *
+ * @param mixed $path
+ *
+ * @return string
+ */
+ private function getExtensionOnly($path)
+ {
+ return pathinfo($path, PATHINFO_EXTENSION);
+ }
+
+ /**
+ * retrieve Binaries Ribbon Objects.
+ *
+ * @param string $what
+ *
+ * @return null|array
+ */
+ public function getRibbonBinObjects($what = 'all')
+ {
+ $ReturnData = null;
+ $what = strtolower($what);
+ switch ($what) {
+ case 'all':
+ return $this->ribbonBinObjects;
+
+ break;
+ case 'names':
+ case 'data':
+ if (is_array($this->ribbonBinObjects) && isset($this->ribbonBinObjects[$what])) {
+ $ReturnData = $this->ribbonBinObjects[$what];
+ }
+
+ break;
+ case 'types':
+ if (is_array($this->ribbonBinObjects) &&
+ isset($this->ribbonBinObjects['data']) && is_array($this->ribbonBinObjects['data'])) {
+ $tmpTypes = array_keys($this->ribbonBinObjects['data']);
+ $ReturnData = array_unique(array_map([$this, 'getExtensionOnly'], $tmpTypes));
+ } else {
+ $ReturnData = []; // the caller want an array... not null if empty
+ }
+
+ break;
+ }
+
+ return $ReturnData;
+ }
+
+ /**
+ * This workbook have a custom UI ?
+ *
+ * @return bool
+ */
+ public function hasRibbon()
+ {
+ return $this->ribbonXMLData !== null;
+ }
+
+ /**
+ * This workbook have additionnal object for the ribbon ?
+ *
+ * @return bool
+ */
+ public function hasRibbonBinObjects()
+ {
+ return $this->ribbonBinObjects !== null;
+ }
+
+ /**
+ * Check if a sheet with a specified code name already exists.
+ *
+ * @param string $pSheetCodeName Name of the worksheet to check
+ *
+ * @return bool
+ */
+ public function sheetCodeNameExists($pSheetCodeName)
+ {
+ return $this->getSheetByCodeName($pSheetCodeName) !== null;
+ }
+
+ /**
+ * Get sheet by code name. Warning : sheet don't have always a code name !
+ *
+ * @param string $pName Sheet name
+ *
+ * @return Worksheet
+ */
+ public function getSheetByCodeName($pName)
+ {
+ $worksheetCount = count($this->workSheetCollection);
+ for ($i = 0; $i < $worksheetCount; ++$i) {
+ if ($this->workSheetCollection[$i]->getCodeName() == $pName) {
+ return $this->workSheetCollection[$i];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Create a new PhpSpreadsheet with one Worksheet.
+ */
+ public function __construct()
+ {
+ $this->uniqueID = uniqid('', true);
+ $this->calculationEngine = new Calculation($this);
+
+ // Initialise worksheet collection and add one worksheet
+ $this->workSheetCollection = [];
+ $this->workSheetCollection[] = new Worksheet($this);
+ $this->activeSheetIndex = 0;
+
+ // Create document properties
+ $this->properties = new Document\Properties();
+
+ // Create document security
+ $this->security = new Document\Security();
+
+ // Set named ranges
+ $this->namedRanges = [];
+
+ // Create the cellXf supervisor
+ $this->cellXfSupervisor = new Style(true);
+ $this->cellXfSupervisor->bindParent($this);
+
+ // Create the default style
+ $this->addCellXf(new Style());
+ $this->addCellStyleXf(new Style());
+ }
+
+ /**
+ * Code to execute when this worksheet is unset().
+ */
+ public function __destruct()
+ {
+ $this->calculationEngine = null;
+ $this->disconnectWorksheets();
+ }
+
+ /**
+ * Disconnect all worksheets from this PhpSpreadsheet workbook object,
+ * typically so that the PhpSpreadsheet object can be unset.
+ */
+ public function disconnectWorksheets(): void
+ {
+ $worksheet = null;
+ foreach ($this->workSheetCollection as $k => &$worksheet) {
+ $worksheet->disconnectCells();
+ $this->workSheetCollection[$k] = null;
+ }
+ unset($worksheet);
+ $this->workSheetCollection = [];
+ }
+
+ /**
+ * Return the calculation engine for this worksheet.
+ *
+ * @return Calculation
+ */
+ public function getCalculationEngine()
+ {
+ return $this->calculationEngine;
+ }
+
+ /**
+ * Get properties.
+ *
+ * @return Document\Properties
+ */
+ public function getProperties()
+ {
+ return $this->properties;
+ }
+
+ /**
+ * Set properties.
+ */
+ public function setProperties(Document\Properties $pValue): void
+ {
+ $this->properties = $pValue;
+ }
+
+ /**
+ * Get security.
+ *
+ * @return Document\Security
+ */
+ public function getSecurity()
+ {
+ return $this->security;
+ }
+
+ /**
+ * Set security.
+ */
+ public function setSecurity(Document\Security $pValue): void
+ {
+ $this->security = $pValue;
+ }
+
+ /**
+ * Get active sheet.
+ *
+ * @return Worksheet
+ */
+ public function getActiveSheet()
+ {
+ return $this->getSheet($this->activeSheetIndex);
+ }
+
+ /**
+ * Create sheet and add it to this workbook.
+ *
+ * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
+ *
+ * @return Worksheet
+ */
+ public function createSheet($sheetIndex = null)
+ {
+ $newSheet = new Worksheet($this);
+ $this->addSheet($newSheet, $sheetIndex);
+
+ return $newSheet;
+ }
+
+ /**
+ * Check if a sheet with a specified name already exists.
+ *
+ * @param string $pSheetName Name of the worksheet to check
+ *
+ * @return bool
+ */
+ public function sheetNameExists($pSheetName)
+ {
+ return $this->getSheetByName($pSheetName) !== null;
+ }
+
+ /**
+ * Add sheet.
+ *
+ * @param null|int $iSheetIndex Index where sheet should go (0,1,..., or null for last)
+ *
+ * @return Worksheet
+ */
+ public function addSheet(Worksheet $pSheet, $iSheetIndex = null)
+ {
+ if ($this->sheetNameExists($pSheet->getTitle())) {
+ throw new Exception(
+ "Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename this worksheet first."
+ );
+ }
+
+ if ($iSheetIndex === null) {
+ if ($this->activeSheetIndex < 0) {
+ $this->activeSheetIndex = 0;
+ }
+ $this->workSheetCollection[] = $pSheet;
+ } else {
+ // Insert the sheet at the requested index
+ array_splice(
+ $this->workSheetCollection,
+ $iSheetIndex,
+ 0,
+ [$pSheet]
+ );
+
+ // Adjust active sheet index if necessary
+ if ($this->activeSheetIndex >= $iSheetIndex) {
+ ++$this->activeSheetIndex;
+ }
+ }
+
+ if ($pSheet->getParent() === null) {
+ $pSheet->rebindParent($this);
+ }
+
+ return $pSheet;
+ }
+
+ /**
+ * Remove sheet by index.
+ *
+ * @param int $pIndex Active sheet index
+ */
+ public function removeSheetByIndex($pIndex): void
+ {
+ $numSheets = count($this->workSheetCollection);
+ if ($pIndex > $numSheets - 1) {
+ throw new Exception(
+ "You tried to remove a sheet by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}."
+ );
+ }
+ array_splice($this->workSheetCollection, $pIndex, 1);
+
+ // Adjust active sheet index if necessary
+ if (($this->activeSheetIndex >= $pIndex) &&
+ ($pIndex > count($this->workSheetCollection) - 1)) {
+ --$this->activeSheetIndex;
+ }
+ }
+
+ /**
+ * Get sheet by index.
+ *
+ * @param int $pIndex Sheet index
+ *
+ * @return Worksheet
+ */
+ public function getSheet($pIndex)
+ {
+ if (!isset($this->workSheetCollection[$pIndex])) {
+ $numSheets = $this->getSheetCount();
+
+ throw new Exception(
+ "Your requested sheet index: {$pIndex} is out of bounds. The actual number of sheets is {$numSheets}."
+ );
+ }
+
+ return $this->workSheetCollection[$pIndex];
+ }
+
+ /**
+ * Get all sheets.
+ *
+ * @return Worksheet[]
+ */
+ public function getAllSheets()
+ {
+ return $this->workSheetCollection;
+ }
+
+ /**
+ * Get sheet by name.
+ *
+ * @param string $pName Sheet name
+ *
+ * @return null|Worksheet
+ */
+ public function getSheetByName($pName)
+ {
+ $worksheetCount = count($this->workSheetCollection);
+ for ($i = 0; $i < $worksheetCount; ++$i) {
+ if ($this->workSheetCollection[$i]->getTitle() === trim($pName, "'")) {
+ return $this->workSheetCollection[$i];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get index for sheet.
+ *
+ * @return int index
+ */
+ public function getIndex(Worksheet $pSheet)
+ {
+ foreach ($this->workSheetCollection as $key => $value) {
+ if ($value->getHashCode() == $pSheet->getHashCode()) {
+ return $key;
+ }
+ }
+
+ throw new Exception('Sheet does not exist.');
+ }
+
+ /**
+ * Set index for sheet by sheet name.
+ *
+ * @param string $sheetName Sheet name to modify index for
+ * @param int $newIndex New index for the sheet
+ *
+ * @return int New sheet index
+ */
+ public function setIndexByName($sheetName, $newIndex)
+ {
+ $oldIndex = $this->getIndex($this->getSheetByName($sheetName));
+ $pSheet = array_splice(
+ $this->workSheetCollection,
+ $oldIndex,
+ 1
+ );
+ array_splice(
+ $this->workSheetCollection,
+ $newIndex,
+ 0,
+ $pSheet
+ );
+
+ return $newIndex;
+ }
+
+ /**
+ * Get sheet count.
+ *
+ * @return int
+ */
+ public function getSheetCount()
+ {
+ return count($this->workSheetCollection);
+ }
+
+ /**
+ * Get active sheet index.
+ *
+ * @return int Active sheet index
+ */
+ public function getActiveSheetIndex()
+ {
+ return $this->activeSheetIndex;
+ }
+
+ /**
+ * Set active sheet index.
+ *
+ * @param int $pIndex Active sheet index
+ *
+ * @return Worksheet
+ */
+ public function setActiveSheetIndex($pIndex)
+ {
+ $numSheets = count($this->workSheetCollection);
+
+ if ($pIndex > $numSheets - 1) {
+ throw new Exception(
+ "You tried to set a sheet active by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}."
+ );
+ }
+ $this->activeSheetIndex = $pIndex;
+
+ return $this->getActiveSheet();
+ }
+
+ /**
+ * Set active sheet index by name.
+ *
+ * @param string $pValue Sheet title
+ *
+ * @return Worksheet
+ */
+ public function setActiveSheetIndexByName($pValue)
+ {
+ if (($worksheet = $this->getSheetByName($pValue)) instanceof Worksheet) {
+ $this->setActiveSheetIndex($this->getIndex($worksheet));
+
+ return $worksheet;
+ }
+
+ throw new Exception('Workbook does not contain sheet:' . $pValue);
+ }
+
+ /**
+ * Get sheet names.
+ *
+ * @return string[]
+ */
+ public function getSheetNames()
+ {
+ $returnValue = [];
+ $worksheetCount = $this->getSheetCount();
+ for ($i = 0; $i < $worksheetCount; ++$i) {
+ $returnValue[] = $this->getSheet($i)->getTitle();
+ }
+
+ return $returnValue;
+ }
+
+ /**
+ * Add external sheet.
+ *
+ * @param Worksheet $pSheet External sheet to add
+ * @param null|int $iSheetIndex Index where sheet should go (0,1,..., or null for last)
+ *
+ * @return Worksheet
+ */
+ public function addExternalSheet(Worksheet $pSheet, $iSheetIndex = null)
+ {
+ if ($this->sheetNameExists($pSheet->getTitle())) {
+ throw new Exception("Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename the external sheet first.");
+ }
+
+ // count how many cellXfs there are in this workbook currently, we will need this below
+ $countCellXfs = count($this->cellXfCollection);
+
+ // copy all the shared cellXfs from the external workbook and append them to the current
+ foreach ($pSheet->getParent()->getCellXfCollection() as $cellXf) {
+ $this->addCellXf(clone $cellXf);
+ }
+
+ // move sheet to this workbook
+ $pSheet->rebindParent($this);
+
+ // update the cellXfs
+ foreach ($pSheet->getCoordinates(false) as $coordinate) {
+ $cell = $pSheet->getCell($coordinate);
+ $cell->setXfIndex($cell->getXfIndex() + $countCellXfs);
+ }
+
+ return $this->addSheet($pSheet, $iSheetIndex);
+ }
+
+ /**
+ * Get named ranges.
+ *
+ * @return NamedRange[]
+ */
+ public function getNamedRanges()
+ {
+ return $this->namedRanges;
+ }
+
+ /**
+ * Add named range.
+ *
+ * @return bool
+ */
+ public function addNamedRange(NamedRange $namedRange)
+ {
+ if ($namedRange->getScope() == null) {
+ // global scope
+ $this->namedRanges[$namedRange->getName()] = $namedRange;
+ } else {
+ // local scope
+ $this->namedRanges[$namedRange->getScope()->getTitle() . '!' . $namedRange->getName()] = $namedRange;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get named range.
+ *
+ * @param string $namedRange
+ * @param null|Worksheet $pSheet Scope. Use null for global scope
+ *
+ * @return null|NamedRange
+ */
+ public function getNamedRange($namedRange, ?Worksheet $pSheet = null)
+ {
+ $returnValue = null;
+
+ if ($namedRange != '' && ($namedRange !== null)) {
+ // first look for global defined name
+ if (isset($this->namedRanges[$namedRange])) {
+ $returnValue = $this->namedRanges[$namedRange];
+ }
+
+ // then look for local defined name (has priority over global defined name if both names exist)
+ if (($pSheet !== null) && isset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange])) {
+ $returnValue = $this->namedRanges[$pSheet->getTitle() . '!' . $namedRange];
+ }
+ }
+
+ return $returnValue;
+ }
+
+ /**
+ * Remove named range.
+ *
+ * @param string $namedRange
+ * @param null|Worksheet $pSheet scope: use null for global scope
+ *
+ * @return $this
+ */
+ public function removeNamedRange($namedRange, ?Worksheet $pSheet = null)
+ {
+ if ($pSheet === null) {
+ if (isset($this->namedRanges[$namedRange])) {
+ unset($this->namedRanges[$namedRange]);
+ }
+ } else {
+ if (isset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange])) {
+ unset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get worksheet iterator.
+ *
+ * @return Iterator
+ */
+ public function getWorksheetIterator()
+ {
+ return new Iterator($this);
+ }
+
+ /**
+ * Copy workbook (!= clone!).
+ *
+ * @return Spreadsheet
+ */
+ public function copy()
+ {
+ $copied = clone $this;
+
+ $worksheetCount = count($this->workSheetCollection);
+ for ($i = 0; $i < $worksheetCount; ++$i) {
+ $this->workSheetCollection[$i] = $this->workSheetCollection[$i]->copy();
+ $this->workSheetCollection[$i]->rebindParent($this);
+ }
+
+ return $copied;
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone()
+ {
+ foreach ($this as $key => $val) {
+ if (is_object($val) || (is_array($val))) {
+ $this->{$key} = unserialize(serialize($val));
+ }
+ }
+ }
+
+ /**
+ * Get the workbook collection of cellXfs.
+ *
+ * @return Style[]
+ */
+ public function getCellXfCollection()
+ {
+ return $this->cellXfCollection;
+ }
+
+ /**
+ * Get cellXf by index.
+ *
+ * @param int $pIndex
+ *
+ * @return Style
+ */
+ public function getCellXfByIndex($pIndex)
+ {
+ return $this->cellXfCollection[$pIndex];
+ }
+
+ /**
+ * Get cellXf by hash code.
+ *
+ * @param string $pValue
+ *
+ * @return false|Style
+ */
+ public function getCellXfByHashCode($pValue)
+ {
+ foreach ($this->cellXfCollection as $cellXf) {
+ if ($cellXf->getHashCode() == $pValue) {
+ return $cellXf;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if style exists in style collection.
+ *
+ * @param Style $pCellStyle
+ *
+ * @return bool
+ */
+ public function cellXfExists($pCellStyle)
+ {
+ return in_array($pCellStyle, $this->cellXfCollection, true);
+ }
+
+ /**
+ * Get default style.
+ *
+ * @return Style
+ */
+ public function getDefaultStyle()
+ {
+ if (isset($this->cellXfCollection[0])) {
+ return $this->cellXfCollection[0];
+ }
+
+ throw new Exception('No default style found for this workbook');
+ }
+
+ /**
+ * Add a cellXf to the workbook.
+ */
+ public function addCellXf(Style $style): void
+ {
+ $this->cellXfCollection[] = $style;
+ $style->setIndex(count($this->cellXfCollection) - 1);
+ }
+
+ /**
+ * Remove cellXf by index. It is ensured that all cells get their xf index updated.
+ *
+ * @param int $pIndex Index to cellXf
+ */
+ public function removeCellXfByIndex($pIndex): void
+ {
+ if ($pIndex > count($this->cellXfCollection) - 1) {
+ throw new Exception('CellXf index is out of bounds.');
+ }
+
+ // first remove the cellXf
+ array_splice($this->cellXfCollection, $pIndex, 1);
+
+ // then update cellXf indexes for cells
+ foreach ($this->workSheetCollection as $worksheet) {
+ foreach ($worksheet->getCoordinates(false) as $coordinate) {
+ $cell = $worksheet->getCell($coordinate);
+ $xfIndex = $cell->getXfIndex();
+ if ($xfIndex > $pIndex) {
+ // decrease xf index by 1
+ $cell->setXfIndex($xfIndex - 1);
+ } elseif ($xfIndex == $pIndex) {
+ // set to default xf index 0
+ $cell->setXfIndex(0);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the cellXf supervisor.
+ *
+ * @return Style
+ */
+ public function getCellXfSupervisor()
+ {
+ return $this->cellXfSupervisor;
+ }
+
+ /**
+ * Get the workbook collection of cellStyleXfs.
+ *
+ * @return Style[]
+ */
+ public function getCellStyleXfCollection()
+ {
+ return $this->cellStyleXfCollection;
+ }
+
+ /**
+ * Get cellStyleXf by index.
+ *
+ * @param int $pIndex Index to cellXf
+ *
+ * @return Style
+ */
+ public function getCellStyleXfByIndex($pIndex)
+ {
+ return $this->cellStyleXfCollection[$pIndex];
+ }
+
+ /**
+ * Get cellStyleXf by hash code.
+ *
+ * @param string $pValue
+ *
+ * @return false|Style
+ */
+ public function getCellStyleXfByHashCode($pValue)
+ {
+ foreach ($this->cellStyleXfCollection as $cellStyleXf) {
+ if ($cellStyleXf->getHashCode() == $pValue) {
+ return $cellStyleXf;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Add a cellStyleXf to the workbook.
+ */
+ public function addCellStyleXf(Style $pStyle): void
+ {
+ $this->cellStyleXfCollection[] = $pStyle;
+ $pStyle->setIndex(count($this->cellStyleXfCollection) - 1);
+ }
+
+ /**
+ * Remove cellStyleXf by index.
+ *
+ * @param int $pIndex Index to cellXf
+ */
+ public function removeCellStyleXfByIndex($pIndex): void
+ {
+ if ($pIndex > count($this->cellStyleXfCollection) - 1) {
+ throw new Exception('CellStyleXf index is out of bounds.');
+ }
+ array_splice($this->cellStyleXfCollection, $pIndex, 1);
+ }
+
+ /**
+ * Eliminate all unneeded cellXf and afterwards update the xfIndex for all cells
+ * and columns in the workbook.
+ */
+ public function garbageCollect(): void
+ {
+ // how many references are there to each cellXf ?
+ $countReferencesCellXf = [];
+ foreach ($this->cellXfCollection as $index => $cellXf) {
+ $countReferencesCellXf[$index] = 0;
+ }
+
+ foreach ($this->getWorksheetIterator() as $sheet) {
+ // from cells
+ foreach ($sheet->getCoordinates(false) as $coordinate) {
+ $cell = $sheet->getCell($coordinate);
+ ++$countReferencesCellXf[$cell->getXfIndex()];
+ }
+
+ // from row dimensions
+ foreach ($sheet->getRowDimensions() as $rowDimension) {
+ if ($rowDimension->getXfIndex() !== null) {
+ ++$countReferencesCellXf[$rowDimension->getXfIndex()];
+ }
+ }
+
+ // from column dimensions
+ foreach ($sheet->getColumnDimensions() as $columnDimension) {
+ ++$countReferencesCellXf[$columnDimension->getXfIndex()];
+ }
+ }
+
+ // remove cellXfs without references and create mapping so we can update xfIndex
+ // for all cells and columns
+ $countNeededCellXfs = 0;
+ foreach ($this->cellXfCollection as $index => $cellXf) {
+ if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf
+ ++$countNeededCellXfs;
+ } else {
+ unset($this->cellXfCollection[$index]);
+ }
+ $map[$index] = $countNeededCellXfs - 1;
+ }
+ $this->cellXfCollection = array_values($this->cellXfCollection);
+
+ // update the index for all cellXfs
+ foreach ($this->cellXfCollection as $i => $cellXf) {
+ $cellXf->setIndex($i);
+ }
+
+ // make sure there is always at least one cellXf (there should be)
+ if (empty($this->cellXfCollection)) {
+ $this->cellXfCollection[] = new Style();
+ }
+
+ // update the xfIndex for all cells, row dimensions, column dimensions
+ foreach ($this->getWorksheetIterator() as $sheet) {
+ // for all cells
+ foreach ($sheet->getCoordinates(false) as $coordinate) {
+ $cell = $sheet->getCell($coordinate);
+ $cell->setXfIndex($map[$cell->getXfIndex()]);
+ }
+
+ // for all row dimensions
+ foreach ($sheet->getRowDimensions() as $rowDimension) {
+ if ($rowDimension->getXfIndex() !== null) {
+ $rowDimension->setXfIndex($map[$rowDimension->getXfIndex()]);
+ }
+ }
+
+ // for all column dimensions
+ foreach ($sheet->getColumnDimensions() as $columnDimension) {
+ $columnDimension->setXfIndex($map[$columnDimension->getXfIndex()]);
+ }
+
+ // also do garbage collection for all the sheets
+ $sheet->garbageCollect();
+ }
+ }
+
+ /**
+ * Return the unique ID value assigned to this spreadsheet workbook.
+ *
+ * @return string
+ */
+ public function getID()
+ {
+ return $this->uniqueID;
+ }
+
+ /**
+ * Get the visibility of the horizonal scroll bar in the application.
+ *
+ * @return bool True if horizonal scroll bar is visible
+ */
+ public function getShowHorizontalScroll()
+ {
+ return $this->showHorizontalScroll;
+ }
+
+ /**
+ * Set the visibility of the horizonal scroll bar in the application.
+ *
+ * @param bool $showHorizontalScroll True if horizonal scroll bar is visible
+ */
+ public function setShowHorizontalScroll($showHorizontalScroll): void
+ {
+ $this->showHorizontalScroll = (bool) $showHorizontalScroll;
+ }
+
+ /**
+ * Get the visibility of the vertical scroll bar in the application.
+ *
+ * @return bool True if vertical scroll bar is visible
+ */
+ public function getShowVerticalScroll()
+ {
+ return $this->showVerticalScroll;
+ }
+
+ /**
+ * Set the visibility of the vertical scroll bar in the application.
+ *
+ * @param bool $showVerticalScroll True if vertical scroll bar is visible
+ */
+ public function setShowVerticalScroll($showVerticalScroll): void
+ {
+ $this->showVerticalScroll = (bool) $showVerticalScroll;
+ }
+
+ /**
+ * Get the visibility of the sheet tabs in the application.
+ *
+ * @return bool True if the sheet tabs are visible
+ */
+ public function getShowSheetTabs()
+ {
+ return $this->showSheetTabs;
+ }
+
+ /**
+ * Set the visibility of the sheet tabs in the application.
+ *
+ * @param bool $showSheetTabs True if sheet tabs are visible
+ */
+ public function setShowSheetTabs($showSheetTabs): void
+ {
+ $this->showSheetTabs = (bool) $showSheetTabs;
+ }
+
+ /**
+ * Return whether the workbook window is minimized.
+ *
+ * @return bool true if workbook window is minimized
+ */
+ public function getMinimized()
+ {
+ return $this->minimized;
+ }
+
+ /**
+ * Set whether the workbook window is minimized.
+ *
+ * @param bool $minimized true if workbook window is minimized
+ */
+ public function setMinimized($minimized): void
+ {
+ $this->minimized = (bool) $minimized;
+ }
+
+ /**
+ * Return whether to group dates when presenting the user with
+ * filtering optiomd in the user interface.
+ *
+ * @return bool true if workbook window is minimized
+ */
+ public function getAutoFilterDateGrouping()
+ {
+ return $this->autoFilterDateGrouping;
+ }
+
+ /**
+ * Set whether to group dates when presenting the user with
+ * filtering optiomd in the user interface.
+ *
+ * @param bool $autoFilterDateGrouping true if workbook window is minimized
+ */
+ public function setAutoFilterDateGrouping($autoFilterDateGrouping): void
+ {
+ $this->autoFilterDateGrouping = (bool) $autoFilterDateGrouping;
+ }
+
+ /**
+ * Return the first sheet in the book view.
+ *
+ * @return int First sheet in book view
+ */
+ public function getFirstSheetIndex()
+ {
+ return $this->firstSheetIndex;
+ }
+
+ /**
+ * Set the first sheet in the book view.
+ *
+ * @param int $firstSheetIndex First sheet in book view
+ */
+ public function setFirstSheetIndex($firstSheetIndex): void
+ {
+ if ($firstSheetIndex >= 0) {
+ $this->firstSheetIndex = (int) $firstSheetIndex;
+ } else {
+ throw new Exception('First sheet index must be a positive integer.');
+ }
+ }
+
+ /**
+ * Return the visibility status of the workbook.
+ *
+ * This may be one of the following three values:
+ * - visibile
+ *
+ * @return string Visible status
+ */
+ public function getVisibility()
+ {
+ return $this->visibility;
+ }
+
+ /**
+ * Set the visibility status of the workbook.
+ *
+ * Valid values are:
+ * - 'visible' (self::VISIBILITY_VISIBLE):
+ * Workbook window is visible
+ * - 'hidden' (self::VISIBILITY_HIDDEN):
+ * Workbook window is hidden, but can be shown by the user
+ * via the user interface
+ * - 'veryHidden' (self::VISIBILITY_VERY_HIDDEN):
+ * Workbook window is hidden and cannot be shown in the
+ * user interface.
+ *
+ * @param string $visibility visibility status of the workbook
+ */
+ public function setVisibility($visibility): void
+ {
+ if ($visibility === null) {
+ $visibility = self::VISIBILITY_VISIBLE;
+ }
+
+ if (in_array($visibility, self::$workbookViewVisibilityValues)) {
+ $this->visibility = $visibility;
+ } else {
+ throw new Exception('Invalid visibility value.');
+ }
+ }
+
+ /**
+ * Get the ratio between the workbook tabs bar and the horizontal scroll bar.
+ * TabRatio is assumed to be out of 1000 of the horizontal window width.
+ *
+ * @return int Ratio between the workbook tabs bar and the horizontal scroll bar
+ */
+ public function getTabRatio()
+ {
+ return $this->tabRatio;
+ }
+
+ /**
+ * Set the ratio between the workbook tabs bar and the horizontal scroll bar
+ * TabRatio is assumed to be out of 1000 of the horizontal window width.
+ *
+ * @param int $tabRatio Ratio between the tabs bar and the horizontal scroll bar
+ */
+ public function setTabRatio($tabRatio): void
+ {
+ if ($tabRatio >= 0 || $tabRatio <= 1000) {
+ $this->tabRatio = (int) $tabRatio;
+ } else {
+ throw new Exception('Tab ratio must be between 0 and 1000.');
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php
new file mode 100755
index 0000000..e54fa2a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php
@@ -0,0 +1,462 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class Alignment extends Supervisor
+{
+ // Horizontal alignment styles
+ const HORIZONTAL_GENERAL = 'general';
+ const HORIZONTAL_LEFT = 'left';
+ const HORIZONTAL_RIGHT = 'right';
+ const HORIZONTAL_CENTER = 'center';
+ const HORIZONTAL_CENTER_CONTINUOUS = 'centerContinuous';
+ const HORIZONTAL_JUSTIFY = 'justify';
+ const HORIZONTAL_FILL = 'fill';
+ const HORIZONTAL_DISTRIBUTED = 'distributed'; // Excel2007 only
+
+ // Vertical alignment styles
+ const VERTICAL_BOTTOM = 'bottom';
+ const VERTICAL_TOP = 'top';
+ const VERTICAL_CENTER = 'center';
+ const VERTICAL_JUSTIFY = 'justify';
+ const VERTICAL_DISTRIBUTED = 'distributed'; // Excel2007 only
+
+ // Read order
+ const READORDER_CONTEXT = 0;
+ const READORDER_LTR = 1;
+ const READORDER_RTL = 2;
+
+ /**
+ * Horizontal alignment.
+ *
+ * @var string
+ */
+ protected $horizontal = self::HORIZONTAL_GENERAL;
+
+ /**
+ * Vertical alignment.
+ *
+ * @var string
+ */
+ protected $vertical = self::VERTICAL_BOTTOM;
+
+ /**
+ * Text rotation.
+ *
+ * @var int
+ */
+ protected $textRotation = 0;
+
+ /**
+ * Wrap text.
+ *
+ * @var bool
+ */
+ protected $wrapText = false;
+
+ /**
+ * Shrink to fit.
+ *
+ * @var bool
+ */
+ protected $shrinkToFit = false;
+
+ /**
+ * Indent - only possible with horizontal alignment left and right.
+ *
+ * @var int
+ */
+ protected $indent = 0;
+
+ /**
+ * Read order.
+ *
+ * @var int
+ */
+ protected $readOrder = 0;
+
+ /**
+ * Create a new Alignment.
+ *
+ * @param bool $isSupervisor Flag indicating if this is a supervisor or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ * @param bool $isConditional Flag indicating if this is a conditional style or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ */
+ public function __construct($isSupervisor = false, $isConditional = false)
+ {
+ // Supervisor?
+ parent::__construct($isSupervisor);
+
+ if ($isConditional) {
+ $this->horizontal = null;
+ $this->vertical = null;
+ $this->textRotation = null;
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Alignment
+ */
+ public function getSharedComponent()
+ {
+ return $this->parent->getSharedComponent()->getAlignment();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['alignment' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ * <code>
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getAlignment()->applyFromArray(
+ * [
+ * 'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
+ * 'vertical' => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER,
+ * 'textRotation' => 0,
+ * 'wrapText' => TRUE
+ * ]
+ * );
+ * </code>
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())
+ ->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['horizontal'])) {
+ $this->setHorizontal($pStyles['horizontal']);
+ }
+ if (isset($pStyles['vertical'])) {
+ $this->setVertical($pStyles['vertical']);
+ }
+ if (isset($pStyles['textRotation'])) {
+ $this->setTextRotation($pStyles['textRotation']);
+ }
+ if (isset($pStyles['wrapText'])) {
+ $this->setWrapText($pStyles['wrapText']);
+ }
+ if (isset($pStyles['shrinkToFit'])) {
+ $this->setShrinkToFit($pStyles['shrinkToFit']);
+ }
+ if (isset($pStyles['indent'])) {
+ $this->setIndent($pStyles['indent']);
+ }
+ if (isset($pStyles['readOrder'])) {
+ $this->setReadOrder($pStyles['readOrder']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Horizontal.
+ *
+ * @return string
+ */
+ public function getHorizontal()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHorizontal();
+ }
+
+ return $this->horizontal;
+ }
+
+ /**
+ * Set Horizontal.
+ *
+ * @param string $pValue see self::HORIZONTAL_*
+ *
+ * @return $this
+ */
+ public function setHorizontal($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = self::HORIZONTAL_GENERAL;
+ }
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['horizontal' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->horizontal = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Vertical.
+ *
+ * @return string
+ */
+ public function getVertical()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getVertical();
+ }
+
+ return $this->vertical;
+ }
+
+ /**
+ * Set Vertical.
+ *
+ * @param string $pValue see self::VERTICAL_*
+ *
+ * @return $this
+ */
+ public function setVertical($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = self::VERTICAL_BOTTOM;
+ }
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['vertical' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->vertical = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get TextRotation.
+ *
+ * @return int
+ */
+ public function getTextRotation()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getTextRotation();
+ }
+
+ return $this->textRotation;
+ }
+
+ /**
+ * Set TextRotation.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setTextRotation($pValue)
+ {
+ // Excel2007 value 255 => PhpSpreadsheet value -165
+ if ($pValue == 255) {
+ $pValue = -165;
+ }
+
+ // Set rotation
+ if (($pValue >= -90 && $pValue <= 90) || $pValue == -165) {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['textRotation' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->textRotation = $pValue;
+ }
+ } else {
+ throw new PhpSpreadsheetException('Text rotation should be a value between -90 and 90.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Wrap Text.
+ *
+ * @return bool
+ */
+ public function getWrapText()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getWrapText();
+ }
+
+ return $this->wrapText;
+ }
+
+ /**
+ * Set Wrap Text.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setWrapText($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['wrapText' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->wrapText = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Shrink to fit.
+ *
+ * @return bool
+ */
+ public function getShrinkToFit()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getShrinkToFit();
+ }
+
+ return $this->shrinkToFit;
+ }
+
+ /**
+ * Set Shrink to fit.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setShrinkToFit($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['shrinkToFit' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->shrinkToFit = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get indent.
+ *
+ * @return int
+ */
+ public function getIndent()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getIndent();
+ }
+
+ return $this->indent;
+ }
+
+ /**
+ * Set indent.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setIndent($pValue)
+ {
+ if ($pValue > 0) {
+ if ($this->getHorizontal() != self::HORIZONTAL_GENERAL &&
+ $this->getHorizontal() != self::HORIZONTAL_LEFT &&
+ $this->getHorizontal() != self::HORIZONTAL_RIGHT) {
+ $pValue = 0; // indent not supported
+ }
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['indent' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->indent = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get read order.
+ *
+ * @return int
+ */
+ public function getReadOrder()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getReadOrder();
+ }
+
+ return $this->readOrder;
+ }
+
+ /**
+ * Set read order.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setReadOrder($pValue)
+ {
+ if ($pValue < 0 || $pValue > 2) {
+ $pValue = 0;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['readOrder' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->readOrder = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->horizontal .
+ $this->vertical .
+ $this->textRotation .
+ ($this->wrapText ? 't' : 'f') .
+ ($this->shrinkToFit ? 't' : 'f') .
+ $this->indent .
+ $this->readOrder .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php
new file mode 100755
index 0000000..78ad8b2
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php
@@ -0,0 +1,231 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class Border extends Supervisor
+{
+ // Border style
+ const BORDER_NONE = 'none';
+ const BORDER_DASHDOT = 'dashDot';
+ const BORDER_DASHDOTDOT = 'dashDotDot';
+ const BORDER_DASHED = 'dashed';
+ const BORDER_DOTTED = 'dotted';
+ const BORDER_DOUBLE = 'double';
+ const BORDER_HAIR = 'hair';
+ const BORDER_MEDIUM = 'medium';
+ const BORDER_MEDIUMDASHDOT = 'mediumDashDot';
+ const BORDER_MEDIUMDASHDOTDOT = 'mediumDashDotDot';
+ const BORDER_MEDIUMDASHED = 'mediumDashed';
+ const BORDER_SLANTDASHDOT = 'slantDashDot';
+ const BORDER_THICK = 'thick';
+ const BORDER_THIN = 'thin';
+
+ /**
+ * Border style.
+ *
+ * @var string
+ */
+ protected $borderStyle = self::BORDER_NONE;
+
+ /**
+ * Border color.
+ *
+ * @var Color
+ */
+ protected $color;
+
+ /**
+ * @var int
+ */
+ public $colorIndex;
+
+ /**
+ * Create a new Border.
+ *
+ * @param bool $isSupervisor Flag indicating if this is a supervisor or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ * @param bool $isConditional Flag indicating if this is a conditional style or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ */
+ public function __construct($isSupervisor = false, $isConditional = false)
+ {
+ // Supervisor?
+ parent::__construct($isSupervisor);
+
+ // Initialise values
+ $this->color = new Color(Color::COLOR_BLACK, $isSupervisor);
+
+ // bind parent if we are a supervisor
+ if ($isSupervisor) {
+ $this->color->bindParent($this, 'color');
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Border
+ */
+ public function getSharedComponent()
+ {
+ switch ($this->parentPropertyName) {
+ case 'allBorders':
+ case 'horizontal':
+ case 'inside':
+ case 'outline':
+ case 'vertical':
+ throw new PhpSpreadsheetException('Cannot get shared component for a pseudo-border.');
+
+ break;
+ case 'bottom':
+ return $this->parent->getSharedComponent()->getBottom();
+ case 'diagonal':
+ return $this->parent->getSharedComponent()->getDiagonal();
+ case 'left':
+ return $this->parent->getSharedComponent()->getLeft();
+ case 'right':
+ return $this->parent->getSharedComponent()->getRight();
+ case 'top':
+ return $this->parent->getSharedComponent()->getTop();
+ }
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return $this->parent->getStyleArray([$this->parentPropertyName => $array]);
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ * <code>
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->getTop()->applyFromArray(
+ * [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * );
+ * </code>
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['borderStyle'])) {
+ $this->setBorderStyle($pStyles['borderStyle']);
+ }
+ if (isset($pStyles['color'])) {
+ $this->getColor()->applyFromArray($pStyles['color']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Border style.
+ *
+ * @return string
+ */
+ public function getBorderStyle()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getBorderStyle();
+ }
+
+ return $this->borderStyle;
+ }
+
+ /**
+ * Set Border style.
+ *
+ * @param bool|string $pValue
+ * When passing a boolean, FALSE equates Border::BORDER_NONE
+ * and TRUE to Border::BORDER_MEDIUM
+ *
+ * @return $this
+ */
+ public function setBorderStyle($pValue)
+ {
+ if (empty($pValue)) {
+ $pValue = self::BORDER_NONE;
+ } elseif (is_bool($pValue) && $pValue) {
+ $pValue = self::BORDER_MEDIUM;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['borderStyle' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->borderStyle = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Border Color.
+ *
+ * @return Color
+ */
+ public function getColor()
+ {
+ return $this->color;
+ }
+
+ /**
+ * Set Border Color.
+ *
+ * @return $this
+ */
+ public function setColor(Color $pValue)
+ {
+ // make sure parameter is a real color and not a supervisor
+ $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue;
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getColor()->getStyleArray(['argb' => $color->getARGB()]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->color = $color;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->borderStyle .
+ $this->color->getHashCode() .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php
new file mode 100755
index 0000000..e75d7ee
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php
@@ -0,0 +1,411 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class Borders extends Supervisor
+{
+ // Diagonal directions
+ const DIAGONAL_NONE = 0;
+ const DIAGONAL_UP = 1;
+ const DIAGONAL_DOWN = 2;
+ const DIAGONAL_BOTH = 3;
+
+ /**
+ * Left.
+ *
+ * @var Border
+ */
+ protected $left;
+
+ /**
+ * Right.
+ *
+ * @var Border
+ */
+ protected $right;
+
+ /**
+ * Top.
+ *
+ * @var Border
+ */
+ protected $top;
+
+ /**
+ * Bottom.
+ *
+ * @var Border
+ */
+ protected $bottom;
+
+ /**
+ * Diagonal.
+ *
+ * @var Border
+ */
+ protected $diagonal;
+
+ /**
+ * DiagonalDirection.
+ *
+ * @var int
+ */
+ protected $diagonalDirection;
+
+ /**
+ * All borders pseudo-border. Only applies to supervisor.
+ *
+ * @var Border
+ */
+ protected $allBorders;
+
+ /**
+ * Outline pseudo-border. Only applies to supervisor.
+ *
+ * @var Border
+ */
+ protected $outline;
+
+ /**
+ * Inside pseudo-border. Only applies to supervisor.
+ *
+ * @var Border
+ */
+ protected $inside;
+
+ /**
+ * Vertical pseudo-border. Only applies to supervisor.
+ *
+ * @var Border
+ */
+ protected $vertical;
+
+ /**
+ * Horizontal pseudo-border. Only applies to supervisor.
+ *
+ * @var Border
+ */
+ protected $horizontal;
+
+ /**
+ * Create a new Borders.
+ *
+ * @param bool $isSupervisor Flag indicating if this is a supervisor or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ * @param bool $isConditional Flag indicating if this is a conditional style or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ */
+ public function __construct($isSupervisor = false, $isConditional = false)
+ {
+ // Supervisor?
+ parent::__construct($isSupervisor);
+
+ // Initialise values
+ $this->left = new Border($isSupervisor, $isConditional);
+ $this->right = new Border($isSupervisor, $isConditional);
+ $this->top = new Border($isSupervisor, $isConditional);
+ $this->bottom = new Border($isSupervisor, $isConditional);
+ $this->diagonal = new Border($isSupervisor, $isConditional);
+ $this->diagonalDirection = self::DIAGONAL_NONE;
+
+ // Specially for supervisor
+ if ($isSupervisor) {
+ // Initialize pseudo-borders
+ $this->allBorders = new Border(true);
+ $this->outline = new Border(true);
+ $this->inside = new Border(true);
+ $this->vertical = new Border(true);
+ $this->horizontal = new Border(true);
+
+ // bind parent if we are a supervisor
+ $this->left->bindParent($this, 'left');
+ $this->right->bindParent($this, 'right');
+ $this->top->bindParent($this, 'top');
+ $this->bottom->bindParent($this, 'bottom');
+ $this->diagonal->bindParent($this, 'diagonal');
+ $this->allBorders->bindParent($this, 'allBorders');
+ $this->outline->bindParent($this, 'outline');
+ $this->inside->bindParent($this, 'inside');
+ $this->vertical->bindParent($this, 'vertical');
+ $this->horizontal->bindParent($this, 'horizontal');
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Borders
+ */
+ public function getSharedComponent()
+ {
+ return $this->parent->getSharedComponent()->getBorders();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['borders' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ * <code>
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->applyFromArray(
+ * [
+ * 'bottom' => [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ],
+ * 'top' => [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * ]
+ * );
+ * </code>
+ *
+ * <code>
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->applyFromArray(
+ * [
+ * 'allBorders' => [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * ]
+ * );
+ * </code>
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['left'])) {
+ $this->getLeft()->applyFromArray($pStyles['left']);
+ }
+ if (isset($pStyles['right'])) {
+ $this->getRight()->applyFromArray($pStyles['right']);
+ }
+ if (isset($pStyles['top'])) {
+ $this->getTop()->applyFromArray($pStyles['top']);
+ }
+ if (isset($pStyles['bottom'])) {
+ $this->getBottom()->applyFromArray($pStyles['bottom']);
+ }
+ if (isset($pStyles['diagonal'])) {
+ $this->getDiagonal()->applyFromArray($pStyles['diagonal']);
+ }
+ if (isset($pStyles['diagonalDirection'])) {
+ $this->setDiagonalDirection($pStyles['diagonalDirection']);
+ }
+ if (isset($pStyles['allBorders'])) {
+ $this->getLeft()->applyFromArray($pStyles['allBorders']);
+ $this->getRight()->applyFromArray($pStyles['allBorders']);
+ $this->getTop()->applyFromArray($pStyles['allBorders']);
+ $this->getBottom()->applyFromArray($pStyles['allBorders']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Left.
+ *
+ * @return Border
+ */
+ public function getLeft()
+ {
+ return $this->left;
+ }
+
+ /**
+ * Get Right.
+ *
+ * @return Border
+ */
+ public function getRight()
+ {
+ return $this->right;
+ }
+
+ /**
+ * Get Top.
+ *
+ * @return Border
+ */
+ public function getTop()
+ {
+ return $this->top;
+ }
+
+ /**
+ * Get Bottom.
+ *
+ * @return Border
+ */
+ public function getBottom()
+ {
+ return $this->bottom;
+ }
+
+ /**
+ * Get Diagonal.
+ *
+ * @return Border
+ */
+ public function getDiagonal()
+ {
+ return $this->diagonal;
+ }
+
+ /**
+ * Get AllBorders (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getAllBorders()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->allBorders;
+ }
+
+ /**
+ * Get Outline (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getOutline()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->outline;
+ }
+
+ /**
+ * Get Inside (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getInside()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->inside;
+ }
+
+ /**
+ * Get Vertical (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getVertical()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->vertical;
+ }
+
+ /**
+ * Get Horizontal (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getHorizontal()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->horizontal;
+ }
+
+ /**
+ * Get DiagonalDirection.
+ *
+ * @return int
+ */
+ public function getDiagonalDirection()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getDiagonalDirection();
+ }
+
+ return $this->diagonalDirection;
+ }
+
+ /**
+ * Set DiagonalDirection.
+ *
+ * @param int $pValue see self::DIAGONAL_*
+ *
+ * @return $this
+ */
+ public function setDiagonalDirection($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = self::DIAGONAL_NONE;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['diagonalDirection' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->diagonalDirection = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashcode();
+ }
+
+ return md5(
+ $this->getLeft()->getHashCode() .
+ $this->getRight()->getHashCode() .
+ $this->getTop()->getHashCode() .
+ $this->getBottom()->getHashCode() .
+ $this->getDiagonal()->getHashCode() .
+ $this->getDiagonalDirection() .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php
new file mode 100755
index 0000000..d8ba08b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php
@@ -0,0 +1,407 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style;
+
+class Color extends Supervisor
+{
+ const NAMED_COLORS = [
+ 'Black',
+ 'White',
+ 'Red',
+ 'Green',
+ 'Blue',
+ 'Yellow',
+ 'Magenta',
+ 'Cyan',
+ ];
+
+ // Colors
+ const COLOR_BLACK = 'FF000000';
+ const COLOR_WHITE = 'FFFFFFFF';
+ const COLOR_RED = 'FFFF0000';
+ const COLOR_DARKRED = 'FF800000';
+ const COLOR_BLUE = 'FF0000FF';
+ const COLOR_DARKBLUE = 'FF000080';
+ const COLOR_GREEN = 'FF00FF00';
+ const COLOR_DARKGREEN = 'FF008000';
+ const COLOR_YELLOW = 'FFFFFF00';
+ const COLOR_DARKYELLOW = 'FF808000';
+
+ /**
+ * Indexed colors array.
+ *
+ * @var array
+ */
+ protected static $indexedColors;
+
+ /**
+ * ARGB - Alpha RGB.
+ *
+ * @var string
+ */
+ protected $argb;
+
+ /**
+ * Create a new Color.
+ *
+ * @param string $pARGB ARGB value for the colour
+ * @param bool $isSupervisor Flag indicating if this is a supervisor or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ * @param bool $isConditional Flag indicating if this is a conditional style or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ */
+ public function __construct($pARGB = self::COLOR_BLACK, $isSupervisor = false, $isConditional = false)
+ {
+ // Supervisor?
+ parent::__construct($isSupervisor);
+
+ // Initialise values
+ if (!$isConditional) {
+ $this->argb = $pARGB;
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Color
+ */
+ public function getSharedComponent()
+ {
+ switch ($this->parentPropertyName) {
+ case 'endColor':
+ return $this->parent->getSharedComponent()->getEndColor();
+ case 'color':
+ return $this->parent->getSharedComponent()->getColor();
+ case 'startColor':
+ return $this->parent->getSharedComponent()->getStartColor();
+ }
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return $this->parent->getStyleArray([$this->parentPropertyName => $array]);
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ * <code>
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->getColor()->applyFromArray(['rgb' => '808080']);
+ * </code>
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['rgb'])) {
+ $this->setRGB($pStyles['rgb']);
+ }
+ if (isset($pStyles['argb'])) {
+ $this->setARGB($pStyles['argb']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get ARGB.
+ *
+ * @return string
+ */
+ public function getARGB()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getARGB();
+ }
+
+ return $this->argb;
+ }
+
+ /**
+ * Set ARGB.
+ *
+ * @param string $pValue see self::COLOR_*
+ *
+ * @return $this
+ */
+ public function setARGB($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = self::COLOR_BLACK;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['argb' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->argb = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get RGB.
+ *
+ * @return string
+ */
+ public function getRGB()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getRGB();
+ }
+
+ return substr($this->argb, 2);
+ }
+
+ /**
+ * Set RGB.
+ *
+ * @param string $pValue RGB value
+ *
+ * @return $this
+ */
+ public function setRGB($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = '000000';
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['argb' => 'FF' . $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->argb = 'FF' . $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get a specified colour component of an RGB value.
+ *
+ * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param int $offset Position within the RGB value to extract
+ * @param bool $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ *
+ * @return string The extracted colour component
+ */
+ private static function getColourComponent($RGB, $offset, $hex = true)
+ {
+ $colour = substr($RGB, $offset, 2);
+
+ return ($hex) ? $colour : hexdec($colour);
+ }
+
+ /**
+ * Get the red colour component of an RGB value.
+ *
+ * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param bool $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ *
+ * @return string The red colour component
+ */
+ public static function getRed($RGB, $hex = true)
+ {
+ return self::getColourComponent($RGB, strlen($RGB) - 6, $hex);
+ }
+
+ /**
+ * Get the green colour component of an RGB value.
+ *
+ * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param bool $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ *
+ * @return string The green colour component
+ */
+ public static function getGreen($RGB, $hex = true)
+ {
+ return self::getColourComponent($RGB, strlen($RGB) - 4, $hex);
+ }
+
+ /**
+ * Get the blue colour component of an RGB value.
+ *
+ * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param bool $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ *
+ * @return string The blue colour component
+ */
+ public static function getBlue($RGB, $hex = true)
+ {
+ return self::getColourComponent($RGB, strlen($RGB) - 2, $hex);
+ }
+
+ /**
+ * Adjust the brightness of a color.
+ *
+ * @param string $hex The colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
+ * @param float $adjustPercentage The percentage by which to adjust the colour as a float from -1 to 1
+ *
+ * @return string The adjusted colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
+ */
+ public static function changeBrightness($hex, $adjustPercentage)
+ {
+ $rgba = (strlen($hex) === 8);
+
+ $red = self::getRed($hex, false);
+ $green = self::getGreen($hex, false);
+ $blue = self::getBlue($hex, false);
+ if ($adjustPercentage > 0) {
+ $red += (255 - $red) * $adjustPercentage;
+ $green += (255 - $green) * $adjustPercentage;
+ $blue += (255 - $blue) * $adjustPercentage;
+ } else {
+ $red += $red * $adjustPercentage;
+ $green += $green * $adjustPercentage;
+ $blue += $blue * $adjustPercentage;
+ }
+
+ if ($red < 0) {
+ $red = 0;
+ } elseif ($red > 255) {
+ $red = 255;
+ }
+ if ($green < 0) {
+ $green = 0;
+ } elseif ($green > 255) {
+ $green = 255;
+ }
+ if ($blue < 0) {
+ $blue = 0;
+ } elseif ($blue > 255) {
+ $blue = 255;
+ }
+
+ $rgb = strtoupper(
+ str_pad(dechex((int) $red), 2, '0', 0) .
+ str_pad(dechex((int) $green), 2, '0', 0) .
+ str_pad(dechex((int) $blue), 2, '0', 0)
+ );
+
+ return (($rgba) ? 'FF' : '') . $rgb;
+ }
+
+ /**
+ * Get indexed color.
+ *
+ * @param int $pIndex Index entry point into the colour array
+ * @param bool $background Flag to indicate whether default background or foreground colour
+ * should be returned if the indexed colour doesn't exist
+ *
+ * @return self
+ */
+ public static function indexedColor($pIndex, $background = false)
+ {
+ // Clean parameter
+ $pIndex = (int) $pIndex;
+
+ // Indexed colors
+ if (self::$indexedColors === null) {
+ self::$indexedColors = [
+ 1 => 'FF000000', // System Colour #1 - Black
+ 2 => 'FFFFFFFF', // System Colour #2 - White
+ 3 => 'FFFF0000', // System Colour #3 - Red
+ 4 => 'FF00FF00', // System Colour #4 - Green
+ 5 => 'FF0000FF', // System Colour #5 - Blue
+ 6 => 'FFFFFF00', // System Colour #6 - Yellow
+ 7 => 'FFFF00FF', // System Colour #7- Magenta
+ 8 => 'FF00FFFF', // System Colour #8- Cyan
+ 9 => 'FF800000', // Standard Colour #9
+ 10 => 'FF008000', // Standard Colour #10
+ 11 => 'FF000080', // Standard Colour #11
+ 12 => 'FF808000', // Standard Colour #12
+ 13 => 'FF800080', // Standard Colour #13
+ 14 => 'FF008080', // Standard Colour #14
+ 15 => 'FFC0C0C0', // Standard Colour #15
+ 16 => 'FF808080', // Standard Colour #16
+ 17 => 'FF9999FF', // Chart Fill Colour #17
+ 18 => 'FF993366', // Chart Fill Colour #18
+ 19 => 'FFFFFFCC', // Chart Fill Colour #19
+ 20 => 'FFCCFFFF', // Chart Fill Colour #20
+ 21 => 'FF660066', // Chart Fill Colour #21
+ 22 => 'FFFF8080', // Chart Fill Colour #22
+ 23 => 'FF0066CC', // Chart Fill Colour #23
+ 24 => 'FFCCCCFF', // Chart Fill Colour #24
+ 25 => 'FF000080', // Chart Line Colour #25
+ 26 => 'FFFF00FF', // Chart Line Colour #26
+ 27 => 'FFFFFF00', // Chart Line Colour #27
+ 28 => 'FF00FFFF', // Chart Line Colour #28
+ 29 => 'FF800080', // Chart Line Colour #29
+ 30 => 'FF800000', // Chart Line Colour #30
+ 31 => 'FF008080', // Chart Line Colour #31
+ 32 => 'FF0000FF', // Chart Line Colour #32
+ 33 => 'FF00CCFF', // Standard Colour #33
+ 34 => 'FFCCFFFF', // Standard Colour #34
+ 35 => 'FFCCFFCC', // Standard Colour #35
+ 36 => 'FFFFFF99', // Standard Colour #36
+ 37 => 'FF99CCFF', // Standard Colour #37
+ 38 => 'FFFF99CC', // Standard Colour #38
+ 39 => 'FFCC99FF', // Standard Colour #39
+ 40 => 'FFFFCC99', // Standard Colour #40
+ 41 => 'FF3366FF', // Standard Colour #41
+ 42 => 'FF33CCCC', // Standard Colour #42
+ 43 => 'FF99CC00', // Standard Colour #43
+ 44 => 'FFFFCC00', // Standard Colour #44
+ 45 => 'FFFF9900', // Standard Colour #45
+ 46 => 'FFFF6600', // Standard Colour #46
+ 47 => 'FF666699', // Standard Colour #47
+ 48 => 'FF969696', // Standard Colour #48
+ 49 => 'FF003366', // Standard Colour #49
+ 50 => 'FF339966', // Standard Colour #50
+ 51 => 'FF003300', // Standard Colour #51
+ 52 => 'FF333300', // Standard Colour #52
+ 53 => 'FF993300', // Standard Colour #53
+ 54 => 'FF993366', // Standard Colour #54
+ 55 => 'FF333399', // Standard Colour #55
+ 56 => 'FF333333', // Standard Colour #56
+ ];
+ }
+
+ if (isset(self::$indexedColors[$pIndex])) {
+ return new self(self::$indexedColors[$pIndex]);
+ }
+
+ if ($background) {
+ return new self(self::COLOR_WHITE);
+ }
+
+ return new self(self::COLOR_BLACK);
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->argb .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php
new file mode 100755
index 0000000..35ec479
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php
@@ -0,0 +1,274 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style;
+
+use PhpOffice\PhpSpreadsheet\IComparable;
+
+class Conditional implements IComparable
+{
+ // Condition types
+ const CONDITION_NONE = 'none';
+ const CONDITION_CELLIS = 'cellIs';
+ const CONDITION_CONTAINSTEXT = 'containsText';
+ const CONDITION_EXPRESSION = 'expression';
+ const CONDITION_CONTAINSBLANKS = 'containsBlanks';
+ const CONDITION_NOTCONTAINSBLANKS = 'notContainsBlanks';
+
+ // Operator types
+ const OPERATOR_NONE = '';
+ const OPERATOR_BEGINSWITH = 'beginsWith';
+ const OPERATOR_ENDSWITH = 'endsWith';
+ const OPERATOR_EQUAL = 'equal';
+ const OPERATOR_GREATERTHAN = 'greaterThan';
+ const OPERATOR_GREATERTHANOREQUAL = 'greaterThanOrEqual';
+ const OPERATOR_LESSTHAN = 'lessThan';
+ const OPERATOR_LESSTHANOREQUAL = 'lessThanOrEqual';
+ const OPERATOR_NOTEQUAL = 'notEqual';
+ const OPERATOR_CONTAINSTEXT = 'containsText';
+ const OPERATOR_NOTCONTAINS = 'notContains';
+ const OPERATOR_BETWEEN = 'between';
+ const OPERATOR_NOTBETWEEN = 'notBetween';
+
+ /**
+ * Condition type.
+ *
+ * @var string
+ */
+ private $conditionType = self::CONDITION_NONE;
+
+ /**
+ * Operator type.
+ *
+ * @var string
+ */
+ private $operatorType = self::OPERATOR_NONE;
+
+ /**
+ * Text.
+ *
+ * @var string
+ */
+ private $text;
+
+ /**
+ * Stop on this condition, if it matches.
+ *
+ * @var bool
+ */
+ private $stopIfTrue = false;
+
+ /**
+ * Condition.
+ *
+ * @var string[]
+ */
+ private $condition = [];
+
+ /**
+ * Style.
+ *
+ * @var Style
+ */
+ private $style;
+
+ /**
+ * Create a new Conditional.
+ */
+ public function __construct()
+ {
+ // Initialise values
+ $this->style = new Style(false, true);
+ }
+
+ /**
+ * Get Condition type.
+ *
+ * @return string
+ */
+ public function getConditionType()
+ {
+ return $this->conditionType;
+ }
+
+ /**
+ * Set Condition type.
+ *
+ * @param string $pValue Condition type, see self::CONDITION_*
+ *
+ * @return $this
+ */
+ public function setConditionType($pValue)
+ {
+ $this->conditionType = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Operator type.
+ *
+ * @return string
+ */
+ public function getOperatorType()
+ {
+ return $this->operatorType;
+ }
+
+ /**
+ * Set Operator type.
+ *
+ * @param string $pValue Conditional operator type, see self::OPERATOR_*
+ *
+ * @return $this
+ */
+ public function setOperatorType($pValue)
+ {
+ $this->operatorType = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get text.
+ *
+ * @return string
+ */
+ public function getText()
+ {
+ return $this->text;
+ }
+
+ /**
+ * Set text.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setText($value)
+ {
+ $this->text = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get StopIfTrue.
+ *
+ * @return bool
+ */
+ public function getStopIfTrue()
+ {
+ return $this->stopIfTrue;
+ }
+
+ /**
+ * Set StopIfTrue.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setStopIfTrue($value)
+ {
+ $this->stopIfTrue = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Conditions.
+ *
+ * @return string[]
+ */
+ public function getConditions()
+ {
+ return $this->condition;
+ }
+
+ /**
+ * Set Conditions.
+ *
+ * @param string[] $pValue Condition
+ *
+ * @return $this
+ */
+ public function setConditions($pValue)
+ {
+ if (!is_array($pValue)) {
+ $pValue = [$pValue];
+ }
+ $this->condition = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Add Condition.
+ *
+ * @param string $pValue Condition
+ *
+ * @return $this
+ */
+ public function addCondition($pValue)
+ {
+ $this->condition[] = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Style.
+ *
+ * @return Style
+ */
+ public function getStyle()
+ {
+ return $this->style;
+ }
+
+ /**
+ * Set Style.
+ *
+ * @param Style $pValue
+ *
+ * @return $this
+ */
+ public function setStyle(?Style $pValue = null)
+ {
+ $this->style = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->conditionType .
+ $this->operatorType .
+ implode(';', $this->condition) .
+ $this->style->getHashCode() .
+ __CLASS__
+ );
+ }
+
+ /**
+ * 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php
new file mode 100755
index 0000000..c6baeed
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php
@@ -0,0 +1,314 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style;
+
+class Fill extends Supervisor
+{
+ // Fill types
+ const FILL_NONE = 'none';
+ const FILL_SOLID = 'solid';
+ const FILL_GRADIENT_LINEAR = 'linear';
+ const FILL_GRADIENT_PATH = 'path';
+ const FILL_PATTERN_DARKDOWN = 'darkDown';
+ const FILL_PATTERN_DARKGRAY = 'darkGray';
+ const FILL_PATTERN_DARKGRID = 'darkGrid';
+ const FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal';
+ const FILL_PATTERN_DARKTRELLIS = 'darkTrellis';
+ const FILL_PATTERN_DARKUP = 'darkUp';
+ const FILL_PATTERN_DARKVERTICAL = 'darkVertical';
+ const FILL_PATTERN_GRAY0625 = 'gray0625';
+ const FILL_PATTERN_GRAY125 = 'gray125';
+ const FILL_PATTERN_LIGHTDOWN = 'lightDown';
+ const FILL_PATTERN_LIGHTGRAY = 'lightGray';
+ const FILL_PATTERN_LIGHTGRID = 'lightGrid';
+ const FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal';
+ const FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis';
+ const FILL_PATTERN_LIGHTUP = 'lightUp';
+ const FILL_PATTERN_LIGHTVERTICAL = 'lightVertical';
+ const FILL_PATTERN_MEDIUMGRAY = 'mediumGray';
+
+ /**
+ * @var int
+ */
+ public $startcolorIndex;
+
+ /**
+ * @var int
+ */
+ public $endcolorIndex;
+
+ /**
+ * Fill type.
+ *
+ * @var string
+ */
+ protected $fillType = self::FILL_NONE;
+
+ /**
+ * Rotation.
+ *
+ * @var float
+ */
+ protected $rotation = 0;
+
+ /**
+ * Start color.
+ *
+ * @var Color
+ */
+ protected $startColor;
+
+ /**
+ * End color.
+ *
+ * @var Color
+ */
+ protected $endColor;
+
+ /**
+ * Create a new Fill.
+ *
+ * @param bool $isSupervisor Flag indicating if this is a supervisor or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ * @param bool $isConditional Flag indicating if this is a conditional style or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ */
+ public function __construct($isSupervisor = false, $isConditional = false)
+ {
+ // Supervisor?
+ parent::__construct($isSupervisor);
+
+ // Initialise values
+ if ($isConditional) {
+ $this->fillType = null;
+ }
+ $this->startColor = new Color(Color::COLOR_WHITE, $isSupervisor, $isConditional);
+ $this->endColor = new Color(Color::COLOR_BLACK, $isSupervisor, $isConditional);
+
+ // bind parent if we are a supervisor
+ if ($isSupervisor) {
+ $this->startColor->bindParent($this, 'startColor');
+ $this->endColor->bindParent($this, 'endColor');
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Fill
+ */
+ public function getSharedComponent()
+ {
+ return $this->parent->getSharedComponent()->getFill();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['fill' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ * <code>
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getFill()->applyFromArray(
+ * [
+ * 'fillType' => Fill::FILL_GRADIENT_LINEAR,
+ * 'rotation' => 0,
+ * 'startColor' => [
+ * 'rgb' => '000000'
+ * ],
+ * 'endColor' => [
+ * 'argb' => 'FFFFFFFF'
+ * ]
+ * ]
+ * );
+ * </code>
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['fillType'])) {
+ $this->setFillType($pStyles['fillType']);
+ }
+ if (isset($pStyles['rotation'])) {
+ $this->setRotation($pStyles['rotation']);
+ }
+ if (isset($pStyles['startColor'])) {
+ $this->getStartColor()->applyFromArray($pStyles['startColor']);
+ }
+ if (isset($pStyles['endColor'])) {
+ $this->getEndColor()->applyFromArray($pStyles['endColor']);
+ }
+ if (isset($pStyles['color'])) {
+ $this->getStartColor()->applyFromArray($pStyles['color']);
+ $this->getEndColor()->applyFromArray($pStyles['color']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Fill Type.
+ *
+ * @return string
+ */
+ public function getFillType()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getFillType();
+ }
+
+ return $this->fillType;
+ }
+
+ /**
+ * Set Fill Type.
+ *
+ * @param string $pValue Fill type, see self::FILL_*
+ *
+ * @return $this
+ */
+ public function setFillType($pValue)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['fillType' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->fillType = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Rotation.
+ *
+ * @return float
+ */
+ public function getRotation()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getRotation();
+ }
+
+ return $this->rotation;
+ }
+
+ /**
+ * Set Rotation.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setRotation($pValue)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['rotation' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->rotation = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Start Color.
+ *
+ * @return Color
+ */
+ public function getStartColor()
+ {
+ return $this->startColor;
+ }
+
+ /**
+ * Set Start Color.
+ *
+ * @return $this
+ */
+ public function setStartColor(Color $pValue)
+ {
+ // make sure parameter is a real color and not a supervisor
+ $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue;
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStartColor()->getStyleArray(['argb' => $color->getARGB()]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->startColor = $color;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get End Color.
+ *
+ * @return Color
+ */
+ public function getEndColor()
+ {
+ return $this->endColor;
+ }
+
+ /**
+ * Set End Color.
+ *
+ * @return $this
+ */
+ public function setEndColor(Color $pValue)
+ {
+ // make sure parameter is a real color and not a supervisor
+ $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue;
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getEndColor()->getStyleArray(['argb' => $color->getARGB()]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->endColor = $color;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+ // Note that we don't care about colours for fill type NONE, but could have duplicate NONEs with
+ // different hashes if we don't explicitly prevent this
+ return md5(
+ $this->getFillType() .
+ $this->getRotation() .
+ ($this->getFillType() !== self::FILL_NONE ? $this->getStartColor()->getHashCode() : '') .
+ ($this->getFillType() !== self::FILL_NONE ? $this->getEndColor()->getHashCode() : '') .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php
new file mode 100755
index 0000000..eee7df0
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php
@@ -0,0 +1,548 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style;
+
+class Font extends Supervisor
+{
+ // Underline types
+ const UNDERLINE_NONE = 'none';
+ const UNDERLINE_DOUBLE = 'double';
+ const UNDERLINE_DOUBLEACCOUNTING = 'doubleAccounting';
+ const UNDERLINE_SINGLE = 'single';
+ const UNDERLINE_SINGLEACCOUNTING = 'singleAccounting';
+
+ /**
+ * Font Name.
+ *
+ * @var string
+ */
+ protected $name = 'Calibri';
+
+ /**
+ * Font Size.
+ *
+ * @var float
+ */
+ protected $size = 11;
+
+ /**
+ * Bold.
+ *
+ * @var bool
+ */
+ protected $bold = false;
+
+ /**
+ * Italic.
+ *
+ * @var bool
+ */
+ protected $italic = false;
+
+ /**
+ * Superscript.
+ *
+ * @var bool
+ */
+ protected $superscript = false;
+
+ /**
+ * Subscript.
+ *
+ * @var bool
+ */
+ protected $subscript = false;
+
+ /**
+ * Underline.
+ *
+ * @var string
+ */
+ protected $underline = self::UNDERLINE_NONE;
+
+ /**
+ * Strikethrough.
+ *
+ * @var bool
+ */
+ protected $strikethrough = false;
+
+ /**
+ * Foreground color.
+ *
+ * @var Color
+ */
+ protected $color;
+
+ /**
+ * @var int
+ */
+ public $colorIndex;
+
+ /**
+ * Create a new Font.
+ *
+ * @param bool $isSupervisor Flag indicating if this is a supervisor or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ * @param bool $isConditional Flag indicating if this is a conditional style or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ */
+ public function __construct($isSupervisor = false, $isConditional = false)
+ {
+ // Supervisor?
+ parent::__construct($isSupervisor);
+
+ // Initialise values
+ if ($isConditional) {
+ $this->name = null;
+ $this->size = null;
+ $this->bold = null;
+ $this->italic = null;
+ $this->superscript = null;
+ $this->subscript = null;
+ $this->underline = null;
+ $this->strikethrough = null;
+ $this->color = new Color(Color::COLOR_BLACK, $isSupervisor, $isConditional);
+ } else {
+ $this->color = new Color(Color::COLOR_BLACK, $isSupervisor);
+ }
+ // bind parent if we are a supervisor
+ if ($isSupervisor) {
+ $this->color->bindParent($this, 'color');
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Font
+ */
+ public function getSharedComponent()
+ {
+ return $this->parent->getSharedComponent()->getFont();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['font' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ * <code>
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->applyFromArray(
+ * [
+ * 'name' => 'Arial',
+ * 'bold' => TRUE,
+ * 'italic' => FALSE,
+ * 'underline' => \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE,
+ * 'strikethrough' => FALSE,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * );
+ * </code>
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['name'])) {
+ $this->setName($pStyles['name']);
+ }
+ if (isset($pStyles['bold'])) {
+ $this->setBold($pStyles['bold']);
+ }
+ if (isset($pStyles['italic'])) {
+ $this->setItalic($pStyles['italic']);
+ }
+ if (isset($pStyles['superscript'])) {
+ $this->setSuperscript($pStyles['superscript']);
+ }
+ if (isset($pStyles['subscript'])) {
+ $this->setSubscript($pStyles['subscript']);
+ }
+ if (isset($pStyles['underline'])) {
+ $this->setUnderline($pStyles['underline']);
+ }
+ if (isset($pStyles['strikethrough'])) {
+ $this->setStrikethrough($pStyles['strikethrough']);
+ }
+ if (isset($pStyles['color'])) {
+ $this->getColor()->applyFromArray($pStyles['color']);
+ }
+ if (isset($pStyles['size'])) {
+ $this->setSize($pStyles['size']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getName();
+ }
+
+ return $this->name;
+ }
+
+ /**
+ * Set Name.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setName($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = 'Calibri';
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['name' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->name = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Size.
+ *
+ * @return float
+ */
+ public function getSize()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getSize();
+ }
+
+ return $this->size;
+ }
+
+ /**
+ * Set Size.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setSize($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = 10;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['size' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->size = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Bold.
+ *
+ * @return bool
+ */
+ public function getBold()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getBold();
+ }
+
+ return $this->bold;
+ }
+
+ /**
+ * Set Bold.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setBold($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['bold' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->bold = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Italic.
+ *
+ * @return bool
+ */
+ public function getItalic()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getItalic();
+ }
+
+ return $this->italic;
+ }
+
+ /**
+ * Set Italic.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setItalic($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['italic' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->italic = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Superscript.
+ *
+ * @return bool
+ */
+ public function getSuperscript()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getSuperscript();
+ }
+
+ return $this->superscript;
+ }
+
+ /**
+ * Set Superscript.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setSuperscript($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['superscript' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->superscript = $pValue;
+ $this->subscript = !$pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Subscript.
+ *
+ * @return bool
+ */
+ public function getSubscript()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getSubscript();
+ }
+
+ return $this->subscript;
+ }
+
+ /**
+ * Set Subscript.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setSubscript($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['subscript' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->subscript = $pValue;
+ $this->superscript = !$pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Underline.
+ *
+ * @return string
+ */
+ public function getUnderline()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getUnderline();
+ }
+
+ return $this->underline;
+ }
+
+ /**
+ * Set Underline.
+ *
+ * @param bool|string $pValue \PhpOffice\PhpSpreadsheet\Style\Font underline type
+ * If a boolean is passed, then TRUE equates to UNDERLINE_SINGLE,
+ * false equates to UNDERLINE_NONE
+ *
+ * @return $this
+ */
+ public function setUnderline($pValue)
+ {
+ if (is_bool($pValue)) {
+ $pValue = ($pValue) ? self::UNDERLINE_SINGLE : self::UNDERLINE_NONE;
+ } elseif ($pValue == '') {
+ $pValue = self::UNDERLINE_NONE;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['underline' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->underline = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Strikethrough.
+ *
+ * @return bool
+ */
+ public function getStrikethrough()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getStrikethrough();
+ }
+
+ return $this->strikethrough;
+ }
+
+ /**
+ * Set Strikethrough.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setStrikethrough($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['strikethrough' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->strikethrough = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Color.
+ *
+ * @return Color
+ */
+ public function getColor()
+ {
+ return $this->color;
+ }
+
+ /**
+ * Set Color.
+ *
+ * @return $this
+ */
+ public function setColor(Color $pValue)
+ {
+ // make sure parameter is a real color and not a supervisor
+ $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue;
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getColor()->getStyleArray(['argb' => $color->getARGB()]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->color = $color;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->name .
+ $this->size .
+ ($this->bold ? 't' : 'f') .
+ ($this->italic ? 't' : 'f') .
+ ($this->superscript ? 't' : 'f') .
+ ($this->subscript ? 't' : 'f') .
+ $this->underline .
+ ($this->strikethrough ? 't' : 'f') .
+ $this->color->getHashCode() .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php
new file mode 100755
index 0000000..259acab
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php
@@ -0,0 +1,873 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style;
+
+use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class NumberFormat extends Supervisor
+{
+ // Pre-defined formats
+ const FORMAT_GENERAL = 'General';
+
+ const FORMAT_TEXT = '@';
+
+ const FORMAT_NUMBER = '0';
+ const FORMAT_NUMBER_00 = '0.00';
+ const FORMAT_NUMBER_COMMA_SEPARATED1 = '#,##0.00';
+ const FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-';
+
+ const FORMAT_PERCENTAGE = '0%';
+ const FORMAT_PERCENTAGE_00 = '0.00%';
+
+ const FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd';
+ const FORMAT_DATE_YYYYMMDD = 'yyyy-mm-dd';
+ const FORMAT_DATE_DDMMYYYY = 'dd/mm/yyyy';
+ const FORMAT_DATE_DMYSLASH = 'd/m/yy';
+ const FORMAT_DATE_DMYMINUS = 'd-m-yy';
+ const FORMAT_DATE_DMMINUS = 'd-m';
+ const FORMAT_DATE_MYMINUS = 'm-yy';
+ const FORMAT_DATE_XLSX14 = 'mm-dd-yy';
+ const FORMAT_DATE_XLSX15 = 'd-mmm-yy';
+ const FORMAT_DATE_XLSX16 = 'd-mmm';
+ const FORMAT_DATE_XLSX17 = 'mmm-yy';
+ const FORMAT_DATE_XLSX22 = 'm/d/yy h:mm';
+ const FORMAT_DATE_DATETIME = 'd/m/yy h:mm';
+ const FORMAT_DATE_TIME1 = 'h:mm AM/PM';
+ const FORMAT_DATE_TIME2 = 'h:mm:ss AM/PM';
+ const FORMAT_DATE_TIME3 = 'h:mm';
+ const FORMAT_DATE_TIME4 = 'h:mm:ss';
+ const FORMAT_DATE_TIME5 = 'mm:ss';
+ const FORMAT_DATE_TIME6 = 'h:mm:ss';
+ const FORMAT_DATE_TIME7 = 'i:s.S';
+ const FORMAT_DATE_TIME8 = 'h:mm:ss;@';
+ const FORMAT_DATE_YYYYMMDDSLASH = 'yyyy/mm/dd;@';
+
+ const FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-';
+ const FORMAT_CURRENCY_USD = '$#,##0_-';
+ const FORMAT_CURRENCY_EUR_SIMPLE = '#,##0.00_-"€"';
+ const FORMAT_CURRENCY_EUR = '#,##0_-"€"';
+ const FORMAT_ACCOUNTING_USD = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)';
+ const FORMAT_ACCOUNTING_EUR = '_("€"* #,##0.00_);_("€"* \(#,##0.00\);_("€"* "-"??_);_(@_)';
+
+ /**
+ * Excel built-in number formats.
+ *
+ * @var array
+ */
+ protected static $builtInFormats;
+
+ /**
+ * Excel built-in number formats (flipped, for faster lookups).
+ *
+ * @var array
+ */
+ protected static $flippedBuiltInFormats;
+
+ /**
+ * Format Code.
+ *
+ * @var string
+ */
+ protected $formatCode = self::FORMAT_GENERAL;
+
+ /**
+ * Built-in format Code.
+ *
+ * @var string
+ */
+ protected $builtInFormatCode = 0;
+
+ /**
+ * Create a new NumberFormat.
+ *
+ * @param bool $isSupervisor Flag indicating if this is a supervisor or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ * @param bool $isConditional Flag indicating if this is a conditional style or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ */
+ public function __construct($isSupervisor = false, $isConditional = false)
+ {
+ // Supervisor?
+ parent::__construct($isSupervisor);
+
+ if ($isConditional) {
+ $this->formatCode = null;
+ $this->builtInFormatCode = false;
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return NumberFormat
+ */
+ public function getSharedComponent()
+ {
+ return $this->parent->getSharedComponent()->getNumberFormat();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['numberFormat' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ * <code>
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getNumberFormat()->applyFromArray(
+ * [
+ * 'formatCode' => NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE
+ * ]
+ * );
+ * </code>
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['formatCode'])) {
+ $this->setFormatCode($pStyles['formatCode']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Format Code.
+ *
+ * @return string
+ */
+ public function getFormatCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getFormatCode();
+ }
+ if ($this->builtInFormatCode !== false) {
+ return self::builtInFormatCode($this->builtInFormatCode);
+ }
+
+ return $this->formatCode;
+ }
+
+ /**
+ * Set Format Code.
+ *
+ * @param string $pValue see self::FORMAT_*
+ *
+ * @return $this
+ */
+ public function setFormatCode($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = self::FORMAT_GENERAL;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['formatCode' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->formatCode = $pValue;
+ $this->builtInFormatCode = self::builtInFormatCodeIndex($pValue);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Built-In Format Code.
+ *
+ * @return int
+ */
+ public function getBuiltInFormatCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getBuiltInFormatCode();
+ }
+
+ return $this->builtInFormatCode;
+ }
+
+ /**
+ * Set Built-In Format Code.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setBuiltInFormatCode($pValue)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['formatCode' => self::builtInFormatCode($pValue)]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->builtInFormatCode = $pValue;
+ $this->formatCode = self::builtInFormatCode($pValue);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Fill built-in format codes.
+ */
+ private static function fillBuiltInFormatCodes(): void
+ {
+ // [MS-OI29500: Microsoft Office Implementation Information for ISO/IEC-29500 Standard Compliance]
+ // 18.8.30. numFmt (Number Format)
+ //
+ // The ECMA standard defines built-in format IDs
+ // 14: "mm-dd-yy"
+ // 22: "m/d/yy h:mm"
+ // 37: "#,##0 ;(#,##0)"
+ // 38: "#,##0 ;[Red](#,##0)"
+ // 39: "#,##0.00;(#,##0.00)"
+ // 40: "#,##0.00;[Red](#,##0.00)"
+ // 47: "mmss.0"
+ // KOR fmt 55: "yyyy-mm-dd"
+ // Excel defines built-in format IDs
+ // 14: "m/d/yyyy"
+ // 22: "m/d/yyyy h:mm"
+ // 37: "#,##0_);(#,##0)"
+ // 38: "#,##0_);[Red](#,##0)"
+ // 39: "#,##0.00_);(#,##0.00)"
+ // 40: "#,##0.00_);[Red](#,##0.00)"
+ // 47: "mm:ss.0"
+ // KOR fmt 55: "yyyy/mm/dd"
+
+ // Built-in format codes
+ if (self::$builtInFormats === null) {
+ self::$builtInFormats = [];
+
+ // General
+ self::$builtInFormats[0] = self::FORMAT_GENERAL;
+ self::$builtInFormats[1] = '0';
+ self::$builtInFormats[2] = '0.00';
+ self::$builtInFormats[3] = '#,##0';
+ self::$builtInFormats[4] = '#,##0.00';
+
+ self::$builtInFormats[9] = '0%';
+ self::$builtInFormats[10] = '0.00%';
+ self::$builtInFormats[11] = '0.00E+00';
+ self::$builtInFormats[12] = '# ?/?';
+ self::$builtInFormats[13] = '# ??/??';
+ self::$builtInFormats[14] = 'm/d/yyyy'; // Despite ECMA 'mm-dd-yy';
+ self::$builtInFormats[15] = 'd-mmm-yy';
+ self::$builtInFormats[16] = 'd-mmm';
+ self::$builtInFormats[17] = 'mmm-yy';
+ self::$builtInFormats[18] = 'h:mm AM/PM';
+ self::$builtInFormats[19] = 'h:mm:ss AM/PM';
+ self::$builtInFormats[20] = 'h:mm';
+ self::$builtInFormats[21] = 'h:mm:ss';
+ self::$builtInFormats[22] = 'm/d/yyyy h:mm'; // Despite ECMA 'm/d/yy h:mm';
+
+ self::$builtInFormats[37] = '#,##0_);(#,##0)'; // Despite ECMA '#,##0 ;(#,##0)';
+ self::$builtInFormats[38] = '#,##0_);[Red](#,##0)'; // Despite ECMA '#,##0 ;[Red](#,##0)';
+ self::$builtInFormats[39] = '#,##0.00_);(#,##0.00)'; // Despite ECMA '#,##0.00;(#,##0.00)';
+ self::$builtInFormats[40] = '#,##0.00_);[Red](#,##0.00)'; // Despite ECMA '#,##0.00;[Red](#,##0.00)';
+
+ self::$builtInFormats[44] = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)';
+ self::$builtInFormats[45] = 'mm:ss';
+ self::$builtInFormats[46] = '[h]:mm:ss';
+ self::$builtInFormats[47] = 'mm:ss.0'; // Despite ECMA 'mmss.0';
+ self::$builtInFormats[48] = '##0.0E+0';
+ self::$builtInFormats[49] = '@';
+
+ // CHT
+ self::$builtInFormats[27] = '[$-404]e/m/d';
+ self::$builtInFormats[30] = 'm/d/yy';
+ self::$builtInFormats[36] = '[$-404]e/m/d';
+ self::$builtInFormats[50] = '[$-404]e/m/d';
+ self::$builtInFormats[57] = '[$-404]e/m/d';
+
+ // THA
+ self::$builtInFormats[59] = 't0';
+ self::$builtInFormats[60] = 't0.00';
+ self::$builtInFormats[61] = 't#,##0';
+ self::$builtInFormats[62] = 't#,##0.00';
+ self::$builtInFormats[67] = 't0%';
+ self::$builtInFormats[68] = 't0.00%';
+ self::$builtInFormats[69] = 't# ?/?';
+ self::$builtInFormats[70] = 't# ??/??';
+
+ // JPN
+ self::$builtInFormats[28] = '[$-411]ggge"年"m"月"d"日"';
+ self::$builtInFormats[29] = '[$-411]ggge"年"m"月"d"日"';
+ self::$builtInFormats[31] = 'yyyy"年"m"月"d"日"';
+ self::$builtInFormats[32] = 'h"時"mm"分"';
+ self::$builtInFormats[33] = 'h"時"mm"分"ss"秒"';
+ self::$builtInFormats[34] = 'yyyy"年"m"月"';
+ self::$builtInFormats[35] = 'm"月"d"日"';
+ self::$builtInFormats[51] = '[$-411]ggge"年"m"月"d"日"';
+ self::$builtInFormats[52] = 'yyyy"年"m"月"';
+ self::$builtInFormats[53] = 'm"月"d"日"';
+ self::$builtInFormats[54] = '[$-411]ggge"年"m"月"d"日"';
+ self::$builtInFormats[55] = 'yyyy"年"m"月"';
+ self::$builtInFormats[56] = 'm"月"d"日"';
+ self::$builtInFormats[58] = '[$-411]ggge"年"m"月"d"日"';
+
+ // Flip array (for faster lookups)
+ self::$flippedBuiltInFormats = array_flip(self::$builtInFormats);
+ }
+ }
+
+ /**
+ * Get built-in format code.
+ *
+ * @param int $pIndex
+ *
+ * @return string
+ */
+ public static function builtInFormatCode($pIndex)
+ {
+ // Clean parameter
+ $pIndex = (int) $pIndex;
+
+ // Ensure built-in format codes are available
+ self::fillBuiltInFormatCodes();
+
+ // Lookup format code
+ if (isset(self::$builtInFormats[$pIndex])) {
+ return self::$builtInFormats[$pIndex];
+ }
+
+ return '';
+ }
+
+ /**
+ * Get built-in format code index.
+ *
+ * @param string $formatCode
+ *
+ * @return bool|int
+ */
+ public static function builtInFormatCodeIndex($formatCode)
+ {
+ // Ensure built-in format codes are available
+ self::fillBuiltInFormatCodes();
+
+ // Lookup format code
+ if (array_key_exists($formatCode, self::$flippedBuiltInFormats)) {
+ return self::$flippedBuiltInFormats[$formatCode];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->formatCode .
+ $this->builtInFormatCode .
+ __CLASS__
+ );
+ }
+
+ /**
+ * Search/replace values to convert Excel date/time format masks to PHP format masks.
+ *
+ * @var array
+ */
+ private static $dateFormatReplacements = [
+ // first remove escapes related to non-format characters
+ '\\' => '',
+ // 12-hour suffix
+ 'am/pm' => 'A',
+ // 4-digit year
+ 'e' => 'Y',
+ 'yyyy' => 'Y',
+ // 2-digit year
+ 'yy' => 'y',
+ // first letter of month - no php equivalent
+ 'mmmmm' => 'M',
+ // full month name
+ 'mmmm' => 'F',
+ // short month name
+ 'mmm' => 'M',
+ // mm is minutes if time, but can also be month w/leading zero
+ // so we try to identify times be the inclusion of a : separator in the mask
+ // It isn't perfect, but the best way I know how
+ ':mm' => ':i',
+ 'mm:' => 'i:',
+ // month leading zero
+ 'mm' => 'm',
+ // month no leading zero
+ 'm' => 'n',
+ // full day of week name
+ 'dddd' => 'l',
+ // short day of week name
+ 'ddd' => 'D',
+ // days leading zero
+ 'dd' => 'd',
+ // days no leading zero
+ 'd' => 'j',
+ // seconds
+ 'ss' => 's',
+ // fractional seconds - no php equivalent
+ '.s' => '',
+ ];
+
+ /**
+ * Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock).
+ *
+ * @var array
+ */
+ private static $dateFormatReplacements24 = [
+ 'hh' => 'H',
+ 'h' => 'G',
+ ];
+
+ /**
+ * Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock).
+ *
+ * @var array
+ */
+ private static $dateFormatReplacements12 = [
+ 'hh' => 'h',
+ 'h' => 'g',
+ ];
+
+ private static function setLowercaseCallback($matches)
+ {
+ return mb_strtolower($matches[0]);
+ }
+
+ private static function escapeQuotesCallback($matches)
+ {
+ return '\\' . implode('\\', str_split($matches[1]));
+ }
+
+ private static function formatAsDate(&$value, &$format): void
+ {
+ // strip off first part containing e.g. [$-F800] or [$USD-409]
+ // general syntax: [$<Currency string>-<language info>]
+ // language info is in hexadecimal
+ // strip off chinese part like [DBNum1][$-804]
+ $format = preg_replace('/^(\[[0-9A-Za-z]*\])*(\[\$[A-Z]*-[0-9A-F]*\])/i', '', $format);
+
+ // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case;
+ // but we don't want to change any quoted strings
+ $format = preg_replace_callback('/(?:^|")([^"]*)(?:$|")/', ['self', 'setLowercaseCallback'], $format);
+
+ // Only process the non-quoted blocks for date format characters
+ $blocks = explode('"', $format);
+ foreach ($blocks as $key => &$block) {
+ if ($key % 2 == 0) {
+ $block = strtr($block, self::$dateFormatReplacements);
+ if (!strpos($block, 'A')) {
+ // 24-hour time format
+ // when [h]:mm format, the [h] should replace to the hours of the value * 24
+ if (false !== strpos($block, '[h]')) {
+ $hours = (int) ($value * 24);
+ $block = str_replace('[h]', $hours, $block);
+
+ continue;
+ }
+ $block = strtr($block, self::$dateFormatReplacements24);
+ } else {
+ // 12-hour time format
+ $block = strtr($block, self::$dateFormatReplacements12);
+ }
+ }
+ }
+ $format = implode('"', $blocks);
+
+ // escape any quoted characters so that DateTime format() will render them correctly
+ $format = preg_replace_callback('/"(.*)"/U', ['self', 'escapeQuotesCallback'], $format);
+
+ $dateObj = Date::excelToDateTimeObject($value);
+ $value = $dateObj->format($format);
+ }
+
+ private static function formatAsPercentage(&$value, &$format): void
+ {
+ if ($format === self::FORMAT_PERCENTAGE) {
+ $value = round((100 * $value), 0) . '%';
+ } else {
+ if (preg_match('/\.[#0]+/', $format, $m)) {
+ $s = substr($m[0], 0, 1) . (strlen($m[0]) - 1);
+ $format = str_replace($m[0], $s, $format);
+ }
+ if (preg_match('/^[#0]+/', $format, $m)) {
+ $format = str_replace($m[0], strlen($m[0]), $format);
+ }
+ $format = '%' . str_replace('%', 'f%%', $format);
+
+ $value = sprintf($format, 100 * $value);
+ }
+ }
+
+ private static function formatAsFraction(&$value, &$format): void
+ {
+ $sign = ($value < 0) ? '-' : '';
+
+ $integerPart = floor(abs($value));
+ $decimalPart = trim(fmod(abs($value), 1), '0.');
+ $decimalLength = strlen($decimalPart);
+ $decimalDivisor = 10 ** $decimalLength;
+
+ $GCD = MathTrig::GCD($decimalPart, $decimalDivisor);
+
+ $adjustedDecimalPart = $decimalPart / $GCD;
+ $adjustedDecimalDivisor = $decimalDivisor / $GCD;
+
+ if ((strpos($format, '0') !== false)) {
+ $value = "$sign$integerPart $adjustedDecimalPart/$adjustedDecimalDivisor";
+ } elseif ((strpos($format, '#') !== false)) {
+ if ($integerPart == 0) {
+ $value = "$sign$adjustedDecimalPart/$adjustedDecimalDivisor";
+ } else {
+ $value = "$sign$integerPart $adjustedDecimalPart/$adjustedDecimalDivisor";
+ }
+ } elseif ((substr($format, 0, 3) == '? ?')) {
+ if ($integerPart == 0) {
+ $integerPart = '';
+ }
+ $value = "$sign$integerPart $adjustedDecimalPart/$adjustedDecimalDivisor";
+ } else {
+ $adjustedDecimalPart += $integerPart * $adjustedDecimalDivisor;
+ $value = "$sign$adjustedDecimalPart/$adjustedDecimalDivisor";
+ }
+ }
+
+ private static function mergeComplexNumberFormatMasks($numbers, $masks)
+ {
+ $decimalCount = strlen($numbers[1]);
+ $postDecimalMasks = [];
+
+ do {
+ $tempMask = array_pop($masks);
+ $postDecimalMasks[] = $tempMask;
+ $decimalCount -= strlen($tempMask);
+ } while ($decimalCount > 0);
+
+ return [
+ implode('.', $masks),
+ implode('.', array_reverse($postDecimalMasks)),
+ ];
+ }
+
+ private static function processComplexNumberFormatMask($number, $mask)
+ {
+ $result = $number;
+ $maskingBlockCount = preg_match_all('/0+/', $mask, $maskingBlocks, PREG_OFFSET_CAPTURE);
+
+ if ($maskingBlockCount > 1) {
+ $maskingBlocks = array_reverse($maskingBlocks[0]);
+
+ foreach ($maskingBlocks as $block) {
+ $divisor = 1 . $block[0];
+ $size = strlen($block[0]);
+ $offset = $block[1];
+
+ $blockValue = sprintf(
+ '%0' . $size . 'd',
+ fmod($number, $divisor)
+ );
+ $number = floor($number / $divisor);
+ $mask = substr_replace($mask, $blockValue, $offset, $size);
+ }
+ if ($number > 0) {
+ $mask = substr_replace($mask, $number, $offset, 0);
+ }
+ $result = $mask;
+ }
+
+ return $result;
+ }
+
+ private static function complexNumberFormatMask($number, $mask, $splitOnPoint = true)
+ {
+ $sign = ($number < 0.0);
+ $number = abs($number);
+
+ if ($splitOnPoint && strpos($mask, '.') !== false && strpos($number, '.') !== false) {
+ $numbers = explode('.', $number);
+ $masks = explode('.', $mask);
+ if (count($masks) > 2) {
+ $masks = self::mergeComplexNumberFormatMasks($numbers, $masks);
+ }
+ $result1 = self::complexNumberFormatMask($numbers[0], $masks[0], false);
+ $result2 = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1]), false));
+
+ return (($sign) ? '-' : '') . $result1 . '.' . $result2;
+ }
+
+ $result = self::processComplexNumberFormatMask($number, $mask);
+
+ return (($sign) ? '-' : '') . $result;
+ }
+
+ private static function formatStraightNumericValue($value, $format, array $matches, $useThousands, $number_regex)
+ {
+ $left = $matches[1];
+ $dec = $matches[2];
+ $right = $matches[3];
+
+ // minimun width of formatted number (including dot)
+ $minWidth = strlen($left) + strlen($dec) + strlen($right);
+ if ($useThousands) {
+ $value = number_format(
+ $value,
+ strlen($right),
+ StringHelper::getDecimalSeparator(),
+ StringHelper::getThousandsSeparator()
+ );
+ $value = preg_replace($number_regex, $value, $format);
+ } else {
+ if (preg_match('/[0#]E[+-]0/i', $format)) {
+ // Scientific format
+ $value = sprintf('%5.2E', $value);
+ } elseif (preg_match('/0([^\d\.]+)0/', $format) || substr_count($format, '.') > 1) {
+ if ($value == (int) $value && substr_count($format, '.') === 1) {
+ $value *= 10 ** strlen(explode('.', $format)[1]);
+ }
+ $value = self::complexNumberFormatMask($value, $format);
+ } else {
+ $sprintf_pattern = "%0$minWidth." . strlen($right) . 'f';
+ $value = sprintf($sprintf_pattern, $value);
+ $value = preg_replace($number_regex, $value, $format);
+ }
+ }
+
+ return $value;
+ }
+
+ private static function formatAsNumber($value, $format)
+ {
+ // The "_" in this string has already been stripped out,
+ // so this test is never true. Furthermore, testing
+ // on Excel shows this format uses Euro symbol, not "EUR".
+ //if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) {
+ // return 'EUR ' . sprintf('%1.2f', $value);
+ //}
+
+ // Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols
+ $format = str_replace(['"', '*'], '', $format);
+
+ // Find out if we need thousands separator
+ // This is indicated by a comma enclosed by a digit placeholder:
+ // #,# or 0,0
+ $useThousands = preg_match('/(#,#|0,0)/', $format);
+ if ($useThousands) {
+ $format = preg_replace('/0,0/', '00', $format);
+ $format = preg_replace('/#,#/', '##', $format);
+ }
+
+ // Scale thousands, millions,...
+ // This is indicated by a number of commas after a digit placeholder:
+ // #, or 0.0,,
+ $scale = 1; // same as no scale
+ $matches = [];
+ if (preg_match('/(#|0)(,+)/', $format, $matches)) {
+ $scale = 1000 ** strlen($matches[2]);
+
+ // strip the commas
+ $format = preg_replace('/0,+/', '0', $format);
+ $format = preg_replace('/#,+/', '#', $format);
+ }
+
+ if (preg_match('/#?.*\?\/\?/', $format, $m)) {
+ if ($value != (int) $value) {
+ self::formatAsFraction($value, $format);
+ }
+ } else {
+ // Handle the number itself
+
+ // scale number
+ $value = $value / $scale;
+ // Strip #
+ $format = preg_replace('/\\#/', '0', $format);
+ // Remove locale code [$-###]
+ $format = preg_replace('/\[\$\-.*\]/', '', $format);
+
+ $n = '/\\[[^\\]]+\\]/';
+ $m = preg_replace($n, '', $format);
+ $number_regex = '/(0+)(\\.?)(0*)/';
+ if (preg_match($number_regex, $m, $matches)) {
+ $value = self::formatStraightNumericValue($value, $format, $matches, $useThousands, $number_regex);
+ }
+ }
+
+ if (preg_match('/\[\$(.*)\]/u', $format, $m)) {
+ // Currency or Accounting
+ $currencyCode = $m[1];
+ [$currencyCode] = explode('-', $currencyCode);
+ if ($currencyCode == '') {
+ $currencyCode = StringHelper::getCurrencyCode();
+ }
+ $value = preg_replace('/\[\$([^\]]*)\]/u', $currencyCode, $value);
+ }
+
+ return $value;
+ }
+
+ private static function splitFormatCompare($value, $cond, $val, $dfcond, $dfval)
+ {
+ if (!$cond) {
+ $cond = $dfcond;
+ $val = $dfval;
+ }
+ switch ($cond) {
+ case '>':
+ return $value > $val;
+
+ case '<':
+ return $value < $val;
+
+ case '<=':
+ return $value <= $val;
+
+ case '<>':
+ return $value != $val;
+
+ case '=':
+ return $value == $val;
+ }
+
+ return $value >= $val;
+ }
+
+ private static function splitFormat($sections, $value)
+ {
+ // Extract the relevant section depending on whether number is positive, negative, or zero?
+ // Text not supported yet.
+ // Here is how the sections apply to various values in Excel:
+ // 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT]
+ // 2 sections: [POSITIVE/ZERO/TEXT] [NEGATIVE]
+ // 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO]
+ // 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT]
+ $cnt = count($sections);
+ $color_regex = '/\\[(' . implode('|', Color::NAMED_COLORS) . ')\\]/';
+ $cond_regex = '/\\[(>|>=|<|<=|=|<>)([+-]?\\d+([.]\\d+)?)\\]/';
+ $colors = ['', '', '', '', ''];
+ $condops = ['', '', '', '', ''];
+ $condvals = [0, 0, 0, 0, 0];
+ for ($idx = 0; $idx < $cnt; ++$idx) {
+ if (preg_match($color_regex, $sections[$idx], $matches)) {
+ $colors[$idx] = $matches[0];
+ $sections[$idx] = preg_replace($color_regex, '', $sections[$idx]);
+ }
+ if (preg_match($cond_regex, $sections[$idx], $matches)) {
+ $condops[$idx] = $matches[1];
+ $condvals[$idx] = $matches[2];
+ $sections[$idx] = preg_replace($cond_regex, '', $sections[$idx]);
+ }
+ }
+ $color = $colors[0];
+ $format = $sections[0];
+ $absval = $value;
+ switch ($cnt) {
+ case 2:
+ $absval = abs($value);
+ if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>=', 0)) {
+ $color = $colors[1];
+ $format = $sections[1];
+ }
+
+ break;
+ case 3:
+ case 4:
+ $absval = abs($value);
+ if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>', 0)) {
+ if (self::splitFormatCompare($value, $condops[1], $condvals[1], '<', 0)) {
+ $color = $colors[1];
+ $format = $sections[1];
+ } else {
+ $color = $colors[2];
+ $format = $sections[2];
+ }
+ }
+
+ break;
+ }
+
+ return [$color, $format, $absval];
+ }
+
+ /**
+ * Convert a value in a pre-defined format to a PHP string.
+ *
+ * @param mixed $value Value to format
+ * @param string $format Format code, see = self::FORMAT_*
+ * @param array $callBack Callback function for additional formatting of string
+ *
+ * @return string Formatted string
+ */
+ public static function toFormattedString($value, $format, $callBack = null)
+ {
+ // For now we do not treat strings although section 4 of a format code affects strings
+ if (!is_numeric($value)) {
+ return $value;
+ }
+
+ // For 'General' format code, we just pass the value although this is not entirely the way Excel does it,
+ // it seems to round numbers to a total of 10 digits.
+ if (($format === self::FORMAT_GENERAL) || ($format === self::FORMAT_TEXT)) {
+ return $value;
+ }
+
+ // Convert any other escaped characters to quoted strings, e.g. (\T to "T")
+ $format = preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/u', '"${2}"', $format);
+
+ // Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal)
+ $sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format);
+
+ [$colors, $format, $value] = self::splitFormat($sections, $value);
+
+ // In Excel formats, "_" is used to add spacing,
+ // The following character indicates the size of the spacing, which we can't do in HTML, so we just use a standard space
+ $format = preg_replace('/_./', ' ', $format);
+
+ // Let's begin inspecting the format and converting the value to a formatted string
+
+ // Check for date/time characters (not inside quotes)
+ if (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format, $matches)) {
+ // datetime format
+ self::formatAsDate($value, $format);
+ } else {
+ if (substr($format, 0, 1) === '"' && substr($format, -1, 1) === '"') {
+ $value = substr($format, 1, -1);
+ } elseif (preg_match('/%$/', $format)) {
+ // % number format
+ self::formatAsPercentage($value, $format);
+ } else {
+ $value = self::formatAsNumber($value, $format);
+ }
+ }
+
+ // Additional formatting provided by callback function
+ if ($callBack !== null) {
+ [$writerInstance, $function] = $callBack;
+ $value = $writerInstance->$function($value, $colors);
+ }
+
+ return $value;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php
new file mode 100755
index 0000000..f695837
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php
@@ -0,0 +1,186 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style;
+
+class Protection extends Supervisor
+{
+ /** Protection styles */
+ const PROTECTION_INHERIT = 'inherit';
+ const PROTECTION_PROTECTED = 'protected';
+ const PROTECTION_UNPROTECTED = 'unprotected';
+
+ /**
+ * Locked.
+ *
+ * @var string
+ */
+ protected $locked;
+
+ /**
+ * Hidden.
+ *
+ * @var string
+ */
+ protected $hidden;
+
+ /**
+ * Create a new Protection.
+ *
+ * @param bool $isSupervisor Flag indicating if this is a supervisor or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ * @param bool $isConditional Flag indicating if this is a conditional style or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ */
+ public function __construct($isSupervisor = false, $isConditional = false)
+ {
+ // Supervisor?
+ parent::__construct($isSupervisor);
+
+ // Initialise values
+ if (!$isConditional) {
+ $this->locked = self::PROTECTION_INHERIT;
+ $this->hidden = self::PROTECTION_INHERIT;
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Protection
+ */
+ public function getSharedComponent()
+ {
+ return $this->parent->getSharedComponent()->getProtection();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['protection' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ * <code>
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getLocked()->applyFromArray(
+ * [
+ * 'locked' => TRUE,
+ * 'hidden' => FALSE
+ * ]
+ * );
+ * </code>
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['locked'])) {
+ $this->setLocked($pStyles['locked']);
+ }
+ if (isset($pStyles['hidden'])) {
+ $this->setHidden($pStyles['hidden']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get locked.
+ *
+ * @return string
+ */
+ public function getLocked()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getLocked();
+ }
+
+ return $this->locked;
+ }
+
+ /**
+ * Set locked.
+ *
+ * @param string $pValue see self::PROTECTION_*
+ *
+ * @return $this
+ */
+ public function setLocked($pValue)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['locked' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->locked = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hidden.
+ *
+ * @return string
+ */
+ public function getHidden()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHidden();
+ }
+
+ return $this->hidden;
+ }
+
+ /**
+ * Set hidden.
+ *
+ * @param string $pValue see self::PROTECTION_*
+ *
+ * @return $this
+ */
+ public function setHidden($pValue)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['hidden' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->hidden = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->locked .
+ $this->hidden .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php
new file mode 100755
index 0000000..533a7c3
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php
@@ -0,0 +1,639 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Style extends Supervisor
+{
+ /**
+ * Font.
+ *
+ * @var Font
+ */
+ protected $font;
+
+ /**
+ * Fill.
+ *
+ * @var Fill
+ */
+ protected $fill;
+
+ /**
+ * Borders.
+ *
+ * @var Borders
+ */
+ protected $borders;
+
+ /**
+ * Alignment.
+ *
+ * @var Alignment
+ */
+ protected $alignment;
+
+ /**
+ * Number Format.
+ *
+ * @var NumberFormat
+ */
+ protected $numberFormat;
+
+ /**
+ * Conditional styles.
+ *
+ * @var Conditional[]
+ */
+ protected $conditionalStyles;
+
+ /**
+ * Protection.
+ *
+ * @var Protection
+ */
+ protected $protection;
+
+ /**
+ * Index of style in collection. Only used for real style.
+ *
+ * @var int
+ */
+ protected $index;
+
+ /**
+ * Use Quote Prefix when displaying in cell editor. Only used for real style.
+ *
+ * @var bool
+ */
+ protected $quotePrefix = false;
+
+ /**
+ * Create a new Style.
+ *
+ * @param bool $isSupervisor Flag indicating if this is a supervisor or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ * @param bool $isConditional Flag indicating if this is a conditional style or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ */
+ public function __construct($isSupervisor = false, $isConditional = false)
+ {
+ parent::__construct($isSupervisor);
+
+ // Initialise values
+ $this->conditionalStyles = [];
+ $this->font = new Font($isSupervisor, $isConditional);
+ $this->fill = new Fill($isSupervisor, $isConditional);
+ $this->borders = new Borders($isSupervisor, $isConditional);
+ $this->alignment = new Alignment($isSupervisor, $isConditional);
+ $this->numberFormat = new NumberFormat($isSupervisor, $isConditional);
+ $this->protection = new Protection($isSupervisor, $isConditional);
+
+ // bind parent if we are a supervisor
+ if ($isSupervisor) {
+ $this->font->bindParent($this);
+ $this->fill->bindParent($this);
+ $this->borders->bindParent($this);
+ $this->alignment->bindParent($this);
+ $this->numberFormat->bindParent($this);
+ $this->protection->bindParent($this);
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Style
+ */
+ public function getSharedComponent()
+ {
+ $activeSheet = $this->getActiveSheet();
+ $selectedCell = $this->getActiveCell(); // e.g. 'A1'
+
+ if ($activeSheet->cellExists($selectedCell)) {
+ $xfIndex = $activeSheet->getCell($selectedCell)->getXfIndex();
+ } else {
+ $xfIndex = 0;
+ }
+
+ return $this->parent->getCellXfByIndex($xfIndex);
+ }
+
+ /**
+ * Get parent. Only used for style supervisor.
+ *
+ * @return Spreadsheet
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['quotePrefix' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ * <code>
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->applyFromArray(
+ * [
+ * 'font' => [
+ * 'name' => 'Arial',
+ * 'bold' => true,
+ * 'italic' => false,
+ * 'underline' => Font::UNDERLINE_DOUBLE,
+ * 'strikethrough' => false,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ],
+ * 'borders' => [
+ * 'bottom' => [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ],
+ * 'top' => [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * ],
+ * 'alignment' => [
+ * 'horizontal' => Alignment::HORIZONTAL_CENTER,
+ * 'vertical' => Alignment::VERTICAL_CENTER,
+ * 'wrapText' => true,
+ * ],
+ * 'quotePrefix' => true
+ * ]
+ * );
+ * </code>
+ *
+ * @param array $pStyles Array containing style information
+ * @param bool $pAdvanced advanced mode for setting borders
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles, $pAdvanced = true)
+ {
+ if ($this->isSupervisor) {
+ $pRange = $this->getSelectedCells();
+
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ // Is it a cell range or a single cell?
+ if (strpos($pRange, ':') === false) {
+ $rangeA = $pRange;
+ $rangeB = $pRange;
+ } else {
+ [$rangeA, $rangeB] = explode(':', $pRange);
+ }
+
+ // Calculate range outer borders
+ $rangeStart = Coordinate::coordinateFromString($rangeA);
+ $rangeEnd = Coordinate::coordinateFromString($rangeB);
+
+ // Translate column into index
+ $rangeStart[0] = Coordinate::columnIndexFromString($rangeStart[0]);
+ $rangeEnd[0] = Coordinate::columnIndexFromString($rangeEnd[0]);
+
+ // Make sure we can loop upwards on rows and columns
+ if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
+ $tmp = $rangeStart;
+ $rangeStart = $rangeEnd;
+ $rangeEnd = $tmp;
+ }
+
+ // ADVANCED MODE:
+ if ($pAdvanced && isset($pStyles['borders'])) {
+ // 'allBorders' is a shorthand property for 'outline' and 'inside' and
+ // it applies to components that have not been set explicitly
+ if (isset($pStyles['borders']['allBorders'])) {
+ foreach (['outline', 'inside'] as $component) {
+ if (!isset($pStyles['borders'][$component])) {
+ $pStyles['borders'][$component] = $pStyles['borders']['allBorders'];
+ }
+ }
+ unset($pStyles['borders']['allBorders']); // not needed any more
+ }
+ // 'outline' is a shorthand property for 'top', 'right', 'bottom', 'left'
+ // it applies to components that have not been set explicitly
+ if (isset($pStyles['borders']['outline'])) {
+ foreach (['top', 'right', 'bottom', 'left'] as $component) {
+ if (!isset($pStyles['borders'][$component])) {
+ $pStyles['borders'][$component] = $pStyles['borders']['outline'];
+ }
+ }
+ unset($pStyles['borders']['outline']); // not needed any more
+ }
+ // 'inside' is a shorthand property for 'vertical' and 'horizontal'
+ // it applies to components that have not been set explicitly
+ if (isset($pStyles['borders']['inside'])) {
+ foreach (['vertical', 'horizontal'] as $component) {
+ if (!isset($pStyles['borders'][$component])) {
+ $pStyles['borders'][$component] = $pStyles['borders']['inside'];
+ }
+ }
+ unset($pStyles['borders']['inside']); // not needed any more
+ }
+ // width and height characteristics of selection, 1, 2, or 3 (for 3 or more)
+ $xMax = min($rangeEnd[0] - $rangeStart[0] + 1, 3);
+ $yMax = min($rangeEnd[1] - $rangeStart[1] + 1, 3);
+
+ // loop through up to 3 x 3 = 9 regions
+ for ($x = 1; $x <= $xMax; ++$x) {
+ // start column index for region
+ $colStart = ($x == 3) ?
+ Coordinate::stringFromColumnIndex($rangeEnd[0])
+ : Coordinate::stringFromColumnIndex($rangeStart[0] + $x - 1);
+ // end column index for region
+ $colEnd = ($x == 1) ?
+ Coordinate::stringFromColumnIndex($rangeStart[0])
+ : Coordinate::stringFromColumnIndex($rangeEnd[0] - $xMax + $x);
+
+ for ($y = 1; $y <= $yMax; ++$y) {
+ // which edges are touching the region
+ $edges = [];
+ if ($x == 1) {
+ // are we at left edge
+ $edges[] = 'left';
+ }
+ if ($x == $xMax) {
+ // are we at right edge
+ $edges[] = 'right';
+ }
+ if ($y == 1) {
+ // are we at top edge?
+ $edges[] = 'top';
+ }
+ if ($y == $yMax) {
+ // are we at bottom edge?
+ $edges[] = 'bottom';
+ }
+
+ // start row index for region
+ $rowStart = ($y == 3) ?
+ $rangeEnd[1] : $rangeStart[1] + $y - 1;
+
+ // end row index for region
+ $rowEnd = ($y == 1) ?
+ $rangeStart[1] : $rangeEnd[1] - $yMax + $y;
+
+ // build range for region
+ $range = $colStart . $rowStart . ':' . $colEnd . $rowEnd;
+
+ // retrieve relevant style array for region
+ $regionStyles = $pStyles;
+ unset($regionStyles['borders']['inside']);
+
+ // what are the inner edges of the region when looking at the selection
+ $innerEdges = array_diff(['top', 'right', 'bottom', 'left'], $edges);
+
+ // inner edges that are not touching the region should take the 'inside' border properties if they have been set
+ foreach ($innerEdges as $innerEdge) {
+ switch ($innerEdge) {
+ case 'top':
+ case 'bottom':
+ // should pick up 'horizontal' border property if set
+ if (isset($pStyles['borders']['horizontal'])) {
+ $regionStyles['borders'][$innerEdge] = $pStyles['borders']['horizontal'];
+ } else {
+ unset($regionStyles['borders'][$innerEdge]);
+ }
+
+ break;
+ case 'left':
+ case 'right':
+ // should pick up 'vertical' border property if set
+ if (isset($pStyles['borders']['vertical'])) {
+ $regionStyles['borders'][$innerEdge] = $pStyles['borders']['vertical'];
+ } else {
+ unset($regionStyles['borders'][$innerEdge]);
+ }
+
+ break;
+ }
+ }
+
+ // apply region style to region by calling applyFromArray() in simple mode
+ $this->getActiveSheet()->getStyle($range)->applyFromArray($regionStyles, false);
+ }
+ }
+
+ // restore initial cell selection range
+ $this->getActiveSheet()->getStyle($pRange);
+
+ return $this;
+ }
+
+ // SIMPLE MODE:
+ // Selection type, inspect
+ if (preg_match('/^[A-Z]+1:[A-Z]+1048576$/', $pRange)) {
+ $selectionType = 'COLUMN';
+ } elseif (preg_match('/^A\d+:XFD\d+$/', $pRange)) {
+ $selectionType = 'ROW';
+ } else {
+ $selectionType = 'CELL';
+ }
+
+ // First loop through columns, rows, or cells to find out which styles are affected by this operation
+ switch ($selectionType) {
+ case 'COLUMN':
+ $oldXfIndexes = [];
+ for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
+ $oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true;
+ }
+
+ break;
+ case 'ROW':
+ $oldXfIndexes = [];
+ for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
+ if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() == null) {
+ $oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style
+ } else {
+ $oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true;
+ }
+ }
+
+ break;
+ case 'CELL':
+ $oldXfIndexes = [];
+ for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
+ for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
+ $oldXfIndexes[$this->getActiveSheet()->getCellByColumnAndRow($col, $row)->getXfIndex()] = true;
+ }
+ }
+
+ break;
+ }
+
+ // clone each of the affected styles, apply the style array, and add the new styles to the workbook
+ $workbook = $this->getActiveSheet()->getParent();
+ foreach ($oldXfIndexes as $oldXfIndex => $dummy) {
+ $style = $workbook->getCellXfByIndex($oldXfIndex);
+ $newStyle = clone $style;
+ $newStyle->applyFromArray($pStyles);
+
+ if ($existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode())) {
+ // there is already such cell Xf in our collection
+ $newXfIndexes[$oldXfIndex] = $existingStyle->getIndex();
+ } else {
+ // we don't have such a cell Xf, need to add
+ $workbook->addCellXf($newStyle);
+ $newXfIndexes[$oldXfIndex] = $newStyle->getIndex();
+ }
+ }
+
+ // Loop through columns, rows, or cells again and update the XF index
+ switch ($selectionType) {
+ case 'COLUMN':
+ for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
+ $columnDimension = $this->getActiveSheet()->getColumnDimensionByColumn($col);
+ $oldXfIndex = $columnDimension->getXfIndex();
+ $columnDimension->setXfIndex($newXfIndexes[$oldXfIndex]);
+ }
+
+ break;
+ case 'ROW':
+ for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
+ $rowDimension = $this->getActiveSheet()->getRowDimension($row);
+ $oldXfIndex = $rowDimension->getXfIndex() === null ?
+ 0 : $rowDimension->getXfIndex(); // row without explicit style should be formatted based on default style
+ $rowDimension->setXfIndex($newXfIndexes[$oldXfIndex]);
+ }
+
+ break;
+ case 'CELL':
+ for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
+ for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
+ $cell = $this->getActiveSheet()->getCellByColumnAndRow($col, $row);
+ $oldXfIndex = $cell->getXfIndex();
+ $cell->setXfIndex($newXfIndexes[$oldXfIndex]);
+ }
+ }
+
+ break;
+ }
+ } else {
+ // not a supervisor, just apply the style array directly on style object
+ if (isset($pStyles['fill'])) {
+ $this->getFill()->applyFromArray($pStyles['fill']);
+ }
+ if (isset($pStyles['font'])) {
+ $this->getFont()->applyFromArray($pStyles['font']);
+ }
+ if (isset($pStyles['borders'])) {
+ $this->getBorders()->applyFromArray($pStyles['borders']);
+ }
+ if (isset($pStyles['alignment'])) {
+ $this->getAlignment()->applyFromArray($pStyles['alignment']);
+ }
+ if (isset($pStyles['numberFormat'])) {
+ $this->getNumberFormat()->applyFromArray($pStyles['numberFormat']);
+ }
+ if (isset($pStyles['protection'])) {
+ $this->getProtection()->applyFromArray($pStyles['protection']);
+ }
+ if (isset($pStyles['quotePrefix'])) {
+ $this->quotePrefix = $pStyles['quotePrefix'];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Fill.
+ *
+ * @return Fill
+ */
+ public function getFill()
+ {
+ return $this->fill;
+ }
+
+ /**
+ * Get Font.
+ *
+ * @return Font
+ */
+ public function getFont()
+ {
+ return $this->font;
+ }
+
+ /**
+ * Set font.
+ *
+ * @return $this
+ */
+ public function setFont(Font $font)
+ {
+ $this->font = $font;
+
+ return $this;
+ }
+
+ /**
+ * Get Borders.
+ *
+ * @return Borders
+ */
+ public function getBorders()
+ {
+ return $this->borders;
+ }
+
+ /**
+ * Get Alignment.
+ *
+ * @return Alignment
+ */
+ public function getAlignment()
+ {
+ return $this->alignment;
+ }
+
+ /**
+ * Get Number Format.
+ *
+ * @return NumberFormat
+ */
+ public function getNumberFormat()
+ {
+ return $this->numberFormat;
+ }
+
+ /**
+ * Get Conditional Styles. Only used on supervisor.
+ *
+ * @return Conditional[]
+ */
+ public function getConditionalStyles()
+ {
+ return $this->getActiveSheet()->getConditionalStyles($this->getActiveCell());
+ }
+
+ /**
+ * Set Conditional Styles. Only used on supervisor.
+ *
+ * @param Conditional[] $pValue Array of conditional styles
+ *
+ * @return $this
+ */
+ public function setConditionalStyles(array $pValue)
+ {
+ $this->getActiveSheet()->setConditionalStyles($this->getSelectedCells(), $pValue);
+
+ return $this;
+ }
+
+ /**
+ * Get Protection.
+ *
+ * @return Protection
+ */
+ public function getProtection()
+ {
+ return $this->protection;
+ }
+
+ /**
+ * Get quote prefix.
+ *
+ * @return bool
+ */
+ public function getQuotePrefix()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getQuotePrefix();
+ }
+
+ return $this->quotePrefix;
+ }
+
+ /**
+ * Set quote prefix.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setQuotePrefix($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = ['quotePrefix' => $pValue];
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->quotePrefix = (bool) $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ $hashConditionals = '';
+ foreach ($this->conditionalStyles as $conditional) {
+ $hashConditionals .= $conditional->getHashCode();
+ }
+
+ return md5(
+ $this->fill->getHashCode() .
+ $this->font->getHashCode() .
+ $this->borders->getHashCode() .
+ $this->alignment->getHashCode() .
+ $this->numberFormat->getHashCode() .
+ $hashConditionals .
+ $this->protection->getHashCode() .
+ ($this->quotePrefix ? 't' : 'f') .
+ __CLASS__
+ );
+ }
+
+ /**
+ * Get own index in style collection.
+ *
+ * @return int
+ */
+ public function getIndex()
+ {
+ return $this->index;
+ }
+
+ /**
+ * Set own index in style collection.
+ *
+ * @param int $pValue
+ */
+ public function setIndex($pValue): void
+ {
+ $this->index = $pValue;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php
new file mode 100755
index 0000000..1a70097
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Style;
+
+use PhpOffice\PhpSpreadsheet\IComparable;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+abstract class Supervisor implements IComparable
+{
+ /**
+ * Supervisor?
+ *
+ * @var bool
+ */
+ protected $isSupervisor;
+
+ /**
+ * Parent. Only used for supervisor.
+ *
+ * @var Spreadsheet|Style
+ */
+ protected $parent;
+
+ /**
+ * Parent property name.
+ *
+ * @var null|string
+ */
+ protected $parentPropertyName;
+
+ /**
+ * Create a new Supervisor.
+ *
+ * @param bool $isSupervisor Flag indicating if this is a supervisor or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ */
+ public function __construct($isSupervisor = false)
+ {
+ // Supervisor?
+ $this->isSupervisor = $isSupervisor;
+ }
+
+ /**
+ * Bind parent. Only used for supervisor.
+ *
+ * @param Spreadsheet|Style $parent
+ * @param null|string $parentPropertyName
+ *
+ * @return $this
+ */
+ public function bindParent($parent, $parentPropertyName = null)
+ {
+ $this->parent = $parent;
+ $this->parentPropertyName = $parentPropertyName;
+
+ return $this;
+ }
+
+ /**
+ * Is this a supervisor or a cell style component?
+ *
+ * @return bool
+ */
+ public function getIsSupervisor()
+ {
+ return $this->isSupervisor;
+ }
+
+ /**
+ * Get the currently active sheet. Only used for supervisor.
+ *
+ * @return Worksheet
+ */
+ public function getActiveSheet()
+ {
+ return $this->parent->getActiveSheet();
+ }
+
+ /**
+ * Get the currently active cell coordinate in currently active sheet.
+ * Only used for supervisor.
+ *
+ * @return string E.g. 'A1'
+ */
+ public function getSelectedCells()
+ {
+ return $this->getActiveSheet()->getSelectedCells();
+ }
+
+ /**
+ * Get the currently active cell coordinate in currently active sheet.
+ * Only used for supervisor.
+ *
+ * @return string E.g. 'A1'
+ */
+ public function getActiveCell()
+ {
+ return $this->getActiveSheet()->getActiveCell();
+ }
+
+ /**
+ * 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)) && ($key != 'parent')) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php
new file mode 100755
index 0000000..c085b59
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php
@@ -0,0 +1,857 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+
+class AutoFilter
+{
+ /**
+ * Autofilter Worksheet.
+ *
+ * @var Worksheet
+ */
+ private $workSheet;
+
+ /**
+ * Autofilter Range.
+ *
+ * @var string
+ */
+ private $range = '';
+
+ /**
+ * Autofilter Column Ruleset.
+ *
+ * @var AutoFilter\Column[]
+ */
+ private $columns = [];
+
+ /**
+ * Create a new AutoFilter.
+ *
+ * @param string $pRange Cell range (i.e. A1:E10)
+ * @param Worksheet $pSheet
+ */
+ public function __construct($pRange = '', ?Worksheet $pSheet = null)
+ {
+ $this->range = $pRange;
+ $this->workSheet = $pSheet;
+ }
+
+ /**
+ * Get AutoFilter Parent Worksheet.
+ *
+ * @return Worksheet
+ */
+ public function getParent()
+ {
+ return $this->workSheet;
+ }
+
+ /**
+ * Set AutoFilter Parent Worksheet.
+ *
+ * @param Worksheet $pSheet
+ *
+ * @return $this
+ */
+ public function setParent(?Worksheet $pSheet = null)
+ {
+ $this->workSheet = $pSheet;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Range.
+ *
+ * @return string
+ */
+ public function getRange()
+ {
+ return $this->range;
+ }
+
+ /**
+ * Set AutoFilter Range.
+ *
+ * @param string $pRange Cell range (i.e. A1:E10)
+ *
+ * @return $this
+ */
+ public function setRange($pRange)
+ {
+ // extract coordinate
+ [$worksheet, $pRange] = Worksheet::extractSheetTitle($pRange, true);
+
+ if (strpos($pRange, ':') !== false) {
+ $this->range = $pRange;
+ } elseif (empty($pRange)) {
+ $this->range = '';
+ } else {
+ throw new PhpSpreadsheetException('Autofilter must be set on a range of cells.');
+ }
+
+ if (empty($pRange)) {
+ // Discard all column rules
+ $this->columns = [];
+ } else {
+ // 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;
+ }
+
+ /**
+ * Get all AutoFilter Columns.
+ *
+ * @return AutoFilter\Column[]
+ */
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * Validate that the specified column is in the AutoFilter range.
+ *
+ * @param string $column Column name (e.g. A)
+ *
+ * @return int The column offset within the autofilter range
+ */
+ public function testColumnInRange($column)
+ {
+ if (empty($this->range)) {
+ throw new PhpSpreadsheetException('No autofilter 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 autofilter range.');
+ }
+
+ return $columnIndex - $rangeStart[0];
+ }
+
+ /**
+ * Get a specified AutoFilter Column Offset within the defined AutoFilter range.
+ *
+ * @param string $pColumn Column name (e.g. A)
+ *
+ * @return int The offset of the specified column within the autofilter range
+ */
+ public function getColumnOffset($pColumn)
+ {
+ return $this->testColumnInRange($pColumn);
+ }
+
+ /**
+ * Get a specified AutoFilter Column.
+ *
+ * @param string $pColumn Column name (e.g. A)
+ *
+ * @return AutoFilter\Column
+ */
+ public function getColumn($pColumn)
+ {
+ $this->testColumnInRange($pColumn);
+
+ if (!isset($this->columns[$pColumn])) {
+ $this->columns[$pColumn] = new AutoFilter\Column($pColumn, $this);
+ }
+
+ return $this->columns[$pColumn];
+ }
+
+ /**
+ * Get a specified AutoFilter Column by it's offset.
+ *
+ * @param int $pColumnOffset Column offset within range (starting from 0)
+ *
+ * @return AutoFilter\Column
+ */
+ public function getColumnByOffset($pColumnOffset)
+ {
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
+ $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $pColumnOffset);
+
+ return $this->getColumn($pColumn);
+ }
+
+ /**
+ * Set AutoFilter.
+ *
+ * @param AutoFilter\Column|string $pColumn
+ * A simple string containing a Column ID like 'A' is permitted
+ *
+ * @return $this
+ */
+ public function setColumn($pColumn)
+ {
+ if ((is_string($pColumn)) && (!empty($pColumn))) {
+ $column = $pColumn;
+ } elseif (is_object($pColumn) && ($pColumn instanceof AutoFilter\Column)) {
+ $column = $pColumn->getColumnIndex();
+ } else {
+ throw new PhpSpreadsheetException('Column is not within the autofilter range.');
+ }
+ $this->testColumnInRange($column);
+
+ if (is_string($pColumn)) {
+ $this->columns[$pColumn] = new AutoFilter\Column($pColumn, $this);
+ } elseif (is_object($pColumn) && ($pColumn instanceof AutoFilter\Column)) {
+ $pColumn->setParent($this);
+ $this->columns[$column] = $pColumn;
+ }
+ ksort($this->columns);
+
+ return $this;
+ }
+
+ /**
+ * Clear a specified AutoFilter Column.
+ *
+ * @param string $pColumn Column name (e.g. A)
+ *
+ * @return $this
+ */
+ public function clearColumn($pColumn)
+ {
+ $this->testColumnInRange($pColumn);
+
+ if (isset($this->columns[$pColumn])) {
+ unset($this->columns[$pColumn]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Shift an AutoFilter Column Rule to a different column.
+ *
+ * Note: This method bypasses validation of the destination column to ensure it is within this AutoFilter 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)
+ *
+ * @return $this
+ */
+ public function shiftColumn($fromColumn, $toColumn)
+ {
+ $fromColumn = strtoupper($fromColumn);
+ $toColumn = strtoupper($toColumn);
+
+ if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) {
+ $this->columns[$fromColumn]->setParent();
+ $this->columns[$fromColumn]->setColumnIndex($toColumn);
+ $this->columns[$toColumn] = $this->columns[$fromColumn];
+ $this->columns[$toColumn]->setParent($this);
+ unset($this->columns[$fromColumn]);
+
+ ksort($this->columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Test if cell value is in the defined set of values.
+ *
+ * @param mixed $cellValue
+ * @param mixed[] $dataSet
+ *
+ * @return bool
+ */
+ private static function filterTestInSimpleDataSet($cellValue, $dataSet)
+ {
+ $dataSetValues = $dataSet['filterValues'];
+ $blanks = $dataSet['blanks'];
+ if (($cellValue == '') || ($cellValue === null)) {
+ return $blanks;
+ }
+
+ return in_array($cellValue, $dataSetValues);
+ }
+
+ /**
+ * Test if cell value is in the defined set of Excel date values.
+ *
+ * @param mixed $cellValue
+ * @param mixed[] $dataSet
+ *
+ * @return bool
+ */
+ private static function filterTestInDateGroupSet($cellValue, $dataSet)
+ {
+ $dateSet = $dataSet['filterValues'];
+ $blanks = $dataSet['blanks'];
+ if (($cellValue == '') || ($cellValue === null)) {
+ return $blanks;
+ }
+
+ if (is_numeric($cellValue)) {
+ $dateValue = Date::excelToTimestamp($cellValue);
+ if ($cellValue < 1) {
+ // Just the time part
+ $dtVal = date('His', $dateValue);
+ $dateSet = $dateSet['time'];
+ } elseif ($cellValue == floor($cellValue)) {
+ // Just the date part
+ $dtVal = date('Ymd', $dateValue);
+ $dateSet = $dateSet['date'];
+ } else {
+ // date and time parts
+ $dtVal = date('YmdHis', $dateValue);
+ $dateSet = $dateSet['dateTime'];
+ }
+ foreach ($dateSet as $dateValue) {
+ // Use of substr to extract value at the appropriate group level
+ if (substr($dtVal, 0, strlen($dateValue)) == $dateValue) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Test if cell value is within a set of values defined by a ruleset.
+ *
+ * @param mixed $cellValue
+ * @param mixed[] $ruleSet
+ *
+ * @return bool
+ */
+ private static function filterTestInCustomDataSet($cellValue, $ruleSet)
+ {
+ $dataSet = $ruleSet['filterRules'];
+ $join = $ruleSet['join'];
+ $customRuleForBlanks = $ruleSet['customRuleForBlanks'] ?? false;
+
+ if (!$customRuleForBlanks) {
+ // Blank cells are always ignored, so return a FALSE
+ if (($cellValue == '') || ($cellValue === null)) {
+ return false;
+ }
+ }
+ $returnVal = ($join == AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND);
+ foreach ($dataSet as $rule) {
+ $retVal = false;
+
+ if (is_numeric($rule['value'])) {
+ // Numeric values are tested using the appropriate operator
+ switch ($rule['operator']) {
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL:
+ $retVal = ($cellValue == $rule['value']);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL:
+ $retVal = ($cellValue != $rule['value']);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN:
+ $retVal = ($cellValue > $rule['value']);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL:
+ $retVal = ($cellValue >= $rule['value']);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN:
+ $retVal = ($cellValue < $rule['value']);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL:
+ $retVal = ($cellValue <= $rule['value']);
+
+ break;
+ }
+ } elseif ($rule['value'] == '') {
+ switch ($rule['operator']) {
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL:
+ $retVal = (($cellValue == '') || ($cellValue === null));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL:
+ $retVal = (($cellValue != '') && ($cellValue !== null));
+
+ break;
+ default:
+ $retVal = true;
+
+ break;
+ }
+ } else {
+ // String values are always tested for equality, factoring in for wildcards (hence a regexp test)
+ $retVal = preg_match('/^' . $rule['value'] . '$/i', $cellValue);
+ }
+ // If there are multiple conditions, then we need to test both using the appropriate join operator
+ switch ($join) {
+ case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR:
+ $returnVal = $returnVal || $retVal;
+ // Break as soon as we have a TRUE match for OR joins,
+ // to avoid unnecessary additional code execution
+ if ($returnVal) {
+ return $returnVal;
+ }
+
+ break;
+ case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND:
+ $returnVal = $returnVal && $retVal;
+
+ break;
+ }
+ }
+
+ return $returnVal;
+ }
+
+ /**
+ * Test if cell date value is matches a set of values defined by a set of months.
+ *
+ * @param mixed $cellValue
+ * @param mixed[] $monthSet
+ *
+ * @return bool
+ */
+ private static function filterTestInPeriodDateSet($cellValue, $monthSet)
+ {
+ // Blank cells are always ignored, so return a FALSE
+ if (($cellValue == '') || ($cellValue === null)) {
+ return false;
+ }
+
+ if (is_numeric($cellValue)) {
+ $dateValue = date('m', Date::excelToTimestamp($cellValue));
+ if (in_array($dateValue, $monthSet)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Search/Replace arrays to convert Excel wildcard syntax to a regexp syntax for preg_matching.
+ *
+ * @var array
+ */
+ private static $fromReplace = ['\*', '\?', '~~', '~.*', '~.?'];
+
+ private static $toReplace = ['.*', '.', '~', '\*', '\?'];
+
+ /**
+ * Convert a dynamic rule daterange to a custom filter range expression for ease of calculation.
+ *
+ * @param string $dynamicRuleType
+ * @param AutoFilter\Column $filterColumn
+ *
+ * @return mixed[]
+ */
+ private function dynamicFilterDateRange($dynamicRuleType, &$filterColumn)
+ {
+ $rDateType = Functions::getReturnDateType();
+ Functions::setReturnDateType(Functions::RETURNDATE_PHP_NUMERIC);
+ $val = $maxVal = null;
+
+ $ruleValues = [];
+ $baseDate = DateTime::DATENOW();
+ // Calculate start/end dates for the required date range based on current date
+ switch ($dynamicRuleType) {
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK:
+ $baseDate = strtotime('-7 days', $baseDate);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK:
+ $baseDate = strtotime('-7 days', $baseDate);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH:
+ $baseDate = strtotime('-1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH:
+ $baseDate = strtotime('+1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER:
+ $baseDate = strtotime('-3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER:
+ $baseDate = strtotime('+3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR:
+ $baseDate = strtotime('-1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR:
+ $baseDate = strtotime('+1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ }
+
+ switch ($dynamicRuleType) {
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TODAY:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW:
+ $maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate));
+ $val = (int) Date::PHPToExcel($baseDate);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE:
+ $maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate));
+ $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR:
+ $maxVal = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 31, 12, date('Y', $baseDate)));
+ ++$maxVal;
+ $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER:
+ $thisMonth = date('m', $baseDate);
+ $thisQuarter = floor(--$thisMonth / 3);
+ $maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), (1 + $thisQuarter) * 3, date('Y', $baseDate)));
+ ++$maxVal;
+ $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1 + $thisQuarter * 3, date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH:
+ $maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), date('m', $baseDate), date('Y', $baseDate)));
+ ++$maxVal;
+ $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK:
+ $dayOfWeek = date('w', $baseDate);
+ $val = (int) Date::PHPToExcel($baseDate) - $dayOfWeek;
+ $maxVal = $val + 7;
+
+ break;
+ }
+
+ switch ($dynamicRuleType) {
+ // Adjust Today dates for Yesterday and Tomorrow
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY:
+ --$maxVal;
+ --$val;
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW:
+ ++$maxVal;
+ ++$val;
+
+ break;
+ }
+
+ // Set the filter column rule attributes ready for writing
+ $filterColumn->setAttributes(['val' => $val, 'maxVal' => $maxVal]);
+
+ // Set the rules for identifying rows for hide/show
+ $ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, 'value' => $val];
+ $ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, 'value' => $maxVal];
+ Functions::setReturnDateType($rDateType);
+
+ return ['method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND]];
+ }
+
+ private function calculateTopTenValue($columnID, $startRow, $endRow, $ruleType, $ruleValue)
+ {
+ $range = $columnID . $startRow . ':' . $columnID . $endRow;
+ $dataValues = Functions::flattenArray($this->workSheet->rangeToArray($range, null, true, false));
+
+ $dataValues = array_filter($dataValues);
+ if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) {
+ rsort($dataValues);
+ } else {
+ sort($dataValues);
+ }
+
+ return array_pop(array_slice($dataValues, 0, $ruleValue));
+ }
+
+ /**
+ * Apply the AutoFilter rules to the AutoFilter Range.
+ *
+ * @return $this
+ */
+ public function showHideRows()
+ {
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
+
+ // The heading row should always be visible
+ $this->workSheet->getRowDimension($rangeStart[1])->setVisible(true);
+
+ $columnFilterTests = [];
+ foreach ($this->columns as $columnID => $filterColumn) {
+ $rules = $filterColumn->getRules();
+ switch ($filterColumn->getFilterType()) {
+ case AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER:
+ $ruleType = null;
+ $ruleValues = [];
+ // Build a list of the filter value selections
+ foreach ($rules as $rule) {
+ $ruleType = $rule->getRuleType();
+ $ruleValues[] = $rule->getValue();
+ }
+ // Test if we want to include blanks in our filter criteria
+ $blanks = false;
+ $ruleDataSet = array_filter($ruleValues);
+ if (count($ruleValues) != count($ruleDataSet)) {
+ $blanks = true;
+ }
+ if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_FILTER) {
+ // Filter on absolute values
+ $columnFilterTests[$columnID] = [
+ 'method' => 'filterTestInSimpleDataSet',
+ 'arguments' => ['filterValues' => $ruleDataSet, 'blanks' => $blanks],
+ ];
+ } else {
+ // Filter on date group values
+ $arguments = [
+ 'date' => [],
+ 'time' => [],
+ 'dateTime' => [],
+ ];
+ foreach ($ruleDataSet as $ruleValue) {
+ $date = $time = '';
+ if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR])) &&
+ ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR] !== '')) {
+ $date .= sprintf('%04d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR]);
+ }
+ if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH])) &&
+ ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH] != '')) {
+ $date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH]);
+ }
+ if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY])) &&
+ ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY] !== '')) {
+ $date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY]);
+ }
+ if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR])) &&
+ ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR] !== '')) {
+ $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR]);
+ }
+ if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE])) &&
+ ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE] !== '')) {
+ $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE]);
+ }
+ if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND])) &&
+ ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND] !== '')) {
+ $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND]);
+ }
+ $dateTime = $date . $time;
+ $arguments['date'][] = $date;
+ $arguments['time'][] = $time;
+ $arguments['dateTime'][] = $dateTime;
+ }
+ // Remove empty elements
+ $arguments['date'] = array_filter($arguments['date']);
+ $arguments['time'] = array_filter($arguments['time']);
+ $arguments['dateTime'] = array_filter($arguments['dateTime']);
+ $columnFilterTests[$columnID] = [
+ 'method' => 'filterTestInDateGroupSet',
+ 'arguments' => ['filterValues' => $arguments, 'blanks' => $blanks],
+ ];
+ }
+
+ break;
+ case AutoFilter\Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER:
+ $customRuleForBlanks = false;
+ $ruleValues = [];
+ // Build a list of the filter value selections
+ foreach ($rules as $rule) {
+ $ruleValue = $rule->getValue();
+ if (!is_numeric($ruleValue)) {
+ // Convert to a regexp allowing for regexp reserved characters, wildcards and escaped wildcards
+ $ruleValue = preg_quote($ruleValue);
+ $ruleValue = str_replace(self::$fromReplace, self::$toReplace, $ruleValue);
+ if (trim($ruleValue) == '') {
+ $customRuleForBlanks = true;
+ $ruleValue = trim($ruleValue);
+ }
+ }
+ $ruleValues[] = ['operator' => $rule->getOperator(), 'value' => $ruleValue];
+ }
+ $join = $filterColumn->getJoin();
+ $columnFilterTests[$columnID] = [
+ 'method' => 'filterTestInCustomDataSet',
+ 'arguments' => ['filterRules' => $ruleValues, 'join' => $join, 'customRuleForBlanks' => $customRuleForBlanks],
+ ];
+
+ break;
+ case AutoFilter\Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER:
+ $ruleValues = [];
+ foreach ($rules as $rule) {
+ // We should only ever have one Dynamic Filter Rule anyway
+ $dynamicRuleType = $rule->getGrouping();
+ if (($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) ||
+ ($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE)) {
+ // Number (Average) based
+ // Calculate the average
+ $averageFormula = '=AVERAGE(' . $columnID . ($rangeStart[1] + 1) . ':' . $columnID . $rangeEnd[1] . ')';
+ $average = Calculation::getInstance()->calculateFormula($averageFormula, null, $this->workSheet->getCell('A1'));
+ // Set above/below rule based on greaterThan or LessTan
+ $operator = ($dynamicRuleType === AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE)
+ ? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN
+ : AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN;
+ $ruleValues[] = [
+ 'operator' => $operator,
+ 'value' => $average,
+ ];
+ $columnFilterTests[$columnID] = [
+ 'method' => 'filterTestInCustomDataSet',
+ 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR],
+ ];
+ } else {
+ // Date based
+ if ($dynamicRuleType[0] == 'M' || $dynamicRuleType[0] == 'Q') {
+ $periodType = '';
+ $period = 0;
+ // Month or Quarter
+ sscanf($dynamicRuleType, '%[A-Z]%d', $periodType, $period);
+ if ($periodType == 'M') {
+ $ruleValues = [$period];
+ } else {
+ --$period;
+ $periodEnd = (1 + $period) * 3;
+ $periodStart = 1 + $period * 3;
+ $ruleValues = range($periodStart, $periodEnd);
+ }
+ $columnFilterTests[$columnID] = [
+ 'method' => 'filterTestInPeriodDateSet',
+ 'arguments' => $ruleValues,
+ ];
+ $filterColumn->setAttributes([]);
+ } else {
+ // Date Range
+ $columnFilterTests[$columnID] = $this->dynamicFilterDateRange($dynamicRuleType, $filterColumn);
+
+ break;
+ }
+ }
+ }
+
+ break;
+ case AutoFilter\Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER:
+ $ruleValues = [];
+ $dataRowCount = $rangeEnd[1] - $rangeStart[1];
+ foreach ($rules as $rule) {
+ // We should only ever have one Dynamic Filter Rule anyway
+ $toptenRuleType = $rule->getGrouping();
+ $ruleValue = $rule->getValue();
+ $ruleOperator = $rule->getOperator();
+ }
+ if ($ruleOperator === AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) {
+ $ruleValue = floor($ruleValue * ($dataRowCount / 100));
+ }
+ if ($ruleValue < 1) {
+ $ruleValue = 1;
+ }
+ if ($ruleValue > 500) {
+ $ruleValue = 500;
+ }
+
+ $maxVal = $this->calculateTopTenValue($columnID, $rangeStart[1] + 1, $rangeEnd[1], $toptenRuleType, $ruleValue);
+
+ $operator = ($toptenRuleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP)
+ ? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL
+ : AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL;
+ $ruleValues[] = ['operator' => $operator, 'value' => $maxVal];
+ $columnFilterTests[$columnID] = [
+ 'method' => 'filterTestInCustomDataSet',
+ 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR],
+ ];
+ $filterColumn->setAttributes(['maxVal' => $maxVal]);
+
+ break;
+ }
+ }
+
+ // Execute the column tests for each row in the autoFilter range to determine show/hide,
+ for ($row = $rangeStart[1] + 1; $row <= $rangeEnd[1]; ++$row) {
+ $result = true;
+ foreach ($columnFilterTests as $columnID => $columnFilterTest) {
+ $cellValue = $this->workSheet->getCell($columnID . $row)->getCalculatedValue();
+ // Execute the filter test
+ $result = $result &&
+ call_user_func_array(
+ [self::class, $columnFilterTest['method']],
+ [$cellValue, $columnFilterTest['arguments']]
+ );
+ // If filter test has resulted in FALSE, exit the loop straightaway rather than running any more tests
+ if (!$result) {
+ break;
+ }
+ }
+ // Set show/hide for the row based on the result of the autoFilter result
+ $this->workSheet->getRowDimension($row)->setVisible($result);
+ }
+
+ 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\AutoFilter objects
+ $this->{$key} = [];
+ foreach ($value as $k => $v) {
+ $this->{$key}[$k] = clone $v;
+ // attach the new cloned Column to this new cloned Autofilter object
+ $this->{$key}[$k]->setParent($this);
+ }
+ } else {
+ $this->{$key} = $value;
+ }
+ }
+ }
+
+ /**
+ * toString method replicates previous behavior by returning the range if object is
+ * referenced as a property of its parent.
+ */
+ public function __toString()
+ {
+ return (string) $this->range;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php
new file mode 100755
index 0000000..09584a7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php
@@ -0,0 +1,380 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
+
+class Column
+{
+ const AUTOFILTER_FILTERTYPE_FILTER = 'filters';
+ const AUTOFILTER_FILTERTYPE_CUSTOMFILTER = 'customFilters';
+ // Supports no more than 2 rules, with an And/Or join criteria
+ // if more than 1 rule is defined
+ const AUTOFILTER_FILTERTYPE_DYNAMICFILTER = 'dynamicFilter';
+ // Even though the filter rule is constant, the filtered data can vary
+ // e.g. filtered by date = TODAY
+ const AUTOFILTER_FILTERTYPE_TOPTENFILTER = 'top10';
+
+ /**
+ * Types of autofilter rules.
+ *
+ * @var string[]
+ */
+ private static $filterTypes = [
+ // Currently we're not handling
+ // colorFilter
+ // extLst
+ // iconFilter
+ self::AUTOFILTER_FILTERTYPE_FILTER,
+ self::AUTOFILTER_FILTERTYPE_CUSTOMFILTER,
+ self::AUTOFILTER_FILTERTYPE_DYNAMICFILTER,
+ self::AUTOFILTER_FILTERTYPE_TOPTENFILTER,
+ ];
+
+ // Multiple Rule Connections
+ const AUTOFILTER_COLUMN_JOIN_AND = 'and';
+ const AUTOFILTER_COLUMN_JOIN_OR = 'or';
+
+ /**
+ * Join options for autofilter rules.
+ *
+ * @var string[]
+ */
+ private static $ruleJoins = [
+ self::AUTOFILTER_COLUMN_JOIN_AND,
+ self::AUTOFILTER_COLUMN_JOIN_OR,
+ ];
+
+ /**
+ * Autofilter.
+ *
+ * @var AutoFilter
+ */
+ private $parent;
+
+ /**
+ * Autofilter Column Index.
+ *
+ * @var string
+ */
+ private $columnIndex = '';
+
+ /**
+ * Autofilter Column Filter Type.
+ *
+ * @var string
+ */
+ private $filterType = self::AUTOFILTER_FILTERTYPE_FILTER;
+
+ /**
+ * Autofilter Multiple Rules And/Or.
+ *
+ * @var string
+ */
+ private $join = self::AUTOFILTER_COLUMN_JOIN_OR;
+
+ /**
+ * Autofilter Column Rules.
+ *
+ * @var array of Column\Rule
+ */
+ private $ruleset = [];
+
+ /**
+ * Autofilter Column Dynamic Attributes.
+ *
+ * @var array of mixed
+ */
+ private $attributes = [];
+
+ /**
+ * Create a new Column.
+ *
+ * @param string $pColumn Column (e.g. A)
+ * @param AutoFilter $pParent Autofilter for this column
+ */
+ public function __construct($pColumn, ?AutoFilter $pParent = null)
+ {
+ $this->columnIndex = $pColumn;
+ $this->parent = $pParent;
+ }
+
+ /**
+ * Get AutoFilter column index as string eg: 'A'.
+ *
+ * @return string
+ */
+ public function getColumnIndex()
+ {
+ return $this->columnIndex;
+ }
+
+ /**
+ * Set AutoFilter column index as string eg: 'A'.
+ *
+ * @param string $pColumn Column (e.g. A)
+ *
+ * @return $this
+ */
+ public function setColumnIndex($pColumn)
+ {
+ // Uppercase coordinate
+ $pColumn = strtoupper($pColumn);
+ if ($this->parent !== null) {
+ $this->parent->testColumnInRange($pColumn);
+ }
+
+ $this->columnIndex = $pColumn;
+
+ return $this;
+ }
+
+ /**
+ * Get this Column's AutoFilter Parent.
+ *
+ * @return AutoFilter
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Set this Column's AutoFilter Parent.
+ *
+ * @param AutoFilter $pParent
+ *
+ * @return $this
+ */
+ public function setParent(?AutoFilter $pParent = null)
+ {
+ $this->parent = $pParent;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Type.
+ *
+ * @return string
+ */
+ public function getFilterType()
+ {
+ return $this->filterType;
+ }
+
+ /**
+ * Set AutoFilter Type.
+ *
+ * @param string $pFilterType
+ *
+ * @return $this
+ */
+ public function setFilterType($pFilterType)
+ {
+ if (!in_array($pFilterType, self::$filterTypes)) {
+ throw new PhpSpreadsheetException('Invalid filter type for column AutoFilter.');
+ }
+
+ $this->filterType = $pFilterType;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Multiple Rules And/Or Join.
+ *
+ * @return string
+ */
+ public function getJoin()
+ {
+ return $this->join;
+ }
+
+ /**
+ * Set AutoFilter Multiple Rules And/Or.
+ *
+ * @param string $pJoin And/Or
+ *
+ * @return $this
+ */
+ public function setJoin($pJoin)
+ {
+ // Lowercase And/Or
+ $pJoin = strtolower($pJoin);
+ if (!in_array($pJoin, self::$ruleJoins)) {
+ throw new PhpSpreadsheetException('Invalid rule connection for column AutoFilter.');
+ }
+
+ $this->join = $pJoin;
+
+ return $this;
+ }
+
+ /**
+ * Set AutoFilter Attributes.
+ *
+ * @param string[] $attributes
+ *
+ * @return $this
+ */
+ public function setAttributes(array $attributes)
+ {
+ $this->attributes = $attributes;
+
+ return $this;
+ }
+
+ /**
+ * Set An AutoFilter Attribute.
+ *
+ * @param string $pName Attribute Name
+ * @param string $pValue Attribute Value
+ *
+ * @return $this
+ */
+ public function setAttribute($pName, $pValue)
+ {
+ $this->attributes[$pName] = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Column Attributes.
+ *
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * Get specific AutoFilter Column Attribute.
+ *
+ * @param string $pName Attribute Name
+ *
+ * @return string
+ */
+ public function getAttribute($pName)
+ {
+ if (isset($this->attributes[$pName])) {
+ return $this->attributes[$pName];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get all AutoFilter Column Rules.
+ *
+ * @return Column\Rule[]
+ */
+ public function getRules()
+ {
+ return $this->ruleset;
+ }
+
+ /**
+ * Get a specified AutoFilter Column Rule.
+ *
+ * @param int $pIndex Rule index in the ruleset array
+ *
+ * @return Column\Rule
+ */
+ public function getRule($pIndex)
+ {
+ if (!isset($this->ruleset[$pIndex])) {
+ $this->ruleset[$pIndex] = new Column\Rule($this);
+ }
+
+ return $this->ruleset[$pIndex];
+ }
+
+ /**
+ * Create a new AutoFilter Column Rule in the ruleset.
+ *
+ * @return Column\Rule
+ */
+ public function createRule()
+ {
+ $this->ruleset[] = new Column\Rule($this);
+
+ return end($this->ruleset);
+ }
+
+ /**
+ * Add a new AutoFilter Column Rule to the ruleset.
+ *
+ * @return $this
+ */
+ public function addRule(Column\Rule $pRule)
+ {
+ $pRule->setParent($this);
+ $this->ruleset[] = $pRule;
+
+ return $this;
+ }
+
+ /**
+ * Delete a specified AutoFilter Column Rule
+ * If the number of rules is reduced to 1, then we reset And/Or logic to Or.
+ *
+ * @param int $pIndex Rule index in the ruleset array
+ *
+ * @return $this
+ */
+ public function deleteRule($pIndex)
+ {
+ if (isset($this->ruleset[$pIndex])) {
+ unset($this->ruleset[$pIndex]);
+ // If we've just deleted down to a single rule, then reset And/Or joining to Or
+ if (count($this->ruleset) <= 1) {
+ $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Delete all AutoFilter Column Rules.
+ *
+ * @return $this
+ */
+ public function clearRules()
+ {
+ $this->ruleset = [];
+ $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR);
+
+ 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 ($key === 'parent') {
+ // Detach from autofilter parent
+ $this->parent = null;
+ } elseif ($key === 'ruleset') {
+ // The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\AutoFilter objects
+ $this->ruleset = [];
+ foreach ($value as $k => $v) {
+ $cloned = clone $v;
+ $cloned->setParent($this); // attach the new cloned Rule to this new cloned Autofilter Cloned object
+ $this->ruleset[$k] = $cloned;
+ }
+ } elseif (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php
new file mode 100755
index 0000000..c0a15a7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php
@@ -0,0 +1,445 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
+
+class Rule
+{
+ const AUTOFILTER_RULETYPE_FILTER = 'filter';
+ const AUTOFILTER_RULETYPE_DATEGROUP = 'dateGroupItem';
+ const AUTOFILTER_RULETYPE_CUSTOMFILTER = 'customFilter';
+ const AUTOFILTER_RULETYPE_DYNAMICFILTER = 'dynamicFilter';
+ const AUTOFILTER_RULETYPE_TOPTENFILTER = 'top10Filter';
+
+ private static $ruleTypes = [
+ // Currently we're not handling
+ // colorFilter
+ // extLst
+ // iconFilter
+ self::AUTOFILTER_RULETYPE_FILTER,
+ self::AUTOFILTER_RULETYPE_DATEGROUP,
+ self::AUTOFILTER_RULETYPE_CUSTOMFILTER,
+ self::AUTOFILTER_RULETYPE_DYNAMICFILTER,
+ self::AUTOFILTER_RULETYPE_TOPTENFILTER,
+ ];
+
+ const AUTOFILTER_RULETYPE_DATEGROUP_YEAR = 'year';
+ const AUTOFILTER_RULETYPE_DATEGROUP_MONTH = 'month';
+ const AUTOFILTER_RULETYPE_DATEGROUP_DAY = 'day';
+ const AUTOFILTER_RULETYPE_DATEGROUP_HOUR = 'hour';
+ const AUTOFILTER_RULETYPE_DATEGROUP_MINUTE = 'minute';
+ const AUTOFILTER_RULETYPE_DATEGROUP_SECOND = 'second';
+
+ private static $dateTimeGroups = [
+ self::AUTOFILTER_RULETYPE_DATEGROUP_YEAR,
+ self::AUTOFILTER_RULETYPE_DATEGROUP_MONTH,
+ self::AUTOFILTER_RULETYPE_DATEGROUP_DAY,
+ self::AUTOFILTER_RULETYPE_DATEGROUP_HOUR,
+ self::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE,
+ self::AUTOFILTER_RULETYPE_DATEGROUP_SECOND,
+ ];
+
+ const AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY = 'yesterday';
+ const AUTOFILTER_RULETYPE_DYNAMIC_TODAY = 'today';
+ const AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW = 'tomorrow';
+ const AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE = 'yearToDate';
+ const AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR = 'thisYear';
+ const AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER = 'thisQuarter';
+ const AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH = 'thisMonth';
+ const AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK = 'thisWeek';
+ const AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR = 'lastYear';
+ const AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER = 'lastQuarter';
+ const AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH = 'lastMonth';
+ const AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK = 'lastWeek';
+ const AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR = 'nextYear';
+ const AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER = 'nextQuarter';
+ const AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH = 'nextMonth';
+ const AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK = 'nextWeek';
+ const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_1 = 'M1';
+ const AUTOFILTER_RULETYPE_DYNAMIC_JANUARY = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_1;
+ const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_2 = 'M2';
+ const AUTOFILTER_RULETYPE_DYNAMIC_FEBRUARY = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_2;
+ const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_3 = 'M3';
+ const AUTOFILTER_RULETYPE_DYNAMIC_MARCH = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_3;
+ const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_4 = 'M4';
+ const AUTOFILTER_RULETYPE_DYNAMIC_APRIL = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_4;
+ const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_5 = 'M5';
+ const AUTOFILTER_RULETYPE_DYNAMIC_MAY = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_5;
+ const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_6 = 'M6';
+ const AUTOFILTER_RULETYPE_DYNAMIC_JUNE = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_6;
+ const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_7 = 'M7';
+ const AUTOFILTER_RULETYPE_DYNAMIC_JULY = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_7;
+ const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_8 = 'M8';
+ const AUTOFILTER_RULETYPE_DYNAMIC_AUGUST = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_8;
+ const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_9 = 'M9';
+ const AUTOFILTER_RULETYPE_DYNAMIC_SEPTEMBER = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_9;
+ const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_10 = 'M10';
+ const AUTOFILTER_RULETYPE_DYNAMIC_OCTOBER = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_10;
+ const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_11 = 'M11';
+ const AUTOFILTER_RULETYPE_DYNAMIC_NOVEMBER = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_11;
+ const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_12 = 'M12';
+ const AUTOFILTER_RULETYPE_DYNAMIC_DECEMBER = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_12;
+ const AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_1 = 'Q1';
+ const AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_2 = 'Q2';
+ const AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_3 = 'Q3';
+ const AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_4 = 'Q4';
+ const AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE = 'aboveAverage';
+ const AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE = 'belowAverage';
+
+ private static $dynamicTypes = [
+ self::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_TODAY,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_1,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_2,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_3,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_4,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_5,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_6,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_7,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_8,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_9,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_10,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_11,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_12,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_1,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_2,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_3,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_4,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE,
+ self::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE,
+ ];
+
+ /*
+ * The only valid filter rule operators for filter and customFilter types are:
+ * <xsd:enumeration value="equal"/>
+ * <xsd:enumeration value="lessThan"/>
+ * <xsd:enumeration value="lessThanOrEqual"/>
+ * <xsd:enumeration value="notEqual"/>
+ * <xsd:enumeration value="greaterThanOrEqual"/>
+ * <xsd:enumeration value="greaterThan"/>
+ */
+ const AUTOFILTER_COLUMN_RULE_EQUAL = 'equal';
+ const AUTOFILTER_COLUMN_RULE_NOTEQUAL = 'notEqual';
+ const AUTOFILTER_COLUMN_RULE_GREATERTHAN = 'greaterThan';
+ const AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL = 'greaterThanOrEqual';
+ const AUTOFILTER_COLUMN_RULE_LESSTHAN = 'lessThan';
+ const AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL = 'lessThanOrEqual';
+
+ private static $operators = [
+ self::AUTOFILTER_COLUMN_RULE_EQUAL,
+ self::AUTOFILTER_COLUMN_RULE_NOTEQUAL,
+ self::AUTOFILTER_COLUMN_RULE_GREATERTHAN,
+ self::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL,
+ self::AUTOFILTER_COLUMN_RULE_LESSTHAN,
+ self::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL,
+ ];
+
+ const AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE = 'byValue';
+ const AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT = 'byPercent';
+
+ private static $topTenValue = [
+ self::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE,
+ self::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT,
+ ];
+
+ const AUTOFILTER_COLUMN_RULE_TOPTEN_TOP = 'top';
+ const AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM = 'bottom';
+
+ private static $topTenType = [
+ self::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP,
+ self::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM,
+ ];
+
+ // Rule Operators (Numeric, Boolean etc)
+// const AUTOFILTER_COLUMN_RULE_BETWEEN = 'between'; // greaterThanOrEqual 1 && lessThanOrEqual 2
+ // Rule Operators (Numeric Special) which are translated to standard numeric operators with calculated values
+// const AUTOFILTER_COLUMN_RULE_TOPTEN = 'topTen'; // greaterThan calculated value
+// const AUTOFILTER_COLUMN_RULE_TOPTENPERCENT = 'topTenPercent'; // greaterThan calculated value
+// const AUTOFILTER_COLUMN_RULE_ABOVEAVERAGE = 'aboveAverage'; // Value is calculated as the average
+// const AUTOFILTER_COLUMN_RULE_BELOWAVERAGE = 'belowAverage'; // Value is calculated as the average
+ // Rule Operators (String) which are set as wild-carded values
+// const AUTOFILTER_COLUMN_RULE_BEGINSWITH = 'beginsWith'; // A*
+// const AUTOFILTER_COLUMN_RULE_ENDSWITH = 'endsWith'; // *Z
+// const AUTOFILTER_COLUMN_RULE_CONTAINS = 'contains'; // *B*
+// const AUTOFILTER_COLUMN_RULE_DOESNTCONTAIN = 'notEqual'; // notEqual *B*
+ // Rule Operators (Date Special) which are translated to standard numeric operators with calculated values
+// const AUTOFILTER_COLUMN_RULE_BEFORE = 'lessThan';
+// const AUTOFILTER_COLUMN_RULE_AFTER = 'greaterThan';
+// const AUTOFILTER_COLUMN_RULE_YESTERDAY = 'yesterday';
+// const AUTOFILTER_COLUMN_RULE_TODAY = 'today';
+// const AUTOFILTER_COLUMN_RULE_TOMORROW = 'tomorrow';
+// const AUTOFILTER_COLUMN_RULE_LASTWEEK = 'lastWeek';
+// const AUTOFILTER_COLUMN_RULE_THISWEEK = 'thisWeek';
+// const AUTOFILTER_COLUMN_RULE_NEXTWEEK = 'nextWeek';
+// const AUTOFILTER_COLUMN_RULE_LASTMONTH = 'lastMonth';
+// const AUTOFILTER_COLUMN_RULE_THISMONTH = 'thisMonth';
+// const AUTOFILTER_COLUMN_RULE_NEXTMONTH = 'nextMonth';
+// const AUTOFILTER_COLUMN_RULE_LASTQUARTER = 'lastQuarter';
+// const AUTOFILTER_COLUMN_RULE_THISQUARTER = 'thisQuarter';
+// const AUTOFILTER_COLUMN_RULE_NEXTQUARTER = 'nextQuarter';
+// const AUTOFILTER_COLUMN_RULE_LASTYEAR = 'lastYear';
+// const AUTOFILTER_COLUMN_RULE_THISYEAR = 'thisYear';
+// const AUTOFILTER_COLUMN_RULE_NEXTYEAR = 'nextYear';
+// const AUTOFILTER_COLUMN_RULE_YEARTODATE = 'yearToDate'; // <dynamicFilter val="40909" type="yearToDate" maxVal="41113"/>
+// const AUTOFILTER_COLUMN_RULE_ALLDATESINMONTH = 'allDatesInMonth'; // <dynamicFilter type="M2"/> for Month/February
+// const AUTOFILTER_COLUMN_RULE_ALLDATESINQUARTER = 'allDatesInQuarter'; // <dynamicFilter type="Q2"/> for Quarter 2
+
+ /**
+ * Autofilter Column.
+ *
+ * @var Column
+ */
+ private $parent;
+
+ /**
+ * Autofilter Rule Type.
+ *
+ * @var string
+ */
+ private $ruleType = self::AUTOFILTER_RULETYPE_FILTER;
+
+ /**
+ * Autofilter Rule Value.
+ *
+ * @var string
+ */
+ private $value = '';
+
+ /**
+ * Autofilter Rule Operator.
+ *
+ * @var string
+ */
+ private $operator = self::AUTOFILTER_COLUMN_RULE_EQUAL;
+
+ /**
+ * DateTimeGrouping Group Value.
+ *
+ * @var string
+ */
+ private $grouping = '';
+
+ /**
+ * Create a new Rule.
+ *
+ * @param Column $pParent
+ */
+ public function __construct(?Column $pParent = null)
+ {
+ $this->parent = $pParent;
+ }
+
+ /**
+ * Get AutoFilter Rule Type.
+ *
+ * @return string
+ */
+ public function getRuleType()
+ {
+ return $this->ruleType;
+ }
+
+ /**
+ * Set AutoFilter Rule Type.
+ *
+ * @param string $pRuleType see self::AUTOFILTER_RULETYPE_*
+ *
+ * @return $this
+ */
+ public function setRuleType($pRuleType)
+ {
+ if (!in_array($pRuleType, self::$ruleTypes)) {
+ throw new PhpSpreadsheetException('Invalid rule type for column AutoFilter Rule.');
+ }
+
+ $this->ruleType = $pRuleType;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Rule Value.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Set AutoFilter Rule Value.
+ *
+ * @param string|string[] $pValue
+ *
+ * @return $this
+ */
+ public function setValue($pValue)
+ {
+ if (is_array($pValue)) {
+ $grouping = -1;
+ foreach ($pValue as $key => $value) {
+ // Validate array entries
+ if (!in_array($key, self::$dateTimeGroups)) {
+ // Remove any invalid entries from the value array
+ unset($pValue[$key]);
+ } else {
+ // Work out what the dateTime grouping will be
+ $grouping = max($grouping, array_search($key, self::$dateTimeGroups));
+ }
+ }
+ if (count($pValue) == 0) {
+ throw new PhpSpreadsheetException('Invalid rule value for column AutoFilter Rule.');
+ }
+ // Set the dateTime grouping that we've anticipated
+ $this->setGrouping(self::$dateTimeGroups[$grouping]);
+ }
+ $this->value = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Rule Operator.
+ *
+ * @return string
+ */
+ public function getOperator()
+ {
+ return $this->operator;
+ }
+
+ /**
+ * Set AutoFilter Rule Operator.
+ *
+ * @param string $pOperator see self::AUTOFILTER_COLUMN_RULE_*
+ *
+ * @return $this
+ */
+ public function setOperator($pOperator)
+ {
+ if (empty($pOperator)) {
+ $pOperator = self::AUTOFILTER_COLUMN_RULE_EQUAL;
+ }
+ if ((!in_array($pOperator, self::$operators)) &&
+ (!in_array($pOperator, self::$topTenValue))) {
+ throw new PhpSpreadsheetException('Invalid operator for column AutoFilter Rule.');
+ }
+ $this->operator = $pOperator;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Rule Grouping.
+ *
+ * @return string
+ */
+ public function getGrouping()
+ {
+ return $this->grouping;
+ }
+
+ /**
+ * Set AutoFilter Rule Grouping.
+ *
+ * @param string $pGrouping
+ *
+ * @return $this
+ */
+ public function setGrouping($pGrouping)
+ {
+ if (($pGrouping !== null) &&
+ (!in_array($pGrouping, self::$dateTimeGroups)) &&
+ (!in_array($pGrouping, self::$dynamicTypes)) &&
+ (!in_array($pGrouping, self::$topTenType))) {
+ throw new PhpSpreadsheetException('Invalid rule type for column AutoFilter Rule.');
+ }
+ $this->grouping = $pGrouping;
+
+ return $this;
+ }
+
+ /**
+ * Set AutoFilter Rule.
+ *
+ * @param string $pOperator see self::AUTOFILTER_COLUMN_RULE_*
+ * @param string|string[] $pValue
+ * @param string $pGrouping
+ *
+ * @return $this
+ */
+ public function setRule($pOperator, $pValue, $pGrouping = null)
+ {
+ $this->setOperator($pOperator);
+ $this->setValue($pValue);
+ // Only set grouping if it's been passed in as a user-supplied argument,
+ // otherwise we're calculating it when we setValue() and don't want to overwrite that
+ // If the user supplies an argumnet for grouping, then on their own head be it
+ if ($pGrouping !== null) {
+ $this->setGrouping($pGrouping);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get this Rule's AutoFilter Column Parent.
+ *
+ * @return Column
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Set this Rule's AutoFilter Column Parent.
+ *
+ * @param Column $pParent
+ *
+ * @return $this
+ */
+ public function setParent(?Column $pParent = null)
+ {
+ $this->parent = $pParent;
+
+ 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 == 'parent') {
+ // Detach from autofilter column parent
+ $this->$key = null;
+ } else {
+ $this->$key = clone $value;
+ }
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php
new file mode 100755
index 0000000..1b145a8
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php
@@ -0,0 +1,532 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\IComparable;
+
+class BaseDrawing implements IComparable
+{
+ /**
+ * Image counter.
+ *
+ * @var int
+ */
+ private static $imageCounter = 0;
+
+ /**
+ * Image index.
+ *
+ * @var int
+ */
+ private $imageIndex = 0;
+
+ /**
+ * Name.
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * Description.
+ *
+ * @var string
+ */
+ protected $description;
+
+ /**
+ * Worksheet.
+ *
+ * @var Worksheet
+ */
+ protected $worksheet;
+
+ /**
+ * Coordinates.
+ *
+ * @var string
+ */
+ protected $coordinates;
+
+ /**
+ * Offset X.
+ *
+ * @var int
+ */
+ protected $offsetX;
+
+ /**
+ * Offset Y.
+ *
+ * @var int
+ */
+ protected $offsetY;
+
+ /**
+ * Width.
+ *
+ * @var int
+ */
+ protected $width;
+
+ /**
+ * Height.
+ *
+ * @var int
+ */
+ protected $height;
+
+ /**
+ * Proportional resize.
+ *
+ * @var bool
+ */
+ protected $resizeProportional;
+
+ /**
+ * Rotation.
+ *
+ * @var int
+ */
+ protected $rotation;
+
+ /**
+ * Shadow.
+ *
+ * @var Drawing\Shadow
+ */
+ protected $shadow;
+
+ /**
+ * Image hyperlink.
+ *
+ * @var null|Hyperlink
+ */
+ private $hyperlink;
+
+ /**
+ * Create a new BaseDrawing.
+ */
+ public function __construct()
+ {
+ // Initialise values
+ $this->name = '';
+ $this->description = '';
+ $this->worksheet = null;
+ $this->coordinates = 'A1';
+ $this->offsetX = 0;
+ $this->offsetY = 0;
+ $this->width = 0;
+ $this->height = 0;
+ $this->resizeProportional = true;
+ $this->rotation = 0;
+ $this->shadow = new Drawing\Shadow();
+
+ // Set image index
+ ++self::$imageCounter;
+ $this->imageIndex = self::$imageCounter;
+ }
+
+ /**
+ * Get image index.
+ *
+ * @return int
+ */
+ public function getImageIndex()
+ {
+ return $this->imageIndex;
+ }
+
+ /**
+ * Get Name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set Name.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setName($pValue)
+ {
+ $this->name = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Description.
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * Set Description.
+ *
+ * @param string $description
+ *
+ * @return $this
+ */
+ public function setDescription($description)
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ /**
+ * Get Worksheet.
+ *
+ * @return Worksheet
+ */
+ public function getWorksheet()
+ {
+ return $this->worksheet;
+ }
+
+ /**
+ * Set Worksheet.
+ *
+ * @param Worksheet $pValue
+ * @param bool $pOverrideOld If a Worksheet has already been assigned, overwrite it and remove image from old Worksheet?
+ *
+ * @return $this
+ */
+ public function setWorksheet(?Worksheet $pValue = null, $pOverrideOld = false)
+ {
+ if ($this->worksheet === null) {
+ // Add drawing to \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ $this->worksheet = $pValue;
+ $this->worksheet->getCell($this->coordinates);
+ $this->worksheet->getDrawingCollection()->append($this);
+ } else {
+ if ($pOverrideOld) {
+ // Remove drawing from old \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ $iterator = $this->worksheet->getDrawingCollection()->getIterator();
+
+ while ($iterator->valid()) {
+ if ($iterator->current()->getHashCode() == $this->getHashCode()) {
+ $this->worksheet->getDrawingCollection()->offsetUnset($iterator->key());
+ $this->worksheet = null;
+
+ break;
+ }
+ }
+
+ // Set new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ $this->setWorksheet($pValue);
+ } else {
+ throw new PhpSpreadsheetException('A Worksheet has already been assigned. Drawings can only exist on one \\PhpOffice\\PhpSpreadsheet\\Worksheet.');
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Coordinates.
+ *
+ * @return string
+ */
+ public function getCoordinates()
+ {
+ return $this->coordinates;
+ }
+
+ /**
+ * Set Coordinates.
+ *
+ * @param string $pValue eg: 'A1'
+ *
+ * @return $this
+ */
+ public function setCoordinates($pValue)
+ {
+ $this->coordinates = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get OffsetX.
+ *
+ * @return int
+ */
+ public function getOffsetX()
+ {
+ return $this->offsetX;
+ }
+
+ /**
+ * Set OffsetX.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setOffsetX($pValue)
+ {
+ $this->offsetX = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get OffsetY.
+ *
+ * @return int
+ */
+ public function getOffsetY()
+ {
+ return $this->offsetY;
+ }
+
+ /**
+ * Set OffsetY.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setOffsetY($pValue)
+ {
+ $this->offsetY = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Width.
+ *
+ * @return int
+ */
+ public function getWidth()
+ {
+ return $this->width;
+ }
+
+ /**
+ * Set Width.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setWidth($pValue)
+ {
+ // Resize proportional?
+ if ($this->resizeProportional && $pValue != 0) {
+ $ratio = $this->height / ($this->width != 0 ? $this->width : 1);
+ $this->height = round($ratio * $pValue);
+ }
+
+ // Set width
+ $this->width = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Height.
+ *
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->height;
+ }
+
+ /**
+ * Set Height.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setHeight($pValue)
+ {
+ // Resize proportional?
+ if ($this->resizeProportional && $pValue != 0) {
+ $ratio = $this->width / ($this->height != 0 ? $this->height : 1);
+ $this->width = round($ratio * $pValue);
+ }
+
+ // Set height
+ $this->height = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Set width and height with proportional resize.
+ *
+ * Example:
+ * <code>
+ * $objDrawing->setResizeProportional(true);
+ * $objDrawing->setWidthAndHeight(160,120);
+ * </code>
+ *
+ * @author Vincent@luo MSN:kele_100@hotmail.com
+ *
+ * @param int $width
+ * @param int $height
+ *
+ * @return $this
+ */
+ public function setWidthAndHeight($width, $height)
+ {
+ $xratio = $width / ($this->width != 0 ? $this->width : 1);
+ $yratio = $height / ($this->height != 0 ? $this->height : 1);
+ if ($this->resizeProportional && !($width == 0 || $height == 0)) {
+ if (($xratio * $this->height) < $height) {
+ $this->height = ceil($xratio * $this->height);
+ $this->width = $width;
+ } else {
+ $this->width = ceil($yratio * $this->width);
+ $this->height = $height;
+ }
+ } else {
+ $this->width = $width;
+ $this->height = $height;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get ResizeProportional.
+ *
+ * @return bool
+ */
+ public function getResizeProportional()
+ {
+ return $this->resizeProportional;
+ }
+
+ /**
+ * Set ResizeProportional.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setResizeProportional($pValue)
+ {
+ $this->resizeProportional = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Rotation.
+ *
+ * @return int
+ */
+ public function getRotation()
+ {
+ return $this->rotation;
+ }
+
+ /**
+ * Set Rotation.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setRotation($pValue)
+ {
+ $this->rotation = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Shadow.
+ *
+ * @return Drawing\Shadow
+ */
+ public function getShadow()
+ {
+ return $this->shadow;
+ }
+
+ /**
+ * Set Shadow.
+ *
+ * @param Drawing\Shadow $pValue
+ *
+ * @return $this
+ */
+ public function setShadow(?Drawing\Shadow $pValue = null)
+ {
+ $this->shadow = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->name .
+ $this->description .
+ $this->worksheet->getHashCode() .
+ $this->coordinates .
+ $this->offsetX .
+ $this->offsetY .
+ $this->width .
+ $this->height .
+ $this->rotation .
+ $this->shadow->getHashCode() .
+ __CLASS__
+ );
+ }
+
+ /**
+ * 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 ($key == 'worksheet') {
+ $this->worksheet = null;
+ } elseif (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ public function setHyperlink(?Hyperlink $pHyperlink = null): void
+ {
+ $this->hyperlink = $pHyperlink;
+ }
+
+ /**
+ * @return null|Hyperlink
+ */
+ public function getHyperlink()
+ {
+ return $this->hyperlink;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php
new file mode 100755
index 0000000..45f76ca
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use Iterator;
+
+abstract class CellIterator implements Iterator
+{
+ /**
+ * Worksheet to iterate.
+ *
+ * @var Worksheet
+ */
+ protected $worksheet;
+
+ /**
+ * Iterate only existing cells.
+ *
+ * @var bool
+ */
+ protected $onlyExistingCells = false;
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->worksheet = null;
+ }
+
+ /**
+ * Get loop only existing cells.
+ *
+ * @return bool
+ */
+ public function getIterateOnlyExistingCells()
+ {
+ return $this->onlyExistingCells;
+ }
+
+ /**
+ * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary.
+ */
+ abstract protected function adjustForExistingOnlyRange();
+
+ /**
+ * Set the iterator to loop only existing cells.
+ *
+ * @param bool $value
+ */
+ public function setIterateOnlyExistingCells($value): void
+ {
+ $this->onlyExistingCells = (bool) $value;
+
+ $this->adjustForExistingOnlyRange();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php
new file mode 100755
index 0000000..410e807
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+class Column
+{
+ /**
+ * \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet.
+ *
+ * @var Worksheet
+ */
+ private $parent;
+
+ /**
+ * Column index.
+ *
+ * @var string
+ */
+ private $columnIndex;
+
+ /**
+ * Create a new column.
+ *
+ * @param Worksheet $parent
+ * @param string $columnIndex
+ */
+ public function __construct(?Worksheet $parent = null, $columnIndex = 'A')
+ {
+ // Set parent and column index
+ $this->parent = $parent;
+ $this->columnIndex = $columnIndex;
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->parent = null;
+ }
+
+ /**
+ * Get column index as string eg: 'A'.
+ *
+ * @return string
+ */
+ public function getColumnIndex()
+ {
+ return $this->columnIndex;
+ }
+
+ /**
+ * Get cell iterator.
+ *
+ * @param int $startRow The row number at which to start iterating
+ * @param int $endRow Optionally, the row number at which to stop iterating
+ *
+ * @return ColumnCellIterator
+ */
+ public function getCellIterator($startRow = 1, $endRow = null)
+ {
+ return new ColumnCellIterator($this->parent, $this->columnIndex, $startRow, $endRow);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php
new file mode 100755
index 0000000..ec16fbe
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php
@@ -0,0 +1,189 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class ColumnCellIterator extends CellIterator
+{
+ /**
+ * Current iterator position.
+ *
+ * @var int
+ */
+ private $currentRow;
+
+ /**
+ * Column index.
+ *
+ * @var string
+ */
+ private $columnIndex;
+
+ /**
+ * Start position.
+ *
+ * @var int
+ */
+ private $startRow = 1;
+
+ /**
+ * End position.
+ *
+ * @var int
+ */
+ private $endRow = 1;
+
+ /**
+ * Create a new row iterator.
+ *
+ * @param Worksheet $subject The worksheet to iterate over
+ * @param string $columnIndex The column that we want to iterate
+ * @param int $startRow The row number at which to start iterating
+ * @param int $endRow Optionally, the row number at which to stop iterating
+ */
+ public function __construct(?Worksheet $subject = null, $columnIndex = 'A', $startRow = 1, $endRow = null)
+ {
+ // Set subject
+ $this->worksheet = $subject;
+ $this->columnIndex = Coordinate::columnIndexFromString($columnIndex);
+ $this->resetEnd($endRow);
+ $this->resetStart($startRow);
+ }
+
+ /**
+ * (Re)Set the start row and the current row pointer.
+ *
+ * @param int $startRow The row number at which to start iterating
+ *
+ * @return $this
+ */
+ public function resetStart($startRow = 1)
+ {
+ $this->startRow = $startRow;
+ $this->adjustForExistingOnlyRange();
+ $this->seek($startRow);
+
+ return $this;
+ }
+
+ /**
+ * (Re)Set the end row.
+ *
+ * @param int $endRow The row number at which to stop iterating
+ *
+ * @return $this
+ */
+ public function resetEnd($endRow = null)
+ {
+ $this->endRow = ($endRow) ? $endRow : $this->worksheet->getHighestRow();
+ $this->adjustForExistingOnlyRange();
+
+ return $this;
+ }
+
+ /**
+ * Set the row pointer to the selected row.
+ *
+ * @param int $row The row number to set the current pointer at
+ *
+ * @return $this
+ */
+ public function seek($row = 1)
+ {
+ if (($row < $this->startRow) || ($row > $this->endRow)) {
+ throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})");
+ } elseif ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $row))) {
+ throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist');
+ }
+ $this->currentRow = $row;
+
+ return $this;
+ }
+
+ /**
+ * Rewind the iterator to the starting row.
+ */
+ public function rewind(): void
+ {
+ $this->currentRow = $this->startRow;
+ }
+
+ /**
+ * Return the current cell in this worksheet column.
+ *
+ * @return null|\PhpOffice\PhpSpreadsheet\Cell\Cell
+ */
+ public function current()
+ {
+ return $this->worksheet->getCellByColumnAndRow($this->columnIndex, $this->currentRow);
+ }
+
+ /**
+ * Return the current iterator key.
+ *
+ * @return int
+ */
+ public function key()
+ {
+ return $this->currentRow;
+ }
+
+ /**
+ * Set the iterator to its next value.
+ */
+ public function next(): void
+ {
+ do {
+ ++$this->currentRow;
+ } while (($this->onlyExistingCells) &&
+ (!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->currentRow)) &&
+ ($this->currentRow <= $this->endRow));
+ }
+
+ /**
+ * Set the iterator to its previous value.
+ */
+ public function prev(): void
+ {
+ do {
+ --$this->currentRow;
+ } while (($this->onlyExistingCells) &&
+ (!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->currentRow)) &&
+ ($this->currentRow >= $this->startRow));
+ }
+
+ /**
+ * Indicate if more rows exist in the worksheet range of rows that we're iterating.
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return $this->currentRow <= $this->endRow && $this->currentRow >= $this->startRow;
+ }
+
+ /**
+ * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary.
+ */
+ protected function adjustForExistingOnlyRange(): void
+ {
+ if ($this->onlyExistingCells) {
+ while ((!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->startRow)) &&
+ ($this->startRow <= $this->endRow)) {
+ ++$this->startRow;
+ }
+ if ($this->startRow > $this->endRow) {
+ throw new PhpSpreadsheetException('No cells exist within the specified range');
+ }
+ while ((!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->endRow)) &&
+ ($this->endRow >= $this->startRow)) {
+ --$this->endRow;
+ }
+ if ($this->endRow < $this->startRow) {
+ throw new PhpSpreadsheetException('No cells exist within the specified range');
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php
new file mode 100755
index 0000000..4e87a34
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+class ColumnDimension extends Dimension
+{
+ /**
+ * Column index.
+ *
+ * @var string
+ */
+ private $columnIndex;
+
+ /**
+ * Column width.
+ *
+ * When this is set to a negative value, the column width should be ignored by IWriter
+ *
+ * @var float
+ */
+ private $width = -1;
+
+ /**
+ * Auto size?
+ *
+ * @var bool
+ */
+ private $autoSize = false;
+
+ /**
+ * Create a new ColumnDimension.
+ *
+ * @param string $pIndex Character column index
+ */
+ public function __construct($pIndex = 'A')
+ {
+ // Initialise values
+ $this->columnIndex = $pIndex;
+
+ // set dimension as unformatted by default
+ parent::__construct(0);
+ }
+
+ /**
+ * Get column index as string eg: 'A'.
+ *
+ * @return string
+ */
+ public function getColumnIndex()
+ {
+ return $this->columnIndex;
+ }
+
+ /**
+ * Set column index as string eg: 'A'.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setColumnIndex($pValue)
+ {
+ $this->columnIndex = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Width.
+ *
+ * @return float
+ */
+ public function getWidth()
+ {
+ return $this->width;
+ }
+
+ /**
+ * Set Width.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setWidth($pValue)
+ {
+ $this->width = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Auto Size.
+ *
+ * @return bool
+ */
+ public function getAutoSize()
+ {
+ return $this->autoSize;
+ }
+
+ /**
+ * Set Auto Size.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setAutoSize($pValue)
+ {
+ $this->autoSize = $pValue;
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php
new file mode 100755
index 0000000..d0bb20c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php
@@ -0,0 +1,172 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use Iterator;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class ColumnIterator implements Iterator
+{
+ /**
+ * Worksheet to iterate.
+ *
+ * @var Worksheet
+ */
+ private $worksheet;
+
+ /**
+ * Current iterator position.
+ *
+ * @var int
+ */
+ private $currentColumnIndex = 1;
+
+ /**
+ * Start position.
+ *
+ * @var int
+ */
+ private $startColumnIndex = 1;
+
+ /**
+ * End position.
+ *
+ * @var int
+ */
+ private $endColumnIndex = 1;
+
+ /**
+ * Create a new column iterator.
+ *
+ * @param Worksheet $worksheet The worksheet to iterate over
+ * @param string $startColumn The column address at which to start iterating
+ * @param string $endColumn Optionally, the column address at which to stop iterating
+ */
+ public function __construct(Worksheet $worksheet, $startColumn = 'A', $endColumn = null)
+ {
+ // Set subject
+ $this->worksheet = $worksheet;
+ $this->resetEnd($endColumn);
+ $this->resetStart($startColumn);
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->worksheet = null;
+ }
+
+ /**
+ * (Re)Set the start column and the current column pointer.
+ *
+ * @param string $startColumn The column address at which to start iterating
+ *
+ * @return $this
+ */
+ public function resetStart($startColumn = 'A')
+ {
+ $startColumnIndex = Coordinate::columnIndexFromString($startColumn);
+ if ($startColumnIndex > Coordinate::columnIndexFromString($this->worksheet->getHighestColumn())) {
+ throw new Exception("Start column ({$startColumn}) is beyond highest column ({$this->worksheet->getHighestColumn()})");
+ }
+
+ $this->startColumnIndex = $startColumnIndex;
+ if ($this->endColumnIndex < $this->startColumnIndex) {
+ $this->endColumnIndex = $this->startColumnIndex;
+ }
+ $this->seek($startColumn);
+
+ return $this;
+ }
+
+ /**
+ * (Re)Set the end column.
+ *
+ * @param string $endColumn The column address at which to stop iterating
+ *
+ * @return $this
+ */
+ public function resetEnd($endColumn = null)
+ {
+ $endColumn = $endColumn ? $endColumn : $this->worksheet->getHighestColumn();
+ $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn);
+
+ return $this;
+ }
+
+ /**
+ * Set the column pointer to the selected column.
+ *
+ * @param string $column The column address to set the current pointer at
+ *
+ * @return $this
+ */
+ public function seek($column = 'A')
+ {
+ $column = Coordinate::columnIndexFromString($column);
+ if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) {
+ throw new PhpSpreadsheetException("Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})");
+ }
+ $this->currentColumnIndex = $column;
+
+ return $this;
+ }
+
+ /**
+ * Rewind the iterator to the starting column.
+ */
+ public function rewind(): void
+ {
+ $this->currentColumnIndex = $this->startColumnIndex;
+ }
+
+ /**
+ * Return the current column in this worksheet.
+ *
+ * @return Column
+ */
+ public function current()
+ {
+ return new Column($this->worksheet, Coordinate::stringFromColumnIndex($this->currentColumnIndex));
+ }
+
+ /**
+ * Return the current iterator key.
+ *
+ * @return string
+ */
+ public function key()
+ {
+ return Coordinate::stringFromColumnIndex($this->currentColumnIndex);
+ }
+
+ /**
+ * Set the iterator to its next value.
+ */
+ public function next(): void
+ {
+ ++$this->currentColumnIndex;
+ }
+
+ /**
+ * Set the iterator to its previous value.
+ */
+ public function prev(): void
+ {
+ --$this->currentColumnIndex;
+ }
+
+ /**
+ * Indicate if more columns exist in the worksheet range of columns that we're iterating.
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php
new file mode 100755
index 0000000..a27daf0
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+abstract class Dimension
+{
+ /**
+ * Visible?
+ *
+ * @var bool
+ */
+ private $visible = true;
+
+ /**
+ * Outline level.
+ *
+ * @var int
+ */
+ private $outlineLevel = 0;
+
+ /**
+ * Collapsed.
+ *
+ * @var bool
+ */
+ private $collapsed = false;
+
+ /**
+ * Index to cellXf. Null value means row has no explicit cellXf format.
+ *
+ * @var null|int
+ */
+ private $xfIndex;
+
+ /**
+ * Create a new Dimension.
+ *
+ * @param int $initialValue Numeric row index
+ */
+ public function __construct($initialValue = null)
+ {
+ // set dimension as unformatted by default
+ $this->xfIndex = $initialValue;
+ }
+
+ /**
+ * Get Visible.
+ *
+ * @return bool
+ */
+ public function getVisible()
+ {
+ return $this->visible;
+ }
+
+ /**
+ * Set Visible.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setVisible($pValue)
+ {
+ $this->visible = (bool) $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Outline Level.
+ *
+ * @return int
+ */
+ public function getOutlineLevel()
+ {
+ return $this->outlineLevel;
+ }
+
+ /**
+ * Set Outline Level.
+ * Value must be between 0 and 7.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setOutlineLevel($pValue)
+ {
+ if ($pValue < 0 || $pValue > 7) {
+ throw new PhpSpreadsheetException('Outline level must range between 0 and 7.');
+ }
+
+ $this->outlineLevel = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Collapsed.
+ *
+ * @return bool
+ */
+ public function getCollapsed()
+ {
+ return $this->collapsed;
+ }
+
+ /**
+ * Set Collapsed.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setCollapsed($pValue)
+ {
+ $this->collapsed = (bool) $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get index to cellXf.
+ *
+ * @return int
+ */
+ public function getXfIndex()
+ {
+ return $this->xfIndex;
+ }
+
+ /**
+ * Set index to cellXf.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setXfIndex($pValue)
+ {
+ $this->xfIndex = $pValue;
+
+ 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php
new file mode 100755
index 0000000..1f1dae9
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class Drawing extends BaseDrawing
+{
+ /**
+ * Path.
+ *
+ * @var string
+ */
+ private $path;
+
+ /**
+ * Create a new Drawing.
+ */
+ public function __construct()
+ {
+ // Initialise values
+ $this->path = '';
+
+ // Initialize parent
+ parent::__construct();
+ }
+
+ /**
+ * Get Filename.
+ *
+ * @return string
+ */
+ public function getFilename()
+ {
+ return basename($this->path);
+ }
+
+ /**
+ * Get indexed filename (using image index).
+ *
+ * @return string
+ */
+ public function getIndexedFilename()
+ {
+ $fileName = $this->getFilename();
+ $fileName = str_replace(' ', '_', $fileName);
+
+ return str_replace('.' . $this->getExtension(), '', $fileName) . $this->getImageIndex() . '.' . $this->getExtension();
+ }
+
+ /**
+ * Get Extension.
+ *
+ * @return string
+ */
+ public function getExtension()
+ {
+ $exploded = explode('.', basename($this->path));
+
+ return $exploded[count($exploded) - 1];
+ }
+
+ /**
+ * Get Path.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Set Path.
+ *
+ * @param string $pValue File path
+ * @param bool $pVerifyFile Verify file
+ *
+ * @return $this
+ */
+ public function setPath($pValue, $pVerifyFile = true)
+ {
+ if ($pVerifyFile) {
+ if (file_exists($pValue)) {
+ $this->path = $pValue;
+
+ if ($this->width == 0 && $this->height == 0) {
+ // Get width/height
+ [$this->width, $this->height] = getimagesize($pValue);
+ }
+ } else {
+ throw new PhpSpreadsheetException("File $pValue not found!");
+ }
+ } else {
+ $this->path = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->path .
+ parent::getHashCode() .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php
new file mode 100755
index 0000000..01ffed9
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php
@@ -0,0 +1,289 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
+
+use PhpOffice\PhpSpreadsheet\IComparable;
+use PhpOffice\PhpSpreadsheet\Style\Color;
+
+class Shadow implements IComparable
+{
+ // Shadow alignment
+ const SHADOW_BOTTOM = 'b';
+ const SHADOW_BOTTOM_LEFT = 'bl';
+ const SHADOW_BOTTOM_RIGHT = 'br';
+ const SHADOW_CENTER = 'ctr';
+ const SHADOW_LEFT = 'l';
+ const SHADOW_TOP = 't';
+ const SHADOW_TOP_LEFT = 'tl';
+ const SHADOW_TOP_RIGHT = 'tr';
+
+ /**
+ * Visible.
+ *
+ * @var bool
+ */
+ private $visible;
+
+ /**
+ * Blur radius.
+ *
+ * Defaults to 6
+ *
+ * @var int
+ */
+ private $blurRadius;
+
+ /**
+ * Shadow distance.
+ *
+ * Defaults to 2
+ *
+ * @var int
+ */
+ private $distance;
+
+ /**
+ * Shadow direction (in degrees).
+ *
+ * @var int
+ */
+ private $direction;
+
+ /**
+ * Shadow alignment.
+ *
+ * @var int
+ */
+ private $alignment;
+
+ /**
+ * Color.
+ *
+ * @var Color
+ */
+ private $color;
+
+ /**
+ * Alpha.
+ *
+ * @var int
+ */
+ private $alpha;
+
+ /**
+ * Create a new Shadow.
+ */
+ public function __construct()
+ {
+ // Initialise values
+ $this->visible = false;
+ $this->blurRadius = 6;
+ $this->distance = 2;
+ $this->direction = 0;
+ $this->alignment = self::SHADOW_BOTTOM_RIGHT;
+ $this->color = new Color(Color::COLOR_BLACK);
+ $this->alpha = 50;
+ }
+
+ /**
+ * Get Visible.
+ *
+ * @return bool
+ */
+ public function getVisible()
+ {
+ return $this->visible;
+ }
+
+ /**
+ * Set Visible.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setVisible($pValue)
+ {
+ $this->visible = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Blur radius.
+ *
+ * @return int
+ */
+ public function getBlurRadius()
+ {
+ return $this->blurRadius;
+ }
+
+ /**
+ * Set Blur radius.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setBlurRadius($pValue)
+ {
+ $this->blurRadius = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Shadow distance.
+ *
+ * @return int
+ */
+ public function getDistance()
+ {
+ return $this->distance;
+ }
+
+ /**
+ * Set Shadow distance.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setDistance($pValue)
+ {
+ $this->distance = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Shadow direction (in degrees).
+ *
+ * @return int
+ */
+ public function getDirection()
+ {
+ return $this->direction;
+ }
+
+ /**
+ * Set Shadow direction (in degrees).
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setDirection($pValue)
+ {
+ $this->direction = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Shadow alignment.
+ *
+ * @return int
+ */
+ public function getAlignment()
+ {
+ return $this->alignment;
+ }
+
+ /**
+ * Set Shadow alignment.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setAlignment($pValue)
+ {
+ $this->alignment = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Color.
+ *
+ * @return Color
+ */
+ public function getColor()
+ {
+ return $this->color;
+ }
+
+ /**
+ * Set Color.
+ *
+ * @param Color $pValue
+ *
+ * @return $this
+ */
+ public function setColor(?Color $pValue = null)
+ {
+ $this->color = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Alpha.
+ *
+ * @return int
+ */
+ public function getAlpha()
+ {
+ return $this->alpha;
+ }
+
+ /**
+ * Set Alpha.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setAlpha($pValue)
+ {
+ $this->alpha = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ ($this->visible ? 't' : 'f') .
+ $this->blurRadius .
+ $this->distance .
+ $this->direction .
+ $this->alignment .
+ $this->color->getHashCode() .
+ $this->alpha .
+ __CLASS__
+ );
+ }
+
+ /**
+ * 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php
new file mode 100755
index 0000000..cc37e7f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php
@@ -0,0 +1,490 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+/**
+ * <code>
+ * Header/Footer Formatting Syntax taken from Office Open XML Part 4 - Markup Language Reference, page 1970:.
+ *
+ * There are a number of formatting codes that can be written inline with the actual header / footer text, which
+ * affect the formatting in the header or footer.
+ *
+ * Example: This example shows the text "Center Bold Header" on the first line (center section), and the date on
+ * the second line (center section).
+ * &CCenter &"-,Bold"Bold&"-,Regular"Header_x000A_&D
+ *
+ * General Rules:
+ * There is no required order in which these codes must appear.
+ *
+ * The first occurrence of the following codes turns the formatting ON, the second occurrence turns it OFF again:
+ * - strikethrough
+ * - superscript
+ * - subscript
+ * Superscript and subscript cannot both be ON at same time. Whichever comes first wins and the other is ignored,
+ * while the first is ON.
+ * &L - code for "left section" (there are three header / footer locations, "left", "center", and "right"). When
+ * two or more occurrences of this section marker exist, the contents from all markers are concatenated, in the
+ * order of appearance, and placed into the left section.
+ * &P - code for "current page #"
+ * &N - code for "total pages"
+ * &font size - code for "text font size", where font size is a font size in points.
+ * &K - code for "text font color"
+ * RGB Color is specified as RRGGBB
+ * Theme Color is specifed as TTSNN where TT is the theme color Id, S is either "+" or "-" of the tint/shade
+ * value, NN is the tint/shade value.
+ * &S - code for "text strikethrough" on / off
+ * &X - code for "text super script" on / off
+ * &Y - code for "text subscript" on / off
+ * &C - code for "center section". When two or more occurrences of this section marker exist, the contents
+ * from all markers are concatenated, in the order of appearance, and placed into the center section.
+ *
+ * &D - code for "date"
+ * &T - code for "time"
+ * &G - code for "picture as background"
+ * &U - code for "text single underline"
+ * &E - code for "double underline"
+ * &R - code for "right section". When two or more occurrences of this section marker exist, the contents
+ * from all markers are concatenated, in the order of appearance, and placed into the right section.
+ * &Z - code for "this workbook's file path"
+ * &F - code for "this workbook's file name"
+ * &A - code for "sheet tab name"
+ * &+ - code for add to page #.
+ * &- - code for subtract from page #.
+ * &"font name,font type" - code for "text font name" and "text font type", where font name and font type
+ * are strings specifying the name and type of the font, separated by a comma. When a hyphen appears in font
+ * name, it means "none specified". Both of font name and font type can be localized values.
+ * &"-,Bold" - code for "bold font style"
+ * &B - also means "bold font style".
+ * &"-,Regular" - code for "regular font style"
+ * &"-,Italic" - code for "italic font style"
+ * &I - also means "italic font style"
+ * &"-,Bold Italic" code for "bold italic font style"
+ * &O - code for "outline style"
+ * &H - code for "shadow style"
+ * </code>
+ */
+class HeaderFooter
+{
+ // Header/footer image location
+ const IMAGE_HEADER_LEFT = 'LH';
+ const IMAGE_HEADER_CENTER = 'CH';
+ const IMAGE_HEADER_RIGHT = 'RH';
+ const IMAGE_FOOTER_LEFT = 'LF';
+ const IMAGE_FOOTER_CENTER = 'CF';
+ const IMAGE_FOOTER_RIGHT = 'RF';
+
+ /**
+ * OddHeader.
+ *
+ * @var string
+ */
+ private $oddHeader = '';
+
+ /**
+ * OddFooter.
+ *
+ * @var string
+ */
+ private $oddFooter = '';
+
+ /**
+ * EvenHeader.
+ *
+ * @var string
+ */
+ private $evenHeader = '';
+
+ /**
+ * EvenFooter.
+ *
+ * @var string
+ */
+ private $evenFooter = '';
+
+ /**
+ * FirstHeader.
+ *
+ * @var string
+ */
+ private $firstHeader = '';
+
+ /**
+ * FirstFooter.
+ *
+ * @var string
+ */
+ private $firstFooter = '';
+
+ /**
+ * Different header for Odd/Even, defaults to false.
+ *
+ * @var bool
+ */
+ private $differentOddEven = false;
+
+ /**
+ * Different header for first page, defaults to false.
+ *
+ * @var bool
+ */
+ private $differentFirst = false;
+
+ /**
+ * Scale with document, defaults to true.
+ *
+ * @var bool
+ */
+ private $scaleWithDocument = true;
+
+ /**
+ * Align with margins, defaults to true.
+ *
+ * @var bool
+ */
+ private $alignWithMargins = true;
+
+ /**
+ * Header/footer images.
+ *
+ * @var HeaderFooterDrawing[]
+ */
+ private $headerFooterImages = [];
+
+ /**
+ * Create a new HeaderFooter.
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * Get OddHeader.
+ *
+ * @return string
+ */
+ public function getOddHeader()
+ {
+ return $this->oddHeader;
+ }
+
+ /**
+ * Set OddHeader.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setOddHeader($pValue)
+ {
+ $this->oddHeader = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get OddFooter.
+ *
+ * @return string
+ */
+ public function getOddFooter()
+ {
+ return $this->oddFooter;
+ }
+
+ /**
+ * Set OddFooter.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setOddFooter($pValue)
+ {
+ $this->oddFooter = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get EvenHeader.
+ *
+ * @return string
+ */
+ public function getEvenHeader()
+ {
+ return $this->evenHeader;
+ }
+
+ /**
+ * Set EvenHeader.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setEvenHeader($pValue)
+ {
+ $this->evenHeader = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get EvenFooter.
+ *
+ * @return string
+ */
+ public function getEvenFooter()
+ {
+ return $this->evenFooter;
+ }
+
+ /**
+ * Set EvenFooter.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setEvenFooter($pValue)
+ {
+ $this->evenFooter = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get FirstHeader.
+ *
+ * @return string
+ */
+ public function getFirstHeader()
+ {
+ return $this->firstHeader;
+ }
+
+ /**
+ * Set FirstHeader.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setFirstHeader($pValue)
+ {
+ $this->firstHeader = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get FirstFooter.
+ *
+ * @return string
+ */
+ public function getFirstFooter()
+ {
+ return $this->firstFooter;
+ }
+
+ /**
+ * Set FirstFooter.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setFirstFooter($pValue)
+ {
+ $this->firstFooter = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get DifferentOddEven.
+ *
+ * @return bool
+ */
+ public function getDifferentOddEven()
+ {
+ return $this->differentOddEven;
+ }
+
+ /**
+ * Set DifferentOddEven.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setDifferentOddEven($pValue)
+ {
+ $this->differentOddEven = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get DifferentFirst.
+ *
+ * @return bool
+ */
+ public function getDifferentFirst()
+ {
+ return $this->differentFirst;
+ }
+
+ /**
+ * Set DifferentFirst.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setDifferentFirst($pValue)
+ {
+ $this->differentFirst = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get ScaleWithDocument.
+ *
+ * @return bool
+ */
+ public function getScaleWithDocument()
+ {
+ return $this->scaleWithDocument;
+ }
+
+ /**
+ * Set ScaleWithDocument.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setScaleWithDocument($pValue)
+ {
+ $this->scaleWithDocument = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get AlignWithMargins.
+ *
+ * @return bool
+ */
+ public function getAlignWithMargins()
+ {
+ return $this->alignWithMargins;
+ }
+
+ /**
+ * Set AlignWithMargins.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setAlignWithMargins($pValue)
+ {
+ $this->alignWithMargins = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Add header/footer image.
+ *
+ * @param string $location
+ *
+ * @return $this
+ */
+ public function addImage(HeaderFooterDrawing $image, $location = self::IMAGE_HEADER_LEFT)
+ {
+ $this->headerFooterImages[$location] = $image;
+
+ return $this;
+ }
+
+ /**
+ * Remove header/footer image.
+ *
+ * @param string $location
+ *
+ * @return $this
+ */
+ public function removeImage($location = self::IMAGE_HEADER_LEFT)
+ {
+ if (isset($this->headerFooterImages[$location])) {
+ unset($this->headerFooterImages[$location]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set header/footer images.
+ *
+ * @param HeaderFooterDrawing[] $images
+ *
+ * @return $this
+ */
+ public function setImages(array $images)
+ {
+ $this->headerFooterImages = $images;
+
+ return $this;
+ }
+
+ /**
+ * Get header/footer images.
+ *
+ * @return HeaderFooterDrawing[]
+ */
+ public function getImages()
+ {
+ // Sort array
+ $images = [];
+ if (isset($this->headerFooterImages[self::IMAGE_HEADER_LEFT])) {
+ $images[self::IMAGE_HEADER_LEFT] = $this->headerFooterImages[self::IMAGE_HEADER_LEFT];
+ }
+ if (isset($this->headerFooterImages[self::IMAGE_HEADER_CENTER])) {
+ $images[self::IMAGE_HEADER_CENTER] = $this->headerFooterImages[self::IMAGE_HEADER_CENTER];
+ }
+ if (isset($this->headerFooterImages[self::IMAGE_HEADER_RIGHT])) {
+ $images[self::IMAGE_HEADER_RIGHT] = $this->headerFooterImages[self::IMAGE_HEADER_RIGHT];
+ }
+ if (isset($this->headerFooterImages[self::IMAGE_FOOTER_LEFT])) {
+ $images[self::IMAGE_FOOTER_LEFT] = $this->headerFooterImages[self::IMAGE_FOOTER_LEFT];
+ }
+ if (isset($this->headerFooterImages[self::IMAGE_FOOTER_CENTER])) {
+ $images[self::IMAGE_FOOTER_CENTER] = $this->headerFooterImages[self::IMAGE_FOOTER_CENTER];
+ }
+ if (isset($this->headerFooterImages[self::IMAGE_FOOTER_RIGHT])) {
+ $images[self::IMAGE_FOOTER_RIGHT] = $this->headerFooterImages[self::IMAGE_FOOTER_RIGHT];
+ }
+ $this->headerFooterImages = $images;
+
+ return $this->headerFooterImages;
+ }
+
+ /**
+ * 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php
new file mode 100755
index 0000000..b42c732
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+class HeaderFooterDrawing extends Drawing
+{
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->getPath() .
+ $this->name .
+ $this->offsetX .
+ $this->offsetY .
+ $this->width .
+ $this->height .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php
new file mode 100755
index 0000000..6cfed37
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Iterator implements \Iterator
+{
+ /**
+ * Spreadsheet to iterate.
+ *
+ * @var Spreadsheet
+ */
+ private $subject;
+
+ /**
+ * Current iterator position.
+ *
+ * @var int
+ */
+ private $position = 0;
+
+ /**
+ * Create a new worksheet iterator.
+ */
+ public function __construct(Spreadsheet $subject)
+ {
+ // Set subject
+ $this->subject = $subject;
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->subject = null;
+ }
+
+ /**
+ * Rewind iterator.
+ */
+ public function rewind(): void
+ {
+ $this->position = 0;
+ }
+
+ /**
+ * Current Worksheet.
+ *
+ * @return Worksheet
+ */
+ public function current()
+ {
+ return $this->subject->getSheet($this->position);
+ }
+
+ /**
+ * Current key.
+ *
+ * @return int
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Next value.
+ */
+ public function next(): void
+ {
+ ++$this->position;
+ }
+
+ /**
+ * Are there more Worksheet instances available?
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return $this->position < $this->subject->getSheetCount() && $this->position >= 0;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php
new file mode 100755
index 0000000..22e0909
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php
@@ -0,0 +1,169 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+class MemoryDrawing extends BaseDrawing
+{
+ // Rendering functions
+ const RENDERING_DEFAULT = 'imagepng';
+ const RENDERING_PNG = 'imagepng';
+ const RENDERING_GIF = 'imagegif';
+ const RENDERING_JPEG = 'imagejpeg';
+
+ // MIME types
+ const MIMETYPE_DEFAULT = 'image/png';
+ const MIMETYPE_PNG = 'image/png';
+ const MIMETYPE_GIF = 'image/gif';
+ const MIMETYPE_JPEG = 'image/jpeg';
+
+ /**
+ * Image resource.
+ *
+ * @var resource
+ */
+ private $imageResource;
+
+ /**
+ * Rendering function.
+ *
+ * @var string
+ */
+ private $renderingFunction;
+
+ /**
+ * Mime type.
+ *
+ * @var string
+ */
+ private $mimeType;
+
+ /**
+ * Unique name.
+ *
+ * @var string
+ */
+ private $uniqueName;
+
+ /**
+ * Create a new MemoryDrawing.
+ */
+ public function __construct()
+ {
+ // Initialise values
+ $this->imageResource = null;
+ $this->renderingFunction = self::RENDERING_DEFAULT;
+ $this->mimeType = self::MIMETYPE_DEFAULT;
+ $this->uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999));
+
+ // Initialize parent
+ parent::__construct();
+ }
+
+ /**
+ * Get image resource.
+ *
+ * @return resource
+ */
+ public function getImageResource()
+ {
+ return $this->imageResource;
+ }
+
+ /**
+ * Set image resource.
+ *
+ * @param resource $value
+ *
+ * @return $this
+ */
+ public function setImageResource($value)
+ {
+ $this->imageResource = $value;
+
+ if ($this->imageResource !== null) {
+ // Get width/height
+ $this->width = imagesx($this->imageResource);
+ $this->height = imagesy($this->imageResource);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get rendering function.
+ *
+ * @return string
+ */
+ public function getRenderingFunction()
+ {
+ return $this->renderingFunction;
+ }
+
+ /**
+ * Set rendering function.
+ *
+ * @param string $value see self::RENDERING_*
+ *
+ * @return $this
+ */
+ public function setRenderingFunction($value)
+ {
+ $this->renderingFunction = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get mime type.
+ *
+ * @return string
+ */
+ public function getMimeType()
+ {
+ return $this->mimeType;
+ }
+
+ /**
+ * Set mime type.
+ *
+ * @param string $value see self::MIMETYPE_*
+ *
+ * @return $this
+ */
+ public function setMimeType($value)
+ {
+ $this->mimeType = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get indexed filename (using image index).
+ *
+ * @return string
+ */
+ public function getIndexedFilename()
+ {
+ $extension = strtolower($this->getMimeType());
+ $extension = explode('/', $extension);
+ $extension = $extension[1];
+
+ return $this->uniqueName . $this->getImageIndex() . '.' . $extension;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->renderingFunction .
+ $this->mimeType .
+ $this->uniqueName .
+ parent::getHashCode() .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php
new file mode 100755
index 0000000..9ebfb64
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php
@@ -0,0 +1,214 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+class PageMargins
+{
+ /**
+ * Left.
+ *
+ * @var float
+ */
+ private $left = 0.7;
+
+ /**
+ * Right.
+ *
+ * @var float
+ */
+ private $right = 0.7;
+
+ /**
+ * Top.
+ *
+ * @var float
+ */
+ private $top = 0.75;
+
+ /**
+ * Bottom.
+ *
+ * @var float
+ */
+ private $bottom = 0.75;
+
+ /**
+ * Header.
+ *
+ * @var float
+ */
+ private $header = 0.3;
+
+ /**
+ * Footer.
+ *
+ * @var float
+ */
+ private $footer = 0.3;
+
+ /**
+ * Create a new PageMargins.
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * Get Left.
+ *
+ * @return float
+ */
+ public function getLeft()
+ {
+ return $this->left;
+ }
+
+ /**
+ * Set Left.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setLeft($pValue)
+ {
+ $this->left = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Right.
+ *
+ * @return float
+ */
+ public function getRight()
+ {
+ return $this->right;
+ }
+
+ /**
+ * Set Right.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setRight($pValue)
+ {
+ $this->right = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Top.
+ *
+ * @return float
+ */
+ public function getTop()
+ {
+ return $this->top;
+ }
+
+ /**
+ * Set Top.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setTop($pValue)
+ {
+ $this->top = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Bottom.
+ *
+ * @return float
+ */
+ public function getBottom()
+ {
+ return $this->bottom;
+ }
+
+ /**
+ * Set Bottom.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setBottom($pValue)
+ {
+ $this->bottom = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Header.
+ *
+ * @return float
+ */
+ public function getHeader()
+ {
+ return $this->header;
+ }
+
+ /**
+ * Set Header.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setHeader($pValue)
+ {
+ $this->header = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Footer.
+ *
+ * @return float
+ */
+ public function getFooter()
+ {
+ return $this->footer;
+ }
+
+ /**
+ * Set Footer.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setFooter($pValue)
+ {
+ $this->footer = $pValue;
+
+ 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php
new file mode 100755
index 0000000..f29dbad
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php
@@ -0,0 +1,835 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+/**
+ * <code>
+ * Paper size taken from Office Open XML Part 4 - Markup Language Reference, page 1988:.
+ *
+ * 1 = Letter paper (8.5 in. by 11 in.)
+ * 2 = Letter small paper (8.5 in. by 11 in.)
+ * 3 = Tabloid paper (11 in. by 17 in.)
+ * 4 = Ledger paper (17 in. by 11 in.)
+ * 5 = Legal paper (8.5 in. by 14 in.)
+ * 6 = Statement paper (5.5 in. by 8.5 in.)
+ * 7 = Executive paper (7.25 in. by 10.5 in.)
+ * 8 = A3 paper (297 mm by 420 mm)
+ * 9 = A4 paper (210 mm by 297 mm)
+ * 10 = A4 small paper (210 mm by 297 mm)
+ * 11 = A5 paper (148 mm by 210 mm)
+ * 12 = B4 paper (250 mm by 353 mm)
+ * 13 = B5 paper (176 mm by 250 mm)
+ * 14 = Folio paper (8.5 in. by 13 in.)
+ * 15 = Quarto paper (215 mm by 275 mm)
+ * 16 = Standard paper (10 in. by 14 in.)
+ * 17 = Standard paper (11 in. by 17 in.)
+ * 18 = Note paper (8.5 in. by 11 in.)
+ * 19 = #9 envelope (3.875 in. by 8.875 in.)
+ * 20 = #10 envelope (4.125 in. by 9.5 in.)
+ * 21 = #11 envelope (4.5 in. by 10.375 in.)
+ * 22 = #12 envelope (4.75 in. by 11 in.)
+ * 23 = #14 envelope (5 in. by 11.5 in.)
+ * 24 = C paper (17 in. by 22 in.)
+ * 25 = D paper (22 in. by 34 in.)
+ * 26 = E paper (34 in. by 44 in.)
+ * 27 = DL envelope (110 mm by 220 mm)
+ * 28 = C5 envelope (162 mm by 229 mm)
+ * 29 = C3 envelope (324 mm by 458 mm)
+ * 30 = C4 envelope (229 mm by 324 mm)
+ * 31 = C6 envelope (114 mm by 162 mm)
+ * 32 = C65 envelope (114 mm by 229 mm)
+ * 33 = B4 envelope (250 mm by 353 mm)
+ * 34 = B5 envelope (176 mm by 250 mm)
+ * 35 = B6 envelope (176 mm by 125 mm)
+ * 36 = Italy envelope (110 mm by 230 mm)
+ * 37 = Monarch envelope (3.875 in. by 7.5 in.).
+ * 38 = 6 3/4 envelope (3.625 in. by 6.5 in.)
+ * 39 = US standard fanfold (14.875 in. by 11 in.)
+ * 40 = German standard fanfold (8.5 in. by 12 in.)
+ * 41 = German legal fanfold (8.5 in. by 13 in.)
+ * 42 = ISO B4 (250 mm by 353 mm)
+ * 43 = Japanese double postcard (200 mm by 148 mm)
+ * 44 = Standard paper (9 in. by 11 in.)
+ * 45 = Standard paper (10 in. by 11 in.)
+ * 46 = Standard paper (15 in. by 11 in.)
+ * 47 = Invite envelope (220 mm by 220 mm)
+ * 50 = Letter extra paper (9.275 in. by 12 in.)
+ * 51 = Legal extra paper (9.275 in. by 15 in.)
+ * 52 = Tabloid extra paper (11.69 in. by 18 in.)
+ * 53 = A4 extra paper (236 mm by 322 mm)
+ * 54 = Letter transverse paper (8.275 in. by 11 in.)
+ * 55 = A4 transverse paper (210 mm by 297 mm)
+ * 56 = Letter extra transverse paper (9.275 in. by 12 in.)
+ * 57 = SuperA/SuperA/A4 paper (227 mm by 356 mm)
+ * 58 = SuperB/SuperB/A3 paper (305 mm by 487 mm)
+ * 59 = Letter plus paper (8.5 in. by 12.69 in.)
+ * 60 = A4 plus paper (210 mm by 330 mm)
+ * 61 = A5 transverse paper (148 mm by 210 mm)
+ * 62 = JIS B5 transverse paper (182 mm by 257 mm)
+ * 63 = A3 extra paper (322 mm by 445 mm)
+ * 64 = A5 extra paper (174 mm by 235 mm)
+ * 65 = ISO B5 extra paper (201 mm by 276 mm)
+ * 66 = A2 paper (420 mm by 594 mm)
+ * 67 = A3 transverse paper (297 mm by 420 mm)
+ * 68 = A3 extra transverse paper (322 mm by 445 mm)
+ * </code>
+ */
+class PageSetup
+{
+ // Paper size
+ const PAPERSIZE_LETTER = 1;
+ const PAPERSIZE_LETTER_SMALL = 2;
+ const PAPERSIZE_TABLOID = 3;
+ const PAPERSIZE_LEDGER = 4;
+ const PAPERSIZE_LEGAL = 5;
+ const PAPERSIZE_STATEMENT = 6;
+ const PAPERSIZE_EXECUTIVE = 7;
+ const PAPERSIZE_A3 = 8;
+ const PAPERSIZE_A4 = 9;
+ const PAPERSIZE_A4_SMALL = 10;
+ const PAPERSIZE_A5 = 11;
+ const PAPERSIZE_B4 = 12;
+ const PAPERSIZE_B5 = 13;
+ const PAPERSIZE_FOLIO = 14;
+ const PAPERSIZE_QUARTO = 15;
+ const PAPERSIZE_STANDARD_1 = 16;
+ const PAPERSIZE_STANDARD_2 = 17;
+ const PAPERSIZE_NOTE = 18;
+ const PAPERSIZE_NO9_ENVELOPE = 19;
+ const PAPERSIZE_NO10_ENVELOPE = 20;
+ const PAPERSIZE_NO11_ENVELOPE = 21;
+ const PAPERSIZE_NO12_ENVELOPE = 22;
+ const PAPERSIZE_NO14_ENVELOPE = 23;
+ const PAPERSIZE_C = 24;
+ const PAPERSIZE_D = 25;
+ const PAPERSIZE_E = 26;
+ const PAPERSIZE_DL_ENVELOPE = 27;
+ const PAPERSIZE_C5_ENVELOPE = 28;
+ const PAPERSIZE_C3_ENVELOPE = 29;
+ const PAPERSIZE_C4_ENVELOPE = 30;
+ const PAPERSIZE_C6_ENVELOPE = 31;
+ const PAPERSIZE_C65_ENVELOPE = 32;
+ const PAPERSIZE_B4_ENVELOPE = 33;
+ const PAPERSIZE_B5_ENVELOPE = 34;
+ const PAPERSIZE_B6_ENVELOPE = 35;
+ const PAPERSIZE_ITALY_ENVELOPE = 36;
+ const PAPERSIZE_MONARCH_ENVELOPE = 37;
+ const PAPERSIZE_6_3_4_ENVELOPE = 38;
+ const PAPERSIZE_US_STANDARD_FANFOLD = 39;
+ const PAPERSIZE_GERMAN_STANDARD_FANFOLD = 40;
+ const PAPERSIZE_GERMAN_LEGAL_FANFOLD = 41;
+ const PAPERSIZE_ISO_B4 = 42;
+ const PAPERSIZE_JAPANESE_DOUBLE_POSTCARD = 43;
+ const PAPERSIZE_STANDARD_PAPER_1 = 44;
+ const PAPERSIZE_STANDARD_PAPER_2 = 45;
+ const PAPERSIZE_STANDARD_PAPER_3 = 46;
+ const PAPERSIZE_INVITE_ENVELOPE = 47;
+ const PAPERSIZE_LETTER_EXTRA_PAPER = 48;
+ const PAPERSIZE_LEGAL_EXTRA_PAPER = 49;
+ const PAPERSIZE_TABLOID_EXTRA_PAPER = 50;
+ const PAPERSIZE_A4_EXTRA_PAPER = 51;
+ const PAPERSIZE_LETTER_TRANSVERSE_PAPER = 52;
+ const PAPERSIZE_A4_TRANSVERSE_PAPER = 53;
+ const PAPERSIZE_LETTER_EXTRA_TRANSVERSE_PAPER = 54;
+ const PAPERSIZE_SUPERA_SUPERA_A4_PAPER = 55;
+ const PAPERSIZE_SUPERB_SUPERB_A3_PAPER = 56;
+ const PAPERSIZE_LETTER_PLUS_PAPER = 57;
+ const PAPERSIZE_A4_PLUS_PAPER = 58;
+ const PAPERSIZE_A5_TRANSVERSE_PAPER = 59;
+ const PAPERSIZE_JIS_B5_TRANSVERSE_PAPER = 60;
+ const PAPERSIZE_A3_EXTRA_PAPER = 61;
+ const PAPERSIZE_A5_EXTRA_PAPER = 62;
+ const PAPERSIZE_ISO_B5_EXTRA_PAPER = 63;
+ const PAPERSIZE_A2_PAPER = 64;
+ const PAPERSIZE_A3_TRANSVERSE_PAPER = 65;
+ const PAPERSIZE_A3_EXTRA_TRANSVERSE_PAPER = 66;
+
+ // Page orientation
+ const ORIENTATION_DEFAULT = 'default';
+ const ORIENTATION_LANDSCAPE = 'landscape';
+ const ORIENTATION_PORTRAIT = 'portrait';
+
+ // Print Range Set Method
+ const SETPRINTRANGE_OVERWRITE = 'O';
+ const SETPRINTRANGE_INSERT = 'I';
+
+ /**
+ * Paper size.
+ *
+ * @var int
+ */
+ private $paperSize = self::PAPERSIZE_LETTER;
+
+ /**
+ * Orientation.
+ *
+ * @var string
+ */
+ private $orientation = self::ORIENTATION_DEFAULT;
+
+ /**
+ * Scale (Print Scale).
+ *
+ * Print scaling. Valid values range from 10 to 400
+ * This setting is overridden when fitToWidth and/or fitToHeight are in use
+ *
+ * @var null|int
+ */
+ private $scale = 100;
+
+ /**
+ * Fit To Page
+ * Whether scale or fitToWith / fitToHeight applies.
+ *
+ * @var bool
+ */
+ private $fitToPage = false;
+
+ /**
+ * Fit To Height
+ * Number of vertical pages to fit on.
+ *
+ * @var null|int
+ */
+ private $fitToHeight = 1;
+
+ /**
+ * Fit To Width
+ * Number of horizontal pages to fit on.
+ *
+ * @var null|int
+ */
+ private $fitToWidth = 1;
+
+ /**
+ * Columns to repeat at left.
+ *
+ * @var array Containing start column and end column, empty array if option unset
+ */
+ private $columnsToRepeatAtLeft = ['', ''];
+
+ /**
+ * Rows to repeat at top.
+ *
+ * @var array Containing start row number and end row number, empty array if option unset
+ */
+ private $rowsToRepeatAtTop = [0, 0];
+
+ /**
+ * Center page horizontally.
+ *
+ * @var bool
+ */
+ private $horizontalCentered = false;
+
+ /**
+ * Center page vertically.
+ *
+ * @var bool
+ */
+ private $verticalCentered = false;
+
+ /**
+ * Print area.
+ *
+ * @var string
+ */
+ private $printArea;
+
+ /**
+ * First page number.
+ *
+ * @var int
+ */
+ private $firstPageNumber;
+
+ /**
+ * Create a new PageSetup.
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * Get Paper Size.
+ *
+ * @return int
+ */
+ public function getPaperSize()
+ {
+ return $this->paperSize;
+ }
+
+ /**
+ * Set Paper Size.
+ *
+ * @param int $pValue see self::PAPERSIZE_*
+ *
+ * @return $this
+ */
+ public function setPaperSize($pValue)
+ {
+ $this->paperSize = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Orientation.
+ *
+ * @return string
+ */
+ public function getOrientation()
+ {
+ return $this->orientation;
+ }
+
+ /**
+ * Set Orientation.
+ *
+ * @param string $pValue see self::ORIENTATION_*
+ *
+ * @return $this
+ */
+ public function setOrientation($pValue)
+ {
+ $this->orientation = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Scale.
+ *
+ * @return null|int
+ */
+ public function getScale()
+ {
+ return $this->scale;
+ }
+
+ /**
+ * Set Scale.
+ * Print scaling. Valid values range from 10 to 400
+ * This setting is overridden when fitToWidth and/or fitToHeight are in use.
+ *
+ * @param null|int $pValue
+ * @param bool $pUpdate Update fitToPage so scaling applies rather than fitToHeight / fitToWidth
+ *
+ * @return $this
+ */
+ public function setScale($pValue, $pUpdate = true)
+ {
+ // Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface,
+ // but it is apparently still able to handle any scale >= 0, where 0 results in 100
+ if (($pValue >= 0) || $pValue === null) {
+ $this->scale = $pValue;
+ if ($pUpdate) {
+ $this->fitToPage = false;
+ }
+ } else {
+ throw new PhpSpreadsheetException('Scale must not be negative');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Fit To Page.
+ *
+ * @return bool
+ */
+ public function getFitToPage()
+ {
+ return $this->fitToPage;
+ }
+
+ /**
+ * Set Fit To Page.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setFitToPage($pValue)
+ {
+ $this->fitToPage = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Fit To Height.
+ *
+ * @return null|int
+ */
+ public function getFitToHeight()
+ {
+ return $this->fitToHeight;
+ }
+
+ /**
+ * Set Fit To Height.
+ *
+ * @param null|int $pValue
+ * @param bool $pUpdate Update fitToPage so it applies rather than scaling
+ *
+ * @return $this
+ */
+ public function setFitToHeight($pValue, $pUpdate = true)
+ {
+ $this->fitToHeight = $pValue;
+ if ($pUpdate) {
+ $this->fitToPage = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Fit To Width.
+ *
+ * @return null|int
+ */
+ public function getFitToWidth()
+ {
+ return $this->fitToWidth;
+ }
+
+ /**
+ * Set Fit To Width.
+ *
+ * @param null|int $pValue
+ * @param bool $pUpdate Update fitToPage so it applies rather than scaling
+ *
+ * @return $this
+ */
+ public function setFitToWidth($pValue, $pUpdate = true)
+ {
+ $this->fitToWidth = $pValue;
+ if ($pUpdate) {
+ $this->fitToPage = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Is Columns to repeat at left set?
+ *
+ * @return bool
+ */
+ public function isColumnsToRepeatAtLeftSet()
+ {
+ if (is_array($this->columnsToRepeatAtLeft)) {
+ if ($this->columnsToRepeatAtLeft[0] != '' && $this->columnsToRepeatAtLeft[1] != '') {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get Columns to repeat at left.
+ *
+ * @return array Containing start column and end column, empty array if option unset
+ */
+ public function getColumnsToRepeatAtLeft()
+ {
+ return $this->columnsToRepeatAtLeft;
+ }
+
+ /**
+ * Set Columns to repeat at left.
+ *
+ * @param array $pValue Containing start column and end column, empty array if option unset
+ *
+ * @return $this
+ */
+ public function setColumnsToRepeatAtLeft(array $pValue)
+ {
+ $this->columnsToRepeatAtLeft = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Set Columns to repeat at left by start and end.
+ *
+ * @param string $pStart eg: 'A'
+ * @param string $pEnd eg: 'B'
+ *
+ * @return $this
+ */
+ public function setColumnsToRepeatAtLeftByStartAndEnd($pStart, $pEnd)
+ {
+ $this->columnsToRepeatAtLeft = [$pStart, $pEnd];
+
+ return $this;
+ }
+
+ /**
+ * Is Rows to repeat at top set?
+ *
+ * @return bool
+ */
+ public function isRowsToRepeatAtTopSet()
+ {
+ if (is_array($this->rowsToRepeatAtTop)) {
+ if ($this->rowsToRepeatAtTop[0] != 0 && $this->rowsToRepeatAtTop[1] != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get Rows to repeat at top.
+ *
+ * @return array Containing start column and end column, empty array if option unset
+ */
+ public function getRowsToRepeatAtTop()
+ {
+ return $this->rowsToRepeatAtTop;
+ }
+
+ /**
+ * Set Rows to repeat at top.
+ *
+ * @param array $pValue Containing start column and end column, empty array if option unset
+ *
+ * @return $this
+ */
+ public function setRowsToRepeatAtTop(array $pValue)
+ {
+ $this->rowsToRepeatAtTop = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Set Rows to repeat at top by start and end.
+ *
+ * @param int $pStart eg: 1
+ * @param int $pEnd eg: 1
+ *
+ * @return $this
+ */
+ public function setRowsToRepeatAtTopByStartAndEnd($pStart, $pEnd)
+ {
+ $this->rowsToRepeatAtTop = [$pStart, $pEnd];
+
+ return $this;
+ }
+
+ /**
+ * Get center page horizontally.
+ *
+ * @return bool
+ */
+ public function getHorizontalCentered()
+ {
+ return $this->horizontalCentered;
+ }
+
+ /**
+ * Set center page horizontally.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setHorizontalCentered($value)
+ {
+ $this->horizontalCentered = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get center page vertically.
+ *
+ * @return bool
+ */
+ public function getVerticalCentered()
+ {
+ return $this->verticalCentered;
+ }
+
+ /**
+ * Set center page vertically.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setVerticalCentered($value)
+ {
+ $this->verticalCentered = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get print area.
+ *
+ * @param int $index Identifier for a specific print area range if several ranges have been set
+ * Default behaviour, or a index value of 0, will return all ranges as a comma-separated string
+ * Otherwise, the specific range identified by the value of $index will be returned
+ * Print areas are numbered from 1
+ *
+ * @return string
+ */
+ public function getPrintArea($index = 0)
+ {
+ if ($index == 0) {
+ return $this->printArea;
+ }
+ $printAreas = explode(',', $this->printArea);
+ if (isset($printAreas[$index - 1])) {
+ return $printAreas[$index - 1];
+ }
+
+ throw new PhpSpreadsheetException('Requested Print Area does not exist');
+ }
+
+ /**
+ * Is print area set?
+ *
+ * @param int $index Identifier for a specific print area range if several ranges have been set
+ * Default behaviour, or an index value of 0, will identify whether any print range is set
+ * Otherwise, existence of the range identified by the value of $index will be returned
+ * Print areas are numbered from 1
+ *
+ * @return bool
+ */
+ public function isPrintAreaSet($index = 0)
+ {
+ if ($index == 0) {
+ return $this->printArea !== null;
+ }
+ $printAreas = explode(',', $this->printArea);
+
+ return isset($printAreas[$index - 1]);
+ }
+
+ /**
+ * Clear a print area.
+ *
+ * @param int $index Identifier for a specific print area range if several ranges have been set
+ * Default behaviour, or an index value of 0, will clear all print ranges that are set
+ * Otherwise, the range identified by the value of $index will be removed from the series
+ * Print areas are numbered from 1
+ *
+ * @return $this
+ */
+ public function clearPrintArea($index = 0)
+ {
+ if ($index == 0) {
+ $this->printArea = null;
+ } else {
+ $printAreas = explode(',', $this->printArea);
+ if (isset($printAreas[$index - 1])) {
+ unset($printAreas[$index - 1]);
+ $this->printArea = implode(',', $printAreas);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set print area. e.g. 'A1:D10' or 'A1:D10,G5:M20'.
+ *
+ * @param string $value
+ * @param int $index Identifier for a specific print area range allowing several ranges to be set
+ * When the method is "O"verwrite, then a positive integer index will overwrite that indexed
+ * entry in the print areas list; a negative index value will identify which entry to
+ * overwrite working bacward through the print area to the list, with the last entry as -1.
+ * Specifying an index value of 0, will overwrite <b>all</b> existing print ranges.
+ * When the method is "I"nsert, then a positive index will insert after that indexed entry in
+ * the print areas list, while a negative index will insert before the indexed entry.
+ * Specifying an index value of 0, will always append the new print range at the end of the
+ * list.
+ * Print areas are numbered from 1
+ * @param string $method Determines the method used when setting multiple print areas
+ * Default behaviour, or the "O" method, overwrites existing print area
+ * The "I" method, inserts the new print area before any specified index, or at the end of the list
+ *
+ * @return $this
+ */
+ public function setPrintArea($value, $index = 0, $method = self::SETPRINTRANGE_OVERWRITE)
+ {
+ if (strpos($value, '!') !== false) {
+ throw new PhpSpreadsheetException('Cell coordinate must not specify a worksheet.');
+ } elseif (strpos($value, ':') === false) {
+ throw new PhpSpreadsheetException('Cell coordinate must be a range of cells.');
+ } elseif (strpos($value, '$') !== false) {
+ throw new PhpSpreadsheetException('Cell coordinate must not be absolute.');
+ }
+ $value = strtoupper($value);
+
+ if ($method == self::SETPRINTRANGE_OVERWRITE) {
+ if ($index == 0) {
+ $this->printArea = $value;
+ } else {
+ $printAreas = explode(',', $this->printArea);
+ if ($index < 0) {
+ $index = count($printAreas) - abs($index) + 1;
+ }
+ if (($index <= 0) || ($index > count($printAreas))) {
+ throw new PhpSpreadsheetException('Invalid index for setting print range.');
+ }
+ $printAreas[$index - 1] = $value;
+ $this->printArea = implode(',', $printAreas);
+ }
+ } elseif ($method == self::SETPRINTRANGE_INSERT) {
+ if ($index == 0) {
+ $this->printArea .= ($this->printArea == '') ? $value : ',' . $value;
+ } else {
+ $printAreas = explode(',', $this->printArea);
+ if ($index < 0) {
+ $index = abs($index) - 1;
+ }
+ if ($index > count($printAreas)) {
+ throw new PhpSpreadsheetException('Invalid index for setting print range.');
+ }
+ $printAreas = array_merge(array_slice($printAreas, 0, $index), [$value], array_slice($printAreas, $index));
+ $this->printArea = implode(',', $printAreas);
+ }
+ } else {
+ throw new PhpSpreadsheetException('Invalid method for setting print range.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a new print area (e.g. 'A1:D10' or 'A1:D10,G5:M20') to the list of print areas.
+ *
+ * @param string $value
+ * @param int $index Identifier for a specific print area range allowing several ranges to be set
+ * A positive index will insert after that indexed entry in the print areas list, while a
+ * negative index will insert before the indexed entry.
+ * Specifying an index value of 0, will always append the new print range at the end of the
+ * list.
+ * Print areas are numbered from 1
+ *
+ * @return $this
+ */
+ public function addPrintArea($value, $index = -1)
+ {
+ return $this->setPrintArea($value, $index, self::SETPRINTRANGE_INSERT);
+ }
+
+ /**
+ * Set print area.
+ *
+ * @param int $column1 Column 1
+ * @param int $row1 Row 1
+ * @param int $column2 Column 2
+ * @param int $row2 Row 2
+ * @param int $index Identifier for a specific print area range allowing several ranges to be set
+ * When the method is "O"verwrite, then a positive integer index will overwrite that indexed
+ * entry in the print areas list; a negative index value will identify which entry to
+ * overwrite working backward through the print area to the list, with the last entry as -1.
+ * Specifying an index value of 0, will overwrite <b>all</b> existing print ranges.
+ * When the method is "I"nsert, then a positive index will insert after that indexed entry in
+ * the print areas list, while a negative index will insert before the indexed entry.
+ * Specifying an index value of 0, will always append the new print range at the end of the
+ * list.
+ * Print areas are numbered from 1
+ * @param string $method Determines the method used when setting multiple print areas
+ * Default behaviour, or the "O" method, overwrites existing print area
+ * The "I" method, inserts the new print area before any specified index, or at the end of the list
+ *
+ * @return $this
+ */
+ public function setPrintAreaByColumnAndRow($column1, $row1, $column2, $row2, $index = 0, $method = self::SETPRINTRANGE_OVERWRITE)
+ {
+ return $this->setPrintArea(
+ Coordinate::stringFromColumnIndex($column1) . $row1 . ':' . Coordinate::stringFromColumnIndex($column2) . $row2,
+ $index,
+ $method
+ );
+ }
+
+ /**
+ * Add a new print area to the list of print areas.
+ *
+ * @param int $column1 Start Column for the print area
+ * @param int $row1 Start Row for the print area
+ * @param int $column2 End Column for the print area
+ * @param int $row2 End Row for the print area
+ * @param int $index Identifier for a specific print area range allowing several ranges to be set
+ * A positive index will insert after that indexed entry in the print areas list, while a
+ * negative index will insert before the indexed entry.
+ * Specifying an index value of 0, will always append the new print range at the end of the
+ * list.
+ * Print areas are numbered from 1
+ *
+ * @return $this
+ */
+ public function addPrintAreaByColumnAndRow($column1, $row1, $column2, $row2, $index = -1)
+ {
+ return $this->setPrintArea(
+ Coordinate::stringFromColumnIndex($column1) . $row1 . ':' . Coordinate::stringFromColumnIndex($column2) . $row2,
+ $index,
+ self::SETPRINTRANGE_INSERT
+ );
+ }
+
+ /**
+ * Get first page number.
+ *
+ * @return int
+ */
+ public function getFirstPageNumber()
+ {
+ return $this->firstPageNumber;
+ }
+
+ /**
+ * Set first page number.
+ *
+ * @param int $value
+ *
+ * @return $this
+ */
+ public function setFirstPageNumber($value)
+ {
+ $this->firstPageNumber = $value;
+
+ return $this;
+ }
+
+ /**
+ * Reset first page number.
+ *
+ * @return $this
+ */
+ public function resetFirstPageNumber()
+ {
+ return $this->setFirstPageNumber(null);
+ }
+
+ /**
+ * 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php
new file mode 100755
index 0000000..81abc0b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php
@@ -0,0 +1,698 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Shared\PasswordHasher;
+
+class Protection
+{
+ const ALGORITHM_MD2 = 'MD2';
+ const ALGORITHM_MD4 = 'MD4';
+ const ALGORITHM_MD5 = 'MD5';
+ const ALGORITHM_SHA_1 = 'SHA-1';
+ const ALGORITHM_SHA_256 = 'SHA-256';
+ const ALGORITHM_SHA_384 = 'SHA-384';
+ const ALGORITHM_SHA_512 = 'SHA-512';
+ const ALGORITHM_RIPEMD_128 = 'RIPEMD-128';
+ const ALGORITHM_RIPEMD_160 = 'RIPEMD-160';
+ const ALGORITHM_WHIRLPOOL = 'WHIRLPOOL';
+
+ /**
+ * Sheet.
+ *
+ * @var bool
+ */
+ private $sheet = false;
+
+ /**
+ * Objects.
+ *
+ * @var bool
+ */
+ private $objects = false;
+
+ /**
+ * Scenarios.
+ *
+ * @var bool
+ */
+ private $scenarios = false;
+
+ /**
+ * Format cells.
+ *
+ * @var bool
+ */
+ private $formatCells = false;
+
+ /**
+ * Format columns.
+ *
+ * @var bool
+ */
+ private $formatColumns = false;
+
+ /**
+ * Format rows.
+ *
+ * @var bool
+ */
+ private $formatRows = false;
+
+ /**
+ * Insert columns.
+ *
+ * @var bool
+ */
+ private $insertColumns = false;
+
+ /**
+ * Insert rows.
+ *
+ * @var bool
+ */
+ private $insertRows = false;
+
+ /**
+ * Insert hyperlinks.
+ *
+ * @var bool
+ */
+ private $insertHyperlinks = false;
+
+ /**
+ * Delete columns.
+ *
+ * @var bool
+ */
+ private $deleteColumns = false;
+
+ /**
+ * Delete rows.
+ *
+ * @var bool
+ */
+ private $deleteRows = false;
+
+ /**
+ * Select locked cells.
+ *
+ * @var bool
+ */
+ private $selectLockedCells = false;
+
+ /**
+ * Sort.
+ *
+ * @var bool
+ */
+ private $sort = false;
+
+ /**
+ * AutoFilter.
+ *
+ * @var bool
+ */
+ private $autoFilter = false;
+
+ /**
+ * Pivot tables.
+ *
+ * @var bool
+ */
+ private $pivotTables = false;
+
+ /**
+ * Select unlocked cells.
+ *
+ * @var bool
+ */
+ private $selectUnlockedCells = false;
+
+ /**
+ * Hashed password.
+ *
+ * @var string
+ */
+ private $password = '';
+
+ /**
+ * Algorithm name.
+ *
+ * @var string
+ */
+ private $algorithm = '';
+
+ /**
+ * Hash value.
+ *
+ * @var string
+ */
+ private $hash = '';
+
+ /**
+ * Salt value.
+ *
+ * @var string
+ */
+ private $salt = '';
+
+ /**
+ * Spin count.
+ *
+ * @var int
+ */
+ private $spinCount = 10000;
+
+ /**
+ * Create a new Protection.
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * Is some sort of protection enabled?
+ *
+ * @return bool
+ */
+ public function isProtectionEnabled()
+ {
+ return $this->sheet ||
+ $this->objects ||
+ $this->scenarios ||
+ $this->formatCells ||
+ $this->formatColumns ||
+ $this->formatRows ||
+ $this->insertColumns ||
+ $this->insertRows ||
+ $this->insertHyperlinks ||
+ $this->deleteColumns ||
+ $this->deleteRows ||
+ $this->selectLockedCells ||
+ $this->sort ||
+ $this->autoFilter ||
+ $this->pivotTables ||
+ $this->selectUnlockedCells;
+ }
+
+ /**
+ * Get Sheet.
+ *
+ * @return bool
+ */
+ public function getSheet()
+ {
+ return $this->sheet;
+ }
+
+ /**
+ * Set Sheet.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setSheet($pValue)
+ {
+ $this->sheet = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Objects.
+ *
+ * @return bool
+ */
+ public function getObjects()
+ {
+ return $this->objects;
+ }
+
+ /**
+ * Set Objects.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setObjects($pValue)
+ {
+ $this->objects = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Scenarios.
+ *
+ * @return bool
+ */
+ public function getScenarios()
+ {
+ return $this->scenarios;
+ }
+
+ /**
+ * Set Scenarios.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setScenarios($pValue)
+ {
+ $this->scenarios = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get FormatCells.
+ *
+ * @return bool
+ */
+ public function getFormatCells()
+ {
+ return $this->formatCells;
+ }
+
+ /**
+ * Set FormatCells.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setFormatCells($pValue)
+ {
+ $this->formatCells = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get FormatColumns.
+ *
+ * @return bool
+ */
+ public function getFormatColumns()
+ {
+ return $this->formatColumns;
+ }
+
+ /**
+ * Set FormatColumns.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setFormatColumns($pValue)
+ {
+ $this->formatColumns = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get FormatRows.
+ *
+ * @return bool
+ */
+ public function getFormatRows()
+ {
+ return $this->formatRows;
+ }
+
+ /**
+ * Set FormatRows.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setFormatRows($pValue)
+ {
+ $this->formatRows = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get InsertColumns.
+ *
+ * @return bool
+ */
+ public function getInsertColumns()
+ {
+ return $this->insertColumns;
+ }
+
+ /**
+ * Set InsertColumns.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setInsertColumns($pValue)
+ {
+ $this->insertColumns = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get InsertRows.
+ *
+ * @return bool
+ */
+ public function getInsertRows()
+ {
+ return $this->insertRows;
+ }
+
+ /**
+ * Set InsertRows.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setInsertRows($pValue)
+ {
+ $this->insertRows = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get InsertHyperlinks.
+ *
+ * @return bool
+ */
+ public function getInsertHyperlinks()
+ {
+ return $this->insertHyperlinks;
+ }
+
+ /**
+ * Set InsertHyperlinks.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setInsertHyperlinks($pValue)
+ {
+ $this->insertHyperlinks = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get DeleteColumns.
+ *
+ * @return bool
+ */
+ public function getDeleteColumns()
+ {
+ return $this->deleteColumns;
+ }
+
+ /**
+ * Set DeleteColumns.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setDeleteColumns($pValue)
+ {
+ $this->deleteColumns = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get DeleteRows.
+ *
+ * @return bool
+ */
+ public function getDeleteRows()
+ {
+ return $this->deleteRows;
+ }
+
+ /**
+ * Set DeleteRows.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setDeleteRows($pValue)
+ {
+ $this->deleteRows = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get SelectLockedCells.
+ *
+ * @return bool
+ */
+ public function getSelectLockedCells()
+ {
+ return $this->selectLockedCells;
+ }
+
+ /**
+ * Set SelectLockedCells.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setSelectLockedCells($pValue)
+ {
+ $this->selectLockedCells = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Sort.
+ *
+ * @return bool
+ */
+ public function getSort()
+ {
+ return $this->sort;
+ }
+
+ /**
+ * Set Sort.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setSort($pValue)
+ {
+ $this->sort = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter.
+ *
+ * @return bool
+ */
+ public function getAutoFilter()
+ {
+ return $this->autoFilter;
+ }
+
+ /**
+ * Set AutoFilter.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setAutoFilter($pValue)
+ {
+ $this->autoFilter = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get PivotTables.
+ *
+ * @return bool
+ */
+ public function getPivotTables()
+ {
+ return $this->pivotTables;
+ }
+
+ /**
+ * Set PivotTables.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setPivotTables($pValue)
+ {
+ $this->pivotTables = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get SelectUnlockedCells.
+ *
+ * @return bool
+ */
+ public function getSelectUnlockedCells()
+ {
+ return $this->selectUnlockedCells;
+ }
+
+ /**
+ * Set SelectUnlockedCells.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setSelectUnlockedCells($pValue)
+ {
+ $this->selectUnlockedCells = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get hashed password.
+ *
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * Set Password.
+ *
+ * @param string $pValue
+ * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
+ *
+ * @return $this
+ */
+ public function setPassword($pValue, $pAlreadyHashed = false)
+ {
+ if (!$pAlreadyHashed) {
+ $salt = $this->generateSalt();
+ $this->setSalt($salt);
+ $pValue = PasswordHasher::hashPassword($pValue, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount());
+ }
+
+ $this->password = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Create a pseudorandom string.
+ */
+ private function generateSalt(): string
+ {
+ return base64_encode(random_bytes(16));
+ }
+
+ /**
+ * Get algorithm name.
+ */
+ public function getAlgorithm(): string
+ {
+ return $this->algorithm;
+ }
+
+ /**
+ * Set algorithm name.
+ */
+ public function setAlgorithm(string $algorithm): void
+ {
+ $this->algorithm = $algorithm;
+ }
+
+ /**
+ * Get salt value.
+ */
+ public function getSalt(): string
+ {
+ return $this->salt;
+ }
+
+ /**
+ * Set salt value.
+ */
+ public function setSalt(string $salt): void
+ {
+ $this->salt = $salt;
+ }
+
+ /**
+ * Get spin count.
+ */
+ public function getSpinCount(): int
+ {
+ return $this->spinCount;
+ }
+
+ /**
+ * Set spin count.
+ */
+ public function setSpinCount(int $spinCount): void
+ {
+ $this->spinCount = $spinCount;
+ }
+
+ /**
+ * Verify that the given non-hashed password can "unlock" the protection.
+ */
+ public function verify(string $password): bool
+ {
+ if (!$this->isProtectionEnabled()) {
+ return true;
+ }
+
+ $hash = PasswordHasher::hashPassword($password, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount());
+
+ return $this->getPassword() === $hash;
+ }
+
+ /**
+ * 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php
new file mode 100755
index 0000000..4f48a34
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+class Row
+{
+ /**
+ * \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet.
+ *
+ * @var Worksheet
+ */
+ private $worksheet;
+
+ /**
+ * Row index.
+ *
+ * @var int
+ */
+ private $rowIndex = 0;
+
+ /**
+ * Create a new row.
+ *
+ * @param Worksheet $worksheet
+ * @param int $rowIndex
+ */
+ public function __construct(?Worksheet $worksheet = null, $rowIndex = 1)
+ {
+ // Set parent and row index
+ $this->worksheet = $worksheet;
+ $this->rowIndex = $rowIndex;
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->worksheet = null;
+ }
+
+ /**
+ * Get row index.
+ *
+ * @return int
+ */
+ public function getRowIndex()
+ {
+ return $this->rowIndex;
+ }
+
+ /**
+ * Get cell iterator.
+ *
+ * @param string $startColumn The column address at which to start iterating
+ * @param string $endColumn Optionally, the column address at which to stop iterating
+ *
+ * @return RowCellIterator
+ */
+ public function getCellIterator($startColumn = 'A', $endColumn = null)
+ {
+ return new RowCellIterator($this->worksheet, $this->rowIndex, $startColumn, $endColumn);
+ }
+
+ /**
+ * Returns bound worksheet.
+ *
+ * @return Worksheet
+ */
+ public function getWorksheet()
+ {
+ return $this->worksheet;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php
new file mode 100755
index 0000000..f5576dc
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php
@@ -0,0 +1,195 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class RowCellIterator extends CellIterator
+{
+ /**
+ * Current iterator position.
+ *
+ * @var int
+ */
+ private $currentColumnIndex;
+
+ /**
+ * Row index.
+ *
+ * @var int
+ */
+ private $rowIndex = 1;
+
+ /**
+ * Start position.
+ *
+ * @var int
+ */
+ private $startColumnIndex = 1;
+
+ /**
+ * End position.
+ *
+ * @var int
+ */
+ private $endColumnIndex = 1;
+
+ /**
+ * Create a new column iterator.
+ *
+ * @param Worksheet $worksheet The worksheet to iterate over
+ * @param int $rowIndex The row that we want to iterate
+ * @param string $startColumn The column address at which to start iterating
+ * @param string $endColumn Optionally, the column address at which to stop iterating
+ */
+ public function __construct(?Worksheet $worksheet = null, $rowIndex = 1, $startColumn = 'A', $endColumn = null)
+ {
+ // Set subject and row index
+ $this->worksheet = $worksheet;
+ $this->rowIndex = $rowIndex;
+ $this->resetEnd($endColumn);
+ $this->resetStart($startColumn);
+ }
+
+ /**
+ * (Re)Set the start column and the current column pointer.
+ *
+ * @param string $startColumn The column address at which to start iterating
+ *
+ * @return $this
+ */
+ public function resetStart($startColumn = 'A')
+ {
+ $this->startColumnIndex = Coordinate::columnIndexFromString($startColumn);
+ $this->adjustForExistingOnlyRange();
+ $this->seek(Coordinate::stringFromColumnIndex($this->startColumnIndex));
+
+ return $this;
+ }
+
+ /**
+ * (Re)Set the end column.
+ *
+ * @param string $endColumn The column address at which to stop iterating
+ *
+ * @return $this
+ */
+ public function resetEnd($endColumn = null)
+ {
+ $endColumn = $endColumn ? $endColumn : $this->worksheet->getHighestColumn();
+ $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn);
+ $this->adjustForExistingOnlyRange();
+
+ return $this;
+ }
+
+ /**
+ * Set the column pointer to the selected column.
+ *
+ * @param string $column The column address to set the current pointer at
+ *
+ * @return $this
+ */
+ public function seek($column = 'A')
+ {
+ $column = Coordinate::columnIndexFromString($column);
+ if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) {
+ throw new PhpSpreadsheetException("Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})");
+ } elseif ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($column, $this->rowIndex))) {
+ throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist');
+ }
+ $this->currentColumnIndex = $column;
+
+ return $this;
+ }
+
+ /**
+ * Rewind the iterator to the starting column.
+ */
+ public function rewind(): void
+ {
+ $this->currentColumnIndex = $this->startColumnIndex;
+ }
+
+ /**
+ * Return the current cell in this worksheet row.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Cell\Cell
+ */
+ public function current()
+ {
+ return $this->worksheet->getCellByColumnAndRow($this->currentColumnIndex, $this->rowIndex);
+ }
+
+ /**
+ * Return the current iterator key.
+ *
+ * @return string
+ */
+ public function key()
+ {
+ return Coordinate::stringFromColumnIndex($this->currentColumnIndex);
+ }
+
+ /**
+ * Set the iterator to its next value.
+ */
+ public function next(): void
+ {
+ do {
+ ++$this->currentColumnIndex;
+ } while (($this->onlyExistingCells) && (!$this->worksheet->cellExistsByColumnAndRow($this->currentColumnIndex, $this->rowIndex)) && ($this->currentColumnIndex <= $this->endColumnIndex));
+ }
+
+ /**
+ * Set the iterator to its previous value.
+ */
+ public function prev(): void
+ {
+ do {
+ --$this->currentColumnIndex;
+ } while (($this->onlyExistingCells) && (!$this->worksheet->cellExistsByColumnAndRow($this->currentColumnIndex, $this->rowIndex)) && ($this->currentColumnIndex >= $this->startColumnIndex));
+ }
+
+ /**
+ * Indicate if more columns exist in the worksheet range of columns that we're iterating.
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex;
+ }
+
+ /**
+ * Return the current iterator position.
+ *
+ * @return int
+ */
+ public function getCurrentColumnIndex()
+ {
+ return $this->currentColumnIndex;
+ }
+
+ /**
+ * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary.
+ */
+ protected function adjustForExistingOnlyRange(): void
+ {
+ if ($this->onlyExistingCells) {
+ while ((!$this->worksheet->cellExistsByColumnAndRow($this->startColumnIndex, $this->rowIndex)) && ($this->startColumnIndex <= $this->endColumnIndex)) {
+ ++$this->startColumnIndex;
+ }
+ if ($this->startColumnIndex > $this->endColumnIndex) {
+ throw new PhpSpreadsheetException('No cells exist within the specified range');
+ }
+ while ((!$this->worksheet->cellExistsByColumnAndRow($this->endColumnIndex, $this->rowIndex)) && ($this->endColumnIndex >= $this->startColumnIndex)) {
+ --$this->endColumnIndex;
+ }
+ if ($this->endColumnIndex < $this->startColumnIndex) {
+ throw new PhpSpreadsheetException('No cells exist within the specified range');
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php
new file mode 100755
index 0000000..c4a87bd
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+class RowDimension extends Dimension
+{
+ /**
+ * Row index.
+ *
+ * @var int
+ */
+ private $rowIndex;
+
+ /**
+ * Row height (in pt).
+ *
+ * When this is set to a negative value, the row height should be ignored by IWriter
+ *
+ * @var float
+ */
+ private $height = -1;
+
+ /**
+ * ZeroHeight for Row?
+ *
+ * @var bool
+ */
+ private $zeroHeight = false;
+
+ /**
+ * Create a new RowDimension.
+ *
+ * @param int $pIndex Numeric row index
+ */
+ public function __construct($pIndex = 0)
+ {
+ // Initialise values
+ $this->rowIndex = $pIndex;
+
+ // set dimension as unformatted by default
+ parent::__construct(null);
+ }
+
+ /**
+ * Get Row Index.
+ *
+ * @return int
+ */
+ public function getRowIndex()
+ {
+ return $this->rowIndex;
+ }
+
+ /**
+ * Set Row Index.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setRowIndex($pValue)
+ {
+ $this->rowIndex = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Row Height.
+ *
+ * @return float
+ */
+ public function getRowHeight()
+ {
+ return $this->height;
+ }
+
+ /**
+ * Set Row Height.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setRowHeight($pValue)
+ {
+ $this->height = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get ZeroHeight.
+ *
+ * @return bool
+ */
+ public function getZeroHeight()
+ {
+ return $this->zeroHeight;
+ }
+
+ /**
+ * Set ZeroHeight.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setZeroHeight($pValue)
+ {
+ $this->zeroHeight = $pValue;
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php
new file mode 100755
index 0000000..4254253
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php
@@ -0,0 +1,167 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use Iterator;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class RowIterator implements Iterator
+{
+ /**
+ * Worksheet to iterate.
+ *
+ * @var Worksheet
+ */
+ private $subject;
+
+ /**
+ * Current iterator position.
+ *
+ * @var int
+ */
+ private $position = 1;
+
+ /**
+ * Start position.
+ *
+ * @var int
+ */
+ private $startRow = 1;
+
+ /**
+ * End position.
+ *
+ * @var int
+ */
+ private $endRow = 1;
+
+ /**
+ * Create a new row iterator.
+ *
+ * @param Worksheet $subject The worksheet to iterate over
+ * @param int $startRow The row number at which to start iterating
+ * @param int $endRow Optionally, the row number at which to stop iterating
+ */
+ public function __construct(Worksheet $subject, $startRow = 1, $endRow = null)
+ {
+ // Set subject
+ $this->subject = $subject;
+ $this->resetEnd($endRow);
+ $this->resetStart($startRow);
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->subject = null;
+ }
+
+ /**
+ * (Re)Set the start row and the current row pointer.
+ *
+ * @param int $startRow The row number at which to start iterating
+ *
+ * @return $this
+ */
+ public function resetStart($startRow = 1)
+ {
+ if ($startRow > $this->subject->getHighestRow()) {
+ throw new PhpSpreadsheetException("Start row ({$startRow}) is beyond highest row ({$this->subject->getHighestRow()})");
+ }
+
+ $this->startRow = $startRow;
+ if ($this->endRow < $this->startRow) {
+ $this->endRow = $this->startRow;
+ }
+ $this->seek($startRow);
+
+ return $this;
+ }
+
+ /**
+ * (Re)Set the end row.
+ *
+ * @param int $endRow The row number at which to stop iterating
+ *
+ * @return $this
+ */
+ public function resetEnd($endRow = null)
+ {
+ $this->endRow = ($endRow) ? $endRow : $this->subject->getHighestRow();
+
+ return $this;
+ }
+
+ /**
+ * Set the row pointer to the selected row.
+ *
+ * @param int $row The row number to set the current pointer at
+ *
+ * @return $this
+ */
+ public function seek($row = 1)
+ {
+ if (($row < $this->startRow) || ($row > $this->endRow)) {
+ throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})");
+ }
+ $this->position = $row;
+
+ return $this;
+ }
+
+ /**
+ * Rewind the iterator to the starting row.
+ */
+ public function rewind(): void
+ {
+ $this->position = $this->startRow;
+ }
+
+ /**
+ * Return the current row in this worksheet.
+ *
+ * @return Row
+ */
+ public function current()
+ {
+ return new Row($this->subject, $this->position);
+ }
+
+ /**
+ * Return the current iterator key.
+ *
+ * @return int
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Set the iterator to its next value.
+ */
+ public function next(): void
+ {
+ ++$this->position;
+ }
+
+ /**
+ * Set the iterator to its previous value.
+ */
+ public function prev(): void
+ {
+ --$this->position;
+ }
+
+ /**
+ * Indicate if more rows exist in the worksheet range of rows that we're iterating.
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return $this->position <= $this->endRow && $this->position >= $this->startRow;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php
new file mode 100755
index 0000000..2f7d381
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php
@@ -0,0 +1,193 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class SheetView
+{
+ // Sheet View types
+ const SHEETVIEW_NORMAL = 'normal';
+ const SHEETVIEW_PAGE_LAYOUT = 'pageLayout';
+ const SHEETVIEW_PAGE_BREAK_PREVIEW = 'pageBreakPreview';
+
+ private static $sheetViewTypes = [
+ self::SHEETVIEW_NORMAL,
+ self::SHEETVIEW_PAGE_LAYOUT,
+ self::SHEETVIEW_PAGE_BREAK_PREVIEW,
+ ];
+
+ /**
+ * ZoomScale.
+ *
+ * Valid values range from 10 to 400.
+ *
+ * @var int
+ */
+ private $zoomScale = 100;
+
+ /**
+ * ZoomScaleNormal.
+ *
+ * Valid values range from 10 to 400.
+ *
+ * @var int
+ */
+ private $zoomScaleNormal = 100;
+
+ /**
+ * ShowZeros.
+ *
+ * If true, "null" values from a calculation will be shown as "0". This is the default Excel behaviour and can be changed
+ * with the advanced worksheet option "Show a zero in cells that have zero value"
+ *
+ * @var bool
+ */
+ private $showZeros = true;
+
+ /**
+ * View.
+ *
+ * Valid values range from 10 to 400.
+ *
+ * @var string
+ */
+ private $sheetviewType = self::SHEETVIEW_NORMAL;
+
+ /**
+ * Create a new SheetView.
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * Get ZoomScale.
+ *
+ * @return int
+ */
+ public function getZoomScale()
+ {
+ return $this->zoomScale;
+ }
+
+ /**
+ * Set ZoomScale.
+ * Valid values range from 10 to 400.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setZoomScale($pValue)
+ {
+ // Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface,
+ // but it is apparently still able to handle any scale >= 1
+ if (($pValue >= 1) || $pValue === null) {
+ $this->zoomScale = $pValue;
+ } else {
+ throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get ZoomScaleNormal.
+ *
+ * @return int
+ */
+ public function getZoomScaleNormal()
+ {
+ return $this->zoomScaleNormal;
+ }
+
+ /**
+ * Set ZoomScale.
+ * Valid values range from 10 to 400.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setZoomScaleNormal($pValue)
+ {
+ if (($pValue >= 1) || $pValue === null) {
+ $this->zoomScaleNormal = $pValue;
+ } else {
+ throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set ShowZeroes setting.
+ *
+ * @param bool $pValue
+ */
+ public function setShowZeros($pValue): void
+ {
+ $this->showZeros = $pValue;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getShowZeros()
+ {
+ return $this->showZeros;
+ }
+
+ /**
+ * Get View.
+ *
+ * @return string
+ */
+ public function getView()
+ {
+ return $this->sheetviewType;
+ }
+
+ /**
+ * Set View.
+ *
+ * Valid values are
+ * 'normal' self::SHEETVIEW_NORMAL
+ * 'pageLayout' self::SHEETVIEW_PAGE_LAYOUT
+ * 'pageBreakPreview' self::SHEETVIEW_PAGE_BREAK_PREVIEW
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setView($pValue)
+ {
+ // MS Excel 2007 allows setting the view to 'normal', 'pageLayout' or 'pageBreakPreview' via the user interface
+ if ($pValue === null) {
+ $pValue = self::SHEETVIEW_NORMAL;
+ }
+ if (in_array($pValue, self::$sheetViewTypes)) {
+ $this->sheetviewType = $pValue;
+ } else {
+ throw new PhpSpreadsheetException('Invalid sheetview layout type.');
+ }
+
+ 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)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php
new file mode 100755
index 0000000..e2b0dd8
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php
@@ -0,0 +1,3013 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Worksheet;
+
+use ArrayObject;
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
+use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
+use PhpOffice\PhpSpreadsheet\Chart\Chart;
+use PhpOffice\PhpSpreadsheet\Collection\Cells;
+use PhpOffice\PhpSpreadsheet\Collection\CellsFactory;
+use PhpOffice\PhpSpreadsheet\Comment;
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\IComparable;
+use PhpOffice\PhpSpreadsheet\NamedRange;
+use PhpOffice\PhpSpreadsheet\ReferenceHelper;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Shared;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Color;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+
+class Worksheet implements IComparable
+{
+ // Break types
+ const BREAK_NONE = 0;
+ const BREAK_ROW = 1;
+ const BREAK_COLUMN = 2;
+
+ // Sheet state
+ const SHEETSTATE_VISIBLE = 'visible';
+ const SHEETSTATE_HIDDEN = 'hidden';
+ const SHEETSTATE_VERYHIDDEN = 'veryHidden';
+
+ /**
+ * Maximum 31 characters allowed for sheet title.
+ *
+ * @var int
+ */
+ const SHEET_TITLE_MAXIMUM_LENGTH = 31;
+
+ /**
+ * Invalid characters in sheet title.
+ *
+ * @var array
+ */
+ private static $invalidCharacters = ['*', ':', '/', '\\', '?', '[', ']'];
+
+ /**
+ * Parent spreadsheet.
+ *
+ * @var Spreadsheet
+ */
+ private $parent;
+
+ /**
+ * Collection of cells.
+ *
+ * @var Cells
+ */
+ private $cellCollection;
+
+ /**
+ * Collection of row dimensions.
+ *
+ * @var RowDimension[]
+ */
+ private $rowDimensions = [];
+
+ /**
+ * Default row dimension.
+ *
+ * @var RowDimension
+ */
+ private $defaultRowDimension;
+
+ /**
+ * Collection of column dimensions.
+ *
+ * @var ColumnDimension[]
+ */
+ private $columnDimensions = [];
+
+ /**
+ * Default column dimension.
+ *
+ * @var ColumnDimension
+ */
+ private $defaultColumnDimension;
+
+ /**
+ * Collection of drawings.
+ *
+ * @var BaseDrawing[]
+ */
+ private $drawingCollection;
+
+ /**
+ * Collection of Chart objects.
+ *
+ * @var Chart[]
+ */
+ private $chartCollection = [];
+
+ /**
+ * Worksheet title.
+ *
+ * @var string
+ */
+ private $title;
+
+ /**
+ * Sheet state.
+ *
+ * @var string
+ */
+ private $sheetState;
+
+ /**
+ * Page setup.
+ *
+ * @var PageSetup
+ */
+ private $pageSetup;
+
+ /**
+ * Page margins.
+ *
+ * @var PageMargins
+ */
+ private $pageMargins;
+
+ /**
+ * Page header/footer.
+ *
+ * @var HeaderFooter
+ */
+ private $headerFooter;
+
+ /**
+ * Sheet view.
+ *
+ * @var SheetView
+ */
+ private $sheetView;
+
+ /**
+ * Protection.
+ *
+ * @var Protection
+ */
+ private $protection;
+
+ /**
+ * Collection of styles.
+ *
+ * @var Style[]
+ */
+ private $styles = [];
+
+ /**
+ * Conditional styles. Indexed by cell coordinate, e.g. 'A1'.
+ *
+ * @var array
+ */
+ private $conditionalStylesCollection = [];
+
+ /**
+ * Is the current cell collection sorted already?
+ *
+ * @var bool
+ */
+ private $cellCollectionIsSorted = false;
+
+ /**
+ * Collection of breaks.
+ *
+ * @var array
+ */
+ private $breaks = [];
+
+ /**
+ * Collection of merged cell ranges.
+ *
+ * @var string[]
+ */
+ private $mergeCells = [];
+
+ /**
+ * Collection of protected cell ranges.
+ *
+ * @var array
+ */
+ private $protectedCells = [];
+
+ /**
+ * Autofilter Range and selection.
+ *
+ * @var AutoFilter
+ */
+ private $autoFilter;
+
+ /**
+ * Freeze pane.
+ *
+ * @var null|string
+ */
+ private $freezePane;
+
+ /**
+ * Default position of the right bottom pane.
+ *
+ * @var null|string
+ */
+ private $topLeftCell;
+
+ /**
+ * Show gridlines?
+ *
+ * @var bool
+ */
+ private $showGridlines = true;
+
+ /**
+ * Print gridlines?
+ *
+ * @var bool
+ */
+ private $printGridlines = false;
+
+ /**
+ * Show row and column headers?
+ *
+ * @var bool
+ */
+ private $showRowColHeaders = true;
+
+ /**
+ * Show summary below? (Row/Column outline).
+ *
+ * @var bool
+ */
+ private $showSummaryBelow = true;
+
+ /**
+ * Show summary right? (Row/Column outline).
+ *
+ * @var bool
+ */
+ private $showSummaryRight = true;
+
+ /**
+ * Collection of comments.
+ *
+ * @var Comment[]
+ */
+ private $comments = [];
+
+ /**
+ * Active cell. (Only one!).
+ *
+ * @var string
+ */
+ private $activeCell = 'A1';
+
+ /**
+ * Selected cells.
+ *
+ * @var string
+ */
+ private $selectedCells = 'A1';
+
+ /**
+ * Cached highest column.
+ *
+ * @var string
+ */
+ private $cachedHighestColumn = 'A';
+
+ /**
+ * Cached highest row.
+ *
+ * @var int
+ */
+ private $cachedHighestRow = 1;
+
+ /**
+ * Right-to-left?
+ *
+ * @var bool
+ */
+ private $rightToLeft = false;
+
+ /**
+ * Hyperlinks. Indexed by cell coordinate, e.g. 'A1'.
+ *
+ * @var array
+ */
+ private $hyperlinkCollection = [];
+
+ /**
+ * Data validation objects. Indexed by cell coordinate, e.g. 'A1'.
+ *
+ * @var array
+ */
+ private $dataValidationCollection = [];
+
+ /**
+ * Tab color.
+ *
+ * @var Color
+ */
+ private $tabColor;
+
+ /**
+ * Dirty flag.
+ *
+ * @var bool
+ */
+ private $dirty = true;
+
+ /**
+ * Hash.
+ *
+ * @var string
+ */
+ private $hash;
+
+ /**
+ * CodeName.
+ *
+ * @var string
+ */
+ private $codeName;
+
+ /**
+ * Create a new worksheet.
+ *
+ * @param Spreadsheet $parent
+ * @param string $pTitle
+ */
+ public function __construct(?Spreadsheet $parent = null, $pTitle = 'Worksheet')
+ {
+ // Set parent and title
+ $this->parent = $parent;
+ $this->setTitle($pTitle, false);
+ // setTitle can change $pTitle
+ $this->setCodeName($this->getTitle());
+ $this->setSheetState(self::SHEETSTATE_VISIBLE);
+
+ $this->cellCollection = CellsFactory::getInstance($this);
+ // Set page setup
+ $this->pageSetup = new PageSetup();
+ // Set page margins
+ $this->pageMargins = new PageMargins();
+ // Set page header/footer
+ $this->headerFooter = new HeaderFooter();
+ // Set sheet view
+ $this->sheetView = new SheetView();
+ // Drawing collection
+ $this->drawingCollection = new ArrayObject();
+ // Chart collection
+ $this->chartCollection = new ArrayObject();
+ // Protection
+ $this->protection = new Protection();
+ // Default row dimension
+ $this->defaultRowDimension = new RowDimension(null);
+ // Default column dimension
+ $this->defaultColumnDimension = new ColumnDimension(null);
+ $this->autoFilter = new AutoFilter(null, $this);
+ }
+
+ /**
+ * Disconnect all cells from this Worksheet object,
+ * typically so that the worksheet object can be unset.
+ */
+ public function disconnectCells(): void
+ {
+ if ($this->cellCollection !== null) {
+ $this->cellCollection->unsetWorksheetCells();
+ $this->cellCollection = null;
+ }
+ // detach ourself from the workbook, so that it can then delete this worksheet successfully
+ $this->parent = null;
+ }
+
+ /**
+ * Code to execute when this worksheet is unset().
+ */
+ public function __destruct()
+ {
+ Calculation::getInstance($this->parent)->clearCalculationCacheForWorksheet($this->title);
+
+ $this->disconnectCells();
+ }
+
+ /**
+ * Return the cell collection.
+ *
+ * @return Cells
+ */
+ public function getCellCollection()
+ {
+ return $this->cellCollection;
+ }
+
+ /**
+ * Get array of invalid characters for sheet title.
+ *
+ * @return array
+ */
+ public static function getInvalidCharacters()
+ {
+ return self::$invalidCharacters;
+ }
+
+ /**
+ * Check sheet code name for valid Excel syntax.
+ *
+ * @param string $pValue The string to check
+ *
+ * @return string The valid string
+ */
+ private static function checkSheetCodeName($pValue)
+ {
+ $CharCount = Shared\StringHelper::countCharacters($pValue);
+ if ($CharCount == 0) {
+ throw new Exception('Sheet code name cannot be empty.');
+ }
+ // Some of the printable ASCII characters are invalid: * : / \ ? [ ] and first and last characters cannot be a "'"
+ if ((str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) ||
+ (Shared\StringHelper::substring($pValue, -1, 1) == '\'') ||
+ (Shared\StringHelper::substring($pValue, 0, 1) == '\'')) {
+ throw new Exception('Invalid character found in sheet code name');
+ }
+
+ // Enforce maximum characters allowed for sheet title
+ if ($CharCount > self::SHEET_TITLE_MAXIMUM_LENGTH) {
+ throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet code name.');
+ }
+
+ return $pValue;
+ }
+
+ /**
+ * Check sheet title for valid Excel syntax.
+ *
+ * @param string $pValue The string to check
+ *
+ * @return string The valid string
+ */
+ private static function checkSheetTitle($pValue)
+ {
+ // Some of the printable ASCII characters are invalid: * : / \ ? [ ]
+ if (str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) {
+ throw new Exception('Invalid character found in sheet title');
+ }
+
+ // Enforce maximum characters allowed for sheet title
+ if (Shared\StringHelper::countCharacters($pValue) > self::SHEET_TITLE_MAXIMUM_LENGTH) {
+ throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet title.');
+ }
+
+ return $pValue;
+ }
+
+ /**
+ * Get a sorted list of all cell coordinates currently held in the collection by row and column.
+ *
+ * @param bool $sorted Also sort the cell collection?
+ *
+ * @return string[]
+ */
+ public function getCoordinates($sorted = true)
+ {
+ if ($this->cellCollection == null) {
+ return [];
+ }
+
+ if ($sorted) {
+ return $this->cellCollection->getSortedCoordinates();
+ }
+
+ return $this->cellCollection->getCoordinates();
+ }
+
+ /**
+ * Get collection of row dimensions.
+ *
+ * @return RowDimension[]
+ */
+ public function getRowDimensions()
+ {
+ return $this->rowDimensions;
+ }
+
+ /**
+ * Get default row dimension.
+ *
+ * @return RowDimension
+ */
+ public function getDefaultRowDimension()
+ {
+ return $this->defaultRowDimension;
+ }
+
+ /**
+ * Get collection of column dimensions.
+ *
+ * @return ColumnDimension[]
+ */
+ public function getColumnDimensions()
+ {
+ return $this->columnDimensions;
+ }
+
+ /**
+ * Get default column dimension.
+ *
+ * @return ColumnDimension
+ */
+ public function getDefaultColumnDimension()
+ {
+ return $this->defaultColumnDimension;
+ }
+
+ /**
+ * Get collection of drawings.
+ *
+ * @return BaseDrawing[]
+ */
+ public function getDrawingCollection()
+ {
+ return $this->drawingCollection;
+ }
+
+ /**
+ * Get collection of charts.
+ *
+ * @return Chart[]
+ */
+ public function getChartCollection()
+ {
+ return $this->chartCollection;
+ }
+
+ /**
+ * Add chart.
+ *
+ * @param null|int $iChartIndex Index where chart should go (0,1,..., or null for last)
+ *
+ * @return Chart
+ */
+ public function addChart(Chart $pChart, $iChartIndex = null)
+ {
+ $pChart->setWorksheet($this);
+ if ($iChartIndex === null) {
+ $this->chartCollection[] = $pChart;
+ } else {
+ // Insert the chart at the requested index
+ array_splice($this->chartCollection, $iChartIndex, 0, [$pChart]);
+ }
+
+ return $pChart;
+ }
+
+ /**
+ * Return the count of charts on this worksheet.
+ *
+ * @return int The number of charts
+ */
+ public function getChartCount()
+ {
+ return count($this->chartCollection);
+ }
+
+ /**
+ * Get a chart by its index position.
+ *
+ * @param string $index Chart index position
+ *
+ * @return Chart|false
+ */
+ public function getChartByIndex($index)
+ {
+ $chartCount = count($this->chartCollection);
+ if ($chartCount == 0) {
+ return false;
+ }
+ if ($index === null) {
+ $index = --$chartCount;
+ }
+ if (!isset($this->chartCollection[$index])) {
+ return false;
+ }
+
+ return $this->chartCollection[$index];
+ }
+
+ /**
+ * Return an array of the names of charts on this worksheet.
+ *
+ * @return string[] The names of charts
+ */
+ public function getChartNames()
+ {
+ $chartNames = [];
+ foreach ($this->chartCollection as $chart) {
+ $chartNames[] = $chart->getName();
+ }
+
+ return $chartNames;
+ }
+
+ /**
+ * Get a chart by name.
+ *
+ * @param string $chartName Chart name
+ *
+ * @return Chart|false
+ */
+ public function getChartByName($chartName)
+ {
+ $chartCount = count($this->chartCollection);
+ if ($chartCount == 0) {
+ return false;
+ }
+ foreach ($this->chartCollection as $index => $chart) {
+ if ($chart->getName() == $chartName) {
+ return $this->chartCollection[$index];
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Refresh column dimensions.
+ *
+ * @return $this
+ */
+ public function refreshColumnDimensions()
+ {
+ $currentColumnDimensions = $this->getColumnDimensions();
+ $newColumnDimensions = [];
+
+ foreach ($currentColumnDimensions as $objColumnDimension) {
+ $newColumnDimensions[$objColumnDimension->getColumnIndex()] = $objColumnDimension;
+ }
+
+ $this->columnDimensions = $newColumnDimensions;
+
+ return $this;
+ }
+
+ /**
+ * Refresh row dimensions.
+ *
+ * @return $this
+ */
+ public function refreshRowDimensions()
+ {
+ $currentRowDimensions = $this->getRowDimensions();
+ $newRowDimensions = [];
+
+ foreach ($currentRowDimensions as $objRowDimension) {
+ $newRowDimensions[$objRowDimension->getRowIndex()] = $objRowDimension;
+ }
+
+ $this->rowDimensions = $newRowDimensions;
+
+ return $this;
+ }
+
+ /**
+ * Calculate worksheet dimension.
+ *
+ * @return string String containing the dimension of this worksheet
+ */
+ public function calculateWorksheetDimension()
+ {
+ // Return
+ return 'A1:' . $this->getHighestColumn() . $this->getHighestRow();
+ }
+
+ /**
+ * Calculate worksheet data dimension.
+ *
+ * @return string String containing the dimension of this worksheet that actually contain data
+ */
+ public function calculateWorksheetDataDimension()
+ {
+ // Return
+ return 'A1:' . $this->getHighestDataColumn() . $this->getHighestDataRow();
+ }
+
+ /**
+ * Calculate widths for auto-size columns.
+ *
+ * @return $this
+ */
+ public function calculateColumnWidths()
+ {
+ // initialize $autoSizes array
+ $autoSizes = [];
+ foreach ($this->getColumnDimensions() as $colDimension) {
+ if ($colDimension->getAutoSize()) {
+ $autoSizes[$colDimension->getColumnIndex()] = -1;
+ }
+ }
+
+ // There is only something to do if there are some auto-size columns
+ if (!empty($autoSizes)) {
+ // build list of cells references that participate in a merge
+ $isMergeCell = [];
+ foreach ($this->getMergeCells() as $cells) {
+ foreach (Coordinate::extractAllCellReferencesInRange($cells) as $cellReference) {
+ $isMergeCell[$cellReference] = true;
+ }
+ }
+
+ // loop through all cells in the worksheet
+ foreach ($this->getCoordinates(false) as $coordinate) {
+ $cell = $this->getCell($coordinate, false);
+ if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) {
+ //Determine if cell is in merge range
+ $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]);
+
+ //By default merged cells should be ignored
+ $isMergedButProceed = false;
+
+ //The only exception is if it's a merge range value cell of a 'vertical' randge (1 column wide)
+ if ($isMerged && $cell->isMergeRangeValueCell()) {
+ $range = $cell->getMergeRange();
+ $rangeBoundaries = Coordinate::rangeDimension($range);
+ if ($rangeBoundaries[0] == 1) {
+ $isMergedButProceed = true;
+ }
+ }
+
+ // Determine width if cell does not participate in a merge or does and is a value cell of 1-column wide range
+ if (!$isMerged || $isMergedButProceed) {
+ // Calculated value
+ // To formatted string
+ $cellValue = NumberFormat::toFormattedString(
+ $cell->getCalculatedValue(),
+ $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode()
+ );
+
+ $autoSizes[$this->cellCollection->getCurrentColumn()] = max(
+ (float) $autoSizes[$this->cellCollection->getCurrentColumn()],
+ (float) Shared\Font::calculateColumnWidth(
+ $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(),
+ $cellValue,
+ $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getAlignment()->getTextRotation(),
+ $this->getParent()->getDefaultStyle()->getFont()
+ )
+ );
+ }
+ }
+ }
+
+ // adjust column widths
+ foreach ($autoSizes as $columnIndex => $width) {
+ if ($width == -1) {
+ $width = $this->getDefaultColumnDimension()->getWidth();
+ }
+ $this->getColumnDimension($columnIndex)->setWidth($width);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get parent.
+ *
+ * @return Spreadsheet
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Re-bind parent.
+ *
+ * @return $this
+ */
+ public function rebindParent(Spreadsheet $parent)
+ {
+ if ($this->parent !== null) {
+ $namedRanges = $this->parent->getNamedRanges();
+ foreach ($namedRanges as $namedRange) {
+ $parent->addNamedRange($namedRange);
+ }
+
+ $this->parent->removeSheetByIndex(
+ $this->parent->getIndex($this)
+ );
+ }
+ $this->parent = $parent;
+
+ return $this;
+ }
+
+ /**
+ * Get title.
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * Set title.
+ *
+ * @param string $pValue String containing the dimension of this worksheet
+ * @param bool $updateFormulaCellReferences Flag indicating whether cell references in formulae should
+ * be updated to reflect the new sheet name.
+ * This should be left as the default true, unless you are
+ * certain that no formula cells on any worksheet contain
+ * references to this worksheet
+ * @param bool $validate False to skip validation of new title. WARNING: This should only be set
+ * at parse time (by Readers), where titles can be assumed to be valid.
+ *
+ * @return $this
+ */
+ public function setTitle($pValue, $updateFormulaCellReferences = true, $validate = true)
+ {
+ // Is this a 'rename' or not?
+ if ($this->getTitle() == $pValue) {
+ return $this;
+ }
+
+ // Old title
+ $oldTitle = $this->getTitle();
+
+ if ($validate) {
+ // Syntax check
+ self::checkSheetTitle($pValue);
+
+ if ($this->parent) {
+ // Is there already such sheet name?
+ if ($this->parent->sheetNameExists($pValue)) {
+ // Use name, but append with lowest possible integer
+
+ if (Shared\StringHelper::countCharacters($pValue) > 29) {
+ $pValue = Shared\StringHelper::substring($pValue, 0, 29);
+ }
+ $i = 1;
+ while ($this->parent->sheetNameExists($pValue . ' ' . $i)) {
+ ++$i;
+ if ($i == 10) {
+ if (Shared\StringHelper::countCharacters($pValue) > 28) {
+ $pValue = Shared\StringHelper::substring($pValue, 0, 28);
+ }
+ } elseif ($i == 100) {
+ if (Shared\StringHelper::countCharacters($pValue) > 27) {
+ $pValue = Shared\StringHelper::substring($pValue, 0, 27);
+ }
+ }
+ }
+
+ $pValue .= " $i";
+ }
+ }
+ }
+
+ // Set title
+ $this->title = $pValue;
+ $this->dirty = true;
+
+ if ($this->parent && $this->parent->getCalculationEngine()) {
+ // New title
+ $newTitle = $this->getTitle();
+ $this->parent->getCalculationEngine()
+ ->renameCalculationCacheForWorksheet($oldTitle, $newTitle);
+ if ($updateFormulaCellReferences) {
+ ReferenceHelper::getInstance()->updateNamedFormulas($this->parent, $oldTitle, $newTitle);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get sheet state.
+ *
+ * @return string Sheet state (visible, hidden, veryHidden)
+ */
+ public function getSheetState()
+ {
+ return $this->sheetState;
+ }
+
+ /**
+ * Set sheet state.
+ *
+ * @param string $value Sheet state (visible, hidden, veryHidden)
+ *
+ * @return $this
+ */
+ public function setSheetState($value)
+ {
+ $this->sheetState = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get page setup.
+ *
+ * @return PageSetup
+ */
+ public function getPageSetup()
+ {
+ return $this->pageSetup;
+ }
+
+ /**
+ * Set page setup.
+ *
+ * @return $this
+ */
+ public function setPageSetup(PageSetup $pValue)
+ {
+ $this->pageSetup = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get page margins.
+ *
+ * @return PageMargins
+ */
+ public function getPageMargins()
+ {
+ return $this->pageMargins;
+ }
+
+ /**
+ * Set page margins.
+ *
+ * @return $this
+ */
+ public function setPageMargins(PageMargins $pValue)
+ {
+ $this->pageMargins = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get page header/footer.
+ *
+ * @return HeaderFooter
+ */
+ public function getHeaderFooter()
+ {
+ return $this->headerFooter;
+ }
+
+ /**
+ * Set page header/footer.
+ *
+ * @return $this
+ */
+ public function setHeaderFooter(HeaderFooter $pValue)
+ {
+ $this->headerFooter = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get sheet view.
+ *
+ * @return SheetView
+ */
+ public function getSheetView()
+ {
+ return $this->sheetView;
+ }
+
+ /**
+ * Set sheet view.
+ *
+ * @return $this
+ */
+ public function setSheetView(SheetView $pValue)
+ {
+ $this->sheetView = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Protection.
+ *
+ * @return Protection
+ */
+ public function getProtection()
+ {
+ return $this->protection;
+ }
+
+ /**
+ * Set Protection.
+ *
+ * @return $this
+ */
+ public function setProtection(Protection $pValue)
+ {
+ $this->protection = $pValue;
+ $this->dirty = true;
+
+ return $this;
+ }
+
+ /**
+ * Get highest worksheet column.
+ *
+ * @param string $row Return the data highest column for the specified row,
+ * or the highest column of any row if no row number is passed
+ *
+ * @return string Highest column name
+ */
+ public function getHighestColumn($row = null)
+ {
+ if ($row == null) {
+ return $this->cachedHighestColumn;
+ }
+
+ return $this->getHighestDataColumn($row);
+ }
+
+ /**
+ * Get highest worksheet column that contains data.
+ *
+ * @param string $row Return the highest data column for the specified row,
+ * or the highest data column of any row if no row number is passed
+ *
+ * @return string Highest column name that contains data
+ */
+ public function getHighestDataColumn($row = null)
+ {
+ return $this->cellCollection->getHighestColumn($row);
+ }
+
+ /**
+ * Get highest worksheet row.
+ *
+ * @param string $column Return the highest data row for the specified column,
+ * or the highest row of any column if no column letter is passed
+ *
+ * @return int Highest row number
+ */
+ public function getHighestRow($column = null)
+ {
+ if ($column == null) {
+ return $this->cachedHighestRow;
+ }
+
+ return $this->getHighestDataRow($column);
+ }
+
+ /**
+ * Get highest worksheet row that contains data.
+ *
+ * @param string $column Return the highest data row for the specified column,
+ * or the highest data row of any column if no column letter is passed
+ *
+ * @return int Highest row number that contains data
+ */
+ public function getHighestDataRow($column = null)
+ {
+ return $this->cellCollection->getHighestRow($column);
+ }
+
+ /**
+ * Get highest worksheet column and highest row that have cell records.
+ *
+ * @return array Highest column name and highest row number
+ */
+ public function getHighestRowAndColumn()
+ {
+ return $this->cellCollection->getHighestRowAndColumn();
+ }
+
+ /**
+ * Set a cell value.
+ *
+ * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
+ * @param mixed $pValue Value of the cell
+ *
+ * @return $this
+ */
+ public function setCellValue($pCoordinate, $pValue)
+ {
+ $this->getCell($pCoordinate)->setValue($pValue);
+
+ return $this;
+ }
+
+ /**
+ * Set a cell value by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ * @param mixed $value Value of the cell
+ *
+ * @return $this
+ */
+ public function setCellValueByColumnAndRow($columnIndex, $row, $value)
+ {
+ $this->getCellByColumnAndRow($columnIndex, $row)->setValue($value);
+
+ return $this;
+ }
+
+ /**
+ * Set a cell value.
+ *
+ * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
+ * @param mixed $pValue Value of the cell
+ * @param string $pDataType Explicit data type, see DataType::TYPE_*
+ *
+ * @return $this
+ */
+ public function setCellValueExplicit($pCoordinate, $pValue, $pDataType)
+ {
+ // Set value
+ $this->getCell($pCoordinate)->setValueExplicit($pValue, $pDataType);
+
+ return $this;
+ }
+
+ /**
+ * Set a cell value by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ * @param mixed $value Value of the cell
+ * @param string $dataType Explicit data type, see DataType::TYPE_*
+ *
+ * @return $this
+ */
+ public function setCellValueExplicitByColumnAndRow($columnIndex, $row, $value, $dataType)
+ {
+ $this->getCellByColumnAndRow($columnIndex, $row)->setValueExplicit($value, $dataType);
+
+ return $this;
+ }
+
+ /**
+ * Get cell at a specific coordinate.
+ *
+ * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
+ * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't
+ * already exist, or a null should be returned instead
+ *
+ * @return null|Cell Cell that was found/created or null
+ */
+ public function getCell($pCoordinate, $createIfNotExists = true)
+ {
+ // Uppercase coordinate
+ $pCoordinateUpper = strtoupper($pCoordinate);
+
+ // Check cell collection
+ if ($this->cellCollection->has($pCoordinateUpper)) {
+ return $this->cellCollection->get($pCoordinateUpper);
+ }
+
+ // Worksheet reference?
+ if (strpos($pCoordinate, '!') !== false) {
+ $worksheetReference = self::extractSheetTitle($pCoordinate, true);
+
+ return $this->parent->getSheetByName($worksheetReference[0])->getCell(strtoupper($worksheetReference[1]), $createIfNotExists);
+ }
+
+ // Named range?
+ if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
+ (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) {
+ $namedRange = NamedRange::resolveRange($pCoordinate, $this);
+ if ($namedRange !== null) {
+ $pCoordinate = $namedRange->getRange();
+
+ return $namedRange->getWorksheet()->getCell($pCoordinate, $createIfNotExists);
+ }
+ }
+
+ if (Coordinate::coordinateIsRange($pCoordinate)) {
+ throw new Exception('Cell coordinate can not be a range of cells.');
+ } elseif (strpos($pCoordinate, '$') !== false) {
+ throw new Exception('Cell coordinate must not be absolute.');
+ }
+
+ // Create new cell object, if required
+ return $createIfNotExists ? $this->createNewCell($pCoordinateUpper) : null;
+ }
+
+ /**
+ * Get cell at a specific coordinate by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't
+ * already exist, or a null should be returned instead
+ *
+ * @return null|Cell Cell that was found/created or null
+ */
+ public function getCellByColumnAndRow($columnIndex, $row, $createIfNotExists = true)
+ {
+ $columnLetter = Coordinate::stringFromColumnIndex($columnIndex);
+ $coordinate = $columnLetter . $row;
+
+ if ($this->cellCollection->has($coordinate)) {
+ return $this->cellCollection->get($coordinate);
+ }
+
+ // Create new cell object, if required
+ return $createIfNotExists ? $this->createNewCell($coordinate) : null;
+ }
+
+ /**
+ * Create a new cell at the specified coordinate.
+ *
+ * @param string $pCoordinate Coordinate of the cell
+ *
+ * @return Cell Cell that was created
+ */
+ private function createNewCell($pCoordinate)
+ {
+ $cell = new Cell(null, DataType::TYPE_NULL, $this);
+ $this->cellCollection->add($pCoordinate, $cell);
+ $this->cellCollectionIsSorted = false;
+
+ // Coordinates
+ $aCoordinates = Coordinate::coordinateFromString($pCoordinate);
+ if (Coordinate::columnIndexFromString($this->cachedHighestColumn) < Coordinate::columnIndexFromString($aCoordinates[0])) {
+ $this->cachedHighestColumn = $aCoordinates[0];
+ }
+ if ($aCoordinates[1] > $this->cachedHighestRow) {
+ $this->cachedHighestRow = $aCoordinates[1];
+ }
+
+ // Cell needs appropriate xfIndex from dimensions records
+ // but don't create dimension records if they don't already exist
+ $rowDimension = $this->getRowDimension($aCoordinates[1], false);
+ $columnDimension = $this->getColumnDimension($aCoordinates[0], false);
+
+ if ($rowDimension !== null && $rowDimension->getXfIndex() > 0) {
+ // then there is a row dimension with explicit style, assign it to the cell
+ $cell->setXfIndex($rowDimension->getXfIndex());
+ } elseif ($columnDimension !== null && $columnDimension->getXfIndex() > 0) {
+ // then there is a column dimension, assign it to the cell
+ $cell->setXfIndex($columnDimension->getXfIndex());
+ }
+
+ return $cell;
+ }
+
+ /**
+ * Does the cell at a specific coordinate exist?
+ *
+ * @param string $pCoordinate Coordinate of the cell eg: 'A1'
+ *
+ * @return bool
+ */
+ public function cellExists($pCoordinate)
+ {
+ // Worksheet reference?
+ if (strpos($pCoordinate, '!') !== false) {
+ $worksheetReference = self::extractSheetTitle($pCoordinate, true);
+
+ return $this->parent->getSheetByName($worksheetReference[0])->cellExists(strtoupper($worksheetReference[1]));
+ }
+
+ // Named range?
+ if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
+ (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) {
+ $namedRange = NamedRange::resolveRange($pCoordinate, $this);
+ if ($namedRange !== null) {
+ $pCoordinate = $namedRange->getRange();
+ if ($this->getHashCode() != $namedRange->getWorksheet()->getHashCode()) {
+ if (!$namedRange->getLocalOnly()) {
+ return $namedRange->getWorksheet()->cellExists($pCoordinate);
+ }
+
+ throw new Exception('Named range ' . $namedRange->getName() . ' is not accessible from within sheet ' . $this->getTitle());
+ }
+ } else {
+ return false;
+ }
+ }
+
+ // Uppercase coordinate
+ $pCoordinate = strtoupper($pCoordinate);
+
+ if (Coordinate::coordinateIsRange($pCoordinate)) {
+ throw new Exception('Cell coordinate can not be a range of cells.');
+ } elseif (strpos($pCoordinate, '$') !== false) {
+ throw new Exception('Cell coordinate must not be absolute.');
+ }
+
+ // Cell exists?
+ return $this->cellCollection->has($pCoordinate);
+ }
+
+ /**
+ * Cell at a specific coordinate by using numeric cell coordinates exists?
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ *
+ * @return bool
+ */
+ public function cellExistsByColumnAndRow($columnIndex, $row)
+ {
+ return $this->cellExists(Coordinate::stringFromColumnIndex($columnIndex) . $row);
+ }
+
+ /**
+ * Get row dimension at a specific row.
+ *
+ * @param int $pRow Numeric index of the row
+ * @param bool $create
+ *
+ * @return RowDimension
+ */
+ public function getRowDimension($pRow, $create = true)
+ {
+ // Found
+ $found = null;
+
+ // Get row dimension
+ if (!isset($this->rowDimensions[$pRow])) {
+ if (!$create) {
+ return null;
+ }
+ $this->rowDimensions[$pRow] = new RowDimension($pRow);
+
+ $this->cachedHighestRow = max($this->cachedHighestRow, $pRow);
+ }
+
+ return $this->rowDimensions[$pRow];
+ }
+
+ /**
+ * Get column dimension at a specific column.
+ *
+ * @param string $pColumn String index of the column eg: 'A'
+ * @param bool $create
+ *
+ * @return ColumnDimension
+ */
+ public function getColumnDimension($pColumn, $create = true)
+ {
+ // Uppercase coordinate
+ $pColumn = strtoupper($pColumn);
+
+ // Fetch dimensions
+ if (!isset($this->columnDimensions[$pColumn])) {
+ if (!$create) {
+ return null;
+ }
+ $this->columnDimensions[$pColumn] = new ColumnDimension($pColumn);
+
+ if (Coordinate::columnIndexFromString($this->cachedHighestColumn) < Coordinate::columnIndexFromString($pColumn)) {
+ $this->cachedHighestColumn = $pColumn;
+ }
+ }
+
+ return $this->columnDimensions[$pColumn];
+ }
+
+ /**
+ * Get column dimension at a specific column by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ *
+ * @return ColumnDimension
+ */
+ public function getColumnDimensionByColumn($columnIndex)
+ {
+ return $this->getColumnDimension(Coordinate::stringFromColumnIndex($columnIndex));
+ }
+
+ /**
+ * Get styles.
+ *
+ * @return Style[]
+ */
+ public function getStyles()
+ {
+ return $this->styles;
+ }
+
+ /**
+ * Get style for cell.
+ *
+ * @param string $pCellCoordinate Cell coordinate (or range) to get style for, eg: 'A1'
+ *
+ * @return Style
+ */
+ public function getStyle($pCellCoordinate)
+ {
+ // set this sheet as active
+ $this->parent->setActiveSheetIndex($this->parent->getIndex($this));
+
+ // set cell coordinate as active
+ $this->setSelectedCells($pCellCoordinate);
+
+ return $this->parent->getCellXfSupervisor();
+ }
+
+ /**
+ * Get conditional styles for a cell.
+ *
+ * @param string $pCoordinate eg: 'A1'
+ *
+ * @return Conditional[]
+ */
+ public function getConditionalStyles($pCoordinate)
+ {
+ $pCoordinate = strtoupper($pCoordinate);
+ if (!isset($this->conditionalStylesCollection[$pCoordinate])) {
+ $this->conditionalStylesCollection[$pCoordinate] = [];
+ }
+
+ return $this->conditionalStylesCollection[$pCoordinate];
+ }
+
+ /**
+ * Do conditional styles exist for this cell?
+ *
+ * @param string $pCoordinate eg: 'A1'
+ *
+ * @return bool
+ */
+ public function conditionalStylesExists($pCoordinate)
+ {
+ return isset($this->conditionalStylesCollection[strtoupper($pCoordinate)]);
+ }
+
+ /**
+ * Removes conditional styles for a cell.
+ *
+ * @param string $pCoordinate eg: 'A1'
+ *
+ * @return $this
+ */
+ public function removeConditionalStyles($pCoordinate)
+ {
+ unset($this->conditionalStylesCollection[strtoupper($pCoordinate)]);
+
+ return $this;
+ }
+
+ /**
+ * Get collection of conditional styles.
+ *
+ * @return array
+ */
+ public function getConditionalStylesCollection()
+ {
+ return $this->conditionalStylesCollection;
+ }
+
+ /**
+ * Set conditional styles.
+ *
+ * @param string $pCoordinate eg: 'A1'
+ * @param $pValue Conditional[]
+ *
+ * @return $this
+ */
+ public function setConditionalStyles($pCoordinate, $pValue)
+ {
+ $this->conditionalStylesCollection[strtoupper($pCoordinate)] = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get style for cell by using numeric cell coordinates.
+ *
+ * @param int $columnIndex1 Numeric column coordinate of the cell
+ * @param int $row1 Numeric row coordinate of the cell
+ * @param null|int $columnIndex2 Numeric column coordinate of the range cell
+ * @param null|int $row2 Numeric row coordinate of the range cell
+ *
+ * @return Style
+ */
+ public function getStyleByColumnAndRow($columnIndex1, $row1, $columnIndex2 = null, $row2 = null)
+ {
+ if ($columnIndex2 !== null && $row2 !== null) {
+ $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
+
+ return $this->getStyle($cellRange);
+ }
+
+ return $this->getStyle(Coordinate::stringFromColumnIndex($columnIndex1) . $row1);
+ }
+
+ /**
+ * Duplicate cell style to a range of cells.
+ *
+ * Please note that this will overwrite existing cell styles for cells in range!
+ *
+ * @param Style $pCellStyle Cell style to duplicate
+ * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
+ *
+ * @return $this
+ */
+ public function duplicateStyle(Style $pCellStyle, $pRange)
+ {
+ // Add the style to the workbook if necessary
+ $workbook = $this->parent;
+ if ($existingStyle = $this->parent->getCellXfByHashCode($pCellStyle->getHashCode())) {
+ // there is already such cell Xf in our collection
+ $xfIndex = $existingStyle->getIndex();
+ } else {
+ // we don't have such a cell Xf, need to add
+ $workbook->addCellXf($pCellStyle);
+ $xfIndex = $pCellStyle->getIndex();
+ }
+
+ // Calculate range outer borders
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange . ':' . $pRange);
+
+ // Make sure we can loop upwards on rows and columns
+ if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
+ $tmp = $rangeStart;
+ $rangeStart = $rangeEnd;
+ $rangeEnd = $tmp;
+ }
+
+ // Loop through cells and apply styles
+ for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
+ for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
+ $this->getCell(Coordinate::stringFromColumnIndex($col) . $row)->setXfIndex($xfIndex);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Duplicate conditional style to a range of cells.
+ *
+ * Please note that this will overwrite existing cell styles for cells in range!
+ *
+ * @param Conditional[] $pCellStyle Cell style to duplicate
+ * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
+ *
+ * @return $this
+ */
+ public function duplicateConditionalStyle(array $pCellStyle, $pRange = '')
+ {
+ foreach ($pCellStyle as $cellStyle) {
+ if (!($cellStyle instanceof Conditional)) {
+ throw new Exception('Style is not a conditional style');
+ }
+ }
+
+ // Calculate range outer borders
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange . ':' . $pRange);
+
+ // Make sure we can loop upwards on rows and columns
+ if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
+ $tmp = $rangeStart;
+ $rangeStart = $rangeEnd;
+ $rangeEnd = $tmp;
+ }
+
+ // Loop through cells and apply styles
+ for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
+ for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
+ $this->setConditionalStyles(Coordinate::stringFromColumnIndex($col) . $row, $pCellStyle);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set break on a cell.
+ *
+ * @param string $pCoordinate Cell coordinate (e.g. A1)
+ * @param int $pBreak Break type (type of Worksheet::BREAK_*)
+ *
+ * @return $this
+ */
+ public function setBreak($pCoordinate, $pBreak)
+ {
+ // Uppercase coordinate
+ $pCoordinate = strtoupper($pCoordinate);
+
+ if ($pCoordinate != '') {
+ if ($pBreak == self::BREAK_NONE) {
+ if (isset($this->breaks[$pCoordinate])) {
+ unset($this->breaks[$pCoordinate]);
+ }
+ } else {
+ $this->breaks[$pCoordinate] = $pBreak;
+ }
+ } else {
+ throw new Exception('No cell coordinate specified.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set break on a cell by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ * @param int $break Break type (type of Worksheet::BREAK_*)
+ *
+ * @return $this
+ */
+ public function setBreakByColumnAndRow($columnIndex, $row, $break)
+ {
+ return $this->setBreak(Coordinate::stringFromColumnIndex($columnIndex) . $row, $break);
+ }
+
+ /**
+ * Get breaks.
+ *
+ * @return array[]
+ */
+ public function getBreaks()
+ {
+ return $this->breaks;
+ }
+
+ /**
+ * Set merge on a cell range.
+ *
+ * @param string $pRange Cell range (e.g. A1:E1)
+ *
+ * @return $this
+ */
+ public function mergeCells($pRange)
+ {
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ if (strpos($pRange, ':') !== false) {
+ $this->mergeCells[$pRange] = $pRange;
+
+ // make sure cells are created
+
+ // get the cells in the range
+ $aReferences = Coordinate::extractAllCellReferencesInRange($pRange);
+
+ // create upper left cell if it does not already exist
+ $upperLeft = $aReferences[0];
+ if (!$this->cellExists($upperLeft)) {
+ $this->getCell($upperLeft)->setValueExplicit(null, DataType::TYPE_NULL);
+ }
+
+ // Blank out the rest of the cells in the range (if they exist)
+ $count = count($aReferences);
+ for ($i = 1; $i < $count; ++$i) {
+ if ($this->cellExists($aReferences[$i])) {
+ $this->getCell($aReferences[$i])->setValueExplicit(null, DataType::TYPE_NULL);
+ }
+ }
+ } else {
+ throw new Exception('Merge must be set on a range of cells.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set merge on a cell range by using numeric cell coordinates.
+ *
+ * @param int $columnIndex1 Numeric column coordinate of the first cell
+ * @param int $row1 Numeric row coordinate of the first cell
+ * @param int $columnIndex2 Numeric column coordinate of the last cell
+ * @param int $row2 Numeric row coordinate of the last cell
+ *
+ * @return $this
+ */
+ public function mergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
+ {
+ $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
+
+ return $this->mergeCells($cellRange);
+ }
+
+ /**
+ * Remove merge on a cell range.
+ *
+ * @param string $pRange Cell range (e.g. A1:E1)
+ *
+ * @return $this
+ */
+ public function unmergeCells($pRange)
+ {
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ if (strpos($pRange, ':') !== false) {
+ if (isset($this->mergeCells[$pRange])) {
+ unset($this->mergeCells[$pRange]);
+ } else {
+ throw new Exception('Cell range ' . $pRange . ' not known as merged.');
+ }
+ } else {
+ throw new Exception('Merge can only be removed from a range of cells.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Remove merge on a cell range by using numeric cell coordinates.
+ *
+ * @param int $columnIndex1 Numeric column coordinate of the first cell
+ * @param int $row1 Numeric row coordinate of the first cell
+ * @param int $columnIndex2 Numeric column coordinate of the last cell
+ * @param int $row2 Numeric row coordinate of the last cell
+ *
+ * @return $this
+ */
+ public function unmergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
+ {
+ $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
+
+ return $this->unmergeCells($cellRange);
+ }
+
+ /**
+ * Get merge cells array.
+ *
+ * @return string[]
+ */
+ public function getMergeCells()
+ {
+ return $this->mergeCells;
+ }
+
+ /**
+ * Set merge cells array for the entire sheet. Use instead mergeCells() to merge
+ * a single cell range.
+ *
+ * @param string[] $pValue
+ *
+ * @return $this
+ */
+ public function setMergeCells(array $pValue)
+ {
+ $this->mergeCells = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Set protection on a cell range.
+ *
+ * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1)
+ * @param string $pPassword Password to unlock the protection
+ * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
+ *
+ * @return $this
+ */
+ public function protectCells($pRange, $pPassword, $pAlreadyHashed = false)
+ {
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ if (!$pAlreadyHashed) {
+ $pPassword = Shared\PasswordHasher::hashPassword($pPassword);
+ }
+ $this->protectedCells[$pRange] = $pPassword;
+
+ return $this;
+ }
+
+ /**
+ * Set protection on a cell range by using numeric cell coordinates.
+ *
+ * @param int $columnIndex1 Numeric column coordinate of the first cell
+ * @param int $row1 Numeric row coordinate of the first cell
+ * @param int $columnIndex2 Numeric column coordinate of the last cell
+ * @param int $row2 Numeric row coordinate of the last cell
+ * @param string $password Password to unlock the protection
+ * @param bool $alreadyHashed If the password has already been hashed, set this to true
+ *
+ * @return $this
+ */
+ public function protectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2, $password, $alreadyHashed = false)
+ {
+ $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
+
+ return $this->protectCells($cellRange, $password, $alreadyHashed);
+ }
+
+ /**
+ * Remove protection on a cell range.
+ *
+ * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1)
+ *
+ * @return $this
+ */
+ public function unprotectCells($pRange)
+ {
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ if (isset($this->protectedCells[$pRange])) {
+ unset($this->protectedCells[$pRange]);
+ } else {
+ throw new Exception('Cell range ' . $pRange . ' not known as protected.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Remove protection on a cell range by using numeric cell coordinates.
+ *
+ * @param int $columnIndex1 Numeric column coordinate of the first cell
+ * @param int $row1 Numeric row coordinate of the first cell
+ * @param int $columnIndex2 Numeric column coordinate of the last cell
+ * @param int $row2 Numeric row coordinate of the last cell
+ *
+ * @return $this
+ */
+ public function unprotectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
+ {
+ $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
+
+ return $this->unprotectCells($cellRange);
+ }
+
+ /**
+ * Get protected cells.
+ *
+ * @return array[]
+ */
+ public function getProtectedCells()
+ {
+ return $this->protectedCells;
+ }
+
+ /**
+ * Get Autofilter.
+ *
+ * @return AutoFilter
+ */
+ public function getAutoFilter()
+ {
+ return $this->autoFilter;
+ }
+
+ /**
+ * Set AutoFilter.
+ *
+ * @param AutoFilter|string $pValue
+ * A simple string containing a Cell range like 'A1:E10' is permitted for backward compatibility
+ *
+ * @return $this
+ */
+ public function setAutoFilter($pValue)
+ {
+ if (is_string($pValue)) {
+ $this->autoFilter->setRange($pValue);
+ } elseif (is_object($pValue) && ($pValue instanceof AutoFilter)) {
+ $this->autoFilter = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Autofilter Range by using numeric cell coordinates.
+ *
+ * @param int $columnIndex1 Numeric column coordinate of the first cell
+ * @param int $row1 Numeric row coordinate of the first cell
+ * @param int $columnIndex2 Numeric column coordinate of the second cell
+ * @param int $row2 Numeric row coordinate of the second cell
+ *
+ * @return $this
+ */
+ public function setAutoFilterByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
+ {
+ return $this->setAutoFilter(
+ Coordinate::stringFromColumnIndex($columnIndex1) . $row1
+ . ':' .
+ Coordinate::stringFromColumnIndex($columnIndex2) . $row2
+ );
+ }
+
+ /**
+ * Remove autofilter.
+ *
+ * @return $this
+ */
+ public function removeAutoFilter()
+ {
+ $this->autoFilter->setRange(null);
+
+ return $this;
+ }
+
+ /**
+ * Get Freeze Pane.
+ *
+ * @return string
+ */
+ public function getFreezePane()
+ {
+ return $this->freezePane;
+ }
+
+ /**
+ * Freeze Pane.
+ *
+ * Examples:
+ *
+ * - A2 will freeze the rows above cell A2 (i.e row 1)
+ * - B1 will freeze the columns to the left of cell B1 (i.e column A)
+ * - B2 will freeze the rows above and to the left of cell B2 (i.e row 1 and column A)
+ *
+ * @param null|string $cell Position of the split
+ * @param null|string $topLeftCell default position of the right bottom pane
+ *
+ * @return $this
+ */
+ public function freezePane($cell, $topLeftCell = null)
+ {
+ if (is_string($cell) && Coordinate::coordinateIsRange($cell)) {
+ throw new Exception('Freeze pane can not be set on a range of cells.');
+ }
+
+ if ($cell !== null && $topLeftCell === null) {
+ $coordinate = Coordinate::coordinateFromString($cell);
+ $topLeftCell = $coordinate[0] . $coordinate[1];
+ }
+
+ $this->freezePane = $cell;
+ $this->topLeftCell = $topLeftCell;
+
+ return $this;
+ }
+
+ /**
+ * Freeze Pane by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ *
+ * @return $this
+ */
+ public function freezePaneByColumnAndRow($columnIndex, $row)
+ {
+ return $this->freezePane(Coordinate::stringFromColumnIndex($columnIndex) . $row);
+ }
+
+ /**
+ * Unfreeze Pane.
+ *
+ * @return $this
+ */
+ public function unfreezePane()
+ {
+ return $this->freezePane(null);
+ }
+
+ /**
+ * Get the default position of the right bottom pane.
+ *
+ * @return int
+ */
+ public function getTopLeftCell()
+ {
+ return $this->topLeftCell;
+ }
+
+ /**
+ * Insert a new row, updating all possible related data.
+ *
+ * @param int $pBefore Insert before this one
+ * @param int $pNumRows Number of rows to insert
+ *
+ * @return $this
+ */
+ public function insertNewRowBefore($pBefore, $pNumRows = 1)
+ {
+ if ($pBefore >= 1) {
+ $objReferenceHelper = ReferenceHelper::getInstance();
+ $objReferenceHelper->insertNewBefore('A' . $pBefore, 0, $pNumRows, $this);
+ } else {
+ throw new Exception('Rows can only be inserted before at least row 1.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Insert a new column, updating all possible related data.
+ *
+ * @param string $pBefore Insert before this one, eg: 'A'
+ * @param int $pNumCols Number of columns to insert
+ *
+ * @return $this
+ */
+ public function insertNewColumnBefore($pBefore, $pNumCols = 1)
+ {
+ if (!is_numeric($pBefore)) {
+ $objReferenceHelper = ReferenceHelper::getInstance();
+ $objReferenceHelper->insertNewBefore($pBefore . '1', $pNumCols, 0, $this);
+ } else {
+ throw new Exception('Column references should not be numeric.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Insert a new column, updating all possible related data.
+ *
+ * @param int $beforeColumnIndex Insert before this one (numeric column coordinate of the cell)
+ * @param int $pNumCols Number of columns to insert
+ *
+ * @return $this
+ */
+ public function insertNewColumnBeforeByIndex($beforeColumnIndex, $pNumCols = 1)
+ {
+ if ($beforeColumnIndex >= 1) {
+ return $this->insertNewColumnBefore(Coordinate::stringFromColumnIndex($beforeColumnIndex), $pNumCols);
+ }
+
+ throw new Exception('Columns can only be inserted before at least column A (1).');
+ }
+
+ /**
+ * Delete a row, updating all possible related data.
+ *
+ * @param int $pRow Remove starting with this one
+ * @param int $pNumRows Number of rows to remove
+ *
+ * @return $this
+ */
+ public function removeRow($pRow, $pNumRows = 1)
+ {
+ if ($pRow < 1) {
+ throw new Exception('Rows to be deleted should at least start from row 1.');
+ }
+
+ $highestRow = $this->getHighestDataRow();
+ $removedRowsCounter = 0;
+
+ for ($r = 0; $r < $pNumRows; ++$r) {
+ if ($pRow + $r <= $highestRow) {
+ $this->getCellCollection()->removeRow($pRow + $r);
+ ++$removedRowsCounter;
+ }
+ }
+
+ $objReferenceHelper = ReferenceHelper::getInstance();
+ $objReferenceHelper->insertNewBefore('A' . ($pRow + $pNumRows), 0, -$pNumRows, $this);
+ for ($r = 0; $r < $removedRowsCounter; ++$r) {
+ $this->getCellCollection()->removeRow($highestRow);
+ --$highestRow;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Remove a column, updating all possible related data.
+ *
+ * @param string $pColumn Remove starting with this one, eg: 'A'
+ * @param int $pNumCols Number of columns to remove
+ *
+ * @return $this
+ */
+ public function removeColumn($pColumn, $pNumCols = 1)
+ {
+ if (is_numeric($pColumn)) {
+ throw new Exception('Column references should not be numeric.');
+ }
+
+ $highestColumn = $this->getHighestDataColumn();
+ $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
+ $pColumnIndex = Coordinate::columnIndexFromString($pColumn);
+
+ if ($pColumnIndex > $highestColumnIndex) {
+ return $this;
+ }
+
+ $pColumn = Coordinate::stringFromColumnIndex($pColumnIndex + $pNumCols);
+ $objReferenceHelper = ReferenceHelper::getInstance();
+ $objReferenceHelper->insertNewBefore($pColumn . '1', -$pNumCols, 0, $this);
+
+ $maxPossibleColumnsToBeRemoved = $highestColumnIndex - $pColumnIndex + 1;
+
+ for ($c = 0, $n = min($maxPossibleColumnsToBeRemoved, $pNumCols); $c < $n; ++$c) {
+ $this->getCellCollection()->removeColumn($highestColumn);
+ $highestColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 1);
+ }
+
+ $this->garbageCollect();
+
+ return $this;
+ }
+
+ /**
+ * Remove a column, updating all possible related data.
+ *
+ * @param int $columnIndex Remove starting with this one (numeric column coordinate of the cell)
+ * @param int $numColumns Number of columns to remove
+ *
+ * @return $this
+ */
+ public function removeColumnByIndex($columnIndex, $numColumns = 1)
+ {
+ if ($columnIndex >= 1) {
+ return $this->removeColumn(Coordinate::stringFromColumnIndex($columnIndex), $numColumns);
+ }
+
+ throw new Exception('Columns to be deleted should at least start from column A (1)');
+ }
+
+ /**
+ * Show gridlines?
+ *
+ * @return bool
+ */
+ public function getShowGridlines()
+ {
+ return $this->showGridlines;
+ }
+
+ /**
+ * Set show gridlines.
+ *
+ * @param bool $pValue Show gridlines (true/false)
+ *
+ * @return $this
+ */
+ public function setShowGridlines($pValue)
+ {
+ $this->showGridlines = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Print gridlines?
+ *
+ * @return bool
+ */
+ public function getPrintGridlines()
+ {
+ return $this->printGridlines;
+ }
+
+ /**
+ * Set print gridlines.
+ *
+ * @param bool $pValue Print gridlines (true/false)
+ *
+ * @return $this
+ */
+ public function setPrintGridlines($pValue)
+ {
+ $this->printGridlines = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Show row and column headers?
+ *
+ * @return bool
+ */
+ public function getShowRowColHeaders()
+ {
+ return $this->showRowColHeaders;
+ }
+
+ /**
+ * Set show row and column headers.
+ *
+ * @param bool $pValue Show row and column headers (true/false)
+ *
+ * @return $this
+ */
+ public function setShowRowColHeaders($pValue)
+ {
+ $this->showRowColHeaders = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Show summary below? (Row/Column outlining).
+ *
+ * @return bool
+ */
+ public function getShowSummaryBelow()
+ {
+ return $this->showSummaryBelow;
+ }
+
+ /**
+ * Set show summary below.
+ *
+ * @param bool $pValue Show summary below (true/false)
+ *
+ * @return $this
+ */
+ public function setShowSummaryBelow($pValue)
+ {
+ $this->showSummaryBelow = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Show summary right? (Row/Column outlining).
+ *
+ * @return bool
+ */
+ public function getShowSummaryRight()
+ {
+ return $this->showSummaryRight;
+ }
+
+ /**
+ * Set show summary right.
+ *
+ * @param bool $pValue Show summary right (true/false)
+ *
+ * @return $this
+ */
+ public function setShowSummaryRight($pValue)
+ {
+ $this->showSummaryRight = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get comments.
+ *
+ * @return Comment[]
+ */
+ public function getComments()
+ {
+ return $this->comments;
+ }
+
+ /**
+ * Set comments array for the entire sheet.
+ *
+ * @param Comment[] $pValue
+ *
+ * @return $this
+ */
+ public function setComments(array $pValue)
+ {
+ $this->comments = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get comment for cell.
+ *
+ * @param string $pCellCoordinate Cell coordinate to get comment for, eg: 'A1'
+ *
+ * @return Comment
+ */
+ public function getComment($pCellCoordinate)
+ {
+ // Uppercase coordinate
+ $pCellCoordinate = strtoupper($pCellCoordinate);
+
+ if (Coordinate::coordinateIsRange($pCellCoordinate)) {
+ throw new Exception('Cell coordinate string can not be a range of cells.');
+ } elseif (strpos($pCellCoordinate, '$') !== false) {
+ throw new Exception('Cell coordinate string must not be absolute.');
+ } elseif ($pCellCoordinate == '') {
+ throw new Exception('Cell coordinate can not be zero-length string.');
+ }
+
+ // Check if we already have a comment for this cell.
+ if (isset($this->comments[$pCellCoordinate])) {
+ return $this->comments[$pCellCoordinate];
+ }
+
+ // If not, create a new comment.
+ $newComment = new Comment();
+ $this->comments[$pCellCoordinate] = $newComment;
+
+ return $newComment;
+ }
+
+ /**
+ * Get comment for cell by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ *
+ * @return Comment
+ */
+ public function getCommentByColumnAndRow($columnIndex, $row)
+ {
+ return $this->getComment(Coordinate::stringFromColumnIndex($columnIndex) . $row);
+ }
+
+ /**
+ * Get active cell.
+ *
+ * @return string Example: 'A1'
+ */
+ public function getActiveCell()
+ {
+ return $this->activeCell;
+ }
+
+ /**
+ * Get selected cells.
+ *
+ * @return string
+ */
+ public function getSelectedCells()
+ {
+ return $this->selectedCells;
+ }
+
+ /**
+ * Selected cell.
+ *
+ * @param string $pCoordinate Cell (i.e. A1)
+ *
+ * @return $this
+ */
+ public function setSelectedCell($pCoordinate)
+ {
+ return $this->setSelectedCells($pCoordinate);
+ }
+
+ /**
+ * Select a range of cells.
+ *
+ * @param string $pCoordinate Cell range, examples: 'A1', 'B2:G5', 'A:C', '3:6'
+ *
+ * @return $this
+ */
+ public function setSelectedCells($pCoordinate)
+ {
+ // Uppercase coordinate
+ $pCoordinate = strtoupper($pCoordinate);
+
+ // Convert 'A' to 'A:A'
+ $pCoordinate = preg_replace('/^([A-Z]+)$/', '${1}:${1}', $pCoordinate);
+
+ // Convert '1' to '1:1'
+ $pCoordinate = preg_replace('/^(\d+)$/', '${1}:${1}', $pCoordinate);
+
+ // Convert 'A:C' to 'A1:C1048576'
+ $pCoordinate = preg_replace('/^([A-Z]+):([A-Z]+)$/', '${1}1:${2}1048576', $pCoordinate);
+
+ // Convert '1:3' to 'A1:XFD3'
+ $pCoordinate = preg_replace('/^(\d+):(\d+)$/', 'A${1}:XFD${2}', $pCoordinate);
+
+ if (Coordinate::coordinateIsRange($pCoordinate)) {
+ [$first] = Coordinate::splitRange($pCoordinate);
+ $this->activeCell = $first[0];
+ } else {
+ $this->activeCell = $pCoordinate;
+ }
+ $this->selectedCells = $pCoordinate;
+
+ return $this;
+ }
+
+ /**
+ * Selected cell by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ *
+ * @return $this
+ */
+ public function setSelectedCellByColumnAndRow($columnIndex, $row)
+ {
+ return $this->setSelectedCells(Coordinate::stringFromColumnIndex($columnIndex) . $row);
+ }
+
+ /**
+ * Get right-to-left.
+ *
+ * @return bool
+ */
+ public function getRightToLeft()
+ {
+ return $this->rightToLeft;
+ }
+
+ /**
+ * Set right-to-left.
+ *
+ * @param bool $value Right-to-left true/false
+ *
+ * @return $this
+ */
+ public function setRightToLeft($value)
+ {
+ $this->rightToLeft = $value;
+
+ return $this;
+ }
+
+ /**
+ * Fill worksheet from values in array.
+ *
+ * @param array $source Source array
+ * @param mixed $nullValue Value in source array that stands for blank cell
+ * @param string $startCell Insert array starting from this cell address as the top left coordinate
+ * @param bool $strictNullComparison Apply strict comparison when testing for null values in the array
+ *
+ * @return $this
+ */
+ public function fromArray(array $source, $nullValue = null, $startCell = 'A1', $strictNullComparison = false)
+ {
+ // Convert a 1-D array to 2-D (for ease of looping)
+ if (!is_array(end($source))) {
+ $source = [$source];
+ }
+
+ // start coordinate
+ [$startColumn, $startRow] = Coordinate::coordinateFromString($startCell);
+
+ // Loop through $source
+ foreach ($source as $rowData) {
+ $currentColumn = $startColumn;
+ foreach ($rowData as $cellValue) {
+ if ($strictNullComparison) {
+ if ($cellValue !== $nullValue) {
+ // Set cell value
+ $this->getCell($currentColumn . $startRow)->setValue($cellValue);
+ }
+ } else {
+ if ($cellValue != $nullValue) {
+ // Set cell value
+ $this->getCell($currentColumn . $startRow)->setValue($cellValue);
+ }
+ }
+ ++$currentColumn;
+ }
+ ++$startRow;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create array from a range of cells.
+ *
+ * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
+ * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
+ * @param bool $calculateFormulas Should formulas be calculated?
+ * @param bool $formatData Should formatting be applied to cell values?
+ * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
+ * True - Return rows and columns indexed by their actual row and column IDs
+ *
+ * @return array
+ */
+ public function rangeToArray($pRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
+ {
+ // Returnvalue
+ $returnValue = [];
+ // Identify the range that we need to extract from the worksheet
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange);
+ $minCol = Coordinate::stringFromColumnIndex($rangeStart[0]);
+ $minRow = $rangeStart[1];
+ $maxCol = Coordinate::stringFromColumnIndex($rangeEnd[0]);
+ $maxRow = $rangeEnd[1];
+
+ ++$maxCol;
+ // Loop through rows
+ $r = -1;
+ for ($row = $minRow; $row <= $maxRow; ++$row) {
+ $rRef = $returnCellRef ? $row : ++$r;
+ $c = -1;
+ // Loop through columns in the current row
+ for ($col = $minCol; $col != $maxCol; ++$col) {
+ $cRef = $returnCellRef ? $col : ++$c;
+ // Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen
+ // so we test and retrieve directly against cellCollection
+ if ($this->cellCollection->has($col . $row)) {
+ // Cell exists
+ $cell = $this->cellCollection->get($col . $row);
+ if ($cell->getValue() !== null) {
+ if ($cell->getValue() instanceof RichText) {
+ $returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText();
+ } else {
+ if ($calculateFormulas) {
+ $returnValue[$rRef][$cRef] = $cell->getCalculatedValue();
+ } else {
+ $returnValue[$rRef][$cRef] = $cell->getValue();
+ }
+ }
+
+ if ($formatData) {
+ $style = $this->parent->getCellXfByIndex($cell->getXfIndex());
+ $returnValue[$rRef][$cRef] = NumberFormat::toFormattedString(
+ $returnValue[$rRef][$cRef],
+ ($style && $style->getNumberFormat()) ? $style->getNumberFormat()->getFormatCode() : NumberFormat::FORMAT_GENERAL
+ );
+ }
+ } else {
+ // Cell holds a NULL
+ $returnValue[$rRef][$cRef] = $nullValue;
+ }
+ } else {
+ // Cell doesn't exist
+ $returnValue[$rRef][$cRef] = $nullValue;
+ }
+ }
+ }
+
+ // Return
+ return $returnValue;
+ }
+
+ /**
+ * Create array from a range of cells.
+ *
+ * @param string $pNamedRange Name of the Named Range
+ * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
+ * @param bool $calculateFormulas Should formulas be calculated?
+ * @param bool $formatData Should formatting be applied to cell values?
+ * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
+ * True - Return rows and columns indexed by their actual row and column IDs
+ *
+ * @return array
+ */
+ public function namedRangeToArray($pNamedRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
+ {
+ $namedRange = NamedRange::resolveRange($pNamedRange, $this);
+ if ($namedRange !== null) {
+ $pWorkSheet = $namedRange->getWorksheet();
+ $pCellRange = $namedRange->getRange();
+
+ return $pWorkSheet->rangeToArray($pCellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
+ }
+
+ throw new Exception('Named Range ' . $pNamedRange . ' does not exist.');
+ }
+
+ /**
+ * Create array from worksheet.
+ *
+ * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
+ * @param bool $calculateFormulas Should formulas be calculated?
+ * @param bool $formatData Should formatting be applied to cell values?
+ * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
+ * True - Return rows and columns indexed by their actual row and column IDs
+ *
+ * @return array
+ */
+ public function toArray($nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
+ {
+ // Garbage collect...
+ $this->garbageCollect();
+
+ // Identify the range that we need to extract from the worksheet
+ $maxCol = $this->getHighestColumn();
+ $maxRow = $this->getHighestRow();
+
+ // Return
+ return $this->rangeToArray('A1:' . $maxCol . $maxRow, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
+ }
+
+ /**
+ * Get row iterator.
+ *
+ * @param int $startRow The row number at which to start iterating
+ * @param int $endRow The row number at which to stop iterating
+ *
+ * @return RowIterator
+ */
+ public function getRowIterator($startRow = 1, $endRow = null)
+ {
+ return new RowIterator($this, $startRow, $endRow);
+ }
+
+ /**
+ * Get column iterator.
+ *
+ * @param string $startColumn The column address at which to start iterating
+ * @param string $endColumn The column address at which to stop iterating
+ *
+ * @return ColumnIterator
+ */
+ public function getColumnIterator($startColumn = 'A', $endColumn = null)
+ {
+ return new ColumnIterator($this, $startColumn, $endColumn);
+ }
+
+ /**
+ * Run PhpSpreadsheet garbage collector.
+ *
+ * @return $this
+ */
+ public function garbageCollect()
+ {
+ // Flush cache
+ $this->cellCollection->get('A1');
+
+ // Lookup highest column and highest row if cells are cleaned
+ $colRow = $this->cellCollection->getHighestRowAndColumn();
+ $highestRow = $colRow['row'];
+ $highestColumn = Coordinate::columnIndexFromString($colRow['column']);
+
+ // Loop through column dimensions
+ foreach ($this->columnDimensions as $dimension) {
+ $highestColumn = max($highestColumn, Coordinate::columnIndexFromString($dimension->getColumnIndex()));
+ }
+
+ // Loop through row dimensions
+ foreach ($this->rowDimensions as $dimension) {
+ $highestRow = max($highestRow, $dimension->getRowIndex());
+ }
+
+ // Cache values
+ if ($highestColumn < 1) {
+ $this->cachedHighestColumn = 'A';
+ } else {
+ $this->cachedHighestColumn = Coordinate::stringFromColumnIndex($highestColumn);
+ }
+ $this->cachedHighestRow = $highestRow;
+
+ // Return
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->dirty) {
+ $this->hash = md5($this->title . $this->autoFilter . ($this->protection->isProtectionEnabled() ? 't' : 'f') . __CLASS__);
+ $this->dirty = false;
+ }
+
+ return $this->hash;
+ }
+
+ /**
+ * Extract worksheet title from range.
+ *
+ * Example: extractSheetTitle("testSheet!A1") ==> 'A1'
+ * Example: extractSheetTitle("'testSheet 1'!A1", true) ==> ['testSheet 1', 'A1'];
+ *
+ * @param string $pRange Range to extract title from
+ * @param bool $returnRange Return range? (see example)
+ *
+ * @return mixed
+ */
+ public static function extractSheetTitle($pRange, $returnRange = false)
+ {
+ // Sheet title included?
+ if (($sep = strrpos($pRange, '!')) === false) {
+ return $returnRange ? ['', $pRange] : '';
+ }
+
+ if ($returnRange) {
+ return [substr($pRange, 0, $sep), substr($pRange, $sep + 1)];
+ }
+
+ return substr($pRange, $sep + 1);
+ }
+
+ /**
+ * Get hyperlink.
+ *
+ * @param string $pCellCoordinate Cell coordinate to get hyperlink for, eg: 'A1'
+ *
+ * @return Hyperlink
+ */
+ public function getHyperlink($pCellCoordinate)
+ {
+ // return hyperlink if we already have one
+ if (isset($this->hyperlinkCollection[$pCellCoordinate])) {
+ return $this->hyperlinkCollection[$pCellCoordinate];
+ }
+
+ // else create hyperlink
+ $this->hyperlinkCollection[$pCellCoordinate] = new Hyperlink();
+
+ return $this->hyperlinkCollection[$pCellCoordinate];
+ }
+
+ /**
+ * Set hyperlink.
+ *
+ * @param string $pCellCoordinate Cell coordinate to insert hyperlink, eg: 'A1'
+ *
+ * @return $this
+ */
+ public function setHyperlink($pCellCoordinate, ?Hyperlink $pHyperlink = null)
+ {
+ if ($pHyperlink === null) {
+ unset($this->hyperlinkCollection[$pCellCoordinate]);
+ } else {
+ $this->hyperlinkCollection[$pCellCoordinate] = $pHyperlink;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Hyperlink at a specific coordinate exists?
+ *
+ * @param string $pCoordinate eg: 'A1'
+ *
+ * @return bool
+ */
+ public function hyperlinkExists($pCoordinate)
+ {
+ return isset($this->hyperlinkCollection[$pCoordinate]);
+ }
+
+ /**
+ * Get collection of hyperlinks.
+ *
+ * @return Hyperlink[]
+ */
+ public function getHyperlinkCollection()
+ {
+ return $this->hyperlinkCollection;
+ }
+
+ /**
+ * Get data validation.
+ *
+ * @param string $pCellCoordinate Cell coordinate to get data validation for, eg: 'A1'
+ *
+ * @return DataValidation
+ */
+ public function getDataValidation($pCellCoordinate)
+ {
+ // return data validation if we already have one
+ if (isset($this->dataValidationCollection[$pCellCoordinate])) {
+ return $this->dataValidationCollection[$pCellCoordinate];
+ }
+
+ // else create data validation
+ $this->dataValidationCollection[$pCellCoordinate] = new DataValidation();
+
+ return $this->dataValidationCollection[$pCellCoordinate];
+ }
+
+ /**
+ * Set data validation.
+ *
+ * @param string $pCellCoordinate Cell coordinate to insert data validation, eg: 'A1'
+ *
+ * @return $this
+ */
+ public function setDataValidation($pCellCoordinate, ?DataValidation $pDataValidation = null)
+ {
+ if ($pDataValidation === null) {
+ unset($this->dataValidationCollection[$pCellCoordinate]);
+ } else {
+ $this->dataValidationCollection[$pCellCoordinate] = $pDataValidation;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Data validation at a specific coordinate exists?
+ *
+ * @param string $pCoordinate eg: 'A1'
+ *
+ * @return bool
+ */
+ public function dataValidationExists($pCoordinate)
+ {
+ return isset($this->dataValidationCollection[$pCoordinate]);
+ }
+
+ /**
+ * Get collection of data validations.
+ *
+ * @return DataValidation[]
+ */
+ public function getDataValidationCollection()
+ {
+ return $this->dataValidationCollection;
+ }
+
+ /**
+ * Accepts a range, returning it as a range that falls within the current highest row and column of the worksheet.
+ *
+ * @param string $range
+ *
+ * @return string Adjusted range value
+ */
+ public function shrinkRangeToFit($range)
+ {
+ $maxCol = $this->getHighestColumn();
+ $maxRow = $this->getHighestRow();
+ $maxCol = Coordinate::columnIndexFromString($maxCol);
+
+ $rangeBlocks = explode(' ', $range);
+ foreach ($rangeBlocks as &$rangeSet) {
+ $rangeBoundaries = Coordinate::getRangeBoundaries($rangeSet);
+
+ if (Coordinate::columnIndexFromString($rangeBoundaries[0][0]) > $maxCol) {
+ $rangeBoundaries[0][0] = Coordinate::stringFromColumnIndex($maxCol);
+ }
+ if ($rangeBoundaries[0][1] > $maxRow) {
+ $rangeBoundaries[0][1] = $maxRow;
+ }
+ if (Coordinate::columnIndexFromString($rangeBoundaries[1][0]) > $maxCol) {
+ $rangeBoundaries[1][0] = Coordinate::stringFromColumnIndex($maxCol);
+ }
+ if ($rangeBoundaries[1][1] > $maxRow) {
+ $rangeBoundaries[1][1] = $maxRow;
+ }
+ $rangeSet = $rangeBoundaries[0][0] . $rangeBoundaries[0][1] . ':' . $rangeBoundaries[1][0] . $rangeBoundaries[1][1];
+ }
+ unset($rangeSet);
+
+ return implode(' ', $rangeBlocks);
+ }
+
+ /**
+ * Get tab color.
+ *
+ * @return Color
+ */
+ public function getTabColor()
+ {
+ if ($this->tabColor === null) {
+ $this->tabColor = new Color();
+ }
+
+ return $this->tabColor;
+ }
+
+ /**
+ * Reset tab color.
+ *
+ * @return $this
+ */
+ public function resetTabColor()
+ {
+ $this->tabColor = null;
+ $this->tabColor = null;
+
+ return $this;
+ }
+
+ /**
+ * Tab color set?
+ *
+ * @return bool
+ */
+ public function isTabColorSet()
+ {
+ return $this->tabColor !== null;
+ }
+
+ /**
+ * Copy worksheet (!= clone!).
+ *
+ * @return static
+ */
+ public function copy()
+ {
+ return clone $this;
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone()
+ {
+ foreach ($this as $key => $val) {
+ if ($key == 'parent') {
+ continue;
+ }
+
+ if (is_object($val) || (is_array($val))) {
+ if ($key == 'cellCollection') {
+ $newCollection = $this->cellCollection->cloneCellCollection($this);
+ $this->cellCollection = $newCollection;
+ } elseif ($key == 'drawingCollection') {
+ $currentCollection = $this->drawingCollection;
+ $this->drawingCollection = new ArrayObject();
+ foreach ($currentCollection as $item) {
+ if (is_object($item)) {
+ $newDrawing = clone $item;
+ $newDrawing->setWorksheet($this);
+ }
+ }
+ } elseif (($key == 'autoFilter') && ($this->autoFilter instanceof AutoFilter)) {
+ $newAutoFilter = clone $this->autoFilter;
+ $this->autoFilter = $newAutoFilter;
+ $this->autoFilter->setParent($this);
+ } else {
+ $this->{$key} = unserialize(serialize($val));
+ }
+ }
+ }
+ }
+
+ /**
+ * Define the code name of the sheet.
+ *
+ * @param string $pValue Same rule as Title minus space not allowed (but, like Excel, change
+ * silently space to underscore)
+ * @param bool $validate False to skip validation of new title. WARNING: This should only be set
+ * at parse time (by Readers), where titles can be assumed to be valid.
+ *
+ * @return $this
+ */
+ public function setCodeName($pValue, $validate = true)
+ {
+ // Is this a 'rename' or not?
+ if ($this->getCodeName() == $pValue) {
+ return $this;
+ }
+
+ if ($validate) {
+ $pValue = str_replace(' ', '_', $pValue); //Excel does this automatically without flinching, we are doing the same
+
+ // Syntax check
+ // throw an exception if not valid
+ self::checkSheetCodeName($pValue);
+
+ // We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_'
+
+ if ($this->getParent()) {
+ // Is there already such sheet name?
+ if ($this->getParent()->sheetCodeNameExists($pValue)) {
+ // Use name, but append with lowest possible integer
+
+ if (Shared\StringHelper::countCharacters($pValue) > 29) {
+ $pValue = Shared\StringHelper::substring($pValue, 0, 29);
+ }
+ $i = 1;
+ while ($this->getParent()->sheetCodeNameExists($pValue . '_' . $i)) {
+ ++$i;
+ if ($i == 10) {
+ if (Shared\StringHelper::countCharacters($pValue) > 28) {
+ $pValue = Shared\StringHelper::substring($pValue, 0, 28);
+ }
+ } elseif ($i == 100) {
+ if (Shared\StringHelper::countCharacters($pValue) > 27) {
+ $pValue = Shared\StringHelper::substring($pValue, 0, 27);
+ }
+ }
+ }
+
+ $pValue .= '_' . $i; // ok, we have a valid name
+ }
+ }
+ }
+
+ $this->codeName = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Return the code name of the sheet.
+ *
+ * @return null|string
+ */
+ public function getCodeName()
+ {
+ return $this->codeName;
+ }
+
+ /**
+ * Sheet has a code name ?
+ *
+ * @return bool
+ */
+ public function hasCodeName()
+ {
+ return $this->codeName !== null;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php
new file mode 100755
index 0000000..afda5c4
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+abstract class BaseWriter implements IWriter
+{
+ /**
+ * Write charts that are defined in the workbook?
+ * Identifies whether the Writer should write definitions for any charts that exist in the PhpSpreadsheet object;.
+ *
+ * @var bool
+ */
+ protected $includeCharts = false;
+
+ /**
+ * Pre-calculate formulas
+ * Forces PhpSpreadsheet to recalculate all formulae in a workbook when saving, so that the pre-calculated values are
+ * immediately available to MS Excel or other office spreadsheet viewer when opening the file.
+ *
+ * @var bool
+ */
+ protected $preCalculateFormulas = true;
+
+ /**
+ * Use disk caching where possible?
+ *
+ * @var bool
+ */
+ private $useDiskCaching = false;
+
+ /**
+ * Disk caching directory.
+ *
+ * @var string
+ */
+ private $diskCachingDirectory = './';
+
+ /**
+ * @var resource
+ */
+ protected $fileHandle;
+
+ /**
+ * @var bool
+ */
+ private $shouldCloseFile;
+
+ public function getIncludeCharts()
+ {
+ return $this->includeCharts;
+ }
+
+ public function setIncludeCharts($pValue)
+ {
+ $this->includeCharts = (bool) $pValue;
+
+ return $this;
+ }
+
+ public function getPreCalculateFormulas()
+ {
+ return $this->preCalculateFormulas;
+ }
+
+ public function setPreCalculateFormulas($pValue)
+ {
+ $this->preCalculateFormulas = (bool) $pValue;
+
+ return $this;
+ }
+
+ public function getUseDiskCaching()
+ {
+ return $this->useDiskCaching;
+ }
+
+ public function setUseDiskCaching($pValue, $pDirectory = null)
+ {
+ $this->useDiskCaching = $pValue;
+
+ if ($pDirectory !== null) {
+ if (is_dir($pDirectory)) {
+ $this->diskCachingDirectory = $pDirectory;
+ } else {
+ throw new Exception("Directory does not exist: $pDirectory");
+ }
+ }
+
+ return $this;
+ }
+
+ public function getDiskCachingDirectory()
+ {
+ return $this->diskCachingDirectory;
+ }
+
+ /**
+ * Open file handle.
+ *
+ * @param resource|string $filename
+ */
+ public function openFileHandle($filename): void
+ {
+ if (is_resource($filename)) {
+ $this->fileHandle = $filename;
+ $this->shouldCloseFile = false;
+
+ return;
+ }
+
+ $fileHandle = $filename ? fopen($filename, 'wb+') : false;
+ if ($fileHandle === false) {
+ throw new Exception('Could not open file "' . $filename . '" for writing.');
+ }
+
+ $this->fileHandle = $fileHandle;
+ $this->shouldCloseFile = true;
+ }
+
+ /**
+ * Close file handle only if we opened it ourselves.
+ */
+ protected function maybeCloseFileHandle(): void
+ {
+ if ($this->shouldCloseFile) {
+ if (!fclose($this->fileHandle)) {
+ throw new Exception('Could not close file after writing.');
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php
new file mode 100755
index 0000000..414bdf9
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php
@@ -0,0 +1,334 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Csv extends BaseWriter
+{
+ /**
+ * PhpSpreadsheet object.
+ *
+ * @var Spreadsheet
+ */
+ private $spreadsheet;
+
+ /**
+ * Delimiter.
+ *
+ * @var string
+ */
+ private $delimiter = ',';
+
+ /**
+ * Enclosure.
+ *
+ * @var string
+ */
+ private $enclosure = '"';
+
+ /**
+ * Line ending.
+ *
+ * @var string
+ */
+ private $lineEnding = PHP_EOL;
+
+ /**
+ * Sheet index to write.
+ *
+ * @var int
+ */
+ private $sheetIndex = 0;
+
+ /**
+ * Whether to write a BOM (for UTF8).
+ *
+ * @var bool
+ */
+ private $useBOM = false;
+
+ /**
+ * Whether to write a Separator line as the first line of the file
+ * sep=x.
+ *
+ * @var bool
+ */
+ private $includeSeparatorLine = false;
+
+ /**
+ * Whether to write a fully Excel compatible CSV file.
+ *
+ * @var bool
+ */
+ private $excelCompatibility = false;
+
+ /**
+ * Create a new CSV.
+ *
+ * @param Spreadsheet $spreadsheet Spreadsheet object
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ $this->spreadsheet = $spreadsheet;
+ }
+
+ /**
+ * Save PhpSpreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ // Fetch sheet
+ $sheet = $this->spreadsheet->getSheet($this->sheetIndex);
+
+ $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
+ $saveArrayReturnType = Calculation::getArrayReturnType();
+ Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE);
+
+ // Open file
+ $this->openFileHandle($pFilename);
+
+ if ($this->excelCompatibility) {
+ $this->setUseBOM(true); // Enforce UTF-8 BOM Header
+ $this->setIncludeSeparatorLine(true); // Set separator line
+ $this->setEnclosure('"'); // Set enclosure to "
+ $this->setDelimiter(';'); // Set delimiter to a semi-colon
+ $this->setLineEnding("\r\n");
+ }
+
+ if ($this->useBOM) {
+ // Write the UTF-8 BOM code if required
+ fwrite($this->fileHandle, "\xEF\xBB\xBF");
+ }
+
+ if ($this->includeSeparatorLine) {
+ // Write the separator line if required
+ fwrite($this->fileHandle, 'sep=' . $this->getDelimiter() . $this->lineEnding);
+ }
+
+ // Identify the range that we need to extract from the worksheet
+ $maxCol = $sheet->getHighestDataColumn();
+ $maxRow = $sheet->getHighestDataRow();
+
+ // Write rows to file
+ for ($row = 1; $row <= $maxRow; ++$row) {
+ // Convert the row to an array...
+ $cellsArray = $sheet->rangeToArray('A' . $row . ':' . $maxCol . $row, '', $this->preCalculateFormulas);
+ // ... and write to the file
+ $this->writeLine($this->fileHandle, $cellsArray[0]);
+ }
+
+ $this->maybeCloseFileHandle();
+ Calculation::setArrayReturnType($saveArrayReturnType);
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
+ }
+
+ /**
+ * Get delimiter.
+ *
+ * @return string
+ */
+ public function getDelimiter()
+ {
+ return $this->delimiter;
+ }
+
+ /**
+ * Set delimiter.
+ *
+ * @param string $pValue Delimiter, defaults to ','
+ *
+ * @return $this
+ */
+ public function setDelimiter($pValue)
+ {
+ $this->delimiter = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get enclosure.
+ *
+ * @return string
+ */
+ public function getEnclosure()
+ {
+ return $this->enclosure;
+ }
+
+ /**
+ * Set enclosure.
+ *
+ * @param string $pValue Enclosure, defaults to "
+ *
+ * @return $this
+ */
+ public function setEnclosure($pValue)
+ {
+ $this->enclosure = $pValue ? $pValue : '"';
+
+ return $this;
+ }
+
+ /**
+ * Get line ending.
+ *
+ * @return string
+ */
+ public function getLineEnding()
+ {
+ return $this->lineEnding;
+ }
+
+ /**
+ * Set line ending.
+ *
+ * @param string $pValue Line ending, defaults to OS line ending (PHP_EOL)
+ *
+ * @return $this
+ */
+ public function setLineEnding($pValue)
+ {
+ $this->lineEnding = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get whether BOM should be used.
+ *
+ * @return bool
+ */
+ public function getUseBOM()
+ {
+ return $this->useBOM;
+ }
+
+ /**
+ * Set whether BOM should be used.
+ *
+ * @param bool $pValue Use UTF-8 byte-order mark? Defaults to false
+ *
+ * @return $this
+ */
+ public function setUseBOM($pValue)
+ {
+ $this->useBOM = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get whether a separator line should be included.
+ *
+ * @return bool
+ */
+ public function getIncludeSeparatorLine()
+ {
+ return $this->includeSeparatorLine;
+ }
+
+ /**
+ * Set whether a separator line should be included as the first line of the file.
+ *
+ * @param bool $pValue Use separator line? Defaults to false
+ *
+ * @return $this
+ */
+ public function setIncludeSeparatorLine($pValue)
+ {
+ $this->includeSeparatorLine = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get whether the file should be saved with full Excel Compatibility.
+ *
+ * @return bool
+ */
+ public function getExcelCompatibility()
+ {
+ return $this->excelCompatibility;
+ }
+
+ /**
+ * Set whether the file should be saved with full Excel Compatibility.
+ *
+ * @param bool $pValue Set the file to be written as a fully Excel compatible csv file
+ * Note that this overrides other settings such as useBOM, enclosure and delimiter
+ *
+ * @return $this
+ */
+ public function setExcelCompatibility($pValue)
+ {
+ $this->excelCompatibility = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get sheet index.
+ *
+ * @return int
+ */
+ public function getSheetIndex()
+ {
+ return $this->sheetIndex;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param int $pValue Sheet index
+ *
+ * @return $this
+ */
+ public function setSheetIndex($pValue)
+ {
+ $this->sheetIndex = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Write line to CSV file.
+ *
+ * @param resource $pFileHandle PHP filehandle
+ * @param array $pValues Array containing values in a row
+ */
+ private function writeLine($pFileHandle, array $pValues): void
+ {
+ // No leading delimiter
+ $writeDelimiter = false;
+
+ // Build the line
+ $line = '';
+
+ foreach ($pValues as $element) {
+ // Escape enclosures
+ $element = str_replace($this->enclosure, $this->enclosure . $this->enclosure, $element);
+
+ // Add delimiter
+ if ($writeDelimiter) {
+ $line .= $this->delimiter;
+ } else {
+ $writeDelimiter = true;
+ }
+
+ // Add enclosed string
+ $line .= $this->enclosure . $element . $this->enclosure;
+ }
+
+ // Add line ending
+ $line .= $this->lineEnding;
+
+ // Write to file
+ fwrite($pFileHandle, $line);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php
new file mode 100755
index 0000000..92e6f5f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class Exception extends PhpSpreadsheetException
+{
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php
new file mode 100755
index 0000000..c9f2d7c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php
@@ -0,0 +1,1846 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Chart\Chart;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\RichText\Run;
+use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing;
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Shared\Font as SharedFont;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Borders;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class Html extends BaseWriter
+{
+ /**
+ * Spreadsheet object.
+ *
+ * @var Spreadsheet
+ */
+ protected $spreadsheet;
+
+ /**
+ * Sheet index to write.
+ *
+ * @var int
+ */
+ private $sheetIndex = 0;
+
+ /**
+ * Images root.
+ *
+ * @var string
+ */
+ private $imagesRoot = '';
+
+ /**
+ * embed images, or link to images.
+ *
+ * @var bool
+ */
+ private $embedImages = false;
+
+ /**
+ * Use inline CSS?
+ *
+ * @var bool
+ */
+ private $useInlineCss = false;
+
+ /**
+ * Use embedded CSS?
+ *
+ * @var bool
+ */
+ private $useEmbeddedCSS = true;
+
+ /**
+ * Array of CSS styles.
+ *
+ * @var array
+ */
+ private $cssStyles;
+
+ /**
+ * Array of column widths in points.
+ *
+ * @var array
+ */
+ private $columnWidths;
+
+ /**
+ * Default font.
+ *
+ * @var Font
+ */
+ private $defaultFont;
+
+ /**
+ * Flag whether spans have been calculated.
+ *
+ * @var bool
+ */
+ private $spansAreCalculated = false;
+
+ /**
+ * Excel cells that should not be written as HTML cells.
+ *
+ * @var array
+ */
+ private $isSpannedCell = [];
+
+ /**
+ * Excel cells that are upper-left corner in a cell merge.
+ *
+ * @var array
+ */
+ private $isBaseCell = [];
+
+ /**
+ * Excel rows that should not be written as HTML rows.
+ *
+ * @var array
+ */
+ private $isSpannedRow = [];
+
+ /**
+ * Is the current writer creating PDF?
+ *
+ * @var bool
+ */
+ protected $isPdf = false;
+
+ /**
+ * Generate the Navigation block.
+ *
+ * @var bool
+ */
+ private $generateSheetNavigationBlock = true;
+
+ /**
+ * Create a new HTML.
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ $this->spreadsheet = $spreadsheet;
+ $this->defaultFont = $this->spreadsheet->getDefaultStyle()->getFont();
+ }
+
+ /**
+ * Save Spreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ // Open file
+ $this->openFileHandle($pFilename);
+
+ // Write html
+ fwrite($this->fileHandle, $this->generateHTMLAll());
+
+ // Close file
+ $this->maybeCloseFileHandle();
+ }
+
+ /**
+ * Save Spreadsheet as html to variable.
+ *
+ * @return string
+ */
+ public function generateHtmlAll()
+ {
+ // garbage collect
+ $this->spreadsheet->garbageCollect();
+
+ $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
+ $saveArrayReturnType = Calculation::getArrayReturnType();
+ Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE);
+
+ // Build CSS
+ $this->buildCSS(!$this->useInlineCss);
+
+ $html = '';
+
+ // Write headers
+ $html .= $this->generateHTMLHeader(!$this->useInlineCss);
+
+ // Write navigation (tabs)
+ if ((!$this->isPdf) && ($this->generateSheetNavigationBlock)) {
+ $html .= $this->generateNavigation();
+ }
+
+ // Write data
+ $html .= $this->generateSheetData();
+
+ // Write footer
+ $html .= $this->generateHTMLFooter();
+
+ Calculation::setArrayReturnType($saveArrayReturnType);
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
+
+ return $html;
+ }
+
+ const VALIGN_ARR = [
+ Alignment::VERTICAL_BOTTOM => 'bottom',
+ Alignment::VERTICAL_TOP => 'top',
+ Alignment::VERTICAL_CENTER => 'middle',
+ Alignment::VERTICAL_JUSTIFY => 'middle',
+ ];
+
+ /**
+ * Map VAlign.
+ *
+ * @param string $vAlign Vertical alignment
+ *
+ * @return string
+ */
+ private function mapVAlign($vAlign)
+ {
+ return array_key_exists($vAlign, self::VALIGN_ARR) ? self::VALIGN_ARR[$vAlign] : 'baseline';
+ }
+
+ const HALIGN_ARR = [
+ Alignment::HORIZONTAL_LEFT => 'left',
+ Alignment::HORIZONTAL_RIGHT => 'right',
+ Alignment::HORIZONTAL_CENTER => 'center',
+ Alignment::HORIZONTAL_CENTER_CONTINUOUS => 'center',
+ Alignment::HORIZONTAL_JUSTIFY => 'justify',
+ ];
+
+ /**
+ * Map HAlign.
+ *
+ * @param string $hAlign Horizontal alignment
+ *
+ * @return string
+ */
+ private function mapHAlign($hAlign)
+ {
+ return array_key_exists($hAlign, self::HALIGN_ARR) ? self::HALIGN_ARR[$hAlign] : '';
+ }
+
+ const BORDER_ARR = [
+ Border::BORDER_NONE => 'none',
+ Border::BORDER_DASHDOT => '1px dashed',
+ Border::BORDER_DASHDOTDOT => '1px dotted',
+ Border::BORDER_DASHED => '1px dashed',
+ Border::BORDER_DOTTED => '1px dotted',
+ Border::BORDER_DOUBLE => '3px double',
+ Border::BORDER_HAIR => '1px solid',
+ Border::BORDER_MEDIUM => '2px solid',
+ Border::BORDER_MEDIUMDASHDOT => '2px dashed',
+ Border::BORDER_MEDIUMDASHDOTDOT => '2px dotted',
+ Border::BORDER_SLANTDASHDOT => '2px dashed',
+ Border::BORDER_THICK => '3px solid',
+ ];
+
+ /**
+ * Map border style.
+ *
+ * @param int $borderStyle Sheet index
+ *
+ * @return string
+ */
+ private function mapBorderStyle($borderStyle)
+ {
+ return array_key_exists($borderStyle, self::BORDER_ARR) ? self::BORDER_ARR[$borderStyle] : '1px solid';
+ }
+
+ /**
+ * Get sheet index.
+ *
+ * @return int
+ */
+ public function getSheetIndex()
+ {
+ return $this->sheetIndex;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param int $pValue Sheet index
+ *
+ * @return $this
+ */
+ public function setSheetIndex($pValue)
+ {
+ $this->sheetIndex = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get sheet index.
+ *
+ * @return bool
+ */
+ public function getGenerateSheetNavigationBlock()
+ {
+ return $this->generateSheetNavigationBlock;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param bool $pValue Flag indicating whether the sheet navigation block should be generated or not
+ *
+ * @return $this
+ */
+ public function setGenerateSheetNavigationBlock($pValue)
+ {
+ $this->generateSheetNavigationBlock = (bool) $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Write all sheets (resets sheetIndex to NULL).
+ *
+ * @return $this
+ */
+ public function writeAllSheets()
+ {
+ $this->sheetIndex = null;
+
+ return $this;
+ }
+
+ private static function generateMeta($val, $desc)
+ {
+ return $val ? (' <meta name="' . $desc . '" content="' . htmlspecialchars($val) . '" />' . PHP_EOL) : '';
+ }
+
+ /**
+ * Generate HTML header.
+ *
+ * @param bool $pIncludeStyles Include styles?
+ *
+ * @return string
+ */
+ public function generateHTMLHeader($pIncludeStyles = false)
+ {
+ // Construct HTML
+ $properties = $this->spreadsheet->getProperties();
+ $html = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . PHP_EOL;
+ $html .= '<html xmlns="http://www.w3.org/1999/xhtml">' . PHP_EOL;
+ $html .= ' <head>' . PHP_EOL;
+ $html .= ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . PHP_EOL;
+ $html .= ' <meta name="generator" content="PhpSpreadsheet, https://github.com/PHPOffice/PhpSpreadsheet" />' . PHP_EOL;
+ $html .= ' <title>' . htmlspecialchars($properties->getTitle()) . '</title>' . PHP_EOL;
+ $html .= self::generateMeta($properties->getCreator(), 'author');
+ $html .= self::generateMeta($properties->getTitle(), 'title');
+ $html .= self::generateMeta($properties->getDescription(), 'description');
+ $html .= self::generateMeta($properties->getSubject(), 'subject');
+ $html .= self::generateMeta($properties->getKeywords(), 'keywords');
+ $html .= self::generateMeta($properties->getCategory(), 'category');
+ $html .= self::generateMeta($properties->getCompany(), 'company');
+ $html .= self::generateMeta($properties->getManager(), 'manager');
+
+ $html .= $pIncludeStyles ? $this->generateStyles(true) : $this->generatePageDeclarations(true);
+
+ $html .= ' </head>' . PHP_EOL;
+ $html .= '' . PHP_EOL;
+ $html .= ' <body>' . PHP_EOL;
+
+ return $html;
+ }
+
+ private function generateSheetPrep()
+ {
+ // Ensure that Spans have been calculated?
+ $this->calculateSpans();
+
+ // Fetch sheets
+ if ($this->sheetIndex === null) {
+ $sheets = $this->spreadsheet->getAllSheets();
+ } else {
+ $sheets = [$this->spreadsheet->getSheet($this->sheetIndex)];
+ }
+
+ return $sheets;
+ }
+
+ private function generateSheetStarts($sheet, $rowMin)
+ {
+ // calculate start of <tbody>, <thead>
+ $tbodyStart = $rowMin;
+ $theadStart = $theadEnd = 0; // default: no <thead> no </thead>
+ if ($sheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
+ $rowsToRepeatAtTop = $sheet->getPageSetup()->getRowsToRepeatAtTop();
+
+ // we can only support repeating rows that start at top row
+ if ($rowsToRepeatAtTop[0] == 1) {
+ $theadStart = $rowsToRepeatAtTop[0];
+ $theadEnd = $rowsToRepeatAtTop[1];
+ $tbodyStart = $rowsToRepeatAtTop[1] + 1;
+ }
+ }
+
+ return [$theadStart, $theadEnd, $tbodyStart];
+ }
+
+ private function generateSheetTags($row, $theadStart, $theadEnd, $tbodyStart)
+ {
+ // <thead> ?
+ $startTag = ($row == $theadStart) ? (' <thead>' . PHP_EOL) : '';
+ if (!$startTag) {
+ $startTag = ($row == $tbodyStart) ? (' <tbody>' . PHP_EOL) : '';
+ }
+ $endTag = ($row == $theadEnd) ? (' </thead>' . PHP_EOL) : '';
+ $cellType = ($row >= $tbodyStart) ? 'td' : 'th';
+
+ return [$cellType, $startTag, $endTag];
+ }
+
+ /**
+ * Generate sheet data.
+ *
+ * @return string
+ */
+ public function generateSheetData()
+ {
+ $sheets = $this->generateSheetPrep();
+
+ // Construct HTML
+ $html = '';
+
+ // Loop all sheets
+ $sheetId = 0;
+ foreach ($sheets as $sheet) {
+ // Write table header
+ $html .= $this->generateTableHeader($sheet);
+ // Get worksheet dimension
+ $dimension = explode(':', $sheet->calculateWorksheetDimension());
+ $dimension[0] = Coordinate::coordinateFromString($dimension[0]);
+ $dimension[0][0] = Coordinate::columnIndexFromString($dimension[0][0]);
+ $dimension[1] = Coordinate::coordinateFromString($dimension[1]);
+ $dimension[1][0] = Coordinate::columnIndexFromString($dimension[1][0]);
+
+ // row min,max
+ $rowMin = $dimension[0][1];
+ $rowMax = $dimension[1][1];
+
+ [$theadStart, $theadEnd, $tbodyStart] = $this->generateSheetStarts($sheet, $rowMin);
+
+ // Loop through cells
+ $row = $rowMin - 1;
+ while ($row++ < $rowMax) {
+ [$cellType, $startTag, $endTag] = $this->generateSheetTags($row, $theadStart, $theadEnd, $tbodyStart);
+ $html .= $startTag;
+
+ // Write row if there are HTML table cells in it
+ if (!isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) {
+ // Start a new rowData
+ $rowData = [];
+ // Loop through columns
+ $column = $dimension[0][0];
+ while ($column <= $dimension[1][0]) {
+ // Cell exists?
+ if ($sheet->cellExistsByColumnAndRow($column, $row)) {
+ $rowData[$column] = Coordinate::stringFromColumnIndex($column) . $row;
+ } else {
+ $rowData[$column] = '';
+ }
+ ++$column;
+ }
+ $html .= $this->generateRow($sheet, $rowData, $row - 1, $cellType);
+ }
+
+ $html .= $endTag;
+ }
+ $html .= $this->extendRowsForChartsAndImages($sheet, $row);
+
+ // Write table footer
+ $html .= $this->generateTableFooter();
+ // Writing PDF?
+ if ($this->isPdf && $this->useInlineCss) {
+ if ($this->sheetIndex === null && $sheetId + 1 < $this->spreadsheet->getSheetCount()) {
+ $html .= '<div style="page-break-before:always" ></div>';
+ }
+ }
+
+ // Next sheet
+ ++$sheetId;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Generate sheet tabs.
+ *
+ * @return string
+ */
+ public function generateNavigation()
+ {
+ // Fetch sheets
+ $sheets = [];
+ if ($this->sheetIndex === null) {
+ $sheets = $this->spreadsheet->getAllSheets();
+ } else {
+ $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
+ }
+
+ // Construct HTML
+ $html = '';
+
+ // Only if there are more than 1 sheets
+ if (count($sheets) > 1) {
+ // Loop all sheets
+ $sheetId = 0;
+
+ $html .= '<ul class="navigation">' . PHP_EOL;
+
+ foreach ($sheets as $sheet) {
+ $html .= ' <li class="sheet' . $sheetId . '"><a href="#sheet' . $sheetId . '">' . $sheet->getTitle() . '</a></li>' . PHP_EOL;
+ ++$sheetId;
+ }
+
+ $html .= '</ul>' . PHP_EOL;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Extend Row if chart is placed after nominal end of row.
+ * This code should be exercised by sample:
+ * Chart/32_Chart_read_write_PDF.php.
+ * However, that test is suppressed due to out-of-date
+ * Jpgraph code issuing warnings. So, don't measure
+ * code coverage for this function till that is fixed.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param int $row Row to check for charts
+ *
+ * @return array
+ *
+ * @codeCoverageIgnore
+ */
+ private function extendRowsForCharts(Worksheet $pSheet, $row)
+ {
+ $rowMax = $row;
+ $colMax = 'A';
+ $anyfound = false;
+ if ($this->includeCharts) {
+ foreach ($pSheet->getChartCollection() as $chart) {
+ if ($chart instanceof Chart) {
+ $anyfound = true;
+ $chartCoordinates = $chart->getTopLeftPosition();
+ $chartTL = Coordinate::coordinateFromString($chartCoordinates['cell']);
+ $chartCol = Coordinate::columnIndexFromString($chartTL[0]);
+ if ($chartTL[1] > $rowMax) {
+ $rowMax = $chartTL[1];
+ if ($chartCol > Coordinate::columnIndexFromString($colMax)) {
+ $colMax = $chartTL[0];
+ }
+ }
+ }
+ }
+ }
+
+ return [$rowMax, $colMax, $anyfound];
+ }
+
+ private function extendRowsForChartsAndImages(Worksheet $pSheet, $row)
+ {
+ [$rowMax, $colMax, $anyfound] = $this->extendRowsForCharts($pSheet, $row);
+
+ foreach ($pSheet->getDrawingCollection() as $drawing) {
+ $anyfound = true;
+ $imageTL = Coordinate::coordinateFromString($drawing->getCoordinates());
+ $imageCol = Coordinate::columnIndexFromString($imageTL[0]);
+ if ($imageTL[1] > $rowMax) {
+ $rowMax = $imageTL[1];
+ if ($imageCol > Coordinate::columnIndexFromString($colMax)) {
+ $colMax = $imageTL[0];
+ }
+ }
+ }
+
+ // Don't extend rows if not needed
+ if ($row === $rowMax || !$anyfound) {
+ return '';
+ }
+
+ $html = '';
+ ++$colMax;
+ ++$row;
+ while ($row <= $rowMax) {
+ $html .= '<tr>';
+ for ($col = 'A'; $col != $colMax; ++$col) {
+ $htmlx = $this->writeImageInCell($pSheet, $col . $row);
+ $htmlx .= $this->includeCharts ? $this->writeChartInCell($pSheet, $col . $row) : '';
+ if ($htmlx) {
+ $html .= "<td class='style0' style='position: relative;'>$htmlx</td>";
+ } else {
+ $html .= "<td class='style0'></td>";
+ }
+ }
+ ++$row;
+ $html .= '</tr>' . PHP_EOL;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Convert Windows file name to file protocol URL.
+ *
+ * @param string $filename file name on local system
+ *
+ * @return string
+ */
+ public static function winFileToUrl($filename)
+ {
+ // Windows filename
+ if (substr($filename, 1, 2) === ':\\') {
+ $filename = 'file:///' . str_replace('\\', '/', $filename);
+ }
+
+ return $filename;
+ }
+
+ /**
+ * Generate image tag in cell.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param string $coordinates Cell coordinates
+ *
+ * @return string
+ */
+ private function writeImageInCell(Worksheet $pSheet, $coordinates)
+ {
+ // Construct HTML
+ $html = '';
+
+ // Write images
+ foreach ($pSheet->getDrawingCollection() as $drawing) {
+ if ($drawing->getCoordinates() != $coordinates) {
+ continue;
+ }
+ $filedesc = $drawing->getDescription();
+ $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded image';
+ if ($drawing instanceof Drawing) {
+ $filename = $drawing->getPath();
+
+ // Strip off eventual '.'
+ $filename = preg_replace('/^[.]/', '', $filename);
+
+ // Prepend images root
+ $filename = $this->getImagesRoot() . $filename;
+
+ // Strip off eventual '.' if followed by non-/
+ $filename = preg_replace('@^[.]([^/])@', '$1', $filename);
+
+ // Convert UTF8 data to PCDATA
+ $filename = htmlspecialchars($filename);
+
+ $html .= PHP_EOL;
+ $imageData = self::winFileToUrl($filename);
+
+ if ($this->embedImages && !$this->isPdf) {
+ $picture = @file_get_contents($filename);
+ if ($picture !== false) {
+ $imageDetails = getimagesize($filename);
+ // base64 encode the binary data
+ $base64 = base64_encode($picture);
+ $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64;
+ }
+ }
+
+ $html .= '<img style="position: absolute; z-index: 1; left: ' .
+ $drawing->getOffsetX() . 'px; top: ' . $drawing->getOffsetY() . 'px; width: ' .
+ $drawing->getWidth() . 'px; height: ' . $drawing->getHeight() . 'px;" src="' .
+ $imageData . '" alt="' . $filedesc . '" />';
+ } elseif ($drawing instanceof MemoryDrawing) {
+ ob_start(); // Let's start output buffering.
+ imagepng($drawing->getImageResource()); // This will normally output the image, but because of ob_start(), it won't.
+ $contents = ob_get_contents(); // Instead, output above is saved to $contents
+ ob_end_clean(); // End the output buffer.
+
+ $dataUri = 'data:image/jpeg;base64,' . base64_encode($contents);
+
+ // Because of the nature of tables, width is more important than height.
+ // max-width: 100% ensures that image doesnt overflow containing cell
+ // width: X sets width of supplied image.
+ // As a result, images bigger than cell will be contained and images smaller will not get stretched
+ $html .= '<img alt="' . $filedesc . '" src="' . $dataUri . '" style="max-width:100%;width:' . $drawing->getWidth() . 'px;" />';
+ }
+ }
+
+ return $html;
+ }
+
+ /**
+ * Generate chart tag in cell.
+ * This code should be exercised by sample:
+ * Chart/32_Chart_read_write_PDF.php.
+ * However, that test is suppressed due to out-of-date
+ * Jpgraph code issuing warnings. So, don't measure
+ * code coverage for this function till that is fixed.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param string $coordinates Cell coordinates
+ *
+ * @return string
+ *
+ * @codeCoverageIgnore
+ */
+ private function writeChartInCell(Worksheet $pSheet, $coordinates)
+ {
+ // Construct HTML
+ $html = '';
+
+ // Write charts
+ foreach ($pSheet->getChartCollection() as $chart) {
+ if ($chart instanceof Chart) {
+ $chartCoordinates = $chart->getTopLeftPosition();
+ if ($chartCoordinates['cell'] == $coordinates) {
+ $chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png';
+ if (!$chart->render($chartFileName)) {
+ return;
+ }
+
+ $html .= PHP_EOL;
+ $imageDetails = getimagesize($chartFileName);
+ $filedesc = $chart->getTitle();
+ $filedesc = $filedesc ? self::getChartCaption($filedesc->getCaption()) : '';
+ $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded chart';
+ if ($fp = fopen($chartFileName, 'rb', 0)) {
+ $picture = fread($fp, filesize($chartFileName));
+ fclose($fp);
+ // base64 encode the binary data
+ $base64 = base64_encode($picture);
+ $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64;
+
+ $html .= '<img style="position: absolute; z-index: 1; left: ' . $chartCoordinates['xOffset'] . 'px; top: ' . $chartCoordinates['yOffset'] . 'px; width: ' . $imageDetails[0] . 'px; height: ' . $imageDetails[1] . 'px;" src="' . $imageData . '" alt="' . $filedesc . '" />' . PHP_EOL;
+
+ unlink($chartFileName);
+ }
+ }
+ }
+ }
+
+ // Return
+ return $html;
+ }
+
+ /**
+ * Extend Row if chart is placed after nominal end of row.
+ * This code should be exercised by sample:
+ * Chart/32_Chart_read_write_PDF.php.
+ * However, that test is suppressed due to out-of-date
+ * Jpgraph code issuing warnings. So, don't measure
+ * code coverage for this function till that is fixed.
+ * Caption is described in documentation as fixed,
+ * but in 32_Chart it is somehow an array of RichText.
+ *
+ * @param mixed $cap
+ *
+ * @return string
+ *
+ * @codeCoverageIgnore
+ */
+ private static function getChartCaption($cap)
+ {
+ return is_array($cap) ? implode(' ', $cap) : $cap;
+ }
+
+ /**
+ * Generate CSS styles.
+ *
+ * @param bool $generateSurroundingHTML Generate surrounding HTML tags? (<style> and </style>)
+ *
+ * @return string
+ */
+ public function generateStyles($generateSurroundingHTML = true)
+ {
+ // Build CSS
+ $css = $this->buildCSS($generateSurroundingHTML);
+
+ // Construct HTML
+ $html = '';
+
+ // Start styles
+ if ($generateSurroundingHTML) {
+ $html .= ' <style type="text/css">' . PHP_EOL;
+ $html .= (array_key_exists('html', $css)) ? (' html { ' . $this->assembleCSS($css['html']) . ' }' . PHP_EOL) : '';
+ }
+
+ // Write all other styles
+ foreach ($css as $styleName => $styleDefinition) {
+ if ($styleName != 'html') {
+ $html .= ' ' . $styleName . ' { ' . $this->assembleCSS($styleDefinition) . ' }' . PHP_EOL;
+ }
+ }
+ $html .= $this->generatePageDeclarations(false);
+
+ // End styles
+ if ($generateSurroundingHTML) {
+ $html .= ' </style>' . PHP_EOL;
+ }
+
+ // Return
+ return $html;
+ }
+
+ private function buildCssRowHeights(Worksheet $sheet, array &$css, int $sheetIndex): void
+ {
+ // Calculate row heights
+ foreach ($sheet->getRowDimensions() as $rowDimension) {
+ $row = $rowDimension->getRowIndex() - 1;
+
+ // table.sheetN tr.rowYYYYYY { }
+ $css['table.sheet' . $sheetIndex . ' tr.row' . $row] = [];
+
+ if ($rowDimension->getRowHeight() != -1) {
+ $pt_height = $rowDimension->getRowHeight();
+ $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['height'] = $pt_height . 'pt';
+ }
+ if ($rowDimension->getVisible() === false) {
+ $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['display'] = 'none';
+ $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['visibility'] = 'hidden';
+ }
+ }
+ }
+
+ private function buildCssPerSheet(Worksheet $sheet, array &$css): void
+ {
+ // Calculate hash code
+ $sheetIndex = $sheet->getParent()->getIndex($sheet);
+
+ // Build styles
+ // Calculate column widths
+ $sheet->calculateColumnWidths();
+
+ // col elements, initialize
+ $highestColumnIndex = Coordinate::columnIndexFromString($sheet->getHighestColumn()) - 1;
+ $column = -1;
+ while ($column++ < $highestColumnIndex) {
+ $this->columnWidths[$sheetIndex][$column] = 42; // approximation
+ $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = '42pt';
+ }
+
+ // col elements, loop through columnDimensions and set width
+ foreach ($sheet->getColumnDimensions() as $columnDimension) {
+ $column = Coordinate::columnIndexFromString($columnDimension->getColumnIndex()) - 1;
+ $width = SharedDrawing::cellDimensionToPixels($columnDimension->getWidth(), $this->defaultFont);
+ $width = SharedDrawing::pixelsToPoints($width);
+ if ($columnDimension->getVisible() === false) {
+ $css['table.sheet' . $sheetIndex . ' .column' . $column]['display'] = 'none';
+ }
+ if ($width >= 0) {
+ $this->columnWidths[$sheetIndex][$column] = $width;
+ $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = $width . 'pt';
+ }
+ }
+
+ // Default row height
+ $rowDimension = $sheet->getDefaultRowDimension();
+
+ // table.sheetN tr { }
+ $css['table.sheet' . $sheetIndex . ' tr'] = [];
+
+ if ($rowDimension->getRowHeight() == -1) {
+ $pt_height = SharedFont::getDefaultRowHeightByFont($this->spreadsheet->getDefaultStyle()->getFont());
+ } else {
+ $pt_height = $rowDimension->getRowHeight();
+ }
+ $css['table.sheet' . $sheetIndex . ' tr']['height'] = $pt_height . 'pt';
+ if ($rowDimension->getVisible() === false) {
+ $css['table.sheet' . $sheetIndex . ' tr']['display'] = 'none';
+ $css['table.sheet' . $sheetIndex . ' tr']['visibility'] = 'hidden';
+ }
+
+ $this->buildCssRowHeights($sheet, $css, $sheetIndex);
+ }
+
+ /**
+ * Build CSS styles.
+ *
+ * @param bool $generateSurroundingHTML Generate surrounding HTML style? (html { })
+ *
+ * @return array
+ */
+ public function buildCSS($generateSurroundingHTML = true)
+ {
+ // Cached?
+ if ($this->cssStyles !== null) {
+ return $this->cssStyles;
+ }
+
+ // Ensure that spans have been calculated
+ $this->calculateSpans();
+
+ // Construct CSS
+ $css = [];
+
+ // Start styles
+ if ($generateSurroundingHTML) {
+ // html { }
+ $css['html']['font-family'] = 'Calibri, Arial, Helvetica, sans-serif';
+ $css['html']['font-size'] = '11pt';
+ $css['html']['background-color'] = 'white';
+ }
+
+ // CSS for comments as found in LibreOffice
+ $css['a.comment-indicator:hover + div.comment'] = [
+ 'background' => '#ffd',
+ 'position' => 'absolute',
+ 'display' => 'block',
+ 'border' => '1px solid black',
+ 'padding' => '0.5em',
+ ];
+
+ $css['a.comment-indicator'] = [
+ 'background' => 'red',
+ 'display' => 'inline-block',
+ 'border' => '1px solid black',
+ 'width' => '0.5em',
+ 'height' => '0.5em',
+ ];
+
+ $css['div.comment']['display'] = 'none';
+
+ // table { }
+ $css['table']['border-collapse'] = 'collapse';
+
+ // .b {}
+ $css['.b']['text-align'] = 'center'; // BOOL
+
+ // .e {}
+ $css['.e']['text-align'] = 'center'; // ERROR
+
+ // .f {}
+ $css['.f']['text-align'] = 'right'; // FORMULA
+
+ // .inlineStr {}
+ $css['.inlineStr']['text-align'] = 'left'; // INLINE
+
+ // .n {}
+ $css['.n']['text-align'] = 'right'; // NUMERIC
+
+ // .s {}
+ $css['.s']['text-align'] = 'left'; // STRING
+
+ // Calculate cell style hashes
+ foreach ($this->spreadsheet->getCellXfCollection() as $index => $style) {
+ $css['td.style' . $index] = $this->createCSSStyle($style);
+ $css['th.style' . $index] = $this->createCSSStyle($style);
+ }
+
+ // Fetch sheets
+ $sheets = [];
+ if ($this->sheetIndex === null) {
+ $sheets = $this->spreadsheet->getAllSheets();
+ } else {
+ $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
+ }
+
+ // Build styles per sheet
+ foreach ($sheets as $sheet) {
+ $this->buildCssPerSheet($sheet, $css);
+ }
+
+ // Cache
+ if ($this->cssStyles === null) {
+ $this->cssStyles = $css;
+ }
+
+ // Return
+ return $css;
+ }
+
+ /**
+ * Create CSS style.
+ *
+ * @return array
+ */
+ private function createCSSStyle(Style $pStyle)
+ {
+ // Create CSS
+ return array_merge(
+ $this->createCSSStyleAlignment($pStyle->getAlignment()),
+ $this->createCSSStyleBorders($pStyle->getBorders()),
+ $this->createCSSStyleFont($pStyle->getFont()),
+ $this->createCSSStyleFill($pStyle->getFill())
+ );
+ }
+
+ /**
+ * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Alignment).
+ *
+ * @param Alignment $pStyle \PhpOffice\PhpSpreadsheet\Style\Alignment
+ *
+ * @return array
+ */
+ private function createCSSStyleAlignment(Alignment $pStyle)
+ {
+ // Construct CSS
+ $css = [];
+
+ // Create CSS
+ $css['vertical-align'] = $this->mapVAlign($pStyle->getVertical());
+ $textAlign = $this->mapHAlign($pStyle->getHorizontal());
+ if ($textAlign) {
+ $css['text-align'] = $textAlign;
+ if (in_array($textAlign, ['left', 'right'])) {
+ $css['padding-' . $textAlign] = (string) ((int) $pStyle->getIndent() * 9) . 'px';
+ }
+ }
+
+ return $css;
+ }
+
+ /**
+ * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Font).
+ *
+ * @return array
+ */
+ private function createCSSStyleFont(Font $pStyle)
+ {
+ // Construct CSS
+ $css = [];
+
+ // Create CSS
+ if ($pStyle->getBold()) {
+ $css['font-weight'] = 'bold';
+ }
+ if ($pStyle->getUnderline() != Font::UNDERLINE_NONE && $pStyle->getStrikethrough()) {
+ $css['text-decoration'] = 'underline line-through';
+ } elseif ($pStyle->getUnderline() != Font::UNDERLINE_NONE) {
+ $css['text-decoration'] = 'underline';
+ } elseif ($pStyle->getStrikethrough()) {
+ $css['text-decoration'] = 'line-through';
+ }
+ if ($pStyle->getItalic()) {
+ $css['font-style'] = 'italic';
+ }
+
+ $css['color'] = '#' . $pStyle->getColor()->getRGB();
+ $css['font-family'] = '\'' . $pStyle->getName() . '\'';
+ $css['font-size'] = $pStyle->getSize() . 'pt';
+
+ return $css;
+ }
+
+ /**
+ * Create CSS style (Borders).
+ *
+ * @param Borders $pStyle Borders
+ *
+ * @return array
+ */
+ private function createCSSStyleBorders(Borders $pStyle)
+ {
+ // Construct CSS
+ $css = [];
+
+ // Create CSS
+ $css['border-bottom'] = $this->createCSSStyleBorder($pStyle->getBottom());
+ $css['border-top'] = $this->createCSSStyleBorder($pStyle->getTop());
+ $css['border-left'] = $this->createCSSStyleBorder($pStyle->getLeft());
+ $css['border-right'] = $this->createCSSStyleBorder($pStyle->getRight());
+
+ return $css;
+ }
+
+ /**
+ * Create CSS style (Border).
+ *
+ * @param Border $pStyle Border
+ *
+ * @return string
+ */
+ private function createCSSStyleBorder(Border $pStyle)
+ {
+ // Create CSS - add !important to non-none border styles for merged cells
+ $borderStyle = $this->mapBorderStyle($pStyle->getBorderStyle());
+
+ return $borderStyle . ' #' . $pStyle->getColor()->getRGB() . (($borderStyle == 'none') ? '' : ' !important');
+ }
+
+ /**
+ * Create CSS style (Fill).
+ *
+ * @param Fill $pStyle Fill
+ *
+ * @return array
+ */
+ private function createCSSStyleFill(Fill $pStyle)
+ {
+ // Construct HTML
+ $css = [];
+
+ // Create CSS
+ $value = $pStyle->getFillType() == Fill::FILL_NONE ?
+ 'white' : '#' . $pStyle->getStartColor()->getRGB();
+ $css['background-color'] = $value;
+
+ return $css;
+ }
+
+ /**
+ * Generate HTML footer.
+ */
+ public function generateHTMLFooter()
+ {
+ // Construct HTML
+ $html = '';
+ $html .= ' </body>' . PHP_EOL;
+ $html .= '</html>' . PHP_EOL;
+
+ return $html;
+ }
+
+ private function generateTableTagInline($pSheet, $id)
+ {
+ $style = isset($this->cssStyles['table']) ?
+ $this->assembleCSS($this->cssStyles['table']) : '';
+
+ $prntgrid = $pSheet->getPrintGridlines();
+ $viewgrid = $this->isPdf ? $prntgrid : $pSheet->getShowGridlines();
+ if ($viewgrid && $prntgrid) {
+ $html = " <table border='1' cellpadding='1' $id cellspacing='1' style='$style' class='gridlines gridlinesp'>" . PHP_EOL;
+ } elseif ($viewgrid) {
+ $html = " <table border='0' cellpadding='0' $id cellspacing='0' style='$style' class='gridlines'>" . PHP_EOL;
+ } elseif ($prntgrid) {
+ $html = " <table border='0' cellpadding='0' $id cellspacing='0' style='$style' class='gridlinesp'>" . PHP_EOL;
+ } else {
+ $html = " <table border='0' cellpadding='1' $id cellspacing='0' style='$style'>" . PHP_EOL;
+ }
+
+ return $html;
+ }
+
+ private function generateTableTag($pSheet, $id, &$html, $sheetIndex): void
+ {
+ if (!$this->useInlineCss) {
+ $gridlines = $pSheet->getShowGridlines() ? ' gridlines' : '';
+ $gridlinesp = $pSheet->getPrintGridlines() ? ' gridlinesp' : '';
+ $html .= " <table border='0' cellpadding='0' cellspacing='0' $id class='sheet$sheetIndex$gridlines$gridlinesp'>" . PHP_EOL;
+ } else {
+ $html .= $this->generateTableTagInline($pSheet, $id);
+ }
+ }
+
+ /**
+ * Generate table header.
+ *
+ * @param Worksheet $pSheet The worksheet for the table we are writing
+ * @param bool $showid whether or not to add id to table tag
+ *
+ * @return string
+ */
+ private function generateTableHeader($pSheet, $showid = true)
+ {
+ $sheetIndex = $pSheet->getParent()->getIndex($pSheet);
+
+ // Construct HTML
+ $html = '';
+ $id = $showid ? "id='sheet$sheetIndex'" : '';
+ if ($showid) {
+ $html .= "<div style='page: page$sheetIndex'>\n";
+ //} elseif ($this->useInlineCss) {
+ // $html .= "<div style='page-break-before: always' ></div>\n";
+ } else {
+ $html .= "<div style='page: page$sheetIndex' class='scrpgbrk'>\n";
+ }
+
+ $this->generateTableTag($pSheet, $id, $html, $sheetIndex);
+
+ // Write <col> elements
+ $highestColumnIndex = Coordinate::columnIndexFromString($pSheet->getHighestColumn()) - 1;
+ $i = -1;
+ while ($i++ < $highestColumnIndex) {
+ if (!$this->useInlineCss) {
+ $html .= ' <col class="col' . $i . '" />' . PHP_EOL;
+ } else {
+ $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) ?
+ $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) : '';
+ $html .= ' <col style="' . $style . '" />' . PHP_EOL;
+ }
+ }
+
+ return $html;
+ }
+
+ /**
+ * Generate table footer.
+ */
+ private function generateTableFooter()
+ {
+ return ' </tbody></table>' . PHP_EOL . '</div>' . PHP_EOL;
+ }
+
+ /**
+ * Generate row start.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param int $sheetIndex Sheet index (0-based)
+ * @param int $pRow row number
+ *
+ * @return string
+ */
+ private function generateRowStart(Worksheet $pSheet, $sheetIndex, $pRow)
+ {
+ $html = '';
+ if (count($pSheet->getBreaks()) > 0) {
+ $breaks = $pSheet->getBreaks();
+
+ // check if a break is needed before this row
+ if (isset($breaks['A' . $pRow])) {
+ // close table: </table>
+ $html .= $this->generateTableFooter();
+ if ($this->isPdf && $this->useInlineCss) {
+ $html .= '<div style="page-break-before:always" />';
+ }
+
+ // open table again: <table> + <col> etc.
+ $html .= $this->generateTableHeader($pSheet, false);
+ $html .= '<tbody>' . PHP_EOL;
+ }
+ }
+
+ // Write row start
+ if (!$this->useInlineCss) {
+ $html .= ' <tr class="row' . $pRow . '">' . PHP_EOL;
+ } else {
+ $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow])
+ ? $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]) : '';
+
+ $html .= ' <tr style="' . $style . '">' . PHP_EOL;
+ }
+
+ return $html;
+ }
+
+ private function generateRowCellCss($pSheet, $cellAddress, $pRow, $colNum)
+ {
+ $cell = ($cellAddress > '') ? $pSheet->getCell($cellAddress) : '';
+ $coordinate = Coordinate::stringFromColumnIndex($colNum + 1) . ($pRow + 1);
+ if (!$this->useInlineCss) {
+ $cssClass = 'column' . $colNum;
+ } else {
+ $cssClass = [];
+ // The statements below do nothing.
+ // Commenting out the code rather than deleting it
+ // in case someone can figure out what their intent was.
+ //if ($cellType == 'th') {
+ // if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum])) {
+ // $this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum];
+ // }
+ //} else {
+ // if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum])) {
+ // $this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum];
+ // }
+ //}
+ // End of mystery statements.
+ }
+
+ return [$cell, $cssClass, $coordinate];
+ }
+
+ private function generateRowCellDataValueRich($cell, &$cellData): void
+ {
+ // Loop through rich text elements
+ $elements = $cell->getValue()->getRichTextElements();
+ foreach ($elements as $element) {
+ // Rich text start?
+ if ($element instanceof Run) {
+ $cellData .= '<span style="' . $this->assembleCSS($this->createCSSStyleFont($element->getFont())) . '">';
+
+ $cellEnd = '';
+ if ($element->getFont()->getSuperscript()) {
+ $cellData .= '<sup>';
+ $cellEnd = '</sup>';
+ } elseif ($element->getFont()->getSubscript()) {
+ $cellData .= '<sub>';
+ $cellEnd = '</sub>';
+ }
+
+ // Convert UTF8 data to PCDATA
+ $cellText = $element->getText();
+ $cellData .= htmlspecialchars($cellText);
+
+ $cellData .= $cellEnd;
+
+ $cellData .= '</span>';
+ } else {
+ // Convert UTF8 data to PCDATA
+ $cellText = $element->getText();
+ $cellData .= htmlspecialchars($cellText);
+ }
+ }
+ }
+
+ private function generateRowCellDataValue($pSheet, $cell, &$cellData): void
+ {
+ if ($cell->getValue() instanceof RichText) {
+ $this->generateRowCellDataValueRich($cell, $cellData);
+ } else {
+ $origData = $this->preCalculateFormulas ? $cell->getCalculatedValue() : $cell->getValue();
+ $cellData = NumberFormat::toFormattedString(
+ $origData,
+ $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(),
+ [$this, 'formatColor']
+ );
+ if ($cellData === $origData) {
+ $cellData = htmlspecialchars($cellData);
+ }
+ if ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperscript()) {
+ $cellData = '<sup>' . $cellData . '</sup>';
+ } elseif ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSubscript()) {
+ $cellData = '<sub>' . $cellData . '</sub>';
+ }
+ }
+ }
+
+ private function generateRowCellData($pSheet, $cell, &$cssClass, $cellType)
+ {
+ $cellData = ' ';
+ if ($cell instanceof Cell) {
+ $cellData = '';
+ // Don't know what this does, and no test cases.
+ //if ($cell->getParent() === null) {
+ // $cell->attach($pSheet);
+ //}
+ // Value
+ $this->generateRowCellDataValue($pSheet, $cell, $cellData);
+
+ // Converts the cell content so that spaces occuring at beginning of each new line are replaced by
+ // Example: " Hello\n to the world" is converted to " Hello\n to the world"
+ $cellData = preg_replace('/(?m)(?:^|\\G) /', ' ', $cellData);
+
+ // convert newline "\n" to '<br>'
+ $cellData = nl2br($cellData);
+
+ // Extend CSS class?
+ if (!$this->useInlineCss) {
+ $cssClass .= ' style' . $cell->getXfIndex();
+ $cssClass .= ' ' . $cell->getDataType();
+ } else {
+ if ($cellType == 'th') {
+ if (isset($this->cssStyles['th.style' . $cell->getXfIndex()])) {
+ $cssClass = array_merge($cssClass, $this->cssStyles['th.style' . $cell->getXfIndex()]);
+ }
+ } else {
+ if (isset($this->cssStyles['td.style' . $cell->getXfIndex()])) {
+ $cssClass = array_merge($cssClass, $this->cssStyles['td.style' . $cell->getXfIndex()]);
+ }
+ }
+
+ // General horizontal alignment: Actual horizontal alignment depends on dataType
+ $sharedStyle = $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex());
+ if ($sharedStyle->getAlignment()->getHorizontal() == Alignment::HORIZONTAL_GENERAL
+ && isset($this->cssStyles['.' . $cell->getDataType()]['text-align'])
+ ) {
+ $cssClass['text-align'] = $this->cssStyles['.' . $cell->getDataType()]['text-align'];
+ }
+ }
+ } else {
+ // Use default borders for empty cell
+ if (is_string($cssClass)) {
+ $cssClass .= ' style0';
+ }
+ }
+
+ return $cellData;
+ }
+
+ private function generateRowIncludeCharts($pSheet, $coordinate)
+ {
+ return $this->includeCharts ? $this->writeChartInCell($pSheet, $coordinate) : '';
+ }
+
+ private function generateRowSpans($html, $rowSpan, $colSpan)
+ {
+ $html .= ($colSpan > 1) ? (' colspan="' . $colSpan . '"') : '';
+ $html .= ($rowSpan > 1) ? (' rowspan="' . $rowSpan . '"') : '';
+
+ return $html;
+ }
+
+ private function generateRowWriteCell(&$html, $pSheet, $coordinate, $cellType, $cellData, $colSpan, $rowSpan, $cssClass, $colNum, $sheetIndex, $pRow): void
+ {
+ // Image?
+ $htmlx = $this->writeImageInCell($pSheet, $coordinate);
+ // Chart?
+ $htmlx .= $this->generateRowIncludeCharts($pSheet, $coordinate);
+ // Column start
+ $html .= ' <' . $cellType;
+ if (!$this->useInlineCss && !$this->isPdf) {
+ $html .= ' class="' . $cssClass . '"';
+ if ($htmlx) {
+ $html .= " style='position: relative;'";
+ }
+ } else {
+ //** Necessary redundant code for the sake of \PhpOffice\PhpSpreadsheet\Writer\Pdf **
+ // We must explicitly write the width of the <td> element because TCPDF
+ // does not recognize e.g. <col style="width:42pt">
+ if ($this->useInlineCss) {
+ $xcssClass = $cssClass;
+ } else {
+ $html .= ' class="' . $cssClass . '"';
+ $xcssClass = [];
+ }
+ $width = 0;
+ $i = $colNum - 1;
+ $e = $colNum + $colSpan - 1;
+ while ($i++ < $e) {
+ if (isset($this->columnWidths[$sheetIndex][$i])) {
+ $width += $this->columnWidths[$sheetIndex][$i];
+ }
+ }
+ $xcssClass['width'] = $width . 'pt';
+
+ // We must also explicitly write the height of the <td> element because TCPDF
+ // does not recognize e.g. <tr style="height:50pt">
+ if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'])) {
+ $height = $this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'];
+ $xcssClass['height'] = $height;
+ }
+ //** end of redundant code **
+
+ if ($htmlx) {
+ $xcssClass['position'] = 'relative';
+ }
+ $html .= ' style="' . $this->assembleCSS($xcssClass) . '"';
+ }
+ $html = $this->generateRowSpans($html, $rowSpan, $colSpan);
+
+ $html .= '>';
+ $html .= $htmlx;
+
+ $html .= $this->writeComment($pSheet, $coordinate);
+
+ // Cell data
+ $html .= $cellData;
+
+ // Column end
+ $html .= '</' . $cellType . '>' . PHP_EOL;
+ }
+
+ /**
+ * Generate row.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param array $pValues Array containing cells in a row
+ * @param int $pRow Row number (0-based)
+ * @param string $cellType eg: 'td'
+ *
+ * @return string
+ */
+ private function generateRow(Worksheet $pSheet, array $pValues, $pRow, $cellType)
+ {
+ // Sheet index
+ $sheetIndex = $pSheet->getParent()->getIndex($pSheet);
+ $html = $this->generateRowStart($pSheet, $sheetIndex, $pRow);
+
+ // Write cells
+ $colNum = 0;
+ foreach ($pValues as $cellAddress) {
+ [$cell, $cssClass, $coordinate] = $this->generateRowCellCss($pSheet, $cellAddress, $pRow, $colNum);
+
+ $colSpan = 1;
+ $rowSpan = 1;
+
+ // Cell Data
+ $cellData = $this->generateRowCellData($pSheet, $cell, $cssClass, $cellType);
+
+ // Hyperlink?
+ if ($pSheet->hyperlinkExists($coordinate) && !$pSheet->getHyperlink($coordinate)->isInternal()) {
+ $cellData = '<a href="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getUrl()) . '" title="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getTooltip()) . '">' . $cellData . '</a>';
+ }
+
+ // Should the cell be written or is it swallowed by a rowspan or colspan?
+ $writeCell = !(isset($this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum])
+ && $this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]);
+
+ // Colspan and Rowspan
+ $colspan = 1;
+ $rowspan = 1;
+ if (isset($this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum])) {
+ $spans = $this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum];
+ $rowSpan = $spans['rowspan'];
+ $colSpan = $spans['colspan'];
+
+ // Also apply style from last cell in merge to fix borders -
+ // relies on !important for non-none border declarations in createCSSStyleBorder
+ $endCellCoord = Coordinate::stringFromColumnIndex($colNum + $colSpan) . ($pRow + $rowSpan);
+ if (!$this->useInlineCss) {
+ $cssClass .= ' style' . $pSheet->getCell($endCellCoord)->getXfIndex();
+ }
+ }
+
+ // Write
+ if ($writeCell) {
+ $this->generateRowWriteCell($html, $pSheet, $coordinate, $cellType, $cellData, $colSpan, $rowSpan, $cssClass, $colNum, $sheetIndex, $pRow);
+ }
+
+ // Next column
+ ++$colNum;
+ }
+
+ // Write row end
+ $html .= ' </tr>' . PHP_EOL;
+
+ // Return
+ return $html;
+ }
+
+ /**
+ * Takes array where of CSS properties / values and converts to CSS string.
+ *
+ * @return string
+ */
+ private function assembleCSS(array $pValue = [])
+ {
+ $pairs = [];
+ foreach ($pValue as $property => $value) {
+ $pairs[] = $property . ':' . $value;
+ }
+ $string = implode('; ', $pairs);
+
+ return $string;
+ }
+
+ /**
+ * Get images root.
+ *
+ * @return string
+ */
+ public function getImagesRoot()
+ {
+ return $this->imagesRoot;
+ }
+
+ /**
+ * Set images root.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setImagesRoot($pValue)
+ {
+ $this->imagesRoot = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get embed images.
+ *
+ * @return bool
+ */
+ public function getEmbedImages()
+ {
+ return $this->embedImages;
+ }
+
+ /**
+ * Set embed images.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setEmbedImages($pValue)
+ {
+ $this->embedImages = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get use inline CSS?
+ *
+ * @return bool
+ */
+ public function getUseInlineCss()
+ {
+ return $this->useInlineCss;
+ }
+
+ /**
+ * Set use inline CSS?
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setUseInlineCss($pValue)
+ {
+ $this->useInlineCss = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get use embedded CSS?
+ *
+ * @deprecated no longer used
+ *
+ * @return bool
+ *
+ * @codeCoverageIgnore
+ */
+ public function getUseEmbeddedCSS()
+ {
+ return $this->useEmbeddedCSS;
+ }
+
+ /**
+ * Set use embedded CSS?
+ *
+ * @deprecated no longer used
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ *
+ * @codeCoverageIgnore
+ */
+ public function setUseEmbeddedCSS($pValue)
+ {
+ $this->useEmbeddedCSS = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Add color to formatted string as inline style.
+ *
+ * @param string $pValue Plain formatted value without color
+ * @param string $pFormat Format code
+ *
+ * @return string
+ */
+ public function formatColor($pValue, $pFormat)
+ {
+ // Color information, e.g. [Red] is always at the beginning
+ $color = null; // initialize
+ $matches = [];
+
+ $color_regex = '/^\\[[a-zA-Z]+\\]/';
+ if (preg_match($color_regex, $pFormat, $matches)) {
+ $color = str_replace(['[', ']'], '', $matches[0]);
+ $color = strtolower($color);
+ }
+
+ // convert to PCDATA
+ $value = htmlspecialchars($pValue);
+
+ // color span tag
+ if ($color !== null) {
+ $value = '<span style="color:' . $color . '">' . $value . '</span>';
+ }
+
+ return $value;
+ }
+
+ /**
+ * Calculate information about HTML colspan and rowspan which is not always the same as Excel's.
+ */
+ private function calculateSpans(): void
+ {
+ if ($this->spansAreCalculated) {
+ return;
+ }
+ // Identify all cells that should be omitted in HTML due to cell merge.
+ // In HTML only the upper-left cell should be written and it should have
+ // appropriate rowspan / colspan attribute
+ $sheetIndexes = $this->sheetIndex !== null ?
+ [$this->sheetIndex] : range(0, $this->spreadsheet->getSheetCount() - 1);
+
+ foreach ($sheetIndexes as $sheetIndex) {
+ $sheet = $this->spreadsheet->getSheet($sheetIndex);
+
+ $candidateSpannedRow = [];
+
+ // loop through all Excel merged cells
+ foreach ($sheet->getMergeCells() as $cells) {
+ [$cells] = Coordinate::splitRange($cells);
+ $first = $cells[0];
+ $last = $cells[1];
+
+ [$fc, $fr] = Coordinate::coordinateFromString($first);
+ $fc = Coordinate::columnIndexFromString($fc) - 1;
+
+ [$lc, $lr] = Coordinate::coordinateFromString($last);
+ $lc = Coordinate::columnIndexFromString($lc) - 1;
+
+ // loop through the individual cells in the individual merge
+ $r = $fr - 1;
+ while ($r++ < $lr) {
+ // also, flag this row as a HTML row that is candidate to be omitted
+ $candidateSpannedRow[$r] = $r;
+
+ $c = $fc - 1;
+ while ($c++ < $lc) {
+ if (!($c == $fc && $r == $fr)) {
+ // not the upper-left cell (should not be written in HTML)
+ $this->isSpannedCell[$sheetIndex][$r][$c] = [
+ 'baseCell' => [$fr, $fc],
+ ];
+ } else {
+ // upper-left is the base cell that should hold the colspan/rowspan attribute
+ $this->isBaseCell[$sheetIndex][$r][$c] = [
+ 'xlrowspan' => $lr - $fr + 1, // Excel rowspan
+ 'rowspan' => $lr - $fr + 1, // HTML rowspan, value may change
+ 'xlcolspan' => $lc - $fc + 1, // Excel colspan
+ 'colspan' => $lc - $fc + 1, // HTML colspan, value may change
+ ];
+ }
+ }
+ }
+ }
+
+ $this->calculateSpansOmitRows($sheet, $sheetIndex, $candidateSpannedRow);
+
+ // TODO: Same for columns
+ }
+
+ // We have calculated the spans
+ $this->spansAreCalculated = true;
+ }
+
+ private function calculateSpansOmitRows($sheet, $sheetIndex, $candidateSpannedRow): void
+ {
+ // Identify which rows should be omitted in HTML. These are the rows where all the cells
+ // participate in a merge and the where base cells are somewhere above.
+ $countColumns = Coordinate::columnIndexFromString($sheet->getHighestColumn());
+ foreach ($candidateSpannedRow as $rowIndex) {
+ if (isset($this->isSpannedCell[$sheetIndex][$rowIndex])) {
+ if (count($this->isSpannedCell[$sheetIndex][$rowIndex]) == $countColumns) {
+ $this->isSpannedRow[$sheetIndex][$rowIndex] = $rowIndex;
+ }
+ }
+ }
+
+ // For each of the omitted rows we found above, the affected rowspans should be subtracted by 1
+ if (isset($this->isSpannedRow[$sheetIndex])) {
+ foreach ($this->isSpannedRow[$sheetIndex] as $rowIndex) {
+ $adjustedBaseCells = [];
+ $c = -1;
+ $e = $countColumns - 1;
+ while ($c++ < $e) {
+ $baseCell = $this->isSpannedCell[$sheetIndex][$rowIndex][$c]['baseCell'];
+
+ if (!in_array($baseCell, $adjustedBaseCells)) {
+ // subtract rowspan by 1
+ --$this->isBaseCell[$sheetIndex][$baseCell[0]][$baseCell[1]]['rowspan'];
+ $adjustedBaseCells[] = $baseCell;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Write a comment in the same format as LibreOffice.
+ *
+ * @see https://github.com/LibreOffice/core/blob/9fc9bf3240f8c62ad7859947ab8a033ac1fe93fa/sc/source/filter/html/htmlexp.cxx#L1073-L1092
+ *
+ * @param string $coordinate
+ *
+ * @return string
+ */
+ private function writeComment(Worksheet $pSheet, $coordinate)
+ {
+ $result = '';
+ if (!$this->isPdf && isset($pSheet->getComments()[$coordinate])) {
+ $result .= '<a class="comment-indicator"></a>';
+ $result .= '<div class="comment">' . nl2br($pSheet->getComment($coordinate)->getText()->getPlainText()) . '</div>';
+ $result .= PHP_EOL;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Generate @page declarations.
+ *
+ * @param bool $generateSurroundingHTML
+ *
+ * @return string
+ */
+ private function generatePageDeclarations($generateSurroundingHTML)
+ {
+ // Ensure that Spans have been calculated?
+ $this->calculateSpans();
+
+ // Fetch sheets
+ $sheets = [];
+ if ($this->sheetIndex === null) {
+ $sheets = $this->spreadsheet->getAllSheets();
+ } else {
+ $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
+ }
+
+ // Construct HTML
+ $htmlPage = $generateSurroundingHTML ? ('<style type="text/css">' . PHP_EOL) : '';
+
+ // Loop all sheets
+ $sheetId = 0;
+ foreach ($sheets as $pSheet) {
+ $htmlPage .= "@page page$sheetId { ";
+ $left = StringHelper::formatNumber($pSheet->getPageMargins()->getLeft()) . 'in; ';
+ $htmlPage .= 'margin-left: ' . $left;
+ $right = StringHelper::FormatNumber($pSheet->getPageMargins()->getRight()) . 'in; ';
+ $htmlPage .= 'margin-right: ' . $right;
+ $top = StringHelper::FormatNumber($pSheet->getPageMargins()->getTop()) . 'in; ';
+ $htmlPage .= 'margin-top: ' . $top;
+ $bottom = StringHelper::FormatNumber($pSheet->getPageMargins()->getBottom()) . 'in; ';
+ $htmlPage .= 'margin-bottom: ' . $bottom;
+ $orientation = $pSheet->getPageSetup()->getOrientation();
+ if ($orientation === \PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_LANDSCAPE) {
+ $htmlPage .= 'size: landscape; ';
+ } elseif ($orientation === \PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_PORTRAIT) {
+ $htmlPage .= 'size: portrait; ';
+ }
+ $htmlPage .= "}\n";
+ ++$sheetId;
+ }
+ $htmlPage .= <<<EOF
+.navigation {page-break-after: always;}
+.scrpgbrk, div + div {page-break-before: always;}
+@media screen {
+ .gridlines td {border: 1px solid black;}
+ .gridlines th {border: 1px solid black;}
+ body>div {margin-top: 5px;}
+ body>div:first-child {margin-top: 0;}
+ .scrpgbrk {margin-top: 1px;}
+}
+@media print {
+ .gridlinesp td {border: 1px solid black;}
+ .gridlinesp th {border: 1px solid black;}
+ .navigation {display: none;}
+}
+
+EOF;
+ $htmlPage .= $generateSurroundingHTML ? ('</style>' . PHP_EOL) : '';
+
+ return $htmlPage;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php
new file mode 100755
index 0000000..5129d65
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+interface IWriter
+{
+ /**
+ * IWriter constructor.
+ */
+ public function __construct(Spreadsheet $spreadsheet);
+
+ /**
+ * Write charts in workbook?
+ * If this is true, then the Writer will write definitions for any charts that exist in the PhpSpreadsheet object.
+ * If false (the default) it will ignore any charts defined in the PhpSpreadsheet object.
+ *
+ * @return bool
+ */
+ public function getIncludeCharts();
+
+ /**
+ * Set write charts in workbook
+ * Set to true, to advise the Writer to include any charts that exist in the PhpSpreadsheet object.
+ * Set to false (the default) to ignore charts.
+ *
+ * @param bool $pValue
+ *
+ * @return IWriter
+ */
+ public function setIncludeCharts($pValue);
+
+ /**
+ * Get Pre-Calculate Formulas flag
+ * If this is true (the default), then the writer will recalculate all formulae in a workbook when saving,
+ * so that the pre-calculated values are immediately available to MS Excel or other office spreadsheet
+ * viewer when opening the file
+ * If false, then formulae are not calculated on save. This is faster for saving in PhpSpreadsheet, but slower
+ * when opening the resulting file in MS Excel, because Excel has to recalculate the formulae itself.
+ *
+ * @return bool
+ */
+ public function getPreCalculateFormulas();
+
+ /**
+ * Set Pre-Calculate Formulas
+ * Set to true (the default) to advise the Writer to calculate all formulae on save
+ * Set to false to prevent precalculation of formulae on save.
+ *
+ * @param bool $pValue Pre-Calculate Formulas?
+ *
+ * @return IWriter
+ */
+ public function setPreCalculateFormulas($pValue);
+
+ /**
+ * Save PhpSpreadsheet to file.
+ *
+ * @param resource|string $pFilename Name of the file to save
+ */
+ public function save($pFilename);
+
+ /**
+ * Get use disk caching where possible?
+ *
+ * @return bool
+ */
+ public function getUseDiskCaching();
+
+ /**
+ * Set use disk caching where possible?
+ *
+ * @param bool $pValue
+ * @param string $pDirectory Disk caching directory
+ *
+ * @return IWriter
+ */
+ public function setUseDiskCaching($pValue, $pDirectory = null);
+
+ /**
+ * Get disk caching directory.
+ *
+ * @return string
+ */
+ public function getDiskCachingDirectory();
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php
new file mode 100755
index 0000000..36f3e9c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php
@@ -0,0 +1,156 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Content;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Meta;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\MetaInf;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Mimetype;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Settings;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Styles;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Thumbnails;
+use ZipStream\Exception\OverflowException;
+use ZipStream\Option\Archive;
+use ZipStream\ZipStream;
+
+class Ods extends BaseWriter
+{
+ /**
+ * Private writer parts.
+ *
+ * @var Ods\WriterPart[]
+ */
+ private $writerParts = [];
+
+ /**
+ * Private PhpSpreadsheet.
+ *
+ * @var Spreadsheet
+ */
+ private $spreadSheet;
+
+ /**
+ * Create a new Ods.
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ $this->setSpreadsheet($spreadsheet);
+
+ $writerPartsArray = [
+ 'content' => Content::class,
+ 'meta' => Meta::class,
+ 'meta_inf' => MetaInf::class,
+ 'mimetype' => Mimetype::class,
+ 'settings' => Settings::class,
+ 'styles' => Styles::class,
+ 'thumbnails' => Thumbnails::class,
+ ];
+
+ foreach ($writerPartsArray as $writer => $class) {
+ $this->writerParts[$writer] = new $class($this);
+ }
+ }
+
+ /**
+ * Get writer part.
+ *
+ * @param string $pPartName Writer part name
+ *
+ * @return null|Ods\WriterPart
+ */
+ public function getWriterPart($pPartName)
+ {
+ if ($pPartName != '' && isset($this->writerParts[strtolower($pPartName)])) {
+ return $this->writerParts[strtolower($pPartName)];
+ }
+
+ return null;
+ }
+
+ /**
+ * Save PhpSpreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ if (!$this->spreadSheet) {
+ throw new WriterException('PhpSpreadsheet object unassigned.');
+ }
+
+ // garbage collect
+ $this->spreadSheet->garbageCollect();
+
+ $this->openFileHandle($pFilename);
+
+ $zip = $this->createZip();
+
+ $zip->addFile('META-INF/manifest.xml', $this->getWriterPart('meta_inf')->writeManifest());
+ $zip->addFile('Thumbnails/thumbnail.png', $this->getWriterPart('thumbnails')->writeThumbnail());
+ $zip->addFile('content.xml', $this->getWriterPart('content')->write());
+ $zip->addFile('meta.xml', $this->getWriterPart('meta')->write());
+ $zip->addFile('mimetype', $this->getWriterPart('mimetype')->write());
+ $zip->addFile('settings.xml', $this->getWriterPart('settings')->write());
+ $zip->addFile('styles.xml', $this->getWriterPart('styles')->write());
+
+ // Close file
+ try {
+ $zip->finish();
+ } catch (OverflowException $e) {
+ throw new WriterException('Could not close resource.');
+ }
+
+ $this->maybeCloseFileHandle();
+ }
+
+ /**
+ * Create zip object.
+ *
+ * @return ZipStream
+ */
+ private function createZip()
+ {
+ // Try opening the ZIP file
+ if (!is_resource($this->fileHandle)) {
+ throw new WriterException('Could not open resource for writing.');
+ }
+
+ // Create new ZIP stream
+ $options = new Archive();
+ $options->setEnableZip64(false);
+ $options->setOutputStream($this->fileHandle);
+
+ return new ZipStream(null, $options);
+ }
+
+ /**
+ * Get Spreadsheet object.
+ *
+ * @return Spreadsheet
+ */
+ public function getSpreadsheet()
+ {
+ if ($this->spreadSheet !== null) {
+ return $this->spreadSheet;
+ }
+
+ throw new WriterException('No PhpSpreadsheet assigned.');
+ }
+
+ /**
+ * Set Spreadsheet object.
+ *
+ * @param Spreadsheet $spreadsheet PhpSpreadsheet object
+ *
+ * @return $this
+ */
+ public function setSpreadsheet(Spreadsheet $spreadsheet)
+ {
+ $this->spreadSheet = $spreadsheet;
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php
new file mode 100755
index 0000000..b0829bf
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods\Cell;
+
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+
+/**
+ * @author Alexander Pervakov <frost-nzcr4@jagmort.com>
+ */
+class Comment
+{
+ public static function write(XMLWriter $objWriter, Cell $cell): void
+ {
+ $comments = $cell->getWorksheet()->getComments();
+ if (!isset($comments[$cell->getCoordinate()])) {
+ return;
+ }
+ $comment = $comments[$cell->getCoordinate()];
+
+ $objWriter->startElement('office:annotation');
+ $objWriter->writeAttribute('svg:width', $comment->getWidth());
+ $objWriter->writeAttribute('svg:height', $comment->getHeight());
+ $objWriter->writeAttribute('svg:x', $comment->getMarginLeft());
+ $objWriter->writeAttribute('svg:y', $comment->getMarginTop());
+ $objWriter->writeElement('dc:creator', $comment->getAuthor());
+ $objWriter->writeElement('text:p', $comment->getText()->getPlainText());
+ $objWriter->endElement();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php
new file mode 100755
index 0000000..6588473
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php
@@ -0,0 +1,367 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+use PhpOffice\PhpSpreadsheet\Worksheet\Row;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use PhpOffice\PhpSpreadsheet\Writer\Exception;
+use PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Comment;
+
+/**
+ * @author Alexander Pervakov <frost-nzcr4@jagmort.com>
+ */
+class Content extends WriterPart
+{
+ const NUMBER_COLS_REPEATED_MAX = 1024;
+ const NUMBER_ROWS_REPEATED_MAX = 1048576;
+ const CELL_STYLE_PREFIX = 'ce';
+
+ /**
+ * Write content.xml to XML format.
+ *
+ * @return string XML Output
+ */
+ public function write()
+ {
+ $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');
+
+ // Content
+ $objWriter->startElement('office:document-content');
+ $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
+ $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0');
+ $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0');
+ $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0');
+ $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0');
+ $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
+ $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
+ $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0');
+ $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0');
+ $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0');
+ $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0');
+ $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML');
+ $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0');
+ $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0');
+ $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
+ $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer');
+ $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc');
+ $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events');
+ $objWriter->writeAttribute('xmlns:xforms', 'http://www.w3.org/2002/xforms');
+ $objWriter->writeAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema');
+ $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
+ $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report');
+ $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2');
+ $objWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
+ $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
+ $objWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table');
+ $objWriter->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0');
+ $objWriter->writeAttribute('xmlns:formx', 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0');
+ $objWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/');
+ $objWriter->writeAttribute('office:version', '1.2');
+
+ $objWriter->writeElement('office:scripts');
+ $objWriter->writeElement('office:font-face-decls');
+
+ // Styles XF
+ $objWriter->startElement('office:automatic-styles');
+ $this->writeXfStyles($objWriter, $this->getParentWriter()->getSpreadsheet());
+ $objWriter->endElement();
+
+ $objWriter->startElement('office:body');
+ $objWriter->startElement('office:spreadsheet');
+ $objWriter->writeElement('table:calculation-settings');
+
+ $this->writeSheets($objWriter);
+
+ $objWriter->writeElement('table:named-expressions');
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write sheets.
+ */
+ private function writeSheets(XMLWriter $objWriter): void
+ {
+ $spreadsheet = $this->getParentWriter()->getSpreadsheet(); /** @var Spreadsheet $spreadsheet */
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $objWriter->startElement('table:table');
+ $objWriter->writeAttribute('table:name', $spreadsheet->getSheet($i)->getTitle());
+ $objWriter->writeElement('office:forms');
+ $objWriter->startElement('table:table-column');
+ $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
+ $objWriter->endElement();
+ $this->writeRows($objWriter, $spreadsheet->getSheet($i));
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write rows of the specified sheet.
+ */
+ private function writeRows(XMLWriter $objWriter, Worksheet $sheet): void
+ {
+ $numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX;
+ $span_row = 0;
+ $rows = $sheet->getRowIterator();
+ while ($rows->valid()) {
+ --$numberRowsRepeated;
+ $row = $rows->current();
+ if ($row->getCellIterator()->valid()) {
+ if ($span_row) {
+ $objWriter->startElement('table:table-row');
+ if ($span_row > 1) {
+ $objWriter->writeAttribute('table:number-rows-repeated', $span_row);
+ }
+ $objWriter->startElement('table:table-cell');
+ $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $span_row = 0;
+ }
+ $objWriter->startElement('table:table-row');
+ $this->writeCells($objWriter, $row);
+ $objWriter->endElement();
+ } else {
+ ++$span_row;
+ }
+ $rows->next();
+ }
+ }
+
+ /**
+ * Write cells of the specified row.
+ */
+ private function writeCells(XMLWriter $objWriter, Row $row): void
+ {
+ $numberColsRepeated = self::NUMBER_COLS_REPEATED_MAX;
+ $prevColumn = -1;
+ $cells = $row->getCellIterator();
+ while ($cells->valid()) {
+ /** @var \PhpOffice\PhpSpreadsheet\Cell\Cell $cell */
+ $cell = $cells->current();
+ $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1;
+
+ $this->writeCellSpan($objWriter, $column, $prevColumn);
+ $objWriter->startElement('table:table-cell');
+ $this->writeCellMerge($objWriter, $cell);
+
+ // Style XF
+ $style = $cell->getXfIndex();
+ if ($style !== null) {
+ $objWriter->writeAttribute('table:style-name', self::CELL_STYLE_PREFIX . $style);
+ }
+
+ switch ($cell->getDataType()) {
+ case DataType::TYPE_BOOL:
+ $objWriter->writeAttribute('office:value-type', 'boolean');
+ $objWriter->writeAttribute('office:value', $cell->getValue());
+ $objWriter->writeElement('text:p', $cell->getValue());
+
+ break;
+ case DataType::TYPE_ERROR:
+ throw new Exception('Writing of error not implemented yet.');
+
+ break;
+ case DataType::TYPE_FORMULA:
+ $formulaValue = $cell->getValue();
+ if ($this->getParentWriter()->getPreCalculateFormulas()) {
+ try {
+ $formulaValue = $cell->getCalculatedValue();
+ } catch (Exception $e) {
+ // don't do anything
+ }
+ }
+ $objWriter->writeAttribute('table:formula', 'of:' . $cell->getValue());
+ if (is_numeric($formulaValue)) {
+ $objWriter->writeAttribute('office:value-type', 'float');
+ } else {
+ $objWriter->writeAttribute('office:value-type', 'string');
+ }
+ $objWriter->writeAttribute('office:value', $formulaValue);
+ $objWriter->writeElement('text:p', $formulaValue);
+
+ break;
+ case DataType::TYPE_INLINE:
+ throw new Exception('Writing of inline not implemented yet.');
+
+ break;
+ case DataType::TYPE_NUMERIC:
+ $objWriter->writeAttribute('office:value-type', 'float');
+ $objWriter->writeAttribute('office:value', $cell->getValue());
+ $objWriter->writeElement('text:p', $cell->getValue());
+
+ break;
+ case DataType::TYPE_STRING:
+ $objWriter->writeAttribute('office:value-type', 'string');
+ $objWriter->writeElement('text:p', $cell->getValue());
+
+ break;
+ }
+ Comment::write($objWriter, $cell);
+ $objWriter->endElement();
+ $prevColumn = $column;
+ $cells->next();
+ }
+ $numberColsRepeated = $numberColsRepeated - $prevColumn - 1;
+ if ($numberColsRepeated > 0) {
+ if ($numberColsRepeated > 1) {
+ $objWriter->startElement('table:table-cell');
+ $objWriter->writeAttribute('table:number-columns-repeated', $numberColsRepeated);
+ $objWriter->endElement();
+ } else {
+ $objWriter->writeElement('table:table-cell');
+ }
+ }
+ }
+
+ /**
+ * Write span.
+ *
+ * @param int $curColumn
+ * @param int $prevColumn
+ */
+ private function writeCellSpan(XMLWriter $objWriter, $curColumn, $prevColumn): void
+ {
+ $diff = $curColumn - $prevColumn - 1;
+ if (1 === $diff) {
+ $objWriter->writeElement('table:table-cell');
+ } elseif ($diff > 1) {
+ $objWriter->startElement('table:table-cell');
+ $objWriter->writeAttribute('table:number-columns-repeated', $diff);
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write XF cell styles.
+ */
+ private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet): void
+ {
+ foreach ($spreadsheet->getCellXfCollection() as $style) {
+ $writer->startElement('style:style');
+ $writer->writeAttribute('style:name', self::CELL_STYLE_PREFIX . $style->getIndex());
+ $writer->writeAttribute('style:family', 'table-cell');
+ $writer->writeAttribute('style:parent-style-name', 'Default');
+
+ // style:text-properties
+
+ // Font
+ $writer->startElement('style:text-properties');
+
+ $font = $style->getFont();
+
+ if ($font->getBold()) {
+ $writer->writeAttribute('fo:font-weight', 'bold');
+ $writer->writeAttribute('style:font-weight-complex', 'bold');
+ $writer->writeAttribute('style:font-weight-asian', 'bold');
+ }
+
+ if ($font->getItalic()) {
+ $writer->writeAttribute('fo:font-style', 'italic');
+ }
+
+ if ($color = $font->getColor()) {
+ $writer->writeAttribute('fo:color', sprintf('#%s', $color->getRGB()));
+ }
+
+ if ($family = $font->getName()) {
+ $writer->writeAttribute('fo:font-family', $family);
+ }
+
+ if ($size = $font->getSize()) {
+ $writer->writeAttribute('fo:font-size', sprintf('%.1Fpt', $size));
+ }
+
+ if ($font->getUnderline() && $font->getUnderline() != Font::UNDERLINE_NONE) {
+ $writer->writeAttribute('style:text-underline-style', 'solid');
+ $writer->writeAttribute('style:text-underline-width', 'auto');
+ $writer->writeAttribute('style:text-underline-color', 'font-color');
+
+ switch ($font->getUnderline()) {
+ case Font::UNDERLINE_DOUBLE:
+ $writer->writeAttribute('style:text-underline-type', 'double');
+
+ break;
+ case Font::UNDERLINE_SINGLE:
+ $writer->writeAttribute('style:text-underline-type', 'single');
+
+ break;
+ }
+ }
+
+ $writer->endElement(); // Close style:text-properties
+
+ // style:table-cell-properties
+
+ $writer->startElement('style:table-cell-properties');
+ $writer->writeAttribute('style:rotation-align', 'none');
+
+ // Fill
+ if ($fill = $style->getFill()) {
+ switch ($fill->getFillType()) {
+ case Fill::FILL_SOLID:
+ $writer->writeAttribute('fo:background-color', sprintf(
+ '#%s',
+ strtolower($fill->getStartColor()->getRGB())
+ ));
+
+ break;
+ case Fill::FILL_GRADIENT_LINEAR:
+ case Fill::FILL_GRADIENT_PATH:
+ /// TODO :: To be implemented
+ break;
+ case Fill::FILL_NONE:
+ default:
+ }
+ }
+
+ $writer->endElement(); // Close style:table-cell-properties
+
+ // End
+
+ $writer->endElement(); // Close style:style
+ }
+ }
+
+ /**
+ * Write attributes for merged cell.
+ */
+ private function writeCellMerge(XMLWriter $objWriter, Cell $cell): void
+ {
+ if (!$cell->isMergeRangeValueCell()) {
+ return;
+ }
+
+ $mergeRange = Coordinate::splitRange($cell->getMergeRange());
+ [$startCell, $endCell] = $mergeRange[0];
+ $start = Coordinate::coordinateFromString($startCell);
+ $end = Coordinate::coordinateFromString($endCell);
+ $columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1;
+ $rowSpan = $end[1] - $start[1] + 1;
+
+ $objWriter->writeAttribute('table:number-columns-spanned', $columnSpan);
+ $objWriter->writeAttribute('table:number-rows-spanned', $rowSpan);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php
new file mode 100755
index 0000000..365221f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Meta extends WriterPart
+{
+ /**
+ * Write meta.xml to XML format.
+ *
+ * @param Spreadsheet $spreadsheet
+ *
+ * @return string XML Output
+ */
+ public function write(?Spreadsheet $spreadsheet = null)
+ {
+ if (!$spreadsheet) {
+ $spreadsheet = $this->getParentWriter()->getSpreadsheet();
+ }
+
+ $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');
+
+ // Meta
+ $objWriter->startElement('office:document-meta');
+
+ $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
+ $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
+ $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
+ $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
+ $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
+ $objWriter->writeAttribute('office:version', '1.2');
+
+ $objWriter->startElement('office:meta');
+
+ $objWriter->writeElement('meta:initial-creator', $spreadsheet->getProperties()->getCreator());
+ $objWriter->writeElement('dc:creator', $spreadsheet->getProperties()->getCreator());
+ $objWriter->writeElement('meta:creation-date', date(DATE_W3C, $spreadsheet->getProperties()->getCreated()));
+ $objWriter->writeElement('dc:date', date(DATE_W3C, $spreadsheet->getProperties()->getCreated()));
+ $objWriter->writeElement('dc:title', $spreadsheet->getProperties()->getTitle());
+ $objWriter->writeElement('dc:description', $spreadsheet->getProperties()->getDescription());
+ $objWriter->writeElement('dc:subject', $spreadsheet->getProperties()->getSubject());
+ $keywords = explode(' ', $spreadsheet->getProperties()->getKeywords());
+ foreach ($keywords as $keyword) {
+ $objWriter->writeElement('meta:keyword', $keyword);
+ }
+
+ //<meta:document-statistic meta:table-count="XXX" meta:cell-count="XXX" meta:object-count="XXX"/>
+ $objWriter->startElement('meta:user-defined');
+ $objWriter->writeAttribute('meta:name', 'Company');
+ $objWriter->writeRaw($spreadsheet->getProperties()->getCompany());
+ $objWriter->endElement();
+
+ $objWriter->startElement('meta:user-defined');
+ $objWriter->writeAttribute('meta:name', 'category');
+ $objWriter->writeRaw($spreadsheet->getProperties()->getCategory());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php
new file mode 100755
index 0000000..c9085cf
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+
+class MetaInf extends WriterPart
+{
+ /**
+ * Write META-INF/manifest.xml to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeManifest()
+ {
+ $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');
+
+ // Manifest
+ $objWriter->startElement('manifest:manifest');
+ $objWriter->writeAttribute('xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0');
+ $objWriter->writeAttribute('manifest:version', '1.2');
+
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', '/');
+ $objWriter->writeAttribute('manifest:version', '1.2');
+ $objWriter->writeAttribute('manifest:media-type', 'application/vnd.oasis.opendocument.spreadsheet');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'meta.xml');
+ $objWriter->writeAttribute('manifest:media-type', 'text/xml');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'settings.xml');
+ $objWriter->writeAttribute('manifest:media-type', 'text/xml');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'content.xml');
+ $objWriter->writeAttribute('manifest:media-type', 'text/xml');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'Thumbnails/thumbnail.png');
+ $objWriter->writeAttribute('manifest:media-type', 'image/png');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'styles.xml');
+ $objWriter->writeAttribute('manifest:media-type', 'text/xml');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php
new file mode 100755
index 0000000..4aac368
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Mimetype extends WriterPart
+{
+ /**
+ * Write mimetype to plain text format.
+ *
+ * @param Spreadsheet $spreadsheet
+ *
+ * @return string XML Output
+ */
+ public function write(?Spreadsheet $spreadsheet = null)
+ {
+ return 'application/vnd.oasis.opendocument.spreadsheet';
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php
new file mode 100755
index 0000000..d458e8c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Settings extends WriterPart
+{
+ /**
+ * Write settings.xml to XML format.
+ *
+ * @param Spreadsheet $spreadsheet
+ *
+ * @return string XML Output
+ */
+ public function write(?Spreadsheet $spreadsheet = null)
+ {
+ $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');
+
+ // Settings
+ $objWriter->startElement('office:document-settings');
+ $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
+ $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $objWriter->writeAttribute('xmlns:config', 'urn:oasis:names:tc:opendocument:xmlns:config:1.0');
+ $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
+ $objWriter->writeAttribute('office:version', '1.2');
+
+ $objWriter->startElement('office:settings');
+ $objWriter->startElement('config:config-item-set');
+ $objWriter->writeAttribute('config:name', 'ooo:view-settings');
+ $objWriter->startElement('config:config-item-map-indexed');
+ $objWriter->writeAttribute('config:name', 'Views');
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->startElement('config:config-item-set');
+ $objWriter->writeAttribute('config:name', 'ooo:configuration-settings');
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php
new file mode 100755
index 0000000..7ba7eba
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Styles extends WriterPart
+{
+ /**
+ * Write styles.xml to XML format.
+ *
+ * @param Spreadsheet $spreadsheet
+ *
+ * @return string XML Output
+ */
+ public function write(?Spreadsheet $spreadsheet = null)
+ {
+ $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');
+
+ // Content
+ $objWriter->startElement('office:document-styles');
+ $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
+ $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0');
+ $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0');
+ $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0');
+ $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0');
+ $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
+ $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
+ $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0');
+ $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0');
+ $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0');
+ $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0');
+ $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML');
+ $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0');
+ $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0');
+ $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
+ $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer');
+ $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc');
+ $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events');
+ $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report');
+ $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2');
+ $objWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
+ $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
+ $objWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table');
+ $objWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/');
+ $objWriter->writeAttribute('office:version', '1.2');
+
+ $objWriter->writeElement('office:font-face-decls');
+ $objWriter->writeElement('office:styles');
+ $objWriter->writeElement('office:automatic-styles');
+ $objWriter->writeElement('office:master-styles');
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php
new file mode 100755
index 0000000..dfab065
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Thumbnails extends WriterPart
+{
+ /**
+ * Write Thumbnails/thumbnail.png to PNG format.
+ *
+ * @param Spreadsheet $spreadsheet
+ *
+ * @return string XML Output
+ */
+ public function writeThumbnail(?Spreadsheet $spreadsheet = null)
+ {
+ return '';
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/WriterPart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/WriterPart.php
new file mode 100755
index 0000000..1982c45
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/WriterPart.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+use PhpOffice\PhpSpreadsheet\Writer\Ods;
+
+abstract class WriterPart
+{
+ /**
+ * Parent Ods object.
+ *
+ * @var Ods
+ */
+ private $parentWriter;
+
+ /**
+ * Get Ods writer.
+ *
+ * @return Ods
+ */
+ public function getParentWriter()
+ {
+ return $this->parentWriter;
+ }
+
+ /**
+ * Set parent Ods writer.
+ */
+ public function __construct(Ods $writer)
+ {
+ $this->parentWriter = $writer;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php
new file mode 100755
index 0000000..8722045
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php
@@ -0,0 +1,253 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+abstract class Pdf extends Html
+{
+ /**
+ * Temporary storage directory.
+ *
+ * @var string
+ */
+ protected $tempDir = '';
+
+ /**
+ * Font.
+ *
+ * @var string
+ */
+ protected $font = 'freesans';
+
+ /**
+ * Orientation (Over-ride).
+ *
+ * @var string
+ */
+ protected $orientation;
+
+ /**
+ * Paper size (Over-ride).
+ *
+ * @var int
+ */
+ protected $paperSize;
+
+ /**
+ * Paper Sizes xRef List.
+ *
+ * @var array
+ */
+ protected static $paperSizes = [
+ PageSetup::PAPERSIZE_LETTER => 'LETTER', // (8.5 in. by 11 in.)
+ PageSetup::PAPERSIZE_LETTER_SMALL => 'LETTER', // (8.5 in. by 11 in.)
+ PageSetup::PAPERSIZE_TABLOID => [792.00, 1224.00], // (11 in. by 17 in.)
+ PageSetup::PAPERSIZE_LEDGER => [1224.00, 792.00], // (17 in. by 11 in.)
+ PageSetup::PAPERSIZE_LEGAL => 'LEGAL', // (8.5 in. by 14 in.)
+ PageSetup::PAPERSIZE_STATEMENT => [396.00, 612.00], // (5.5 in. by 8.5 in.)
+ PageSetup::PAPERSIZE_EXECUTIVE => 'EXECUTIVE', // (7.25 in. by 10.5 in.)
+ PageSetup::PAPERSIZE_A3 => 'A3', // (297 mm by 420 mm)
+ PageSetup::PAPERSIZE_A4 => 'A4', // (210 mm by 297 mm)
+ PageSetup::PAPERSIZE_A4_SMALL => 'A4', // (210 mm by 297 mm)
+ PageSetup::PAPERSIZE_A5 => 'A5', // (148 mm by 210 mm)
+ PageSetup::PAPERSIZE_B4 => 'B4', // (250 mm by 353 mm)
+ PageSetup::PAPERSIZE_B5 => 'B5', // (176 mm by 250 mm)
+ PageSetup::PAPERSIZE_FOLIO => 'FOLIO', // (8.5 in. by 13 in.)
+ PageSetup::PAPERSIZE_QUARTO => [609.45, 779.53], // (215 mm by 275 mm)
+ PageSetup::PAPERSIZE_STANDARD_1 => [720.00, 1008.00], // (10 in. by 14 in.)
+ PageSetup::PAPERSIZE_STANDARD_2 => [792.00, 1224.00], // (11 in. by 17 in.)
+ PageSetup::PAPERSIZE_NOTE => 'LETTER', // (8.5 in. by 11 in.)
+ PageSetup::PAPERSIZE_NO9_ENVELOPE => [279.00, 639.00], // (3.875 in. by 8.875 in.)
+ PageSetup::PAPERSIZE_NO10_ENVELOPE => [297.00, 684.00], // (4.125 in. by 9.5 in.)
+ PageSetup::PAPERSIZE_NO11_ENVELOPE => [324.00, 747.00], // (4.5 in. by 10.375 in.)
+ PageSetup::PAPERSIZE_NO12_ENVELOPE => [342.00, 792.00], // (4.75 in. by 11 in.)
+ PageSetup::PAPERSIZE_NO14_ENVELOPE => [360.00, 828.00], // (5 in. by 11.5 in.)
+ PageSetup::PAPERSIZE_C => [1224.00, 1584.00], // (17 in. by 22 in.)
+ PageSetup::PAPERSIZE_D => [1584.00, 2448.00], // (22 in. by 34 in.)
+ PageSetup::PAPERSIZE_E => [2448.00, 3168.00], // (34 in. by 44 in.)
+ PageSetup::PAPERSIZE_DL_ENVELOPE => [311.81, 623.62], // (110 mm by 220 mm)
+ PageSetup::PAPERSIZE_C5_ENVELOPE => 'C5', // (162 mm by 229 mm)
+ PageSetup::PAPERSIZE_C3_ENVELOPE => 'C3', // (324 mm by 458 mm)
+ PageSetup::PAPERSIZE_C4_ENVELOPE => 'C4', // (229 mm by 324 mm)
+ PageSetup::PAPERSIZE_C6_ENVELOPE => 'C6', // (114 mm by 162 mm)
+ PageSetup::PAPERSIZE_C65_ENVELOPE => [323.15, 649.13], // (114 mm by 229 mm)
+ PageSetup::PAPERSIZE_B4_ENVELOPE => 'B4', // (250 mm by 353 mm)
+ PageSetup::PAPERSIZE_B5_ENVELOPE => 'B5', // (176 mm by 250 mm)
+ PageSetup::PAPERSIZE_B6_ENVELOPE => [498.90, 354.33], // (176 mm by 125 mm)
+ PageSetup::PAPERSIZE_ITALY_ENVELOPE => [311.81, 651.97], // (110 mm by 230 mm)
+ PageSetup::PAPERSIZE_MONARCH_ENVELOPE => [279.00, 540.00], // (3.875 in. by 7.5 in.)
+ PageSetup::PAPERSIZE_6_3_4_ENVELOPE => [261.00, 468.00], // (3.625 in. by 6.5 in.)
+ PageSetup::PAPERSIZE_US_STANDARD_FANFOLD => [1071.00, 792.00], // (14.875 in. by 11 in.)
+ PageSetup::PAPERSIZE_GERMAN_STANDARD_FANFOLD => [612.00, 864.00], // (8.5 in. by 12 in.)
+ PageSetup::PAPERSIZE_GERMAN_LEGAL_FANFOLD => 'FOLIO', // (8.5 in. by 13 in.)
+ PageSetup::PAPERSIZE_ISO_B4 => 'B4', // (250 mm by 353 mm)
+ PageSetup::PAPERSIZE_JAPANESE_DOUBLE_POSTCARD => [566.93, 419.53], // (200 mm by 148 mm)
+ PageSetup::PAPERSIZE_STANDARD_PAPER_1 => [648.00, 792.00], // (9 in. by 11 in.)
+ PageSetup::PAPERSIZE_STANDARD_PAPER_2 => [720.00, 792.00], // (10 in. by 11 in.)
+ PageSetup::PAPERSIZE_STANDARD_PAPER_3 => [1080.00, 792.00], // (15 in. by 11 in.)
+ PageSetup::PAPERSIZE_INVITE_ENVELOPE => [623.62, 623.62], // (220 mm by 220 mm)
+ PageSetup::PAPERSIZE_LETTER_EXTRA_PAPER => [667.80, 864.00], // (9.275 in. by 12 in.)
+ PageSetup::PAPERSIZE_LEGAL_EXTRA_PAPER => [667.80, 1080.00], // (9.275 in. by 15 in.)
+ PageSetup::PAPERSIZE_TABLOID_EXTRA_PAPER => [841.68, 1296.00], // (11.69 in. by 18 in.)
+ PageSetup::PAPERSIZE_A4_EXTRA_PAPER => [668.98, 912.76], // (236 mm by 322 mm)
+ PageSetup::PAPERSIZE_LETTER_TRANSVERSE_PAPER => [595.80, 792.00], // (8.275 in. by 11 in.)
+ PageSetup::PAPERSIZE_A4_TRANSVERSE_PAPER => 'A4', // (210 mm by 297 mm)
+ PageSetup::PAPERSIZE_LETTER_EXTRA_TRANSVERSE_PAPER => [667.80, 864.00], // (9.275 in. by 12 in.)
+ PageSetup::PAPERSIZE_SUPERA_SUPERA_A4_PAPER => [643.46, 1009.13], // (227 mm by 356 mm)
+ PageSetup::PAPERSIZE_SUPERB_SUPERB_A3_PAPER => [864.57, 1380.47], // (305 mm by 487 mm)
+ PageSetup::PAPERSIZE_LETTER_PLUS_PAPER => [612.00, 913.68], // (8.5 in. by 12.69 in.)
+ PageSetup::PAPERSIZE_A4_PLUS_PAPER => [595.28, 935.43], // (210 mm by 330 mm)
+ PageSetup::PAPERSIZE_A5_TRANSVERSE_PAPER => 'A5', // (148 mm by 210 mm)
+ PageSetup::PAPERSIZE_JIS_B5_TRANSVERSE_PAPER => [515.91, 728.50], // (182 mm by 257 mm)
+ PageSetup::PAPERSIZE_A3_EXTRA_PAPER => [912.76, 1261.42], // (322 mm by 445 mm)
+ PageSetup::PAPERSIZE_A5_EXTRA_PAPER => [493.23, 666.14], // (174 mm by 235 mm)
+ PageSetup::PAPERSIZE_ISO_B5_EXTRA_PAPER => [569.76, 782.36], // (201 mm by 276 mm)
+ PageSetup::PAPERSIZE_A2_PAPER => 'A2', // (420 mm by 594 mm)
+ PageSetup::PAPERSIZE_A3_TRANSVERSE_PAPER => 'A3', // (297 mm by 420 mm)
+ PageSetup::PAPERSIZE_A3_EXTRA_TRANSVERSE_PAPER => [912.76, 1261.42], // (322 mm by 445 mm)
+ ];
+
+ /**
+ * Create a new PDF Writer instance.
+ *
+ * @param Spreadsheet $spreadsheet Spreadsheet object
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ parent::__construct($spreadsheet);
+ //$this->setUseInlineCss(true);
+ $this->tempDir = File::sysGetTempDir() . '/phpsppdf';
+ $this->isPdf = true;
+ }
+
+ /**
+ * Get Font.
+ *
+ * @return string
+ */
+ public function getFont()
+ {
+ return $this->font;
+ }
+
+ /**
+ * Set font. Examples:
+ * 'arialunicid0-chinese-simplified'
+ * 'arialunicid0-chinese-traditional'
+ * 'arialunicid0-korean'
+ * 'arialunicid0-japanese'.
+ *
+ * @param string $fontName
+ *
+ * @return $this
+ */
+ public function setFont($fontName)
+ {
+ $this->font = $fontName;
+
+ return $this;
+ }
+
+ /**
+ * Get Paper Size.
+ *
+ * @return int
+ */
+ public function getPaperSize()
+ {
+ return $this->paperSize;
+ }
+
+ /**
+ * Set Paper Size.
+ *
+ * @param string $pValue Paper size see PageSetup::PAPERSIZE_*
+ *
+ * @return self
+ */
+ public function setPaperSize($pValue)
+ {
+ $this->paperSize = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Orientation.
+ *
+ * @return string
+ */
+ public function getOrientation()
+ {
+ return $this->orientation;
+ }
+
+ /**
+ * Set Orientation.
+ *
+ * @param string $pValue Page orientation see PageSetup::ORIENTATION_*
+ *
+ * @return self
+ */
+ public function setOrientation($pValue)
+ {
+ $this->orientation = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get temporary storage directory.
+ *
+ * @return string
+ */
+ public function getTempDir()
+ {
+ return $this->tempDir;
+ }
+
+ /**
+ * Set temporary storage directory.
+ *
+ * @param string $pValue Temporary storage directory
+ *
+ * @return self
+ */
+ public function setTempDir($pValue)
+ {
+ if (is_dir($pValue)) {
+ $this->tempDir = $pValue;
+ } else {
+ throw new WriterException("Directory does not exist: $pValue");
+ }
+
+ return $this;
+ }
+
+ /**
+ * Save Spreadsheet to PDF file, pre-save.
+ *
+ * @param string $pFilename Name of the file to save as
+ *
+ * @return resource
+ */
+ protected function prepareForSave($pFilename)
+ {
+ // Open file
+ $this->openFileHandle($pFilename);
+
+ return $this->fileHandle;
+ }
+
+ /**
+ * Save PhpSpreadsheet to PDF file, post-save.
+ */
+ protected function restoreStateAfterSave(): void
+ {
+ $this->maybeCloseFileHandle();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php
new file mode 100755
index 0000000..9ae2cce
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Pdf;
+
+use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
+use PhpOffice\PhpSpreadsheet\Writer\Pdf;
+
+class Dompdf extends Pdf
+{
+ /**
+ * Gets the implementation of external PDF library that should be used.
+ *
+ * @return \Dompdf\Dompdf implementation
+ */
+ protected function createExternalWriterInstance()
+ {
+ return new \Dompdf\Dompdf();
+ }
+
+ /**
+ * Save Spreadsheet to file.
+ *
+ * @param string $pFilename Name of the file to save as
+ */
+ public function save($pFilename): void
+ {
+ $fileHandle = parent::prepareForSave($pFilename);
+
+ // Default PDF paper size
+ $paperSize = 'LETTER'; // Letter (8.5 in. by 11 in.)
+
+ // Check for paper size and page orientation
+ if ($this->getSheetIndex() === null) {
+ $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize();
+ } else {
+ $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize();
+ }
+
+ $orientation = ($orientation == 'L') ? 'landscape' : 'portrait';
+
+ // Override Page Orientation
+ if ($this->getOrientation() !== null) {
+ $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_DEFAULT)
+ ? PageSetup::ORIENTATION_PORTRAIT
+ : $this->getOrientation();
+ }
+ // Override Paper Size
+ if ($this->getPaperSize() !== null) {
+ $printPaperSize = $this->getPaperSize();
+ }
+
+ if (isset(self::$paperSizes[$printPaperSize])) {
+ $paperSize = self::$paperSizes[$printPaperSize];
+ }
+
+ // Create PDF
+ $pdf = $this->createExternalWriterInstance();
+ $pdf->setPaper(strtolower($paperSize), $orientation);
+
+ $pdf->loadHtml($this->generateHTMLAll());
+ $pdf->render();
+
+ // Write to file
+ fwrite($fileHandle, $pdf->output());
+
+ parent::restoreStateAfterSave();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php
new file mode 100755
index 0000000..75e0010
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Pdf;
+
+use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
+use PhpOffice\PhpSpreadsheet\Writer\Pdf;
+
+class Mpdf extends Pdf
+{
+ /**
+ * Gets the implementation of external PDF library that should be used.
+ *
+ * @param array $config Configuration array
+ *
+ * @return \Mpdf\Mpdf implementation
+ */
+ protected function createExternalWriterInstance($config)
+ {
+ return new \Mpdf\Mpdf($config);
+ }
+
+ /**
+ * Save Spreadsheet to file.
+ *
+ * @param string $pFilename Name of the file to save as
+ */
+ public function save($pFilename): void
+ {
+ $fileHandle = parent::prepareForSave($pFilename);
+
+ // Default PDF paper size
+ $paperSize = 'LETTER'; // Letter (8.5 in. by 11 in.)
+
+ // Check for paper size and page orientation
+ if (null === $this->getSheetIndex()) {
+ $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize();
+ } else {
+ $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize();
+ }
+ $this->setOrientation($orientation);
+
+ // Override Page Orientation
+ if (null !== $this->getOrientation()) {
+ $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_DEFAULT)
+ ? PageSetup::ORIENTATION_PORTRAIT
+ : $this->getOrientation();
+ }
+ $orientation = strtoupper($orientation);
+
+ // Override Paper Size
+ if (null !== $this->getPaperSize()) {
+ $printPaperSize = $this->getPaperSize();
+ }
+
+ if (isset(self::$paperSizes[$printPaperSize])) {
+ $paperSize = self::$paperSizes[$printPaperSize];
+ }
+
+ // Create PDF
+ $config = ['tempDir' => $this->tempDir . '/mpdf'];
+ $pdf = $this->createExternalWriterInstance($config);
+ $ortmp = $orientation;
+ $pdf->_setPageSize(strtoupper($paperSize), $ortmp);
+ $pdf->DefOrientation = $orientation;
+ $pdf->AddPageByArray([
+ 'orientation' => $orientation,
+ 'margin-left' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getLeft()),
+ 'margin-right' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getRight()),
+ 'margin-top' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getTop()),
+ 'margin-bottom' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getBottom()),
+ ]);
+
+ // Document info
+ $pdf->SetTitle($this->spreadsheet->getProperties()->getTitle());
+ $pdf->SetAuthor($this->spreadsheet->getProperties()->getCreator());
+ $pdf->SetSubject($this->spreadsheet->getProperties()->getSubject());
+ $pdf->SetKeywords($this->spreadsheet->getProperties()->getKeywords());
+ $pdf->SetCreator($this->spreadsheet->getProperties()->getCreator());
+
+ $html = $this->generateHTMLAll();
+ foreach (\array_chunk(\explode(PHP_EOL, $html), 1000) as $lines) {
+ $pdf->WriteHTML(\implode(PHP_EOL, $lines));
+ }
+
+ // Write to file
+ fwrite($fileHandle, $pdf->Output('', 'S'));
+
+ parent::restoreStateAfterSave();
+ }
+
+ /**
+ * Convert inches to mm.
+ *
+ * @param float $inches
+ *
+ * @return float
+ */
+ private function inchesToMm($inches)
+ {
+ return $inches * 25.4;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php
new file mode 100755
index 0000000..7530b1e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Pdf;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
+use PhpOffice\PhpSpreadsheet\Writer\Pdf;
+
+class Tcpdf extends Pdf
+{
+ /**
+ * Create a new PDF Writer instance.
+ *
+ * @param Spreadsheet $spreadsheet Spreadsheet object
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ parent::__construct($spreadsheet);
+ $this->setUseInlineCss(true);
+ }
+
+ /**
+ * Gets the implementation of external PDF library that should be used.
+ *
+ * @param string $orientation Page orientation
+ * @param string $unit Unit measure
+ * @param string $paperSize Paper size
+ *
+ * @return \TCPDF implementation
+ */
+ protected function createExternalWriterInstance($orientation, $unit, $paperSize)
+ {
+ return new \TCPDF($orientation, $unit, $paperSize);
+ }
+
+ /**
+ * Save Spreadsheet to file.
+ *
+ * @param string $pFilename Name of the file to save as
+ */
+ public function save($pFilename): void
+ {
+ $fileHandle = parent::prepareForSave($pFilename);
+
+ // Default PDF paper size
+ $paperSize = 'LETTER'; // Letter (8.5 in. by 11 in.)
+
+ // Check for paper size and page orientation
+ if ($this->getSheetIndex() === null) {
+ $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize();
+ $printMargins = $this->spreadsheet->getSheet(0)->getPageMargins();
+ } else {
+ $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize();
+ $printMargins = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageMargins();
+ }
+
+ // Override Page Orientation
+ if ($this->getOrientation() !== null) {
+ $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_LANDSCAPE)
+ ? 'L'
+ : 'P';
+ }
+ // Override Paper Size
+ if ($this->getPaperSize() !== null) {
+ $printPaperSize = $this->getPaperSize();
+ }
+
+ if (isset(self::$paperSizes[$printPaperSize])) {
+ $paperSize = self::$paperSizes[$printPaperSize];
+ }
+
+ // Create PDF
+ $pdf = $this->createExternalWriterInstance($orientation, 'pt', $paperSize);
+ $pdf->setFontSubsetting(false);
+ // Set margins, converting inches to points (using 72 dpi)
+ $pdf->SetMargins($printMargins->getLeft() * 72, $printMargins->getTop() * 72, $printMargins->getRight() * 72);
+ $pdf->SetAutoPageBreak(true, $printMargins->getBottom() * 72);
+
+ $pdf->setPrintHeader(false);
+ $pdf->setPrintFooter(false);
+
+ $pdf->AddPage();
+
+ // Set the appropriate font
+ $pdf->SetFont($this->getFont());
+ $pdf->writeHTML($this->generateHTMLAll());
+
+ // Document info
+ $pdf->SetTitle($this->spreadsheet->getProperties()->getTitle());
+ $pdf->SetAuthor($this->spreadsheet->getProperties()->getCreator());
+ $pdf->SetSubject($this->spreadsheet->getProperties()->getSubject());
+ $pdf->SetKeywords($this->spreadsheet->getProperties()->getKeywords());
+ $pdf->SetCreator($this->spreadsheet->getProperties()->getCreator());
+
+ // Write to file
+ fwrite($fileHandle, $pdf->output($pFilename, 'S'));
+
+ parent::restoreStateAfterSave();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php
new file mode 100755
index 0000000..cf87d5b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php
@@ -0,0 +1,946 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\RichText\Run;
+use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing;
+use PhpOffice\PhpSpreadsheet\Shared\Escher;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip;
+use PhpOffice\PhpSpreadsheet\Shared\OLE;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
+use RuntimeException;
+
+class Xls extends BaseWriter
+{
+ /**
+ * PhpSpreadsheet object.
+ *
+ * @var Spreadsheet
+ */
+ private $spreadsheet;
+
+ /**
+ * Total number of shared strings in workbook.
+ *
+ * @var int
+ */
+ private $strTotal = 0;
+
+ /**
+ * Number of unique shared strings in workbook.
+ *
+ * @var int
+ */
+ private $strUnique = 0;
+
+ /**
+ * Array of unique shared strings in workbook.
+ *
+ * @var array
+ */
+ private $strTable = [];
+
+ /**
+ * Color cache. Mapping between RGB value and color index.
+ *
+ * @var array
+ */
+ private $colors;
+
+ /**
+ * Formula parser.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser
+ */
+ private $parser;
+
+ /**
+ * Identifier clusters for drawings. Used in MSODRAWINGGROUP record.
+ *
+ * @var array
+ */
+ private $IDCLs;
+
+ /**
+ * Basic OLE object summary information.
+ *
+ * @var array
+ */
+ private $summaryInformation;
+
+ /**
+ * Extended OLE object document summary information.
+ *
+ * @var array
+ */
+ private $documentSummaryInformation;
+
+ /**
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook
+ */
+ private $writerWorkbook;
+
+ /**
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Worksheet[]
+ */
+ private $writerWorksheets;
+
+ /**
+ * Create a new Xls Writer.
+ *
+ * @param Spreadsheet $spreadsheet PhpSpreadsheet object
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ $this->spreadsheet = $spreadsheet;
+
+ $this->parser = new Xls\Parser();
+ }
+
+ /**
+ * Save Spreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ // garbage collect
+ $this->spreadsheet->garbageCollect();
+
+ $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
+ $saveDateReturnType = Functions::getReturnDateType();
+ Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
+
+ // initialize colors array
+ $this->colors = [];
+
+ // Initialise workbook writer
+ $this->writerWorkbook = new Xls\Workbook($this->spreadsheet, $this->strTotal, $this->strUnique, $this->strTable, $this->colors, $this->parser);
+
+ // Initialise worksheet writers
+ $countSheets = $this->spreadsheet->getSheetCount();
+ for ($i = 0; $i < $countSheets; ++$i) {
+ $this->writerWorksheets[$i] = new Xls\Worksheet($this->strTotal, $this->strUnique, $this->strTable, $this->colors, $this->parser, $this->preCalculateFormulas, $this->spreadsheet->getSheet($i));
+ }
+
+ // build Escher objects. Escher objects for workbooks needs to be build before Escher object for workbook.
+ $this->buildWorksheetEschers();
+ $this->buildWorkbookEscher();
+
+ // add 15 identical cell style Xfs
+ // for now, we use the first cellXf instead of cellStyleXf
+ $cellXfCollection = $this->spreadsheet->getCellXfCollection();
+ for ($i = 0; $i < 15; ++$i) {
+ $this->writerWorkbook->addXfWriter($cellXfCollection[0], true);
+ }
+
+ // add all the cell Xfs
+ foreach ($this->spreadsheet->getCellXfCollection() as $style) {
+ $this->writerWorkbook->addXfWriter($style, false);
+ }
+
+ // add fonts from rich text eleemnts
+ for ($i = 0; $i < $countSheets; ++$i) {
+ foreach ($this->writerWorksheets[$i]->phpSheet->getCoordinates() as $coordinate) {
+ $cell = $this->writerWorksheets[$i]->phpSheet->getCell($coordinate);
+ $cVal = $cell->getValue();
+ if ($cVal instanceof RichText) {
+ $elements = $cVal->getRichTextElements();
+ foreach ($elements as $element) {
+ if ($element instanceof Run) {
+ $font = $element->getFont();
+ $this->writerWorksheets[$i]->fontHashIndex[$font->getHashCode()] = $this->writerWorkbook->addFont($font);
+ }
+ }
+ }
+ }
+ }
+
+ // initialize OLE file
+ $workbookStreamName = 'Workbook';
+ $OLE = new File(OLE::ascToUcs($workbookStreamName));
+
+ // Write the worksheet streams before the global workbook stream,
+ // because the byte sizes of these are needed in the global workbook stream
+ $worksheetSizes = [];
+ for ($i = 0; $i < $countSheets; ++$i) {
+ $this->writerWorksheets[$i]->close();
+ $worksheetSizes[] = $this->writerWorksheets[$i]->_datasize;
+ }
+
+ // add binary data for global workbook stream
+ $OLE->append($this->writerWorkbook->writeWorkbook($worksheetSizes));
+
+ // add binary data for sheet streams
+ for ($i = 0; $i < $countSheets; ++$i) {
+ $OLE->append($this->writerWorksheets[$i]->getData());
+ }
+
+ $this->documentSummaryInformation = $this->writeDocumentSummaryInformation();
+ // initialize OLE Document Summary Information
+ if (isset($this->documentSummaryInformation) && !empty($this->documentSummaryInformation)) {
+ $OLE_DocumentSummaryInformation = new File(OLE::ascToUcs(chr(5) . 'DocumentSummaryInformation'));
+ $OLE_DocumentSummaryInformation->append($this->documentSummaryInformation);
+ }
+
+ $this->summaryInformation = $this->writeSummaryInformation();
+ // initialize OLE Summary Information
+ if (isset($this->summaryInformation) && !empty($this->summaryInformation)) {
+ $OLE_SummaryInformation = new File(OLE::ascToUcs(chr(5) . 'SummaryInformation'));
+ $OLE_SummaryInformation->append($this->summaryInformation);
+ }
+
+ // define OLE Parts
+ $arrRootData = [$OLE];
+ // initialize OLE Properties file
+ if (isset($OLE_SummaryInformation)) {
+ $arrRootData[] = $OLE_SummaryInformation;
+ }
+ // initialize OLE Extended Properties file
+ if (isset($OLE_DocumentSummaryInformation)) {
+ $arrRootData[] = $OLE_DocumentSummaryInformation;
+ }
+
+ $root = new Root(time(), time(), $arrRootData);
+ // save the OLE file
+ $this->openFileHandle($pFilename);
+ $root->save($this->fileHandle);
+ $this->maybeCloseFileHandle();
+
+ Functions::setReturnDateType($saveDateReturnType);
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
+ }
+
+ /**
+ * Build the Worksheet Escher objects.
+ */
+ private function buildWorksheetEschers(): void
+ {
+ // 1-based index to BstoreContainer
+ $blipIndex = 0;
+ $lastReducedSpId = 0;
+ $lastSpId = 0;
+
+ foreach ($this->spreadsheet->getAllsheets() as $sheet) {
+ // sheet index
+ $sheetIndex = $sheet->getParent()->getIndex($sheet);
+
+ $escher = null;
+
+ // check if there are any shapes for this sheet
+ $filterRange = $sheet->getAutoFilter()->getRange();
+ if (count($sheet->getDrawingCollection()) == 0 && empty($filterRange)) {
+ continue;
+ }
+
+ // create intermediate Escher object
+ $escher = new Escher();
+
+ // dgContainer
+ $dgContainer = new DgContainer();
+
+ // set the drawing index (we use sheet index + 1)
+ $dgId = $sheet->getParent()->getIndex($sheet) + 1;
+ $dgContainer->setDgId($dgId);
+ $escher->setDgContainer($dgContainer);
+
+ // spgrContainer
+ $spgrContainer = new SpgrContainer();
+ $dgContainer->setSpgrContainer($spgrContainer);
+
+ // add one shape which is the group shape
+ $spContainer = new SpContainer();
+ $spContainer->setSpgr(true);
+ $spContainer->setSpType(0);
+ $spContainer->setSpId(($sheet->getParent()->getIndex($sheet) + 1) << 10);
+ $spgrContainer->addChild($spContainer);
+
+ // add the shapes
+
+ $countShapes[$sheetIndex] = 0; // count number of shapes (minus group shape), in sheet
+
+ foreach ($sheet->getDrawingCollection() as $drawing) {
+ ++$blipIndex;
+
+ ++$countShapes[$sheetIndex];
+
+ // add the shape
+ $spContainer = new SpContainer();
+
+ // set the shape type
+ $spContainer->setSpType(0x004B);
+ // set the shape flag
+ $spContainer->setSpFlag(0x02);
+
+ // set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
+ $reducedSpId = $countShapes[$sheetIndex];
+ $spId = $reducedSpId | ($sheet->getParent()->getIndex($sheet) + 1) << 10;
+ $spContainer->setSpId($spId);
+
+ // keep track of last reducedSpId
+ $lastReducedSpId = $reducedSpId;
+
+ // keep track of last spId
+ $lastSpId = $spId;
+
+ // set the BLIP index
+ $spContainer->setOPT(0x4104, $blipIndex);
+
+ // set coordinates and offsets, client anchor
+ $coordinates = $drawing->getCoordinates();
+ $offsetX = $drawing->getOffsetX();
+ $offsetY = $drawing->getOffsetY();
+ $width = $drawing->getWidth();
+ $height = $drawing->getHeight();
+
+ $twoAnchor = \PhpOffice\PhpSpreadsheet\Shared\Xls::oneAnchor2twoAnchor($sheet, $coordinates, $offsetX, $offsetY, $width, $height);
+
+ $spContainer->setStartCoordinates($twoAnchor['startCoordinates']);
+ $spContainer->setStartOffsetX($twoAnchor['startOffsetX']);
+ $spContainer->setStartOffsetY($twoAnchor['startOffsetY']);
+ $spContainer->setEndCoordinates($twoAnchor['endCoordinates']);
+ $spContainer->setEndOffsetX($twoAnchor['endOffsetX']);
+ $spContainer->setEndOffsetY($twoAnchor['endOffsetY']);
+
+ $spgrContainer->addChild($spContainer);
+ }
+
+ // AutoFilters
+ if (!empty($filterRange)) {
+ $rangeBounds = Coordinate::rangeBoundaries($filterRange);
+ $iNumColStart = $rangeBounds[0][0];
+ $iNumColEnd = $rangeBounds[1][0];
+
+ $iInc = $iNumColStart;
+ while ($iInc <= $iNumColEnd) {
+ ++$countShapes[$sheetIndex];
+
+ // create an Drawing Object for the dropdown
+ $oDrawing = new BaseDrawing();
+ // get the coordinates of drawing
+ $cDrawing = Coordinate::stringFromColumnIndex($iInc) . $rangeBounds[0][1];
+ $oDrawing->setCoordinates($cDrawing);
+ $oDrawing->setWorksheet($sheet);
+
+ // add the shape
+ $spContainer = new SpContainer();
+ // set the shape type
+ $spContainer->setSpType(0x00C9);
+ // set the shape flag
+ $spContainer->setSpFlag(0x01);
+
+ // set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
+ $reducedSpId = $countShapes[$sheetIndex];
+ $spId = $reducedSpId | ($sheet->getParent()->getIndex($sheet) + 1) << 10;
+ $spContainer->setSpId($spId);
+
+ // keep track of last reducedSpId
+ $lastReducedSpId = $reducedSpId;
+
+ // keep track of last spId
+ $lastSpId = $spId;
+
+ $spContainer->setOPT(0x007F, 0x01040104); // Protection -> fLockAgainstGrouping
+ $spContainer->setOPT(0x00BF, 0x00080008); // Text -> fFitTextToShape
+ $spContainer->setOPT(0x01BF, 0x00010000); // Fill Style -> fNoFillHitTest
+ $spContainer->setOPT(0x01FF, 0x00080000); // Line Style -> fNoLineDrawDash
+ $spContainer->setOPT(0x03BF, 0x000A0000); // Group Shape -> fPrint
+
+ // set coordinates and offsets, client anchor
+ $endCoordinates = Coordinate::stringFromColumnIndex($iInc);
+ $endCoordinates .= $rangeBounds[0][1] + 1;
+
+ $spContainer->setStartCoordinates($cDrawing);
+ $spContainer->setStartOffsetX(0);
+ $spContainer->setStartOffsetY(0);
+ $spContainer->setEndCoordinates($endCoordinates);
+ $spContainer->setEndOffsetX(0);
+ $spContainer->setEndOffsetY(0);
+
+ $spgrContainer->addChild($spContainer);
+ ++$iInc;
+ }
+ }
+
+ // identifier clusters, used for workbook Escher object
+ $this->IDCLs[$dgId] = $lastReducedSpId;
+
+ // set last shape index
+ $dgContainer->setLastSpId($lastSpId);
+
+ // set the Escher object
+ $this->writerWorksheets[$sheetIndex]->setEscher($escher);
+ }
+ }
+
+ /**
+ * Build the Escher object corresponding to the MSODRAWINGGROUP record.
+ */
+ private function buildWorkbookEscher(): void
+ {
+ $escher = null;
+
+ // any drawings in this workbook?
+ $found = false;
+ foreach ($this->spreadsheet->getAllSheets() as $sheet) {
+ if (count($sheet->getDrawingCollection()) > 0) {
+ $found = true;
+
+ break;
+ }
+ }
+
+ // nothing to do if there are no drawings
+ if (!$found) {
+ return;
+ }
+
+ // if we reach here, then there are drawings in the workbook
+ $escher = new Escher();
+
+ // dggContainer
+ $dggContainer = new DggContainer();
+ $escher->setDggContainer($dggContainer);
+
+ // set IDCLs (identifier clusters)
+ $dggContainer->setIDCLs($this->IDCLs);
+
+ // this loop is for determining maximum shape identifier of all drawing
+ $spIdMax = 0;
+ $totalCountShapes = 0;
+ $countDrawings = 0;
+
+ foreach ($this->spreadsheet->getAllsheets() as $sheet) {
+ $sheetCountShapes = 0; // count number of shapes (minus group shape), in sheet
+
+ if (count($sheet->getDrawingCollection()) > 0) {
+ ++$countDrawings;
+
+ foreach ($sheet->getDrawingCollection() as $drawing) {
+ ++$sheetCountShapes;
+ ++$totalCountShapes;
+
+ $spId = $sheetCountShapes | ($this->spreadsheet->getIndex($sheet) + 1) << 10;
+ $spIdMax = max($spId, $spIdMax);
+ }
+ }
+ }
+
+ $dggContainer->setSpIdMax($spIdMax + 1);
+ $dggContainer->setCDgSaved($countDrawings);
+ $dggContainer->setCSpSaved($totalCountShapes + $countDrawings); // total number of shapes incl. one group shapes per drawing
+
+ // bstoreContainer
+ $bstoreContainer = new BstoreContainer();
+ $dggContainer->setBstoreContainer($bstoreContainer);
+
+ // the BSE's (all the images)
+ foreach ($this->spreadsheet->getAllsheets() as $sheet) {
+ foreach ($sheet->getDrawingCollection() as $drawing) {
+ if (!extension_loaded('gd')) {
+ throw new RuntimeException('Saving images in xls requires gd extension');
+ }
+ if ($drawing instanceof Drawing) {
+ $filename = $drawing->getPath();
+
+ [$imagesx, $imagesy, $imageFormat] = getimagesize($filename);
+
+ switch ($imageFormat) {
+ case 1: // GIF, not supported by BIFF8, we convert to PNG
+ $blipType = BSE::BLIPTYPE_PNG;
+ ob_start();
+ imagepng(imagecreatefromgif($filename));
+ $blipData = ob_get_contents();
+ ob_end_clean();
+
+ break;
+ case 2: // JPEG
+ $blipType = BSE::BLIPTYPE_JPEG;
+ $blipData = file_get_contents($filename);
+
+ break;
+ case 3: // PNG
+ $blipType = BSE::BLIPTYPE_PNG;
+ $blipData = file_get_contents($filename);
+
+ break;
+ case 6: // Windows DIB (BMP), we convert to PNG
+ $blipType = BSE::BLIPTYPE_PNG;
+ ob_start();
+ imagepng(SharedDrawing::imagecreatefrombmp($filename));
+ $blipData = ob_get_contents();
+ ob_end_clean();
+
+ break;
+ default:
+ continue 2;
+ }
+
+ $blip = new Blip();
+ $blip->setData($blipData);
+
+ $BSE = new BSE();
+ $BSE->setBlipType($blipType);
+ $BSE->setBlip($blip);
+
+ $bstoreContainer->addBSE($BSE);
+ } elseif ($drawing instanceof MemoryDrawing) {
+ switch ($drawing->getRenderingFunction()) {
+ case MemoryDrawing::RENDERING_JPEG:
+ $blipType = BSE::BLIPTYPE_JPEG;
+ $renderingFunction = 'imagejpeg';
+
+ break;
+ case MemoryDrawing::RENDERING_GIF:
+ case MemoryDrawing::RENDERING_PNG:
+ case MemoryDrawing::RENDERING_DEFAULT:
+ $blipType = BSE::BLIPTYPE_PNG;
+ $renderingFunction = 'imagepng';
+
+ break;
+ }
+
+ ob_start();
+ call_user_func($renderingFunction, $drawing->getImageResource());
+ $blipData = ob_get_contents();
+ ob_end_clean();
+
+ $blip = new Blip();
+ $blip->setData($blipData);
+
+ $BSE = new BSE();
+ $BSE->setBlipType($blipType);
+ $BSE->setBlip($blip);
+
+ $bstoreContainer->addBSE($BSE);
+ }
+ }
+ }
+
+ // Set the Escher object
+ $this->writerWorkbook->setEscher($escher);
+ }
+
+ /**
+ * Build the OLE Part for DocumentSummary Information.
+ *
+ * @return string
+ */
+ private function writeDocumentSummaryInformation()
+ {
+ // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
+ $data = pack('v', 0xFFFE);
+ // offset: 2; size: 2;
+ $data .= pack('v', 0x0000);
+ // offset: 4; size: 2; OS version
+ $data .= pack('v', 0x0106);
+ // offset: 6; size: 2; OS indicator
+ $data .= pack('v', 0x0002);
+ // offset: 8; size: 16
+ $data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00);
+ // offset: 24; size: 4; section count
+ $data .= pack('V', 0x0001);
+
+ // offset: 28; size: 16; first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae
+ $data .= pack('vvvvvvvv', 0xD502, 0xD5CD, 0x2E9C, 0x101B, 0x9793, 0x0008, 0x2C2B, 0xAEF9);
+ // offset: 44; size: 4; offset of the start
+ $data .= pack('V', 0x30);
+
+ // SECTION
+ $dataSection = [];
+ $dataSection_NumProps = 0;
+ $dataSection_Summary = '';
+ $dataSection_Content = '';
+
+ // GKPIDDSI_CODEPAGE: CodePage
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x01],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x02], // 2 byte signed integer
+ 'data' => ['data' => 1252],
+ ];
+ ++$dataSection_NumProps;
+
+ // GKPIDDSI_CATEGORY : Category
+ if ($this->spreadsheet->getProperties()->getCategory()) {
+ $dataProp = $this->spreadsheet->getProperties()->getCategory();
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x02],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x1E],
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ // GKPIDDSI_VERSION :Version of the application that wrote the property storage
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x17],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x03],
+ 'data' => ['pack' => 'V', 'data' => 0x000C0000],
+ ];
+ ++$dataSection_NumProps;
+ // GKPIDDSI_SCALE : FALSE
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x0B],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x0B],
+ 'data' => ['data' => false],
+ ];
+ ++$dataSection_NumProps;
+ // GKPIDDSI_LINKSDIRTY : True if any of the values for the linked properties have changed outside of the application
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x10],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x0B],
+ 'data' => ['data' => false],
+ ];
+ ++$dataSection_NumProps;
+ // GKPIDDSI_SHAREDOC : FALSE
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x13],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x0B],
+ 'data' => ['data' => false],
+ ];
+ ++$dataSection_NumProps;
+ // GKPIDDSI_HYPERLINKSCHANGED : True if any of the values for the _PID_LINKS (hyperlink text) have changed outside of the application
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x16],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x0B],
+ 'data' => ['data' => false],
+ ];
+ ++$dataSection_NumProps;
+
+ // GKPIDDSI_DOCSPARTS
+ // MS-OSHARED p75 (2.3.3.2.2.1)
+ // Structure is VtVecUnalignedLpstrValue (2.3.3.1.9)
+ // cElements
+ $dataProp = pack('v', 0x0001);
+ $dataProp .= pack('v', 0x0000);
+ // array of UnalignedLpstr
+ // cch
+ $dataProp .= pack('v', 0x000A);
+ $dataProp .= pack('v', 0x0000);
+ // value
+ $dataProp .= 'Worksheet' . chr(0);
+
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x0D],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x101E],
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+
+ // GKPIDDSI_HEADINGPAIR
+ // VtVecHeadingPairValue
+ // cElements
+ $dataProp = pack('v', 0x0002);
+ $dataProp .= pack('v', 0x0000);
+ // Array of vtHeadingPair
+ // vtUnalignedString - headingString
+ // stringType
+ $dataProp .= pack('v', 0x001E);
+ // padding
+ $dataProp .= pack('v', 0x0000);
+ // UnalignedLpstr
+ // cch
+ $dataProp .= pack('v', 0x0013);
+ $dataProp .= pack('v', 0x0000);
+ // value
+ $dataProp .= 'Feuilles de calcul';
+ // vtUnalignedString - headingParts
+ // wType : 0x0003 = 32 bit signed integer
+ $dataProp .= pack('v', 0x0300);
+ // padding
+ $dataProp .= pack('v', 0x0000);
+ // value
+ $dataProp .= pack('v', 0x0100);
+ $dataProp .= pack('v', 0x0000);
+ $dataProp .= pack('v', 0x0000);
+ $dataProp .= pack('v', 0x0000);
+
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x0C],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x100C],
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+
+ // 4 Section Length
+ // 4 Property count
+ // 8 * $dataSection_NumProps (8 = ID (4) + OffSet(4))
+ $dataSection_Content_Offset = 8 + $dataSection_NumProps * 8;
+ foreach ($dataSection as $dataProp) {
+ // Summary
+ $dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']);
+ // Offset
+ $dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset);
+ // DataType
+ $dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']);
+ // Data
+ if ($dataProp['type']['data'] == 0x02) { // 2 byte signed integer
+ $dataSection_Content .= pack('V', $dataProp['data']['data']);
+
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x03) { // 4 byte signed integer
+ $dataSection_Content .= pack('V', $dataProp['data']['data']);
+
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x0B) { // Boolean
+ if ($dataProp['data']['data'] == false) {
+ $dataSection_Content .= pack('V', 0x0000);
+ } else {
+ $dataSection_Content .= pack('V', 0x0001);
+ }
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length
+ // Null-terminated string
+ $dataProp['data']['data'] .= chr(0);
+ ++$dataProp['data']['length'];
+ // Complete the string with null string for being a %4
+ $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4) == 4 ? 0 : (4 - $dataProp['data']['length'] % 4));
+ $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT);
+
+ $dataSection_Content .= pack('V', $dataProp['data']['length']);
+ $dataSection_Content .= $dataProp['data']['data'];
+
+ $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']);
+ } elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
+ $dataSection_Content .= $dataProp['data']['data'];
+
+ $dataSection_Content_Offset += 4 + 8;
+ } else {
+ // Data Type Not Used at the moment
+ $dataSection_Content .= $dataProp['data']['data'];
+
+ $dataSection_Content_Offset += 4 + $dataProp['data']['length'];
+ }
+ }
+ // Now $dataSection_Content_Offset contains the size of the content
+
+ // section header
+ // offset: $secOffset; size: 4; section length
+ // + x Size of the content (summary + content)
+ $data .= pack('V', $dataSection_Content_Offset);
+ // offset: $secOffset+4; size: 4; property count
+ $data .= pack('V', $dataSection_NumProps);
+ // Section Summary
+ $data .= $dataSection_Summary;
+ // Section Content
+ $data .= $dataSection_Content;
+
+ return $data;
+ }
+
+ /**
+ * Build the OLE Part for Summary Information.
+ *
+ * @return string
+ */
+ private function writeSummaryInformation()
+ {
+ // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
+ $data = pack('v', 0xFFFE);
+ // offset: 2; size: 2;
+ $data .= pack('v', 0x0000);
+ // offset: 4; size: 2; OS version
+ $data .= pack('v', 0x0106);
+ // offset: 6; size: 2; OS indicator
+ $data .= pack('v', 0x0002);
+ // offset: 8; size: 16
+ $data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00);
+ // offset: 24; size: 4; section count
+ $data .= pack('V', 0x0001);
+
+ // offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9
+ $data .= pack('vvvvvvvv', 0x85E0, 0xF29F, 0x4FF9, 0x1068, 0x91AB, 0x0008, 0x272B, 0xD9B3);
+ // offset: 44; size: 4; offset of the start
+ $data .= pack('V', 0x30);
+
+ // SECTION
+ $dataSection = [];
+ $dataSection_NumProps = 0;
+ $dataSection_Summary = '';
+ $dataSection_Content = '';
+
+ // CodePage : CP-1252
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x01],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x02], // 2 byte signed integer
+ 'data' => ['data' => 1252],
+ ];
+ ++$dataSection_NumProps;
+
+ // Title
+ if ($this->spreadsheet->getProperties()->getTitle()) {
+ $dataProp = $this->spreadsheet->getProperties()->getTitle();
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x02],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ // Subject
+ if ($this->spreadsheet->getProperties()->getSubject()) {
+ $dataProp = $this->spreadsheet->getProperties()->getSubject();
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x03],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ // Author (Creator)
+ if ($this->spreadsheet->getProperties()->getCreator()) {
+ $dataProp = $this->spreadsheet->getProperties()->getCreator();
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x04],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ // Keywords
+ if ($this->spreadsheet->getProperties()->getKeywords()) {
+ $dataProp = $this->spreadsheet->getProperties()->getKeywords();
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x05],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ // Comments (Description)
+ if ($this->spreadsheet->getProperties()->getDescription()) {
+ $dataProp = $this->spreadsheet->getProperties()->getDescription();
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x06],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ // Last Saved By (LastModifiedBy)
+ if ($this->spreadsheet->getProperties()->getLastModifiedBy()) {
+ $dataProp = $this->spreadsheet->getProperties()->getLastModifiedBy();
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x08],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ // Created Date/Time
+ if ($this->spreadsheet->getProperties()->getCreated()) {
+ $dataProp = $this->spreadsheet->getProperties()->getCreated();
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x0C],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x40], // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
+ 'data' => ['data' => OLE::localDateToOLE($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ // Modified Date/Time
+ if ($this->spreadsheet->getProperties()->getModified()) {
+ $dataProp = $this->spreadsheet->getProperties()->getModified();
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x0D],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x40], // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
+ 'data' => ['data' => OLE::localDateToOLE($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ // Security
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x13],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x03], // 4 byte signed integer
+ 'data' => ['data' => 0x00],
+ ];
+ ++$dataSection_NumProps;
+
+ // 4 Section Length
+ // 4 Property count
+ // 8 * $dataSection_NumProps (8 = ID (4) + OffSet(4))
+ $dataSection_Content_Offset = 8 + $dataSection_NumProps * 8;
+ foreach ($dataSection as $dataProp) {
+ // Summary
+ $dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']);
+ // Offset
+ $dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset);
+ // DataType
+ $dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']);
+ // Data
+ if ($dataProp['type']['data'] == 0x02) { // 2 byte signed integer
+ $dataSection_Content .= pack('V', $dataProp['data']['data']);
+
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x03) { // 4 byte signed integer
+ $dataSection_Content .= pack('V', $dataProp['data']['data']);
+
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length
+ // Null-terminated string
+ $dataProp['data']['data'] .= chr(0);
+ ++$dataProp['data']['length'];
+ // Complete the string with null string for being a %4
+ $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4) == 4 ? 0 : (4 - $dataProp['data']['length'] % 4));
+ $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT);
+
+ $dataSection_Content .= pack('V', $dataProp['data']['length']);
+ $dataSection_Content .= $dataProp['data']['data'];
+
+ $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']);
+ } elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
+ $dataSection_Content .= $dataProp['data']['data'];
+
+ $dataSection_Content_Offset += 4 + 8;
+ }
+ // Data Type Not Used at the moment
+ }
+ // Now $dataSection_Content_Offset contains the size of the content
+
+ // section header
+ // offset: $secOffset; size: 4; section length
+ // + x Size of the content (summary + content)
+ $data .= pack('V', $dataSection_Content_Offset);
+ // offset: $secOffset+4; size: 4; property count
+ $data .= pack('V', $dataSection_NumProps);
+ // Section Summary
+ $data .= $dataSection_Summary;
+ // Section Content
+ $data .= $dataSection_Content;
+
+ return $data;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php
new file mode 100755
index 0000000..84e27d0
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php
@@ -0,0 +1,224 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+// Original file header of PEAR::Spreadsheet_Excel_Writer_BIFFwriter (used as the base for this class):
+// -----------------------------------------------------------------------------------------
+// * Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
+// *
+// * The majority of this is _NOT_ my code. I simply ported it from the
+// * PERL Spreadsheet::WriteExcel module.
+// *
+// * The author of the Spreadsheet::WriteExcel module is John McNamara
+// * <jmcnamara@cpan.org>
+// *
+// * I _DO_ maintain this code, and John McNamara has nothing to do with the
+// * porting of this code to PHP. Any questions directly related to this
+// * class library should be directed to me.
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class BIFFwriter
+{
+ /**
+ * The byte order of this architecture. 0 => little endian, 1 => big endian.
+ *
+ * @var int
+ */
+ private static $byteOrder;
+
+ /**
+ * The string containing the data of the BIFF stream.
+ *
+ * @var string
+ */
+ public $_data;
+
+ /**
+ * The size of the data in bytes. Should be the same as strlen($this->_data).
+ *
+ * @var int
+ */
+ public $_datasize;
+
+ /**
+ * The maximum length for a BIFF record (excluding record header and length field). See addContinue().
+ *
+ * @var int
+ *
+ * @see addContinue()
+ */
+ private $limit = 8224;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->_data = '';
+ $this->_datasize = 0;
+ }
+
+ /**
+ * Determine the byte order and store it as class data to avoid
+ * recalculating it for each call to new().
+ *
+ * @return int
+ */
+ public static function getByteOrder()
+ {
+ if (!isset(self::$byteOrder)) {
+ // Check if "pack" gives the required IEEE 64bit float
+ $teststr = pack('d', 1.2345);
+ $number = pack('C8', 0x8D, 0x97, 0x6E, 0x12, 0x83, 0xC0, 0xF3, 0x3F);
+ if ($number == $teststr) {
+ $byte_order = 0; // Little Endian
+ } elseif ($number == strrev($teststr)) {
+ $byte_order = 1; // Big Endian
+ } else {
+ // Give up. I'll fix this in a later version.
+ throw new WriterException('Required floating point format not supported on this platform.');
+ }
+ self::$byteOrder = $byte_order;
+ }
+
+ return self::$byteOrder;
+ }
+
+ /**
+ * General storage function.
+ *
+ * @param string $data binary data to append
+ */
+ protected function append($data): void
+ {
+ if (strlen($data) - 4 > $this->limit) {
+ $data = $this->addContinue($data);
+ }
+ $this->_data .= $data;
+ $this->_datasize += strlen($data);
+ }
+
+ /**
+ * General storage function like append, but returns string instead of modifying $this->_data.
+ *
+ * @param string $data binary data to write
+ *
+ * @return string
+ */
+ public function writeData($data)
+ {
+ if (strlen($data) - 4 > $this->limit) {
+ $data = $this->addContinue($data);
+ }
+ $this->_datasize += strlen($data);
+
+ return $data;
+ }
+
+ /**
+ * Writes Excel BOF record to indicate the beginning of a stream or
+ * sub-stream in the BIFF file.
+ *
+ * @param int $type type of BIFF file to write: 0x0005 Workbook,
+ * 0x0010 Worksheet
+ */
+ protected function storeBof($type): void
+ {
+ $record = 0x0809; // Record identifier (BIFF5-BIFF8)
+ $length = 0x0010;
+
+ // by inspection of real files, MS Office Excel 2007 writes the following
+ $unknown = pack('VV', 0x000100D1, 0x00000406);
+
+ $build = 0x0DBB; // Excel 97
+ $year = 0x07CC; // Excel 97
+
+ $version = 0x0600; // BIFF8
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvv', $version, $type, $build, $year);
+ $this->append($header . $data . $unknown);
+ }
+
+ /**
+ * Writes Excel EOF record to indicate the end of a BIFF stream.
+ */
+ protected function storeEof(): void
+ {
+ $record = 0x000A; // Record identifier
+ $length = 0x0000; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $this->append($header);
+ }
+
+ /**
+ * Writes Excel EOF record to indicate the end of a BIFF stream.
+ */
+ public function writeEof()
+ {
+ $record = 0x000A; // Record identifier
+ $length = 0x0000; // Number of bytes to follow
+ $header = pack('vv', $record, $length);
+
+ return $this->writeData($header);
+ }
+
+ /**
+ * Excel limits the size of BIFF records. In Excel 5 the limit is 2084 bytes. In
+ * Excel 97 the limit is 8228 bytes. Records that are longer than these limits
+ * must be split up into CONTINUE blocks.
+ *
+ * This function takes a long BIFF record and inserts CONTINUE records as
+ * necessary.
+ *
+ * @param string $data The original binary data to be written
+ *
+ * @return string A very convenient string of continue blocks
+ */
+ private function addContinue($data)
+ {
+ $limit = $this->limit;
+ $record = 0x003C; // Record identifier
+
+ // The first 2080/8224 bytes remain intact. However, we have to change
+ // the length field of the record.
+ $tmp = substr($data, 0, 2) . pack('v', $limit) . substr($data, 4, $limit);
+
+ $header = pack('vv', $record, $limit); // Headers for continue records
+
+ // Retrieve chunks of 2080/8224 bytes +4 for the header.
+ $data_length = strlen($data);
+ for ($i = $limit + 4; $i < ($data_length - $limit); $i += $limit) {
+ $tmp .= $header;
+ $tmp .= substr($data, $i, $limit);
+ }
+
+ // Retrieve the last chunk of data
+ $header = pack('vv', $record, strlen($data) - $i);
+ $tmp .= $header;
+ $tmp .= substr($data, $i);
+
+ return $tmp;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php
new file mode 100755
index 0000000..1ee2e90
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php
@@ -0,0 +1,510 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
+use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip;
+
+class Escher
+{
+ /**
+ * The object we are writing.
+ */
+ private $object;
+
+ /**
+ * The written binary data.
+ */
+ private $data;
+
+ /**
+ * Shape offsets. Positions in binary stream where a new shape record begins.
+ *
+ * @var array
+ */
+ private $spOffsets;
+
+ /**
+ * Shape types.
+ *
+ * @var array
+ */
+ private $spTypes;
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $object
+ */
+ public function __construct($object)
+ {
+ $this->object = $object;
+ }
+
+ /**
+ * Process the object to be written.
+ *
+ * @return string
+ */
+ public function close()
+ {
+ // initialize
+ $this->data = '';
+
+ switch (get_class($this->object)) {
+ case \PhpOffice\PhpSpreadsheet\Shared\Escher::class:
+ if ($dggContainer = $this->object->getDggContainer()) {
+ $writer = new self($dggContainer);
+ $this->data = $writer->close();
+ } elseif ($dgContainer = $this->object->getDgContainer()) {
+ $writer = new self($dgContainer);
+ $this->data = $writer->close();
+ $this->spOffsets = $writer->getSpOffsets();
+ $this->spTypes = $writer->getSpTypes();
+ }
+
+ break;
+ case DggContainer::class:
+ // this is a container record
+
+ // initialize
+ $innerData = '';
+
+ // write the dgg
+ $recVer = 0x0;
+ $recInstance = 0x0000;
+ $recType = 0xF006;
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ // dgg data
+ $dggData =
+ pack(
+ 'VVVV',
+ $this->object->getSpIdMax(), // maximum shape identifier increased by one
+ $this->object->getCDgSaved() + 1, // number of file identifier clusters increased by one
+ $this->object->getCSpSaved(),
+ $this->object->getCDgSaved() // count total number of drawings saved
+ );
+
+ // add file identifier clusters (one per drawing)
+ $IDCLs = $this->object->getIDCLs();
+
+ foreach ($IDCLs as $dgId => $maxReducedSpId) {
+ $dggData .= pack('VV', $dgId, $maxReducedSpId + 1);
+ }
+
+ $header = pack('vvV', $recVerInstance, $recType, strlen($dggData));
+ $innerData .= $header . $dggData;
+
+ // write the bstoreContainer
+ if ($bstoreContainer = $this->object->getBstoreContainer()) {
+ $writer = new self($bstoreContainer);
+ $innerData .= $writer->close();
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = 0x0000;
+ $recType = 0xF000;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $innerData;
+
+ break;
+ case BstoreContainer::class:
+ // this is a container record
+
+ // initialize
+ $innerData = '';
+
+ // treat the inner data
+ if ($BSECollection = $this->object->getBSECollection()) {
+ foreach ($BSECollection as $BSE) {
+ $writer = new self($BSE);
+ $innerData .= $writer->close();
+ }
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = count($this->object->getBSECollection());
+ $recType = 0xF001;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $innerData;
+
+ break;
+ case BSE::class:
+ // this is a semi-container record
+
+ // initialize
+ $innerData = '';
+
+ // here we treat the inner data
+ if ($blip = $this->object->getBlip()) {
+ $writer = new self($blip);
+ $innerData .= $writer->close();
+ }
+
+ // initialize
+ $data = '';
+
+ $btWin32 = $this->object->getBlipType();
+ $btMacOS = $this->object->getBlipType();
+ $data .= pack('CC', $btWin32, $btMacOS);
+
+ $rgbUid = pack('VVVV', 0, 0, 0, 0); // todo
+ $data .= $rgbUid;
+
+ $tag = 0;
+ $size = strlen($innerData);
+ $cRef = 1;
+ $foDelay = 0; //todo
+ $unused1 = 0x0;
+ $cbName = 0x0;
+ $unused2 = 0x0;
+ $unused3 = 0x0;
+ $data .= pack('vVVVCCCC', $tag, $size, $cRef, $foDelay, $unused1, $cbName, $unused2, $unused3);
+
+ $data .= $innerData;
+
+ // write the record
+ $recVer = 0x2;
+ $recInstance = $this->object->getBlipType();
+ $recType = 0xF007;
+ $length = strlen($data);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header;
+
+ $this->data .= $data;
+
+ break;
+ case Blip::class:
+ // this is an atom record
+
+ // write the record
+ switch ($this->object->getParent()->getBlipType()) {
+ case BSE::BLIPTYPE_JPEG:
+ // initialize
+ $innerData = '';
+
+ $rgbUid1 = pack('VVVV', 0, 0, 0, 0); // todo
+ $innerData .= $rgbUid1;
+
+ $tag = 0xFF; // todo
+ $innerData .= pack('C', $tag);
+
+ $innerData .= $this->object->getData();
+
+ $recVer = 0x0;
+ $recInstance = 0x46A;
+ $recType = 0xF01D;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header;
+
+ $this->data .= $innerData;
+
+ break;
+ case BSE::BLIPTYPE_PNG:
+ // initialize
+ $innerData = '';
+
+ $rgbUid1 = pack('VVVV', 0, 0, 0, 0); // todo
+ $innerData .= $rgbUid1;
+
+ $tag = 0xFF; // todo
+ $innerData .= pack('C', $tag);
+
+ $innerData .= $this->object->getData();
+
+ $recVer = 0x0;
+ $recInstance = 0x6E0;
+ $recType = 0xF01E;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header;
+
+ $this->data .= $innerData;
+
+ break;
+ }
+
+ break;
+ case DgContainer::class:
+ // this is a container record
+
+ // initialize
+ $innerData = '';
+
+ // write the dg
+ $recVer = 0x0;
+ $recInstance = $this->object->getDgId();
+ $recType = 0xF008;
+ $length = 8;
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ // number of shapes in this drawing (including group shape)
+ $countShapes = count($this->object->getSpgrContainer()->getChildren());
+ $innerData .= $header . pack('VV', $countShapes, $this->object->getLastSpId());
+
+ // write the spgrContainer
+ if ($spgrContainer = $this->object->getSpgrContainer()) {
+ $writer = new self($spgrContainer);
+ $innerData .= $writer->close();
+
+ // get the shape offsets relative to the spgrContainer record
+ $spOffsets = $writer->getSpOffsets();
+ $spTypes = $writer->getSpTypes();
+
+ // save the shape offsets relative to dgContainer
+ foreach ($spOffsets as &$spOffset) {
+ $spOffset += 24; // add length of dgContainer header data (8 bytes) plus dg data (16 bytes)
+ }
+
+ $this->spOffsets = $spOffsets;
+ $this->spTypes = $spTypes;
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = 0x0000;
+ $recType = 0xF002;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $innerData;
+
+ break;
+ case SpgrContainer::class:
+ // this is a container record
+
+ // initialize
+ $innerData = '';
+
+ // initialize spape offsets
+ $totalSize = 8;
+ $spOffsets = [];
+ $spTypes = [];
+
+ // treat the inner data
+ foreach ($this->object->getChildren() as $spContainer) {
+ $writer = new self($spContainer);
+ $spData = $writer->close();
+ $innerData .= $spData;
+
+ // save the shape offsets (where new shape records begin)
+ $totalSize += strlen($spData);
+ $spOffsets[] = $totalSize;
+
+ $spTypes = array_merge($spTypes, $writer->getSpTypes());
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = 0x0000;
+ $recType = 0xF003;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $innerData;
+ $this->spOffsets = $spOffsets;
+ $this->spTypes = $spTypes;
+
+ break;
+ case SpContainer::class:
+ // initialize
+ $data = '';
+
+ // build the data
+
+ // write group shape record, if necessary?
+ if ($this->object->getSpgr()) {
+ $recVer = 0x1;
+ $recInstance = 0x0000;
+ $recType = 0xF009;
+ $length = 0x00000010;
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $data .= $header . pack('VVVV', 0, 0, 0, 0);
+ }
+ $this->spTypes[] = ($this->object->getSpType());
+
+ // write the shape record
+ $recVer = 0x2;
+ $recInstance = $this->object->getSpType(); // shape type
+ $recType = 0xF00A;
+ $length = 0x00000008;
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $data .= $header . pack('VV', $this->object->getSpId(), $this->object->getSpgr() ? 0x0005 : 0x0A00);
+
+ // the options
+ if ($this->object->getOPTCollection()) {
+ $optData = '';
+
+ $recVer = 0x3;
+ $recInstance = count($this->object->getOPTCollection());
+ $recType = 0xF00B;
+ foreach ($this->object->getOPTCollection() as $property => $value) {
+ $optData .= pack('vV', $property, $value);
+ }
+ $length = strlen($optData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+ $data .= $header . $optData;
+ }
+
+ // the client anchor
+ if ($this->object->getStartCoordinates()) {
+ $clientAnchorData = '';
+
+ $recVer = 0x0;
+ $recInstance = 0x0;
+ $recType = 0xF010;
+
+ // start coordinates
+ [$column, $row] = Coordinate::coordinateFromString($this->object->getStartCoordinates());
+ $c1 = Coordinate::columnIndexFromString($column) - 1;
+ $r1 = $row - 1;
+
+ // start offsetX
+ $startOffsetX = $this->object->getStartOffsetX();
+
+ // start offsetY
+ $startOffsetY = $this->object->getStartOffsetY();
+
+ // end coordinates
+ [$column, $row] = Coordinate::coordinateFromString($this->object->getEndCoordinates());
+ $c2 = Coordinate::columnIndexFromString($column) - 1;
+ $r2 = $row - 1;
+
+ // end offsetX
+ $endOffsetX = $this->object->getEndOffsetX();
+
+ // end offsetY
+ $endOffsetY = $this->object->getEndOffsetY();
+
+ $clientAnchorData = pack('vvvvvvvvv', $this->object->getSpFlag(), $c1, $startOffsetX, $r1, $startOffsetY, $c2, $endOffsetX, $r2, $endOffsetY);
+
+ $length = strlen($clientAnchorData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+ $data .= $header . $clientAnchorData;
+ }
+
+ // the client data, just empty for now
+ if (!$this->object->getSpgr()) {
+ $clientDataData = '';
+
+ $recVer = 0x0;
+ $recInstance = 0x0;
+ $recType = 0xF011;
+
+ $length = strlen($clientDataData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+ $data .= $header . $clientDataData;
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = 0x0000;
+ $recType = 0xF004;
+ $length = strlen($data);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $data;
+
+ break;
+ }
+
+ return $this->data;
+ }
+
+ /**
+ * Gets the shape offsets.
+ *
+ * @return array
+ */
+ public function getSpOffsets()
+ {
+ return $this->spOffsets;
+ }
+
+ /**
+ * Gets the shape types.
+ *
+ * @return array
+ */
+ public function getSpTypes()
+ {
+ return $this->spTypes;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php
new file mode 100755
index 0000000..9cb31ea
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class Font
+{
+ /**
+ * Color index.
+ *
+ * @var int
+ */
+ private $colorIndex;
+
+ /**
+ * Font.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Style\Font
+ */
+ private $font;
+
+ /**
+ * Constructor.
+ */
+ public function __construct(\PhpOffice\PhpSpreadsheet\Style\Font $font)
+ {
+ $this->colorIndex = 0x7FFF;
+ $this->font = $font;
+ }
+
+ /**
+ * Set the color index.
+ *
+ * @param int $colorIndex
+ */
+ public function setColorIndex($colorIndex): void
+ {
+ $this->colorIndex = $colorIndex;
+ }
+
+ /**
+ * Get font record data.
+ *
+ * @return string
+ */
+ public function writeFont()
+ {
+ $font_outline = 0;
+ $font_shadow = 0;
+
+ $icv = $this->colorIndex; // Index to color palette
+ if ($this->font->getSuperscript()) {
+ $sss = 1;
+ } elseif ($this->font->getSubscript()) {
+ $sss = 2;
+ } else {
+ $sss = 0;
+ }
+ $bFamily = 0; // Font family
+ $bCharSet = \PhpOffice\PhpSpreadsheet\Shared\Font::getCharsetFromFontName($this->font->getName()); // Character set
+
+ $record = 0x31; // Record identifier
+ $reserved = 0x00; // Reserved
+ $grbit = 0x00; // Font attributes
+ if ($this->font->getItalic()) {
+ $grbit |= 0x02;
+ }
+ if ($this->font->getStrikethrough()) {
+ $grbit |= 0x08;
+ }
+ if ($font_outline) {
+ $grbit |= 0x10;
+ }
+ if ($font_shadow) {
+ $grbit |= 0x20;
+ }
+
+ $data = pack(
+ 'vvvvvCCCC',
+ // Fontsize (in twips)
+ $this->font->getSize() * 20,
+ $grbit,
+ // Colour
+ $icv,
+ // Font weight
+ self::mapBold($this->font->getBold()),
+ // Superscript/Subscript
+ $sss,
+ self::mapUnderline($this->font->getUnderline()),
+ $bFamily,
+ $bCharSet,
+ $reserved
+ );
+ $data .= StringHelper::UTF8toBIFF8UnicodeShort($this->font->getName());
+
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ return $header . $data;
+ }
+
+ /**
+ * Map to BIFF5-BIFF8 codes for bold.
+ *
+ * @param bool $bold
+ *
+ * @return int
+ */
+ private static function mapBold($bold)
+ {
+ if ($bold) {
+ return 0x2BC; // 700 = Bold font weight
+ }
+
+ return 0x190; // 400 = Normal font weight
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for underline styles.
+ *
+ * @var array of int
+ */
+ private static $mapUnderline = [
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE => 0x00,
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE => 0x01,
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE => 0x02,
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLEACCOUNTING => 0x21,
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLEACCOUNTING => 0x22,
+ ];
+
+ /**
+ * Map underline.
+ *
+ * @param string $underline
+ *
+ * @return int
+ */
+ private static function mapUnderline($underline)
+ {
+ if (isset(self::$mapUnderline[$underline])) {
+ return self::$mapUnderline[$underline];
+ }
+
+ return 0x00;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php
new file mode 100755
index 0000000..5e3316a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php
@@ -0,0 +1,1438 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet as PhpspreadsheetWorksheet;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+// Original file header of PEAR::Spreadsheet_Excel_Writer_Parser (used as the base for this class):
+// -----------------------------------------------------------------------------------------
+// * Class for parsing Excel formulas
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class Parser
+{
+ /** Constants */
+ // Sheet title in unquoted form
+ // Invalid sheet title characters cannot occur in the sheet title:
+ // *:/\?[]
+ // Moreover, there are valid sheet title characters that cannot occur in unquoted form (there may be more?)
+ // +-% '^&<>=,;#()"{}
+ const REGEX_SHEET_TITLE_UNQUOTED = '[^\*\:\/\\\\\?\[\]\+\-\% \\\'\^\&\<\>\=\,\;\#\(\)\"\{\}]+';
+
+ // Sheet title in quoted form (without surrounding quotes)
+ // Invalid sheet title characters cannot occur in the sheet title:
+ // *:/\?[] (usual invalid sheet title characters)
+ // Single quote is represented as a pair ''
+ const REGEX_SHEET_TITLE_QUOTED = '(([^\*\:\/\\\\\?\[\]\\\'])+|(\\\'\\\')+)+';
+
+ /**
+ * The index of the character we are currently looking at.
+ *
+ * @var int
+ */
+ public $currentCharacter;
+
+ /**
+ * The token we are working on.
+ *
+ * @var string
+ */
+ public $currentToken;
+
+ /**
+ * The formula to parse.
+ *
+ * @var string
+ */
+ private $formula;
+
+ /**
+ * The character ahead of the current char.
+ *
+ * @var string
+ */
+ public $lookAhead;
+
+ /**
+ * The parse tree to be generated.
+ *
+ * @var string
+ */
+ private $parseTree;
+
+ /**
+ * Array of external sheets.
+ *
+ * @var array
+ */
+ private $externalSheets;
+
+ /**
+ * Array of sheet references in the form of REF structures.
+ *
+ * @var array
+ */
+ public $references;
+
+ /**
+ * The Excel ptg indices.
+ *
+ * @var array
+ */
+ private $ptg = [
+ 'ptgExp' => 0x01,
+ 'ptgTbl' => 0x02,
+ 'ptgAdd' => 0x03,
+ 'ptgSub' => 0x04,
+ 'ptgMul' => 0x05,
+ 'ptgDiv' => 0x06,
+ 'ptgPower' => 0x07,
+ 'ptgConcat' => 0x08,
+ 'ptgLT' => 0x09,
+ 'ptgLE' => 0x0A,
+ 'ptgEQ' => 0x0B,
+ 'ptgGE' => 0x0C,
+ 'ptgGT' => 0x0D,
+ 'ptgNE' => 0x0E,
+ 'ptgIsect' => 0x0F,
+ 'ptgUnion' => 0x10,
+ 'ptgRange' => 0x11,
+ 'ptgUplus' => 0x12,
+ 'ptgUminus' => 0x13,
+ 'ptgPercent' => 0x14,
+ 'ptgParen' => 0x15,
+ 'ptgMissArg' => 0x16,
+ 'ptgStr' => 0x17,
+ 'ptgAttr' => 0x19,
+ 'ptgSheet' => 0x1A,
+ 'ptgEndSheet' => 0x1B,
+ 'ptgErr' => 0x1C,
+ 'ptgBool' => 0x1D,
+ 'ptgInt' => 0x1E,
+ 'ptgNum' => 0x1F,
+ 'ptgArray' => 0x20,
+ 'ptgFunc' => 0x21,
+ 'ptgFuncVar' => 0x22,
+ 'ptgName' => 0x23,
+ 'ptgRef' => 0x24,
+ 'ptgArea' => 0x25,
+ 'ptgMemArea' => 0x26,
+ 'ptgMemErr' => 0x27,
+ 'ptgMemNoMem' => 0x28,
+ 'ptgMemFunc' => 0x29,
+ 'ptgRefErr' => 0x2A,
+ 'ptgAreaErr' => 0x2B,
+ 'ptgRefN' => 0x2C,
+ 'ptgAreaN' => 0x2D,
+ 'ptgMemAreaN' => 0x2E,
+ 'ptgMemNoMemN' => 0x2F,
+ 'ptgNameX' => 0x39,
+ 'ptgRef3d' => 0x3A,
+ 'ptgArea3d' => 0x3B,
+ 'ptgRefErr3d' => 0x3C,
+ 'ptgAreaErr3d' => 0x3D,
+ 'ptgArrayV' => 0x40,
+ 'ptgFuncV' => 0x41,
+ 'ptgFuncVarV' => 0x42,
+ 'ptgNameV' => 0x43,
+ 'ptgRefV' => 0x44,
+ 'ptgAreaV' => 0x45,
+ 'ptgMemAreaV' => 0x46,
+ 'ptgMemErrV' => 0x47,
+ 'ptgMemNoMemV' => 0x48,
+ 'ptgMemFuncV' => 0x49,
+ 'ptgRefErrV' => 0x4A,
+ 'ptgAreaErrV' => 0x4B,
+ 'ptgRefNV' => 0x4C,
+ 'ptgAreaNV' => 0x4D,
+ 'ptgMemAreaNV' => 0x4E,
+ 'ptgMemNoMemNV' => 0x4F,
+ 'ptgFuncCEV' => 0x58,
+ 'ptgNameXV' => 0x59,
+ 'ptgRef3dV' => 0x5A,
+ 'ptgArea3dV' => 0x5B,
+ 'ptgRefErr3dV' => 0x5C,
+ 'ptgAreaErr3dV' => 0x5D,
+ 'ptgArrayA' => 0x60,
+ 'ptgFuncA' => 0x61,
+ 'ptgFuncVarA' => 0x62,
+ 'ptgNameA' => 0x63,
+ 'ptgRefA' => 0x64,
+ 'ptgAreaA' => 0x65,
+ 'ptgMemAreaA' => 0x66,
+ 'ptgMemErrA' => 0x67,
+ 'ptgMemNoMemA' => 0x68,
+ 'ptgMemFuncA' => 0x69,
+ 'ptgRefErrA' => 0x6A,
+ 'ptgAreaErrA' => 0x6B,
+ 'ptgRefNA' => 0x6C,
+ 'ptgAreaNA' => 0x6D,
+ 'ptgMemAreaNA' => 0x6E,
+ 'ptgMemNoMemNA' => 0x6F,
+ 'ptgFuncCEA' => 0x78,
+ 'ptgNameXA' => 0x79,
+ 'ptgRef3dA' => 0x7A,
+ 'ptgArea3dA' => 0x7B,
+ 'ptgRefErr3dA' => 0x7C,
+ 'ptgAreaErr3dA' => 0x7D,
+ ];
+
+ /**
+ * Thanks to Michael Meeks and Gnumeric for the initial arg values.
+ *
+ * The following hash was generated by "function_locale.pl" in the distro.
+ * Refer to function_locale.pl for non-English function names.
+ *
+ * The array elements are as follow:
+ * ptg: The Excel function ptg code.
+ * args: The number of arguments that the function takes:
+ * >=0 is a fixed number of arguments.
+ * -1 is a variable number of arguments.
+ * class: The reference, value or array class of the function args.
+ * vol: The function is volatile.
+ *
+ * @var array
+ */
+ private $functions = [
+ // function ptg args class vol
+ 'COUNT' => [0, -1, 0, 0],
+ 'IF' => [1, -1, 1, 0],
+ 'ISNA' => [2, 1, 1, 0],
+ 'ISERROR' => [3, 1, 1, 0],
+ 'SUM' => [4, -1, 0, 0],
+ 'AVERAGE' => [5, -1, 0, 0],
+ 'MIN' => [6, -1, 0, 0],
+ 'MAX' => [7, -1, 0, 0],
+ 'ROW' => [8, -1, 0, 0],
+ 'COLUMN' => [9, -1, 0, 0],
+ 'NA' => [10, 0, 0, 0],
+ 'NPV' => [11, -1, 1, 0],
+ 'STDEV' => [12, -1, 0, 0],
+ 'DOLLAR' => [13, -1, 1, 0],
+ 'FIXED' => [14, -1, 1, 0],
+ 'SIN' => [15, 1, 1, 0],
+ 'COS' => [16, 1, 1, 0],
+ 'TAN' => [17, 1, 1, 0],
+ 'ATAN' => [18, 1, 1, 0],
+ 'PI' => [19, 0, 1, 0],
+ 'SQRT' => [20, 1, 1, 0],
+ 'EXP' => [21, 1, 1, 0],
+ 'LN' => [22, 1, 1, 0],
+ 'LOG10' => [23, 1, 1, 0],
+ 'ABS' => [24, 1, 1, 0],
+ 'INT' => [25, 1, 1, 0],
+ 'SIGN' => [26, 1, 1, 0],
+ 'ROUND' => [27, 2, 1, 0],
+ 'LOOKUP' => [28, -1, 0, 0],
+ 'INDEX' => [29, -1, 0, 1],
+ 'REPT' => [30, 2, 1, 0],
+ 'MID' => [31, 3, 1, 0],
+ 'LEN' => [32, 1, 1, 0],
+ 'VALUE' => [33, 1, 1, 0],
+ 'TRUE' => [34, 0, 1, 0],
+ 'FALSE' => [35, 0, 1, 0],
+ 'AND' => [36, -1, 0, 0],
+ 'OR' => [37, -1, 0, 0],
+ 'NOT' => [38, 1, 1, 0],
+ 'MOD' => [39, 2, 1, 0],
+ 'DCOUNT' => [40, 3, 0, 0],
+ 'DSUM' => [41, 3, 0, 0],
+ 'DAVERAGE' => [42, 3, 0, 0],
+ 'DMIN' => [43, 3, 0, 0],
+ 'DMAX' => [44, 3, 0, 0],
+ 'DSTDEV' => [45, 3, 0, 0],
+ 'VAR' => [46, -1, 0, 0],
+ 'DVAR' => [47, 3, 0, 0],
+ 'TEXT' => [48, 2, 1, 0],
+ 'LINEST' => [49, -1, 0, 0],
+ 'TREND' => [50, -1, 0, 0],
+ 'LOGEST' => [51, -1, 0, 0],
+ 'GROWTH' => [52, -1, 0, 0],
+ 'PV' => [56, -1, 1, 0],
+ 'FV' => [57, -1, 1, 0],
+ 'NPER' => [58, -1, 1, 0],
+ 'PMT' => [59, -1, 1, 0],
+ 'RATE' => [60, -1, 1, 0],
+ 'MIRR' => [61, 3, 0, 0],
+ 'IRR' => [62, -1, 0, 0],
+ 'RAND' => [63, 0, 1, 1],
+ 'MATCH' => [64, -1, 0, 0],
+ 'DATE' => [65, 3, 1, 0],
+ 'TIME' => [66, 3, 1, 0],
+ 'DAY' => [67, 1, 1, 0],
+ 'MONTH' => [68, 1, 1, 0],
+ 'YEAR' => [69, 1, 1, 0],
+ 'WEEKDAY' => [70, -1, 1, 0],
+ 'HOUR' => [71, 1, 1, 0],
+ 'MINUTE' => [72, 1, 1, 0],
+ 'SECOND' => [73, 1, 1, 0],
+ 'NOW' => [74, 0, 1, 1],
+ 'AREAS' => [75, 1, 0, 1],
+ 'ROWS' => [76, 1, 0, 1],
+ 'COLUMNS' => [77, 1, 0, 1],
+ 'OFFSET' => [78, -1, 0, 1],
+ 'SEARCH' => [82, -1, 1, 0],
+ 'TRANSPOSE' => [83, 1, 1, 0],
+ 'TYPE' => [86, 1, 1, 0],
+ 'ATAN2' => [97, 2, 1, 0],
+ 'ASIN' => [98, 1, 1, 0],
+ 'ACOS' => [99, 1, 1, 0],
+ 'CHOOSE' => [100, -1, 1, 0],
+ 'HLOOKUP' => [101, -1, 0, 0],
+ 'VLOOKUP' => [102, -1, 0, 0],
+ 'ISREF' => [105, 1, 0, 0],
+ 'LOG' => [109, -1, 1, 0],
+ 'CHAR' => [111, 1, 1, 0],
+ 'LOWER' => [112, 1, 1, 0],
+ 'UPPER' => [113, 1, 1, 0],
+ 'PROPER' => [114, 1, 1, 0],
+ 'LEFT' => [115, -1, 1, 0],
+ 'RIGHT' => [116, -1, 1, 0],
+ 'EXACT' => [117, 2, 1, 0],
+ 'TRIM' => [118, 1, 1, 0],
+ 'REPLACE' => [119, 4, 1, 0],
+ 'SUBSTITUTE' => [120, -1, 1, 0],
+ 'CODE' => [121, 1, 1, 0],
+ 'FIND' => [124, -1, 1, 0],
+ 'CELL' => [125, -1, 0, 1],
+ 'ISERR' => [126, 1, 1, 0],
+ 'ISTEXT' => [127, 1, 1, 0],
+ 'ISNUMBER' => [128, 1, 1, 0],
+ 'ISBLANK' => [129, 1, 1, 0],
+ 'T' => [130, 1, 0, 0],
+ 'N' => [131, 1, 0, 0],
+ 'DATEVALUE' => [140, 1, 1, 0],
+ 'TIMEVALUE' => [141, 1, 1, 0],
+ 'SLN' => [142, 3, 1, 0],
+ 'SYD' => [143, 4, 1, 0],
+ 'DDB' => [144, -1, 1, 0],
+ 'INDIRECT' => [148, -1, 1, 1],
+ 'CALL' => [150, -1, 1, 0],
+ 'CLEAN' => [162, 1, 1, 0],
+ 'MDETERM' => [163, 1, 2, 0],
+ 'MINVERSE' => [164, 1, 2, 0],
+ 'MMULT' => [165, 2, 2, 0],
+ 'IPMT' => [167, -1, 1, 0],
+ 'PPMT' => [168, -1, 1, 0],
+ 'COUNTA' => [169, -1, 0, 0],
+ 'PRODUCT' => [183, -1, 0, 0],
+ 'FACT' => [184, 1, 1, 0],
+ 'DPRODUCT' => [189, 3, 0, 0],
+ 'ISNONTEXT' => [190, 1, 1, 0],
+ 'STDEVP' => [193, -1, 0, 0],
+ 'VARP' => [194, -1, 0, 0],
+ 'DSTDEVP' => [195, 3, 0, 0],
+ 'DVARP' => [196, 3, 0, 0],
+ 'TRUNC' => [197, -1, 1, 0],
+ 'ISLOGICAL' => [198, 1, 1, 0],
+ 'DCOUNTA' => [199, 3, 0, 0],
+ 'USDOLLAR' => [204, -1, 1, 0],
+ 'FINDB' => [205, -1, 1, 0],
+ 'SEARCHB' => [206, -1, 1, 0],
+ 'REPLACEB' => [207, 4, 1, 0],
+ 'LEFTB' => [208, -1, 1, 0],
+ 'RIGHTB' => [209, -1, 1, 0],
+ 'MIDB' => [210, 3, 1, 0],
+ 'LENB' => [211, 1, 1, 0],
+ 'ROUNDUP' => [212, 2, 1, 0],
+ 'ROUNDDOWN' => [213, 2, 1, 0],
+ 'ASC' => [214, 1, 1, 0],
+ 'DBCS' => [215, 1, 1, 0],
+ 'RANK' => [216, -1, 0, 0],
+ 'ADDRESS' => [219, -1, 1, 0],
+ 'DAYS360' => [220, -1, 1, 0],
+ 'TODAY' => [221, 0, 1, 1],
+ 'VDB' => [222, -1, 1, 0],
+ 'MEDIAN' => [227, -1, 0, 0],
+ 'SUMPRODUCT' => [228, -1, 2, 0],
+ 'SINH' => [229, 1, 1, 0],
+ 'COSH' => [230, 1, 1, 0],
+ 'TANH' => [231, 1, 1, 0],
+ 'ASINH' => [232, 1, 1, 0],
+ 'ACOSH' => [233, 1, 1, 0],
+ 'ATANH' => [234, 1, 1, 0],
+ 'DGET' => [235, 3, 0, 0],
+ 'INFO' => [244, 1, 1, 1],
+ 'DB' => [247, -1, 1, 0],
+ 'FREQUENCY' => [252, 2, 0, 0],
+ 'ERROR.TYPE' => [261, 1, 1, 0],
+ 'REGISTER.ID' => [267, -1, 1, 0],
+ 'AVEDEV' => [269, -1, 0, 0],
+ 'BETADIST' => [270, -1, 1, 0],
+ 'GAMMALN' => [271, 1, 1, 0],
+ 'BETAINV' => [272, -1, 1, 0],
+ 'BINOMDIST' => [273, 4, 1, 0],
+ 'CHIDIST' => [274, 2, 1, 0],
+ 'CHIINV' => [275, 2, 1, 0],
+ 'COMBIN' => [276, 2, 1, 0],
+ 'CONFIDENCE' => [277, 3, 1, 0],
+ 'CRITBINOM' => [278, 3, 1, 0],
+ 'EVEN' => [279, 1, 1, 0],
+ 'EXPONDIST' => [280, 3, 1, 0],
+ 'FDIST' => [281, 3, 1, 0],
+ 'FINV' => [282, 3, 1, 0],
+ 'FISHER' => [283, 1, 1, 0],
+ 'FISHERINV' => [284, 1, 1, 0],
+ 'FLOOR' => [285, 2, 1, 0],
+ 'GAMMADIST' => [286, 4, 1, 0],
+ 'GAMMAINV' => [287, 3, 1, 0],
+ 'CEILING' => [288, 2, 1, 0],
+ 'HYPGEOMDIST' => [289, 4, 1, 0],
+ 'LOGNORMDIST' => [290, 3, 1, 0],
+ 'LOGINV' => [291, 3, 1, 0],
+ 'NEGBINOMDIST' => [292, 3, 1, 0],
+ 'NORMDIST' => [293, 4, 1, 0],
+ 'NORMSDIST' => [294, 1, 1, 0],
+ 'NORMINV' => [295, 3, 1, 0],
+ 'NORMSINV' => [296, 1, 1, 0],
+ 'STANDARDIZE' => [297, 3, 1, 0],
+ 'ODD' => [298, 1, 1, 0],
+ 'PERMUT' => [299, 2, 1, 0],
+ 'POISSON' => [300, 3, 1, 0],
+ 'TDIST' => [301, 3, 1, 0],
+ 'WEIBULL' => [302, 4, 1, 0],
+ 'SUMXMY2' => [303, 2, 2, 0],
+ 'SUMX2MY2' => [304, 2, 2, 0],
+ 'SUMX2PY2' => [305, 2, 2, 0],
+ 'CHITEST' => [306, 2, 2, 0],
+ 'CORREL' => [307, 2, 2, 0],
+ 'COVAR' => [308, 2, 2, 0],
+ 'FORECAST' => [309, 3, 2, 0],
+ 'FTEST' => [310, 2, 2, 0],
+ 'INTERCEPT' => [311, 2, 2, 0],
+ 'PEARSON' => [312, 2, 2, 0],
+ 'RSQ' => [313, 2, 2, 0],
+ 'STEYX' => [314, 2, 2, 0],
+ 'SLOPE' => [315, 2, 2, 0],
+ 'TTEST' => [316, 4, 2, 0],
+ 'PROB' => [317, -1, 2, 0],
+ 'DEVSQ' => [318, -1, 0, 0],
+ 'GEOMEAN' => [319, -1, 0, 0],
+ 'HARMEAN' => [320, -1, 0, 0],
+ 'SUMSQ' => [321, -1, 0, 0],
+ 'KURT' => [322, -1, 0, 0],
+ 'SKEW' => [323, -1, 0, 0],
+ 'ZTEST' => [324, -1, 0, 0],
+ 'LARGE' => [325, 2, 0, 0],
+ 'SMALL' => [326, 2, 0, 0],
+ 'QUARTILE' => [327, 2, 0, 0],
+ 'PERCENTILE' => [328, 2, 0, 0],
+ 'PERCENTRANK' => [329, -1, 0, 0],
+ 'MODE' => [330, -1, 2, 0],
+ 'TRIMMEAN' => [331, 2, 0, 0],
+ 'TINV' => [332, 2, 1, 0],
+ 'CONCATENATE' => [336, -1, 1, 0],
+ 'POWER' => [337, 2, 1, 0],
+ 'RADIANS' => [342, 1, 1, 0],
+ 'DEGREES' => [343, 1, 1, 0],
+ 'SUBTOTAL' => [344, -1, 0, 0],
+ 'SUMIF' => [345, -1, 0, 0],
+ 'COUNTIF' => [346, 2, 0, 0],
+ 'COUNTBLANK' => [347, 1, 0, 0],
+ 'ISPMT' => [350, 4, 1, 0],
+ 'DATEDIF' => [351, 3, 1, 0],
+ 'DATESTRING' => [352, 1, 1, 0],
+ 'NUMBERSTRING' => [353, 2, 1, 0],
+ 'ROMAN' => [354, -1, 1, 0],
+ 'GETPIVOTDATA' => [358, -1, 0, 0],
+ 'HYPERLINK' => [359, -1, 1, 0],
+ 'PHONETIC' => [360, 1, 0, 0],
+ 'AVERAGEA' => [361, -1, 0, 0],
+ 'MAXA' => [362, -1, 0, 0],
+ 'MINA' => [363, -1, 0, 0],
+ 'STDEVPA' => [364, -1, 0, 0],
+ 'VARPA' => [365, -1, 0, 0],
+ 'STDEVA' => [366, -1, 0, 0],
+ 'VARA' => [367, -1, 0, 0],
+ 'BAHTTEXT' => [368, 1, 0, 0],
+ ];
+
+ /**
+ * The class constructor.
+ */
+ public function __construct()
+ {
+ $this->currentCharacter = 0;
+ $this->currentToken = ''; // The token we are working on.
+ $this->formula = ''; // The formula to parse.
+ $this->lookAhead = ''; // The character ahead of the current char.
+ $this->parseTree = ''; // The parse tree to be generated.
+ $this->externalSheets = [];
+ $this->references = [];
+ }
+
+ /**
+ * Convert a token to the proper ptg value.
+ *
+ * @param mixed $token the token to convert
+ *
+ * @return mixed the converted token on success
+ */
+ private function convert($token)
+ {
+ if (preg_match('/"([^"]|""){0,255}"/', $token)) {
+ return $this->convertString($token);
+ } elseif (is_numeric($token)) {
+ return $this->convertNumber($token);
+ // match references like A1 or $A$1
+ } elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/', $token)) {
+ return $this->convertRef2d($token);
+ // match external references like Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?(\\d+)$/u', $token)) {
+ return $this->convertRef3d($token);
+ // match external references like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?(\\d+)$/u", $token)) {
+ return $this->convertRef3d($token);
+ // match ranges like A1:B2 or $A$1:$B$2
+ } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/', $token)) {
+ return $this->convertRange2d($token);
+ // match external ranges like Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?(\\d+)\\:\$?([A-Ia-i]?[A-Za-z])?\$?(\\d+)$/u', $token)) {
+ return $this->convertRange3d($token);
+ // match external ranges like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?(\\d+)\\:\\$?([A-Ia-i]?[A-Za-z])?\\$?(\\d+)$/u", $token)) {
+ return $this->convertRange3d($token);
+ // operators (including parentheses)
+ } elseif (isset($this->ptg[$token])) {
+ return pack('C', $this->ptg[$token]);
+ // match error codes
+ } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) || $token == '#N/A') {
+ return $this->convertError($token);
+ // commented so argument number can be processed correctly. See toReversePolish().
+ /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/", $token))
+ {
+ return($this->convertFunction($token, $this->_func_args));
+ }*/
+ // if it's an argument, ignore the token (the argument remains)
+ } elseif ($token == 'arg') {
+ return '';
+ }
+
+ // TODO: use real error codes
+ throw new WriterException("Unknown token $token");
+ }
+
+ /**
+ * Convert a number token to ptgInt or ptgNum.
+ *
+ * @param mixed $num an integer or double for conversion to its ptg value
+ *
+ * @return string
+ */
+ private function convertNumber($num)
+ {
+ // Integer in the range 0..2**16-1
+ if ((preg_match('/^\\d+$/', $num)) && ($num <= 65535)) {
+ return pack('Cv', $this->ptg['ptgInt'], $num);
+ }
+
+ // A float
+ if (BIFFwriter::getByteOrder()) { // if it's Big Endian
+ $num = strrev($num);
+ }
+
+ return pack('Cd', $this->ptg['ptgNum'], $num);
+ }
+
+ /**
+ * Convert a string token to ptgStr.
+ *
+ * @param string $string a string for conversion to its ptg value
+ *
+ * @return mixed the converted token on success
+ */
+ private function convertString($string)
+ {
+ // chop away beggining and ending quotes
+ $string = substr($string, 1, -1);
+ if (strlen($string) > 255) {
+ throw new WriterException('String is too long');
+ }
+
+ return pack('C', $this->ptg['ptgStr']) . StringHelper::UTF8toBIFF8UnicodeShort($string);
+ }
+
+ /**
+ * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
+ * args that it takes.
+ *
+ * @param string $token the name of the function for convertion to ptg value
+ * @param int $num_args the number of arguments the function receives
+ *
+ * @return string The packed ptg for the function
+ */
+ private function convertFunction($token, $num_args)
+ {
+ $args = $this->functions[$token][1];
+
+ // Fixed number of args eg. TIME($i, $j, $k).
+ if ($args >= 0) {
+ return pack('Cv', $this->ptg['ptgFuncV'], $this->functions[$token][0]);
+ }
+ // Variable number of args eg. SUM($i, $j, $k, ..).
+ if ($args == -1) {
+ return pack('CCv', $this->ptg['ptgFuncVarV'], $num_args, $this->functions[$token][0]);
+ }
+ }
+
+ /**
+ * Convert an Excel range such as A1:D4 to a ptgRefV.
+ *
+ * @param string $range An Excel range in the A1:A2
+ * @param int $class
+ *
+ * @return string
+ */
+ private function convertRange2d($range, $class = 0)
+ {
+ // TODO: possible class value 0,1,2 check Formula.pm
+ // Split the range into 2 cell refs
+ if (preg_match('/^(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)\:(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)$/', $range)) {
+ [$cell1, $cell2] = explode(':', $range);
+ } else {
+ // TODO: use real error codes
+ throw new WriterException('Unknown range separator');
+ }
+
+ // Convert the cell references
+ [$row1, $col1] = $this->cellToPackedRowcol($cell1);
+ [$row2, $col2] = $this->cellToPackedRowcol($cell2);
+
+ // The ptg value depends on the class of the ptg.
+ if ($class == 0) {
+ $ptgArea = pack('C', $this->ptg['ptgArea']);
+ } elseif ($class == 1) {
+ $ptgArea = pack('C', $this->ptg['ptgAreaV']);
+ } elseif ($class == 2) {
+ $ptgArea = pack('C', $this->ptg['ptgAreaA']);
+ } else {
+ // TODO: use real error codes
+ throw new WriterException("Unknown class $class");
+ }
+
+ return $ptgArea . $row1 . $row2 . $col1 . $col2;
+ }
+
+ /**
+ * Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
+ * a ptgArea3d.
+ *
+ * @param string $token an Excel range in the Sheet1!A1:A2 format
+ *
+ * @return mixed the packed ptgArea3d token on success
+ */
+ private function convertRange3d($token)
+ {
+ // Split the ref at the ! symbol
+ [$ext_ref, $range] = PhpspreadsheetWorksheet::extractSheetTitle($token, true);
+
+ // Convert the external reference part (different for BIFF8)
+ $ext_ref = $this->getRefIndex($ext_ref);
+
+ // Split the range into 2 cell refs
+ [$cell1, $cell2] = explode(':', $range);
+
+ // Convert the cell references
+ if (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\\d+)$/', $cell1)) {
+ [$row1, $col1] = $this->cellToPackedRowcol($cell1);
+ [$row2, $col2] = $this->cellToPackedRowcol($cell2);
+ } else { // It's a rows range (like 26:27)
+ [$row1, $col1, $row2, $col2] = $this->rangeToPackedRange($cell1 . ':' . $cell2);
+ }
+
+ // The ptg value depends on the class of the ptg.
+ $ptgArea = pack('C', $this->ptg['ptgArea3d']);
+
+ return $ptgArea . $ext_ref . $row1 . $row2 . $col1 . $col2;
+ }
+
+ /**
+ * Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
+ *
+ * @param string $cell An Excel cell reference
+ *
+ * @return string The cell in packed() format with the corresponding ptg
+ */
+ private function convertRef2d($cell)
+ {
+ // Convert the cell reference
+ $cell_array = $this->cellToPackedRowcol($cell);
+ [$row, $col] = $cell_array;
+
+ // The ptg value depends on the class of the ptg.
+ $ptgRef = pack('C', $this->ptg['ptgRefA']);
+
+ return $ptgRef . $row . $col;
+ }
+
+ /**
+ * Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
+ * ptgRef3d.
+ *
+ * @param string $cell An Excel cell reference
+ *
+ * @return mixed the packed ptgRef3d token on success
+ */
+ private function convertRef3d($cell)
+ {
+ // Split the ref at the ! symbol
+ [$ext_ref, $cell] = PhpspreadsheetWorksheet::extractSheetTitle($cell, true);
+
+ // Convert the external reference part (different for BIFF8)
+ $ext_ref = $this->getRefIndex($ext_ref);
+
+ // Convert the cell reference part
+ [$row, $col] = $this->cellToPackedRowcol($cell);
+
+ // The ptg value depends on the class of the ptg.
+ $ptgRef = pack('C', $this->ptg['ptgRef3dA']);
+
+ return $ptgRef . $ext_ref . $row . $col;
+ }
+
+ /**
+ * Convert an error code to a ptgErr.
+ *
+ * @param string $errorCode The error code for conversion to its ptg value
+ *
+ * @return string The error code ptgErr
+ */
+ private function convertError($errorCode)
+ {
+ switch ($errorCode) {
+ case '#NULL!':
+ return pack('C', 0x00);
+ case '#DIV/0!':
+ return pack('C', 0x07);
+ case '#VALUE!':
+ return pack('C', 0x0F);
+ case '#REF!':
+ return pack('C', 0x17);
+ case '#NAME?':
+ return pack('C', 0x1D);
+ case '#NUM!':
+ return pack('C', 0x24);
+ case '#N/A':
+ return pack('C', 0x2A);
+ }
+
+ return pack('C', 0xFF);
+ }
+
+ /**
+ * Look up the REF index that corresponds to an external sheet name
+ * (or range). If it doesn't exist yet add it to the workbook's references
+ * array. It assumes all sheet names given must exist.
+ *
+ * @param string $ext_ref The name of the external reference
+ *
+ * @return mixed The reference index in packed() format on success
+ */
+ private function getRefIndex($ext_ref)
+ {
+ $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading ' if any.
+ $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
+ $ext_ref = str_replace('\'\'', '\'', $ext_ref); // Replace escaped '' with '
+
+ // Check if there is a sheet range eg., Sheet1:Sheet2.
+ if (preg_match('/:/', $ext_ref)) {
+ [$sheet_name1, $sheet_name2] = explode(':', $ext_ref);
+
+ $sheet1 = $this->getSheetIndex($sheet_name1);
+ if ($sheet1 == -1) {
+ throw new WriterException("Unknown sheet name $sheet_name1 in formula");
+ }
+ $sheet2 = $this->getSheetIndex($sheet_name2);
+ if ($sheet2 == -1) {
+ throw new WriterException("Unknown sheet name $sheet_name2 in formula");
+ }
+
+ // Reverse max and min sheet numbers if necessary
+ if ($sheet1 > $sheet2) {
+ [$sheet1, $sheet2] = [$sheet2, $sheet1];
+ }
+ } else { // Single sheet name only.
+ $sheet1 = $this->getSheetIndex($ext_ref);
+ if ($sheet1 == -1) {
+ throw new WriterException("Unknown sheet name $ext_ref in formula");
+ }
+ $sheet2 = $sheet1;
+ }
+
+ // assume all references belong to this document
+ $supbook_index = 0x00;
+ $ref = pack('vvv', $supbook_index, $sheet1, $sheet2);
+ $totalreferences = count($this->references);
+ $index = -1;
+ for ($i = 0; $i < $totalreferences; ++$i) {
+ if ($ref == $this->references[$i]) {
+ $index = $i;
+
+ break;
+ }
+ }
+ // if REF was not found add it to references array
+ if ($index == -1) {
+ $this->references[$totalreferences] = $ref;
+ $index = $totalreferences;
+ }
+
+ return pack('v', $index);
+ }
+
+ /**
+ * Look up the index that corresponds to an external sheet name. The hash of
+ * sheet names is updated by the addworksheet() method of the
+ * \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook class.
+ *
+ * @param string $sheet_name Sheet name
+ *
+ * @return int The sheet index, -1 if the sheet was not found
+ */
+ private function getSheetIndex($sheet_name)
+ {
+ if (!isset($this->externalSheets[$sheet_name])) {
+ return -1;
+ }
+
+ return $this->externalSheets[$sheet_name];
+ }
+
+ /**
+ * This method is used to update the array of sheet names. It is
+ * called by the addWorksheet() method of the
+ * \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook class.
+ *
+ * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::addWorksheet()
+ *
+ * @param string $name The name of the worksheet being added
+ * @param int $index The index of the worksheet being added
+ */
+ public function setExtSheet($name, $index): void
+ {
+ $this->externalSheets[$name] = $index;
+ }
+
+ /**
+ * pack() row and column into the required 3 or 4 byte format.
+ *
+ * @param string $cell The Excel cell reference to be packed
+ *
+ * @return array Array containing the row and column in packed() format
+ */
+ private function cellToPackedRowcol($cell)
+ {
+ $cell = strtoupper($cell);
+ [$row, $col, $row_rel, $col_rel] = $this->cellToRowcol($cell);
+ if ($col >= 256) {
+ throw new WriterException("Column in: $cell greater than 255");
+ }
+ if ($row >= 65536) {
+ throw new WriterException("Row in: $cell greater than 65536 ");
+ }
+
+ // Set the high bits to indicate if row or col are relative.
+ $col |= $col_rel << 14;
+ $col |= $row_rel << 15;
+ $col = pack('v', $col);
+
+ $row = pack('v', $row);
+
+ return [$row, $col];
+ }
+
+ /**
+ * pack() row range into the required 3 or 4 byte format.
+ * Just using maximum col/rows, which is probably not the correct solution.
+ *
+ * @param string $range The Excel range to be packed
+ *
+ * @return array Array containing (row1,col1,row2,col2) in packed() format
+ */
+ private function rangeToPackedRange($range)
+ {
+ preg_match('/(\$)?(\d+)\:(\$)?(\d+)/', $range, $match);
+ // return absolute rows if there is a $ in the ref
+ $row1_rel = empty($match[1]) ? 1 : 0;
+ $row1 = $match[2];
+ $row2_rel = empty($match[3]) ? 1 : 0;
+ $row2 = $match[4];
+ // Convert 1-index to zero-index
+ --$row1;
+ --$row2;
+ // Trick poor inocent Excel
+ $col1 = 0;
+ $col2 = 65535; // FIXME: maximum possible value for Excel 5 (change this!!!)
+
+ // FIXME: this changes for BIFF8
+ if (($row1 >= 65536) || ($row2 >= 65536)) {
+ throw new WriterException("Row in: $range greater than 65536 ");
+ }
+
+ // Set the high bits to indicate if rows are relative.
+ $col1 |= $row1_rel << 15;
+ $col2 |= $row2_rel << 15;
+ $col1 = pack('v', $col1);
+ $col2 = pack('v', $col2);
+
+ $row1 = pack('v', $row1);
+ $row2 = pack('v', $row2);
+
+ return [$row1, $col1, $row2, $col2];
+ }
+
+ /**
+ * Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
+ * indexed row and column number. Also returns two (0,1) values to indicate
+ * whether the row or column are relative references.
+ *
+ * @param string $cell the Excel cell reference in A1 format
+ *
+ * @return array
+ */
+ private function cellToRowcol($cell)
+ {
+ preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/', $cell, $match);
+ // return absolute column if there is a $ in the ref
+ $col_rel = empty($match[1]) ? 1 : 0;
+ $col_ref = $match[2];
+ $row_rel = empty($match[3]) ? 1 : 0;
+ $row = $match[4];
+
+ // Convert base26 column string to a number.
+ $expn = strlen($col_ref) - 1;
+ $col = 0;
+ $col_ref_length = strlen($col_ref);
+ for ($i = 0; $i < $col_ref_length; ++$i) {
+ $col += (ord($col_ref[$i]) - 64) * 26 ** $expn;
+ --$expn;
+ }
+
+ // Convert 1-index to zero-index
+ --$row;
+ --$col;
+
+ return [$row, $col, $row_rel, $col_rel];
+ }
+
+ /**
+ * Advance to the next valid token.
+ */
+ private function advance()
+ {
+ $i = $this->currentCharacter;
+ $formula_length = strlen($this->formula);
+ // eat up white spaces
+ if ($i < $formula_length) {
+ while ($this->formula[$i] == ' ') {
+ ++$i;
+ }
+
+ if ($i < ($formula_length - 1)) {
+ $this->lookAhead = $this->formula[$i + 1];
+ }
+ $token = '';
+ }
+
+ while ($i < $formula_length) {
+ $token .= $this->formula[$i];
+
+ if ($i < ($formula_length - 1)) {
+ $this->lookAhead = $this->formula[$i + 1];
+ } else {
+ $this->lookAhead = '';
+ }
+
+ if ($this->match($token) != '') {
+ $this->currentCharacter = $i + 1;
+ $this->currentToken = $token;
+
+ return 1;
+ }
+
+ if ($i < ($formula_length - 2)) {
+ $this->lookAhead = $this->formula[$i + 2];
+ } else { // if we run out of characters lookAhead becomes empty
+ $this->lookAhead = '';
+ }
+ ++$i;
+ }
+ //die("Lexical error ".$this->currentCharacter);
+ }
+
+ /**
+ * Checks if it's a valid token.
+ *
+ * @param mixed $token the token to check
+ *
+ * @return mixed The checked token or false on failure
+ */
+ private function match($token)
+ {
+ switch ($token) {
+ case '+':
+ case '-':
+ case '*':
+ case '/':
+ case '(':
+ case ')':
+ case ',':
+ case ';':
+ case '>=':
+ case '<=':
+ case '=':
+ case '<>':
+ case '^':
+ case '&':
+ case '%':
+ return $token;
+
+ break;
+ case '>':
+ if ($this->lookAhead === '=') { // it's a GE token
+ break;
+ }
+
+ return $token;
+
+ break;
+ case '<':
+ // it's a LE or a NE token
+ if (($this->lookAhead === '=') || ($this->lookAhead === '>')) {
+ break;
+ }
+
+ return $token;
+
+ break;
+ default:
+ // if it's a reference A1 or $A$1 or $A1 or A$1
+ if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?\d+$/', $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.') && ($this->lookAhead !== '!')) {
+ return $token;
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?\\d+$/u', $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.')) {
+ // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1)
+ return $token;
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?\\d+$/u", $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.')) {
+ // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1)
+ return $token;
+ } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+:(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $token) && !preg_match('/\d/', $this->lookAhead)) {
+ // if it's a range A1:A2 or $A$1:$A$2
+ return $token;
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?\\d+:\$?([A-Ia-i]?[A-Za-z])?\$?\\d+$/u', $token) && !preg_match('/\d/', $this->lookAhead)) {
+ // If it's an external range like Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2
+ return $token;
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+:\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+$/u", $token) && !preg_match('/\d/', $this->lookAhead)) {
+ // If it's an external range like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2
+ return $token;
+ } elseif (is_numeric($token) && (!is_numeric($token . $this->lookAhead) || ($this->lookAhead == '')) && ($this->lookAhead !== '!') && ($this->lookAhead !== ':')) {
+ // If it's a number (check that it's not a sheet name or range)
+ return $token;
+ } elseif (preg_match('/"([^"]|""){0,255}"/', $token) && $this->lookAhead !== '"' && (substr_count($token, '"') % 2 == 0)) {
+ // If it's a string (of maximum 255 characters)
+ return $token;
+ } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) || $token === '#N/A') {
+ // If it's an error code
+ return $token;
+ } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $token) && ($this->lookAhead === '(')) {
+ // if it's a function call
+ return $token;
+ } elseif (substr($token, -1) === ')') {
+ // It's an argument of some description (e.g. a named range),
+ // precise nature yet to be determined
+ return $token;
+ }
+
+ return '';
+ }
+ }
+
+ /**
+ * The parsing method. It parses a formula.
+ *
+ * @param string $formula the formula to parse, without the initial equal
+ * sign (=)
+ *
+ * @return mixed true on success
+ */
+ public function parse($formula)
+ {
+ $this->currentCharacter = 0;
+ $this->formula = (string) $formula;
+ $this->lookAhead = $formula[1] ?? '';
+ $this->advance();
+ $this->parseTree = $this->condition();
+
+ return true;
+ }
+
+ /**
+ * It parses a condition. It assumes the following rule:
+ * Cond -> Expr [(">" | "<") Expr].
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function condition()
+ {
+ $result = $this->expression();
+ if ($this->currentToken == '<') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgLT', $result, $result2);
+ } elseif ($this->currentToken == '>') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgGT', $result, $result2);
+ } elseif ($this->currentToken == '<=') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgLE', $result, $result2);
+ } elseif ($this->currentToken == '>=') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgGE', $result, $result2);
+ } elseif ($this->currentToken == '=') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgEQ', $result, $result2);
+ } elseif ($this->currentToken == '<>') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgNE', $result, $result2);
+ } elseif ($this->currentToken == '&') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgConcat', $result, $result2);
+ }
+
+ return $result;
+ }
+
+ /**
+ * It parses a expression. It assumes the following rule:
+ * Expr -> Term [("+" | "-") Term]
+ * -> "string"
+ * -> "-" Term : Negative value
+ * -> "+" Term : Positive value
+ * -> Error code.
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function expression()
+ {
+ // If it's a string return a string node
+ if (preg_match('/"([^"]|""){0,255}"/', $this->currentToken)) {
+ $tmp = str_replace('""', '"', $this->currentToken);
+ if (($tmp == '"') || ($tmp == '')) {
+ // Trap for "" that has been used for an empty string
+ $tmp = '""';
+ }
+ $result = $this->createTree($tmp, '', '');
+ $this->advance();
+
+ return $result;
+ // If it's an error code
+ } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $this->currentToken) || $this->currentToken == '#N/A') {
+ $result = $this->createTree($this->currentToken, 'ptgErr', '');
+ $this->advance();
+
+ return $result;
+ // If it's a negative value
+ } elseif ($this->currentToken == '-') {
+ // catch "-" Term
+ $this->advance();
+ $result2 = $this->expression();
+
+ return $this->createTree('ptgUminus', $result2, '');
+ // If it's a positive value
+ } elseif ($this->currentToken == '+') {
+ // catch "+" Term
+ $this->advance();
+ $result2 = $this->expression();
+
+ return $this->createTree('ptgUplus', $result2, '');
+ }
+ $result = $this->term();
+ while (($this->currentToken == '+') ||
+ ($this->currentToken == '-') ||
+ ($this->currentToken == '^')) {
+ if ($this->currentToken == '+') {
+ $this->advance();
+ $result2 = $this->term();
+ $result = $this->createTree('ptgAdd', $result, $result2);
+ } elseif ($this->currentToken == '-') {
+ $this->advance();
+ $result2 = $this->term();
+ $result = $this->createTree('ptgSub', $result, $result2);
+ } else {
+ $this->advance();
+ $result2 = $this->term();
+ $result = $this->createTree('ptgPower', $result, $result2);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * This function just introduces a ptgParen element in the tree, so that Excel
+ * doesn't get confused when working with a parenthesized formula afterwards.
+ *
+ * @see fact()
+ *
+ * @return array The parsed ptg'd tree
+ */
+ private function parenthesizedExpression()
+ {
+ return $this->createTree('ptgParen', $this->expression(), '');
+ }
+
+ /**
+ * It parses a term. It assumes the following rule:
+ * Term -> Fact [("*" | "/") Fact].
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function term()
+ {
+ $result = $this->fact();
+ while (($this->currentToken == '*') ||
+ ($this->currentToken == '/')) {
+ if ($this->currentToken == '*') {
+ $this->advance();
+ $result2 = $this->fact();
+ $result = $this->createTree('ptgMul', $result, $result2);
+ } else {
+ $this->advance();
+ $result2 = $this->fact();
+ $result = $this->createTree('ptgDiv', $result, $result2);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * It parses a factor. It assumes the following rule:
+ * Fact -> ( Expr )
+ * | CellRef
+ * | CellRange
+ * | Number
+ * | Function.
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function fact()
+ {
+ if ($this->currentToken === '(') {
+ $this->advance(); // eat the "("
+ $result = $this->parenthesizedExpression();
+ if ($this->currentToken !== ')') {
+ throw new WriterException("')' token expected.");
+ }
+ $this->advance(); // eat the ")"
+ return $result;
+ }
+ // if it's a reference
+ if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?\d+$/', $this->currentToken)) {
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?\\d+$/u', $this->currentToken)) {
+ // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1)
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?\\d+$/u", $this->currentToken)) {
+ // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1)
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+:(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $this->currentToken) ||
+ preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $this->currentToken)) {
+ // if it's a range A1:B2 or $A$1:$B$2
+ // must be an error?
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?\\d+:\$?([A-Ia-i]?[A-Za-z])?\$?\\d+$/u', $this->currentToken)) {
+ // If it's an external range (Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2)
+ // must be an error?
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+:\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+$/u", $this->currentToken)) {
+ // If it's an external range ('Sheet1'!A1:B2 or 'Sheet1'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1'!$A$1:$B$2)
+ // must be an error?
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (is_numeric($this->currentToken)) {
+ // If it's a number or a percent
+ if ($this->lookAhead === '%') {
+ $result = $this->createTree('ptgPercent', $this->currentToken, '');
+ $this->advance(); // Skip the percentage operator once we've pre-built that tree
+ } else {
+ $result = $this->createTree($this->currentToken, '', '');
+ }
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $this->currentToken)) {
+ // if it's a function call
+ return $this->func();
+ }
+
+ throw new WriterException('Syntax error: ' . $this->currentToken . ', lookahead: ' . $this->lookAhead . ', current char: ' . $this->currentCharacter);
+ }
+
+ /**
+ * It parses a function call. It assumes the following rule:
+ * Func -> ( Expr [,Expr]* ).
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function func()
+ {
+ $num_args = 0; // number of arguments received
+ $function = strtoupper($this->currentToken);
+ $result = ''; // initialize result
+ $this->advance();
+ $this->advance(); // eat the "("
+ while ($this->currentToken !== ')') {
+ if ($num_args > 0) {
+ if ($this->currentToken === ',' || $this->currentToken === ';') {
+ $this->advance(); // eat the "," or ";"
+ } else {
+ throw new WriterException("Syntax error: comma expected in function $function, arg #{$num_args}");
+ }
+ $result2 = $this->condition();
+ $result = $this->createTree('arg', $result, $result2);
+ } else { // first argument
+ $result2 = $this->condition();
+ $result = $this->createTree('arg', '', $result2);
+ }
+ ++$num_args;
+ }
+ if (!isset($this->functions[$function])) {
+ throw new WriterException("Function $function() doesn't exist");
+ }
+ $args = $this->functions[$function][1];
+ // If fixed number of args eg. TIME($i, $j, $k). Check that the number of args is valid.
+ if (($args >= 0) && ($args != $num_args)) {
+ throw new WriterException("Incorrect number of arguments in function $function() ");
+ }
+
+ $result = $this->createTree($function, $result, $num_args);
+ $this->advance(); // eat the ")"
+ return $result;
+ }
+
+ /**
+ * Creates a tree. In fact an array which may have one or two arrays (sub-trees)
+ * as elements.
+ *
+ * @param mixed $value the value of this node
+ * @param mixed $left the left array (sub-tree) or a final node
+ * @param mixed $right the right array (sub-tree) or a final node
+ *
+ * @return array A tree
+ */
+ private function createTree($value, $left, $right)
+ {
+ return ['value' => $value, 'left' => $left, 'right' => $right];
+ }
+
+ /**
+ * Builds a string containing the tree in reverse polish notation (What you
+ * would use in a HP calculator stack).
+ * The following tree:.
+ *
+ * +
+ * / \
+ * 2 3
+ *
+ * produces: "23+"
+ *
+ * The following tree:
+ *
+ * +
+ * / \
+ * 3 *
+ * / \
+ * 6 A1
+ *
+ * produces: "36A1*+"
+ *
+ * In fact all operands, functions, references, etc... are written as ptg's
+ *
+ * @param array $tree the optional tree to convert
+ *
+ * @return string The tree in reverse polish notation
+ */
+ public function toReversePolish($tree = [])
+ {
+ $polish = ''; // the string we are going to return
+ if (empty($tree)) { // If it's the first call use parseTree
+ $tree = $this->parseTree;
+ }
+
+ if (is_array($tree['left'])) {
+ $converted_tree = $this->toReversePolish($tree['left']);
+ $polish .= $converted_tree;
+ } elseif ($tree['left'] != '') { // It's a final node
+ $converted_tree = $this->convert($tree['left']);
+ $polish .= $converted_tree;
+ }
+ if (is_array($tree['right'])) {
+ $converted_tree = $this->toReversePolish($tree['right']);
+ $polish .= $converted_tree;
+ } elseif ($tree['right'] != '') { // It's a final node
+ $converted_tree = $this->convert($tree['right']);
+ $polish .= $converted_tree;
+ }
+ // if it's a function convert it here (so we can set it's arguments)
+ if (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/", $tree['value']) &&
+ !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/', $tree['value']) &&
+ !preg_match('/^[A-Ia-i]?[A-Za-z](\\d+)\\.\\.[A-Ia-i]?[A-Za-z](\\d+)$/', $tree['value']) &&
+ !is_numeric($tree['value']) &&
+ !isset($this->ptg[$tree['value']])) {
+ // left subtree for a function is always an array.
+ if ($tree['left'] != '') {
+ $left_tree = $this->toReversePolish($tree['left']);
+ } else {
+ $left_tree = '';
+ }
+ // add it's left subtree and return.
+ return $left_tree . $this->convertFunction($tree['value'], $tree['right']);
+ }
+ $converted_tree = $this->convert($tree['value']);
+
+ return $polish . $converted_tree;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php
new file mode 100755
index 0000000..d5f61bf
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php
@@ -0,0 +1,1147 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+
+// Original file header of PEAR::Spreadsheet_Excel_Writer_Workbook (used as the base for this class):
+// -----------------------------------------------------------------------------------------
+// /*
+// * Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
+// *
+// * The majority of this is _NOT_ my code. I simply ported it from the
+// * PERL Spreadsheet::WriteExcel module.
+// *
+// * The author of the Spreadsheet::WriteExcel module is John McNamara
+// * <jmcnamara@cpan.org>
+// *
+// * I _DO_ maintain this code, and John McNamara has nothing to do with the
+// * porting of this code to PHP. Any questions directly related to this
+// * class library should be directed to me.
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class Workbook extends BIFFwriter
+{
+ /**
+ * Formula parser.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser
+ */
+ private $parser;
+
+ /**
+ * The BIFF file size for the workbook.
+ *
+ * @var int
+ *
+ * @see calcSheetOffsets()
+ */
+ private $biffSize;
+
+ /**
+ * XF Writers.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Xf[]
+ */
+ private $xfWriters = [];
+
+ /**
+ * Array containing the colour palette.
+ *
+ * @var array
+ */
+ private $palette;
+
+ /**
+ * The codepage indicates the text encoding used for strings.
+ *
+ * @var int
+ */
+ private $codepage;
+
+ /**
+ * The country code used for localization.
+ *
+ * @var int
+ */
+ private $countryCode;
+
+ /**
+ * Workbook.
+ *
+ * @var Spreadsheet
+ */
+ private $spreadsheet;
+
+ /**
+ * Fonts writers.
+ *
+ * @var Font[]
+ */
+ private $fontWriters = [];
+
+ /**
+ * Added fonts. Maps from font's hash => index in workbook.
+ *
+ * @var array
+ */
+ private $addedFonts = [];
+
+ /**
+ * Shared number formats.
+ *
+ * @var array
+ */
+ private $numberFormats = [];
+
+ /**
+ * Added number formats. Maps from numberFormat's hash => index in workbook.
+ *
+ * @var array
+ */
+ private $addedNumberFormats = [];
+
+ /**
+ * Sizes of the binary worksheet streams.
+ *
+ * @var array
+ */
+ private $worksheetSizes = [];
+
+ /**
+ * Offsets of the binary worksheet streams relative to the start of the global workbook stream.
+ *
+ * @var array
+ */
+ private $worksheetOffsets = [];
+
+ /**
+ * Total number of shared strings in workbook.
+ *
+ * @var int
+ */
+ private $stringTotal;
+
+ /**
+ * Number of unique shared strings in workbook.
+ *
+ * @var int
+ */
+ private $stringUnique;
+
+ /**
+ * Array of unique shared strings in workbook.
+ *
+ * @var array
+ */
+ private $stringTable;
+
+ /**
+ * Color cache.
+ */
+ private $colors;
+
+ /**
+ * Escher object corresponding to MSODRAWINGGROUP.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Shared\Escher
+ */
+ private $escher;
+
+ /**
+ * Class constructor.
+ *
+ * @param Spreadsheet $spreadsheet The Workbook
+ * @param int $str_total Total number of strings
+ * @param int $str_unique Total number of unique strings
+ * @param array $str_table String Table
+ * @param array $colors Colour Table
+ * @param Parser $parser The formula parser created for the Workbook
+ */
+ public function __construct(Spreadsheet $spreadsheet, &$str_total, &$str_unique, &$str_table, &$colors, Parser $parser)
+ {
+ // It needs to call its parent's constructor explicitly
+ parent::__construct();
+
+ $this->parser = $parser;
+ $this->biffSize = 0;
+ $this->palette = [];
+ $this->countryCode = -1;
+
+ $this->stringTotal = &$str_total;
+ $this->stringUnique = &$str_unique;
+ $this->stringTable = &$str_table;
+ $this->colors = &$colors;
+ $this->setPaletteXl97();
+
+ $this->spreadsheet = $spreadsheet;
+
+ $this->codepage = 0x04B0;
+
+ // Add empty sheets and Build color cache
+ $countSheets = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $countSheets; ++$i) {
+ $phpSheet = $spreadsheet->getSheet($i);
+
+ $this->parser->setExtSheet($phpSheet->getTitle(), $i); // Register worksheet name with parser
+
+ $supbook_index = 0x00;
+ $ref = pack('vvv', $supbook_index, $i, $i);
+ $this->parser->references[] = $ref; // Register reference with parser
+
+ // Sheet tab colors?
+ if ($phpSheet->isTabColorSet()) {
+ $this->addColor($phpSheet->getTabColor()->getRGB());
+ }
+ }
+ }
+
+ /**
+ * Add a new XF writer.
+ *
+ * @param bool $isStyleXf Is it a style XF?
+ *
+ * @return int Index to XF record
+ */
+ public function addXfWriter(Style $style, $isStyleXf = false)
+ {
+ $xfWriter = new Xf($style);
+ $xfWriter->setIsStyleXf($isStyleXf);
+
+ // Add the font if not already added
+ $fontIndex = $this->addFont($style->getFont());
+
+ // Assign the font index to the xf record
+ $xfWriter->setFontIndex($fontIndex);
+
+ // Background colors, best to treat these after the font so black will come after white in custom palette
+ $xfWriter->setFgColor($this->addColor($style->getFill()->getStartColor()->getRGB()));
+ $xfWriter->setBgColor($this->addColor($style->getFill()->getEndColor()->getRGB()));
+ $xfWriter->setBottomColor($this->addColor($style->getBorders()->getBottom()->getColor()->getRGB()));
+ $xfWriter->setTopColor($this->addColor($style->getBorders()->getTop()->getColor()->getRGB()));
+ $xfWriter->setRightColor($this->addColor($style->getBorders()->getRight()->getColor()->getRGB()));
+ $xfWriter->setLeftColor($this->addColor($style->getBorders()->getLeft()->getColor()->getRGB()));
+ $xfWriter->setDiagColor($this->addColor($style->getBorders()->getDiagonal()->getColor()->getRGB()));
+
+ // Add the number format if it is not a built-in one and not already added
+ if ($style->getNumberFormat()->getBuiltInFormatCode() === false) {
+ $numberFormatHashCode = $style->getNumberFormat()->getHashCode();
+
+ if (isset($this->addedNumberFormats[$numberFormatHashCode])) {
+ $numberFormatIndex = $this->addedNumberFormats[$numberFormatHashCode];
+ } else {
+ $numberFormatIndex = 164 + count($this->numberFormats);
+ $this->numberFormats[$numberFormatIndex] = $style->getNumberFormat();
+ $this->addedNumberFormats[$numberFormatHashCode] = $numberFormatIndex;
+ }
+ } else {
+ $numberFormatIndex = (int) $style->getNumberFormat()->getBuiltInFormatCode();
+ }
+
+ // Assign the number format index to xf record
+ $xfWriter->setNumberFormatIndex($numberFormatIndex);
+
+ $this->xfWriters[] = $xfWriter;
+
+ return count($this->xfWriters) - 1;
+ }
+
+ /**
+ * Add a font to added fonts.
+ *
+ * @return int Index to FONT record
+ */
+ public function addFont(\PhpOffice\PhpSpreadsheet\Style\Font $font)
+ {
+ $fontHashCode = $font->getHashCode();
+ if (isset($this->addedFonts[$fontHashCode])) {
+ $fontIndex = $this->addedFonts[$fontHashCode];
+ } else {
+ $countFonts = count($this->fontWriters);
+ $fontIndex = ($countFonts < 4) ? $countFonts : $countFonts + 1;
+
+ $fontWriter = new Font($font);
+ $fontWriter->setColorIndex($this->addColor($font->getColor()->getRGB()));
+ $this->fontWriters[] = $fontWriter;
+
+ $this->addedFonts[$fontHashCode] = $fontIndex;
+ }
+
+ return $fontIndex;
+ }
+
+ /**
+ * Alter color palette adding a custom color.
+ *
+ * @param string $rgb E.g. 'FF00AA'
+ *
+ * @return int Color index
+ */
+ private function addColor($rgb)
+ {
+ if (!isset($this->colors[$rgb])) {
+ $color =
+ [
+ hexdec(substr($rgb, 0, 2)),
+ hexdec(substr($rgb, 2, 2)),
+ hexdec(substr($rgb, 4)),
+ 0,
+ ];
+ $colorIndex = array_search($color, $this->palette);
+ if ($colorIndex) {
+ $this->colors[$rgb] = $colorIndex;
+ } else {
+ if (count($this->colors) === 0) {
+ $lastColor = 7;
+ } else {
+ $lastColor = end($this->colors);
+ }
+ if ($lastColor < 57) {
+ // then we add a custom color altering the palette
+ $colorIndex = $lastColor + 1;
+ $this->palette[$colorIndex] = $color;
+ $this->colors[$rgb] = $colorIndex;
+ } else {
+ // no room for more custom colors, just map to black
+ $colorIndex = 0;
+ }
+ }
+ } else {
+ // fetch already added custom color
+ $colorIndex = $this->colors[$rgb];
+ }
+
+ return $colorIndex;
+ }
+
+ /**
+ * Sets the colour palette to the Excel 97+ default.
+ */
+ private function setPaletteXl97(): void
+ {
+ $this->palette = [
+ 0x08 => [0x00, 0x00, 0x00, 0x00],
+ 0x09 => [0xff, 0xff, 0xff, 0x00],
+ 0x0A => [0xff, 0x00, 0x00, 0x00],
+ 0x0B => [0x00, 0xff, 0x00, 0x00],
+ 0x0C => [0x00, 0x00, 0xff, 0x00],
+ 0x0D => [0xff, 0xff, 0x00, 0x00],
+ 0x0E => [0xff, 0x00, 0xff, 0x00],
+ 0x0F => [0x00, 0xff, 0xff, 0x00],
+ 0x10 => [0x80, 0x00, 0x00, 0x00],
+ 0x11 => [0x00, 0x80, 0x00, 0x00],
+ 0x12 => [0x00, 0x00, 0x80, 0x00],
+ 0x13 => [0x80, 0x80, 0x00, 0x00],
+ 0x14 => [0x80, 0x00, 0x80, 0x00],
+ 0x15 => [0x00, 0x80, 0x80, 0x00],
+ 0x16 => [0xc0, 0xc0, 0xc0, 0x00],
+ 0x17 => [0x80, 0x80, 0x80, 0x00],
+ 0x18 => [0x99, 0x99, 0xff, 0x00],
+ 0x19 => [0x99, 0x33, 0x66, 0x00],
+ 0x1A => [0xff, 0xff, 0xcc, 0x00],
+ 0x1B => [0xcc, 0xff, 0xff, 0x00],
+ 0x1C => [0x66, 0x00, 0x66, 0x00],
+ 0x1D => [0xff, 0x80, 0x80, 0x00],
+ 0x1E => [0x00, 0x66, 0xcc, 0x00],
+ 0x1F => [0xcc, 0xcc, 0xff, 0x00],
+ 0x20 => [0x00, 0x00, 0x80, 0x00],
+ 0x21 => [0xff, 0x00, 0xff, 0x00],
+ 0x22 => [0xff, 0xff, 0x00, 0x00],
+ 0x23 => [0x00, 0xff, 0xff, 0x00],
+ 0x24 => [0x80, 0x00, 0x80, 0x00],
+ 0x25 => [0x80, 0x00, 0x00, 0x00],
+ 0x26 => [0x00, 0x80, 0x80, 0x00],
+ 0x27 => [0x00, 0x00, 0xff, 0x00],
+ 0x28 => [0x00, 0xcc, 0xff, 0x00],
+ 0x29 => [0xcc, 0xff, 0xff, 0x00],
+ 0x2A => [0xcc, 0xff, 0xcc, 0x00],
+ 0x2B => [0xff, 0xff, 0x99, 0x00],
+ 0x2C => [0x99, 0xcc, 0xff, 0x00],
+ 0x2D => [0xff, 0x99, 0xcc, 0x00],
+ 0x2E => [0xcc, 0x99, 0xff, 0x00],
+ 0x2F => [0xff, 0xcc, 0x99, 0x00],
+ 0x30 => [0x33, 0x66, 0xff, 0x00],
+ 0x31 => [0x33, 0xcc, 0xcc, 0x00],
+ 0x32 => [0x99, 0xcc, 0x00, 0x00],
+ 0x33 => [0xff, 0xcc, 0x00, 0x00],
+ 0x34 => [0xff, 0x99, 0x00, 0x00],
+ 0x35 => [0xff, 0x66, 0x00, 0x00],
+ 0x36 => [0x66, 0x66, 0x99, 0x00],
+ 0x37 => [0x96, 0x96, 0x96, 0x00],
+ 0x38 => [0x00, 0x33, 0x66, 0x00],
+ 0x39 => [0x33, 0x99, 0x66, 0x00],
+ 0x3A => [0x00, 0x33, 0x00, 0x00],
+ 0x3B => [0x33, 0x33, 0x00, 0x00],
+ 0x3C => [0x99, 0x33, 0x00, 0x00],
+ 0x3D => [0x99, 0x33, 0x66, 0x00],
+ 0x3E => [0x33, 0x33, 0x99, 0x00],
+ 0x3F => [0x33, 0x33, 0x33, 0x00],
+ ];
+ }
+
+ /**
+ * Assemble worksheets into a workbook and send the BIFF data to an OLE
+ * storage.
+ *
+ * @param array $pWorksheetSizes The sizes in bytes of the binary worksheet streams
+ *
+ * @return string Binary data for workbook stream
+ */
+ public function writeWorkbook(array $pWorksheetSizes)
+ {
+ $this->worksheetSizes = $pWorksheetSizes;
+
+ // Calculate the number of selected worksheet tabs and call the finalization
+ // methods for each worksheet
+ $total_worksheets = $this->spreadsheet->getSheetCount();
+
+ // Add part 1 of the Workbook globals, what goes before the SHEET records
+ $this->storeBof(0x0005);
+ $this->writeCodepage();
+ $this->writeWindow1();
+
+ $this->writeDateMode();
+ $this->writeAllFonts();
+ $this->writeAllNumberFormats();
+ $this->writeAllXfs();
+ $this->writeAllStyles();
+ $this->writePalette();
+
+ // Prepare part 3 of the workbook global stream, what goes after the SHEET records
+ $part3 = '';
+ if ($this->countryCode !== -1) {
+ $part3 .= $this->writeCountry();
+ }
+ $part3 .= $this->writeRecalcId();
+
+ $part3 .= $this->writeSupbookInternal();
+ /* TODO: store external SUPBOOK records and XCT and CRN records
+ in case of external references for BIFF8 */
+ $part3 .= $this->writeExternalsheetBiff8();
+ $part3 .= $this->writeAllDefinedNamesBiff8();
+ $part3 .= $this->writeMsoDrawingGroup();
+ $part3 .= $this->writeSharedStringsTable();
+
+ $part3 .= $this->writeEof();
+
+ // Add part 2 of the Workbook globals, the SHEET records
+ $this->calcSheetOffsets();
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $this->writeBoundSheet($this->spreadsheet->getSheet($i), $this->worksheetOffsets[$i]);
+ }
+
+ // Add part 3 of the Workbook globals
+ $this->_data .= $part3;
+
+ return $this->_data;
+ }
+
+ /**
+ * Calculate offsets for Worksheet BOF records.
+ */
+ private function calcSheetOffsets(): void
+ {
+ $boundsheet_length = 10; // fixed length for a BOUNDSHEET record
+
+ // size of Workbook globals part 1 + 3
+ $offset = $this->_datasize;
+
+ // add size of Workbook globals part 2, the length of the SHEET records
+ $total_worksheets = count($this->spreadsheet->getAllSheets());
+ foreach ($this->spreadsheet->getWorksheetIterator() as $sheet) {
+ $offset += $boundsheet_length + strlen(StringHelper::UTF8toBIFF8UnicodeShort($sheet->getTitle()));
+ }
+
+ // add the sizes of each of the Sheet substreams, respectively
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $this->worksheetOffsets[$i] = $offset;
+ $offset += $this->worksheetSizes[$i];
+ }
+ $this->biffSize = $offset;
+ }
+
+ /**
+ * Store the Excel FONT records.
+ */
+ private function writeAllFonts(): void
+ {
+ foreach ($this->fontWriters as $fontWriter) {
+ $this->append($fontWriter->writeFont());
+ }
+ }
+
+ /**
+ * Store user defined numerical formats i.e. FORMAT records.
+ */
+ private function writeAllNumberFormats(): void
+ {
+ foreach ($this->numberFormats as $numberFormatIndex => $numberFormat) {
+ $this->writeNumberFormat($numberFormat->getFormatCode(), $numberFormatIndex);
+ }
+ }
+
+ /**
+ * Write all XF records.
+ */
+ private function writeAllXfs(): void
+ {
+ foreach ($this->xfWriters as $xfWriter) {
+ $this->append($xfWriter->writeXf());
+ }
+ }
+
+ /**
+ * Write all STYLE records.
+ */
+ private function writeAllStyles(): void
+ {
+ $this->writeStyle();
+ }
+
+ /**
+ * Writes all the DEFINEDNAME records (BIFF8).
+ * So far this is only used for repeating rows/columns (print titles) and print areas.
+ */
+ private function writeAllDefinedNamesBiff8()
+ {
+ $chunk = '';
+
+ // Named ranges
+ if (count($this->spreadsheet->getNamedRanges()) > 0) {
+ // Loop named ranges
+ $namedRanges = $this->spreadsheet->getNamedRanges();
+ foreach ($namedRanges as $namedRange) {
+ // Create absolute coordinate
+ $range = Coordinate::splitRange($namedRange->getRange());
+ $iMax = count($range);
+ for ($i = 0; $i < $iMax; ++$i) {
+ $range[$i][0] = '\'' . str_replace("'", "''", $namedRange->getWorksheet()->getTitle()) . '\'!' . Coordinate::absoluteCoordinate($range[$i][0]);
+ if (isset($range[$i][1])) {
+ $range[$i][1] = Coordinate::absoluteCoordinate($range[$i][1]);
+ }
+ }
+ $range = Coordinate::buildRange($range); // e.g. Sheet1!$A$1:$B$2
+
+ // parse formula
+ try {
+ $error = $this->parser->parse($range);
+ $formulaData = $this->parser->toReversePolish();
+
+ // make sure tRef3d is of type tRef3dR (0x3A)
+ if (isset($formulaData[0]) && ($formulaData[0] == "\x7A" || $formulaData[0] == "\x5A")) {
+ $formulaData = "\x3A" . substr($formulaData, 1);
+ }
+
+ if ($namedRange->getLocalOnly()) {
+ // local scope
+ $scope = $this->spreadsheet->getIndex($namedRange->getScope()) + 1;
+ } else {
+ // global scope
+ $scope = 0;
+ }
+ $chunk .= $this->writeData($this->writeDefinedNameBiff8($namedRange->getName(), $formulaData, $scope, false));
+ } catch (PhpSpreadsheetException $e) {
+ // do nothing
+ }
+ }
+ }
+
+ // total number of sheets
+ $total_worksheets = $this->spreadsheet->getSheetCount();
+
+ // write the print titles (repeating rows, columns), if any
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
+ // simultaneous repeatColumns repeatRows
+ if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) {
+ $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
+ $colmin = Coordinate::columnIndexFromString($repeat[0]) - 1;
+ $colmax = Coordinate::columnIndexFromString($repeat[1]) - 1;
+
+ $repeat = $sheetSetup->getRowsToRepeatAtTop();
+ $rowmin = $repeat[0] - 1;
+ $rowmax = $repeat[1] - 1;
+
+ // construct formula data manually
+ $formulaData = pack('Cv', 0x29, 0x17); // tMemFunc
+ $formulaData .= pack('Cvvvvv', 0x3B, $i, 0, 65535, $colmin, $colmax); // tArea3d
+ $formulaData .= pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, 0, 255); // tArea3d
+ $formulaData .= pack('C', 0x10); // tList
+
+ // store the DEFINEDNAME record
+ $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
+
+ // (exclusive) either repeatColumns or repeatRows
+ } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
+ // Columns to repeat
+ if ($sheetSetup->isColumnsToRepeatAtLeftSet()) {
+ $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
+ $colmin = Coordinate::columnIndexFromString($repeat[0]) - 1;
+ $colmax = Coordinate::columnIndexFromString($repeat[1]) - 1;
+ } else {
+ $colmin = 0;
+ $colmax = 255;
+ }
+ // Rows to repeat
+ if ($sheetSetup->isRowsToRepeatAtTopSet()) {
+ $repeat = $sheetSetup->getRowsToRepeatAtTop();
+ $rowmin = $repeat[0] - 1;
+ $rowmax = $repeat[1] - 1;
+ } else {
+ $rowmin = 0;
+ $rowmax = 65535;
+ }
+
+ // construct formula data manually because parser does not recognize absolute 3d cell references
+ $formulaData = pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, $colmin, $colmax);
+
+ // store the DEFINEDNAME record
+ $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
+ }
+ }
+
+ // write the print areas, if any
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
+ if ($sheetSetup->isPrintAreaSet()) {
+ // Print area, e.g. A3:J6,H1:X20
+ $printArea = Coordinate::splitRange($sheetSetup->getPrintArea());
+ $countPrintArea = count($printArea);
+
+ $formulaData = '';
+ for ($j = 0; $j < $countPrintArea; ++$j) {
+ $printAreaRect = $printArea[$j]; // e.g. A3:J6
+ $printAreaRect[0] = Coordinate::coordinateFromString($printAreaRect[0]);
+ $printAreaRect[1] = Coordinate::coordinateFromString($printAreaRect[1]);
+
+ $print_rowmin = $printAreaRect[0][1] - 1;
+ $print_rowmax = $printAreaRect[1][1] - 1;
+ $print_colmin = Coordinate::columnIndexFromString($printAreaRect[0][0]) - 1;
+ $print_colmax = Coordinate::columnIndexFromString($printAreaRect[1][0]) - 1;
+
+ // construct formula data manually because parser does not recognize absolute 3d cell references
+ $formulaData .= pack('Cvvvvv', 0x3B, $i, $print_rowmin, $print_rowmax, $print_colmin, $print_colmax);
+
+ if ($j > 0) {
+ $formulaData .= pack('C', 0x10); // list operator token ','
+ }
+ }
+
+ // store the DEFINEDNAME record
+ $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x06), $formulaData, $i + 1, true));
+ }
+ }
+
+ // write autofilters, if any
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $sheetAutoFilter = $this->spreadsheet->getSheet($i)->getAutoFilter();
+ $autoFilterRange = $sheetAutoFilter->getRange();
+ if (!empty($autoFilterRange)) {
+ $rangeBounds = Coordinate::rangeBoundaries($autoFilterRange);
+
+ //Autofilter built in name
+ $name = pack('C', 0x0D);
+
+ $chunk .= $this->writeData($this->writeShortNameBiff8($name, $i + 1, $rangeBounds, true));
+ }
+ }
+
+ return $chunk;
+ }
+
+ /**
+ * Write a DEFINEDNAME record for BIFF8 using explicit binary formula data.
+ *
+ * @param string $name The name in UTF-8
+ * @param string $formulaData The binary formula data
+ * @param int $sheetIndex 1-based sheet index the defined name applies to. 0 = global
+ * @param bool $isBuiltIn Built-in name?
+ *
+ * @return string Complete binary record data
+ */
+ private function writeDefinedNameBiff8($name, $formulaData, $sheetIndex = 0, $isBuiltIn = false)
+ {
+ $record = 0x0018;
+
+ // option flags
+ $options = $isBuiltIn ? 0x20 : 0x00;
+
+ // length of the name, character count
+ $nlen = StringHelper::countCharacters($name);
+
+ // name with stripped length field
+ $name = substr(StringHelper::UTF8toBIFF8UnicodeLong($name), 2);
+
+ // size of the formula (in bytes)
+ $sz = strlen($formulaData);
+
+ // combine the parts
+ $data = pack('vCCvvvCCCC', $options, 0, $nlen, $sz, 0, $sheetIndex, 0, 0, 0, 0)
+ . $name . $formulaData;
+ $length = strlen($data);
+
+ $header = pack('vv', $record, $length);
+
+ return $header . $data;
+ }
+
+ /**
+ * Write a short NAME record.
+ *
+ * @param string $name
+ * @param string $sheetIndex 1-based sheet index the defined name applies to. 0 = global
+ * @param integer[][] $rangeBounds range boundaries
+ * @param bool $isHidden
+ *
+ * @return string Complete binary record data
+ * */
+ private function writeShortNameBiff8($name, $sheetIndex, $rangeBounds, $isHidden = false)
+ {
+ $record = 0x0018;
+
+ // option flags
+ $options = ($isHidden ? 0x21 : 0x00);
+
+ $extra = pack(
+ 'Cvvvvv',
+ 0x3B,
+ $sheetIndex - 1,
+ $rangeBounds[0][1] - 1,
+ $rangeBounds[1][1] - 1,
+ $rangeBounds[0][0] - 1,
+ $rangeBounds[1][0] - 1
+ );
+
+ // size of the formula (in bytes)
+ $sz = strlen($extra);
+
+ // combine the parts
+ $data = pack('vCCvvvCCCCC', $options, 0, 1, $sz, 0, $sheetIndex, 0, 0, 0, 0, 0)
+ . $name . $extra;
+ $length = strlen($data);
+
+ $header = pack('vv', $record, $length);
+
+ return $header . $data;
+ }
+
+ /**
+ * Stores the CODEPAGE biff record.
+ */
+ private function writeCodepage(): void
+ {
+ $record = 0x0042; // Record identifier
+ $length = 0x0002; // Number of bytes to follow
+ $cv = $this->codepage; // The code page
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $cv);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write Excel BIFF WINDOW1 record.
+ */
+ private function writeWindow1(): void
+ {
+ $record = 0x003D; // Record identifier
+ $length = 0x0012; // Number of bytes to follow
+
+ $xWn = 0x0000; // Horizontal position of window
+ $yWn = 0x0000; // Vertical position of window
+ $dxWn = 0x25BC; // Width of window
+ $dyWn = 0x1572; // Height of window
+
+ $grbit = 0x0038; // Option flags
+
+ // not supported by PhpSpreadsheet, so there is only one selected sheet, the active
+ $ctabsel = 1; // Number of workbook tabs selected
+
+ $wTabRatio = 0x0258; // Tab to scrollbar ratio
+
+ // not supported by PhpSpreadsheet, set to 0
+ $itabFirst = 0; // 1st displayed worksheet
+ $itabCur = $this->spreadsheet->getActiveSheetIndex(); // Active worksheet
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvvvvvvv', $xWn, $yWn, $dxWn, $dyWn, $grbit, $itabCur, $itabFirst, $ctabsel, $wTabRatio);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Writes Excel BIFF BOUNDSHEET record.
+ *
+ * @param Worksheet $sheet Worksheet name
+ * @param int $offset Location of worksheet BOF
+ */
+ private function writeBoundSheet($sheet, $offset): void
+ {
+ $sheetname = $sheet->getTitle();
+ $record = 0x0085; // Record identifier
+
+ // sheet state
+ switch ($sheet->getSheetState()) {
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VISIBLE:
+ $ss = 0x00;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_HIDDEN:
+ $ss = 0x01;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VERYHIDDEN:
+ $ss = 0x02;
+
+ break;
+ default:
+ $ss = 0x00;
+
+ break;
+ }
+
+ // sheet type
+ $st = 0x00;
+
+ $grbit = 0x0000; // Visibility and sheet type
+
+ $data = pack('VCC', $offset, $ss, $st);
+ $data .= StringHelper::UTF8toBIFF8UnicodeShort($sheetname);
+
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write Internal SUPBOOK record.
+ */
+ private function writeSupbookInternal()
+ {
+ $record = 0x01AE; // Record identifier
+ $length = 0x0004; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vv', $this->spreadsheet->getSheetCount(), 0x0401);
+
+ return $this->writeData($header . $data);
+ }
+
+ /**
+ * Writes the Excel BIFF EXTERNSHEET record. These references are used by
+ * formulas.
+ */
+ private function writeExternalsheetBiff8()
+ {
+ $totalReferences = count($this->parser->references);
+ $record = 0x0017; // Record identifier
+ $length = 2 + 6 * $totalReferences; // Number of bytes to follow
+
+ $supbook_index = 0; // FIXME: only using internal SUPBOOK record
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $totalReferences);
+ for ($i = 0; $i < $totalReferences; ++$i) {
+ $data .= $this->parser->references[$i];
+ }
+
+ return $this->writeData($header . $data);
+ }
+
+ /**
+ * Write Excel BIFF STYLE records.
+ */
+ private function writeStyle(): void
+ {
+ $record = 0x0293; // Record identifier
+ $length = 0x0004; // Bytes to follow
+
+ $ixfe = 0x8000; // Index to cell style XF
+ $BuiltIn = 0x00; // Built-in style
+ $iLevel = 0xff; // Outline style level
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vCC', $ixfe, $BuiltIn, $iLevel);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Writes Excel FORMAT record for non "built-in" numerical formats.
+ *
+ * @param string $format Custom format string
+ * @param int $ifmt Format index code
+ */
+ private function writeNumberFormat($format, $ifmt): void
+ {
+ $record = 0x041E; // Record identifier
+
+ $numberFormatString = StringHelper::UTF8toBIFF8UnicodeLong($format);
+ $length = 2 + strlen($numberFormatString); // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $ifmt) . $numberFormatString;
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write DATEMODE record to indicate the date system in use (1904 or 1900).
+ */
+ private function writeDateMode(): void
+ {
+ $record = 0x0022; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $f1904 = (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904)
+ ? 1
+ : 0; // Flag for 1904 date system
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $f1904);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Stores the COUNTRY record for localization.
+ *
+ * @return string
+ */
+ private function writeCountry()
+ {
+ $record = 0x008C; // Record identifier
+ $length = 4; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ // using the same country code always for simplicity
+ $data = pack('vv', $this->countryCode, $this->countryCode);
+
+ return $this->writeData($header . $data);
+ }
+
+ /**
+ * Write the RECALCID record.
+ *
+ * @return string
+ */
+ private function writeRecalcId()
+ {
+ $record = 0x01C1; // Record identifier
+ $length = 8; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+
+ // by inspection of real Excel files, MS Office Excel 2007 writes this
+ $data = pack('VV', 0x000001C1, 0x00001E667);
+
+ return $this->writeData($header . $data);
+ }
+
+ /**
+ * Stores the PALETTE biff record.
+ */
+ private function writePalette(): void
+ {
+ $aref = $this->palette;
+
+ $record = 0x0092; // Record identifier
+ $length = 2 + 4 * count($aref); // Number of bytes to follow
+ $ccv = count($aref); // Number of RGB values to follow
+ $data = ''; // The RGB data
+
+ // Pack the RGB data
+ foreach ($aref as $color) {
+ foreach ($color as $byte) {
+ $data .= pack('C', $byte);
+ }
+ }
+
+ $header = pack('vvv', $record, $length, $ccv);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Handling of the SST continue blocks is complicated by the need to include an
+ * additional continuation byte depending on whether the string is split between
+ * blocks or whether it starts at the beginning of the block. (There are also
+ * additional complications that will arise later when/if Rich Strings are
+ * supported).
+ *
+ * The Excel documentation says that the SST record should be followed by an
+ * EXTSST record. The EXTSST record is a hash table that is used to optimise
+ * access to SST. However, despite the documentation it doesn't seem to be
+ * required so we will ignore it.
+ *
+ * @return string Binary data
+ */
+ private function writeSharedStringsTable()
+ {
+ // maximum size of record data (excluding record header)
+ $continue_limit = 8224;
+
+ // initialize array of record data blocks
+ $recordDatas = [];
+
+ // start SST record data block with total number of strings, total number of unique strings
+ $recordData = pack('VV', $this->stringTotal, $this->stringUnique);
+
+ // loop through all (unique) strings in shared strings table
+ foreach (array_keys($this->stringTable) as $string) {
+ // here $string is a BIFF8 encoded string
+
+ // length = character count
+ $headerinfo = unpack('vlength/Cencoding', $string);
+
+ // currently, this is always 1 = uncompressed
+ $encoding = $headerinfo['encoding'];
+
+ // initialize finished writing current $string
+ $finished = false;
+
+ while ($finished === false) {
+ // normally, there will be only one cycle, but if string cannot immediately be written as is
+ // there will be need for more than one cylcle, if string longer than one record data block, there
+ // may be need for even more cycles
+
+ if (strlen($recordData) + strlen($string) <= $continue_limit) {
+ // then we can write the string (or remainder of string) without any problems
+ $recordData .= $string;
+
+ if (strlen($recordData) + strlen($string) == $continue_limit) {
+ // we close the record data block, and initialize a new one
+ $recordDatas[] = $recordData;
+ $recordData = '';
+ }
+
+ // we are finished writing this string
+ $finished = true;
+ } else {
+ // special treatment writing the string (or remainder of the string)
+ // If the string is very long it may need to be written in more than one CONTINUE record.
+
+ // check how many bytes more there is room for in the current record
+ $space_remaining = $continue_limit - strlen($recordData);
+
+ // minimum space needed
+ // uncompressed: 2 byte string length length field + 1 byte option flags + 2 byte character
+ // compressed: 2 byte string length length field + 1 byte option flags + 1 byte character
+ $min_space_needed = ($encoding == 1) ? 5 : 4;
+
+ // We have two cases
+ // 1. space remaining is less than minimum space needed
+ // here we must waste the space remaining and move to next record data block
+ // 2. space remaining is greater than or equal to minimum space needed
+ // here we write as much as we can in the current block, then move to next record data block
+
+ // 1. space remaining is less than minimum space needed
+ if ($space_remaining < $min_space_needed) {
+ // we close the block, store the block data
+ $recordDatas[] = $recordData;
+
+ // and start new record data block where we start writing the string
+ $recordData = '';
+
+ // 2. space remaining is greater than or equal to minimum space needed
+ } else {
+ // initialize effective remaining space, for Unicode strings this may need to be reduced by 1, see below
+ $effective_space_remaining = $space_remaining;
+
+ // for uncompressed strings, sometimes effective space remaining is reduced by 1
+ if ($encoding == 1 && (strlen($string) - $space_remaining) % 2 == 1) {
+ --$effective_space_remaining;
+ }
+
+ // one block fininshed, store the block data
+ $recordData .= substr($string, 0, $effective_space_remaining);
+
+ $string = substr($string, $effective_space_remaining); // for next cycle in while loop
+ $recordDatas[] = $recordData;
+
+ // start new record data block with the repeated option flags
+ $recordData = pack('C', $encoding);
+ }
+ }
+ }
+ }
+
+ // Store the last record data block unless it is empty
+ // if there was no need for any continue records, this will be the for SST record data block itself
+ if (strlen($recordData) > 0) {
+ $recordDatas[] = $recordData;
+ }
+
+ // combine into one chunk with all the blocks SST, CONTINUE,...
+ $chunk = '';
+ foreach ($recordDatas as $i => $recordData) {
+ // first block should have the SST record header, remaing should have CONTINUE header
+ $record = ($i == 0) ? 0x00FC : 0x003C;
+
+ $header = pack('vv', $record, strlen($recordData));
+ $data = $header . $recordData;
+
+ $chunk .= $this->writeData($data);
+ }
+
+ return $chunk;
+ }
+
+ /**
+ * Writes the MSODRAWINGGROUP record if needed. Possibly split using CONTINUE records.
+ */
+ private function writeMsoDrawingGroup()
+ {
+ // write the Escher stream if necessary
+ if (isset($this->escher)) {
+ $writer = new Escher($this->escher);
+ $data = $writer->close();
+
+ $record = 0x00EB;
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ return $this->writeData($header . $data);
+ }
+
+ return '';
+ }
+
+ /**
+ * Get Escher object.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Shared\Escher
+ */
+ public function getEscher()
+ {
+ return $this->escher;
+ }
+
+ /**
+ * Set Escher object.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Shared\Escher $pValue
+ */
+ public function setEscher(?\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null): void
+ {
+ $this->escher = $pValue;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php
new file mode 100755
index 0000000..0dce7ba
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php
@@ -0,0 +1,4484 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\RichText\Run;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Shared\Xls;
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Color;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Protection;
+use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
+use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+// Original file header of PEAR::Spreadsheet_Excel_Writer_Worksheet (used as the base for this class):
+// -----------------------------------------------------------------------------------------
+// /*
+// * Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
+// *
+// * The majority of this is _NOT_ my code. I simply ported it from the
+// * PERL Spreadsheet::WriteExcel module.
+// *
+// * The author of the Spreadsheet::WriteExcel module is John McNamara
+// * <jmcnamara@cpan.org>
+// *
+// * I _DO_ maintain this code, and John McNamara has nothing to do with the
+// * porting of this code to PHP. Any questions directly related to this
+// * class library should be directed to me.
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class Worksheet extends BIFFwriter
+{
+ /**
+ * Formula parser.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser
+ */
+ private $parser;
+
+ /**
+ * Maximum number of characters for a string (LABEL record in BIFF5).
+ *
+ * @var int
+ */
+ private $xlsStringMaxLength;
+
+ /**
+ * Array containing format information for columns.
+ *
+ * @var array
+ */
+ private $columnInfo;
+
+ /**
+ * Array containing the selected area for the worksheet.
+ *
+ * @var array
+ */
+ private $selection;
+
+ /**
+ * The active pane for the worksheet.
+ *
+ * @var int
+ */
+ private $activePane;
+
+ /**
+ * Whether to use outline.
+ *
+ * @var int
+ */
+ private $outlineOn;
+
+ /**
+ * Auto outline styles.
+ *
+ * @var bool
+ */
+ private $outlineStyle;
+
+ /**
+ * Whether to have outline summary below.
+ *
+ * @var bool
+ */
+ private $outlineBelow;
+
+ /**
+ * Whether to have outline summary at the right.
+ *
+ * @var bool
+ */
+ private $outlineRight;
+
+ /**
+ * Reference to the total number of strings in the workbook.
+ *
+ * @var int
+ */
+ private $stringTotal;
+
+ /**
+ * Reference to the number of unique strings in the workbook.
+ *
+ * @var int
+ */
+ private $stringUnique;
+
+ /**
+ * Reference to the array containing all the unique strings in the workbook.
+ *
+ * @var array
+ */
+ private $stringTable;
+
+ /**
+ * Color cache.
+ */
+ private $colors;
+
+ /**
+ * Index of first used row (at least 0).
+ *
+ * @var int
+ */
+ private $firstRowIndex;
+
+ /**
+ * Index of last used row. (no used rows means -1).
+ *
+ * @var int
+ */
+ private $lastRowIndex;
+
+ /**
+ * Index of first used column (at least 0).
+ *
+ * @var int
+ */
+ private $firstColumnIndex;
+
+ /**
+ * Index of last used column (no used columns means -1).
+ *
+ * @var int
+ */
+ private $lastColumnIndex;
+
+ /**
+ * Sheet object.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ */
+ public $phpSheet;
+
+ /**
+ * Count cell style Xfs.
+ *
+ * @var int
+ */
+ private $countCellStyleXfs;
+
+ /**
+ * Escher object corresponding to MSODRAWING.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Shared\Escher
+ */
+ private $escher;
+
+ /**
+ * Array of font hashes associated to FONT records index.
+ *
+ * @var array
+ */
+ public $fontHashIndex;
+
+ /**
+ * @var bool
+ */
+ private $preCalculateFormulas;
+
+ /**
+ * @var int
+ */
+ private $printHeaders;
+
+ /**
+ * Constructor.
+ *
+ * @param int $str_total Total number of strings
+ * @param int $str_unique Total number of unique strings
+ * @param array &$str_table String Table
+ * @param array &$colors Colour Table
+ * @param Parser $parser The formula parser created for the Workbook
+ * @param bool $preCalculateFormulas Flag indicating whether formulas should be calculated or just written
+ * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $phpSheet The worksheet to write
+ */
+ public function __construct(&$str_total, &$str_unique, &$str_table, &$colors, Parser $parser, $preCalculateFormulas, \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $phpSheet)
+ {
+ // It needs to call its parent's constructor explicitly
+ parent::__construct();
+
+ $this->preCalculateFormulas = $preCalculateFormulas;
+ $this->stringTotal = &$str_total;
+ $this->stringUnique = &$str_unique;
+ $this->stringTable = &$str_table;
+ $this->colors = &$colors;
+ $this->parser = $parser;
+
+ $this->phpSheet = $phpSheet;
+
+ $this->xlsStringMaxLength = 255;
+ $this->columnInfo = [];
+ $this->selection = [0, 0, 0, 0];
+ $this->activePane = 3;
+
+ $this->printHeaders = 0;
+
+ $this->outlineStyle = 0;
+ $this->outlineBelow = 1;
+ $this->outlineRight = 1;
+ $this->outlineOn = 1;
+
+ $this->fontHashIndex = [];
+
+ // calculate values for DIMENSIONS record
+ $minR = 1;
+ $minC = 'A';
+
+ $maxR = $this->phpSheet->getHighestRow();
+ $maxC = $this->phpSheet->getHighestColumn();
+
+ // Determine lowest and highest column and row
+ $this->lastRowIndex = ($maxR > 65535) ? 65535 : $maxR;
+
+ $this->firstColumnIndex = Coordinate::columnIndexFromString($minC);
+ $this->lastColumnIndex = Coordinate::columnIndexFromString($maxC);
+
+// if ($this->firstColumnIndex > 255) $this->firstColumnIndex = 255;
+ if ($this->lastColumnIndex > 255) {
+ $this->lastColumnIndex = 255;
+ }
+
+ $this->countCellStyleXfs = count($phpSheet->getParent()->getCellStyleXfCollection());
+ }
+
+ /**
+ * Add data to the beginning of the workbook (note the reverse order)
+ * and to the end of the workbook.
+ *
+ * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::storeWorkbook()
+ */
+ public function close(): void
+ {
+ $phpSheet = $this->phpSheet;
+
+ // Storing selected cells and active sheet because it changes while parsing cells with formulas.
+ $selectedCells = $this->phpSheet->getSelectedCells();
+ $activeSheetIndex = $this->phpSheet->getParent()->getActiveSheetIndex();
+
+ // Write BOF record
+ $this->storeBof(0x0010);
+
+ // Write PRINTHEADERS
+ $this->writePrintHeaders();
+
+ // Write PRINTGRIDLINES
+ $this->writePrintGridlines();
+
+ // Write GRIDSET
+ $this->writeGridset();
+
+ // Calculate column widths
+ $phpSheet->calculateColumnWidths();
+
+ // Column dimensions
+ if (($defaultWidth = $phpSheet->getDefaultColumnDimension()->getWidth()) < 0) {
+ $defaultWidth = \PhpOffice\PhpSpreadsheet\Shared\Font::getDefaultColumnWidthByFont($phpSheet->getParent()->getDefaultStyle()->getFont());
+ }
+
+ $columnDimensions = $phpSheet->getColumnDimensions();
+ $maxCol = $this->lastColumnIndex - 1;
+ for ($i = 0; $i <= $maxCol; ++$i) {
+ $hidden = 0;
+ $level = 0;
+ $xfIndex = 15; // there are 15 cell style Xfs
+
+ $width = $defaultWidth;
+
+ $columnLetter = Coordinate::stringFromColumnIndex($i + 1);
+ if (isset($columnDimensions[$columnLetter])) {
+ $columnDimension = $columnDimensions[$columnLetter];
+ if ($columnDimension->getWidth() >= 0) {
+ $width = $columnDimension->getWidth();
+ }
+ $hidden = $columnDimension->getVisible() ? 0 : 1;
+ $level = $columnDimension->getOutlineLevel();
+ $xfIndex = $columnDimension->getXfIndex() + 15; // there are 15 cell style Xfs
+ }
+
+ // Components of columnInfo:
+ // $firstcol first column on the range
+ // $lastcol last column on the range
+ // $width width to set
+ // $xfIndex The optional cell style Xf index to apply to the columns
+ // $hidden The optional hidden atribute
+ // $level The optional outline level
+ $this->columnInfo[] = [$i, $i, $width, $xfIndex, $hidden, $level];
+ }
+
+ // Write GUTS
+ $this->writeGuts();
+
+ // Write DEFAULTROWHEIGHT
+ $this->writeDefaultRowHeight();
+ // Write WSBOOL
+ $this->writeWsbool();
+ // Write horizontal and vertical page breaks
+ $this->writeBreaks();
+ // Write page header
+ $this->writeHeader();
+ // Write page footer
+ $this->writeFooter();
+ // Write page horizontal centering
+ $this->writeHcenter();
+ // Write page vertical centering
+ $this->writeVcenter();
+ // Write left margin
+ $this->writeMarginLeft();
+ // Write right margin
+ $this->writeMarginRight();
+ // Write top margin
+ $this->writeMarginTop();
+ // Write bottom margin
+ $this->writeMarginBottom();
+ // Write page setup
+ $this->writeSetup();
+ // Write sheet protection
+ $this->writeProtect();
+ // Write SCENPROTECT
+ $this->writeScenProtect();
+ // Write OBJECTPROTECT
+ $this->writeObjectProtect();
+ // Write sheet password
+ $this->writePassword();
+ // Write DEFCOLWIDTH record
+ $this->writeDefcol();
+
+ // Write the COLINFO records if they exist
+ if (!empty($this->columnInfo)) {
+ $colcount = count($this->columnInfo);
+ for ($i = 0; $i < $colcount; ++$i) {
+ $this->writeColinfo($this->columnInfo[$i]);
+ }
+ }
+ $autoFilterRange = $phpSheet->getAutoFilter()->getRange();
+ if (!empty($autoFilterRange)) {
+ // Write AUTOFILTERINFO
+ $this->writeAutoFilterInfo();
+ }
+
+ // Write sheet dimensions
+ $this->writeDimensions();
+
+ // Row dimensions
+ foreach ($phpSheet->getRowDimensions() as $rowDimension) {
+ $xfIndex = $rowDimension->getXfIndex() + 15; // there are 15 cellXfs
+ $this->writeRow($rowDimension->getRowIndex() - 1, $rowDimension->getRowHeight(), $xfIndex, ($rowDimension->getVisible() ? '0' : '1'), $rowDimension->getOutlineLevel());
+ }
+
+ // Write Cells
+ foreach ($phpSheet->getCoordinates() as $coordinate) {
+ $cell = $phpSheet->getCell($coordinate);
+ $row = $cell->getRow() - 1;
+ $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1;
+
+ // Don't break Excel break the code!
+ if ($row > 65535 || $column > 255) {
+ throw new WriterException('Rows or columns overflow! Excel5 has limit to 65535 rows and 255 columns. Use XLSX instead.');
+ }
+
+ // Write cell value
+ $xfIndex = $cell->getXfIndex() + 15; // there are 15 cell style Xfs
+
+ $cVal = $cell->getValue();
+ if ($cVal instanceof RichText) {
+ $arrcRun = [];
+ $str_len = StringHelper::countCharacters($cVal->getPlainText(), 'UTF-8');
+ $str_pos = 0;
+ $elements = $cVal->getRichTextElements();
+ foreach ($elements as $element) {
+ // FONT Index
+ if ($element instanceof Run) {
+ $str_fontidx = $this->fontHashIndex[$element->getFont()->getHashCode()];
+ } else {
+ $str_fontidx = 0;
+ }
+ $arrcRun[] = ['strlen' => $str_pos, 'fontidx' => $str_fontidx];
+ // Position FROM
+ $str_pos += StringHelper::countCharacters($element->getText(), 'UTF-8');
+ }
+ $this->writeRichTextString($row, $column, $cVal->getPlainText(), $xfIndex, $arrcRun);
+ } else {
+ switch ($cell->getDatatype()) {
+ case DataType::TYPE_STRING:
+ case DataType::TYPE_NULL:
+ if ($cVal === '' || $cVal === null) {
+ $this->writeBlank($row, $column, $xfIndex);
+ } else {
+ $this->writeString($row, $column, $cVal, $xfIndex);
+ }
+
+ break;
+ case DataType::TYPE_NUMERIC:
+ $this->writeNumber($row, $column, $cVal, $xfIndex);
+
+ break;
+ case DataType::TYPE_FORMULA:
+ $calculatedValue = $this->preCalculateFormulas ?
+ $cell->getCalculatedValue() : null;
+ if (self::WRITE_FORMULA_EXCEPTION == $this->writeFormula($row, $column, $cVal, $xfIndex, $calculatedValue)) {
+ if ($calculatedValue === null) {
+ $calculatedValue = $cell->getCalculatedValue();
+ }
+ $calctype = gettype($calculatedValue);
+ switch ($calctype) {
+ case 'integer':
+ case 'double':
+ $this->writeNumber($row, $column, $calculatedValue, $xfIndex);
+
+ break;
+ case 'string':
+ $this->writeString($row, $column, $calculatedValue, $xfIndex);
+
+ break;
+ case 'boolean':
+ $this->writeBoolErr($row, $column, $calculatedValue, 0, $xfIndex);
+
+ break;
+ default:
+ $this->writeString($row, $column, $cVal, $xfIndex);
+ }
+ }
+
+ break;
+ case DataType::TYPE_BOOL:
+ $this->writeBoolErr($row, $column, $cVal, 0, $xfIndex);
+
+ break;
+ case DataType::TYPE_ERROR:
+ $this->writeBoolErr($row, $column, self::mapErrorCode($cVal), 1, $xfIndex);
+
+ break;
+ }
+ }
+ }
+
+ // Append
+ $this->writeMsoDrawing();
+
+ // Restoring active sheet.
+ $this->phpSheet->getParent()->setActiveSheetIndex($activeSheetIndex);
+
+ // Write WINDOW2 record
+ $this->writeWindow2();
+
+ // Write PLV record
+ $this->writePageLayoutView();
+
+ // Write ZOOM record
+ $this->writeZoom();
+ if ($phpSheet->getFreezePane()) {
+ $this->writePanes();
+ }
+
+ // Restoring selected cells.
+ $this->phpSheet->setSelectedCells($selectedCells);
+
+ // Write SELECTION record
+ $this->writeSelection();
+
+ // Write MergedCellsTable Record
+ $this->writeMergedCells();
+
+ // Hyperlinks
+ foreach ($phpSheet->getHyperLinkCollection() as $coordinate => $hyperlink) {
+ [$column, $row] = Coordinate::coordinateFromString($coordinate);
+
+ $url = $hyperlink->getUrl();
+
+ if (strpos($url, 'sheet://') !== false) {
+ // internal to current workbook
+ $url = str_replace('sheet://', 'internal:', $url);
+ } elseif (preg_match('/^(http:|https:|ftp:|mailto:)/', $url)) {
+ // URL
+ } else {
+ // external (local file)
+ $url = 'external:' . $url;
+ }
+
+ $this->writeUrl($row - 1, Coordinate::columnIndexFromString($column) - 1, $url);
+ }
+
+ $this->writeDataValidity();
+ $this->writeSheetLayout();
+
+ // Write SHEETPROTECTION record
+ $this->writeSheetProtection();
+ $this->writeRangeProtection();
+
+ $arrConditionalStyles = $phpSheet->getConditionalStylesCollection();
+ if (!empty($arrConditionalStyles)) {
+ $arrConditional = [];
+ // @TODO CFRule & CFHeader
+ // Write CFHEADER record
+ $this->writeCFHeader();
+ // Write ConditionalFormattingTable records
+ foreach ($arrConditionalStyles as $cellCoordinate => $conditionalStyles) {
+ foreach ($conditionalStyles as $conditional) {
+ if ($conditional->getConditionType() == Conditional::CONDITION_EXPRESSION
+ || $conditional->getConditionType() == Conditional::CONDITION_CELLIS) {
+ if (!isset($arrConditional[$conditional->getHashCode()])) {
+ // This hash code has been handled
+ $arrConditional[$conditional->getHashCode()] = true;
+
+ // Write CFRULE record
+ $this->writeCFRule($conditional);
+ }
+ }
+ }
+ }
+ }
+
+ $this->storeEof();
+ }
+
+ /**
+ * Write a cell range address in BIFF8
+ * always fixed range
+ * See section 2.5.14 in OpenOffice.org's Documentation of the Microsoft Excel File Format.
+ *
+ * @param string $range E.g. 'A1' or 'A1:B6'
+ *
+ * @return string Binary data
+ */
+ private function writeBIFF8CellRangeAddressFixed($range)
+ {
+ $explodes = explode(':', $range);
+
+ // extract first cell, e.g. 'A1'
+ $firstCell = $explodes[0];
+
+ // extract last cell, e.g. 'B6'
+ if (count($explodes) == 1) {
+ $lastCell = $firstCell;
+ } else {
+ $lastCell = $explodes[1];
+ }
+
+ $firstCellCoordinates = Coordinate::coordinateFromString($firstCell); // e.g. [0, 1]
+ $lastCellCoordinates = Coordinate::coordinateFromString($lastCell); // e.g. [1, 6]
+
+ return pack('vvvv', $firstCellCoordinates[1] - 1, $lastCellCoordinates[1] - 1, Coordinate::columnIndexFromString($firstCellCoordinates[0]) - 1, Coordinate::columnIndexFromString($lastCellCoordinates[0]) - 1);
+ }
+
+ /**
+ * Retrieves data from memory in one chunk, or from disk in $buffer
+ * sized chunks.
+ *
+ * @return string The data
+ */
+ public function getData()
+ {
+ $buffer = 4096;
+
+ // Return data stored in memory
+ if (isset($this->_data)) {
+ $tmp = $this->_data;
+ $this->_data = null;
+
+ return $tmp;
+ }
+
+ // No data to return
+ return false;
+ }
+
+ /**
+ * Set the option to print the row and column headers on the printed page.
+ *
+ * @param int $print Whether to print the headers or not. Defaults to 1 (print).
+ */
+ public function printRowColHeaders($print = 1): void
+ {
+ $this->printHeaders = $print;
+ }
+
+ /**
+ * This method sets the properties for outlining and grouping. The defaults
+ * correspond to Excel's defaults.
+ *
+ * @param bool $visible
+ * @param bool $symbols_below
+ * @param bool $symbols_right
+ * @param bool $auto_style
+ */
+ public function setOutline($visible = true, $symbols_below = true, $symbols_right = true, $auto_style = false): void
+ {
+ $this->outlineOn = $visible;
+ $this->outlineBelow = $symbols_below;
+ $this->outlineRight = $symbols_right;
+ $this->outlineStyle = $auto_style;
+
+ // Ensure this is a boolean vale for Window2
+ if ($this->outlineOn) {
+ $this->outlineOn = 1;
+ }
+ }
+
+ /**
+ * Write a double to the specified row and column (zero indexed).
+ * An integer can be written as a double. Excel will display an
+ * integer. $format is optional.
+ *
+ * Returns 0 : normal termination
+ * -2 : row or column out of range
+ *
+ * @param int $row Zero indexed row
+ * @param int $col Zero indexed column
+ * @param float $num The number to write
+ * @param mixed $xfIndex The optional XF format
+ *
+ * @return int
+ */
+ private function writeNumber($row, $col, $num, $xfIndex)
+ {
+ $record = 0x0203; // Record identifier
+ $length = 0x000E; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvv', $row, $col, $xfIndex);
+ $xl_double = pack('d', $num);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $xl_double = strrev($xl_double);
+ }
+
+ $this->append($header . $data . $xl_double);
+
+ return 0;
+ }
+
+ /**
+ * Write a LABELSST record or a LABEL record. Which one depends on BIFF version.
+ *
+ * @param int $row Row index (0-based)
+ * @param int $col Column index (0-based)
+ * @param string $str The string
+ * @param int $xfIndex Index to XF record
+ */
+ private function writeString($row, $col, $str, $xfIndex): void
+ {
+ $this->writeLabelSst($row, $col, $str, $xfIndex);
+ }
+
+ /**
+ * Write a LABELSST record or a LABEL record. Which one depends on BIFF version
+ * It differs from writeString by the writing of rich text strings.
+ *
+ * @param int $row Row index (0-based)
+ * @param int $col Column index (0-based)
+ * @param string $str The string
+ * @param int $xfIndex The XF format index for the cell
+ * @param array $arrcRun Index to Font record and characters beginning
+ */
+ private function writeRichTextString($row, $col, $str, $xfIndex, $arrcRun): void
+ {
+ $record = 0x00FD; // Record identifier
+ $length = 0x000A; // Bytes to follow
+ $str = StringHelper::UTF8toBIFF8UnicodeShort($str, $arrcRun);
+
+ // check if string is already present
+ if (!isset($this->stringTable[$str])) {
+ $this->stringTable[$str] = $this->stringUnique++;
+ }
+ ++$this->stringTotal;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvV', $row, $col, $xfIndex, $this->stringTable[$str]);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write a string to the specified row and column (zero indexed).
+ * This is the BIFF8 version (no 255 chars limit).
+ * $format is optional.
+ *
+ * @param int $row Zero indexed row
+ * @param int $col Zero indexed column
+ * @param string $str The string to write
+ * @param mixed $xfIndex The XF format index for the cell
+ */
+ private function writeLabelSst($row, $col, $str, $xfIndex): void
+ {
+ $record = 0x00FD; // Record identifier
+ $length = 0x000A; // Bytes to follow
+
+ $str = StringHelper::UTF8toBIFF8UnicodeLong($str);
+
+ // check if string is already present
+ if (!isset($this->stringTable[$str])) {
+ $this->stringTable[$str] = $this->stringUnique++;
+ }
+ ++$this->stringTotal;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvV', $row, $col, $xfIndex, $this->stringTable[$str]);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write a blank cell to the specified row and column (zero indexed).
+ * A blank cell is used to specify formatting without adding a string
+ * or a number.
+ *
+ * A blank cell without a format serves no purpose. Therefore, we don't write
+ * a BLANK record unless a format is specified.
+ *
+ * Returns 0 : normal termination (including no format)
+ * -1 : insufficient number of arguments
+ * -2 : row or column out of range
+ *
+ * @param int $row Zero indexed row
+ * @param int $col Zero indexed column
+ * @param mixed $xfIndex The XF format index
+ *
+ * @return int
+ */
+ public function writeBlank($row, $col, $xfIndex)
+ {
+ $record = 0x0201; // Record identifier
+ $length = 0x0006; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvv', $row, $col, $xfIndex);
+ $this->append($header . $data);
+
+ return 0;
+ }
+
+ /**
+ * Write a boolean or an error type to the specified row and column (zero indexed).
+ *
+ * @param int $row Row index (0-based)
+ * @param int $col Column index (0-based)
+ * @param int $value
+ * @param bool $isError Error or Boolean?
+ * @param int $xfIndex
+ *
+ * @return int
+ */
+ private function writeBoolErr($row, $col, $value, $isError, $xfIndex)
+ {
+ $record = 0x0205;
+ $length = 8;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvCC', $row, $col, $xfIndex, $value, $isError);
+ $this->append($header . $data);
+
+ return 0;
+ }
+
+ const WRITE_FORMULA_NORMAL = 0;
+ const WRITE_FORMULA_ERRORS = -1;
+ const WRITE_FORMULA_RANGE = -2;
+ const WRITE_FORMULA_EXCEPTION = -3;
+
+ /**
+ * Write a formula to the specified row and column (zero indexed).
+ * The textual representation of the formula is passed to the parser in
+ * Parser.php which returns a packed binary string.
+ *
+ * Returns 0 : WRITE_FORMULA_NORMAL normal termination
+ * -1 : WRITE_FORMULA_ERRORS formula errors (bad formula)
+ * -2 : WRITE_FORMULA_RANGE row or column out of range
+ * -3 : WRITE_FORMULA_EXCEPTION parse raised exception, probably due to definedname
+ *
+ * @param int $row Zero indexed row
+ * @param int $col Zero indexed column
+ * @param string $formula The formula text string
+ * @param mixed $xfIndex The XF format index
+ * @param mixed $calculatedValue Calculated value
+ *
+ * @return int
+ */
+ private function writeFormula($row, $col, $formula, $xfIndex, $calculatedValue)
+ {
+ $record = 0x0006; // Record identifier
+
+ // Initialize possible additional value for STRING record that should be written after the FORMULA record?
+ $stringValue = null;
+
+ // calculated value
+ if (isset($calculatedValue)) {
+ // Since we can't yet get the data type of the calculated value,
+ // we use best effort to determine data type
+ if (is_bool($calculatedValue)) {
+ // Boolean value
+ $num = pack('CCCvCv', 0x01, 0x00, (int) $calculatedValue, 0x00, 0x00, 0xFFFF);
+ } elseif (is_int($calculatedValue) || is_float($calculatedValue)) {
+ // Numeric value
+ $num = pack('d', $calculatedValue);
+ } elseif (is_string($calculatedValue)) {
+ $errorCodes = DataType::getErrorCodes();
+ if (isset($errorCodes[$calculatedValue])) {
+ // Error value
+ $num = pack('CCCvCv', 0x02, 0x00, self::mapErrorCode($calculatedValue), 0x00, 0x00, 0xFFFF);
+ } elseif ($calculatedValue === '') {
+ // Empty string (and BIFF8)
+ $num = pack('CCCvCv', 0x03, 0x00, 0x00, 0x00, 0x00, 0xFFFF);
+ } else {
+ // Non-empty string value (or empty string BIFF5)
+ $stringValue = $calculatedValue;
+ $num = pack('CCCvCv', 0x00, 0x00, 0x00, 0x00, 0x00, 0xFFFF);
+ }
+ } else {
+ // We are really not supposed to reach here
+ $num = pack('d', 0x00);
+ }
+ } else {
+ $num = pack('d', 0x00);
+ }
+
+ $grbit = 0x03; // Option flags
+ $unknown = 0x0000; // Must be zero
+
+ // Strip the '=' or '@' sign at the beginning of the formula string
+ if ($formula[0] == '=') {
+ $formula = substr($formula, 1);
+ } else {
+ // Error handling
+ $this->writeString($row, $col, 'Unrecognised character for formula', 0);
+
+ return self::WRITE_FORMULA_ERRORS;
+ }
+
+ // Parse the formula using the parser in Parser.php
+ try {
+ $error = $this->parser->parse($formula);
+ $formula = $this->parser->toReversePolish();
+
+ $formlen = strlen($formula); // Length of the binary string
+ $length = 0x16 + $formlen; // Length of the record data
+
+ $header = pack('vv', $record, $length);
+
+ $data = pack('vvv', $row, $col, $xfIndex)
+ . $num
+ . pack('vVv', $grbit, $unknown, $formlen);
+ $this->append($header . $data . $formula);
+
+ // Append also a STRING record if necessary
+ if ($stringValue !== null) {
+ $this->writeStringRecord($stringValue);
+ }
+
+ return self::WRITE_FORMULA_NORMAL;
+ } catch (PhpSpreadsheetException $e) {
+ return self::WRITE_FORMULA_EXCEPTION;
+ }
+ }
+
+ /**
+ * Write a STRING record. This.
+ *
+ * @param string $stringValue
+ */
+ private function writeStringRecord($stringValue): void
+ {
+ $record = 0x0207; // Record identifier
+ $data = StringHelper::UTF8toBIFF8UnicodeLong($stringValue);
+
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write a hyperlink.
+ * This is comprised of two elements: the visible label and
+ * the invisible link. The visible label is the same as the link unless an
+ * alternative string is specified. The label is written using the
+ * writeString() method. Therefore the 255 characters string limit applies.
+ * $string and $format are optional.
+ *
+ * The hyperlink can be to a http, ftp, mail, internal sheet (not yet), or external
+ * directory url.
+ *
+ * Returns 0 : normal termination
+ * -2 : row or column out of range
+ * -3 : long string truncated to 255 chars
+ *
+ * @param int $row Row
+ * @param int $col Column
+ * @param string $url URL string
+ *
+ * @return int
+ */
+ private function writeUrl($row, $col, $url)
+ {
+ // Add start row and col to arg list
+ return $this->writeUrlRange($row, $col, $row, $col, $url);
+ }
+
+ /**
+ * This is the more general form of writeUrl(). It allows a hyperlink to be
+ * written to a range of cells. This function also decides the type of hyperlink
+ * to be written. These are either, Web (http, ftp, mailto), Internal
+ * (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1').
+ *
+ * @param int $row1 Start row
+ * @param int $col1 Start column
+ * @param int $row2 End row
+ * @param int $col2 End column
+ * @param string $url URL string
+ *
+ * @return int
+ *
+ * @see writeUrl()
+ */
+ public function writeUrlRange($row1, $col1, $row2, $col2, $url)
+ {
+ // Check for internal/external sheet links or default to web link
+ if (preg_match('[^internal:]', $url)) {
+ return $this->writeUrlInternal($row1, $col1, $row2, $col2, $url);
+ }
+ if (preg_match('[^external:]', $url)) {
+ return $this->writeUrlExternal($row1, $col1, $row2, $col2, $url);
+ }
+
+ return $this->writeUrlWeb($row1, $col1, $row2, $col2, $url);
+ }
+
+ /**
+ * Used to write http, ftp and mailto hyperlinks.
+ * The link type ($options) is 0x03 is the same as absolute dir ref without
+ * sheet. However it is differentiated by the $unknown2 data stream.
+ *
+ * @param int $row1 Start row
+ * @param int $col1 Start column
+ * @param int $row2 End row
+ * @param int $col2 End column
+ * @param string $url URL string
+ *
+ * @return int
+ *
+ * @see writeUrl()
+ */
+ public function writeUrlWeb($row1, $col1, $row2, $col2, $url)
+ {
+ $record = 0x01B8; // Record identifier
+ $length = 0x00000; // Bytes to follow
+
+ // Pack the undocumented parts of the hyperlink stream
+ $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000');
+ $unknown2 = pack('H*', 'E0C9EA79F9BACE118C8200AA004BA90B');
+
+ // Pack the option flags
+ $options = pack('V', 0x03);
+
+ // Convert URL to a null terminated wchar string
+ $url = implode("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
+ $url = $url . "\0\0\0";
+
+ // Pack the length of the URL
+ $url_len = pack('V', strlen($url));
+
+ // Calculate the data length
+ $length = 0x34 + strlen($url);
+
+ // Pack the header data
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvv', $row1, $row2, $col1, $col2);
+
+ // Write the packed data
+ $this->append($header . $data . $unknown1 . $options . $unknown2 . $url_len . $url);
+
+ return 0;
+ }
+
+ /**
+ * Used to write internal reference hyperlinks such as "Sheet1!A1".
+ *
+ * @param int $row1 Start row
+ * @param int $col1 Start column
+ * @param int $row2 End row
+ * @param int $col2 End column
+ * @param string $url URL string
+ *
+ * @return int
+ *
+ * @see writeUrl()
+ */
+ public function writeUrlInternal($row1, $col1, $row2, $col2, $url)
+ {
+ $record = 0x01B8; // Record identifier
+ $length = 0x00000; // Bytes to follow
+
+ // Strip URL type
+ $url = preg_replace('/^internal:/', '', $url);
+
+ // Pack the undocumented parts of the hyperlink stream
+ $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000');
+
+ // Pack the option flags
+ $options = pack('V', 0x08);
+
+ // Convert the URL type and to a null terminated wchar string
+ $url .= "\0";
+
+ // character count
+ $url_len = StringHelper::countCharacters($url);
+ $url_len = pack('V', $url_len);
+
+ $url = StringHelper::convertEncoding($url, 'UTF-16LE', 'UTF-8');
+
+ // Calculate the data length
+ $length = 0x24 + strlen($url);
+
+ // Pack the header data
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvv', $row1, $row2, $col1, $col2);
+
+ // Write the packed data
+ $this->append($header . $data . $unknown1 . $options . $url_len . $url);
+
+ return 0;
+ }
+
+ /**
+ * Write links to external directory names such as 'c:\foo.xls',
+ * c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'.
+ *
+ * Note: Excel writes some relative links with the $dir_long string. We ignore
+ * these cases for the sake of simpler code.
+ *
+ * @param int $row1 Start row
+ * @param int $col1 Start column
+ * @param int $row2 End row
+ * @param int $col2 End column
+ * @param string $url URL string
+ *
+ * @return int
+ *
+ * @see writeUrl()
+ */
+ public function writeUrlExternal($row1, $col1, $row2, $col2, $url)
+ {
+ // Network drives are different. We will handle them separately
+ // MS/Novell network drives and shares start with \\
+ if (preg_match('[^external:\\\\]', $url)) {
+ return; //($this->writeUrlExternal_net($row1, $col1, $row2, $col2, $url, $str, $format));
+ }
+
+ $record = 0x01B8; // Record identifier
+ $length = 0x00000; // Bytes to follow
+
+ // Strip URL type and change Unix dir separator to Dos style (if needed)
+ //
+ $url = preg_replace('/^external:/', '', $url);
+ $url = preg_replace('/\//', '\\', $url);
+
+ // Determine if the link is relative or absolute:
+ // relative if link contains no dir separator, "somefile.xls"
+ // relative if link starts with up-dir, "..\..\somefile.xls"
+ // otherwise, absolute
+
+ $absolute = 0x00; // relative path
+ if (preg_match('/^[A-Z]:/', $url)) {
+ $absolute = 0x02; // absolute path on Windows, e.g. C:\...
+ }
+ $link_type = 0x01 | $absolute;
+
+ // Determine if the link contains a sheet reference and change some of the
+ // parameters accordingly.
+ // Split the dir name and sheet name (if it exists)
+ $dir_long = $url;
+ if (preg_match('/\\#/', $url)) {
+ $link_type |= 0x08;
+ }
+
+ // Pack the link type
+ $link_type = pack('V', $link_type);
+
+ // Calculate the up-level dir count e.g.. (..\..\..\ == 3)
+ $up_count = preg_match_all('/\\.\\.\\\\/', $dir_long, $useless);
+ $up_count = pack('v', $up_count);
+
+ // Store the short dos dir name (null terminated)
+ $dir_short = preg_replace('/\\.\\.\\\\/', '', $dir_long) . "\0";
+
+ // Store the long dir name as a wchar string (non-null terminated)
+ $dir_long = $dir_long . "\0";
+
+ // Pack the lengths of the dir strings
+ $dir_short_len = pack('V', strlen($dir_short));
+ $dir_long_len = pack('V', strlen($dir_long));
+ $stream_len = pack('V', 0); //strlen($dir_long) + 0x06);
+
+ // Pack the undocumented parts of the hyperlink stream
+ $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000');
+ $unknown2 = pack('H*', '0303000000000000C000000000000046');
+ $unknown3 = pack('H*', 'FFFFADDE000000000000000000000000000000000000000');
+ $unknown4 = pack('v', 0x03);
+
+ // Pack the main data stream
+ $data = pack('vvvv', $row1, $row2, $col1, $col2) .
+ $unknown1 .
+ $link_type .
+ $unknown2 .
+ $up_count .
+ $dir_short_len .
+ $dir_short .
+ $unknown3 .
+ $stream_len; /*.
+ $dir_long_len .
+ $unknown4 .
+ $dir_long .
+ $sheet_len .
+ $sheet ;*/
+
+ // Pack the header data
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ // Write the packed data
+ $this->append($header . $data);
+
+ return 0;
+ }
+
+ /**
+ * This method is used to set the height and format for a row.
+ *
+ * @param int $row The row to set
+ * @param int $height Height we are giving to the row.
+ * Use null to set XF without setting height
+ * @param int $xfIndex The optional cell style Xf index to apply to the columns
+ * @param bool $hidden The optional hidden attribute
+ * @param int $level The optional outline level for row, in range [0,7]
+ */
+ private function writeRow($row, $height, $xfIndex, $hidden = false, $level = 0): void
+ {
+ $record = 0x0208; // Record identifier
+ $length = 0x0010; // Number of bytes to follow
+
+ $colMic = 0x0000; // First defined column
+ $colMac = 0x0000; // Last defined column
+ $irwMac = 0x0000; // Used by Excel to optimise loading
+ $reserved = 0x0000; // Reserved
+ $grbit = 0x0000; // Option flags
+ $ixfe = $xfIndex;
+
+ if ($height < 0) {
+ $height = null;
+ }
+
+ // Use writeRow($row, null, $XF) to set XF format without setting height
+ if ($height != null) {
+ $miyRw = $height * 20; // row height
+ } else {
+ $miyRw = 0xff; // default row height is 256
+ }
+
+ // Set the options flags. fUnsynced is used to show that the font and row
+ // heights are not compatible. This is usually the case for WriteExcel.
+ // The collapsed flag 0x10 doesn't seem to be used to indicate that a row
+ // is collapsed. Instead it is used to indicate that the previous row is
+ // collapsed. The zero height flag, 0x20, is used to collapse a row.
+
+ $grbit |= $level;
+ if ($hidden) {
+ $grbit |= 0x0030;
+ }
+ if ($height !== null) {
+ $grbit |= 0x0040; // fUnsynced
+ }
+ if ($xfIndex !== 0xF) {
+ $grbit |= 0x0080;
+ }
+ $grbit |= 0x0100;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvvvvvv', $row, $colMic, $colMac, $miyRw, $irwMac, $reserved, $grbit, $ixfe);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Writes Excel DIMENSIONS to define the area in which there is data.
+ */
+ private function writeDimensions(): void
+ {
+ $record = 0x0200; // Record identifier
+
+ $length = 0x000E;
+ $data = pack('VVvvv', $this->firstRowIndex, $this->lastRowIndex + 1, $this->firstColumnIndex, $this->lastColumnIndex + 1, 0x0000); // reserved
+
+ $header = pack('vv', $record, $length);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record Window2.
+ */
+ private function writeWindow2(): void
+ {
+ $record = 0x023E; // Record identifier
+ $length = 0x0012;
+
+ $grbit = 0x00B6; // Option flags
+ $rwTop = 0x0000; // Top row visible in window
+ $colLeft = 0x0000; // Leftmost column visible in window
+
+ // The options flags that comprise $grbit
+ $fDspFmla = 0; // 0 - bit
+ $fDspGrid = $this->phpSheet->getShowGridlines() ? 1 : 0; // 1
+ $fDspRwCol = $this->phpSheet->getShowRowColHeaders() ? 1 : 0; // 2
+ $fFrozen = $this->phpSheet->getFreezePane() ? 1 : 0; // 3
+ $fDspZeros = 1; // 4
+ $fDefaultHdr = 1; // 5
+ $fArabic = $this->phpSheet->getRightToLeft() ? 1 : 0; // 6
+ $fDspGuts = $this->outlineOn; // 7
+ $fFrozenNoSplit = 0; // 0 - bit
+ // no support in PhpSpreadsheet for selected sheet, therefore sheet is only selected if it is the active sheet
+ $fSelected = ($this->phpSheet === $this->phpSheet->getParent()->getActiveSheet()) ? 1 : 0;
+ $fPageBreakPreview = $this->phpSheet->getSheetView()->getView() === SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW;
+
+ $grbit = $fDspFmla;
+ $grbit |= $fDspGrid << 1;
+ $grbit |= $fDspRwCol << 2;
+ $grbit |= $fFrozen << 3;
+ $grbit |= $fDspZeros << 4;
+ $grbit |= $fDefaultHdr << 5;
+ $grbit |= $fArabic << 6;
+ $grbit |= $fDspGuts << 7;
+ $grbit |= $fFrozenNoSplit << 8;
+ $grbit |= $fSelected << 9; // Selected sheets.
+ $grbit |= $fSelected << 10; // Active sheet.
+ $grbit |= $fPageBreakPreview << 11;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvv', $grbit, $rwTop, $colLeft);
+
+ // FIXME !!!
+ $rgbHdr = 0x0040; // Row/column heading and gridline color index
+ $zoom_factor_page_break = ($fPageBreakPreview ? $this->phpSheet->getSheetView()->getZoomScale() : 0x0000);
+ $zoom_factor_normal = $this->phpSheet->getSheetView()->getZoomScaleNormal();
+
+ $data .= pack('vvvvV', $rgbHdr, 0x0000, $zoom_factor_page_break, $zoom_factor_normal, 0x00000000);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record DEFAULTROWHEIGHT.
+ */
+ private function writeDefaultRowHeight(): void
+ {
+ $defaultRowHeight = $this->phpSheet->getDefaultRowDimension()->getRowHeight();
+
+ if ($defaultRowHeight < 0) {
+ return;
+ }
+
+ // convert to twips
+ $defaultRowHeight = (int) 20 * $defaultRowHeight;
+
+ $record = 0x0225; // Record identifier
+ $length = 0x0004; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vv', 1, $defaultRowHeight);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record DEFCOLWIDTH if COLINFO records are in use.
+ */
+ private function writeDefcol(): void
+ {
+ $defaultColWidth = 8;
+
+ $record = 0x0055; // Record identifier
+ $length = 0x0002; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $defaultColWidth);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record COLINFO to define column widths.
+ *
+ * Note: The SDK says the record length is 0x0B but Excel writes a 0x0C
+ * length record.
+ *
+ * @param array $col_array This is the only parameter received and is composed of the following:
+ * 0 => First formatted column,
+ * 1 => Last formatted column,
+ * 2 => Col width (8.43 is Excel default),
+ * 3 => The optional XF format of the column,
+ * 4 => Option flags.
+ * 5 => Optional outline level
+ */
+ private function writeColinfo($col_array): void
+ {
+ if (isset($col_array[0])) {
+ $colFirst = $col_array[0];
+ }
+ if (isset($col_array[1])) {
+ $colLast = $col_array[1];
+ }
+ if (isset($col_array[2])) {
+ $coldx = $col_array[2];
+ } else {
+ $coldx = 8.43;
+ }
+ if (isset($col_array[3])) {
+ $xfIndex = $col_array[3];
+ } else {
+ $xfIndex = 15;
+ }
+ if (isset($col_array[4])) {
+ $grbit = $col_array[4];
+ } else {
+ $grbit = 0;
+ }
+ if (isset($col_array[5])) {
+ $level = $col_array[5];
+ } else {
+ $level = 0;
+ }
+ $record = 0x007D; // Record identifier
+ $length = 0x000C; // Number of bytes to follow
+
+ $coldx *= 256; // Convert to units of 1/256 of a char
+
+ $ixfe = $xfIndex;
+ $reserved = 0x0000; // Reserved
+
+ $level = max(0, min($level, 7));
+ $grbit |= $level << 8;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvvvv', $colFirst, $colLast, $coldx, $ixfe, $grbit, $reserved);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record SELECTION.
+ */
+ private function writeSelection(): void
+ {
+ // look up the selected cell range
+ $selectedCells = Coordinate::splitRange($this->phpSheet->getSelectedCells());
+ $selectedCells = $selectedCells[0];
+ if (count($selectedCells) == 2) {
+ [$first, $last] = $selectedCells;
+ } else {
+ $first = $selectedCells[0];
+ $last = $selectedCells[0];
+ }
+
+ [$colFirst, $rwFirst] = Coordinate::coordinateFromString($first);
+ $colFirst = Coordinate::columnIndexFromString($colFirst) - 1; // base 0 column index
+ --$rwFirst; // base 0 row index
+
+ [$colLast, $rwLast] = Coordinate::coordinateFromString($last);
+ $colLast = Coordinate::columnIndexFromString($colLast) - 1; // base 0 column index
+ --$rwLast; // base 0 row index
+
+ // make sure we are not out of bounds
+ $colFirst = min($colFirst, 255);
+ $colLast = min($colLast, 255);
+
+ $rwFirst = min($rwFirst, 65535);
+ $rwLast = min($rwLast, 65535);
+
+ $record = 0x001D; // Record identifier
+ $length = 0x000F; // Number of bytes to follow
+
+ $pnn = $this->activePane; // Pane position
+ $rwAct = $rwFirst; // Active row
+ $colAct = $colFirst; // Active column
+ $irefAct = 0; // Active cell ref
+ $cref = 1; // Number of refs
+
+ if (!isset($rwLast)) {
+ $rwLast = $rwFirst; // Last row in reference
+ }
+ if (!isset($colLast)) {
+ $colLast = $colFirst; // Last col in reference
+ }
+
+ // Swap last row/col for first row/col as necessary
+ if ($rwFirst > $rwLast) {
+ [$rwFirst, $rwLast] = [$rwLast, $rwFirst];
+ }
+
+ if ($colFirst > $colLast) {
+ [$colFirst, $colLast] = [$colLast, $colFirst];
+ }
+
+ $header = pack('vv', $record, $length);
+ $data = pack('CvvvvvvCC', $pnn, $rwAct, $colAct, $irefAct, $cref, $rwFirst, $rwLast, $colFirst, $colLast);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the MERGEDCELLS records for all ranges of merged cells.
+ */
+ private function writeMergedCells(): void
+ {
+ $mergeCells = $this->phpSheet->getMergeCells();
+ $countMergeCells = count($mergeCells);
+
+ if ($countMergeCells == 0) {
+ return;
+ }
+
+ // maximum allowed number of merged cells per record
+ $maxCountMergeCellsPerRecord = 1027;
+
+ // record identifier
+ $record = 0x00E5;
+
+ // counter for total number of merged cells treated so far by the writer
+ $i = 0;
+
+ // counter for number of merged cells written in record currently being written
+ $j = 0;
+
+ // initialize record data
+ $recordData = '';
+
+ // loop through the merged cells
+ foreach ($mergeCells as $mergeCell) {
+ ++$i;
+ ++$j;
+
+ // extract the row and column indexes
+ $range = Coordinate::splitRange($mergeCell);
+ [$first, $last] = $range[0];
+ [$firstColumn, $firstRow] = Coordinate::coordinateFromString($first);
+ [$lastColumn, $lastRow] = Coordinate::coordinateFromString($last);
+
+ $recordData .= pack('vvvv', $firstRow - 1, $lastRow - 1, Coordinate::columnIndexFromString($firstColumn) - 1, Coordinate::columnIndexFromString($lastColumn) - 1);
+
+ // flush record if we have reached limit for number of merged cells, or reached final merged cell
+ if ($j == $maxCountMergeCellsPerRecord || $i == $countMergeCells) {
+ $recordData = pack('v', $j) . $recordData;
+ $length = strlen($recordData);
+ $header = pack('vv', $record, $length);
+ $this->append($header . $recordData);
+
+ // initialize for next record, if any
+ $recordData = '';
+ $j = 0;
+ }
+ }
+ }
+
+ /**
+ * Write SHEETLAYOUT record.
+ */
+ private function writeSheetLayout(): void
+ {
+ if (!$this->phpSheet->isTabColorSet()) {
+ return;
+ }
+
+ $recordData = pack(
+ 'vvVVVvv',
+ 0x0862,
+ 0x0000, // unused
+ 0x00000000, // unused
+ 0x00000000, // unused
+ 0x00000014, // size of record data
+ $this->colors[$this->phpSheet->getTabColor()->getRGB()], // color index
+ 0x0000 // unused
+ );
+
+ $length = strlen($recordData);
+
+ $record = 0x0862; // Record identifier
+ $header = pack('vv', $record, $length);
+ $this->append($header . $recordData);
+ }
+
+ /**
+ * Write SHEETPROTECTION.
+ */
+ private function writeSheetProtection(): void
+ {
+ // record identifier
+ $record = 0x0867;
+
+ // prepare options
+ $options = (int) !$this->phpSheet->getProtection()->getObjects()
+ | (int) !$this->phpSheet->getProtection()->getScenarios() << 1
+ | (int) !$this->phpSheet->getProtection()->getFormatCells() << 2
+ | (int) !$this->phpSheet->getProtection()->getFormatColumns() << 3
+ | (int) !$this->phpSheet->getProtection()->getFormatRows() << 4
+ | (int) !$this->phpSheet->getProtection()->getInsertColumns() << 5
+ | (int) !$this->phpSheet->getProtection()->getInsertRows() << 6
+ | (int) !$this->phpSheet->getProtection()->getInsertHyperlinks() << 7
+ | (int) !$this->phpSheet->getProtection()->getDeleteColumns() << 8
+ | (int) !$this->phpSheet->getProtection()->getDeleteRows() << 9
+ | (int) !$this->phpSheet->getProtection()->getSelectLockedCells() << 10
+ | (int) !$this->phpSheet->getProtection()->getSort() << 11
+ | (int) !$this->phpSheet->getProtection()->getAutoFilter() << 12
+ | (int) !$this->phpSheet->getProtection()->getPivotTables() << 13
+ | (int) !$this->phpSheet->getProtection()->getSelectUnlockedCells() << 14;
+
+ // record data
+ $recordData = pack(
+ 'vVVCVVvv',
+ 0x0867, // repeated record identifier
+ 0x0000, // not used
+ 0x0000, // not used
+ 0x00, // not used
+ 0x01000200, // unknown data
+ 0xFFFFFFFF, // unknown data
+ $options, // options
+ 0x0000 // not used
+ );
+
+ $length = strlen($recordData);
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $recordData);
+ }
+
+ /**
+ * Write BIFF record RANGEPROTECTION.
+ *
+ * Openoffice.org's Documentaion of the Microsoft Excel File Format uses term RANGEPROTECTION for these records
+ * Microsoft Office Excel 97-2007 Binary File Format Specification uses term FEAT for these records
+ */
+ private function writeRangeProtection(): void
+ {
+ foreach ($this->phpSheet->getProtectedCells() as $range => $password) {
+ // number of ranges, e.g. 'A1:B3 C20:D25'
+ $cellRanges = explode(' ', $range);
+ $cref = count($cellRanges);
+
+ $recordData = pack(
+ 'vvVVvCVvVv',
+ 0x0868,
+ 0x00,
+ 0x0000,
+ 0x0000,
+ 0x02,
+ 0x0,
+ 0x0000,
+ $cref,
+ 0x0000,
+ 0x00
+ );
+
+ foreach ($cellRanges as $cellRange) {
+ $recordData .= $this->writeBIFF8CellRangeAddressFixed($cellRange);
+ }
+
+ // the rgbFeat structure
+ $recordData .= pack(
+ 'VV',
+ 0x0000,
+ hexdec($password)
+ );
+
+ $recordData .= StringHelper::UTF8toBIFF8UnicodeLong('p' . md5($recordData));
+
+ $length = strlen($recordData);
+
+ $record = 0x0868; // Record identifier
+ $header = pack('vv', $record, $length);
+ $this->append($header . $recordData);
+ }
+ }
+
+ /**
+ * Writes the Excel BIFF PANE record.
+ * The panes can either be frozen or thawed (unfrozen).
+ * Frozen panes are specified in terms of an integer number of rows and columns.
+ * Thawed panes are specified in terms of Excel's units for rows and columns.
+ */
+ private function writePanes(): void
+ {
+ $panes = [];
+ if ($this->phpSheet->getFreezePane()) {
+ [$column, $row] = Coordinate::coordinateFromString($this->phpSheet->getFreezePane());
+ $panes[0] = Coordinate::columnIndexFromString($column) - 1;
+ $panes[1] = $row - 1;
+
+ [$leftMostColumn, $topRow] = Coordinate::coordinateFromString($this->phpSheet->getTopLeftCell());
+ //Coordinates are zero-based in xls files
+ $panes[2] = $topRow - 1;
+ $panes[3] = Coordinate::columnIndexFromString($leftMostColumn) - 1;
+ } else {
+ // thaw panes
+ return;
+ }
+
+ $x = $panes[0] ?? null;
+ $y = $panes[1] ?? null;
+ $rwTop = $panes[2] ?? null;
+ $colLeft = $panes[3] ?? null;
+ if (count($panes) > 4) { // if Active pane was received
+ $pnnAct = $panes[4];
+ } else {
+ $pnnAct = null;
+ }
+ $record = 0x0041; // Record identifier
+ $length = 0x000A; // Number of bytes to follow
+
+ // Code specific to frozen or thawed panes.
+ if ($this->phpSheet->getFreezePane()) {
+ // Set default values for $rwTop and $colLeft
+ if (!isset($rwTop)) {
+ $rwTop = $y;
+ }
+ if (!isset($colLeft)) {
+ $colLeft = $x;
+ }
+ } else {
+ // Set default values for $rwTop and $colLeft
+ if (!isset($rwTop)) {
+ $rwTop = 0;
+ }
+ if (!isset($colLeft)) {
+ $colLeft = 0;
+ }
+
+ // Convert Excel's row and column units to the internal units.
+ // The default row height is 12.75
+ // The default column width is 8.43
+ // The following slope and intersection values were interpolated.
+ //
+ $y = 20 * $y + 255;
+ $x = 113.879 * $x + 390;
+ }
+
+ // Determine which pane should be active. There is also the undocumented
+ // option to override this should it be necessary: may be removed later.
+ //
+ if (!isset($pnnAct)) {
+ if ($x != 0 && $y != 0) {
+ $pnnAct = 0; // Bottom right
+ }
+ if ($x != 0 && $y == 0) {
+ $pnnAct = 1; // Top right
+ }
+ if ($x == 0 && $y != 0) {
+ $pnnAct = 2; // Bottom left
+ }
+ if ($x == 0 && $y == 0) {
+ $pnnAct = 3; // Top left
+ }
+ }
+
+ $this->activePane = $pnnAct; // Used in writeSelection
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvvv', $x, $y, $rwTop, $colLeft, $pnnAct);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the page setup SETUP BIFF record.
+ */
+ private function writeSetup(): void
+ {
+ $record = 0x00A1; // Record identifier
+ $length = 0x0022; // Number of bytes to follow
+
+ $iPaperSize = $this->phpSheet->getPageSetup()->getPaperSize(); // Paper size
+
+ $iScale = $this->phpSheet->getPageSetup()->getScale() ?
+ $this->phpSheet->getPageSetup()->getScale() : 100; // Print scaling factor
+
+ $iPageStart = 0x01; // Starting page number
+ $iFitWidth = (int) $this->phpSheet->getPageSetup()->getFitToWidth(); // Fit to number of pages wide
+ $iFitHeight = (int) $this->phpSheet->getPageSetup()->getFitToHeight(); // Fit to number of pages high
+ $grbit = 0x00; // Option flags
+ $iRes = 0x0258; // Print resolution
+ $iVRes = 0x0258; // Vertical print resolution
+
+ $numHdr = $this->phpSheet->getPageMargins()->getHeader(); // Header Margin
+
+ $numFtr = $this->phpSheet->getPageMargins()->getFooter(); // Footer Margin
+ $iCopies = 0x01; // Number of copies
+
+ $fLeftToRight = 0x0; // Print over then down
+
+ // Page orientation
+ $fLandscape = ($this->phpSheet->getPageSetup()->getOrientation() == PageSetup::ORIENTATION_LANDSCAPE) ?
+ 0x0 : 0x1;
+
+ $fNoPls = 0x0; // Setup not read from printer
+ $fNoColor = 0x0; // Print black and white
+ $fDraft = 0x0; // Print draft quality
+ $fNotes = 0x0; // Print notes
+ $fNoOrient = 0x0; // Orientation not set
+ $fUsePage = 0x0; // Use custom starting page
+
+ $grbit = $fLeftToRight;
+ $grbit |= $fLandscape << 1;
+ $grbit |= $fNoPls << 2;
+ $grbit |= $fNoColor << 3;
+ $grbit |= $fDraft << 4;
+ $grbit |= $fNotes << 5;
+ $grbit |= $fNoOrient << 6;
+ $grbit |= $fUsePage << 7;
+
+ $numHdr = pack('d', $numHdr);
+ $numFtr = pack('d', $numFtr);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $numHdr = strrev($numHdr);
+ $numFtr = strrev($numFtr);
+ }
+
+ $header = pack('vv', $record, $length);
+ $data1 = pack('vvvvvvvv', $iPaperSize, $iScale, $iPageStart, $iFitWidth, $iFitHeight, $grbit, $iRes, $iVRes);
+ $data2 = $numHdr . $numFtr;
+ $data3 = pack('v', $iCopies);
+ $this->append($header . $data1 . $data2 . $data3);
+ }
+
+ /**
+ * Store the header caption BIFF record.
+ */
+ private function writeHeader(): void
+ {
+ $record = 0x0014; // Record identifier
+
+ /* removing for now
+ // need to fix character count (multibyte!)
+ if (strlen($this->phpSheet->getHeaderFooter()->getOddHeader()) <= 255) {
+ $str = $this->phpSheet->getHeaderFooter()->getOddHeader(); // header string
+ } else {
+ $str = '';
+ }
+ */
+
+ $recordData = StringHelper::UTF8toBIFF8UnicodeLong($this->phpSheet->getHeaderFooter()->getOddHeader());
+ $length = strlen($recordData);
+
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $recordData);
+ }
+
+ /**
+ * Store the footer caption BIFF record.
+ */
+ private function writeFooter(): void
+ {
+ $record = 0x0015; // Record identifier
+
+ /* removing for now
+ // need to fix character count (multibyte!)
+ if (strlen($this->phpSheet->getHeaderFooter()->getOddFooter()) <= 255) {
+ $str = $this->phpSheet->getHeaderFooter()->getOddFooter();
+ } else {
+ $str = '';
+ }
+ */
+
+ $recordData = StringHelper::UTF8toBIFF8UnicodeLong($this->phpSheet->getHeaderFooter()->getOddFooter());
+ $length = strlen($recordData);
+
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $recordData);
+ }
+
+ /**
+ * Store the horizontal centering HCENTER BIFF record.
+ */
+ private function writeHcenter(): void
+ {
+ $record = 0x0083; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fHCenter = $this->phpSheet->getPageSetup()->getHorizontalCentered() ? 1 : 0; // Horizontal centering
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fHCenter);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the vertical centering VCENTER BIFF record.
+ */
+ private function writeVcenter(): void
+ {
+ $record = 0x0084; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fVCenter = $this->phpSheet->getPageSetup()->getVerticalCentered() ? 1 : 0; // Horizontal centering
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fVCenter);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the LEFTMARGIN BIFF record.
+ */
+ private function writeMarginLeft(): void
+ {
+ $record = 0x0026; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $margin = $this->phpSheet->getPageMargins()->getLeft(); // Margin in inches
+
+ $header = pack('vv', $record, $length);
+ $data = pack('d', $margin);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $data = strrev($data);
+ }
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the RIGHTMARGIN BIFF record.
+ */
+ private function writeMarginRight(): void
+ {
+ $record = 0x0027; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $margin = $this->phpSheet->getPageMargins()->getRight(); // Margin in inches
+
+ $header = pack('vv', $record, $length);
+ $data = pack('d', $margin);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $data = strrev($data);
+ }
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the TOPMARGIN BIFF record.
+ */
+ private function writeMarginTop(): void
+ {
+ $record = 0x0028; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $margin = $this->phpSheet->getPageMargins()->getTop(); // Margin in inches
+
+ $header = pack('vv', $record, $length);
+ $data = pack('d', $margin);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $data = strrev($data);
+ }
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the BOTTOMMARGIN BIFF record.
+ */
+ private function writeMarginBottom(): void
+ {
+ $record = 0x0029; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $margin = $this->phpSheet->getPageMargins()->getBottom(); // Margin in inches
+
+ $header = pack('vv', $record, $length);
+ $data = pack('d', $margin);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $data = strrev($data);
+ }
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the PRINTHEADERS BIFF record.
+ */
+ private function writePrintHeaders(): void
+ {
+ $record = 0x002a; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fPrintRwCol = $this->printHeaders; // Boolean flag
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fPrintRwCol);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the
+ * GRIDSET record.
+ */
+ private function writePrintGridlines(): void
+ {
+ $record = 0x002b; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fPrintGrid = $this->phpSheet->getPrintGridlines() ? 1 : 0; // Boolean flag
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fPrintGrid);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the GRIDSET BIFF record. Must be used in conjunction with the
+ * PRINTGRIDLINES record.
+ */
+ private function writeGridset(): void
+ {
+ $record = 0x0082; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fGridSet = !$this->phpSheet->getPrintGridlines(); // Boolean flag
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fGridSet);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the AUTOFILTERINFO BIFF record. This is used to configure the number of autofilter select used in the sheet.
+ */
+ private function writeAutoFilterInfo(): void
+ {
+ $record = 0x009D; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $rangeBounds = Coordinate::rangeBoundaries($this->phpSheet->getAutoFilter()->getRange());
+ $iNumFilters = 1 + $rangeBounds[1][0] - $rangeBounds[0][0];
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $iNumFilters);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the GUTS BIFF record. This is used to configure the gutter margins
+ * where Excel outline symbols are displayed. The visibility of the gutters is
+ * controlled by a flag in WSBOOL.
+ *
+ * @see writeWsbool()
+ */
+ private function writeGuts(): void
+ {
+ $record = 0x0080; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $dxRwGut = 0x0000; // Size of row gutter
+ $dxColGut = 0x0000; // Size of col gutter
+
+ // determine maximum row outline level
+ $maxRowOutlineLevel = 0;
+ foreach ($this->phpSheet->getRowDimensions() as $rowDimension) {
+ $maxRowOutlineLevel = max($maxRowOutlineLevel, $rowDimension->getOutlineLevel());
+ }
+
+ $col_level = 0;
+
+ // Calculate the maximum column outline level. The equivalent calculation
+ // for the row outline level is carried out in writeRow().
+ $colcount = count($this->columnInfo);
+ for ($i = 0; $i < $colcount; ++$i) {
+ $col_level = max($this->columnInfo[$i][5], $col_level);
+ }
+
+ // Set the limits for the outline levels (0 <= x <= 7).
+ $col_level = max(0, min($col_level, 7));
+
+ // The displayed level is one greater than the max outline levels
+ if ($maxRowOutlineLevel) {
+ ++$maxRowOutlineLevel;
+ }
+ if ($col_level) {
+ ++$col_level;
+ }
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvv', $dxRwGut, $dxColGut, $maxRowOutlineLevel, $col_level);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the WSBOOL BIFF record, mainly for fit-to-page. Used in conjunction
+ * with the SETUP record.
+ */
+ private function writeWsbool(): void
+ {
+ $record = 0x0081; // Record identifier
+ $length = 0x0002; // Bytes to follow
+ $grbit = 0x0000;
+
+ // The only option that is of interest is the flag for fit to page. So we
+ // set all the options in one go.
+ //
+ // Set the option flags
+ $grbit |= 0x0001; // Auto page breaks visible
+ if ($this->outlineStyle) {
+ $grbit |= 0x0020; // Auto outline styles
+ }
+ if ($this->phpSheet->getShowSummaryBelow()) {
+ $grbit |= 0x0040; // Outline summary below
+ }
+ if ($this->phpSheet->getShowSummaryRight()) {
+ $grbit |= 0x0080; // Outline summary right
+ }
+ if ($this->phpSheet->getPageSetup()->getFitToPage()) {
+ $grbit |= 0x0100; // Page setup fit to page
+ }
+ if ($this->outlineOn) {
+ $grbit |= 0x0400; // Outline symbols displayed
+ }
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $grbit);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the HORIZONTALPAGEBREAKS and VERTICALPAGEBREAKS BIFF records.
+ */
+ private function writeBreaks(): void
+ {
+ // initialize
+ $vbreaks = [];
+ $hbreaks = [];
+
+ foreach ($this->phpSheet->getBreaks() as $cell => $breakType) {
+ // Fetch coordinates
+ $coordinates = Coordinate::coordinateFromString($cell);
+
+ // Decide what to do by the type of break
+ switch ($breakType) {
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_COLUMN:
+ // Add to list of vertical breaks
+ $vbreaks[] = Coordinate::columnIndexFromString($coordinates[0]) - 1;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_ROW:
+ // Add to list of horizontal breaks
+ $hbreaks[] = $coordinates[1];
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_NONE:
+ default:
+ // Nothing to do
+ break;
+ }
+ }
+
+ //horizontal page breaks
+ if (!empty($hbreaks)) {
+ // Sort and filter array of page breaks
+ sort($hbreaks, SORT_NUMERIC);
+ if ($hbreaks[0] == 0) { // don't use first break if it's 0
+ array_shift($hbreaks);
+ }
+
+ $record = 0x001b; // Record identifier
+ $cbrk = count($hbreaks); // Number of page breaks
+ $length = 2 + 6 * $cbrk; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $cbrk);
+
+ // Append each page break
+ foreach ($hbreaks as $hbreak) {
+ $data .= pack('vvv', $hbreak, 0x0000, 0x00ff);
+ }
+
+ $this->append($header . $data);
+ }
+
+ // vertical page breaks
+ if (!empty($vbreaks)) {
+ // 1000 vertical pagebreaks appears to be an internal Excel 5 limit.
+ // It is slightly higher in Excel 97/200, approx. 1026
+ $vbreaks = array_slice($vbreaks, 0, 1000);
+
+ // Sort and filter array of page breaks
+ sort($vbreaks, SORT_NUMERIC);
+ if ($vbreaks[0] == 0) { // don't use first break if it's 0
+ array_shift($vbreaks);
+ }
+
+ $record = 0x001a; // Record identifier
+ $cbrk = count($vbreaks); // Number of page breaks
+ $length = 2 + 6 * $cbrk; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $cbrk);
+
+ // Append each page break
+ foreach ($vbreaks as $vbreak) {
+ $data .= pack('vvv', $vbreak, 0x0000, 0xffff);
+ }
+
+ $this->append($header . $data);
+ }
+ }
+
+ /**
+ * Set the Biff PROTECT record to indicate that the worksheet is protected.
+ */
+ private function writeProtect(): void
+ {
+ // Exit unless sheet protection has been specified
+ if (!$this->phpSheet->getProtection()->getSheet()) {
+ return;
+ }
+
+ $record = 0x0012; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fLock = 1; // Worksheet is protected
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fLock);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write SCENPROTECT.
+ */
+ private function writeScenProtect(): void
+ {
+ // Exit if sheet protection is not active
+ if (!$this->phpSheet->getProtection()->getSheet()) {
+ return;
+ }
+
+ // Exit if scenarios are not protected
+ if (!$this->phpSheet->getProtection()->getScenarios()) {
+ return;
+ }
+
+ $record = 0x00DD; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', 1);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write OBJECTPROTECT.
+ */
+ private function writeObjectProtect(): void
+ {
+ // Exit if sheet protection is not active
+ if (!$this->phpSheet->getProtection()->getSheet()) {
+ return;
+ }
+
+ // Exit if objects are not protected
+ if (!$this->phpSheet->getProtection()->getObjects()) {
+ return;
+ }
+
+ $record = 0x0063; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', 1);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the worksheet PASSWORD record.
+ */
+ private function writePassword(): void
+ {
+ // Exit unless sheet protection and password have been specified
+ if (!$this->phpSheet->getProtection()->getSheet() || !$this->phpSheet->getProtection()->getPassword()) {
+ return;
+ }
+
+ $record = 0x0013; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $wPassword = hexdec($this->phpSheet->getProtection()->getPassword()); // Encoded password
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $wPassword);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Insert a 24bit bitmap image in a worksheet.
+ *
+ * @param int $row The row we are going to insert the bitmap into
+ * @param int $col The column we are going to insert the bitmap into
+ * @param mixed $bitmap The bitmap filename or GD-image resource
+ * @param int $x the horizontal position (offset) of the image inside the cell
+ * @param int $y the vertical position (offset) of the image inside the cell
+ * @param float $scale_x The horizontal scale
+ * @param float $scale_y The vertical scale
+ */
+ public function insertBitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1): void
+ {
+ $bitmap_array = (is_resource($bitmap) ? $this->processBitmapGd($bitmap) : $this->processBitmap($bitmap));
+ [$width, $height, $size, $data] = $bitmap_array;
+
+ // Scale the frame of the image.
+ $width *= $scale_x;
+ $height *= $scale_y;
+
+ // Calculate the vertices of the image and write the OBJ record
+ $this->positionImage($col, $row, $x, $y, $width, $height);
+
+ // Write the IMDATA record to store the bitmap data
+ $record = 0x007f;
+ $length = 8 + $size;
+ $cf = 0x09;
+ $env = 0x01;
+ $lcb = $size;
+
+ $header = pack('vvvvV', $record, $length, $cf, $env, $lcb);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Calculate the vertices that define the position of the image as required by
+ * the OBJ record.
+ *
+ * +------------+------------+
+ * | A | B |
+ * +-----+------------+------------+
+ * | |(x1,y1) | |
+ * | 1 |(A1)._______|______ |
+ * | | | | |
+ * | | | | |
+ * +-----+----| BITMAP |-----+
+ * | | | | |
+ * | 2 | |______________. |
+ * | | | (B2)|
+ * | | | (x2,y2)|
+ * +---- +------------+------------+
+ *
+ * Example of a bitmap that covers some of the area from cell A1 to cell B2.
+ *
+ * Based on the width and height of the bitmap we need to calculate 8 vars:
+ * $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
+ * The width and height of the cells are also variable and have to be taken into
+ * account.
+ * The values of $col_start and $row_start are passed in from the calling
+ * function. The values of $col_end and $row_end are calculated by subtracting
+ * the width and height of the bitmap from the width and height of the
+ * underlying cells.
+ * The vertices are expressed as a percentage of the underlying cell width as
+ * follows (rhs values are in pixels):
+ *
+ * x1 = X / W *1024
+ * y1 = Y / H *256
+ * x2 = (X-1) / W *1024
+ * y2 = (Y-1) / H *256
+ *
+ * Where: X is distance from the left side of the underlying cell
+ * Y is distance from the top of the underlying cell
+ * W is the width of the cell
+ * H is the height of the cell
+ * The SDK incorrectly states that the height should be expressed as a
+ * percentage of 1024.
+ *
+ * @param int $col_start Col containing upper left corner of object
+ * @param int $row_start Row containing top left corner of object
+ * @param int $x1 Distance to left side of object
+ * @param int $y1 Distance to top of object
+ * @param int $width Width of image frame
+ * @param int $height Height of image frame
+ */
+ public function positionImage($col_start, $row_start, $x1, $y1, $width, $height): void
+ {
+ // Initialise end cell to the same as the start cell
+ $col_end = $col_start; // Col containing lower right corner of object
+ $row_end = $row_start; // Row containing bottom right corner of object
+
+ // Zero the specified offset if greater than the cell dimensions
+ if ($x1 >= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1))) {
+ $x1 = 0;
+ }
+ if ($y1 >= Xls::sizeRow($this->phpSheet, $row_start + 1)) {
+ $y1 = 0;
+ }
+
+ $width = $width + $x1 - 1;
+ $height = $height + $y1 - 1;
+
+ // Subtract the underlying cell widths to find the end cell of the image
+ while ($width >= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1))) {
+ $width -= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1));
+ ++$col_end;
+ }
+
+ // Subtract the underlying cell heights to find the end cell of the image
+ while ($height >= Xls::sizeRow($this->phpSheet, $row_end + 1)) {
+ $height -= Xls::sizeRow($this->phpSheet, $row_end + 1);
+ ++$row_end;
+ }
+
+ // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
+ // with zero eight or width.
+ //
+ if (Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1)) == 0) {
+ return;
+ }
+ if (Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1)) == 0) {
+ return;
+ }
+ if (Xls::sizeRow($this->phpSheet, $row_start + 1) == 0) {
+ return;
+ }
+ if (Xls::sizeRow($this->phpSheet, $row_end + 1) == 0) {
+ return;
+ }
+
+ // Convert the pixel values to the percentage value expected by Excel
+ $x1 = $x1 / Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1)) * 1024;
+ $y1 = $y1 / Xls::sizeRow($this->phpSheet, $row_start + 1) * 256;
+ $x2 = $width / Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1)) * 1024; // Distance to right side of object
+ $y2 = $height / Xls::sizeRow($this->phpSheet, $row_end + 1) * 256; // Distance to bottom of object
+
+ $this->writeObjPicture($col_start, $x1, $row_start, $y1, $col_end, $x2, $row_end, $y2);
+ }
+
+ /**
+ * Store the OBJ record that precedes an IMDATA record. This could be generalise
+ * to support other Excel objects.
+ *
+ * @param int $colL Column containing upper left corner of object
+ * @param int $dxL Distance from left side of cell
+ * @param int $rwT Row containing top left corner of object
+ * @param int $dyT Distance from top of cell
+ * @param int $colR Column containing lower right corner of object
+ * @param int $dxR Distance from right of cell
+ * @param int $rwB Row containing bottom right corner of object
+ * @param int $dyB Distance from bottom of cell
+ */
+ private function writeObjPicture($colL, $dxL, $rwT, $dyT, $colR, $dxR, $rwB, $dyB): void
+ {
+ $record = 0x005d; // Record identifier
+ $length = 0x003c; // Bytes to follow
+
+ $cObj = 0x0001; // Count of objects in file (set to 1)
+ $OT = 0x0008; // Object type. 8 = Picture
+ $id = 0x0001; // Object ID
+ $grbit = 0x0614; // Option flags
+
+ $cbMacro = 0x0000; // Length of FMLA structure
+ $Reserved1 = 0x0000; // Reserved
+ $Reserved2 = 0x0000; // Reserved
+
+ $icvBack = 0x09; // Background colour
+ $icvFore = 0x09; // Foreground colour
+ $fls = 0x00; // Fill pattern
+ $fAuto = 0x00; // Automatic fill
+ $icv = 0x08; // Line colour
+ $lns = 0xff; // Line style
+ $lnw = 0x01; // Line weight
+ $fAutoB = 0x00; // Automatic border
+ $frs = 0x0000; // Frame style
+ $cf = 0x0009; // Image format, 9 = bitmap
+ $Reserved3 = 0x0000; // Reserved
+ $cbPictFmla = 0x0000; // Length of FMLA structure
+ $Reserved4 = 0x0000; // Reserved
+ $grbit2 = 0x0001; // Option flags
+ $Reserved5 = 0x0000; // Reserved
+
+ $header = pack('vv', $record, $length);
+ $data = pack('V', $cObj);
+ $data .= pack('v', $OT);
+ $data .= pack('v', $id);
+ $data .= pack('v', $grbit);
+ $data .= pack('v', $colL);
+ $data .= pack('v', $dxL);
+ $data .= pack('v', $rwT);
+ $data .= pack('v', $dyT);
+ $data .= pack('v', $colR);
+ $data .= pack('v', $dxR);
+ $data .= pack('v', $rwB);
+ $data .= pack('v', $dyB);
+ $data .= pack('v', $cbMacro);
+ $data .= pack('V', $Reserved1);
+ $data .= pack('v', $Reserved2);
+ $data .= pack('C', $icvBack);
+ $data .= pack('C', $icvFore);
+ $data .= pack('C', $fls);
+ $data .= pack('C', $fAuto);
+ $data .= pack('C', $icv);
+ $data .= pack('C', $lns);
+ $data .= pack('C', $lnw);
+ $data .= pack('C', $fAutoB);
+ $data .= pack('v', $frs);
+ $data .= pack('V', $cf);
+ $data .= pack('v', $Reserved3);
+ $data .= pack('v', $cbPictFmla);
+ $data .= pack('v', $Reserved4);
+ $data .= pack('v', $grbit2);
+ $data .= pack('V', $Reserved5);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Convert a GD-image into the internal format.
+ *
+ * @param resource $image The image to process
+ *
+ * @return array Array with data and properties of the bitmap
+ */
+ public function processBitmapGd($image)
+ {
+ $width = imagesx($image);
+ $height = imagesy($image);
+
+ $data = pack('Vvvvv', 0x000c, $width, $height, 0x01, 0x18);
+ for ($j = $height; --$j;) {
+ for ($i = 0; $i < $width; ++$i) {
+ $color = imagecolorsforindex($image, imagecolorat($image, $i, $j));
+ foreach (['red', 'green', 'blue'] as $key) {
+ $color[$key] = $color[$key] + round((255 - $color[$key]) * $color['alpha'] / 127);
+ }
+ $data .= chr($color['blue']) . chr($color['green']) . chr($color['red']);
+ }
+ if (3 * $width % 4) {
+ $data .= str_repeat("\x00", 4 - 3 * $width % 4);
+ }
+ }
+
+ return [$width, $height, strlen($data), $data];
+ }
+
+ /**
+ * Convert a 24 bit bitmap into the modified internal format used by Windows.
+ * This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
+ * MSDN library.
+ *
+ * @param string $bitmap The bitmap to process
+ *
+ * @return array Array with data and properties of the bitmap
+ */
+ public function processBitmap($bitmap)
+ {
+ // Open file.
+ $bmp_fd = @fopen($bitmap, 'rb');
+ if (!$bmp_fd) {
+ throw new WriterException("Couldn't import $bitmap");
+ }
+
+ // Slurp the file into a string.
+ $data = fread($bmp_fd, filesize($bitmap));
+
+ // Check that the file is big enough to be a bitmap.
+ if (strlen($data) <= 0x36) {
+ throw new WriterException("$bitmap doesn't contain enough data.\n");
+ }
+
+ // The first 2 bytes are used to identify the bitmap.
+ $identity = unpack('A2ident', $data);
+ if ($identity['ident'] != 'BM') {
+ throw new WriterException("$bitmap doesn't appear to be a valid bitmap image.\n");
+ }
+
+ // Remove bitmap data: ID.
+ $data = substr($data, 2);
+
+ // Read and remove the bitmap size. This is more reliable than reading
+ // the data size at offset 0x22.
+ //
+ $size_array = unpack('Vsa', substr($data, 0, 4));
+ $size = $size_array['sa'];
+ $data = substr($data, 4);
+ $size -= 0x36; // Subtract size of bitmap header.
+ $size += 0x0C; // Add size of BIFF header.
+
+ // Remove bitmap data: reserved, offset, header length.
+ $data = substr($data, 12);
+
+ // Read and remove the bitmap width and height. Verify the sizes.
+ $width_and_height = unpack('V2', substr($data, 0, 8));
+ $width = $width_and_height[1];
+ $height = $width_and_height[2];
+ $data = substr($data, 8);
+ if ($width > 0xFFFF) {
+ throw new WriterException("$bitmap: largest image width supported is 65k.\n");
+ }
+ if ($height > 0xFFFF) {
+ throw new WriterException("$bitmap: largest image height supported is 65k.\n");
+ }
+
+ // Read and remove the bitmap planes and bpp data. Verify them.
+ $planes_and_bitcount = unpack('v2', substr($data, 0, 4));
+ $data = substr($data, 4);
+ if ($planes_and_bitcount[2] != 24) { // Bitcount
+ throw new WriterException("$bitmap isn't a 24bit true color bitmap.\n");
+ }
+ if ($planes_and_bitcount[1] != 1) {
+ throw new WriterException("$bitmap: only 1 plane supported in bitmap image.\n");
+ }
+
+ // Read and remove the bitmap compression. Verify compression.
+ $compression = unpack('Vcomp', substr($data, 0, 4));
+ $data = substr($data, 4);
+
+ if ($compression['comp'] != 0) {
+ throw new WriterException("$bitmap: compression not supported in bitmap image.\n");
+ }
+
+ // Remove bitmap data: data size, hres, vres, colours, imp. colours.
+ $data = substr($data, 20);
+
+ // Add the BITMAPCOREHEADER data
+ $header = pack('Vvvvv', 0x000c, $width, $height, 0x01, 0x18);
+ $data = $header . $data;
+
+ return [$width, $height, $size, $data];
+ }
+
+ /**
+ * Store the window zoom factor. This should be a reduced fraction but for
+ * simplicity we will store all fractions with a numerator of 100.
+ */
+ private function writeZoom(): void
+ {
+ // If scale is 100 we don't need to write a record
+ if ($this->phpSheet->getSheetView()->getZoomScale() == 100) {
+ return;
+ }
+
+ $record = 0x00A0; // Record identifier
+ $length = 0x0004; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vv', $this->phpSheet->getSheetView()->getZoomScale(), 100);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Get Escher object.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Shared\Escher
+ */
+ public function getEscher()
+ {
+ return $this->escher;
+ }
+
+ /**
+ * Set Escher object.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Shared\Escher $pValue
+ */
+ public function setEscher(?\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null): void
+ {
+ $this->escher = $pValue;
+ }
+
+ /**
+ * Write MSODRAWING record.
+ */
+ private function writeMsoDrawing(): void
+ {
+ // write the Escher stream if necessary
+ if (isset($this->escher)) {
+ $writer = new Escher($this->escher);
+ $data = $writer->close();
+ $spOffsets = $writer->getSpOffsets();
+ $spTypes = $writer->getSpTypes();
+ // write the neccesary MSODRAWING, OBJ records
+
+ // split the Escher stream
+ $spOffsets[0] = 0;
+ $nm = count($spOffsets) - 1; // number of shapes excluding first shape
+ for ($i = 1; $i <= $nm; ++$i) {
+ // MSODRAWING record
+ $record = 0x00EC; // Record identifier
+
+ // chunk of Escher stream for one shape
+ $dataChunk = substr($data, $spOffsets[$i - 1], $spOffsets[$i] - $spOffsets[$i - 1]);
+
+ $length = strlen($dataChunk);
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $dataChunk);
+
+ // OBJ record
+ $record = 0x005D; // record identifier
+ $objData = '';
+
+ // ftCmo
+ if ($spTypes[$i] == 0x00C9) {
+ // Add ftCmo (common object data) subobject
+ $objData .=
+ pack(
+ 'vvvvvVVV',
+ 0x0015, // 0x0015 = ftCmo
+ 0x0012, // length of ftCmo data
+ 0x0014, // object type, 0x0014 = filter
+ $i, // object id number, Excel seems to use 1-based index, local for the sheet
+ 0x2101, // option flags, 0x2001 is what OpenOffice.org uses
+ 0, // reserved
+ 0, // reserved
+ 0 // reserved
+ );
+
+ // Add ftSbs Scroll bar subobject
+ $objData .= pack('vv', 0x00C, 0x0014);
+ $objData .= pack('H*', '0000000000000000640001000A00000010000100');
+ // Add ftLbsData (List box data) subobject
+ $objData .= pack('vv', 0x0013, 0x1FEE);
+ $objData .= pack('H*', '00000000010001030000020008005700');
+ } else {
+ // Add ftCmo (common object data) subobject
+ $objData .=
+ pack(
+ 'vvvvvVVV',
+ 0x0015, // 0x0015 = ftCmo
+ 0x0012, // length of ftCmo data
+ 0x0008, // object type, 0x0008 = picture
+ $i, // object id number, Excel seems to use 1-based index, local for the sheet
+ 0x6011, // option flags, 0x6011 is what OpenOffice.org uses
+ 0, // reserved
+ 0, // reserved
+ 0 // reserved
+ );
+ }
+
+ // ftEnd
+ $objData .=
+ pack(
+ 'vv',
+ 0x0000, // 0x0000 = ftEnd
+ 0x0000 // length of ftEnd data
+ );
+
+ $length = strlen($objData);
+ $header = pack('vv', $record, $length);
+ $this->append($header . $objData);
+ }
+ }
+ }
+
+ /**
+ * Store the DATAVALIDATIONS and DATAVALIDATION records.
+ */
+ private function writeDataValidity(): void
+ {
+ // Datavalidation collection
+ $dataValidationCollection = $this->phpSheet->getDataValidationCollection();
+
+ // Write data validations?
+ if (!empty($dataValidationCollection)) {
+ // DATAVALIDATIONS record
+ $record = 0x01B2; // Record identifier
+ $length = 0x0012; // Bytes to follow
+
+ $grbit = 0x0000; // Prompt box at cell, no cached validity data at DV records
+ $horPos = 0x00000000; // Horizontal position of prompt box, if fixed position
+ $verPos = 0x00000000; // Vertical position of prompt box, if fixed position
+ $objId = 0xFFFFFFFF; // Object identifier of drop down arrow object, or -1 if not visible
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vVVVV', $grbit, $horPos, $verPos, $objId, count($dataValidationCollection));
+ $this->append($header . $data);
+
+ // DATAVALIDATION records
+ $record = 0x01BE; // Record identifier
+
+ foreach ($dataValidationCollection as $cellCoordinate => $dataValidation) {
+ // initialize record data
+ $data = '';
+
+ // options
+ $options = 0x00000000;
+
+ // data type
+ $type = 0x00;
+ switch ($dataValidation->getType()) {
+ case DataValidation::TYPE_NONE:
+ $type = 0x00;
+
+ break;
+ case DataValidation::TYPE_WHOLE:
+ $type = 0x01;
+
+ break;
+ case DataValidation::TYPE_DECIMAL:
+ $type = 0x02;
+
+ break;
+ case DataValidation::TYPE_LIST:
+ $type = 0x03;
+
+ break;
+ case DataValidation::TYPE_DATE:
+ $type = 0x04;
+
+ break;
+ case DataValidation::TYPE_TIME:
+ $type = 0x05;
+
+ break;
+ case DataValidation::TYPE_TEXTLENGTH:
+ $type = 0x06;
+
+ break;
+ case DataValidation::TYPE_CUSTOM:
+ $type = 0x07;
+
+ break;
+ }
+
+ $options |= $type << 0;
+
+ // error style
+ $errorStyle = 0x00;
+ switch ($dataValidation->getErrorStyle()) {
+ case DataValidation::STYLE_STOP:
+ $errorStyle = 0x00;
+
+ break;
+ case DataValidation::STYLE_WARNING:
+ $errorStyle = 0x01;
+
+ break;
+ case DataValidation::STYLE_INFORMATION:
+ $errorStyle = 0x02;
+
+ break;
+ }
+
+ $options |= $errorStyle << 4;
+
+ // explicit formula?
+ if ($type == 0x03 && preg_match('/^\".*\"$/', $dataValidation->getFormula1())) {
+ $options |= 0x01 << 7;
+ }
+
+ // empty cells allowed
+ $options |= $dataValidation->getAllowBlank() << 8;
+
+ // show drop down
+ $options |= (!$dataValidation->getShowDropDown()) << 9;
+
+ // show input message
+ $options |= $dataValidation->getShowInputMessage() << 18;
+
+ // show error message
+ $options |= $dataValidation->getShowErrorMessage() << 19;
+
+ // condition operator
+ $operator = 0x00;
+ switch ($dataValidation->getOperator()) {
+ case DataValidation::OPERATOR_BETWEEN:
+ $operator = 0x00;
+
+ break;
+ case DataValidation::OPERATOR_NOTBETWEEN:
+ $operator = 0x01;
+
+ break;
+ case DataValidation::OPERATOR_EQUAL:
+ $operator = 0x02;
+
+ break;
+ case DataValidation::OPERATOR_NOTEQUAL:
+ $operator = 0x03;
+
+ break;
+ case DataValidation::OPERATOR_GREATERTHAN:
+ $operator = 0x04;
+
+ break;
+ case DataValidation::OPERATOR_LESSTHAN:
+ $operator = 0x05;
+
+ break;
+ case DataValidation::OPERATOR_GREATERTHANOREQUAL:
+ $operator = 0x06;
+
+ break;
+ case DataValidation::OPERATOR_LESSTHANOREQUAL:
+ $operator = 0x07;
+
+ break;
+ }
+
+ $options |= $operator << 20;
+
+ $data = pack('V', $options);
+
+ // prompt title
+ $promptTitle = $dataValidation->getPromptTitle() !== '' ?
+ $dataValidation->getPromptTitle() : chr(0);
+ $data .= StringHelper::UTF8toBIFF8UnicodeLong($promptTitle);
+
+ // error title
+ $errorTitle = $dataValidation->getErrorTitle() !== '' ?
+ $dataValidation->getErrorTitle() : chr(0);
+ $data .= StringHelper::UTF8toBIFF8UnicodeLong($errorTitle);
+
+ // prompt text
+ $prompt = $dataValidation->getPrompt() !== '' ?
+ $dataValidation->getPrompt() : chr(0);
+ $data .= StringHelper::UTF8toBIFF8UnicodeLong($prompt);
+
+ // error text
+ $error = $dataValidation->getError() !== '' ?
+ $dataValidation->getError() : chr(0);
+ $data .= StringHelper::UTF8toBIFF8UnicodeLong($error);
+
+ // formula 1
+ try {
+ $formula1 = $dataValidation->getFormula1();
+ if ($type == 0x03) { // list type
+ $formula1 = str_replace(',', chr(0), $formula1);
+ }
+ $this->parser->parse($formula1);
+ $formula1 = $this->parser->toReversePolish();
+ $sz1 = strlen($formula1);
+ } catch (PhpSpreadsheetException $e) {
+ $sz1 = 0;
+ $formula1 = '';
+ }
+ $data .= pack('vv', $sz1, 0x0000);
+ $data .= $formula1;
+
+ // formula 2
+ try {
+ $formula2 = $dataValidation->getFormula2();
+ if ($formula2 === '') {
+ throw new WriterException('No formula2');
+ }
+ $this->parser->parse($formula2);
+ $formula2 = $this->parser->toReversePolish();
+ $sz2 = strlen($formula2);
+ } catch (PhpSpreadsheetException $e) {
+ $sz2 = 0;
+ $formula2 = '';
+ }
+ $data .= pack('vv', $sz2, 0x0000);
+ $data .= $formula2;
+
+ // cell range address list
+ $data .= pack('v', 0x0001);
+ $data .= $this->writeBIFF8CellRangeAddressFixed($cellCoordinate);
+
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $data);
+ }
+ }
+ }
+
+ /**
+ * Map Error code.
+ *
+ * @param string $errorCode
+ *
+ * @return int
+ */
+ private static function mapErrorCode($errorCode)
+ {
+ switch ($errorCode) {
+ case '#NULL!':
+ return 0x00;
+ case '#DIV/0!':
+ return 0x07;
+ case '#VALUE!':
+ return 0x0F;
+ case '#REF!':
+ return 0x17;
+ case '#NAME?':
+ return 0x1D;
+ case '#NUM!':
+ return 0x24;
+ case '#N/A':
+ return 0x2A;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Write PLV Record.
+ */
+ private function writePageLayoutView(): void
+ {
+ $record = 0x088B; // Record identifier
+ $length = 0x0010; // Bytes to follow
+
+ $rt = 0x088B; // 2
+ $grbitFrt = 0x0000; // 2
+ $reserved = 0x0000000000000000; // 8
+ $wScalvePLV = $this->phpSheet->getSheetView()->getZoomScale(); // 2
+
+ // The options flags that comprise $grbit
+ if ($this->phpSheet->getSheetView()->getView() == SheetView::SHEETVIEW_PAGE_LAYOUT) {
+ $fPageLayoutView = 1;
+ } else {
+ $fPageLayoutView = 0;
+ }
+ $fRulerVisible = 0;
+ $fWhitespaceHidden = 0;
+
+ $grbit = $fPageLayoutView; // 2
+ $grbit |= $fRulerVisible << 1;
+ $grbit |= $fWhitespaceHidden << 3;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvVVvv', $rt, $grbitFrt, 0x00000000, 0x00000000, $wScalvePLV, $grbit);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write CFRule Record.
+ */
+ private function writeCFRule(Conditional $conditional): void
+ {
+ $record = 0x01B1; // Record identifier
+
+ // $type : Type of the CF
+ // $operatorType : Comparison operator
+ if ($conditional->getConditionType() == Conditional::CONDITION_EXPRESSION) {
+ $type = 0x02;
+ $operatorType = 0x00;
+ } elseif ($conditional->getConditionType() == Conditional::CONDITION_CELLIS) {
+ $type = 0x01;
+
+ switch ($conditional->getOperatorType()) {
+ case Conditional::OPERATOR_NONE:
+ $operatorType = 0x00;
+
+ break;
+ case Conditional::OPERATOR_EQUAL:
+ $operatorType = 0x03;
+
+ break;
+ case Conditional::OPERATOR_GREATERTHAN:
+ $operatorType = 0x05;
+
+ break;
+ case Conditional::OPERATOR_GREATERTHANOREQUAL:
+ $operatorType = 0x07;
+
+ break;
+ case Conditional::OPERATOR_LESSTHAN:
+ $operatorType = 0x06;
+
+ break;
+ case Conditional::OPERATOR_LESSTHANOREQUAL:
+ $operatorType = 0x08;
+
+ break;
+ case Conditional::OPERATOR_NOTEQUAL:
+ $operatorType = 0x04;
+
+ break;
+ case Conditional::OPERATOR_BETWEEN:
+ $operatorType = 0x01;
+
+ break;
+ // not OPERATOR_NOTBETWEEN 0x02
+ }
+ }
+
+ // $szValue1 : size of the formula data for first value or formula
+ // $szValue2 : size of the formula data for second value or formula
+ $arrConditions = $conditional->getConditions();
+ $numConditions = count($arrConditions);
+ if ($numConditions == 1) {
+ $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000);
+ $szValue2 = 0x0000;
+ $operand1 = pack('Cv', 0x1E, $arrConditions[0]);
+ $operand2 = null;
+ } elseif ($numConditions == 2 && ($conditional->getOperatorType() == Conditional::OPERATOR_BETWEEN)) {
+ $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000);
+ $szValue2 = ($arrConditions[1] <= 65535 ? 3 : 0x0000);
+ $operand1 = pack('Cv', 0x1E, $arrConditions[0]);
+ $operand2 = pack('Cv', 0x1E, $arrConditions[1]);
+ } else {
+ $szValue1 = 0x0000;
+ $szValue2 = 0x0000;
+ $operand1 = null;
+ $operand2 = null;
+ }
+
+ // $flags : Option flags
+ // Alignment
+ $bAlignHz = ($conditional->getStyle()->getAlignment()->getHorizontal() == null ? 1 : 0);
+ $bAlignVt = ($conditional->getStyle()->getAlignment()->getVertical() == null ? 1 : 0);
+ $bAlignWrapTx = ($conditional->getStyle()->getAlignment()->getWrapText() == false ? 1 : 0);
+ $bTxRotation = ($conditional->getStyle()->getAlignment()->getTextRotation() == null ? 1 : 0);
+ $bIndent = ($conditional->getStyle()->getAlignment()->getIndent() == 0 ? 1 : 0);
+ $bShrinkToFit = ($conditional->getStyle()->getAlignment()->getShrinkToFit() == false ? 1 : 0);
+ if ($bAlignHz == 0 || $bAlignVt == 0 || $bAlignWrapTx == 0 || $bTxRotation == 0 || $bIndent == 0 || $bShrinkToFit == 0) {
+ $bFormatAlign = 1;
+ } else {
+ $bFormatAlign = 0;
+ }
+ // Protection
+ $bProtLocked = ($conditional->getStyle()->getProtection()->getLocked() == null ? 1 : 0);
+ $bProtHidden = ($conditional->getStyle()->getProtection()->getHidden() == null ? 1 : 0);
+ if ($bProtLocked == 0 || $bProtHidden == 0) {
+ $bFormatProt = 1;
+ } else {
+ $bFormatProt = 0;
+ }
+ // Border
+ $bBorderLeft = ($conditional->getStyle()->getBorders()->getLeft()->getColor()->getARGB() == Color::COLOR_BLACK
+ && $conditional->getStyle()->getBorders()->getLeft()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
+ $bBorderRight = ($conditional->getStyle()->getBorders()->getRight()->getColor()->getARGB() == Color::COLOR_BLACK
+ && $conditional->getStyle()->getBorders()->getRight()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
+ $bBorderTop = ($conditional->getStyle()->getBorders()->getTop()->getColor()->getARGB() == Color::COLOR_BLACK
+ && $conditional->getStyle()->getBorders()->getTop()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
+ $bBorderBottom = ($conditional->getStyle()->getBorders()->getBottom()->getColor()->getARGB() == Color::COLOR_BLACK
+ && $conditional->getStyle()->getBorders()->getBottom()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
+ if ($bBorderLeft == 0 || $bBorderRight == 0 || $bBorderTop == 0 || $bBorderBottom == 0) {
+ $bFormatBorder = 1;
+ } else {
+ $bFormatBorder = 0;
+ }
+ // Pattern
+ $bFillStyle = ($conditional->getStyle()->getFill()->getFillType() == null ? 0 : 1);
+ $bFillColor = ($conditional->getStyle()->getFill()->getStartColor()->getARGB() == null ? 0 : 1);
+ $bFillColorBg = ($conditional->getStyle()->getFill()->getEndColor()->getARGB() == null ? 0 : 1);
+ if ($bFillStyle == 0 || $bFillColor == 0 || $bFillColorBg == 0) {
+ $bFormatFill = 1;
+ } else {
+ $bFormatFill = 0;
+ }
+ // Font
+ if ($conditional->getStyle()->getFont()->getName() != null
+ || $conditional->getStyle()->getFont()->getSize() != null
+ || $conditional->getStyle()->getFont()->getBold() != null
+ || $conditional->getStyle()->getFont()->getItalic() != null
+ || $conditional->getStyle()->getFont()->getSuperscript() != null
+ || $conditional->getStyle()->getFont()->getSubscript() != null
+ || $conditional->getStyle()->getFont()->getUnderline() != null
+ || $conditional->getStyle()->getFont()->getStrikethrough() != null
+ || $conditional->getStyle()->getFont()->getColor()->getARGB() != null) {
+ $bFormatFont = 1;
+ } else {
+ $bFormatFont = 0;
+ }
+ // Alignment
+ $flags = 0;
+ $flags |= (1 == $bAlignHz ? 0x00000001 : 0);
+ $flags |= (1 == $bAlignVt ? 0x00000002 : 0);
+ $flags |= (1 == $bAlignWrapTx ? 0x00000004 : 0);
+ $flags |= (1 == $bTxRotation ? 0x00000008 : 0);
+ // Justify last line flag
+ $flags |= (1 == 1 ? 0x00000010 : 0);
+ $flags |= (1 == $bIndent ? 0x00000020 : 0);
+ $flags |= (1 == $bShrinkToFit ? 0x00000040 : 0);
+ // Default
+ $flags |= (1 == 1 ? 0x00000080 : 0);
+ // Protection
+ $flags |= (1 == $bProtLocked ? 0x00000100 : 0);
+ $flags |= (1 == $bProtHidden ? 0x00000200 : 0);
+ // Border
+ $flags |= (1 == $bBorderLeft ? 0x00000400 : 0);
+ $flags |= (1 == $bBorderRight ? 0x00000800 : 0);
+ $flags |= (1 == $bBorderTop ? 0x00001000 : 0);
+ $flags |= (1 == $bBorderBottom ? 0x00002000 : 0);
+ $flags |= (1 == 1 ? 0x00004000 : 0); // Top left to Bottom right border
+ $flags |= (1 == 1 ? 0x00008000 : 0); // Bottom left to Top right border
+ // Pattern
+ $flags |= (1 == $bFillStyle ? 0x00010000 : 0);
+ $flags |= (1 == $bFillColor ? 0x00020000 : 0);
+ $flags |= (1 == $bFillColorBg ? 0x00040000 : 0);
+ $flags |= (1 == 1 ? 0x00380000 : 0);
+ // Font
+ $flags |= (1 == $bFormatFont ? 0x04000000 : 0);
+ // Alignment:
+ $flags |= (1 == $bFormatAlign ? 0x08000000 : 0);
+ // Border
+ $flags |= (1 == $bFormatBorder ? 0x10000000 : 0);
+ // Pattern
+ $flags |= (1 == $bFormatFill ? 0x20000000 : 0);
+ // Protection
+ $flags |= (1 == $bFormatProt ? 0x40000000 : 0);
+ // Text direction
+ $flags |= (1 == 0 ? 0x80000000 : 0);
+
+ // Data Blocks
+ if ($bFormatFont == 1) {
+ // Font Name
+ if ($conditional->getStyle()->getFont()->getName() == null) {
+ $dataBlockFont = pack('VVVVVVVV', 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000);
+ $dataBlockFont .= pack('VVVVVVVV', 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000);
+ } else {
+ $dataBlockFont = StringHelper::UTF8toBIFF8UnicodeLong($conditional->getStyle()->getFont()->getName());
+ }
+ // Font Size
+ if ($conditional->getStyle()->getFont()->getSize() == null) {
+ $dataBlockFont .= pack('V', 20 * 11);
+ } else {
+ $dataBlockFont .= pack('V', 20 * $conditional->getStyle()->getFont()->getSize());
+ }
+ // Font Options
+ $dataBlockFont .= pack('V', 0);
+ // Font weight
+ if ($conditional->getStyle()->getFont()->getBold() == true) {
+ $dataBlockFont .= pack('v', 0x02BC);
+ } else {
+ $dataBlockFont .= pack('v', 0x0190);
+ }
+ // Escapement type
+ if ($conditional->getStyle()->getFont()->getSubscript() == true) {
+ $dataBlockFont .= pack('v', 0x02);
+ $fontEscapement = 0;
+ } elseif ($conditional->getStyle()->getFont()->getSuperscript() == true) {
+ $dataBlockFont .= pack('v', 0x01);
+ $fontEscapement = 0;
+ } else {
+ $dataBlockFont .= pack('v', 0x00);
+ $fontEscapement = 1;
+ }
+ // Underline type
+ switch ($conditional->getStyle()->getFont()->getUnderline()) {
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE:
+ $dataBlockFont .= pack('C', 0x00);
+ $fontUnderline = 0;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE:
+ $dataBlockFont .= pack('C', 0x02);
+ $fontUnderline = 0;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLEACCOUNTING:
+ $dataBlockFont .= pack('C', 0x22);
+ $fontUnderline = 0;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE:
+ $dataBlockFont .= pack('C', 0x01);
+ $fontUnderline = 0;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLEACCOUNTING:
+ $dataBlockFont .= pack('C', 0x21);
+ $fontUnderline = 0;
+
+ break;
+ default:
+ $dataBlockFont .= pack('C', 0x00);
+ $fontUnderline = 1;
+
+ break;
+ }
+ // Not used (3)
+ $dataBlockFont .= pack('vC', 0x0000, 0x00);
+ // Font color index
+ switch ($conditional->getStyle()->getFont()->getColor()->getRGB()) {
+ case '000000':
+ $colorIdx = 0x08;
+
+ break;
+ case 'FFFFFF':
+ $colorIdx = 0x09;
+
+ break;
+ case 'FF0000':
+ $colorIdx = 0x0A;
+
+ break;
+ case '00FF00':
+ $colorIdx = 0x0B;
+
+ break;
+ case '0000FF':
+ $colorIdx = 0x0C;
+
+ break;
+ case 'FFFF00':
+ $colorIdx = 0x0D;
+
+ break;
+ case 'FF00FF':
+ $colorIdx = 0x0E;
+
+ break;
+ case '00FFFF':
+ $colorIdx = 0x0F;
+
+ break;
+ case '800000':
+ $colorIdx = 0x10;
+
+ break;
+ case '008000':
+ $colorIdx = 0x11;
+
+ break;
+ case '000080':
+ $colorIdx = 0x12;
+
+ break;
+ case '808000':
+ $colorIdx = 0x13;
+
+ break;
+ case '800080':
+ $colorIdx = 0x14;
+
+ break;
+ case '008080':
+ $colorIdx = 0x15;
+
+ break;
+ case 'C0C0C0':
+ $colorIdx = 0x16;
+
+ break;
+ case '808080':
+ $colorIdx = 0x17;
+
+ break;
+ case '9999FF':
+ $colorIdx = 0x18;
+
+ break;
+ case '993366':
+ $colorIdx = 0x19;
+
+ break;
+ case 'FFFFCC':
+ $colorIdx = 0x1A;
+
+ break;
+ case 'CCFFFF':
+ $colorIdx = 0x1B;
+
+ break;
+ case '660066':
+ $colorIdx = 0x1C;
+
+ break;
+ case 'FF8080':
+ $colorIdx = 0x1D;
+
+ break;
+ case '0066CC':
+ $colorIdx = 0x1E;
+
+ break;
+ case 'CCCCFF':
+ $colorIdx = 0x1F;
+
+ break;
+ case '000080':
+ $colorIdx = 0x20;
+
+ break;
+ case 'FF00FF':
+ $colorIdx = 0x21;
+
+ break;
+ case 'FFFF00':
+ $colorIdx = 0x22;
+
+ break;
+ case '00FFFF':
+ $colorIdx = 0x23;
+
+ break;
+ case '800080':
+ $colorIdx = 0x24;
+
+ break;
+ case '800000':
+ $colorIdx = 0x25;
+
+ break;
+ case '008080':
+ $colorIdx = 0x26;
+
+ break;
+ case '0000FF':
+ $colorIdx = 0x27;
+
+ break;
+ case '00CCFF':
+ $colorIdx = 0x28;
+
+ break;
+ case 'CCFFFF':
+ $colorIdx = 0x29;
+
+ break;
+ case 'CCFFCC':
+ $colorIdx = 0x2A;
+
+ break;
+ case 'FFFF99':
+ $colorIdx = 0x2B;
+
+ break;
+ case '99CCFF':
+ $colorIdx = 0x2C;
+
+ break;
+ case 'FF99CC':
+ $colorIdx = 0x2D;
+
+ break;
+ case 'CC99FF':
+ $colorIdx = 0x2E;
+
+ break;
+ case 'FFCC99':
+ $colorIdx = 0x2F;
+
+ break;
+ case '3366FF':
+ $colorIdx = 0x30;
+
+ break;
+ case '33CCCC':
+ $colorIdx = 0x31;
+
+ break;
+ case '99CC00':
+ $colorIdx = 0x32;
+
+ break;
+ case 'FFCC00':
+ $colorIdx = 0x33;
+
+ break;
+ case 'FF9900':
+ $colorIdx = 0x34;
+
+ break;
+ case 'FF6600':
+ $colorIdx = 0x35;
+
+ break;
+ case '666699':
+ $colorIdx = 0x36;
+
+ break;
+ case '969696':
+ $colorIdx = 0x37;
+
+ break;
+ case '003366':
+ $colorIdx = 0x38;
+
+ break;
+ case '339966':
+ $colorIdx = 0x39;
+
+ break;
+ case '003300':
+ $colorIdx = 0x3A;
+
+ break;
+ case '333300':
+ $colorIdx = 0x3B;
+
+ break;
+ case '993300':
+ $colorIdx = 0x3C;
+
+ break;
+ case '993366':
+ $colorIdx = 0x3D;
+
+ break;
+ case '333399':
+ $colorIdx = 0x3E;
+
+ break;
+ case '333333':
+ $colorIdx = 0x3F;
+
+ break;
+ default:
+ $colorIdx = 0x00;
+
+ break;
+ }
+ $dataBlockFont .= pack('V', $colorIdx);
+ // Not used (4)
+ $dataBlockFont .= pack('V', 0x00000000);
+ // Options flags for modified font attributes
+ $optionsFlags = 0;
+ $optionsFlagsBold = ($conditional->getStyle()->getFont()->getBold() == null ? 1 : 0);
+ $optionsFlags |= (1 == $optionsFlagsBold ? 0x00000002 : 0);
+ $optionsFlags |= (1 == 1 ? 0x00000008 : 0);
+ $optionsFlags |= (1 == 1 ? 0x00000010 : 0);
+ $optionsFlags |= (1 == 0 ? 0x00000020 : 0);
+ $optionsFlags |= (1 == 1 ? 0x00000080 : 0);
+ $dataBlockFont .= pack('V', $optionsFlags);
+ // Escapement type
+ $dataBlockFont .= pack('V', $fontEscapement);
+ // Underline type
+ $dataBlockFont .= pack('V', $fontUnderline);
+ // Always
+ $dataBlockFont .= pack('V', 0x00000000);
+ // Always
+ $dataBlockFont .= pack('V', 0x00000000);
+ // Not used (8)
+ $dataBlockFont .= pack('VV', 0x00000000, 0x00000000);
+ // Always
+ $dataBlockFont .= pack('v', 0x0001);
+ }
+ if ($bFormatAlign == 1) {
+ $blockAlign = 0;
+ // Alignment and text break
+ switch ($conditional->getStyle()->getAlignment()->getHorizontal()) {
+ case Alignment::HORIZONTAL_GENERAL:
+ $blockAlign = 0;
+
+ break;
+ case Alignment::HORIZONTAL_LEFT:
+ $blockAlign = 1;
+
+ break;
+ case Alignment::HORIZONTAL_RIGHT:
+ $blockAlign = 3;
+
+ break;
+ case Alignment::HORIZONTAL_CENTER:
+ $blockAlign = 2;
+
+ break;
+ case Alignment::HORIZONTAL_CENTER_CONTINUOUS:
+ $blockAlign = 6;
+
+ break;
+ case Alignment::HORIZONTAL_JUSTIFY:
+ $blockAlign = 5;
+
+ break;
+ }
+ if ($conditional->getStyle()->getAlignment()->getWrapText() == true) {
+ $blockAlign |= 1 << 3;
+ } else {
+ $blockAlign |= 0 << 3;
+ }
+ switch ($conditional->getStyle()->getAlignment()->getVertical()) {
+ case Alignment::VERTICAL_BOTTOM:
+ $blockAlign = 2 << 4;
+
+ break;
+ case Alignment::VERTICAL_TOP:
+ $blockAlign = 0 << 4;
+
+ break;
+ case Alignment::VERTICAL_CENTER:
+ $blockAlign = 1 << 4;
+
+ break;
+ case Alignment::VERTICAL_JUSTIFY:
+ $blockAlign = 3 << 4;
+
+ break;
+ }
+ $blockAlign |= 0 << 7;
+
+ // Text rotation angle
+ $blockRotation = $conditional->getStyle()->getAlignment()->getTextRotation();
+
+ // Indentation
+ $blockIndent = $conditional->getStyle()->getAlignment()->getIndent();
+ if ($conditional->getStyle()->getAlignment()->getShrinkToFit() == true) {
+ $blockIndent |= 1 << 4;
+ } else {
+ $blockIndent |= 0 << 4;
+ }
+ $blockIndent |= 0 << 6;
+
+ // Relative indentation
+ $blockIndentRelative = 255;
+
+ $dataBlockAlign = pack('CCvvv', $blockAlign, $blockRotation, $blockIndent, $blockIndentRelative, 0x0000);
+ }
+ if ($bFormatBorder == 1) {
+ $blockLineStyle = 0;
+ switch ($conditional->getStyle()->getBorders()->getLeft()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockLineStyle |= 0x00;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockLineStyle |= 0x01;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockLineStyle |= 0x02;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockLineStyle |= 0x03;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockLineStyle |= 0x04;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockLineStyle |= 0x05;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockLineStyle |= 0x06;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockLineStyle |= 0x07;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockLineStyle |= 0x08;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockLineStyle |= 0x09;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockLineStyle |= 0x0A;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockLineStyle |= 0x0B;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockLineStyle |= 0x0C;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockLineStyle |= 0x0D;
+
+ break;
+ }
+ switch ($conditional->getStyle()->getBorders()->getRight()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockLineStyle |= 0x00 << 4;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockLineStyle |= 0x01 << 4;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockLineStyle |= 0x02 << 4;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockLineStyle |= 0x03 << 4;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockLineStyle |= 0x04 << 4;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockLineStyle |= 0x05 << 4;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockLineStyle |= 0x06 << 4;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockLineStyle |= 0x07 << 4;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockLineStyle |= 0x08 << 4;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockLineStyle |= 0x09 << 4;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockLineStyle |= 0x0A << 4;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockLineStyle |= 0x0B << 4;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockLineStyle |= 0x0C << 4;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockLineStyle |= 0x0D << 4;
+
+ break;
+ }
+ switch ($conditional->getStyle()->getBorders()->getTop()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockLineStyle |= 0x00 << 8;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockLineStyle |= 0x01 << 8;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockLineStyle |= 0x02 << 8;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockLineStyle |= 0x03 << 8;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockLineStyle |= 0x04 << 8;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockLineStyle |= 0x05 << 8;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockLineStyle |= 0x06 << 8;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockLineStyle |= 0x07 << 8;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockLineStyle |= 0x08 << 8;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockLineStyle |= 0x09 << 8;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockLineStyle |= 0x0A << 8;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockLineStyle |= 0x0B << 8;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockLineStyle |= 0x0C << 8;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockLineStyle |= 0x0D << 8;
+
+ break;
+ }
+ switch ($conditional->getStyle()->getBorders()->getBottom()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockLineStyle |= 0x00 << 12;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockLineStyle |= 0x01 << 12;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockLineStyle |= 0x02 << 12;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockLineStyle |= 0x03 << 12;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockLineStyle |= 0x04 << 12;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockLineStyle |= 0x05 << 12;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockLineStyle |= 0x06 << 12;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockLineStyle |= 0x07 << 12;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockLineStyle |= 0x08 << 12;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockLineStyle |= 0x09 << 12;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockLineStyle |= 0x0A << 12;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockLineStyle |= 0x0B << 12;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockLineStyle |= 0x0C << 12;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockLineStyle |= 0x0D << 12;
+
+ break;
+ }
+
+ // TODO writeCFRule() => $blockLineStyle => Index Color for left line
+ // TODO writeCFRule() => $blockLineStyle => Index Color for right line
+ // TODO writeCFRule() => $blockLineStyle => Top-left to bottom-right on/off
+ // TODO writeCFRule() => $blockLineStyle => Bottom-left to top-right on/off
+ $blockColor = 0;
+ // TODO writeCFRule() => $blockColor => Index Color for top line
+ // TODO writeCFRule() => $blockColor => Index Color for bottom line
+ // TODO writeCFRule() => $blockColor => Index Color for diagonal line
+ switch ($conditional->getStyle()->getBorders()->getDiagonal()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockColor |= 0x00 << 21;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockColor |= 0x01 << 21;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockColor |= 0x02 << 21;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockColor |= 0x03 << 21;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockColor |= 0x04 << 21;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockColor |= 0x05 << 21;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockColor |= 0x06 << 21;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockColor |= 0x07 << 21;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockColor |= 0x08 << 21;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockColor |= 0x09 << 21;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockColor |= 0x0A << 21;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockColor |= 0x0B << 21;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockColor |= 0x0C << 21;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockColor |= 0x0D << 21;
+
+ break;
+ }
+ $dataBlockBorder = pack('vv', $blockLineStyle, $blockColor);
+ }
+ if ($bFormatFill == 1) {
+ // Fill Patern Style
+ $blockFillPatternStyle = 0;
+ switch ($conditional->getStyle()->getFill()->getFillType()) {
+ case Fill::FILL_NONE:
+ $blockFillPatternStyle = 0x00;
+
+ break;
+ case Fill::FILL_SOLID:
+ $blockFillPatternStyle = 0x01;
+
+ break;
+ case Fill::FILL_PATTERN_MEDIUMGRAY:
+ $blockFillPatternStyle = 0x02;
+
+ break;
+ case Fill::FILL_PATTERN_DARKGRAY:
+ $blockFillPatternStyle = 0x03;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTGRAY:
+ $blockFillPatternStyle = 0x04;
+
+ break;
+ case Fill::FILL_PATTERN_DARKHORIZONTAL:
+ $blockFillPatternStyle = 0x05;
+
+ break;
+ case Fill::FILL_PATTERN_DARKVERTICAL:
+ $blockFillPatternStyle = 0x06;
+
+ break;
+ case Fill::FILL_PATTERN_DARKDOWN:
+ $blockFillPatternStyle = 0x07;
+
+ break;
+ case Fill::FILL_PATTERN_DARKUP:
+ $blockFillPatternStyle = 0x08;
+
+ break;
+ case Fill::FILL_PATTERN_DARKGRID:
+ $blockFillPatternStyle = 0x09;
+
+ break;
+ case Fill::FILL_PATTERN_DARKTRELLIS:
+ $blockFillPatternStyle = 0x0A;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTHORIZONTAL:
+ $blockFillPatternStyle = 0x0B;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTVERTICAL:
+ $blockFillPatternStyle = 0x0C;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTDOWN:
+ $blockFillPatternStyle = 0x0D;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTUP:
+ $blockFillPatternStyle = 0x0E;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTGRID:
+ $blockFillPatternStyle = 0x0F;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTTRELLIS:
+ $blockFillPatternStyle = 0x10;
+
+ break;
+ case Fill::FILL_PATTERN_GRAY125:
+ $blockFillPatternStyle = 0x11;
+
+ break;
+ case Fill::FILL_PATTERN_GRAY0625:
+ $blockFillPatternStyle = 0x12;
+
+ break;
+ case Fill::FILL_GRADIENT_LINEAR:
+ $blockFillPatternStyle = 0x00;
+
+ break; // does not exist in BIFF8
+ case Fill::FILL_GRADIENT_PATH:
+ $blockFillPatternStyle = 0x00;
+
+ break; // does not exist in BIFF8
+ default:
+ $blockFillPatternStyle = 0x00;
+
+ break;
+ }
+ // Color
+ switch ($conditional->getStyle()->getFill()->getStartColor()->getRGB()) {
+ case '000000':
+ $colorIdxBg = 0x08;
+
+ break;
+ case 'FFFFFF':
+ $colorIdxBg = 0x09;
+
+ break;
+ case 'FF0000':
+ $colorIdxBg = 0x0A;
+
+ break;
+ case '00FF00':
+ $colorIdxBg = 0x0B;
+
+ break;
+ case '0000FF':
+ $colorIdxBg = 0x0C;
+
+ break;
+ case 'FFFF00':
+ $colorIdxBg = 0x0D;
+
+ break;
+ case 'FF00FF':
+ $colorIdxBg = 0x0E;
+
+ break;
+ case '00FFFF':
+ $colorIdxBg = 0x0F;
+
+ break;
+ case '800000':
+ $colorIdxBg = 0x10;
+
+ break;
+ case '008000':
+ $colorIdxBg = 0x11;
+
+ break;
+ case '000080':
+ $colorIdxBg = 0x12;
+
+ break;
+ case '808000':
+ $colorIdxBg = 0x13;
+
+ break;
+ case '800080':
+ $colorIdxBg = 0x14;
+
+ break;
+ case '008080':
+ $colorIdxBg = 0x15;
+
+ break;
+ case 'C0C0C0':
+ $colorIdxBg = 0x16;
+
+ break;
+ case '808080':
+ $colorIdxBg = 0x17;
+
+ break;
+ case '9999FF':
+ $colorIdxBg = 0x18;
+
+ break;
+ case '993366':
+ $colorIdxBg = 0x19;
+
+ break;
+ case 'FFFFCC':
+ $colorIdxBg = 0x1A;
+
+ break;
+ case 'CCFFFF':
+ $colorIdxBg = 0x1B;
+
+ break;
+ case '660066':
+ $colorIdxBg = 0x1C;
+
+ break;
+ case 'FF8080':
+ $colorIdxBg = 0x1D;
+
+ break;
+ case '0066CC':
+ $colorIdxBg = 0x1E;
+
+ break;
+ case 'CCCCFF':
+ $colorIdxBg = 0x1F;
+
+ break;
+ case '000080':
+ $colorIdxBg = 0x20;
+
+ break;
+ case 'FF00FF':
+ $colorIdxBg = 0x21;
+
+ break;
+ case 'FFFF00':
+ $colorIdxBg = 0x22;
+
+ break;
+ case '00FFFF':
+ $colorIdxBg = 0x23;
+
+ break;
+ case '800080':
+ $colorIdxBg = 0x24;
+
+ break;
+ case '800000':
+ $colorIdxBg = 0x25;
+
+ break;
+ case '008080':
+ $colorIdxBg = 0x26;
+
+ break;
+ case '0000FF':
+ $colorIdxBg = 0x27;
+
+ break;
+ case '00CCFF':
+ $colorIdxBg = 0x28;
+
+ break;
+ case 'CCFFFF':
+ $colorIdxBg = 0x29;
+
+ break;
+ case 'CCFFCC':
+ $colorIdxBg = 0x2A;
+
+ break;
+ case 'FFFF99':
+ $colorIdxBg = 0x2B;
+
+ break;
+ case '99CCFF':
+ $colorIdxBg = 0x2C;
+
+ break;
+ case 'FF99CC':
+ $colorIdxBg = 0x2D;
+
+ break;
+ case 'CC99FF':
+ $colorIdxBg = 0x2E;
+
+ break;
+ case 'FFCC99':
+ $colorIdxBg = 0x2F;
+
+ break;
+ case '3366FF':
+ $colorIdxBg = 0x30;
+
+ break;
+ case '33CCCC':
+ $colorIdxBg = 0x31;
+
+ break;
+ case '99CC00':
+ $colorIdxBg = 0x32;
+
+ break;
+ case 'FFCC00':
+ $colorIdxBg = 0x33;
+
+ break;
+ case 'FF9900':
+ $colorIdxBg = 0x34;
+
+ break;
+ case 'FF6600':
+ $colorIdxBg = 0x35;
+
+ break;
+ case '666699':
+ $colorIdxBg = 0x36;
+
+ break;
+ case '969696':
+ $colorIdxBg = 0x37;
+
+ break;
+ case '003366':
+ $colorIdxBg = 0x38;
+
+ break;
+ case '339966':
+ $colorIdxBg = 0x39;
+
+ break;
+ case '003300':
+ $colorIdxBg = 0x3A;
+
+ break;
+ case '333300':
+ $colorIdxBg = 0x3B;
+
+ break;
+ case '993300':
+ $colorIdxBg = 0x3C;
+
+ break;
+ case '993366':
+ $colorIdxBg = 0x3D;
+
+ break;
+ case '333399':
+ $colorIdxBg = 0x3E;
+
+ break;
+ case '333333':
+ $colorIdxBg = 0x3F;
+
+ break;
+ default:
+ $colorIdxBg = 0x41;
+
+ break;
+ }
+ // Fg Color
+ switch ($conditional->getStyle()->getFill()->getEndColor()->getRGB()) {
+ case '000000':
+ $colorIdxFg = 0x08;
+
+ break;
+ case 'FFFFFF':
+ $colorIdxFg = 0x09;
+
+ break;
+ case 'FF0000':
+ $colorIdxFg = 0x0A;
+
+ break;
+ case '00FF00':
+ $colorIdxFg = 0x0B;
+
+ break;
+ case '0000FF':
+ $colorIdxFg = 0x0C;
+
+ break;
+ case 'FFFF00':
+ $colorIdxFg = 0x0D;
+
+ break;
+ case 'FF00FF':
+ $colorIdxFg = 0x0E;
+
+ break;
+ case '00FFFF':
+ $colorIdxFg = 0x0F;
+
+ break;
+ case '800000':
+ $colorIdxFg = 0x10;
+
+ break;
+ case '008000':
+ $colorIdxFg = 0x11;
+
+ break;
+ case '000080':
+ $colorIdxFg = 0x12;
+
+ break;
+ case '808000':
+ $colorIdxFg = 0x13;
+
+ break;
+ case '800080':
+ $colorIdxFg = 0x14;
+
+ break;
+ case '008080':
+ $colorIdxFg = 0x15;
+
+ break;
+ case 'C0C0C0':
+ $colorIdxFg = 0x16;
+
+ break;
+ case '808080':
+ $colorIdxFg = 0x17;
+
+ break;
+ case '9999FF':
+ $colorIdxFg = 0x18;
+
+ break;
+ case '993366':
+ $colorIdxFg = 0x19;
+
+ break;
+ case 'FFFFCC':
+ $colorIdxFg = 0x1A;
+
+ break;
+ case 'CCFFFF':
+ $colorIdxFg = 0x1B;
+
+ break;
+ case '660066':
+ $colorIdxFg = 0x1C;
+
+ break;
+ case 'FF8080':
+ $colorIdxFg = 0x1D;
+
+ break;
+ case '0066CC':
+ $colorIdxFg = 0x1E;
+
+ break;
+ case 'CCCCFF':
+ $colorIdxFg = 0x1F;
+
+ break;
+ case '000080':
+ $colorIdxFg = 0x20;
+
+ break;
+ case 'FF00FF':
+ $colorIdxFg = 0x21;
+
+ break;
+ case 'FFFF00':
+ $colorIdxFg = 0x22;
+
+ break;
+ case '00FFFF':
+ $colorIdxFg = 0x23;
+
+ break;
+ case '800080':
+ $colorIdxFg = 0x24;
+
+ break;
+ case '800000':
+ $colorIdxFg = 0x25;
+
+ break;
+ case '008080':
+ $colorIdxFg = 0x26;
+
+ break;
+ case '0000FF':
+ $colorIdxFg = 0x27;
+
+ break;
+ case '00CCFF':
+ $colorIdxFg = 0x28;
+
+ break;
+ case 'CCFFFF':
+ $colorIdxFg = 0x29;
+
+ break;
+ case 'CCFFCC':
+ $colorIdxFg = 0x2A;
+
+ break;
+ case 'FFFF99':
+ $colorIdxFg = 0x2B;
+
+ break;
+ case '99CCFF':
+ $colorIdxFg = 0x2C;
+
+ break;
+ case 'FF99CC':
+ $colorIdxFg = 0x2D;
+
+ break;
+ case 'CC99FF':
+ $colorIdxFg = 0x2E;
+
+ break;
+ case 'FFCC99':
+ $colorIdxFg = 0x2F;
+
+ break;
+ case '3366FF':
+ $colorIdxFg = 0x30;
+
+ break;
+ case '33CCCC':
+ $colorIdxFg = 0x31;
+
+ break;
+ case '99CC00':
+ $colorIdxFg = 0x32;
+
+ break;
+ case 'FFCC00':
+ $colorIdxFg = 0x33;
+
+ break;
+ case 'FF9900':
+ $colorIdxFg = 0x34;
+
+ break;
+ case 'FF6600':
+ $colorIdxFg = 0x35;
+
+ break;
+ case '666699':
+ $colorIdxFg = 0x36;
+
+ break;
+ case '969696':
+ $colorIdxFg = 0x37;
+
+ break;
+ case '003366':
+ $colorIdxFg = 0x38;
+
+ break;
+ case '339966':
+ $colorIdxFg = 0x39;
+
+ break;
+ case '003300':
+ $colorIdxFg = 0x3A;
+
+ break;
+ case '333300':
+ $colorIdxFg = 0x3B;
+
+ break;
+ case '993300':
+ $colorIdxFg = 0x3C;
+
+ break;
+ case '993366':
+ $colorIdxFg = 0x3D;
+
+ break;
+ case '333399':
+ $colorIdxFg = 0x3E;
+
+ break;
+ case '333333':
+ $colorIdxFg = 0x3F;
+
+ break;
+ default:
+ $colorIdxFg = 0x40;
+
+ break;
+ }
+ $dataBlockFill = pack('v', $blockFillPatternStyle);
+ $dataBlockFill .= pack('v', $colorIdxFg | ($colorIdxBg << 7));
+ }
+ if ($bFormatProt == 1) {
+ $dataBlockProtection = 0;
+ if ($conditional->getStyle()->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED) {
+ $dataBlockProtection = 1;
+ }
+ if ($conditional->getStyle()->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED) {
+ $dataBlockProtection = 1 << 1;
+ }
+ }
+
+ $data = pack('CCvvVv', $type, $operatorType, $szValue1, $szValue2, $flags, 0x0000);
+ if ($bFormatFont == 1) { // Block Formatting : OK
+ $data .= $dataBlockFont;
+ }
+ if ($bFormatAlign == 1) {
+ $data .= $dataBlockAlign;
+ }
+ if ($bFormatBorder == 1) {
+ $data .= $dataBlockBorder;
+ }
+ if ($bFormatFill == 1) { // Block Formatting : OK
+ $data .= $dataBlockFill;
+ }
+ if ($bFormatProt == 1) {
+ $data .= $dataBlockProtection;
+ }
+ if ($operand1 !== null) {
+ $data .= $operand1;
+ }
+ if ($operand2 !== null) {
+ $data .= $operand2;
+ }
+ $header = pack('vv', $record, strlen($data));
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write CFHeader record.
+ */
+ private function writeCFHeader(): void
+ {
+ $record = 0x01B0; // Record identifier
+ $length = 0x0016; // Bytes to follow
+
+ $numColumnMin = null;
+ $numColumnMax = null;
+ $numRowMin = null;
+ $numRowMax = null;
+ $arrConditional = [];
+ foreach ($this->phpSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) {
+ foreach ($conditionalStyles as $conditional) {
+ if ($conditional->getConditionType() == Conditional::CONDITION_EXPRESSION
+ || $conditional->getConditionType() == Conditional::CONDITION_CELLIS) {
+ if (!in_array($conditional->getHashCode(), $arrConditional)) {
+ $arrConditional[] = $conditional->getHashCode();
+ }
+ // Cells
+ $arrCoord = Coordinate::coordinateFromString($cellCoordinate);
+ if (!is_numeric($arrCoord[0])) {
+ $arrCoord[0] = Coordinate::columnIndexFromString($arrCoord[0]);
+ }
+ if ($numColumnMin === null || ($numColumnMin > $arrCoord[0])) {
+ $numColumnMin = $arrCoord[0];
+ }
+ if ($numColumnMax === null || ($numColumnMax < $arrCoord[0])) {
+ $numColumnMax = $arrCoord[0];
+ }
+ if ($numRowMin === null || ($numRowMin > $arrCoord[1])) {
+ $numRowMin = $arrCoord[1];
+ }
+ if ($numRowMax === null || ($numRowMax < $arrCoord[1])) {
+ $numRowMax = $arrCoord[1];
+ }
+ }
+ }
+ }
+ $needRedraw = 1;
+ $cellRange = pack('vvvv', $numRowMin - 1, $numRowMax - 1, $numColumnMin - 1, $numColumnMax - 1);
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vv', count($arrConditional), $needRedraw);
+ $data .= $cellRange;
+ $data .= pack('v', 0x0001);
+ $data .= $cellRange;
+ $this->append($header . $data);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php
new file mode 100755
index 0000000..3e8169b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php
@@ -0,0 +1,548 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Borders;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Protection;
+use PhpOffice\PhpSpreadsheet\Style\Style;
+
+// Original file header of PEAR::Spreadsheet_Excel_Writer_Format (used as the base for this class):
+// -----------------------------------------------------------------------------------------
+// /*
+// * Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
+// *
+// * The majority of this is _NOT_ my code. I simply ported it from the
+// * PERL Spreadsheet::WriteExcel module.
+// *
+// * The author of the Spreadsheet::WriteExcel module is John McNamara
+// * <jmcnamara@cpan.org>
+// *
+// * I _DO_ maintain this code, and John McNamara has nothing to do with the
+// * porting of this code to PHP. Any questions directly related to this
+// * class library should be directed to me.
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class Xf
+{
+ /**
+ * Style XF or a cell XF ?
+ *
+ * @var bool
+ */
+ private $isStyleXf;
+
+ /**
+ * Index to the FONT record. Index 4 does not exist.
+ *
+ * @var int
+ */
+ private $fontIndex;
+
+ /**
+ * An index (2 bytes) to a FORMAT record (number format).
+ *
+ * @var int
+ */
+ private $numberFormatIndex;
+
+ /**
+ * 1 bit, apparently not used.
+ *
+ * @var int
+ */
+ private $textJustLast;
+
+ /**
+ * The cell's foreground color.
+ *
+ * @var int
+ */
+ private $foregroundColor;
+
+ /**
+ * The cell's background color.
+ *
+ * @var int
+ */
+ private $backgroundColor;
+
+ /**
+ * Color of the bottom border of the cell.
+ *
+ * @var int
+ */
+ private $bottomBorderColor;
+
+ /**
+ * Color of the top border of the cell.
+ *
+ * @var int
+ */
+ private $topBorderColor;
+
+ /**
+ * Color of the left border of the cell.
+ *
+ * @var int
+ */
+ private $leftBorderColor;
+
+ /**
+ * Color of the right border of the cell.
+ *
+ * @var int
+ */
+ private $rightBorderColor;
+
+ /**
+ * Constructor.
+ *
+ * @param Style $style The XF format
+ */
+ public function __construct(Style $style)
+ {
+ $this->isStyleXf = false;
+ $this->fontIndex = 0;
+
+ $this->numberFormatIndex = 0;
+
+ $this->textJustLast = 0;
+
+ $this->foregroundColor = 0x40;
+ $this->backgroundColor = 0x41;
+
+ $this->_diag = 0;
+
+ $this->bottomBorderColor = 0x40;
+ $this->topBorderColor = 0x40;
+ $this->leftBorderColor = 0x40;
+ $this->rightBorderColor = 0x40;
+ $this->_diag_color = 0x40;
+ $this->_style = $style;
+ }
+
+ /**
+ * Generate an Excel BIFF XF record (style or cell).
+ *
+ * @return string The XF record
+ */
+ public function writeXf()
+ {
+ // Set the type of the XF record and some of the attributes.
+ if ($this->isStyleXf) {
+ $style = 0xFFF5;
+ } else {
+ $style = self::mapLocked($this->_style->getProtection()->getLocked());
+ $style |= self::mapHidden($this->_style->getProtection()->getHidden()) << 1;
+ }
+
+ // Flags to indicate if attributes have been set.
+ $atr_num = ($this->numberFormatIndex != 0) ? 1 : 0;
+ $atr_fnt = ($this->fontIndex != 0) ? 1 : 0;
+ $atr_alc = ((int) $this->_style->getAlignment()->getWrapText()) ? 1 : 0;
+ $atr_bdr = (self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) ||
+ self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) ||
+ self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()) ||
+ self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle())) ? 1 : 0;
+ $atr_pat = (($this->foregroundColor != 0x40) ||
+ ($this->backgroundColor != 0x41) ||
+ self::mapFillType($this->_style->getFill()->getFillType())) ? 1 : 0;
+ $atr_prot = self::mapLocked($this->_style->getProtection()->getLocked())
+ | self::mapHidden($this->_style->getProtection()->getHidden());
+
+ // Zero the default border colour if the border has not been set.
+ if (self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) == 0) {
+ $this->bottomBorderColor = 0;
+ }
+ if (self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) == 0) {
+ $this->topBorderColor = 0;
+ }
+ if (self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle()) == 0) {
+ $this->rightBorderColor = 0;
+ }
+ if (self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()) == 0) {
+ $this->leftBorderColor = 0;
+ }
+ if (self::mapBorderStyle($this->_style->getBorders()->getDiagonal()->getBorderStyle()) == 0) {
+ $this->_diag_color = 0;
+ }
+
+ $record = 0x00E0; // Record identifier
+ $length = 0x0014; // Number of bytes to follow
+
+ $ifnt = $this->fontIndex; // Index to FONT record
+ $ifmt = $this->numberFormatIndex; // Index to FORMAT record
+
+ $align = $this->mapHAlign($this->_style->getAlignment()->getHorizontal()); // Alignment
+ $align |= (int) $this->_style->getAlignment()->getWrapText() << 3;
+ $align |= self::mapVAlign($this->_style->getAlignment()->getVertical()) << 4;
+ $align |= $this->textJustLast << 7;
+
+ $used_attrib = $atr_num << 2;
+ $used_attrib |= $atr_fnt << 3;
+ $used_attrib |= $atr_alc << 4;
+ $used_attrib |= $atr_bdr << 5;
+ $used_attrib |= $atr_pat << 6;
+ $used_attrib |= $atr_prot << 7;
+
+ $icv = $this->foregroundColor; // fg and bg pattern colors
+ $icv |= $this->backgroundColor << 7;
+
+ $border1 = self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()); // Border line style and color
+ $border1 |= self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle()) << 4;
+ $border1 |= self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) << 8;
+ $border1 |= self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) << 12;
+ $border1 |= $this->leftBorderColor << 16;
+ $border1 |= $this->rightBorderColor << 23;
+
+ $diagonalDirection = $this->_style->getBorders()->getDiagonalDirection();
+ $diag_tl_to_rb = $diagonalDirection == Borders::DIAGONAL_BOTH
+ || $diagonalDirection == Borders::DIAGONAL_DOWN;
+ $diag_tr_to_lb = $diagonalDirection == Borders::DIAGONAL_BOTH
+ || $diagonalDirection == Borders::DIAGONAL_UP;
+ $border1 |= $diag_tl_to_rb << 30;
+ $border1 |= $diag_tr_to_lb << 31;
+
+ $border2 = $this->topBorderColor; // Border color
+ $border2 |= $this->bottomBorderColor << 7;
+ $border2 |= $this->_diag_color << 14;
+ $border2 |= self::mapBorderStyle($this->_style->getBorders()->getDiagonal()->getBorderStyle()) << 21;
+ $border2 |= self::mapFillType($this->_style->getFill()->getFillType()) << 26;
+
+ $header = pack('vv', $record, $length);
+
+ //BIFF8 options: identation, shrinkToFit and text direction
+ $biff8_options = $this->_style->getAlignment()->getIndent();
+ $biff8_options |= (int) $this->_style->getAlignment()->getShrinkToFit() << 4;
+
+ $data = pack('vvvC', $ifnt, $ifmt, $style, $align);
+ $data .= pack('CCC', self::mapTextRotation($this->_style->getAlignment()->getTextRotation()), $biff8_options, $used_attrib);
+ $data .= pack('VVv', $border1, $border2, $icv);
+
+ return $header . $data;
+ }
+
+ /**
+ * Is this a style XF ?
+ *
+ * @param bool $value
+ */
+ public function setIsStyleXf($value): void
+ {
+ $this->isStyleXf = $value;
+ }
+
+ /**
+ * Sets the cell's bottom border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setBottomColor($colorIndex): void
+ {
+ $this->bottomBorderColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's top border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setTopColor($colorIndex): void
+ {
+ $this->topBorderColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's left border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setLeftColor($colorIndex): void
+ {
+ $this->leftBorderColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's right border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setRightColor($colorIndex): void
+ {
+ $this->rightBorderColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's diagonal border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setDiagColor($colorIndex): void
+ {
+ $this->_diag_color = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's foreground color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setFgColor($colorIndex): void
+ {
+ $this->foregroundColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's background color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setBgColor($colorIndex): void
+ {
+ $this->backgroundColor = $colorIndex;
+ }
+
+ /**
+ * Sets the index to the number format record
+ * It can be date, time, currency, etc...
+ *
+ * @param int $numberFormatIndex Index to format record
+ */
+ public function setNumberFormatIndex($numberFormatIndex): void
+ {
+ $this->numberFormatIndex = $numberFormatIndex;
+ }
+
+ /**
+ * Set the font index.
+ *
+ * @param int $value Font index, note that value 4 does not exist
+ */
+ public function setFontIndex($value): void
+ {
+ $this->fontIndex = $value;
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for border styles.
+ *
+ * @var array of int
+ */
+ private static $mapBorderStyles = [
+ Border::BORDER_NONE => 0x00,
+ Border::BORDER_THIN => 0x01,
+ Border::BORDER_MEDIUM => 0x02,
+ Border::BORDER_DASHED => 0x03,
+ Border::BORDER_DOTTED => 0x04,
+ Border::BORDER_THICK => 0x05,
+ Border::BORDER_DOUBLE => 0x06,
+ Border::BORDER_HAIR => 0x07,
+ Border::BORDER_MEDIUMDASHED => 0x08,
+ Border::BORDER_DASHDOT => 0x09,
+ Border::BORDER_MEDIUMDASHDOT => 0x0A,
+ Border::BORDER_DASHDOTDOT => 0x0B,
+ Border::BORDER_MEDIUMDASHDOTDOT => 0x0C,
+ Border::BORDER_SLANTDASHDOT => 0x0D,
+ ];
+
+ /**
+ * Map border style.
+ *
+ * @param string $borderStyle
+ *
+ * @return int
+ */
+ private static function mapBorderStyle($borderStyle)
+ {
+ if (isset(self::$mapBorderStyles[$borderStyle])) {
+ return self::$mapBorderStyles[$borderStyle];
+ }
+
+ return 0x00;
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for fill types.
+ *
+ * @var array of int
+ */
+ private static $mapFillTypes = [
+ Fill::FILL_NONE => 0x00,
+ Fill::FILL_SOLID => 0x01,
+ Fill::FILL_PATTERN_MEDIUMGRAY => 0x02,
+ Fill::FILL_PATTERN_DARKGRAY => 0x03,
+ Fill::FILL_PATTERN_LIGHTGRAY => 0x04,
+ Fill::FILL_PATTERN_DARKHORIZONTAL => 0x05,
+ Fill::FILL_PATTERN_DARKVERTICAL => 0x06,
+ Fill::FILL_PATTERN_DARKDOWN => 0x07,
+ Fill::FILL_PATTERN_DARKUP => 0x08,
+ Fill::FILL_PATTERN_DARKGRID => 0x09,
+ Fill::FILL_PATTERN_DARKTRELLIS => 0x0A,
+ Fill::FILL_PATTERN_LIGHTHORIZONTAL => 0x0B,
+ Fill::FILL_PATTERN_LIGHTVERTICAL => 0x0C,
+ Fill::FILL_PATTERN_LIGHTDOWN => 0x0D,
+ Fill::FILL_PATTERN_LIGHTUP => 0x0E,
+ Fill::FILL_PATTERN_LIGHTGRID => 0x0F,
+ Fill::FILL_PATTERN_LIGHTTRELLIS => 0x10,
+ Fill::FILL_PATTERN_GRAY125 => 0x11,
+ Fill::FILL_PATTERN_GRAY0625 => 0x12,
+ Fill::FILL_GRADIENT_LINEAR => 0x00, // does not exist in BIFF8
+ Fill::FILL_GRADIENT_PATH => 0x00, // does not exist in BIFF8
+ ];
+
+ /**
+ * Map fill type.
+ *
+ * @param string $fillType
+ *
+ * @return int
+ */
+ private static function mapFillType($fillType)
+ {
+ if (isset(self::$mapFillTypes[$fillType])) {
+ return self::$mapFillTypes[$fillType];
+ }
+
+ return 0x00;
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for horizontal alignment.
+ *
+ * @var array of int
+ */
+ private static $mapHAlignments = [
+ Alignment::HORIZONTAL_GENERAL => 0,
+ Alignment::HORIZONTAL_LEFT => 1,
+ Alignment::HORIZONTAL_CENTER => 2,
+ Alignment::HORIZONTAL_RIGHT => 3,
+ Alignment::HORIZONTAL_FILL => 4,
+ Alignment::HORIZONTAL_JUSTIFY => 5,
+ Alignment::HORIZONTAL_CENTER_CONTINUOUS => 6,
+ ];
+
+ /**
+ * Map to BIFF2-BIFF8 codes for horizontal alignment.
+ *
+ * @param string $hAlign
+ *
+ * @return int
+ */
+ private function mapHAlign($hAlign)
+ {
+ if (isset(self::$mapHAlignments[$hAlign])) {
+ return self::$mapHAlignments[$hAlign];
+ }
+
+ return 0;
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for vertical alignment.
+ *
+ * @var array of int
+ */
+ private static $mapVAlignments = [
+ Alignment::VERTICAL_TOP => 0,
+ Alignment::VERTICAL_CENTER => 1,
+ Alignment::VERTICAL_BOTTOM => 2,
+ Alignment::VERTICAL_JUSTIFY => 3,
+ ];
+
+ /**
+ * Map to BIFF2-BIFF8 codes for vertical alignment.
+ *
+ * @param string $vAlign
+ *
+ * @return int
+ */
+ private static function mapVAlign($vAlign)
+ {
+ if (isset(self::$mapVAlignments[$vAlign])) {
+ return self::$mapVAlignments[$vAlign];
+ }
+
+ return 2;
+ }
+
+ /**
+ * Map to BIFF8 codes for text rotation angle.
+ *
+ * @param int $textRotation
+ *
+ * @return int
+ */
+ private static function mapTextRotation($textRotation)
+ {
+ if ($textRotation >= 0) {
+ return $textRotation;
+ } elseif ($textRotation == -165) {
+ return 255;
+ } elseif ($textRotation < 0) {
+ return 90 - $textRotation;
+ }
+ }
+
+ /**
+ * Map locked.
+ *
+ * @param string $locked
+ *
+ * @return int
+ */
+ private static function mapLocked($locked)
+ {
+ switch ($locked) {
+ case Protection::PROTECTION_INHERIT:
+ return 1;
+ case Protection::PROTECTION_PROTECTED:
+ return 1;
+ case Protection::PROTECTION_UNPROTECTED:
+ return 0;
+ default:
+ return 1;
+ }
+ }
+
+ /**
+ * Map hidden.
+ *
+ * @param string $hidden
+ *
+ * @return int
+ */
+ private static function mapHidden($hidden)
+ {
+ switch ($hidden) {
+ case Protection::PROTECTION_INHERIT:
+ return 0;
+ case Protection::PROTECTION_PROTECTED:
+ return 1;
+ case Protection::PROTECTION_UNPROTECTED:
+ return 0;
+ default:
+ return 0;
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php
new file mode 100755
index 0000000..bfcde04
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php
@@ -0,0 +1,538 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\HashTable;
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\Drawing as WorksheetDrawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Chart;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Comments;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\ContentTypes;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\DocProps;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Drawing;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsRibbon;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsVBA;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\StringTable;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Style;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Theme;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Workbook;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet;
+use ZipArchive;
+use ZipStream\Exception\OverflowException;
+use ZipStream\Option\Archive;
+use ZipStream\ZipStream;
+
+class Xlsx extends BaseWriter
+{
+ /**
+ * Office2003 compatibility.
+ *
+ * @var bool
+ */
+ private $office2003compatibility = false;
+
+ /**
+ * Private writer parts.
+ *
+ * @var Xlsx\WriterPart[]
+ */
+ private $writerParts = [];
+
+ /**
+ * Private Spreadsheet.
+ *
+ * @var Spreadsheet
+ */
+ private $spreadSheet;
+
+ /**
+ * Private string table.
+ *
+ * @var string[]
+ */
+ private $stringTable = [];
+
+ /**
+ * Private unique Conditional HashTable.
+ *
+ * @var HashTable
+ */
+ private $stylesConditionalHashTable;
+
+ /**
+ * Private unique Style HashTable.
+ *
+ * @var HashTable
+ */
+ private $styleHashTable;
+
+ /**
+ * Private unique Fill HashTable.
+ *
+ * @var HashTable
+ */
+ private $fillHashTable;
+
+ /**
+ * Private unique \PhpOffice\PhpSpreadsheet\Style\Font HashTable.
+ *
+ * @var HashTable
+ */
+ private $fontHashTable;
+
+ /**
+ * Private unique Borders HashTable.
+ *
+ * @var HashTable
+ */
+ private $bordersHashTable;
+
+ /**
+ * Private unique NumberFormat HashTable.
+ *
+ * @var HashTable
+ */
+ private $numFmtHashTable;
+
+ /**
+ * Private unique \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable.
+ *
+ * @var HashTable
+ */
+ private $drawingHashTable;
+
+ /**
+ * Create a new Xlsx Writer.
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ // Assign PhpSpreadsheet
+ $this->setSpreadsheet($spreadsheet);
+
+ $writerPartsArray = [
+ 'stringtable' => StringTable::class,
+ 'contenttypes' => ContentTypes::class,
+ 'docprops' => DocProps::class,
+ 'rels' => Rels::class,
+ 'theme' => Theme::class,
+ 'style' => Style::class,
+ 'workbook' => Workbook::class,
+ 'worksheet' => Worksheet::class,
+ 'drawing' => Drawing::class,
+ 'comments' => Comments::class,
+ 'chart' => Chart::class,
+ 'relsvba' => RelsVBA::class,
+ 'relsribbonobjects' => RelsRibbon::class,
+ ];
+
+ // Initialise writer parts
+ // and Assign their parent IWriters
+ foreach ($writerPartsArray as $writer => $class) {
+ $this->writerParts[$writer] = new $class($this);
+ }
+
+ $hashTablesArray = ['stylesConditionalHashTable', 'fillHashTable', 'fontHashTable',
+ 'bordersHashTable', 'numFmtHashTable', 'drawingHashTable',
+ 'styleHashTable',
+ ];
+
+ // Set HashTable variables
+ foreach ($hashTablesArray as $tableName) {
+ $this->$tableName = new HashTable();
+ }
+ }
+
+ /**
+ * Get writer part.
+ *
+ * @param string $pPartName Writer part name
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Writer\Xlsx\WriterPart
+ */
+ public function getWriterPart($pPartName)
+ {
+ if ($pPartName != '' && isset($this->writerParts[strtolower($pPartName)])) {
+ return $this->writerParts[strtolower($pPartName)];
+ }
+
+ return null;
+ }
+
+ /**
+ * Save PhpSpreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ if ($this->spreadSheet !== null) {
+ // garbage collect
+ $this->spreadSheet->garbageCollect();
+
+ $this->openFileHandle($pFilename);
+
+ $saveDebugLog = Calculation::getInstance($this->spreadSheet)->getDebugLog()->getWriteDebugLog();
+ Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog(false);
+ $saveDateReturnType = Functions::getReturnDateType();
+ Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
+
+ // Create string lookup table
+ $this->stringTable = [];
+ for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
+ $this->stringTable = $this->getWriterPart('StringTable')->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable);
+ }
+
+ // Create styles dictionaries
+ $this->styleHashTable->addFromSource($this->getWriterPart('Style')->allStyles($this->spreadSheet));
+ $this->stylesConditionalHashTable->addFromSource($this->getWriterPart('Style')->allConditionalStyles($this->spreadSheet));
+ $this->fillHashTable->addFromSource($this->getWriterPart('Style')->allFills($this->spreadSheet));
+ $this->fontHashTable->addFromSource($this->getWriterPart('Style')->allFonts($this->spreadSheet));
+ $this->bordersHashTable->addFromSource($this->getWriterPart('Style')->allBorders($this->spreadSheet));
+ $this->numFmtHashTable->addFromSource($this->getWriterPart('Style')->allNumberFormats($this->spreadSheet));
+
+ // Create drawing dictionary
+ $this->drawingHashTable->addFromSource($this->getWriterPart('Drawing')->allDrawings($this->spreadSheet));
+
+ $options = new Archive();
+ $options->setEnableZip64(false);
+ $options->setOutputStream($this->fileHandle);
+
+ $zip = new ZipStream(null, $options);
+
+ // Add [Content_Types].xml to ZIP file
+ $zip->addFile('[Content_Types].xml', $this->getWriterPart('ContentTypes')->writeContentTypes($this->spreadSheet, $this->includeCharts));
+
+ //if hasMacros, add the vbaProject.bin file, Certificate file(if exists)
+ if ($this->spreadSheet->hasMacros()) {
+ $macrosCode = $this->spreadSheet->getMacrosCode();
+ if ($macrosCode !== null) {
+ // we have the code ?
+ $zip->addFile('xl/vbaProject.bin', $macrosCode); //allways in 'xl', allways named vbaProject.bin
+ if ($this->spreadSheet->hasMacrosCertificate()) {
+ //signed macros ?
+ // Yes : add the certificate file and the related rels file
+ $zip->addFile('xl/vbaProjectSignature.bin', $this->spreadSheet->getMacrosCertificate());
+ $zip->addFile('xl/_rels/vbaProject.bin.rels', $this->getWriterPart('RelsVBA')->writeVBARelationships($this->spreadSheet));
+ }
+ }
+ }
+ //a custom UI in this workbook ? add it ("base" xml and additional objects (pictures) and rels)
+ if ($this->spreadSheet->hasRibbon()) {
+ $tmpRibbonTarget = $this->spreadSheet->getRibbonXMLData('target');
+ $zip->addFile($tmpRibbonTarget, $this->spreadSheet->getRibbonXMLData('data'));
+ if ($this->spreadSheet->hasRibbonBinObjects()) {
+ $tmpRootPath = dirname($tmpRibbonTarget) . '/';
+ $ribbonBinObjects = $this->spreadSheet->getRibbonBinObjects('data'); //the files to write
+ foreach ($ribbonBinObjects as $aPath => $aContent) {
+ $zip->addFile($tmpRootPath . $aPath, $aContent);
+ }
+ //the rels for files
+ $zip->addFile($tmpRootPath . '_rels/' . basename($tmpRibbonTarget) . '.rels', $this->getWriterPart('RelsRibbonObjects')->writeRibbonRelationships($this->spreadSheet));
+ }
+ }
+
+ // Add relationships to ZIP file
+ $zip->addFile('_rels/.rels', $this->getWriterPart('Rels')->writeRelationships($this->spreadSheet));
+ $zip->addFile('xl/_rels/workbook.xml.rels', $this->getWriterPart('Rels')->writeWorkbookRelationships($this->spreadSheet));
+
+ // Add document properties to ZIP file
+ $zip->addFile('docProps/app.xml', $this->getWriterPart('DocProps')->writeDocPropsApp($this->spreadSheet));
+ $zip->addFile('docProps/core.xml', $this->getWriterPart('DocProps')->writeDocPropsCore($this->spreadSheet));
+ $customPropertiesPart = $this->getWriterPart('DocProps')->writeDocPropsCustom($this->spreadSheet);
+ if ($customPropertiesPart !== null) {
+ $zip->addFile('docProps/custom.xml', $customPropertiesPart);
+ }
+
+ // Add theme to ZIP file
+ $zip->addFile('xl/theme/theme1.xml', $this->getWriterPart('Theme')->writeTheme($this->spreadSheet));
+
+ // Add string table to ZIP file
+ $zip->addFile('xl/sharedStrings.xml', $this->getWriterPart('StringTable')->writeStringTable($this->stringTable));
+
+ // Add styles to ZIP file
+ $zip->addFile('xl/styles.xml', $this->getWriterPart('Style')->writeStyles($this->spreadSheet));
+
+ // Add workbook to ZIP file
+ $zip->addFile('xl/workbook.xml', $this->getWriterPart('Workbook')->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas));
+
+ $chartCount = 0;
+ // Add worksheets
+ for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
+ $zip->addFile('xl/worksheets/sheet' . ($i + 1) . '.xml', $this->getWriterPart('Worksheet')->writeWorksheet($this->spreadSheet->getSheet($i), $this->stringTable, $this->includeCharts));
+ if ($this->includeCharts) {
+ $charts = $this->spreadSheet->getSheet($i)->getChartCollection();
+ if (count($charts) > 0) {
+ foreach ($charts as $chart) {
+ $zip->addFile('xl/charts/chart' . ($chartCount + 1) . '.xml', $this->getWriterPart('Chart')->writeChart($chart, $this->preCalculateFormulas));
+ ++$chartCount;
+ }
+ }
+ }
+ }
+
+ $chartRef1 = 0;
+ // Add worksheet relationships (drawings, ...)
+ for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
+ // Add relationships
+ $zip->addFile('xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts));
+
+ // Add unparsedLoadedData
+ $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName();
+ $unparsedLoadedData = $this->spreadSheet->getUnparsedLoadedData();
+ if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) {
+ foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) {
+ $zip->addFile($ctrlProp['filePath'], $ctrlProp['content']);
+ }
+ }
+ if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) {
+ foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) {
+ $zip->addFile($ctrlProp['filePath'], $ctrlProp['content']);
+ }
+ }
+
+ $drawings = $this->spreadSheet->getSheet($i)->getDrawingCollection();
+ $drawingCount = count($drawings);
+ if ($this->includeCharts) {
+ $chartCount = $this->spreadSheet->getSheet($i)->getChartCount();
+ }
+
+ // Add drawing and image relationship parts
+ if (($drawingCount > 0) || ($chartCount > 0)) {
+ // Drawing relationships
+ $zip->addFile('xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts));
+
+ // Drawings
+ $zip->addFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts));
+ } elseif (isset($unparsedLoadedData['sheets'][$sheetCodeName]['drawingAlternateContents'])) {
+ // Drawings
+ $zip->addFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts));
+ }
+
+ // Add unparsed drawings
+ if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'])) {
+ foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'] as $relId => $drawingXml) {
+ $drawingFile = array_search($relId, $unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']);
+ if ($drawingFile !== false) {
+ $drawingFile = ltrim($drawingFile, '.');
+ $zip->addFile('xl' . $drawingFile, $drawingXml);
+ }
+ }
+ }
+
+ // Add comment relationship parts
+ if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) {
+ // VML Comments
+ $zip->addFile('xl/drawings/vmlDrawing' . ($i + 1) . '.vml', $this->getWriterPart('Comments')->writeVMLComments($this->spreadSheet->getSheet($i)));
+
+ // Comments
+ $zip->addFile('xl/comments' . ($i + 1) . '.xml', $this->getWriterPart('Comments')->writeComments($this->spreadSheet->getSheet($i)));
+ }
+
+ // Add unparsed relationship parts
+ if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) {
+ foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) {
+ $zip->addFile($vmlDrawing['filePath'], $vmlDrawing['content']);
+ }
+ }
+
+ // Add header/footer relationship parts
+ if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) {
+ // VML Drawings
+ $zip->addFile('xl/drawings/vmlDrawingHF' . ($i + 1) . '.vml', $this->getWriterPart('Drawing')->writeVMLHeaderFooterImages($this->spreadSheet->getSheet($i)));
+
+ // VML Drawing relationships
+ $zip->addFile('xl/drawings/_rels/vmlDrawingHF' . ($i + 1) . '.vml.rels', $this->getWriterPart('Rels')->writeHeaderFooterDrawingRelationships($this->spreadSheet->getSheet($i)));
+
+ // Media
+ foreach ($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages() as $image) {
+ $zip->addFile('xl/media/' . $image->getIndexedFilename(), file_get_contents($image->getPath()));
+ }
+ }
+ }
+
+ // Add media
+ for ($i = 0; $i < $this->getDrawingHashTable()->count(); ++$i) {
+ if ($this->getDrawingHashTable()->getByIndex($i) instanceof WorksheetDrawing) {
+ $imageContents = null;
+ $imagePath = $this->getDrawingHashTable()->getByIndex($i)->getPath();
+ if (strpos($imagePath, 'zip://') !== false) {
+ $imagePath = substr($imagePath, 6);
+ $imagePathSplitted = explode('#', $imagePath);
+
+ $imageZip = new ZipArchive();
+ $imageZip->open($imagePathSplitted[0]);
+ $imageContents = $imageZip->getFromName($imagePathSplitted[1]);
+ $imageZip->close();
+ unset($imageZip);
+ } else {
+ $imageContents = file_get_contents($imagePath);
+ }
+
+ $zip->addFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents);
+ } elseif ($this->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) {
+ ob_start();
+ call_user_func(
+ $this->getDrawingHashTable()->getByIndex($i)->getRenderingFunction(),
+ $this->getDrawingHashTable()->getByIndex($i)->getImageResource()
+ );
+ $imageContents = ob_get_contents();
+ ob_end_clean();
+
+ $zip->addFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents);
+ }
+ }
+
+ Functions::setReturnDateType($saveDateReturnType);
+ Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
+
+ // Close file
+ try {
+ $zip->finish();
+ } catch (OverflowException $e) {
+ throw new WriterException('Could not close resource.');
+ }
+
+ $this->maybeCloseFileHandle();
+ } else {
+ throw new WriterException('PhpSpreadsheet object unassigned.');
+ }
+ }
+
+ /**
+ * Get Spreadsheet object.
+ *
+ * @return Spreadsheet
+ */
+ public function getSpreadsheet()
+ {
+ if ($this->spreadSheet !== null) {
+ return $this->spreadSheet;
+ }
+
+ throw new WriterException('No Spreadsheet object assigned.');
+ }
+
+ /**
+ * Set Spreadsheet object.
+ *
+ * @param Spreadsheet $spreadsheet PhpSpreadsheet object
+ *
+ * @return $this
+ */
+ public function setSpreadsheet(Spreadsheet $spreadsheet)
+ {
+ $this->spreadSheet = $spreadsheet;
+
+ return $this;
+ }
+
+ /**
+ * Get string table.
+ *
+ * @return string[]
+ */
+ public function getStringTable()
+ {
+ return $this->stringTable;
+ }
+
+ /**
+ * Get Style HashTable.
+ *
+ * @return HashTable
+ */
+ public function getStyleHashTable()
+ {
+ return $this->styleHashTable;
+ }
+
+ /**
+ * Get Conditional HashTable.
+ *
+ * @return HashTable
+ */
+ public function getStylesConditionalHashTable()
+ {
+ return $this->stylesConditionalHashTable;
+ }
+
+ /**
+ * Get Fill HashTable.
+ *
+ * @return HashTable
+ */
+ public function getFillHashTable()
+ {
+ return $this->fillHashTable;
+ }
+
+ /**
+ * Get \PhpOffice\PhpSpreadsheet\Style\Font HashTable.
+ *
+ * @return HashTable
+ */
+ public function getFontHashTable()
+ {
+ return $this->fontHashTable;
+ }
+
+ /**
+ * Get Borders HashTable.
+ *
+ * @return HashTable
+ */
+ public function getBordersHashTable()
+ {
+ return $this->bordersHashTable;
+ }
+
+ /**
+ * Get NumberFormat HashTable.
+ *
+ * @return HashTable
+ */
+ public function getNumFmtHashTable()
+ {
+ return $this->numFmtHashTable;
+ }
+
+ /**
+ * Get \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable.
+ *
+ * @return HashTable
+ */
+ public function getDrawingHashTable()
+ {
+ return $this->drawingHashTable;
+ }
+
+ /**
+ * Get Office2003 compatibility.
+ *
+ * @return bool
+ */
+ public function getOffice2003Compatibility()
+ {
+ return $this->office2003compatibility;
+ }
+
+ /**
+ * Set Office2003 compatibility.
+ *
+ * @param bool $pValue Office2003 compatibility?
+ *
+ * @return $this
+ */
+ public function setOffice2003Compatibility($pValue)
+ {
+ $this->office2003compatibility = $pValue;
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
new file mode 100755
index 0000000..583b262
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
@@ -0,0 +1,1516 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Chart\Axis;
+use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
+use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
+use PhpOffice\PhpSpreadsheet\Chart\GridLines;
+use PhpOffice\PhpSpreadsheet\Chart\Layout;
+use PhpOffice\PhpSpreadsheet\Chart\Legend;
+use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
+use PhpOffice\PhpSpreadsheet\Chart\Title;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+class Chart extends WriterPart
+{
+ protected $calculateCellValues;
+
+ /**
+ * @var int
+ */
+ private $seriesIndex;
+
+ /**
+ * Write charts to XML format.
+ *
+ * @param mixed $calculateCellValues
+ *
+ * @return string XML Output
+ */
+ public function writeChart(\PhpOffice\PhpSpreadsheet\Chart\Chart $pChart, $calculateCellValues = true)
+ {
+ $this->calculateCellValues = $calculateCellValues;
+
+ // 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);
+ }
+ // Ensure that data series values are up-to-date before we save
+ if ($this->calculateCellValues) {
+ $pChart->refresh();
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // c:chartSpace
+ $objWriter->startElement('c:chartSpace');
+ $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
+ $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+
+ $objWriter->startElement('c:date1904');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ $objWriter->startElement('c:lang');
+ $objWriter->writeAttribute('val', 'en-GB');
+ $objWriter->endElement();
+ $objWriter->startElement('c:roundedCorners');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $this->writeAlternateContent($objWriter);
+
+ $objWriter->startElement('c:chart');
+
+ $this->writeTitle($objWriter, $pChart->getTitle());
+
+ $objWriter->startElement('c:autoTitleDeleted');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $this->writePlotArea($objWriter, $pChart->getWorksheet(), $pChart->getPlotArea(), $pChart->getXAxisLabel(), $pChart->getYAxisLabel(), $pChart->getChartAxisX(), $pChart->getChartAxisY(), $pChart->getMajorGridlines(), $pChart->getMinorGridlines());
+
+ $this->writeLegend($objWriter, $pChart->getLegend());
+
+ $objWriter->startElement('c:plotVisOnly');
+ $objWriter->writeAttribute('val', (int) $pChart->getPlotVisibleOnly());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:dispBlanksAs');
+ $objWriter->writeAttribute('val', $pChart->getDisplayBlanksAs());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showDLblsOverMax');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $this->writePrintSettings($objWriter);
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write Chart Title.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Title $title
+ */
+ private function writeTitle(XMLWriter $objWriter, ?Title $title = null): void
+ {
+ if ($title === null) {
+ return;
+ }
+
+ $objWriter->startElement('c:title');
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:rich');
+
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+
+ $caption = $title->getCaption();
+ if ((is_array($caption)) && (count($caption) > 0)) {
+ $caption = $caption[0];
+ }
+ $this->getParentWriter()->getWriterPart('stringtable')->writeRichTextForCharts($objWriter, $caption, 'a');
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $this->writeLayout($objWriter, $title->getLayout());
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Chart Legend.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Legend $legend
+ */
+ private function writeLegend(XMLWriter $objWriter, ?Legend $legend = null): void
+ {
+ if ($legend === null) {
+ return;
+ }
+
+ $objWriter->startElement('c:legend');
+
+ $objWriter->startElement('c:legendPos');
+ $objWriter->writeAttribute('val', $legend->getPosition());
+ $objWriter->endElement();
+
+ $this->writeLayout($objWriter, $legend->getLayout());
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', ($legend->getOverlay()) ? '1' : '0');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:txPr');
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+ $objWriter->startElement('a:pPr');
+ $objWriter->writeAttribute('rtl', 0);
+
+ $objWriter->startElement('a:defRPr');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:endParaRPr');
+ $objWriter->writeAttribute('lang', 'en-US');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Chart Plot Area.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Title $xAxisLabel
+ * @param Title $yAxisLabel
+ * @param Axis $xAxis
+ * @param Axis $yAxis
+ */
+ private function writePlotArea(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pSheet, PlotArea $plotArea, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null, ?GridLines $majorGridlines = null, ?GridLines $minorGridlines = null): void
+ {
+ if ($plotArea === null) {
+ return;
+ }
+
+ $id1 = $id2 = 0;
+ $this->seriesIndex = 0;
+ $objWriter->startElement('c:plotArea');
+
+ $layout = $plotArea->getLayout();
+
+ $this->writeLayout($objWriter, $layout);
+
+ $chartTypes = self::getChartType($plotArea);
+ $catIsMultiLevelSeries = $valIsMultiLevelSeries = false;
+ $plotGroupingType = '';
+ foreach ($chartTypes as $chartType) {
+ $objWriter->startElement('c:' . $chartType);
+
+ $groupCount = $plotArea->getPlotGroupCount();
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $plotGroup = $plotArea->getPlotGroupByIndex($i);
+ $groupType = $plotGroup->getPlotType();
+ if ($groupType == $chartType) {
+ $plotStyle = $plotGroup->getPlotStyle();
+ if ($groupType === DataSeries::TYPE_RADARCHART) {
+ $objWriter->startElement('c:radarStyle');
+ $objWriter->writeAttribute('val', $plotStyle);
+ $objWriter->endElement();
+ } elseif ($groupType === DataSeries::TYPE_SCATTERCHART) {
+ $objWriter->startElement('c:scatterStyle');
+ $objWriter->writeAttribute('val', $plotStyle);
+ $objWriter->endElement();
+ }
+
+ $this->writePlotGroup($plotGroup, $chartType, $objWriter, $catIsMultiLevelSeries, $valIsMultiLevelSeries, $plotGroupingType);
+ }
+ }
+
+ $this->writeDataLabels($objWriter, $layout);
+
+ if ($chartType === DataSeries::TYPE_LINECHART) {
+ // Line only, Line3D can't be smoothed
+ $objWriter->startElement('c:smooth');
+ $objWriter->writeAttribute('val', (int) $plotGroup->getSmoothLine());
+ $objWriter->endElement();
+ } elseif (($chartType === DataSeries::TYPE_BARCHART) || ($chartType === DataSeries::TYPE_BARCHART_3D)) {
+ $objWriter->startElement('c:gapWidth');
+ $objWriter->writeAttribute('val', 150);
+ $objWriter->endElement();
+
+ if ($plotGroupingType == 'percentStacked' || $plotGroupingType == 'stacked') {
+ $objWriter->startElement('c:overlap');
+ $objWriter->writeAttribute('val', 100);
+ $objWriter->endElement();
+ }
+ } elseif ($chartType === DataSeries::TYPE_BUBBLECHART) {
+ $objWriter->startElement('c:bubbleScale');
+ $objWriter->writeAttribute('val', 25);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showNegBubbles');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ } elseif ($chartType === DataSeries::TYPE_STOCKCHART) {
+ $objWriter->startElement('c:hiLowLines');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:upDownBars');
+
+ $objWriter->startElement('c:gapWidth');
+ $objWriter->writeAttribute('val', 300);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:upBars');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:downBars');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ // Generate 2 unique numbers to use for axId values
+ $id1 = '75091328';
+ $id2 = '75089408';
+
+ if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id1);
+ $objWriter->endElement();
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:firstSliceAng');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ if ($chartType === DataSeries::TYPE_DONUTCHART) {
+ $objWriter->startElement('c:holeSize');
+ $objWriter->writeAttribute('val', 50);
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
+ if ($chartType === DataSeries::TYPE_BUBBLECHART) {
+ $this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id1, $id2, $catIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines);
+ } else {
+ $this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $yAxis);
+ }
+
+ $this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines);
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Data Labels.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param \PhpOffice\PhpSpreadsheet\Chart\Layout $chartLayout Chart layout
+ */
+ private function writeDataLabels(XMLWriter $objWriter, ?Layout $chartLayout = null): void
+ {
+ $objWriter->startElement('c:dLbls');
+
+ $objWriter->startElement('c:showLegendKey');
+ $showLegendKey = (empty($chartLayout)) ? 0 : $chartLayout->getShowLegendKey();
+ $objWriter->writeAttribute('val', ((empty($showLegendKey)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showVal');
+ $showVal = (empty($chartLayout)) ? 0 : $chartLayout->getShowVal();
+ $objWriter->writeAttribute('val', ((empty($showVal)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showCatName');
+ $showCatName = (empty($chartLayout)) ? 0 : $chartLayout->getShowCatName();
+ $objWriter->writeAttribute('val', ((empty($showCatName)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showSerName');
+ $showSerName = (empty($chartLayout)) ? 0 : $chartLayout->getShowSerName();
+ $objWriter->writeAttribute('val', ((empty($showSerName)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showPercent');
+ $showPercent = (empty($chartLayout)) ? 0 : $chartLayout->getShowPercent();
+ $objWriter->writeAttribute('val', ((empty($showPercent)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showBubbleSize');
+ $showBubbleSize = (empty($chartLayout)) ? 0 : $chartLayout->getShowBubbleSize();
+ $objWriter->writeAttribute('val', ((empty($showBubbleSize)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showLeaderLines');
+ $showLeaderLines = (empty($chartLayout)) ? 1 : $chartLayout->getShowLeaderLines();
+ $objWriter->writeAttribute('val', ((empty($showLeaderLines)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Category Axis.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Title $xAxisLabel
+ * @param string $id1
+ * @param string $id2
+ * @param bool $isMultiLevelSeries
+ */
+ private function writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $isMultiLevelSeries, Axis $yAxis): void
+ {
+ $objWriter->startElement('c:catAx');
+
+ if ($id1 > 0) {
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id1);
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:scaling');
+ $objWriter->startElement('c:orientation');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('orientation'));
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:delete');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:axPos');
+ $objWriter->writeAttribute('val', 'b');
+ $objWriter->endElement();
+
+ if ($xAxisLabel !== null) {
+ $objWriter->startElement('c:title');
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:rich');
+
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+ $objWriter->startElement('a:r');
+
+ $caption = $xAxisLabel->getCaption();
+ if (is_array($caption)) {
+ $caption = $caption[0];
+ }
+ $objWriter->startElement('a:t');
+ $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $layout = $xAxisLabel->getLayout();
+ $this->writeLayout($objWriter, $layout);
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:numFmt');
+ $objWriter->writeAttribute('formatCode', $yAxis->getAxisNumberFormat());
+ $objWriter->writeAttribute('sourceLinked', $yAxis->getAxisNumberSourceLinked());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:majorTickMark');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('major_tick_mark'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:minorTickMark');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minor_tick_mark'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:tickLblPos');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('axis_labels'));
+ $objWriter->endElement();
+
+ if ($id2 > 0) {
+ $objWriter->startElement('c:crossAx');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:crosses');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('horizontal_crosses'));
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:auto');
+ $objWriter->writeAttribute('val', 1);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:lblAlgn');
+ $objWriter->writeAttribute('val', 'ctr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:lblOffset');
+ $objWriter->writeAttribute('val', 100);
+ $objWriter->endElement();
+
+ if ($isMultiLevelSeries) {
+ $objWriter->startElement('c:noMultiLvlLbl');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Value Axis.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Title $yAxisLabel
+ * @param string $groupType Chart type
+ * @param string $id1
+ * @param string $id2
+ * @param bool $isMultiLevelSeries
+ */
+ private function writeValueAxis($objWriter, $yAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries, Axis $xAxis, GridLines $majorGridlines, GridLines $minorGridlines): void
+ {
+ $objWriter->startElement('c:valAx');
+
+ if ($id2 > 0) {
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:scaling');
+
+ if ($xAxis->getAxisOptionsProperty('maximum') !== null) {
+ $objWriter->startElement('c:max');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('maximum'));
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getAxisOptionsProperty('minimum') !== null) {
+ $objWriter->startElement('c:min');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minimum'));
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:orientation');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('orientation'));
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:delete');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:axPos');
+ $objWriter->writeAttribute('val', 'l');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:majorGridlines');
+ $objWriter->startElement('c:spPr');
+
+ if ($majorGridlines->getLineColorProperty('value') !== null) {
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', $majorGridlines->getLineStyleProperty('width'));
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement("a:{$majorGridlines->getLineColorProperty('type')}");
+ $objWriter->writeAttribute('val', $majorGridlines->getLineColorProperty('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $majorGridlines->getLineColorProperty('alpha'));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end srgbClr
+ $objWriter->endElement(); //end solidFill
+
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', $majorGridlines->getLineStyleProperty('dash'));
+ $objWriter->endElement();
+
+ if ($majorGridlines->getLineStyleProperty('join') == 'miter') {
+ $objWriter->startElement('a:miter');
+ $objWriter->writeAttribute('lim', '800000');
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('a:bevel');
+ $objWriter->endElement();
+ }
+
+ if ($majorGridlines->getLineStyleProperty(['arrow', 'head', 'type']) !== null) {
+ $objWriter->startElement('a:headEnd');
+ $objWriter->writeAttribute('type', $majorGridlines->getLineStyleProperty(['arrow', 'head', 'type']));
+ $objWriter->writeAttribute('w', $majorGridlines->getLineStyleArrowParameters('head', 'w'));
+ $objWriter->writeAttribute('len', $majorGridlines->getLineStyleArrowParameters('head', 'len'));
+ $objWriter->endElement();
+ }
+
+ if ($majorGridlines->getLineStyleProperty(['arrow', 'end', 'type']) !== null) {
+ $objWriter->startElement('a:tailEnd');
+ $objWriter->writeAttribute('type', $majorGridlines->getLineStyleProperty(['arrow', 'end', 'type']));
+ $objWriter->writeAttribute('w', $majorGridlines->getLineStyleArrowParameters('end', 'w'));
+ $objWriter->writeAttribute('len', $majorGridlines->getLineStyleArrowParameters('end', 'len'));
+ $objWriter->endElement();
+ }
+ $objWriter->endElement(); //end ln
+ }
+ $objWriter->startElement('a:effectLst');
+
+ if ($majorGridlines->getGlowSize() !== null) {
+ $objWriter->startElement('a:glow');
+ $objWriter->writeAttribute('rad', $majorGridlines->getGlowSize());
+ $objWriter->startElement("a:{$majorGridlines->getGlowColor('type')}");
+ $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('alpha'));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end schemeClr
+ $objWriter->endElement(); //end glow
+ }
+
+ if ($majorGridlines->getShadowProperty('presets') !== null) {
+ $objWriter->startElement("a:{$majorGridlines->getShadowProperty('effect')}");
+ if ($majorGridlines->getShadowProperty('blur') !== null) {
+ $objWriter->writeAttribute('blurRad', $majorGridlines->getShadowProperty('blur'));
+ }
+ if ($majorGridlines->getShadowProperty('distance') !== null) {
+ $objWriter->writeAttribute('dist', $majorGridlines->getShadowProperty('distance'));
+ }
+ if ($majorGridlines->getShadowProperty('direction') !== null) {
+ $objWriter->writeAttribute('dir', $majorGridlines->getShadowProperty('direction'));
+ }
+ if ($majorGridlines->getShadowProperty('algn') !== null) {
+ $objWriter->writeAttribute('algn', $majorGridlines->getShadowProperty('algn'));
+ }
+ if ($majorGridlines->getShadowProperty(['size', 'sx']) !== null) {
+ $objWriter->writeAttribute('sx', $majorGridlines->getShadowProperty(['size', 'sx']));
+ }
+ if ($majorGridlines->getShadowProperty(['size', 'sy']) !== null) {
+ $objWriter->writeAttribute('sy', $majorGridlines->getShadowProperty(['size', 'sy']));
+ }
+ if ($majorGridlines->getShadowProperty(['size', 'kx']) !== null) {
+ $objWriter->writeAttribute('kx', $majorGridlines->getShadowProperty(['size', 'kx']));
+ }
+ if ($majorGridlines->getShadowProperty('rotWithShape') !== null) {
+ $objWriter->writeAttribute('rotWithShape', $majorGridlines->getShadowProperty('rotWithShape'));
+ }
+ $objWriter->startElement("a:{$majorGridlines->getShadowProperty(['color', 'type'])}");
+ $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'value']));
+
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'alpha']));
+ $objWriter->endElement(); //end alpha
+
+ $objWriter->endElement(); //end color:type
+ $objWriter->endElement(); //end shadow
+ }
+
+ if ($majorGridlines->getSoftEdgesSize() !== null) {
+ $objWriter->startElement('a:softEdge');
+ $objWriter->writeAttribute('rad', $majorGridlines->getSoftEdgesSize());
+ $objWriter->endElement(); //end softEdge
+ }
+
+ $objWriter->endElement(); //end effectLst
+ $objWriter->endElement(); //end spPr
+ $objWriter->endElement(); //end majorGridLines
+
+ if ($minorGridlines->getObjectState()) {
+ $objWriter->startElement('c:minorGridlines');
+ $objWriter->startElement('c:spPr');
+
+ if ($minorGridlines->getLineColorProperty('value') !== null) {
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', $minorGridlines->getLineStyleProperty('width'));
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement("a:{$minorGridlines->getLineColorProperty('type')}");
+ $objWriter->writeAttribute('val', $minorGridlines->getLineColorProperty('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $minorGridlines->getLineColorProperty('alpha'));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end srgbClr
+ $objWriter->endElement(); //end solidFill
+
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', $minorGridlines->getLineStyleProperty('dash'));
+ $objWriter->endElement();
+
+ if ($minorGridlines->getLineStyleProperty('join') == 'miter') {
+ $objWriter->startElement('a:miter');
+ $objWriter->writeAttribute('lim', '800000');
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('a:bevel');
+ $objWriter->endElement();
+ }
+
+ if ($minorGridlines->getLineStyleProperty(['arrow', 'head', 'type']) !== null) {
+ $objWriter->startElement('a:headEnd');
+ $objWriter->writeAttribute('type', $minorGridlines->getLineStyleProperty(['arrow', 'head', 'type']));
+ $objWriter->writeAttribute('w', $minorGridlines->getLineStyleArrowParameters('head', 'w'));
+ $objWriter->writeAttribute('len', $minorGridlines->getLineStyleArrowParameters('head', 'len'));
+ $objWriter->endElement();
+ }
+
+ if ($minorGridlines->getLineStyleProperty(['arrow', 'end', 'type']) !== null) {
+ $objWriter->startElement('a:tailEnd');
+ $objWriter->writeAttribute('type', $minorGridlines->getLineStyleProperty(['arrow', 'end', 'type']));
+ $objWriter->writeAttribute('w', $minorGridlines->getLineStyleArrowParameters('end', 'w'));
+ $objWriter->writeAttribute('len', $minorGridlines->getLineStyleArrowParameters('end', 'len'));
+ $objWriter->endElement();
+ }
+ $objWriter->endElement(); //end ln
+ }
+
+ $objWriter->startElement('a:effectLst');
+
+ if ($minorGridlines->getGlowSize() !== null) {
+ $objWriter->startElement('a:glow');
+ $objWriter->writeAttribute('rad', $minorGridlines->getGlowSize());
+ $objWriter->startElement("a:{$minorGridlines->getGlowColor('type')}");
+ $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('alpha'));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end schemeClr
+ $objWriter->endElement(); //end glow
+ }
+
+ if ($minorGridlines->getShadowProperty('presets') !== null) {
+ $objWriter->startElement("a:{$minorGridlines->getShadowProperty('effect')}");
+ if ($minorGridlines->getShadowProperty('blur') !== null) {
+ $objWriter->writeAttribute('blurRad', $minorGridlines->getShadowProperty('blur'));
+ }
+ if ($minorGridlines->getShadowProperty('distance') !== null) {
+ $objWriter->writeAttribute('dist', $minorGridlines->getShadowProperty('distance'));
+ }
+ if ($minorGridlines->getShadowProperty('direction') !== null) {
+ $objWriter->writeAttribute('dir', $minorGridlines->getShadowProperty('direction'));
+ }
+ if ($minorGridlines->getShadowProperty('algn') !== null) {
+ $objWriter->writeAttribute('algn', $minorGridlines->getShadowProperty('algn'));
+ }
+ if ($minorGridlines->getShadowProperty(['size', 'sx']) !== null) {
+ $objWriter->writeAttribute('sx', $minorGridlines->getShadowProperty(['size', 'sx']));
+ }
+ if ($minorGridlines->getShadowProperty(['size', 'sy']) !== null) {
+ $objWriter->writeAttribute('sy', $minorGridlines->getShadowProperty(['size', 'sy']));
+ }
+ if ($minorGridlines->getShadowProperty(['size', 'kx']) !== null) {
+ $objWriter->writeAttribute('kx', $minorGridlines->getShadowProperty(['size', 'kx']));
+ }
+ if ($minorGridlines->getShadowProperty('rotWithShape') !== null) {
+ $objWriter->writeAttribute('rotWithShape', $minorGridlines->getShadowProperty('rotWithShape'));
+ }
+ $objWriter->startElement("a:{$minorGridlines->getShadowProperty(['color', 'type'])}");
+ $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'value']));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'alpha']));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end color:type
+ $objWriter->endElement(); //end shadow
+ }
+
+ if ($minorGridlines->getSoftEdgesSize() !== null) {
+ $objWriter->startElement('a:softEdge');
+ $objWriter->writeAttribute('rad', $minorGridlines->getSoftEdgesSize());
+ $objWriter->endElement(); //end softEdge
+ }
+
+ $objWriter->endElement(); //end effectLst
+ $objWriter->endElement(); //end spPr
+ $objWriter->endElement(); //end minorGridLines
+ }
+
+ if ($yAxisLabel !== null) {
+ $objWriter->startElement('c:title');
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:rich');
+
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+ $objWriter->startElement('a:r');
+
+ $caption = $yAxisLabel->getCaption();
+ if (is_array($caption)) {
+ $caption = $caption[0];
+ }
+
+ $objWriter->startElement('a:t');
+ $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ if ($groupType !== DataSeries::TYPE_BUBBLECHART) {
+ $layout = $yAxisLabel->getLayout();
+ $this->writeLayout($objWriter, $layout);
+ }
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:numFmt');
+ $objWriter->writeAttribute('formatCode', $xAxis->getAxisNumberFormat());
+ $objWriter->writeAttribute('sourceLinked', $xAxis->getAxisNumberSourceLinked());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:majorTickMark');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_tick_mark'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:minorTickMark');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_tick_mark'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:tickLblPos');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('axis_labels'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:spPr');
+
+ if ($xAxis->getFillProperty('value') !== null) {
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:' . $xAxis->getFillProperty('type'));
+ $objWriter->writeAttribute('val', $xAxis->getFillProperty('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $xAxis->getFillProperty('alpha'));
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('a:ln');
+
+ $objWriter->writeAttribute('w', $xAxis->getLineStyleProperty('width'));
+ $objWriter->writeAttribute('cap', $xAxis->getLineStyleProperty('cap'));
+ $objWriter->writeAttribute('cmpd', $xAxis->getLineStyleProperty('compound'));
+
+ if ($xAxis->getLineProperty('value') !== null) {
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:' . $xAxis->getLineProperty('type'));
+ $objWriter->writeAttribute('val', $xAxis->getLineProperty('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $xAxis->getLineProperty('alpha'));
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', $xAxis->getLineStyleProperty('dash'));
+ $objWriter->endElement();
+
+ if ($xAxis->getLineStyleProperty('join') == 'miter') {
+ $objWriter->startElement('a:miter');
+ $objWriter->writeAttribute('lim', '800000');
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('a:bevel');
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getLineStyleProperty(['arrow', 'head', 'type']) !== null) {
+ $objWriter->startElement('a:headEnd');
+ $objWriter->writeAttribute('type', $xAxis->getLineStyleProperty(['arrow', 'head', 'type']));
+ $objWriter->writeAttribute('w', $xAxis->getLineStyleArrowWidth('head'));
+ $objWriter->writeAttribute('len', $xAxis->getLineStyleArrowLength('head'));
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getLineStyleProperty(['arrow', 'end', 'type']) !== null) {
+ $objWriter->startElement('a:tailEnd');
+ $objWriter->writeAttribute('type', $xAxis->getLineStyleProperty(['arrow', 'end', 'type']));
+ $objWriter->writeAttribute('w', $xAxis->getLineStyleArrowWidth('end'));
+ $objWriter->writeAttribute('len', $xAxis->getLineStyleArrowLength('end'));
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:effectLst');
+
+ if ($xAxis->getGlowProperty('size') !== null) {
+ $objWriter->startElement('a:glow');
+ $objWriter->writeAttribute('rad', $xAxis->getGlowProperty('size'));
+ $objWriter->startElement("a:{$xAxis->getGlowProperty(['color', 'type'])}");
+ $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'value']));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'alpha']));
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getShadowProperty('presets') !== null) {
+ $objWriter->startElement("a:{$xAxis->getShadowProperty('effect')}");
+
+ if ($xAxis->getShadowProperty('blur') !== null) {
+ $objWriter->writeAttribute('blurRad', $xAxis->getShadowProperty('blur'));
+ }
+ if ($xAxis->getShadowProperty('distance') !== null) {
+ $objWriter->writeAttribute('dist', $xAxis->getShadowProperty('distance'));
+ }
+ if ($xAxis->getShadowProperty('direction') !== null) {
+ $objWriter->writeAttribute('dir', $xAxis->getShadowProperty('direction'));
+ }
+ if ($xAxis->getShadowProperty('algn') !== null) {
+ $objWriter->writeAttribute('algn', $xAxis->getShadowProperty('algn'));
+ }
+ if ($xAxis->getShadowProperty(['size', 'sx']) !== null) {
+ $objWriter->writeAttribute('sx', $xAxis->getShadowProperty(['size', 'sx']));
+ }
+ if ($xAxis->getShadowProperty(['size', 'sy']) !== null) {
+ $objWriter->writeAttribute('sy', $xAxis->getShadowProperty(['size', 'sy']));
+ }
+ if ($xAxis->getShadowProperty(['size', 'kx']) !== null) {
+ $objWriter->writeAttribute('kx', $xAxis->getShadowProperty(['size', 'kx']));
+ }
+ if ($xAxis->getShadowProperty('rotWithShape') !== null) {
+ $objWriter->writeAttribute('rotWithShape', $xAxis->getShadowProperty('rotWithShape'));
+ }
+
+ $objWriter->startElement("a:{$xAxis->getShadowProperty(['color', 'type'])}");
+ $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'value']));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'alpha']));
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getSoftEdgesSize() !== null) {
+ $objWriter->startElement('a:softEdge');
+ $objWriter->writeAttribute('rad', $xAxis->getSoftEdgesSize());
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement(); //effectList
+ $objWriter->endElement(); //end spPr
+
+ if ($id1 > 0) {
+ $objWriter->startElement('c:crossAx');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+
+ if ($xAxis->getAxisOptionsProperty('horizontal_crosses_value') !== null) {
+ $objWriter->startElement('c:crossesAt');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses_value'));
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:crosses');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses'));
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:crossBetween');
+ $objWriter->writeAttribute('val', 'midCat');
+ $objWriter->endElement();
+
+ if ($xAxis->getAxisOptionsProperty('major_unit') !== null) {
+ $objWriter->startElement('c:majorUnit');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_unit'));
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getAxisOptionsProperty('minor_unit') !== null) {
+ $objWriter->startElement('c:minorUnit');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_unit'));
+ $objWriter->endElement();
+ }
+ }
+
+ if ($isMultiLevelSeries) {
+ if ($groupType !== DataSeries::TYPE_BUBBLECHART) {
+ $objWriter->startElement('c:noMultiLvlLbl');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Get the data series type(s) for a chart plot series.
+ *
+ * @param PlotArea $plotArea
+ *
+ * @return array|string
+ */
+ private static function getChartType($plotArea)
+ {
+ $groupCount = $plotArea->getPlotGroupCount();
+
+ if ($groupCount == 1) {
+ $chartType = [$plotArea->getPlotGroupByIndex(0)->getPlotType()];
+ } else {
+ $chartTypes = [];
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $chartTypes[] = $plotArea->getPlotGroupByIndex($i)->getPlotType();
+ }
+ $chartType = array_unique($chartTypes);
+ if (count($chartTypes) == 0) {
+ throw new WriterException('Chart is not yet implemented');
+ }
+ }
+
+ return $chartType;
+ }
+
+ /**
+ * Method writing plot series values.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $val value for idx (default: 3)
+ * @param string $fillColor hex color (default: FF9900)
+ *
+ * @return XMLWriter XML Writer
+ */
+ private function writePlotSeriesValuesElement($objWriter, $val = 3, $fillColor = 'FF9900')
+ {
+ $objWriter->startElement('c:dPt');
+ $objWriter->startElement('c:idx');
+ $objWriter->writeAttribute('val', $val);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:bubble3D');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:spPr');
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $fillColor);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter;
+ }
+
+ /**
+ * Write Plot Group (series of related plots).
+ *
+ * @param DataSeries $plotGroup
+ * @param string $groupType Type of plot for dataseries
+ * @param XMLWriter $objWriter XML Writer
+ * @param bool &$catIsMultiLevelSeries Is category a multi-series category
+ * @param bool &$valIsMultiLevelSeries Is value set a multi-series set
+ * @param string &$plotGroupingType Type of grouping for multi-series values
+ */
+ private function writePlotGroup($plotGroup, $groupType, $objWriter, &$catIsMultiLevelSeries, &$valIsMultiLevelSeries, &$plotGroupingType): void
+ {
+ if ($plotGroup === null) {
+ return;
+ }
+
+ if (($groupType == DataSeries::TYPE_BARCHART) || ($groupType == DataSeries::TYPE_BARCHART_3D)) {
+ $objWriter->startElement('c:barDir');
+ $objWriter->writeAttribute('val', $plotGroup->getPlotDirection());
+ $objWriter->endElement();
+ }
+
+ if ($plotGroup->getPlotGrouping() !== null) {
+ $plotGroupingType = $plotGroup->getPlotGrouping();
+ $objWriter->startElement('c:grouping');
+ $objWriter->writeAttribute('val', $plotGroupingType);
+ $objWriter->endElement();
+ }
+
+ // Get these details before the loop, because we can use the count to check for varyColors
+ $plotSeriesOrder = $plotGroup->getPlotOrder();
+ $plotSeriesCount = count($plotSeriesOrder);
+
+ if (($groupType !== DataSeries::TYPE_RADARCHART) && ($groupType !== DataSeries::TYPE_STOCKCHART)) {
+ if ($groupType !== DataSeries::TYPE_LINECHART) {
+ if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART) || ($plotSeriesCount > 1)) {
+ $objWriter->startElement('c:varyColors');
+ $objWriter->writeAttribute('val', 1);
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:varyColors');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ foreach ($plotSeriesOrder as $plotSeriesIdx => $plotSeriesRef) {
+ $objWriter->startElement('c:ser');
+
+ $plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx);
+ if ($plotLabel) {
+ $fillColor = $plotLabel->getFillColor();
+ if ($fillColor !== null && !is_array($fillColor)) {
+ $objWriter->startElement('c:spPr');
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $fillColor);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->startElement('c:idx');
+ $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesIdx);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:order');
+ $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesRef);
+ $objWriter->endElement();
+
+ // Values
+ $plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesRef);
+
+ if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) {
+ $fillColorValues = $plotSeriesValues->getFillColor();
+ if ($fillColorValues !== null && is_array($fillColorValues)) {
+ foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) {
+ $this->writePlotSeriesValuesElement($objWriter, $dataKey, ($fillColorValues[$dataKey] ?? 'FF9900'));
+ }
+ } else {
+ $this->writePlotSeriesValuesElement($objWriter);
+ }
+ }
+
+ // Labels
+ $plotSeriesLabel = $plotGroup->getPlotLabelByIndex($plotSeriesRef);
+ if ($plotSeriesLabel && ($plotSeriesLabel->getPointCount() > 0)) {
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:strRef');
+ $this->writePlotSeriesLabel($plotSeriesLabel, $objWriter);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ // Formatting for the points
+ if (($groupType == DataSeries::TYPE_LINECHART) || ($groupType == DataSeries::TYPE_STOCKCHART)) {
+ $plotLineWidth = 12700;
+ if ($plotSeriesValues) {
+ $plotLineWidth = $plotSeriesValues->getLineWidth();
+ }
+
+ $objWriter->startElement('c:spPr');
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', $plotLineWidth);
+ if ($groupType == DataSeries::TYPE_STOCKCHART) {
+ $objWriter->startElement('a:noFill');
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ if ($plotSeriesValues) {
+ $plotSeriesMarker = $plotSeriesValues->getPointMarker();
+ if ($plotSeriesMarker) {
+ $objWriter->startElement('c:marker');
+ $objWriter->startElement('c:symbol');
+ $objWriter->writeAttribute('val', $plotSeriesMarker);
+ $objWriter->endElement();
+
+ if ($plotSeriesMarker !== 'none') {
+ $objWriter->startElement('c:size');
+ $objWriter->writeAttribute('val', 3);
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ if (($groupType === DataSeries::TYPE_BARCHART) || ($groupType === DataSeries::TYPE_BARCHART_3D) || ($groupType === DataSeries::TYPE_BUBBLECHART)) {
+ $objWriter->startElement('c:invertIfNegative');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+
+ // Category Labels
+ $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesRef);
+ if ($plotSeriesCategory && ($plotSeriesCategory->getPointCount() > 0)) {
+ $catIsMultiLevelSeries = $catIsMultiLevelSeries || $plotSeriesCategory->isMultiLevelSeries();
+
+ if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) {
+ if ($plotGroup->getPlotStyle() !== null) {
+ $plotStyle = $plotGroup->getPlotStyle();
+ if ($plotStyle) {
+ $objWriter->startElement('c:explosion');
+ $objWriter->writeAttribute('val', 25);
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) {
+ $objWriter->startElement('c:xVal');
+ } else {
+ $objWriter->startElement('c:cat');
+ }
+
+ $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'str');
+ $objWriter->endElement();
+ }
+
+ // Values
+ if ($plotSeriesValues) {
+ $valIsMultiLevelSeries = $valIsMultiLevelSeries || $plotSeriesValues->isMultiLevelSeries();
+
+ if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) {
+ $objWriter->startElement('c:yVal');
+ } else {
+ $objWriter->startElement('c:val');
+ }
+
+ $this->writePlotSeriesValues($plotSeriesValues, $objWriter, $groupType, 'num');
+ $objWriter->endElement();
+ }
+
+ if ($groupType === DataSeries::TYPE_BUBBLECHART) {
+ $this->writeBubbles($plotSeriesValues, $objWriter);
+ }
+
+ $objWriter->endElement();
+ }
+
+ $this->seriesIndex += $plotSeriesIdx + 1;
+ }
+
+ /**
+ * Write Plot Series Label.
+ *
+ * @param DataSeriesValues $plotSeriesLabel
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writePlotSeriesLabel($plotSeriesLabel, $objWriter): void
+ {
+ if ($plotSeriesLabel === null) {
+ return;
+ }
+
+ $objWriter->startElement('c:f');
+ $objWriter->writeRawData($plotSeriesLabel->getDataSource());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:strCache');
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesLabel->getPointCount());
+ $objWriter->endElement();
+
+ foreach ($plotSeriesLabel->getDataValues() as $plotLabelKey => $plotLabelValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotLabelKey);
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData($plotLabelValue);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Plot Series Values.
+ *
+ * @param DataSeriesValues $plotSeriesValues
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $groupType Type of plot for dataseries
+ * @param string $dataType Datatype of series values
+ */
+ private function writePlotSeriesValues($plotSeriesValues, XMLWriter $objWriter, $groupType, $dataType = 'str'): void
+ {
+ if ($plotSeriesValues === null) {
+ return;
+ }
+
+ if ($plotSeriesValues->isMultiLevelSeries()) {
+ $levelCount = $plotSeriesValues->multiLevelCount();
+
+ $objWriter->startElement('c:multiLvlStrRef');
+
+ $objWriter->startElement('c:f');
+ $objWriter->writeRawData($plotSeriesValues->getDataSource());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:multiLvlStrCache');
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
+ $objWriter->endElement();
+
+ for ($level = 0; $level < $levelCount; ++$level) {
+ $objWriter->startElement('c:lvl');
+
+ foreach ($plotSeriesValues->getDataValues() as $plotSeriesKey => $plotSeriesValue) {
+ if (isset($plotSeriesValue[$level])) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey);
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData($plotSeriesValue[$level]);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:' . $dataType . 'Ref');
+
+ $objWriter->startElement('c:f');
+ $objWriter->writeRawData($plotSeriesValues->getDataSource());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:' . $dataType . 'Cache');
+
+ if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) {
+ if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) {
+ $objWriter->startElement('c:formatCode');
+ $objWriter->writeRawData($plotSeriesValues->getFormatCode());
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
+ $objWriter->endElement();
+
+ $dataValues = $plotSeriesValues->getDataValues();
+ if (!empty($dataValues)) {
+ if (is_array($dataValues)) {
+ foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey);
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData($plotSeriesValue);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Bubble Chart Details.
+ *
+ * @param DataSeriesValues $plotSeriesValues
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeBubbles($plotSeriesValues, $objWriter): void
+ {
+ if ($plotSeriesValues === null) {
+ return;
+ }
+
+ $objWriter->startElement('c:bubbleSize');
+ $objWriter->startElement('c:numLit');
+
+ $objWriter->startElement('c:formatCode');
+ $objWriter->writeRawData('General');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
+ $objWriter->endElement();
+
+ $dataValues = $plotSeriesValues->getDataValues();
+ if (!empty($dataValues)) {
+ if (is_array($dataValues)) {
+ foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey);
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData(1);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:bubble3D');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Layout.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Layout $layout
+ */
+ private function writeLayout(XMLWriter $objWriter, ?Layout $layout = null): void
+ {
+ $objWriter->startElement('c:layout');
+
+ if ($layout !== null) {
+ $objWriter->startElement('c:manualLayout');
+
+ $layoutTarget = $layout->getLayoutTarget();
+ if ($layoutTarget !== null) {
+ $objWriter->startElement('c:layoutTarget');
+ $objWriter->writeAttribute('val', $layoutTarget);
+ $objWriter->endElement();
+ }
+
+ $xMode = $layout->getXMode();
+ if ($xMode !== null) {
+ $objWriter->startElement('c:xMode');
+ $objWriter->writeAttribute('val', $xMode);
+ $objWriter->endElement();
+ }
+
+ $yMode = $layout->getYMode();
+ if ($yMode !== null) {
+ $objWriter->startElement('c:yMode');
+ $objWriter->writeAttribute('val', $yMode);
+ $objWriter->endElement();
+ }
+
+ $x = $layout->getXPosition();
+ if ($x !== null) {
+ $objWriter->startElement('c:x');
+ $objWriter->writeAttribute('val', $x);
+ $objWriter->endElement();
+ }
+
+ $y = $layout->getYPosition();
+ if ($y !== null) {
+ $objWriter->startElement('c:y');
+ $objWriter->writeAttribute('val', $y);
+ $objWriter->endElement();
+ }
+
+ $w = $layout->getWidth();
+ if ($w !== null) {
+ $objWriter->startElement('c:w');
+ $objWriter->writeAttribute('val', $w);
+ $objWriter->endElement();
+ }
+
+ $h = $layout->getHeight();
+ if ($h !== null) {
+ $objWriter->startElement('c:h');
+ $objWriter->writeAttribute('val', $h);
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Alternate Content block.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeAlternateContent($objWriter): void
+ {
+ $objWriter->startElement('mc:AlternateContent');
+ $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006');
+
+ $objWriter->startElement('mc:Choice');
+ $objWriter->writeAttribute('xmlns:c14', 'http://schemas.microsoft.com/office/drawing/2007/8/2/chart');
+ $objWriter->writeAttribute('Requires', 'c14');
+
+ $objWriter->startElement('c14:style');
+ $objWriter->writeAttribute('val', '102');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('mc:Fallback');
+ $objWriter->startElement('c:style');
+ $objWriter->writeAttribute('val', '2');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Printer Settings.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writePrintSettings($objWriter): void
+ {
+ $objWriter->startElement('c:printSettings');
+
+ $objWriter->startElement('c:headerFooter');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:pageMargins');
+ $objWriter->writeAttribute('footer', 0.3);
+ $objWriter->writeAttribute('header', 0.3);
+ $objWriter->writeAttribute('r', 0.7);
+ $objWriter->writeAttribute('l', 0.7);
+ $objWriter->writeAttribute('t', 0.75);
+ $objWriter->writeAttribute('b', 0.75);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:pageSetup');
+ $objWriter->writeAttribute('orientation', 'portrait');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php
new file mode 100755
index 0000000..73c4308
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php
@@ -0,0 +1,232 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Comment;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+
+class Comments extends WriterPart
+{
+ /**
+ * Write comments to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeComments(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
+ {
+ // 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');
+
+ // Comments cache
+ $comments = $pWorksheet->getComments();
+
+ // Authors cache
+ $authors = [];
+ $authorId = 0;
+ foreach ($comments as $comment) {
+ if (!isset($authors[$comment->getAuthor()])) {
+ $authors[$comment->getAuthor()] = $authorId++;
+ }
+ }
+
+ // comments
+ $objWriter->startElement('comments');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+
+ // Loop through authors
+ $objWriter->startElement('authors');
+ foreach ($authors as $author => $index) {
+ $objWriter->writeElement('author', $author);
+ }
+ $objWriter->endElement();
+
+ // Loop through comments
+ $objWriter->startElement('commentList');
+ foreach ($comments as $key => $value) {
+ $this->writeComment($objWriter, $key, $value, $authors);
+ }
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write comment to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pCellReference Cell reference
+ * @param Comment $pComment Comment
+ * @param array $pAuthors Array of authors
+ */
+ private function writeComment(XMLWriter $objWriter, $pCellReference, Comment $pComment, array $pAuthors): void
+ {
+ // comment
+ $objWriter->startElement('comment');
+ $objWriter->writeAttribute('ref', $pCellReference);
+ $objWriter->writeAttribute('authorId', $pAuthors[$pComment->getAuthor()]);
+
+ // text
+ $objWriter->startElement('text');
+ $this->getParentWriter()->getWriterPart('stringtable')->writeRichText($objWriter, $pComment->getText());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write VML comments to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeVMLComments(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
+ {
+ // 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');
+
+ // Comments cache
+ $comments = $pWorksheet->getComments();
+
+ // xml
+ $objWriter->startElement('xml');
+ $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml');
+ $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office');
+ $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel');
+
+ // o:shapelayout
+ $objWriter->startElement('o:shapelayout');
+ $objWriter->writeAttribute('v:ext', 'edit');
+
+ // o:idmap
+ $objWriter->startElement('o:idmap');
+ $objWriter->writeAttribute('v:ext', 'edit');
+ $objWriter->writeAttribute('data', '1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // v:shapetype
+ $objWriter->startElement('v:shapetype');
+ $objWriter->writeAttribute('id', '_x0000_t202');
+ $objWriter->writeAttribute('coordsize', '21600,21600');
+ $objWriter->writeAttribute('o:spt', '202');
+ $objWriter->writeAttribute('path', 'm,l,21600r21600,l21600,xe');
+
+ // v:stroke
+ $objWriter->startElement('v:stroke');
+ $objWriter->writeAttribute('joinstyle', 'miter');
+ $objWriter->endElement();
+
+ // v:path
+ $objWriter->startElement('v:path');
+ $objWriter->writeAttribute('gradientshapeok', 't');
+ $objWriter->writeAttribute('o:connecttype', 'rect');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Loop through comments
+ foreach ($comments as $key => $value) {
+ $this->writeVMLComment($objWriter, $key, $value);
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write VML comment to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pCellReference Cell reference, eg: 'A1'
+ * @param Comment $pComment Comment
+ */
+ private function writeVMLComment(XMLWriter $objWriter, $pCellReference, Comment $pComment): void
+ {
+ // Metadata
+ [$column, $row] = Coordinate::coordinateFromString($pCellReference);
+ $column = Coordinate::columnIndexFromString($column);
+ $id = 1024 + $column + $row;
+ $id = substr($id, 0, 4);
+
+ // v:shape
+ $objWriter->startElement('v:shape');
+ $objWriter->writeAttribute('id', '_x0000_s' . $id);
+ $objWriter->writeAttribute('type', '#_x0000_t202');
+ $objWriter->writeAttribute('style', 'position:absolute;margin-left:' . $pComment->getMarginLeft() . ';margin-top:' . $pComment->getMarginTop() . ';width:' . $pComment->getWidth() . ';height:' . $pComment->getHeight() . ';z-index:1;visibility:' . ($pComment->getVisible() ? 'visible' : 'hidden'));
+ $objWriter->writeAttribute('fillcolor', '#' . $pComment->getFillColor()->getRGB());
+ $objWriter->writeAttribute('o:insetmode', 'auto');
+
+ // v:fill
+ $objWriter->startElement('v:fill');
+ $objWriter->writeAttribute('color2', '#' . $pComment->getFillColor()->getRGB());
+ $objWriter->endElement();
+
+ // v:shadow
+ $objWriter->startElement('v:shadow');
+ $objWriter->writeAttribute('on', 't');
+ $objWriter->writeAttribute('color', 'black');
+ $objWriter->writeAttribute('obscured', 't');
+ $objWriter->endElement();
+
+ // v:path
+ $objWriter->startElement('v:path');
+ $objWriter->writeAttribute('o:connecttype', 'none');
+ $objWriter->endElement();
+
+ // v:textbox
+ $objWriter->startElement('v:textbox');
+ $objWriter->writeAttribute('style', 'mso-direction-alt:auto');
+
+ // div
+ $objWriter->startElement('div');
+ $objWriter->writeAttribute('style', 'text-align:left');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // x:ClientData
+ $objWriter->startElement('x:ClientData');
+ $objWriter->writeAttribute('ObjectType', 'Note');
+
+ // x:MoveWithCells
+ $objWriter->writeElement('x:MoveWithCells', '');
+
+ // x:SizeWithCells
+ $objWriter->writeElement('x:SizeWithCells', '');
+
+ // x:AutoFill
+ $objWriter->writeElement('x:AutoFill', 'False');
+
+ // x:Row
+ $objWriter->writeElement('x:Row', ($row - 1));
+
+ // x:Column
+ $objWriter->writeElement('x:Column', ($column - 1));
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php
new file mode 100755
index 0000000..2cff1a8
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php
@@ -0,0 +1,240 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+class ContentTypes extends WriterPart
+{
+ /**
+ * Write content types to XML format.
+ *
+ * @param bool $includeCharts Flag indicating if we should include drawing details for charts
+ *
+ * @return string XML Output
+ */
+ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = false)
+ {
+ // 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');
+
+ // Types
+ $objWriter->startElement('Types');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/content-types');
+
+ // Theme
+ $this->writeOverrideContentType($objWriter, '/xl/theme/theme1.xml', 'application/vnd.openxmlformats-officedocument.theme+xml');
+
+ // Styles
+ $this->writeOverrideContentType($objWriter, '/xl/styles.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml');
+
+ // Rels
+ $this->writeDefaultContentType($objWriter, 'rels', 'application/vnd.openxmlformats-package.relationships+xml');
+
+ // XML
+ $this->writeDefaultContentType($objWriter, 'xml', 'application/xml');
+
+ // VML
+ $this->writeDefaultContentType($objWriter, 'vml', 'application/vnd.openxmlformats-officedocument.vmlDrawing');
+
+ // Workbook
+ if ($spreadsheet->hasMacros()) { //Macros in workbook ?
+ // Yes : not standard content but "macroEnabled"
+ $this->writeOverrideContentType($objWriter, '/xl/workbook.xml', 'application/vnd.ms-excel.sheet.macroEnabled.main+xml');
+ //... and define a new type for the VBA project
+ // Better use Override, because we can use 'bin' also for xl\printerSettings\printerSettings1.bin
+ $this->writeOverrideContentType($objWriter, '/xl/vbaProject.bin', 'application/vnd.ms-office.vbaProject');
+ if ($spreadsheet->hasMacrosCertificate()) {
+ // signed macros ?
+ // Yes : add needed information
+ $this->writeOverrideContentType($objWriter, '/xl/vbaProjectSignature.bin', 'application/vnd.ms-office.vbaProjectSignature');
+ }
+ } else {
+ // no macros in workbook, so standard type
+ $this->writeOverrideContentType($objWriter, '/xl/workbook.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml');
+ }
+
+ // DocProps
+ $this->writeOverrideContentType($objWriter, '/docProps/app.xml', 'application/vnd.openxmlformats-officedocument.extended-properties+xml');
+
+ $this->writeOverrideContentType($objWriter, '/docProps/core.xml', 'application/vnd.openxmlformats-package.core-properties+xml');
+
+ $customPropertyList = $spreadsheet->getProperties()->getCustomProperties();
+ if (!empty($customPropertyList)) {
+ $this->writeOverrideContentType($objWriter, '/docProps/custom.xml', 'application/vnd.openxmlformats-officedocument.custom-properties+xml');
+ }
+
+ // Worksheets
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $this->writeOverrideContentType($objWriter, '/xl/worksheets/sheet' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml');
+ }
+
+ // Shared strings
+ $this->writeOverrideContentType($objWriter, '/xl/sharedStrings.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml');
+
+ // Add worksheet relationship content types
+ $unparsedLoadedData = $spreadsheet->getUnparsedLoadedData();
+ $chart = 1;
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $drawings = $spreadsheet->getSheet($i)->getDrawingCollection();
+ $drawingCount = count($drawings);
+ $chartCount = ($includeCharts) ? $spreadsheet->getSheet($i)->getChartCount() : 0;
+ $hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$spreadsheet->getSheet($i)->getCodeName()]['drawingOriginalIds']);
+
+ // We need a drawing relationship for the worksheet if we have either drawings or charts
+ if (($drawingCount > 0) || ($chartCount > 0) || $hasUnparsedDrawing) {
+ $this->writeOverrideContentType($objWriter, '/xl/drawings/drawing' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.drawing+xml');
+ }
+
+ // If we have charts, then we need a chart relationship for every individual chart
+ if ($chartCount > 0) {
+ for ($c = 0; $c < $chartCount; ++$c) {
+ $this->writeOverrideContentType($objWriter, '/xl/charts/chart' . $chart++ . '.xml', 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml');
+ }
+ }
+ }
+
+ // Comments
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ if (count($spreadsheet->getSheet($i)->getComments()) > 0) {
+ $this->writeOverrideContentType($objWriter, '/xl/comments' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml');
+ }
+ }
+
+ // Add media content-types
+ $aMediaContentTypes = [];
+ $mediaCount = $this->getParentWriter()->getDrawingHashTable()->count();
+ for ($i = 0; $i < $mediaCount; ++$i) {
+ $extension = '';
+ $mimeType = '';
+
+ if ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing) {
+ $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getExtension());
+ $mimeType = $this->getImageMimeType($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getPath());
+ } elseif ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) {
+ $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType());
+ $extension = explode('/', $extension);
+ $extension = $extension[1];
+
+ $mimeType = $this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType();
+ }
+
+ if (!isset($aMediaContentTypes[$extension])) {
+ $aMediaContentTypes[$extension] = $mimeType;
+
+ $this->writeDefaultContentType($objWriter, $extension, $mimeType);
+ }
+ }
+ if ($spreadsheet->hasRibbonBinObjects()) {
+ // Some additional objects in the ribbon ?
+ // we need to write "Extension" but not already write for media content
+ $tabRibbonTypes = array_diff($spreadsheet->getRibbonBinObjects('types'), array_keys($aMediaContentTypes));
+ foreach ($tabRibbonTypes as $aRibbonType) {
+ $mimeType = 'image/.' . $aRibbonType; //we wrote $mimeType like customUI Editor
+ $this->writeDefaultContentType($objWriter, $aRibbonType, $mimeType);
+ }
+ }
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ if (count($spreadsheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) {
+ foreach ($spreadsheet->getSheet($i)->getHeaderFooter()->getImages() as $image) {
+ if (!isset($aMediaContentTypes[strtolower($image->getExtension())])) {
+ $aMediaContentTypes[strtolower($image->getExtension())] = $this->getImageMimeType($image->getPath());
+
+ $this->writeDefaultContentType($objWriter, strtolower($image->getExtension()), $aMediaContentTypes[strtolower($image->getExtension())]);
+ }
+ }
+ }
+ }
+
+ // unparsed defaults
+ if (isset($unparsedLoadedData['default_content_types'])) {
+ foreach ($unparsedLoadedData['default_content_types'] as $extName => $contentType) {
+ $this->writeDefaultContentType($objWriter, $extName, $contentType);
+ }
+ }
+
+ // unparsed overrides
+ if (isset($unparsedLoadedData['override_content_types'])) {
+ foreach ($unparsedLoadedData['override_content_types'] as $partName => $overrideType) {
+ $this->writeOverrideContentType($objWriter, $partName, $overrideType);
+ }
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Get image mime type.
+ *
+ * @param string $pFile Filename
+ *
+ * @return string Mime Type
+ */
+ private function getImageMimeType($pFile)
+ {
+ if (File::fileExists($pFile)) {
+ $image = getimagesize($pFile);
+
+ return image_type_to_mime_type($image[2]);
+ }
+
+ throw new WriterException("File $pFile does not exist");
+ }
+
+ /**
+ * Write Default content type.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pPartname Part name
+ * @param string $pContentType Content type
+ */
+ private function writeDefaultContentType(XMLWriter $objWriter, $pPartname, $pContentType): void
+ {
+ if ($pPartname != '' && $pContentType != '') {
+ // Write content type
+ $objWriter->startElement('Default');
+ $objWriter->writeAttribute('Extension', $pPartname);
+ $objWriter->writeAttribute('ContentType', $pContentType);
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+
+ /**
+ * Write Override content type.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pPartname Part name
+ * @param string $pContentType Content type
+ */
+ private function writeOverrideContentType(XMLWriter $objWriter, $pPartname, $pContentType): void
+ {
+ if ($pPartname != '' && $pContentType != '') {
+ // Write content type
+ $objWriter->startElement('Override');
+ $objWriter->writeAttribute('PartName', $pPartname);
+ $objWriter->writeAttribute('ContentType', $pContentType);
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php
new file mode 100755
index 0000000..bcbc237
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php
@@ -0,0 +1,239 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class DocProps extends WriterPart
+{
+ /**
+ * Write docProps/app.xml to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeDocPropsApp(Spreadsheet $spreadsheet)
+ {
+ // 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');
+
+ // Properties
+ $objWriter->startElement('Properties');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties');
+ $objWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes');
+
+ // Application
+ $objWriter->writeElement('Application', 'Microsoft Excel');
+
+ // DocSecurity
+ $objWriter->writeElement('DocSecurity', '0');
+
+ // ScaleCrop
+ $objWriter->writeElement('ScaleCrop', 'false');
+
+ // HeadingPairs
+ $objWriter->startElement('HeadingPairs');
+
+ // Vector
+ $objWriter->startElement('vt:vector');
+ $objWriter->writeAttribute('size', '2');
+ $objWriter->writeAttribute('baseType', 'variant');
+
+ // Variant
+ $objWriter->startElement('vt:variant');
+ $objWriter->writeElement('vt:lpstr', 'Worksheets');
+ $objWriter->endElement();
+
+ // Variant
+ $objWriter->startElement('vt:variant');
+ $objWriter->writeElement('vt:i4', $spreadsheet->getSheetCount());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // TitlesOfParts
+ $objWriter->startElement('TitlesOfParts');
+
+ // Vector
+ $objWriter->startElement('vt:vector');
+ $objWriter->writeAttribute('size', $spreadsheet->getSheetCount());
+ $objWriter->writeAttribute('baseType', 'lpstr');
+
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $objWriter->writeElement('vt:lpstr', $spreadsheet->getSheet($i)->getTitle());
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Company
+ $objWriter->writeElement('Company', $spreadsheet->getProperties()->getCompany());
+
+ // Company
+ $objWriter->writeElement('Manager', $spreadsheet->getProperties()->getManager());
+
+ // LinksUpToDate
+ $objWriter->writeElement('LinksUpToDate', 'false');
+
+ // SharedDoc
+ $objWriter->writeElement('SharedDoc', 'false');
+
+ // HyperlinksChanged
+ $objWriter->writeElement('HyperlinksChanged', 'false');
+
+ // AppVersion
+ $objWriter->writeElement('AppVersion', '12.0000');
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write docProps/core.xml to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeDocPropsCore(Spreadsheet $spreadsheet)
+ {
+ // 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');
+
+ // cp:coreProperties
+ $objWriter->startElement('cp:coreProperties');
+ $objWriter->writeAttribute('xmlns:cp', 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties');
+ $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
+ $objWriter->writeAttribute('xmlns:dcterms', 'http://purl.org/dc/terms/');
+ $objWriter->writeAttribute('xmlns:dcmitype', 'http://purl.org/dc/dcmitype/');
+ $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
+
+ // dc:creator
+ $objWriter->writeElement('dc:creator', $spreadsheet->getProperties()->getCreator());
+
+ // cp:lastModifiedBy
+ $objWriter->writeElement('cp:lastModifiedBy', $spreadsheet->getProperties()->getLastModifiedBy());
+
+ // dcterms:created
+ $objWriter->startElement('dcterms:created');
+ $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF');
+ $objWriter->writeRawData(date(DATE_W3C, $spreadsheet->getProperties()->getCreated()));
+ $objWriter->endElement();
+
+ // dcterms:modified
+ $objWriter->startElement('dcterms:modified');
+ $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF');
+ $objWriter->writeRawData(date(DATE_W3C, $spreadsheet->getProperties()->getModified()));
+ $objWriter->endElement();
+
+ // dc:title
+ $objWriter->writeElement('dc:title', $spreadsheet->getProperties()->getTitle());
+
+ // dc:description
+ $objWriter->writeElement('dc:description', $spreadsheet->getProperties()->getDescription());
+
+ // dc:subject
+ $objWriter->writeElement('dc:subject', $spreadsheet->getProperties()->getSubject());
+
+ // cp:keywords
+ $objWriter->writeElement('cp:keywords', $spreadsheet->getProperties()->getKeywords());
+
+ // cp:category
+ $objWriter->writeElement('cp:category', $spreadsheet->getProperties()->getCategory());
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write docProps/custom.xml to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeDocPropsCustom(Spreadsheet $spreadsheet)
+ {
+ $customPropertyList = $spreadsheet->getProperties()->getCustomProperties();
+ if (empty($customPropertyList)) {
+ return;
+ }
+
+ // 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');
+
+ // cp:coreProperties
+ $objWriter->startElement('Properties');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/custom-properties');
+ $objWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes');
+
+ foreach ($customPropertyList as $key => $customProperty) {
+ $propertyValue = $spreadsheet->getProperties()->getCustomPropertyValue($customProperty);
+ $propertyType = $spreadsheet->getProperties()->getCustomPropertyType($customProperty);
+
+ $objWriter->startElement('property');
+ $objWriter->writeAttribute('fmtid', '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}');
+ $objWriter->writeAttribute('pid', $key + 2);
+ $objWriter->writeAttribute('name', $customProperty);
+
+ switch ($propertyType) {
+ case 'i':
+ $objWriter->writeElement('vt:i4', $propertyValue);
+
+ break;
+ case 'f':
+ $objWriter->writeElement('vt:r8', $propertyValue);
+
+ break;
+ case 'b':
+ $objWriter->writeElement('vt:bool', ($propertyValue) ? 'true' : 'false');
+
+ break;
+ case 'd':
+ $objWriter->startElement('vt:filetime');
+ $objWriter->writeRawData(date(DATE_W3C, $propertyValue));
+ $objWriter->endElement();
+
+ break;
+ default:
+ $objWriter->writeElement('vt:lpwstr', $propertyValue);
+
+ break;
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
new file mode 100755
index 0000000..1713b98
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
@@ -0,0 +1,505 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+class Drawing extends WriterPart
+{
+ /**
+ * Write drawings to XML format.
+ *
+ * @param bool $includeCharts Flag indicating if we should include drawing details for charts
+ *
+ * @return string XML Output
+ */
+ public function writeDrawings(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, $includeCharts = false)
+ {
+ // 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');
+
+ // xdr:wsDr
+ $objWriter->startElement('xdr:wsDr');
+ $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
+ $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
+
+ // Loop through images and write drawings
+ $i = 1;
+ $iterator = $pWorksheet->getDrawingCollection()->getIterator();
+ while ($iterator->valid()) {
+ /** @var BaseDrawing $pDrawing */
+ $pDrawing = $iterator->current();
+ $pRelationId = $i;
+ $hlinkClickId = $pDrawing->getHyperlink() === null ? null : ++$i;
+
+ $this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId);
+
+ $iterator->next();
+ ++$i;
+ }
+
+ if ($includeCharts) {
+ $chartCount = $pWorksheet->getChartCount();
+ // Loop through charts and write the chart position
+ if ($chartCount > 0) {
+ for ($c = 0; $c < $chartCount; ++$c) {
+ $this->writeChart($objWriter, $pWorksheet->getChartByIndex($c), $c + $i);
+ }
+ }
+ }
+
+ // unparsed AlternateContent
+ $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
+ if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'])) {
+ foreach ($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'] as $drawingAlternateContent) {
+ $objWriter->writeRaw($drawingAlternateContent);
+ }
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write drawings to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $pRelationId
+ */
+ public function writeChart(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Chart\Chart $pChart, $pRelationId = -1): void
+ {
+ $tl = $pChart->getTopLeftPosition();
+ $tl['colRow'] = Coordinate::coordinateFromString($tl['cell']);
+ $br = $pChart->getBottomRightPosition();
+ $br['colRow'] = Coordinate::coordinateFromString($br['cell']);
+
+ $objWriter->startElement('xdr:twoCellAnchor');
+
+ $objWriter->startElement('xdr:from');
+ $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($tl['colRow'][0]) - 1);
+ $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['xOffset']));
+ $objWriter->writeElement('xdr:row', $tl['colRow'][1] - 1);
+ $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['yOffset']));
+ $objWriter->endElement();
+ $objWriter->startElement('xdr:to');
+ $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($br['colRow'][0]) - 1);
+ $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['xOffset']));
+ $objWriter->writeElement('xdr:row', $br['colRow'][1] - 1);
+ $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['yOffset']));
+ $objWriter->endElement();
+
+ $objWriter->startElement('xdr:graphicFrame');
+ $objWriter->writeAttribute('macro', '');
+ $objWriter->startElement('xdr:nvGraphicFramePr');
+ $objWriter->startElement('xdr:cNvPr');
+ $objWriter->writeAttribute('name', 'Chart ' . $pRelationId);
+ $objWriter->writeAttribute('id', 1025 * $pRelationId);
+ $objWriter->endElement();
+ $objWriter->startElement('xdr:cNvGraphicFramePr');
+ $objWriter->startElement('a:graphicFrameLocks');
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('xdr:xfrm');
+ $objWriter->startElement('a:off');
+ $objWriter->writeAttribute('x', '0');
+ $objWriter->writeAttribute('y', '0');
+ $objWriter->endElement();
+ $objWriter->startElement('a:ext');
+ $objWriter->writeAttribute('cx', '0');
+ $objWriter->writeAttribute('cy', '0');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:graphic');
+ $objWriter->startElement('a:graphicData');
+ $objWriter->writeAttribute('uri', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
+ $objWriter->startElement('c:chart');
+ $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+ $objWriter->writeAttribute('r:id', 'rId' . $pRelationId);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('xdr:clientData');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write drawings to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $pRelationId
+ * @param null|int $hlinkClickId
+ */
+ public function writeDrawing(XMLWriter $objWriter, BaseDrawing $pDrawing, $pRelationId = -1, $hlinkClickId = null): void
+ {
+ if ($pRelationId >= 0) {
+ // xdr:oneCellAnchor
+ $objWriter->startElement('xdr:oneCellAnchor');
+ // Image location
+ $aCoordinates = Coordinate::coordinateFromString($pDrawing->getCoordinates());
+ $aCoordinates[0] = Coordinate::columnIndexFromString($aCoordinates[0]);
+
+ // xdr:from
+ $objWriter->startElement('xdr:from');
+ $objWriter->writeElement('xdr:col', $aCoordinates[0] - 1);
+ $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getOffsetX()));
+ $objWriter->writeElement('xdr:row', $aCoordinates[1] - 1);
+ $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getOffsetY()));
+ $objWriter->endElement();
+
+ // xdr:ext
+ $objWriter->startElement('xdr:ext');
+ $objWriter->writeAttribute('cx', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getWidth()));
+ $objWriter->writeAttribute('cy', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getHeight()));
+ $objWriter->endElement();
+
+ // xdr:pic
+ $objWriter->startElement('xdr:pic');
+
+ // xdr:nvPicPr
+ $objWriter->startElement('xdr:nvPicPr');
+
+ // xdr:cNvPr
+ $objWriter->startElement('xdr:cNvPr');
+ $objWriter->writeAttribute('id', $pRelationId);
+ $objWriter->writeAttribute('name', $pDrawing->getName());
+ $objWriter->writeAttribute('descr', $pDrawing->getDescription());
+
+ //a:hlinkClick
+ $this->writeHyperLinkDrawing($objWriter, $hlinkClickId);
+
+ $objWriter->endElement();
+
+ // xdr:cNvPicPr
+ $objWriter->startElement('xdr:cNvPicPr');
+
+ // a:picLocks
+ $objWriter->startElement('a:picLocks');
+ $objWriter->writeAttribute('noChangeAspect', '1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // xdr:blipFill
+ $objWriter->startElement('xdr:blipFill');
+
+ // a:blip
+ $objWriter->startElement('a:blip');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+ $objWriter->writeAttribute('r:embed', 'rId' . $pRelationId);
+ $objWriter->endElement();
+
+ // a:stretch
+ $objWriter->startElement('a:stretch');
+ $objWriter->writeElement('a:fillRect', null);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // xdr:spPr
+ $objWriter->startElement('xdr:spPr');
+
+ // a:xfrm
+ $objWriter->startElement('a:xfrm');
+ $objWriter->writeAttribute('rot', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($pDrawing->getRotation()));
+ $objWriter->endElement();
+
+ // a:prstGeom
+ $objWriter->startElement('a:prstGeom');
+ $objWriter->writeAttribute('prst', 'rect');
+
+ // a:avLst
+ $objWriter->writeElement('a:avLst', null);
+
+ $objWriter->endElement();
+
+ if ($pDrawing->getShadow()->getVisible()) {
+ // a:effectLst
+ $objWriter->startElement('a:effectLst');
+
+ // a:outerShdw
+ $objWriter->startElement('a:outerShdw');
+ $objWriter->writeAttribute('blurRad', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getShadow()->getBlurRadius()));
+ $objWriter->writeAttribute('dist', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getShadow()->getDistance()));
+ $objWriter->writeAttribute('dir', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($pDrawing->getShadow()->getDirection()));
+ $objWriter->writeAttribute('algn', $pDrawing->getShadow()->getAlignment());
+ $objWriter->writeAttribute('rotWithShape', '0');
+
+ // a:srgbClr
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $pDrawing->getShadow()->getColor()->getRGB());
+
+ // a:alpha
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $pDrawing->getShadow()->getAlpha() * 1000);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // xdr:clientData
+ $objWriter->writeElement('xdr:clientData', null);
+
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+
+ /**
+ * Write VML header/footer images to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeVMLHeaderFooterImages(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
+ {
+ // 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');
+
+ // Header/footer images
+ $images = $pWorksheet->getHeaderFooter()->getImages();
+
+ // xml
+ $objWriter->startElement('xml');
+ $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml');
+ $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office');
+ $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel');
+
+ // o:shapelayout
+ $objWriter->startElement('o:shapelayout');
+ $objWriter->writeAttribute('v:ext', 'edit');
+
+ // o:idmap
+ $objWriter->startElement('o:idmap');
+ $objWriter->writeAttribute('v:ext', 'edit');
+ $objWriter->writeAttribute('data', '1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // v:shapetype
+ $objWriter->startElement('v:shapetype');
+ $objWriter->writeAttribute('id', '_x0000_t75');
+ $objWriter->writeAttribute('coordsize', '21600,21600');
+ $objWriter->writeAttribute('o:spt', '75');
+ $objWriter->writeAttribute('o:preferrelative', 't');
+ $objWriter->writeAttribute('path', 'm@4@5l@4@11@9@11@9@5xe');
+ $objWriter->writeAttribute('filled', 'f');
+ $objWriter->writeAttribute('stroked', 'f');
+
+ // v:stroke
+ $objWriter->startElement('v:stroke');
+ $objWriter->writeAttribute('joinstyle', 'miter');
+ $objWriter->endElement();
+
+ // v:formulas
+ $objWriter->startElement('v:formulas');
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'if lineDrawn pixelLineWidth 0');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum @0 1 0');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum 0 0 @1');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @2 1 2');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @3 21600 pixelWidth');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @3 21600 pixelHeight');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum @0 0 1');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @6 1 2');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @7 21600 pixelWidth');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum @8 21600 0');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @7 21600 pixelHeight');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum @10 21600 0');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // v:path
+ $objWriter->startElement('v:path');
+ $objWriter->writeAttribute('o:extrusionok', 'f');
+ $objWriter->writeAttribute('gradientshapeok', 't');
+ $objWriter->writeAttribute('o:connecttype', 'rect');
+ $objWriter->endElement();
+
+ // o:lock
+ $objWriter->startElement('o:lock');
+ $objWriter->writeAttribute('v:ext', 'edit');
+ $objWriter->writeAttribute('aspectratio', 't');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Loop through images
+ foreach ($images as $key => $value) {
+ $this->writeVMLHeaderFooterImage($objWriter, $key, $value);
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write VML comment to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pReference Reference
+ * @param HeaderFooterDrawing $pImage Image
+ */
+ private function writeVMLHeaderFooterImage(XMLWriter $objWriter, $pReference, HeaderFooterDrawing $pImage): void
+ {
+ // Calculate object id
+ preg_match('{(\d+)}', md5($pReference), $m);
+ $id = 1500 + (substr($m[1], 0, 2) * 1);
+
+ // Calculate offset
+ $width = $pImage->getWidth();
+ $height = $pImage->getHeight();
+ $marginLeft = $pImage->getOffsetX();
+ $marginTop = $pImage->getOffsetY();
+
+ // v:shape
+ $objWriter->startElement('v:shape');
+ $objWriter->writeAttribute('id', $pReference);
+ $objWriter->writeAttribute('o:spid', '_x0000_s' . $id);
+ $objWriter->writeAttribute('type', '#_x0000_t75');
+ $objWriter->writeAttribute('style', "position:absolute;margin-left:{$marginLeft}px;margin-top:{$marginTop}px;width:{$width}px;height:{$height}px;z-index:1");
+
+ // v:imagedata
+ $objWriter->startElement('v:imagedata');
+ $objWriter->writeAttribute('o:relid', 'rId' . $pReference);
+ $objWriter->writeAttribute('o:title', $pImage->getName());
+ $objWriter->endElement();
+
+ // o:lock
+ $objWriter->startElement('o:lock');
+ $objWriter->writeAttribute('v:ext', 'edit');
+ $objWriter->writeAttribute('textRotation', 't');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Get an array of all drawings.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Worksheet\Drawing[] All drawings in PhpSpreadsheet
+ */
+ public function allDrawings(Spreadsheet $spreadsheet)
+ {
+ // Get an array of all drawings
+ $aDrawings = [];
+
+ // Loop through PhpSpreadsheet
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ // Loop through images and add to array
+ $iterator = $spreadsheet->getSheet($i)->getDrawingCollection()->getIterator();
+ while ($iterator->valid()) {
+ $aDrawings[] = $iterator->current();
+
+ $iterator->next();
+ }
+ }
+
+ return $aDrawings;
+ }
+
+ /**
+ * @param null|int $hlinkClickId
+ */
+ private function writeHyperLinkDrawing(XMLWriter $objWriter, $hlinkClickId): void
+ {
+ if ($hlinkClickId === null) {
+ return;
+ }
+
+ $objWriter->startElement('a:hlinkClick');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+ $objWriter->writeAttribute('r:id', 'rId' . $hlinkClickId);
+ $objWriter->endElement();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php
new file mode 100755
index 0000000..a4e5769
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php
@@ -0,0 +1,449 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+class Rels extends WriterPart
+{
+ /**
+ * Write relationships to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeRelationships(Spreadsheet $spreadsheet)
+ {
+ // 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');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ $customPropertyList = $spreadsheet->getProperties()->getCustomProperties();
+ if (!empty($customPropertyList)) {
+ // Relationship docProps/app.xml
+ $this->writeRelationship(
+ $objWriter,
+ 4,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties',
+ 'docProps/custom.xml'
+ );
+ }
+
+ // Relationship docProps/app.xml
+ $this->writeRelationship(
+ $objWriter,
+ 3,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties',
+ 'docProps/app.xml'
+ );
+
+ // Relationship docProps/core.xml
+ $this->writeRelationship(
+ $objWriter,
+ 2,
+ 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties',
+ 'docProps/core.xml'
+ );
+
+ // Relationship xl/workbook.xml
+ $this->writeRelationship(
+ $objWriter,
+ 1,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument',
+ 'xl/workbook.xml'
+ );
+ // a custom UI in workbook ?
+ if ($spreadsheet->hasRibbon()) {
+ $this->writeRelationShip(
+ $objWriter,
+ 5,
+ 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility',
+ $spreadsheet->getRibbonXMLData('target')
+ );
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write workbook relationships to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeWorkbookRelationships(Spreadsheet $spreadsheet)
+ {
+ // 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');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ // Relationship styles.xml
+ $this->writeRelationship(
+ $objWriter,
+ 1,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles',
+ 'styles.xml'
+ );
+
+ // Relationship theme/theme1.xml
+ $this->writeRelationship(
+ $objWriter,
+ 2,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme',
+ 'theme/theme1.xml'
+ );
+
+ // Relationship sharedStrings.xml
+ $this->writeRelationship(
+ $objWriter,
+ 3,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings',
+ 'sharedStrings.xml'
+ );
+
+ // Relationships with sheets
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $this->writeRelationship(
+ $objWriter,
+ ($i + 1 + 3),
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet',
+ 'worksheets/sheet' . ($i + 1) . '.xml'
+ );
+ }
+ // Relationships for vbaProject if needed
+ // id : just after the last sheet
+ if ($spreadsheet->hasMacros()) {
+ $this->writeRelationShip(
+ $objWriter,
+ ($i + 1 + 3),
+ 'http://schemas.microsoft.com/office/2006/relationships/vbaProject',
+ 'vbaProject.bin'
+ );
+ ++$i; //increment i if needed for an another relation
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write worksheet relationships to XML format.
+ *
+ * Numbering is as follows:
+ * rId1 - Drawings
+ * rId_hyperlink_x - Hyperlinks
+ *
+ * @param int $pWorksheetId
+ * @param bool $includeCharts Flag indicating if we should write charts
+ *
+ * @return string XML Output
+ */
+ public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, $pWorksheetId = 1, $includeCharts = false)
+ {
+ // 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');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ // Write drawing relationships?
+ $drawingOriginalIds = [];
+ $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
+ if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'])) {
+ $drawingOriginalIds = $unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'];
+ }
+
+ if ($includeCharts) {
+ $charts = $pWorksheet->getChartCollection();
+ } else {
+ $charts = [];
+ }
+
+ if (($pWorksheet->getDrawingCollection()->count() > 0) || (count($charts) > 0) || $drawingOriginalIds) {
+ $rId = 1;
+
+ // Use original $relPath to get original $rId.
+ // Take first. In future can be overwritten.
+ // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet::writeDrawings)
+ reset($drawingOriginalIds);
+ $relPath = key($drawingOriginalIds);
+ if (isset($drawingOriginalIds[$relPath])) {
+ $rId = (int) (substr($drawingOriginalIds[$relPath], 3));
+ }
+
+ // Generate new $relPath to write drawing relationship
+ $relPath = '../drawings/drawing' . $pWorksheetId . '.xml';
+ $this->writeRelationship(
+ $objWriter,
+ $rId,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing',
+ $relPath
+ );
+ }
+
+ // Write hyperlink relationships?
+ $i = 1;
+ foreach ($pWorksheet->getHyperlinkCollection() as $hyperlink) {
+ if (!$hyperlink->isInternal()) {
+ $this->writeRelationship(
+ $objWriter,
+ '_hyperlink_' . $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
+ $hyperlink->getUrl(),
+ 'External'
+ );
+
+ ++$i;
+ }
+ }
+
+ // Write comments relationship?
+ $i = 1;
+ if (count($pWorksheet->getComments()) > 0) {
+ $this->writeRelationship(
+ $objWriter,
+ '_comments_vml' . $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing',
+ '../drawings/vmlDrawing' . $pWorksheetId . '.vml'
+ );
+
+ $this->writeRelationship(
+ $objWriter,
+ '_comments' . $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments',
+ '../comments' . $pWorksheetId . '.xml'
+ );
+ }
+
+ // Write header/footer relationship?
+ $i = 1;
+ if (count($pWorksheet->getHeaderFooter()->getImages()) > 0) {
+ $this->writeRelationship(
+ $objWriter,
+ '_headerfooter_vml' . $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing',
+ '../drawings/vmlDrawingHF' . $pWorksheetId . '.vml'
+ );
+ }
+
+ $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'ctrlProps', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp');
+ $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'vmlDrawings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing');
+ $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'printerSettings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings');
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ private function writeUnparsedRelationship(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, XMLWriter $objWriter, $relationship, $type): void
+ {
+ $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
+ if (!isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()][$relationship])) {
+ return;
+ }
+
+ foreach ($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()][$relationship] as $rId => $value) {
+ $this->writeRelationship(
+ $objWriter,
+ $rId,
+ $type,
+ $value['relFilePath']
+ );
+ }
+ }
+
+ /**
+ * Write drawing relationships to XML format.
+ *
+ * @param int &$chartRef Chart ID
+ * @param bool $includeCharts Flag indicating if we should write charts
+ *
+ * @return string XML Output
+ */
+ public function writeDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, &$chartRef, $includeCharts = false)
+ {
+ // 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');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ // Loop through images and write relationships
+ $i = 1;
+ $iterator = $pWorksheet->getDrawingCollection()->getIterator();
+ while ($iterator->valid()) {
+ if ($iterator->current() instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing
+ || $iterator->current() instanceof MemoryDrawing) {
+ // Write relationship for image drawing
+ /** @var \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing */
+ $drawing = $iterator->current();
+ $this->writeRelationship(
+ $objWriter,
+ $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
+ '../media/' . str_replace(' ', '', $drawing->getIndexedFilename())
+ );
+
+ $i = $this->writeDrawingHyperLink($objWriter, $drawing, $i);
+ }
+
+ $iterator->next();
+ ++$i;
+ }
+
+ if ($includeCharts) {
+ // Loop through charts and write relationships
+ $chartCount = $pWorksheet->getChartCount();
+ if ($chartCount > 0) {
+ for ($c = 0; $c < $chartCount; ++$c) {
+ $this->writeRelationship(
+ $objWriter,
+ $i++,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart',
+ '../charts/chart' . ++$chartRef . '.xml'
+ );
+ }
+ }
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write header/footer drawing relationships to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeHeaderFooterDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
+ {
+ // 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');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ // Loop through images and write relationships
+ foreach ($pWorksheet->getHeaderFooter()->getImages() as $key => $value) {
+ // Write relationship for image drawing
+ $this->writeRelationship(
+ $objWriter,
+ $key,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
+ '../media/' . $value->getIndexedFilename()
+ );
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write Override content type.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $pId Relationship ID. rId will be prepended!
+ * @param string $pType Relationship type
+ * @param string $pTarget Relationship target
+ * @param string $pTargetMode Relationship target mode
+ */
+ private function writeRelationship(XMLWriter $objWriter, $pId, $pType, $pTarget, $pTargetMode = ''): void
+ {
+ if ($pType != '' && $pTarget != '') {
+ // Write relationship
+ $objWriter->startElement('Relationship');
+ $objWriter->writeAttribute('Id', 'rId' . $pId);
+ $objWriter->writeAttribute('Type', $pType);
+ $objWriter->writeAttribute('Target', $pTarget);
+
+ if ($pTargetMode != '') {
+ $objWriter->writeAttribute('TargetMode', $pTargetMode);
+ }
+
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+
+ /**
+ * @param $objWriter
+ * @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing
+ * @param $i
+ *
+ * @return int
+ */
+ private function writeDrawingHyperLink($objWriter, $drawing, $i)
+ {
+ if ($drawing->getHyperlink() === null) {
+ return $i;
+ }
+
+ ++$i;
+ $this->writeRelationship(
+ $objWriter,
+ $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
+ $drawing->getHyperlink()->getUrl(),
+ $drawing->getHyperlink()->getTypeHyperlink()
+ );
+
+ return $i;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php
new file mode 100755
index 0000000..8005207
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class RelsRibbon extends WriterPart
+{
+ /**
+ * Write relationships for additional objects of custom UI (ribbon).
+ *
+ * @return string XML Output
+ */
+ public function writeRibbonRelationships(Spreadsheet $spreadsheet)
+ {
+ // 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');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+ $localRels = $spreadsheet->getRibbonBinObjects('names');
+ if (is_array($localRels)) {
+ foreach ($localRels as $aId => $aTarget) {
+ $objWriter->startElement('Relationship');
+ $objWriter->writeAttribute('Id', $aId);
+ $objWriter->writeAttribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image');
+ $objWriter->writeAttribute('Target', $aTarget);
+ $objWriter->endElement();
+ }
+ }
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php
new file mode 100755
index 0000000..55bcd36
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class RelsVBA extends WriterPart
+{
+ /**
+ * Write relationships for a signed VBA Project.
+ *
+ * @return string XML Output
+ */
+ public function writeVBARelationships(Spreadsheet $spreadsheet)
+ {
+ // 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');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+ $objWriter->startElement('Relationship');
+ $objWriter->writeAttribute('Id', 'rId1');
+ $objWriter->writeAttribute('Type', 'http://schemas.microsoft.com/office/2006/relationships/vbaProjectSignature');
+ $objWriter->writeAttribute('Target', 'vbaProjectSignature.bin');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
new file mode 100755
index 0000000..872d034
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
@@ -0,0 +1,278 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\RichText\Run;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+
+class StringTable extends WriterPart
+{
+ /**
+ * Create worksheet stringtable.
+ *
+ * @param Worksheet $pSheet Worksheet
+ * @param string[] $pExistingTable Existing table to eventually merge with
+ *
+ * @return string[] String table for worksheet
+ */
+ public function createStringTable(Worksheet $pSheet, $pExistingTable = null)
+ {
+ // Create string lookup table
+ $aStringTable = [];
+ $cellCollection = null;
+ $aFlippedStringTable = null; // For faster lookup
+
+ // Is an existing table given?
+ if (($pExistingTable !== null) && is_array($pExistingTable)) {
+ $aStringTable = $pExistingTable;
+ }
+
+ // Fill index array
+ $aFlippedStringTable = $this->flipStringTable($aStringTable);
+
+ // Loop through cells
+ foreach ($pSheet->getCoordinates() as $coordinate) {
+ $cell = $pSheet->getCell($coordinate);
+ $cellValue = $cell->getValue();
+ if (!is_object($cellValue) &&
+ ($cellValue !== null) &&
+ $cellValue !== '' &&
+ !isset($aFlippedStringTable[$cellValue]) &&
+ ($cell->getDataType() == DataType::TYPE_STRING || $cell->getDataType() == DataType::TYPE_STRING2 || $cell->getDataType() == DataType::TYPE_NULL)) {
+ $aStringTable[] = $cellValue;
+ $aFlippedStringTable[$cellValue] = true;
+ } elseif ($cellValue instanceof RichText &&
+ ($cellValue !== null) &&
+ !isset($aFlippedStringTable[$cellValue->getHashCode()])) {
+ $aStringTable[] = $cellValue;
+ $aFlippedStringTable[$cellValue->getHashCode()] = true;
+ }
+ }
+
+ return $aStringTable;
+ }
+
+ /**
+ * Write string table to XML format.
+ *
+ * @param string[] $pStringTable
+ *
+ * @return string XML Output
+ */
+ public function writeStringTable(array $pStringTable)
+ {
+ // 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');
+
+ // String table
+ $objWriter->startElement('sst');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+ $objWriter->writeAttribute('uniqueCount', count($pStringTable));
+
+ // Loop through string table
+ foreach ($pStringTable as $textElement) {
+ $objWriter->startElement('si');
+
+ if (!$textElement instanceof RichText) {
+ $textToWrite = StringHelper::controlCharacterPHP2OOXML($textElement);
+ $objWriter->startElement('t');
+ if ($textToWrite !== trim($textToWrite)) {
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ }
+ $objWriter->writeRawData($textToWrite);
+ $objWriter->endElement();
+ } elseif ($textElement instanceof RichText) {
+ $this->writeRichText($objWriter, $textElement);
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write Rich Text.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param RichText $pRichText Rich text
+ * @param string $prefix Optional Namespace prefix
+ */
+ public function writeRichText(XMLWriter $objWriter, RichText $pRichText, $prefix = null): void
+ {
+ if ($prefix !== null) {
+ $prefix .= ':';
+ }
+
+ // Loop through rich text elements
+ $elements = $pRichText->getRichTextElements();
+ foreach ($elements as $element) {
+ // r
+ $objWriter->startElement($prefix . 'r');
+
+ // rPr
+ if ($element instanceof Run) {
+ // rPr
+ $objWriter->startElement($prefix . 'rPr');
+
+ // rFont
+ $objWriter->startElement($prefix . 'rFont');
+ $objWriter->writeAttribute('val', $element->getFont()->getName());
+ $objWriter->endElement();
+
+ // Bold
+ $objWriter->startElement($prefix . 'b');
+ $objWriter->writeAttribute('val', ($element->getFont()->getBold() ? 'true' : 'false'));
+ $objWriter->endElement();
+
+ // Italic
+ $objWriter->startElement($prefix . 'i');
+ $objWriter->writeAttribute('val', ($element->getFont()->getItalic() ? 'true' : 'false'));
+ $objWriter->endElement();
+
+ // Superscript / subscript
+ if ($element->getFont()->getSuperscript() || $element->getFont()->getSubscript()) {
+ $objWriter->startElement($prefix . 'vertAlign');
+ if ($element->getFont()->getSuperscript()) {
+ $objWriter->writeAttribute('val', 'superscript');
+ } elseif ($element->getFont()->getSubscript()) {
+ $objWriter->writeAttribute('val', 'subscript');
+ }
+ $objWriter->endElement();
+ }
+
+ // Strikethrough
+ $objWriter->startElement($prefix . 'strike');
+ $objWriter->writeAttribute('val', ($element->getFont()->getStrikethrough() ? 'true' : 'false'));
+ $objWriter->endElement();
+
+ // Color
+ $objWriter->startElement($prefix . 'color');
+ $objWriter->writeAttribute('rgb', $element->getFont()->getColor()->getARGB());
+ $objWriter->endElement();
+
+ // Size
+ $objWriter->startElement($prefix . 'sz');
+ $objWriter->writeAttribute('val', $element->getFont()->getSize());
+ $objWriter->endElement();
+
+ // Underline
+ $objWriter->startElement($prefix . 'u');
+ $objWriter->writeAttribute('val', $element->getFont()->getUnderline());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ // t
+ $objWriter->startElement($prefix . 't');
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText()));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Rich Text.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param RichText|string $pRichText text string or Rich text
+ * @param string $prefix Optional Namespace prefix
+ */
+ public function writeRichTextForCharts(XMLWriter $objWriter, $pRichText = null, $prefix = null): void
+ {
+ if (!$pRichText instanceof RichText) {
+ $textRun = $pRichText;
+ $pRichText = new RichText();
+ $pRichText->createTextRun($textRun);
+ }
+
+ if ($prefix !== null) {
+ $prefix .= ':';
+ }
+
+ // Loop through rich text elements
+ $elements = $pRichText->getRichTextElements();
+ foreach ($elements as $element) {
+ // r
+ $objWriter->startElement($prefix . 'r');
+
+ // rPr
+ $objWriter->startElement($prefix . 'rPr');
+
+ // Bold
+ $objWriter->writeAttribute('b', ($element->getFont()->getBold() ? 1 : 0));
+ // Italic
+ $objWriter->writeAttribute('i', ($element->getFont()->getItalic() ? 1 : 0));
+ // Underline
+ $underlineType = $element->getFont()->getUnderline();
+ switch ($underlineType) {
+ case 'single':
+ $underlineType = 'sng';
+
+ break;
+ case 'double':
+ $underlineType = 'dbl';
+
+ break;
+ }
+ $objWriter->writeAttribute('u', $underlineType);
+ // Strikethrough
+ $objWriter->writeAttribute('strike', ($element->getFont()->getStrikethrough() ? 'sngStrike' : 'noStrike'));
+
+ // rFont
+ $objWriter->startElement($prefix . 'latin');
+ $objWriter->writeAttribute('typeface', $element->getFont()->getName());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // t
+ $objWriter->startElement($prefix . 't');
+ $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText()));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Flip string table (for index searching).
+ *
+ * @param array $stringTable Stringtable
+ *
+ * @return array
+ */
+ public function flipStringTable(array $stringTable)
+ {
+ // Return value
+ $returnValue = [];
+
+ // Loop through stringtable and add flipped items to $returnValue
+ foreach ($stringTable as $key => $value) {
+ if (!$value instanceof RichText) {
+ $returnValue[$value] = $key;
+ } elseif ($value instanceof RichText) {
+ $returnValue[$value->getHashCode()] = $key;
+ }
+ }
+
+ return $returnValue;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php
new file mode 100755
index 0000000..74300a9
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php
@@ -0,0 +1,668 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Borders;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PhpOffice\PhpSpreadsheet\Style\Protection;
+
+class Style extends WriterPart
+{
+ /**
+ * Write styles to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeStyles(Spreadsheet $spreadsheet)
+ {
+ // 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');
+
+ // styleSheet
+ $objWriter->startElement('styleSheet');
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+
+ // numFmts
+ $objWriter->startElement('numFmts');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getNumFmtHashTable()->count());
+
+ // numFmt
+ for ($i = 0; $i < $this->getParentWriter()->getNumFmtHashTable()->count(); ++$i) {
+ $this->writeNumFmt($objWriter, $this->getParentWriter()->getNumFmtHashTable()->getByIndex($i), $i);
+ }
+
+ $objWriter->endElement();
+
+ // fonts
+ $objWriter->startElement('fonts');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getFontHashTable()->count());
+
+ // font
+ for ($i = 0; $i < $this->getParentWriter()->getFontHashTable()->count(); ++$i) {
+ $this->writeFont($objWriter, $this->getParentWriter()->getFontHashTable()->getByIndex($i));
+ }
+
+ $objWriter->endElement();
+
+ // fills
+ $objWriter->startElement('fills');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getFillHashTable()->count());
+
+ // fill
+ for ($i = 0; $i < $this->getParentWriter()->getFillHashTable()->count(); ++$i) {
+ $this->writeFill($objWriter, $this->getParentWriter()->getFillHashTable()->getByIndex($i));
+ }
+
+ $objWriter->endElement();
+
+ // borders
+ $objWriter->startElement('borders');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getBordersHashTable()->count());
+
+ // border
+ for ($i = 0; $i < $this->getParentWriter()->getBordersHashTable()->count(); ++$i) {
+ $this->writeBorder($objWriter, $this->getParentWriter()->getBordersHashTable()->getByIndex($i));
+ }
+
+ $objWriter->endElement();
+
+ // cellStyleXfs
+ $objWriter->startElement('cellStyleXfs');
+ $objWriter->writeAttribute('count', 1);
+
+ // xf
+ $objWriter->startElement('xf');
+ $objWriter->writeAttribute('numFmtId', 0);
+ $objWriter->writeAttribute('fontId', 0);
+ $objWriter->writeAttribute('fillId', 0);
+ $objWriter->writeAttribute('borderId', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // cellXfs
+ $objWriter->startElement('cellXfs');
+ $objWriter->writeAttribute('count', count($spreadsheet->getCellXfCollection()));
+
+ // xf
+ foreach ($spreadsheet->getCellXfCollection() as $cellXf) {
+ $this->writeCellStyleXf($objWriter, $cellXf, $spreadsheet);
+ }
+
+ $objWriter->endElement();
+
+ // cellStyles
+ $objWriter->startElement('cellStyles');
+ $objWriter->writeAttribute('count', 1);
+
+ // cellStyle
+ $objWriter->startElement('cellStyle');
+ $objWriter->writeAttribute('name', 'Normal');
+ $objWriter->writeAttribute('xfId', 0);
+ $objWriter->writeAttribute('builtinId', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // dxfs
+ $objWriter->startElement('dxfs');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getStylesConditionalHashTable()->count());
+
+ // dxf
+ for ($i = 0; $i < $this->getParentWriter()->getStylesConditionalHashTable()->count(); ++$i) {
+ $this->writeCellStyleDxf($objWriter, $this->getParentWriter()->getStylesConditionalHashTable()->getByIndex($i)->getStyle());
+ }
+
+ $objWriter->endElement();
+
+ // tableStyles
+ $objWriter->startElement('tableStyles');
+ $objWriter->writeAttribute('defaultTableStyle', 'TableStyleMedium9');
+ $objWriter->writeAttribute('defaultPivotStyle', 'PivotTableStyle1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write Fill.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Fill $pFill Fill style
+ */
+ private function writeFill(XMLWriter $objWriter, Fill $pFill): void
+ {
+ // Check if this is a pattern type or gradient type
+ if ($pFill->getFillType() === Fill::FILL_GRADIENT_LINEAR ||
+ $pFill->getFillType() === Fill::FILL_GRADIENT_PATH) {
+ // Gradient fill
+ $this->writeGradientFill($objWriter, $pFill);
+ } elseif ($pFill->getFillType() !== null) {
+ // Pattern fill
+ $this->writePatternFill($objWriter, $pFill);
+ }
+ }
+
+ /**
+ * Write Gradient Fill.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Fill $pFill Fill style
+ */
+ private function writeGradientFill(XMLWriter $objWriter, Fill $pFill): void
+ {
+ // fill
+ $objWriter->startElement('fill');
+
+ // gradientFill
+ $objWriter->startElement('gradientFill');
+ $objWriter->writeAttribute('type', $pFill->getFillType());
+ $objWriter->writeAttribute('degree', $pFill->getRotation());
+
+ // stop
+ $objWriter->startElement('stop');
+ $objWriter->writeAttribute('position', '0');
+
+ // color
+ $objWriter->startElement('color');
+ $objWriter->writeAttribute('rgb', $pFill->getStartColor()->getARGB());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // stop
+ $objWriter->startElement('stop');
+ $objWriter->writeAttribute('position', '1');
+
+ // color
+ $objWriter->startElement('color');
+ $objWriter->writeAttribute('rgb', $pFill->getEndColor()->getARGB());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Pattern Fill.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Fill $pFill Fill style
+ */
+ private function writePatternFill(XMLWriter $objWriter, Fill $pFill): void
+ {
+ // fill
+ $objWriter->startElement('fill');
+
+ // patternFill
+ $objWriter->startElement('patternFill');
+ $objWriter->writeAttribute('patternType', $pFill->getFillType());
+
+ if ($pFill->getFillType() !== Fill::FILL_NONE) {
+ // fgColor
+ if ($pFill->getStartColor()->getARGB()) {
+ $objWriter->startElement('fgColor');
+ $objWriter->writeAttribute('rgb', $pFill->getStartColor()->getARGB());
+ $objWriter->endElement();
+ }
+ }
+ if ($pFill->getFillType() !== Fill::FILL_NONE) {
+ // bgColor
+ if ($pFill->getEndColor()->getARGB()) {
+ $objWriter->startElement('bgColor');
+ $objWriter->writeAttribute('rgb', $pFill->getEndColor()->getARGB());
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Font.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Font $pFont Font style
+ */
+ private function writeFont(XMLWriter $objWriter, Font $pFont): void
+ {
+ // font
+ $objWriter->startElement('font');
+ // Weird! The order of these elements actually makes a difference when opening Xlsx
+ // files in Excel2003 with the compatibility pack. It's not documented behaviour,
+ // and makes for a real WTF!
+
+ // Bold. We explicitly write this element also when false (like MS Office Excel 2007 does
+ // for conditional formatting). Otherwise it will apparently not be picked up in conditional
+ // formatting style dialog
+ if ($pFont->getBold() !== null) {
+ $objWriter->startElement('b');
+ $objWriter->writeAttribute('val', $pFont->getBold() ? '1' : '0');
+ $objWriter->endElement();
+ }
+
+ // Italic
+ if ($pFont->getItalic() !== null) {
+ $objWriter->startElement('i');
+ $objWriter->writeAttribute('val', $pFont->getItalic() ? '1' : '0');
+ $objWriter->endElement();
+ }
+
+ // Strikethrough
+ if ($pFont->getStrikethrough() !== null) {
+ $objWriter->startElement('strike');
+ $objWriter->writeAttribute('val', $pFont->getStrikethrough() ? '1' : '0');
+ $objWriter->endElement();
+ }
+
+ // Underline
+ if ($pFont->getUnderline() !== null) {
+ $objWriter->startElement('u');
+ $objWriter->writeAttribute('val', $pFont->getUnderline());
+ $objWriter->endElement();
+ }
+
+ // Superscript / subscript
+ if ($pFont->getSuperscript() === true || $pFont->getSubscript() === true) {
+ $objWriter->startElement('vertAlign');
+ if ($pFont->getSuperscript() === true) {
+ $objWriter->writeAttribute('val', 'superscript');
+ } elseif ($pFont->getSubscript() === true) {
+ $objWriter->writeAttribute('val', 'subscript');
+ }
+ $objWriter->endElement();
+ }
+
+ // Size
+ if ($pFont->getSize() !== null) {
+ $objWriter->startElement('sz');
+ $objWriter->writeAttribute('val', StringHelper::formatNumber($pFont->getSize()));
+ $objWriter->endElement();
+ }
+
+ // Foreground color
+ if ($pFont->getColor()->getARGB() !== null) {
+ $objWriter->startElement('color');
+ $objWriter->writeAttribute('rgb', $pFont->getColor()->getARGB());
+ $objWriter->endElement();
+ }
+
+ // Name
+ if ($pFont->getName() !== null) {
+ $objWriter->startElement('name');
+ $objWriter->writeAttribute('val', $pFont->getName());
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Border.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Borders $pBorders Borders style
+ */
+ private function writeBorder(XMLWriter $objWriter, Borders $pBorders): void
+ {
+ // Write border
+ $objWriter->startElement('border');
+ // Diagonal?
+ switch ($pBorders->getDiagonalDirection()) {
+ case Borders::DIAGONAL_UP:
+ $objWriter->writeAttribute('diagonalUp', 'true');
+ $objWriter->writeAttribute('diagonalDown', 'false');
+
+ break;
+ case Borders::DIAGONAL_DOWN:
+ $objWriter->writeAttribute('diagonalUp', 'false');
+ $objWriter->writeAttribute('diagonalDown', 'true');
+
+ break;
+ case Borders::DIAGONAL_BOTH:
+ $objWriter->writeAttribute('diagonalUp', 'true');
+ $objWriter->writeAttribute('diagonalDown', 'true');
+
+ break;
+ }
+
+ // BorderPr
+ $this->writeBorderPr($objWriter, 'left', $pBorders->getLeft());
+ $this->writeBorderPr($objWriter, 'right', $pBorders->getRight());
+ $this->writeBorderPr($objWriter, 'top', $pBorders->getTop());
+ $this->writeBorderPr($objWriter, 'bottom', $pBorders->getBottom());
+ $this->writeBorderPr($objWriter, 'diagonal', $pBorders->getDiagonal());
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Cell Style Xf.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param \PhpOffice\PhpSpreadsheet\Style\Style $pStyle Style
+ * @param Spreadsheet $spreadsheet Workbook
+ */
+ private function writeCellStyleXf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $pStyle, Spreadsheet $spreadsheet): void
+ {
+ // xf
+ $objWriter->startElement('xf');
+ $objWriter->writeAttribute('xfId', 0);
+ $objWriter->writeAttribute('fontId', (int) $this->getParentWriter()->getFontHashTable()->getIndexForHashCode($pStyle->getFont()->getHashCode()));
+ if ($pStyle->getQuotePrefix()) {
+ $objWriter->writeAttribute('quotePrefix', 1);
+ }
+
+ if ($pStyle->getNumberFormat()->getBuiltInFormatCode() === false) {
+ $objWriter->writeAttribute('numFmtId', (int) ($this->getParentWriter()->getNumFmtHashTable()->getIndexForHashCode($pStyle->getNumberFormat()->getHashCode()) + 164));
+ } else {
+ $objWriter->writeAttribute('numFmtId', (int) $pStyle->getNumberFormat()->getBuiltInFormatCode());
+ }
+
+ $objWriter->writeAttribute('fillId', (int) $this->getParentWriter()->getFillHashTable()->getIndexForHashCode($pStyle->getFill()->getHashCode()));
+ $objWriter->writeAttribute('borderId', (int) $this->getParentWriter()->getBordersHashTable()->getIndexForHashCode($pStyle->getBorders()->getHashCode()));
+
+ // Apply styles?
+ $objWriter->writeAttribute('applyFont', ($spreadsheet->getDefaultStyle()->getFont()->getHashCode() != $pStyle->getFont()->getHashCode()) ? '1' : '0');
+ $objWriter->writeAttribute('applyNumberFormat', ($spreadsheet->getDefaultStyle()->getNumberFormat()->getHashCode() != $pStyle->getNumberFormat()->getHashCode()) ? '1' : '0');
+ $objWriter->writeAttribute('applyFill', ($spreadsheet->getDefaultStyle()->getFill()->getHashCode() != $pStyle->getFill()->getHashCode()) ? '1' : '0');
+ $objWriter->writeAttribute('applyBorder', ($spreadsheet->getDefaultStyle()->getBorders()->getHashCode() != $pStyle->getBorders()->getHashCode()) ? '1' : '0');
+ $objWriter->writeAttribute('applyAlignment', ($spreadsheet->getDefaultStyle()->getAlignment()->getHashCode() != $pStyle->getAlignment()->getHashCode()) ? '1' : '0');
+ if ($pStyle->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $pStyle->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) {
+ $objWriter->writeAttribute('applyProtection', 'true');
+ }
+
+ // alignment
+ $objWriter->startElement('alignment');
+ $objWriter->writeAttribute('horizontal', $pStyle->getAlignment()->getHorizontal());
+ $objWriter->writeAttribute('vertical', $pStyle->getAlignment()->getVertical());
+
+ $textRotation = 0;
+ if ($pStyle->getAlignment()->getTextRotation() >= 0) {
+ $textRotation = $pStyle->getAlignment()->getTextRotation();
+ } elseif ($pStyle->getAlignment()->getTextRotation() < 0) {
+ $textRotation = 90 - $pStyle->getAlignment()->getTextRotation();
+ }
+ $objWriter->writeAttribute('textRotation', $textRotation);
+
+ $objWriter->writeAttribute('wrapText', ($pStyle->getAlignment()->getWrapText() ? 'true' : 'false'));
+ $objWriter->writeAttribute('shrinkToFit', ($pStyle->getAlignment()->getShrinkToFit() ? 'true' : 'false'));
+
+ if ($pStyle->getAlignment()->getIndent() > 0) {
+ $objWriter->writeAttribute('indent', $pStyle->getAlignment()->getIndent());
+ }
+ if ($pStyle->getAlignment()->getReadOrder() > 0) {
+ $objWriter->writeAttribute('readingOrder', $pStyle->getAlignment()->getReadOrder());
+ }
+ $objWriter->endElement();
+
+ // protection
+ if ($pStyle->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $pStyle->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) {
+ $objWriter->startElement('protection');
+ if ($pStyle->getProtection()->getLocked() != Protection::PROTECTION_INHERIT) {
+ $objWriter->writeAttribute('locked', ($pStyle->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED ? 'true' : 'false'));
+ }
+ if ($pStyle->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) {
+ $objWriter->writeAttribute('hidden', ($pStyle->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED ? 'true' : 'false'));
+ }
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Cell Style Dxf.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param \PhpOffice\PhpSpreadsheet\Style\Style $pStyle Style
+ */
+ private function writeCellStyleDxf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $pStyle): void
+ {
+ // dxf
+ $objWriter->startElement('dxf');
+
+ // font
+ $this->writeFont($objWriter, $pStyle->getFont());
+
+ // numFmt
+ $this->writeNumFmt($objWriter, $pStyle->getNumberFormat());
+
+ // fill
+ $this->writeFill($objWriter, $pStyle->getFill());
+
+ // alignment
+ $objWriter->startElement('alignment');
+ if ($pStyle->getAlignment()->getHorizontal() !== null) {
+ $objWriter->writeAttribute('horizontal', $pStyle->getAlignment()->getHorizontal());
+ }
+ if ($pStyle->getAlignment()->getVertical() !== null) {
+ $objWriter->writeAttribute('vertical', $pStyle->getAlignment()->getVertical());
+ }
+
+ if ($pStyle->getAlignment()->getTextRotation() !== null) {
+ $textRotation = 0;
+ if ($pStyle->getAlignment()->getTextRotation() >= 0) {
+ $textRotation = $pStyle->getAlignment()->getTextRotation();
+ } elseif ($pStyle->getAlignment()->getTextRotation() < 0) {
+ $textRotation = 90 - $pStyle->getAlignment()->getTextRotation();
+ }
+ $objWriter->writeAttribute('textRotation', $textRotation);
+ }
+ $objWriter->endElement();
+
+ // border
+ $this->writeBorder($objWriter, $pStyle->getBorders());
+
+ // protection
+ if (($pStyle->getProtection()->getLocked() !== null) || ($pStyle->getProtection()->getHidden() !== null)) {
+ if ($pStyle->getProtection()->getLocked() !== Protection::PROTECTION_INHERIT ||
+ $pStyle->getProtection()->getHidden() !== Protection::PROTECTION_INHERIT) {
+ $objWriter->startElement('protection');
+ if (($pStyle->getProtection()->getLocked() !== null) &&
+ ($pStyle->getProtection()->getLocked() !== Protection::PROTECTION_INHERIT)) {
+ $objWriter->writeAttribute('locked', ($pStyle->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED ? 'true' : 'false'));
+ }
+ if (($pStyle->getProtection()->getHidden() !== null) &&
+ ($pStyle->getProtection()->getHidden() !== Protection::PROTECTION_INHERIT)) {
+ $objWriter->writeAttribute('hidden', ($pStyle->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED ? 'true' : 'false'));
+ }
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write BorderPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pName Element name
+ * @param Border $pBorder Border style
+ */
+ private function writeBorderPr(XMLWriter $objWriter, $pName, Border $pBorder): void
+ {
+ // Write BorderPr
+ if ($pBorder->getBorderStyle() != Border::BORDER_NONE) {
+ $objWriter->startElement($pName);
+ $objWriter->writeAttribute('style', $pBorder->getBorderStyle());
+
+ // color
+ $objWriter->startElement('color');
+ $objWriter->writeAttribute('rgb', $pBorder->getColor()->getARGB());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write NumberFormat.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param NumberFormat $pNumberFormat Number Format
+ * @param int $pId Number Format identifier
+ */
+ private function writeNumFmt(XMLWriter $objWriter, NumberFormat $pNumberFormat, $pId = 0): void
+ {
+ // Translate formatcode
+ $formatCode = $pNumberFormat->getFormatCode();
+
+ // numFmt
+ if ($formatCode !== null) {
+ $objWriter->startElement('numFmt');
+ $objWriter->writeAttribute('numFmtId', ($pId + 164));
+ $objWriter->writeAttribute('formatCode', $formatCode);
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Get an array of all styles.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Style\Style[] All styles in PhpSpreadsheet
+ */
+ public function allStyles(Spreadsheet $spreadsheet)
+ {
+ return $spreadsheet->getCellXfCollection();
+ }
+
+ /**
+ * Get an array of all conditional styles.
+ *
+ * @return Conditional[] All conditional styles in PhpSpreadsheet
+ */
+ public function allConditionalStyles(Spreadsheet $spreadsheet)
+ {
+ // Get an array of all styles
+ $aStyles = [];
+
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ foreach ($spreadsheet->getSheet($i)->getConditionalStylesCollection() as $conditionalStyles) {
+ foreach ($conditionalStyles as $conditionalStyle) {
+ $aStyles[] = $conditionalStyle;
+ }
+ }
+ }
+
+ return $aStyles;
+ }
+
+ /**
+ * Get an array of all fills.
+ *
+ * @return Fill[] All fills in PhpSpreadsheet
+ */
+ public function allFills(Spreadsheet $spreadsheet)
+ {
+ // Get an array of unique fills
+ $aFills = [];
+
+ // Two first fills are predefined
+ $fill0 = new Fill();
+ $fill0->setFillType(Fill::FILL_NONE);
+ $aFills[] = $fill0;
+
+ $fill1 = new Fill();
+ $fill1->setFillType(Fill::FILL_PATTERN_GRAY125);
+ $aFills[] = $fill1;
+ // The remaining fills
+ $aStyles = $this->allStyles($spreadsheet);
+ /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */
+ foreach ($aStyles as $style) {
+ if (!isset($aFills[$style->getFill()->getHashCode()])) {
+ $aFills[$style->getFill()->getHashCode()] = $style->getFill();
+ }
+ }
+
+ return $aFills;
+ }
+
+ /**
+ * Get an array of all fonts.
+ *
+ * @return Font[] All fonts in PhpSpreadsheet
+ */
+ public function allFonts(Spreadsheet $spreadsheet)
+ {
+ // Get an array of unique fonts
+ $aFonts = [];
+ $aStyles = $this->allStyles($spreadsheet);
+
+ /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */
+ foreach ($aStyles as $style) {
+ if (!isset($aFonts[$style->getFont()->getHashCode()])) {
+ $aFonts[$style->getFont()->getHashCode()] = $style->getFont();
+ }
+ }
+
+ return $aFonts;
+ }
+
+ /**
+ * Get an array of all borders.
+ *
+ * @return Borders[] All borders in PhpSpreadsheet
+ */
+ public function allBorders(Spreadsheet $spreadsheet)
+ {
+ // Get an array of unique borders
+ $aBorders = [];
+ $aStyles = $this->allStyles($spreadsheet);
+
+ /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */
+ foreach ($aStyles as $style) {
+ if (!isset($aBorders[$style->getBorders()->getHashCode()])) {
+ $aBorders[$style->getBorders()->getHashCode()] = $style->getBorders();
+ }
+ }
+
+ return $aBorders;
+ }
+
+ /**
+ * Get an array of all number formats.
+ *
+ * @return NumberFormat[] All number formats in PhpSpreadsheet
+ */
+ public function allNumberFormats(Spreadsheet $spreadsheet)
+ {
+ // Get an array of unique number formats
+ $aNumFmts = [];
+ $aStyles = $this->allStyles($spreadsheet);
+
+ /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */
+ foreach ($aStyles as $style) {
+ if ($style->getNumberFormat()->getBuiltInFormatCode() === false && !isset($aNumFmts[$style->getNumberFormat()->getHashCode()])) {
+ $aNumFmts[$style->getNumberFormat()->getHashCode()] = $style->getNumberFormat();
+ }
+ }
+
+ return $aNumFmts;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php
new file mode 100755
index 0000000..3a47be7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php
@@ -0,0 +1,837 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+
+class Theme extends WriterPart
+{
+ /**
+ * Map of Major fonts to write.
+ *
+ * @var array of string
+ */
+ private static $majorFonts = [
+ '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',
+ ];
+
+ /**
+ * Map of Minor fonts to write.
+ *
+ * @var array of string
+ */
+ private static $minorFonts = [
+ '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',
+ ];
+
+ /**
+ * Map of core colours.
+ *
+ * @var array of string
+ */
+ private static $colourScheme = [
+ 'dk2' => '1F497D',
+ 'lt2' => 'EEECE1',
+ 'accent1' => '4F81BD',
+ 'accent2' => 'C0504D',
+ 'accent3' => '9BBB59',
+ 'accent4' => '8064A2',
+ 'accent5' => '4BACC6',
+ 'accent6' => 'F79646',
+ 'hlink' => '0000FF',
+ 'folHlink' => '800080',
+ ];
+
+ /**
+ * Write theme to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeTheme(Spreadsheet $spreadsheet)
+ {
+ // 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');
+
+ // a:theme
+ $objWriter->startElement('a:theme');
+ $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
+ $objWriter->writeAttribute('name', 'Office Theme');
+
+ // a:themeElements
+ $objWriter->startElement('a:themeElements');
+
+ // a:clrScheme
+ $objWriter->startElement('a:clrScheme');
+ $objWriter->writeAttribute('name', 'Office');
+
+ // a:dk1
+ $objWriter->startElement('a:dk1');
+
+ // a:sysClr
+ $objWriter->startElement('a:sysClr');
+ $objWriter->writeAttribute('val', 'windowText');
+ $objWriter->writeAttribute('lastClr', '000000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lt1
+ $objWriter->startElement('a:lt1');
+
+ // a:sysClr
+ $objWriter->startElement('a:sysClr');
+ $objWriter->writeAttribute('val', 'window');
+ $objWriter->writeAttribute('lastClr', 'FFFFFF');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:dk2
+ $this->writeColourScheme($objWriter);
+
+ $objWriter->endElement();
+
+ // a:fontScheme
+ $objWriter->startElement('a:fontScheme');
+ $objWriter->writeAttribute('name', 'Office');
+
+ // a:majorFont
+ $objWriter->startElement('a:majorFont');
+ $this->writeFonts($objWriter, 'Cambria', self::$majorFonts);
+ $objWriter->endElement();
+
+ // a:minorFont
+ $objWriter->startElement('a:minorFont');
+ $this->writeFonts($objWriter, 'Calibri', self::$minorFonts);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:fmtScheme
+ $objWriter->startElement('a:fmtScheme');
+ $objWriter->writeAttribute('name', 'Office');
+
+ // a:fillStyleLst
+ $objWriter->startElement('a:fillStyleLst');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gradFill
+ $objWriter->startElement('a:gradFill');
+ $objWriter->writeAttribute('rotWithShape', '1');
+
+ // a:gsLst
+ $objWriter->startElement('a:gsLst');
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '0');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '50000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '300000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '35000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '37000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '300000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '100000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '15000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '350000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lin
+ $objWriter->startElement('a:lin');
+ $objWriter->writeAttribute('ang', '16200000');
+ $objWriter->writeAttribute('scaled', '1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gradFill
+ $objWriter->startElement('a:gradFill');
+ $objWriter->writeAttribute('rotWithShape', '1');
+
+ // a:gsLst
+ $objWriter->startElement('a:gsLst');
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '0');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '51000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '130000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '80000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '93000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '130000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '100000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '94000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '135000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lin
+ $objWriter->startElement('a:lin');
+ $objWriter->writeAttribute('ang', '16200000');
+ $objWriter->writeAttribute('scaled', '0');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lnStyleLst
+ $objWriter->startElement('a:lnStyleLst');
+
+ // a:ln
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', '9525');
+ $objWriter->writeAttribute('cap', 'flat');
+ $objWriter->writeAttribute('cmpd', 'sng');
+ $objWriter->writeAttribute('algn', 'ctr');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '95000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '105000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:prstDash
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', 'solid');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:ln
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', '25400');
+ $objWriter->writeAttribute('cap', 'flat');
+ $objWriter->writeAttribute('cmpd', 'sng');
+ $objWriter->writeAttribute('algn', 'ctr');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:prstDash
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', 'solid');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:ln
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', '38100');
+ $objWriter->writeAttribute('cap', 'flat');
+ $objWriter->writeAttribute('cmpd', 'sng');
+ $objWriter->writeAttribute('algn', 'ctr');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:prstDash
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', 'solid');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:effectStyleLst
+ $objWriter->startElement('a:effectStyleLst');
+
+ // a:effectStyle
+ $objWriter->startElement('a:effectStyle');
+
+ // a:effectLst
+ $objWriter->startElement('a:effectLst');
+
+ // a:outerShdw
+ $objWriter->startElement('a:outerShdw');
+ $objWriter->writeAttribute('blurRad', '40000');
+ $objWriter->writeAttribute('dist', '20000');
+ $objWriter->writeAttribute('dir', '5400000');
+ $objWriter->writeAttribute('rotWithShape', '0');
+
+ // a:srgbClr
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', '000000');
+
+ // a:alpha
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', '38000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:effectStyle
+ $objWriter->startElement('a:effectStyle');
+
+ // a:effectLst
+ $objWriter->startElement('a:effectLst');
+
+ // a:outerShdw
+ $objWriter->startElement('a:outerShdw');
+ $objWriter->writeAttribute('blurRad', '40000');
+ $objWriter->writeAttribute('dist', '23000');
+ $objWriter->writeAttribute('dir', '5400000');
+ $objWriter->writeAttribute('rotWithShape', '0');
+
+ // a:srgbClr
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', '000000');
+
+ // a:alpha
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', '35000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:effectStyle
+ $objWriter->startElement('a:effectStyle');
+
+ // a:effectLst
+ $objWriter->startElement('a:effectLst');
+
+ // a:outerShdw
+ $objWriter->startElement('a:outerShdw');
+ $objWriter->writeAttribute('blurRad', '40000');
+ $objWriter->writeAttribute('dist', '23000');
+ $objWriter->writeAttribute('dir', '5400000');
+ $objWriter->writeAttribute('rotWithShape', '0');
+
+ // a:srgbClr
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', '000000');
+
+ // a:alpha
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', '35000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:scene3d
+ $objWriter->startElement('a:scene3d');
+
+ // a:camera
+ $objWriter->startElement('a:camera');
+ $objWriter->writeAttribute('prst', 'orthographicFront');
+
+ // a:rot
+ $objWriter->startElement('a:rot');
+ $objWriter->writeAttribute('lat', '0');
+ $objWriter->writeAttribute('lon', '0');
+ $objWriter->writeAttribute('rev', '0');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lightRig
+ $objWriter->startElement('a:lightRig');
+ $objWriter->writeAttribute('rig', 'threePt');
+ $objWriter->writeAttribute('dir', 't');
+
+ // a:rot
+ $objWriter->startElement('a:rot');
+ $objWriter->writeAttribute('lat', '0');
+ $objWriter->writeAttribute('lon', '0');
+ $objWriter->writeAttribute('rev', '1200000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:sp3d
+ $objWriter->startElement('a:sp3d');
+
+ // a:bevelT
+ $objWriter->startElement('a:bevelT');
+ $objWriter->writeAttribute('w', '63500');
+ $objWriter->writeAttribute('h', '25400');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:bgFillStyleLst
+ $objWriter->startElement('a:bgFillStyleLst');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gradFill
+ $objWriter->startElement('a:gradFill');
+ $objWriter->writeAttribute('rotWithShape', '1');
+
+ // a:gsLst
+ $objWriter->startElement('a:gsLst');
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '0');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '40000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '350000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '40000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '45000');
+ $objWriter->endElement();
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '99000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '350000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '100000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '20000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '255000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:path
+ $objWriter->startElement('a:path');
+ $objWriter->writeAttribute('path', 'circle');
+
+ // a:fillToRect
+ $objWriter->startElement('a:fillToRect');
+ $objWriter->writeAttribute('l', '50000');
+ $objWriter->writeAttribute('t', '-80000');
+ $objWriter->writeAttribute('r', '50000');
+ $objWriter->writeAttribute('b', '180000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gradFill
+ $objWriter->startElement('a:gradFill');
+ $objWriter->writeAttribute('rotWithShape', '1');
+
+ // a:gsLst
+ $objWriter->startElement('a:gsLst');
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '0');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '80000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '300000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '100000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '30000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '200000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:path
+ $objWriter->startElement('a:path');
+ $objWriter->writeAttribute('path', 'circle');
+
+ // a:fillToRect
+ $objWriter->startElement('a:fillToRect');
+ $objWriter->writeAttribute('l', '50000');
+ $objWriter->writeAttribute('t', '50000');
+ $objWriter->writeAttribute('r', '50000');
+ $objWriter->writeAttribute('b', '50000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:objectDefaults
+ $objWriter->writeElement('a:objectDefaults', null);
+
+ // a:extraClrSchemeLst
+ $objWriter->writeElement('a:extraClrSchemeLst', null);
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write fonts to XML format.
+ *
+ * @param XMLWriter $objWriter
+ * @param string $latinFont
+ * @param array of string $fontSet
+ *
+ * @return string XML Output
+ */
+ private function writeFonts($objWriter, $latinFont, $fontSet)
+ {
+ // a:latin
+ $objWriter->startElement('a:latin');
+ $objWriter->writeAttribute('typeface', $latinFont);
+ $objWriter->endElement();
+
+ // a:ea
+ $objWriter->startElement('a:ea');
+ $objWriter->writeAttribute('typeface', '');
+ $objWriter->endElement();
+
+ // a:cs
+ $objWriter->startElement('a:cs');
+ $objWriter->writeAttribute('typeface', '');
+ $objWriter->endElement();
+
+ foreach ($fontSet as $fontScript => $typeface) {
+ $objWriter->startElement('a:font');
+ $objWriter->writeAttribute('script', $fontScript);
+ $objWriter->writeAttribute('typeface', $typeface);
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write colour scheme to XML format.
+ *
+ * @param XMLWriter $objWriter
+ *
+ * @return string XML Output
+ */
+ private function writeColourScheme($objWriter)
+ {
+ foreach (self::$colourScheme as $colourName => $colourValue) {
+ $objWriter->startElement('a:' . $colourName);
+
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $colourValue);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php
new file mode 100755
index 0000000..88befd0
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php
@@ -0,0 +1,406 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\NamedRange;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
+use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
+
+class Workbook extends WriterPart
+{
+ /**
+ * Write workbook to XML format.
+ *
+ * @param bool $recalcRequired Indicate whether formulas should be recalculated before writing
+ *
+ * @return string XML Output
+ */
+ public function writeWorkbook(Spreadsheet $spreadsheet, $recalcRequired = false)
+ {
+ // Create XML writer
+ 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');
+
+ // workbook
+ $objWriter->startElement('workbook');
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+
+ // fileVersion
+ $this->writeFileVersion($objWriter);
+
+ // workbookPr
+ $this->writeWorkbookPr($objWriter);
+
+ // workbookProtection
+ $this->writeWorkbookProtection($objWriter, $spreadsheet);
+
+ // bookViews
+ if ($this->getParentWriter()->getOffice2003Compatibility() === false) {
+ $this->writeBookViews($objWriter, $spreadsheet);
+ }
+
+ // sheets
+ $this->writeSheets($objWriter, $spreadsheet);
+
+ // definedNames
+ $this->writeDefinedNames($objWriter, $spreadsheet);
+
+ // calcPr
+ $this->writeCalcPr($objWriter, $recalcRequired);
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write file version.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeFileVersion(XMLWriter $objWriter): void
+ {
+ $objWriter->startElement('fileVersion');
+ $objWriter->writeAttribute('appName', 'xl');
+ $objWriter->writeAttribute('lastEdited', '4');
+ $objWriter->writeAttribute('lowestEdited', '4');
+ $objWriter->writeAttribute('rupBuild', '4505');
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write WorkbookPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeWorkbookPr(XMLWriter $objWriter): void
+ {
+ $objWriter->startElement('workbookPr');
+
+ if (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904) {
+ $objWriter->writeAttribute('date1904', '1');
+ }
+
+ $objWriter->writeAttribute('codeName', 'ThisWorkbook');
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write BookViews.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeBookViews(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
+ {
+ // bookViews
+ $objWriter->startElement('bookViews');
+
+ // workbookView
+ $objWriter->startElement('workbookView');
+
+ $objWriter->writeAttribute('activeTab', $spreadsheet->getActiveSheetIndex());
+ $objWriter->writeAttribute('autoFilterDateGrouping', ($spreadsheet->getAutoFilterDateGrouping() ? 'true' : 'false'));
+ $objWriter->writeAttribute('firstSheet', $spreadsheet->getFirstSheetIndex());
+ $objWriter->writeAttribute('minimized', ($spreadsheet->getMinimized() ? 'true' : 'false'));
+ $objWriter->writeAttribute('showHorizontalScroll', ($spreadsheet->getShowHorizontalScroll() ? 'true' : 'false'));
+ $objWriter->writeAttribute('showSheetTabs', ($spreadsheet->getShowSheetTabs() ? 'true' : 'false'));
+ $objWriter->writeAttribute('showVerticalScroll', ($spreadsheet->getShowVerticalScroll() ? 'true' : 'false'));
+ $objWriter->writeAttribute('tabRatio', $spreadsheet->getTabRatio());
+ $objWriter->writeAttribute('visibility', $spreadsheet->getVisibility());
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write WorkbookProtection.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeWorkbookProtection(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
+ {
+ if ($spreadsheet->getSecurity()->isSecurityEnabled()) {
+ $objWriter->startElement('workbookProtection');
+ $objWriter->writeAttribute('lockRevision', ($spreadsheet->getSecurity()->getLockRevision() ? 'true' : 'false'));
+ $objWriter->writeAttribute('lockStructure', ($spreadsheet->getSecurity()->getLockStructure() ? 'true' : 'false'));
+ $objWriter->writeAttribute('lockWindows', ($spreadsheet->getSecurity()->getLockWindows() ? 'true' : 'false'));
+
+ if ($spreadsheet->getSecurity()->getRevisionsPassword() != '') {
+ $objWriter->writeAttribute('revisionsPassword', $spreadsheet->getSecurity()->getRevisionsPassword());
+ }
+
+ if ($spreadsheet->getSecurity()->getWorkbookPassword() != '') {
+ $objWriter->writeAttribute('workbookPassword', $spreadsheet->getSecurity()->getWorkbookPassword());
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write calcPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param bool $recalcRequired Indicate whether formulas should be recalculated before writing
+ */
+ private function writeCalcPr(XMLWriter $objWriter, $recalcRequired = true): void
+ {
+ $objWriter->startElement('calcPr');
+
+ // Set the calcid to a higher value than Excel itself will use, otherwise Excel will always recalc
+ // If MS Excel does do a recalc, then users opening a file in MS Excel will be prompted to save on exit
+ // because the file has changed
+ $objWriter->writeAttribute('calcId', '999999');
+ $objWriter->writeAttribute('calcMode', 'auto');
+ // fullCalcOnLoad isn't needed if we've recalculating for the save
+ $objWriter->writeAttribute('calcCompleted', ($recalcRequired) ? 1 : 0);
+ $objWriter->writeAttribute('fullCalcOnLoad', ($recalcRequired) ? 0 : 1);
+ $objWriter->writeAttribute('forceFullCalc', ($recalcRequired) ? 0 : 1);
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write sheets.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeSheets(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
+ {
+ // Write sheets
+ $objWriter->startElement('sheets');
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ // sheet
+ $this->writeSheet(
+ $objWriter,
+ $spreadsheet->getSheet($i)->getTitle(),
+ ($i + 1),
+ ($i + 1 + 3),
+ $spreadsheet->getSheet($i)->getSheetState()
+ );
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write sheet.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pSheetname Sheet name
+ * @param int $pSheetId Sheet id
+ * @param int $pRelId Relationship ID
+ * @param string $sheetState Sheet state (visible, hidden, veryHidden)
+ */
+ private function writeSheet(XMLWriter $objWriter, $pSheetname, $pSheetId = 1, $pRelId = 1, $sheetState = 'visible'): void
+ {
+ if ($pSheetname != '') {
+ // Write sheet
+ $objWriter->startElement('sheet');
+ $objWriter->writeAttribute('name', $pSheetname);
+ $objWriter->writeAttribute('sheetId', $pSheetId);
+ if ($sheetState !== 'visible' && $sheetState != '') {
+ $objWriter->writeAttribute('state', $sheetState);
+ }
+ $objWriter->writeAttribute('r:id', 'rId' . $pRelId);
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+
+ /**
+ * Write Defined Names.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeDefinedNames(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
+ {
+ // Write defined names
+ $objWriter->startElement('definedNames');
+
+ // Named ranges
+ if (count($spreadsheet->getNamedRanges()) > 0) {
+ // Named ranges
+ $this->writeNamedRanges($objWriter, $spreadsheet);
+ }
+
+ // Other defined names
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ // definedName for autoFilter
+ $this->writeDefinedNameForAutofilter($objWriter, $spreadsheet->getSheet($i), $i);
+
+ // definedName for Print_Titles
+ $this->writeDefinedNameForPrintTitles($objWriter, $spreadsheet->getSheet($i), $i);
+
+ // definedName for Print_Area
+ $this->writeDefinedNameForPrintArea($objWriter, $spreadsheet->getSheet($i), $i);
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write named ranges.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeNamedRanges(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
+ {
+ // Loop named ranges
+ $namedRanges = $spreadsheet->getNamedRanges();
+ foreach ($namedRanges as $namedRange) {
+ $this->writeDefinedNameForNamedRange($objWriter, $namedRange);
+ }
+ }
+
+ /**
+ * Write Defined Name for named range.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeDefinedNameForNamedRange(XMLWriter $objWriter, NamedRange $pNamedRange): void
+ {
+ // definedName for named range
+ $objWriter->startElement('definedName');
+ $objWriter->writeAttribute('name', $pNamedRange->getName());
+ if ($pNamedRange->getLocalOnly()) {
+ $objWriter->writeAttribute('localSheetId', $pNamedRange->getScope()->getParent()->getIndex($pNamedRange->getScope()));
+ }
+
+ // Create absolute coordinate and write as raw text
+ $range = Coordinate::splitRange($pNamedRange->getRange());
+ $iMax = count($range);
+ for ($i = 0; $i < $iMax; ++$i) {
+ $range[$i][0] = '\'' . str_replace("'", "''", $pNamedRange->getWorksheet()->getTitle()) . '\'!' . Coordinate::absoluteReference($range[$i][0]);
+ if (isset($range[$i][1])) {
+ $range[$i][1] = Coordinate::absoluteReference($range[$i][1]);
+ }
+ }
+ $range = Coordinate::buildRange($range);
+
+ $objWriter->writeRawData($range);
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Defined Name for autoFilter.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $pSheetId
+ */
+ private function writeDefinedNameForAutofilter(XMLWriter $objWriter, Worksheet $pSheet, $pSheetId = 0): void
+ {
+ // definedName for autoFilter
+ $autoFilterRange = $pSheet->getAutoFilter()->getRange();
+ if (!empty($autoFilterRange)) {
+ $objWriter->startElement('definedName');
+ $objWriter->writeAttribute('name', '_xlnm._FilterDatabase');
+ $objWriter->writeAttribute('localSheetId', $pSheetId);
+ $objWriter->writeAttribute('hidden', '1');
+
+ // Create absolute coordinate and write as raw text
+ $range = Coordinate::splitRange($autoFilterRange);
+ $range = $range[0];
+ // Strip any worksheet ref so we can make the cell ref absolute
+ [$ws, $range[0]] = Worksheet::extractSheetTitle($range[0], true);
+
+ $range[0] = Coordinate::absoluteCoordinate($range[0]);
+ $range[1] = Coordinate::absoluteCoordinate($range[1]);
+ $range = implode(':', $range);
+
+ $objWriter->writeRawData('\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!' . $range);
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Defined Name for PrintTitles.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $pSheetId
+ */
+ private function writeDefinedNameForPrintTitles(XMLWriter $objWriter, Worksheet $pSheet, $pSheetId = 0): void
+ {
+ // definedName for PrintTitles
+ if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet() || $pSheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
+ $objWriter->startElement('definedName');
+ $objWriter->writeAttribute('name', '_xlnm.Print_Titles');
+ $objWriter->writeAttribute('localSheetId', $pSheetId);
+
+ // Setting string
+ $settingString = '';
+
+ // Columns to repeat
+ if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) {
+ $repeat = $pSheet->getPageSetup()->getColumnsToRepeatAtLeft();
+
+ $settingString .= '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1];
+ }
+
+ // Rows to repeat
+ if ($pSheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
+ if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) {
+ $settingString .= ',';
+ }
+
+ $repeat = $pSheet->getPageSetup()->getRowsToRepeatAtTop();
+
+ $settingString .= '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1];
+ }
+
+ $objWriter->writeRawData($settingString);
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Defined Name for PrintTitles.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $pSheetId
+ */
+ private function writeDefinedNameForPrintArea(XMLWriter $objWriter, Worksheet $pSheet, $pSheetId = 0): void
+ {
+ // definedName for PrintArea
+ if ($pSheet->getPageSetup()->isPrintAreaSet()) {
+ $objWriter->startElement('definedName');
+ $objWriter->writeAttribute('name', '_xlnm.Print_Area');
+ $objWriter->writeAttribute('localSheetId', $pSheetId);
+
+ // Print area
+ $printArea = Coordinate::splitRange($pSheet->getPageSetup()->getPrintArea());
+
+ $chunks = [];
+ foreach ($printArea as $printAreaRect) {
+ $printAreaRect[0] = Coordinate::absoluteReference($printAreaRect[0]);
+ $printAreaRect[1] = Coordinate::absoluteReference($printAreaRect[1]);
+ $chunks[] = '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!' . implode(':', $printAreaRect);
+ }
+
+ $objWriter->writeRawData(implode(',', $chunks));
+
+ $objWriter->endElement();
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
new file mode 100755
index 0000000..d101bb4
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
@@ -0,0 +1,1275 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Style\Conditional;
+use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
+use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule;
+use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet as PhpspreadsheetWorksheet;
+
+class Worksheet extends WriterPart
+{
+ /**
+ * Write worksheet to XML format.
+ *
+ * @param string[] $pStringTable
+ * @param bool $includeCharts Flag indicating if we should write charts
+ *
+ * @return string XML Output
+ */
+ public function writeWorksheet(PhpspreadsheetWorksheet $pSheet, $pStringTable = null, $includeCharts = false)
+ {
+ // 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');
+
+ // Worksheet
+ $objWriter->startElement('worksheet');
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+
+ $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
+ $objWriter->writeAttribute('xmlns:x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main');
+ $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006');
+ $objWriter->writeAttribute('mc:Ignorable', 'x14ac');
+ $objWriter->writeAttribute('xmlns:x14ac', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac');
+
+ // sheetPr
+ $this->writeSheetPr($objWriter, $pSheet);
+
+ // Dimension
+ $this->writeDimension($objWriter, $pSheet);
+
+ // sheetViews
+ $this->writeSheetViews($objWriter, $pSheet);
+
+ // sheetFormatPr
+ $this->writeSheetFormatPr($objWriter, $pSheet);
+
+ // cols
+ $this->writeCols($objWriter, $pSheet);
+
+ // sheetData
+ $this->writeSheetData($objWriter, $pSheet, $pStringTable);
+
+ // sheetProtection
+ $this->writeSheetProtection($objWriter, $pSheet);
+
+ // protectedRanges
+ $this->writeProtectedRanges($objWriter, $pSheet);
+
+ // autoFilter
+ $this->writeAutoFilter($objWriter, $pSheet);
+
+ // mergeCells
+ $this->writeMergeCells($objWriter, $pSheet);
+
+ // conditionalFormatting
+ $this->writeConditionalFormatting($objWriter, $pSheet);
+
+ // dataValidations
+ $this->writeDataValidations($objWriter, $pSheet);
+
+ // hyperlinks
+ $this->writeHyperlinks($objWriter, $pSheet);
+
+ // Print options
+ $this->writePrintOptions($objWriter, $pSheet);
+
+ // Page margins
+ $this->writePageMargins($objWriter, $pSheet);
+
+ // Page setup
+ $this->writePageSetup($objWriter, $pSheet);
+
+ // Header / footer
+ $this->writeHeaderFooter($objWriter, $pSheet);
+
+ // Breaks
+ $this->writeBreaks($objWriter, $pSheet);
+
+ // Drawings and/or Charts
+ $this->writeDrawings($objWriter, $pSheet, $includeCharts);
+
+ // LegacyDrawing
+ $this->writeLegacyDrawing($objWriter, $pSheet);
+
+ // LegacyDrawingHF
+ $this->writeLegacyDrawingHF($objWriter, $pSheet);
+
+ // AlternateContent
+ $this->writeAlternateContent($objWriter, $pSheet);
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write SheetPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeSheetPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // sheetPr
+ $objWriter->startElement('sheetPr');
+ if ($pSheet->getParent()->hasMacros()) {
+ //if the workbook have macros, we need to have codeName for the sheet
+ if (!$pSheet->hasCodeName()) {
+ $pSheet->setCodeName($pSheet->getTitle());
+ }
+ $objWriter->writeAttribute('codeName', $pSheet->getCodeName());
+ }
+ $autoFilterRange = $pSheet->getAutoFilter()->getRange();
+ if (!empty($autoFilterRange)) {
+ $objWriter->writeAttribute('filterMode', 1);
+ $pSheet->getAutoFilter()->showHideRows();
+ }
+
+ // tabColor
+ if ($pSheet->isTabColorSet()) {
+ $objWriter->startElement('tabColor');
+ $objWriter->writeAttribute('rgb', $pSheet->getTabColor()->getARGB());
+ $objWriter->endElement();
+ }
+
+ // outlinePr
+ $objWriter->startElement('outlinePr');
+ $objWriter->writeAttribute('summaryBelow', ($pSheet->getShowSummaryBelow() ? '1' : '0'));
+ $objWriter->writeAttribute('summaryRight', ($pSheet->getShowSummaryRight() ? '1' : '0'));
+ $objWriter->endElement();
+
+ // pageSetUpPr
+ if ($pSheet->getPageSetup()->getFitToPage()) {
+ $objWriter->startElement('pageSetUpPr');
+ $objWriter->writeAttribute('fitToPage', '1');
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Dimension.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeDimension(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // dimension
+ $objWriter->startElement('dimension');
+ $objWriter->writeAttribute('ref', $pSheet->calculateWorksheetDimension());
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write SheetViews.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeSheetViews(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // sheetViews
+ $objWriter->startElement('sheetViews');
+
+ // Sheet selected?
+ $sheetSelected = false;
+ if ($this->getParentWriter()->getSpreadsheet()->getIndex($pSheet) == $this->getParentWriter()->getSpreadsheet()->getActiveSheetIndex()) {
+ $sheetSelected = true;
+ }
+
+ // sheetView
+ $objWriter->startElement('sheetView');
+ $objWriter->writeAttribute('tabSelected', $sheetSelected ? '1' : '0');
+ $objWriter->writeAttribute('workbookViewId', '0');
+
+ // Zoom scales
+ if ($pSheet->getSheetView()->getZoomScale() != 100) {
+ $objWriter->writeAttribute('zoomScale', $pSheet->getSheetView()->getZoomScale());
+ }
+ if ($pSheet->getSheetView()->getZoomScaleNormal() != 100) {
+ $objWriter->writeAttribute('zoomScaleNormal', $pSheet->getSheetView()->getZoomScaleNormal());
+ }
+
+ // Show zeros (Excel also writes this attribute only if set to false)
+ if ($pSheet->getSheetView()->getShowZeros() === false) {
+ $objWriter->writeAttribute('showZeros', 0);
+ }
+
+ // View Layout Type
+ if ($pSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_NORMAL) {
+ $objWriter->writeAttribute('view', $pSheet->getSheetView()->getView());
+ }
+
+ // Gridlines
+ if ($pSheet->getShowGridlines()) {
+ $objWriter->writeAttribute('showGridLines', 'true');
+ } else {
+ $objWriter->writeAttribute('showGridLines', 'false');
+ }
+
+ // Row and column headers
+ if ($pSheet->getShowRowColHeaders()) {
+ $objWriter->writeAttribute('showRowColHeaders', '1');
+ } else {
+ $objWriter->writeAttribute('showRowColHeaders', '0');
+ }
+
+ // Right-to-left
+ if ($pSheet->getRightToLeft()) {
+ $objWriter->writeAttribute('rightToLeft', 'true');
+ }
+
+ $activeCell = $pSheet->getActiveCell();
+ $sqref = $pSheet->getSelectedCells();
+
+ // Pane
+ $pane = '';
+ if ($pSheet->getFreezePane()) {
+ [$xSplit, $ySplit] = Coordinate::coordinateFromString($pSheet->getFreezePane());
+ $xSplit = Coordinate::columnIndexFromString($xSplit);
+ --$xSplit;
+ --$ySplit;
+
+ $topLeftCell = $pSheet->getTopLeftCell();
+
+ // pane
+ $pane = 'topRight';
+ $objWriter->startElement('pane');
+ if ($xSplit > 0) {
+ $objWriter->writeAttribute('xSplit', $xSplit);
+ }
+ if ($ySplit > 0) {
+ $objWriter->writeAttribute('ySplit', $ySplit);
+ $pane = ($xSplit > 0) ? 'bottomRight' : 'bottomLeft';
+ }
+ $objWriter->writeAttribute('topLeftCell', $topLeftCell);
+ $objWriter->writeAttribute('activePane', $pane);
+ $objWriter->writeAttribute('state', 'frozen');
+ $objWriter->endElement();
+
+ if (($xSplit > 0) && ($ySplit > 0)) {
+ // Write additional selections if more than two panes (ie both an X and a Y split)
+ $objWriter->startElement('selection');
+ $objWriter->writeAttribute('pane', 'topRight');
+ $objWriter->endElement();
+ $objWriter->startElement('selection');
+ $objWriter->writeAttribute('pane', 'bottomLeft');
+ $objWriter->endElement();
+ }
+ }
+
+ // Selection
+ // Only need to write selection element if we have a split pane
+ // We cheat a little by over-riding the active cell selection, setting it to the split cell
+ $objWriter->startElement('selection');
+ if ($pane != '') {
+ $objWriter->writeAttribute('pane', $pane);
+ }
+ $objWriter->writeAttribute('activeCell', $activeCell);
+ $objWriter->writeAttribute('sqref', $sqref);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write SheetFormatPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeSheetFormatPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // sheetFormatPr
+ $objWriter->startElement('sheetFormatPr');
+
+ // Default row height
+ if ($pSheet->getDefaultRowDimension()->getRowHeight() >= 0) {
+ $objWriter->writeAttribute('customHeight', 'true');
+ $objWriter->writeAttribute('defaultRowHeight', StringHelper::formatNumber($pSheet->getDefaultRowDimension()->getRowHeight()));
+ } else {
+ $objWriter->writeAttribute('defaultRowHeight', '14.4');
+ }
+
+ // Set Zero Height row
+ if ((string) $pSheet->getDefaultRowDimension()->getZeroHeight() === '1' ||
+ strtolower((string) $pSheet->getDefaultRowDimension()->getZeroHeight()) == 'true') {
+ $objWriter->writeAttribute('zeroHeight', '1');
+ }
+
+ // Default column width
+ if ($pSheet->getDefaultColumnDimension()->getWidth() >= 0) {
+ $objWriter->writeAttribute('defaultColWidth', StringHelper::formatNumber($pSheet->getDefaultColumnDimension()->getWidth()));
+ }
+
+ // Outline level - row
+ $outlineLevelRow = 0;
+ foreach ($pSheet->getRowDimensions() as $dimension) {
+ if ($dimension->getOutlineLevel() > $outlineLevelRow) {
+ $outlineLevelRow = $dimension->getOutlineLevel();
+ }
+ }
+ $objWriter->writeAttribute('outlineLevelRow', (int) $outlineLevelRow);
+
+ // Outline level - column
+ $outlineLevelCol = 0;
+ foreach ($pSheet->getColumnDimensions() as $dimension) {
+ if ($dimension->getOutlineLevel() > $outlineLevelCol) {
+ $outlineLevelCol = $dimension->getOutlineLevel();
+ }
+ }
+ $objWriter->writeAttribute('outlineLevelCol', (int) $outlineLevelCol);
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Cols.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeCols(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // cols
+ if (count($pSheet->getColumnDimensions()) > 0) {
+ $objWriter->startElement('cols');
+
+ $pSheet->calculateColumnWidths();
+
+ // Loop through column dimensions
+ foreach ($pSheet->getColumnDimensions() as $colDimension) {
+ // col
+ $objWriter->startElement('col');
+ $objWriter->writeAttribute('min', Coordinate::columnIndexFromString($colDimension->getColumnIndex()));
+ $objWriter->writeAttribute('max', Coordinate::columnIndexFromString($colDimension->getColumnIndex()));
+
+ if ($colDimension->getWidth() < 0) {
+ // No width set, apply default of 10
+ $objWriter->writeAttribute('width', '9.10');
+ } else {
+ // Width set
+ $objWriter->writeAttribute('width', StringHelper::formatNumber($colDimension->getWidth()));
+ }
+
+ // Column visibility
+ if ($colDimension->getVisible() === false) {
+ $objWriter->writeAttribute('hidden', 'true');
+ }
+
+ // Auto size?
+ if ($colDimension->getAutoSize()) {
+ $objWriter->writeAttribute('bestFit', 'true');
+ }
+
+ // Custom width?
+ if ($colDimension->getWidth() != $pSheet->getDefaultColumnDimension()->getWidth()) {
+ $objWriter->writeAttribute('customWidth', 'true');
+ }
+
+ // Collapsed
+ if ($colDimension->getCollapsed() === true) {
+ $objWriter->writeAttribute('collapsed', 'true');
+ }
+
+ // Outline level
+ if ($colDimension->getOutlineLevel() > 0) {
+ $objWriter->writeAttribute('outlineLevel', $colDimension->getOutlineLevel());
+ }
+
+ // Style
+ $objWriter->writeAttribute('style', $colDimension->getXfIndex());
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write SheetProtection.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeSheetProtection(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // sheetProtection
+ $objWriter->startElement('sheetProtection');
+
+ $protection = $pSheet->getProtection();
+
+ if ($protection->getAlgorithm()) {
+ $objWriter->writeAttribute('algorithmName', $protection->getAlgorithm());
+ $objWriter->writeAttribute('hashValue', $protection->getPassword());
+ $objWriter->writeAttribute('saltValue', $protection->getSalt());
+ $objWriter->writeAttribute('spinCount', $protection->getSpinCount());
+ } elseif ($protection->getPassword() !== '') {
+ $objWriter->writeAttribute('password', $protection->getPassword());
+ }
+
+ $objWriter->writeAttribute('sheet', ($protection->getSheet() ? 'true' : 'false'));
+ $objWriter->writeAttribute('objects', ($protection->getObjects() ? 'true' : 'false'));
+ $objWriter->writeAttribute('scenarios', ($protection->getScenarios() ? 'true' : 'false'));
+ $objWriter->writeAttribute('formatCells', ($protection->getFormatCells() ? 'true' : 'false'));
+ $objWriter->writeAttribute('formatColumns', ($protection->getFormatColumns() ? 'true' : 'false'));
+ $objWriter->writeAttribute('formatRows', ($protection->getFormatRows() ? 'true' : 'false'));
+ $objWriter->writeAttribute('insertColumns', ($protection->getInsertColumns() ? 'true' : 'false'));
+ $objWriter->writeAttribute('insertRows', ($protection->getInsertRows() ? 'true' : 'false'));
+ $objWriter->writeAttribute('insertHyperlinks', ($protection->getInsertHyperlinks() ? 'true' : 'false'));
+ $objWriter->writeAttribute('deleteColumns', ($protection->getDeleteColumns() ? 'true' : 'false'));
+ $objWriter->writeAttribute('deleteRows', ($protection->getDeleteRows() ? 'true' : 'false'));
+ $objWriter->writeAttribute('selectLockedCells', ($protection->getSelectLockedCells() ? 'true' : 'false'));
+ $objWriter->writeAttribute('sort', ($protection->getSort() ? 'true' : 'false'));
+ $objWriter->writeAttribute('autoFilter', ($protection->getAutoFilter() ? 'true' : 'false'));
+ $objWriter->writeAttribute('pivotTables', ($protection->getPivotTables() ? 'true' : 'false'));
+ $objWriter->writeAttribute('selectUnlockedCells', ($protection->getSelectUnlockedCells() ? 'true' : 'false'));
+ $objWriter->endElement();
+ }
+
+ private static function writeAttributeIf(XMLWriter $objWriter, $condition, string $attr, string $val): void
+ {
+ if ($condition) {
+ $objWriter->writeAttribute($attr, $val);
+ }
+ }
+
+ private static function writeElementIf(XMLWriter $objWriter, $condition, string $attr, string $val): void
+ {
+ if ($condition) {
+ $objWriter->writeElement($attr, $val);
+ }
+ }
+
+ private static function writeOtherCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void
+ {
+ if ($conditional->getConditionType() == Conditional::CONDITION_CELLIS
+ || $conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT
+ || $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION) {
+ foreach ($conditional->getConditions() as $formula) {
+ // Formula
+ $objWriter->writeElement('formula', Xlfn::addXlfn($formula));
+ }
+ } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSBLANKS) {
+ // formula copied from ms xlsx xml source file
+ $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))=0');
+ } elseif ($conditional->getConditionType() == Conditional::CONDITION_NOTCONTAINSBLANKS) {
+ // formula copied from ms xlsx xml source file
+ $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))>0');
+ }
+ }
+
+ private static function writeTextCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void
+ {
+ $txt = $conditional->getText();
+ if ($txt !== null) {
+ $objWriter->writeAttribute('text', $txt);
+ if ($conditional->getOperatorType() == Conditional::OPERATOR_CONTAINSTEXT) {
+ $objWriter->writeElement('formula', 'NOT(ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . ')))');
+ } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_BEGINSWITH) {
+ $objWriter->writeElement('formula', 'LEFT(' . $cellCoordinate . ',' . strlen($txt) . ')="' . $txt . '"');
+ } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_ENDSWITH) {
+ $objWriter->writeElement('formula', 'RIGHT(' . $cellCoordinate . ',' . strlen($txt) . ')="' . $txt . '"');
+ } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_NOTCONTAINS) {
+ $objWriter->writeElement('formula', 'ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . '))');
+ }
+ }
+ }
+
+ /**
+ * Write ConditionalFormatting.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeConditionalFormatting(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // Conditional id
+ $id = 1;
+
+ // Loop through styles in the current worksheet
+ foreach ($pSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) {
+ foreach ($conditionalStyles as $conditional) {
+ // WHY was this again?
+ // if ($this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) == '') {
+ // continue;
+ // }
+ if ($conditional->getConditionType() != Conditional::CONDITION_NONE) {
+ // conditionalFormatting
+ $objWriter->startElement('conditionalFormatting');
+ $objWriter->writeAttribute('sqref', $cellCoordinate);
+
+ // cfRule
+ $objWriter->startElement('cfRule');
+ $objWriter->writeAttribute('type', $conditional->getConditionType());
+ $objWriter->writeAttribute('dxfId', $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()));
+ $objWriter->writeAttribute('priority', $id++);
+
+ self::writeAttributeif(
+ $objWriter,
+ ($conditional->getConditionType() == Conditional::CONDITION_CELLIS || $conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT)
+ && $conditional->getOperatorType() != Conditional::OPERATOR_NONE,
+ 'operator',
+ $conditional->getOperatorType()
+ );
+
+ self::writeAttributeIf($objWriter, $conditional->getStopIfTrue(), 'stopIfTrue', '1');
+
+ if ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT) {
+ self::writeTextCondElements($objWriter, $conditional, $cellCoordinate);
+ } else {
+ self::writeOtherCondElements($objWriter, $conditional, $cellCoordinate);
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+ }
+ }
+
+ /**
+ * Write DataValidations.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // Datavalidation collection
+ $dataValidationCollection = $pSheet->getDataValidationCollection();
+
+ // Write data validations?
+ if (!empty($dataValidationCollection)) {
+ $dataValidationCollection = Coordinate::mergeRangesInCollection($dataValidationCollection);
+ $objWriter->startElement('dataValidations');
+ $objWriter->writeAttribute('count', count($dataValidationCollection));
+
+ foreach ($dataValidationCollection as $coordinate => $dv) {
+ $objWriter->startElement('dataValidation');
+
+ if ($dv->getType() != '') {
+ $objWriter->writeAttribute('type', $dv->getType());
+ }
+
+ if ($dv->getErrorStyle() != '') {
+ $objWriter->writeAttribute('errorStyle', $dv->getErrorStyle());
+ }
+
+ if ($dv->getOperator() != '') {
+ $objWriter->writeAttribute('operator', $dv->getOperator());
+ }
+
+ $objWriter->writeAttribute('allowBlank', ($dv->getAllowBlank() ? '1' : '0'));
+ $objWriter->writeAttribute('showDropDown', (!$dv->getShowDropDown() ? '1' : '0'));
+ $objWriter->writeAttribute('showInputMessage', ($dv->getShowInputMessage() ? '1' : '0'));
+ $objWriter->writeAttribute('showErrorMessage', ($dv->getShowErrorMessage() ? '1' : '0'));
+
+ if ($dv->getErrorTitle() !== '') {
+ $objWriter->writeAttribute('errorTitle', $dv->getErrorTitle());
+ }
+ if ($dv->getError() !== '') {
+ $objWriter->writeAttribute('error', $dv->getError());
+ }
+ if ($dv->getPromptTitle() !== '') {
+ $objWriter->writeAttribute('promptTitle', $dv->getPromptTitle());
+ }
+ if ($dv->getPrompt() !== '') {
+ $objWriter->writeAttribute('prompt', $dv->getPrompt());
+ }
+
+ $objWriter->writeAttribute('sqref', $coordinate);
+
+ if ($dv->getFormula1() !== '') {
+ $objWriter->writeElement('formula1', $dv->getFormula1());
+ }
+ if ($dv->getFormula2() !== '') {
+ $objWriter->writeElement('formula2', $dv->getFormula2());
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Hyperlinks.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeHyperlinks(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // Hyperlink collection
+ $hyperlinkCollection = $pSheet->getHyperlinkCollection();
+
+ // Relation ID
+ $relationId = 1;
+
+ // Write hyperlinks?
+ if (!empty($hyperlinkCollection)) {
+ $objWriter->startElement('hyperlinks');
+
+ foreach ($hyperlinkCollection as $coordinate => $hyperlink) {
+ $objWriter->startElement('hyperlink');
+
+ $objWriter->writeAttribute('ref', $coordinate);
+ if (!$hyperlink->isInternal()) {
+ $objWriter->writeAttribute('r:id', 'rId_hyperlink_' . $relationId);
+ ++$relationId;
+ } else {
+ $objWriter->writeAttribute('location', str_replace('sheet://', '', $hyperlink->getUrl()));
+ }
+
+ if ($hyperlink->getTooltip() !== '') {
+ $objWriter->writeAttribute('tooltip', $hyperlink->getTooltip());
+ $objWriter->writeAttribute('display', $hyperlink->getTooltip());
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write ProtectedRanges.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeProtectedRanges(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ if (count($pSheet->getProtectedCells()) > 0) {
+ // protectedRanges
+ $objWriter->startElement('protectedRanges');
+
+ // Loop protectedRanges
+ foreach ($pSheet->getProtectedCells() as $protectedCell => $passwordHash) {
+ // protectedRange
+ $objWriter->startElement('protectedRange');
+ $objWriter->writeAttribute('name', 'p' . md5($protectedCell));
+ $objWriter->writeAttribute('sqref', $protectedCell);
+ if (!empty($passwordHash)) {
+ $objWriter->writeAttribute('password', $passwordHash);
+ }
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write MergeCells.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeMergeCells(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ if (count($pSheet->getMergeCells()) > 0) {
+ // mergeCells
+ $objWriter->startElement('mergeCells');
+
+ // Loop mergeCells
+ foreach ($pSheet->getMergeCells() as $mergeCell) {
+ // mergeCell
+ $objWriter->startElement('mergeCell');
+ $objWriter->writeAttribute('ref', $mergeCell);
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write PrintOptions.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writePrintOptions(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // printOptions
+ $objWriter->startElement('printOptions');
+
+ $objWriter->writeAttribute('gridLines', ($pSheet->getPrintGridlines() ? 'true' : 'false'));
+ $objWriter->writeAttribute('gridLinesSet', 'true');
+
+ if ($pSheet->getPageSetup()->getHorizontalCentered()) {
+ $objWriter->writeAttribute('horizontalCentered', 'true');
+ }
+
+ if ($pSheet->getPageSetup()->getVerticalCentered()) {
+ $objWriter->writeAttribute('verticalCentered', 'true');
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write PageMargins.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writePageMargins(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // pageMargins
+ $objWriter->startElement('pageMargins');
+ $objWriter->writeAttribute('left', StringHelper::formatNumber($pSheet->getPageMargins()->getLeft()));
+ $objWriter->writeAttribute('right', StringHelper::formatNumber($pSheet->getPageMargins()->getRight()));
+ $objWriter->writeAttribute('top', StringHelper::formatNumber($pSheet->getPageMargins()->getTop()));
+ $objWriter->writeAttribute('bottom', StringHelper::formatNumber($pSheet->getPageMargins()->getBottom()));
+ $objWriter->writeAttribute('header', StringHelper::formatNumber($pSheet->getPageMargins()->getHeader()));
+ $objWriter->writeAttribute('footer', StringHelper::formatNumber($pSheet->getPageMargins()->getFooter()));
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write AutoFilter.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeAutoFilter(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ $autoFilterRange = $pSheet->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]] = PhpspreadsheetWorksheet::extractSheetTitle($range[0], true);
+ $range = implode(':', $range);
+
+ $objWriter->writeAttribute('ref', str_replace('$', '', $range));
+
+ $columns = $pSheet->getAutoFilter()->getColumns();
+ if (count($columns) > 0) {
+ foreach ($columns as $columnID => $column) {
+ $rules = $column->getRules();
+ if (count($rules) > 0) {
+ $objWriter->startElement('filterColumn');
+ $objWriter->writeAttribute('colId', $pSheet->getAutoFilter()->getColumnOffset($columnID));
+
+ $objWriter->startElement($column->getFilterType());
+ if ($column->getJoin() == Column::AUTOFILTER_COLUMN_JOIN_AND) {
+ $objWriter->writeAttribute('and', 1);
+ }
+
+ foreach ($rules as $rule) {
+ 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
+ $objWriter->writeAttribute('val', $rule->getValue());
+ $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
+ foreach ($rule->getValue() as $key => $value) {
+ if ($value > '') {
+ $objWriter->writeAttribute($key, $value);
+ }
+ }
+ $objWriter->writeAttribute('dateTimeGrouping', $rule->getGrouping());
+ } else {
+ $objWriter->writeAttribute('val', $rule->getValue());
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+ }
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write PageSetup.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writePageSetup(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // pageSetup
+ $objWriter->startElement('pageSetup');
+ $objWriter->writeAttribute('paperSize', $pSheet->getPageSetup()->getPaperSize());
+ $objWriter->writeAttribute('orientation', $pSheet->getPageSetup()->getOrientation());
+
+ if ($pSheet->getPageSetup()->getScale() !== null) {
+ $objWriter->writeAttribute('scale', $pSheet->getPageSetup()->getScale());
+ }
+ if ($pSheet->getPageSetup()->getFitToHeight() !== null) {
+ $objWriter->writeAttribute('fitToHeight', $pSheet->getPageSetup()->getFitToHeight());
+ } else {
+ $objWriter->writeAttribute('fitToHeight', '0');
+ }
+ if ($pSheet->getPageSetup()->getFitToWidth() !== null) {
+ $objWriter->writeAttribute('fitToWidth', $pSheet->getPageSetup()->getFitToWidth());
+ } else {
+ $objWriter->writeAttribute('fitToWidth', '0');
+ }
+ if ($pSheet->getPageSetup()->getFirstPageNumber() !== null) {
+ $objWriter->writeAttribute('firstPageNumber', $pSheet->getPageSetup()->getFirstPageNumber());
+ $objWriter->writeAttribute('useFirstPageNumber', '1');
+ }
+
+ $getUnparsedLoadedData = $pSheet->getParent()->getUnparsedLoadedData();
+ if (isset($getUnparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId'])) {
+ $objWriter->writeAttribute('r:id', $getUnparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId']);
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Header / Footer.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeHeaderFooter(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // headerFooter
+ $objWriter->startElement('headerFooter');
+ $objWriter->writeAttribute('differentOddEven', ($pSheet->getHeaderFooter()->getDifferentOddEven() ? 'true' : 'false'));
+ $objWriter->writeAttribute('differentFirst', ($pSheet->getHeaderFooter()->getDifferentFirst() ? 'true' : 'false'));
+ $objWriter->writeAttribute('scaleWithDoc', ($pSheet->getHeaderFooter()->getScaleWithDocument() ? 'true' : 'false'));
+ $objWriter->writeAttribute('alignWithMargins', ($pSheet->getHeaderFooter()->getAlignWithMargins() ? 'true' : 'false'));
+
+ $objWriter->writeElement('oddHeader', $pSheet->getHeaderFooter()->getOddHeader());
+ $objWriter->writeElement('oddFooter', $pSheet->getHeaderFooter()->getOddFooter());
+ $objWriter->writeElement('evenHeader', $pSheet->getHeaderFooter()->getEvenHeader());
+ $objWriter->writeElement('evenFooter', $pSheet->getHeaderFooter()->getEvenFooter());
+ $objWriter->writeElement('firstHeader', $pSheet->getHeaderFooter()->getFirstHeader());
+ $objWriter->writeElement('firstFooter', $pSheet->getHeaderFooter()->getFirstFooter());
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Breaks.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeBreaks(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // Get row and column breaks
+ $aRowBreaks = [];
+ $aColumnBreaks = [];
+ foreach ($pSheet->getBreaks() as $cell => $breakType) {
+ if ($breakType == PhpspreadsheetWorksheet::BREAK_ROW) {
+ $aRowBreaks[] = $cell;
+ } elseif ($breakType == PhpspreadsheetWorksheet::BREAK_COLUMN) {
+ $aColumnBreaks[] = $cell;
+ }
+ }
+
+ // rowBreaks
+ if (!empty($aRowBreaks)) {
+ $objWriter->startElement('rowBreaks');
+ $objWriter->writeAttribute('count', count($aRowBreaks));
+ $objWriter->writeAttribute('manualBreakCount', count($aRowBreaks));
+
+ foreach ($aRowBreaks as $cell) {
+ $coords = Coordinate::coordinateFromString($cell);
+
+ $objWriter->startElement('brk');
+ $objWriter->writeAttribute('id', $coords[1]);
+ $objWriter->writeAttribute('man', '1');
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ // Second, write column breaks
+ if (!empty($aColumnBreaks)) {
+ $objWriter->startElement('colBreaks');
+ $objWriter->writeAttribute('count', count($aColumnBreaks));
+ $objWriter->writeAttribute('manualBreakCount', count($aColumnBreaks));
+
+ foreach ($aColumnBreaks as $cell) {
+ $coords = Coordinate::coordinateFromString($cell);
+
+ $objWriter->startElement('brk');
+ $objWriter->writeAttribute('id', Coordinate::columnIndexFromString($coords[0]) - 1);
+ $objWriter->writeAttribute('man', '1');
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write SheetData.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ * @param string[] $pStringTable String table
+ */
+ private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, array $pStringTable): void
+ {
+ // Flipped stringtable, for faster index searching
+ $aFlippedStringTable = $this->getParentWriter()->getWriterPart('stringtable')->flipStringTable($pStringTable);
+
+ // sheetData
+ $objWriter->startElement('sheetData');
+
+ // Get column count
+ $colCount = Coordinate::columnIndexFromString($pSheet->getHighestColumn());
+
+ // Highest row number
+ $highestRow = $pSheet->getHighestRow();
+
+ // Loop through cells
+ $cellsByRow = [];
+ foreach ($pSheet->getCoordinates() as $coordinate) {
+ $cellAddress = Coordinate::coordinateFromString($coordinate);
+ $cellsByRow[$cellAddress[1]][] = $coordinate;
+ }
+
+ $currentRow = 0;
+ while ($currentRow++ < $highestRow) {
+ // Get row dimension
+ $rowDimension = $pSheet->getRowDimension($currentRow);
+
+ // Write current row?
+ $writeCurrentRow = isset($cellsByRow[$currentRow]) || $rowDimension->getRowHeight() >= 0 || $rowDimension->getVisible() == false || $rowDimension->getCollapsed() == true || $rowDimension->getOutlineLevel() > 0 || $rowDimension->getXfIndex() !== null;
+
+ if ($writeCurrentRow) {
+ // Start a new row
+ $objWriter->startElement('row');
+ $objWriter->writeAttribute('r', $currentRow);
+ $objWriter->writeAttribute('spans', '1:' . $colCount);
+
+ // Row dimensions
+ if ($rowDimension->getRowHeight() >= 0) {
+ $objWriter->writeAttribute('customHeight', '1');
+ $objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight()));
+ }
+
+ // Row visibility
+ if (!$rowDimension->getVisible() === true) {
+ $objWriter->writeAttribute('hidden', 'true');
+ }
+
+ // Collapsed
+ if ($rowDimension->getCollapsed() === true) {
+ $objWriter->writeAttribute('collapsed', 'true');
+ }
+
+ // Outline level
+ if ($rowDimension->getOutlineLevel() > 0) {
+ $objWriter->writeAttribute('outlineLevel', $rowDimension->getOutlineLevel());
+ }
+
+ // Style
+ if ($rowDimension->getXfIndex() !== null) {
+ $objWriter->writeAttribute('s', $rowDimension->getXfIndex());
+ $objWriter->writeAttribute('customFormat', '1');
+ }
+
+ // Write cells
+ if (isset($cellsByRow[$currentRow])) {
+ foreach ($cellsByRow[$currentRow] as $cellAddress) {
+ // Write cell
+ $this->writeCell($objWriter, $pSheet, $cellAddress, $aFlippedStringTable);
+ }
+ }
+
+ // End row
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * @param RichText|string $cellValue
+ */
+ private function writeCellInlineStr(XMLWriter $objWriter, string $mappedType, $cellValue): void
+ {
+ $objWriter->writeAttribute('t', $mappedType);
+ if (!$cellValue instanceof RichText) {
+ $objWriter->writeElement('t', StringHelper::controlCharacterPHP2OOXML(htmlspecialchars($cellValue)));
+ } elseif ($cellValue instanceof RichText) {
+ $objWriter->startElement('is');
+ $this->getParentWriter()->getWriterPart('stringtable')->writeRichText($objWriter, $cellValue);
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * @param RichText|string $cellValue
+ * @param string[] $pFlippedStringTable
+ */
+ private function writeCellString(XMLWriter $objWriter, string $mappedType, $cellValue, array $pFlippedStringTable): void
+ {
+ $objWriter->writeAttribute('t', $mappedType);
+ if (!$cellValue instanceof RichText) {
+ self::writeElementIf($objWriter, isset($pFlippedStringTable[$cellValue]), 'v', $pFlippedStringTable[$cellValue]);
+ } else {
+ $objWriter->writeElement('v', $pFlippedStringTable[$cellValue->getHashCode()]);
+ }
+ }
+
+ /**
+ * @param float|int $cellValue
+ */
+ private function writeCellNumeric(XMLWriter $objWriter, $cellValue): void
+ {
+ //force a decimal to be written if the type is float
+ if (is_float($cellValue)) {
+ // force point as decimal separator in case current locale uses comma
+ $cellValue = str_replace(',', '.', (string) $cellValue);
+ if (strpos($cellValue, '.') === false) {
+ $cellValue = $cellValue . '.0';
+ }
+ }
+ $objWriter->writeElement('v', $cellValue);
+ }
+
+ private function writeCellBoolean(XMLWriter $objWriter, string $mappedType, bool $cellValue): void
+ {
+ $objWriter->writeAttribute('t', $mappedType);
+ $objWriter->writeElement('v', $cellValue ? '1' : '0');
+ }
+
+ private function writeCellError(XMLWriter $objWriter, string $mappedType, string $cellValue, string $formulaerr = '#NULL!'): void
+ {
+ $objWriter->writeAttribute('t', $mappedType);
+ $cellIsFormula = substr($cellValue, 0, 1) === '=';
+ self::writeElementIf($objWriter, $cellIsFormula, 'f', Xlfn::addXlfnStripEquals($cellValue));
+ $objWriter->writeElement('v', $cellIsFormula ? $formulaerr : $cellValue);
+ }
+
+ private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell $pCell): void
+ {
+ $calculatedValue = $this->getParentWriter()->getPreCalculateFormulas() ? $pCell->getCalculatedValue() : $cellValue;
+ if (is_string($calculatedValue)) {
+ if (substr($calculatedValue, 0, 1) === '#') {
+ $this->writeCellError($objWriter, 'e', $cellValue, $calculatedValue);
+
+ return;
+ }
+ $objWriter->writeAttribute('t', 'str');
+ } elseif (is_bool($calculatedValue)) {
+ $objWriter->writeAttribute('t', 'b');
+ }
+ // array values are not yet supported
+ //$attributes = $pCell->getFormulaAttributes();
+ //if (($attributes['t'] ?? null) === 'array') {
+ // $objWriter->startElement('f');
+ // $objWriter->writeAttribute('t', 'array');
+ // $objWriter->writeAttribute('ref', $pCellAddress);
+ // $objWriter->writeAttribute('aca', '1');
+ // $objWriter->writeAttribute('ca', '1');
+ // $objWriter->text(substr($cellValue, 1));
+ // $objWriter->endElement();
+ //} else {
+ // $objWriter->writeElement('f', Xlfn::addXlfnStripEquals($cellValue));
+ //}
+ $objWriter->writeElement('f', Xlfn::addXlfnStripEquals($cellValue));
+ self::writeElementIf(
+ $objWriter,
+ $this->getParentWriter()->getOffice2003Compatibility() === false,
+ 'v',
+ ($this->getParentWriter()->getPreCalculateFormulas() && !is_array($calculatedValue) && substr($calculatedValue, 0, 1) !== '#')
+ ? StringHelper::formatNumber($calculatedValue) : '0'
+ );
+ }
+
+ /**
+ * Write Cell.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ * @param string $pCellAddress Cell Address
+ * @param string[] $pFlippedStringTable String table (flipped), for faster index searching
+ */
+ private function writeCell(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, string $pCellAddress, array $pFlippedStringTable): void
+ {
+ // Cell
+ $pCell = $pSheet->getCell($pCellAddress);
+ $objWriter->startElement('c');
+ $objWriter->writeAttribute('r', $pCellAddress);
+
+ // Sheet styles
+ $xfi = $pCell->getXfIndex();
+ self::writeAttributeIf($objWriter, $xfi, 's', $xfi);
+
+ // If cell value is supplied, write cell value
+ $cellValue = $pCell->getValue();
+ if (is_object($cellValue) || $cellValue !== '') {
+ // Map type
+ $mappedType = $pCell->getDataType();
+
+ // Write data depending on its type
+ switch (strtolower($mappedType)) {
+ case 'inlinestr': // Inline string
+ $this->writeCellInlineStr($objWriter, $mappedType, $cellValue);
+
+ break;
+ case 's': // String
+ $this->writeCellString($objWriter, $mappedType, $cellValue, $pFlippedStringTable);
+
+ break;
+ case 'f': // Formula
+ $this->writeCellFormula($objWriter, $cellValue, $pCell);
+
+ break;
+ case 'n': // Numeric
+ $this->writeCellNumeric($objWriter, $cellValue);
+
+ break;
+ case 'b': // Boolean
+ $this->writeCellBoolean($objWriter, $mappedType, $cellValue);
+
+ break;
+ case 'e': // Error
+ $this->writeCellError($objWriter, $mappedType, $cellValue);
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Drawings.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ * @param bool $includeCharts Flag indicating if we should include drawing details for charts
+ */
+ private function writeDrawings(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, $includeCharts = false): void
+ {
+ $unparsedLoadedData = $pSheet->getParent()->getUnparsedLoadedData();
+ $hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds']);
+ $chartCount = ($includeCharts) ? $pSheet->getChartCollection()->count() : 0;
+ if ($chartCount == 0 && $pSheet->getDrawingCollection()->count() == 0 && !$hasUnparsedDrawing) {
+ return;
+ }
+
+ // If sheet contains drawings, add the relationships
+ $objWriter->startElement('drawing');
+
+ $rId = 'rId1';
+ if (isset($unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'])) {
+ $drawingOriginalIds = $unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'];
+ // take first. In future can be overriten
+ // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels::writeWorksheetRelationships)
+ $rId = reset($drawingOriginalIds);
+ }
+
+ $objWriter->writeAttribute('r:id', $rId);
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write LegacyDrawing.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeLegacyDrawing(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // If sheet contains comments, add the relationships
+ if (count($pSheet->getComments()) > 0) {
+ $objWriter->startElement('legacyDrawing');
+ $objWriter->writeAttribute('r:id', 'rId_comments_vml1');
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write LegacyDrawingHF.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeLegacyDrawingHF(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // If sheet contains images, add the relationships
+ if (count($pSheet->getHeaderFooter()->getImages()) > 0) {
+ $objWriter->startElement('legacyDrawingHF');
+ $objWriter->writeAttribute('r:id', 'rId_headerfooter_vml1');
+ $objWriter->endElement();
+ }
+ }
+
+ private function writeAlternateContent(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ if (empty($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'])) {
+ return;
+ }
+
+ foreach ($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'] as $alternateContent) {
+ $objWriter->writeRaw($alternateContent);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php
new file mode 100755
index 0000000..a9137df
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+abstract class WriterPart
+{
+ /**
+ * Parent Xlsx object.
+ *
+ * @var Xlsx
+ */
+ private $parentWriter;
+
+ /**
+ * Get parent Xlsx object.
+ *
+ * @return Xlsx
+ */
+ public function getParentWriter()
+ {
+ return $this->parentWriter;
+ }
+
+ /**
+ * Set parent Xlsx object.
+ */
+ public function __construct(Xlsx $pWriter)
+ {
+ $this->parentWriter = $pWriter;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php
new file mode 100755
index 0000000..8f7c07e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php
@@ -0,0 +1,159 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+class Xlfn
+{
+ const XLFNREGEXP = '/(?<!_xlfn[.])\\b('
+ // functions added with Excel 2010
+ . 'beta[.]dist'
+ . '|beta[.]inv'
+ . '|binom[.]dist'
+ . '|binom[.]inv'
+ . '|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'
+ . '|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'
+ . '|sequence'
+ . '|sort'
+ . '|sortby'
+ . '|unique'
+ . '|xlookup'
+ . '|xmatch'
+ . ')(?=\\s*[(])/i';
+
+ /**
+ * Prefix function name in string with _xlfn. where required.
+ */
+ public static function addXlfn(string $funcstring): string
+ {
+ return preg_replace(self::XLFNREGEXP, '_xlfn.$1', $funcstring);
+ }
+
+ /**
+ * Prefix function name in string with _xlfn. where required.
+ * Leading character, expected to be equals sign, is stripped.
+ */
+ public static function addXlfnStripEquals(string $funcstring): string
+ {
+ return self::addXlfn(substr($funcstring, 1));
+ }
+}
diff --git a/vendor/pimple/pimple/.gitignore b/vendor/pimple/pimple/.gitignore
new file mode 100755
index 0000000..c089b09
--- /dev/null
+++ b/vendor/pimple/pimple/.gitignore
@@ -0,0 +1,3 @@
+phpunit.xml
+composer.lock
+/vendor/
diff --git a/vendor/pimple/pimple/.travis.yml b/vendor/pimple/pimple/.travis.yml
new file mode 100755
index 0000000..196f7fc
--- /dev/null
+++ b/vendor/pimple/pimple/.travis.yml
@@ -0,0 +1,40 @@
+language: php
+
+env:
+ matrix:
+ - PIMPLE_EXT=no
+ - PIMPLE_EXT=yes
+ global:
+ - REPORT_EXIT_STATUS=1
+
+php:
+ - 5.3
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+
+before_script:
+ - composer self-update
+ - COMPOSER_ROOT_VERSION=dev-master composer install
+ - if [ "$PIMPLE_EXT" == "yes" ]; then sh -c "cd ext/pimple && phpize && ./configure && make && sudo make install"; fi
+ - if [ "$PIMPLE_EXT" == "yes" ]; then echo "extension=pimple.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`; fi
+
+script:
+ - cd ext/pimple
+ - if [ "$PIMPLE_EXT" == "yes" ]; then yes n | make test | tee output ; grep -E 'Tests failed +. +0' output; fi
+ - if [ "$PIMPLE_EXT" == "yes" ]; then export SYMFONY_DEPRECATIONS_HELPER=weak; fi
+ - cd ../..
+ - ./vendor/bin/simple-phpunit
+
+matrix:
+ include:
+ - php: hhvm
+ dist: trusty
+ env: PIMPLE_EXT=no
+ exclude:
+ - php: 7.0
+ env: PIMPLE_EXT=yes
+ - php: 7.1
+ env: PIMPLE_EXT=yes
diff --git a/vendor/pimple/pimple/CHANGELOG b/vendor/pimple/pimple/CHANGELOG
new file mode 100755
index 0000000..ba56760
--- /dev/null
+++ b/vendor/pimple/pimple/CHANGELOG
@@ -0,0 +1,59 @@
+* 3.2.3 (2017-XX-XX)
+
+ * n/a
+
+* 3.2.2 (2017-07-23)
+
+ * reverted extending a protected closure throws an exception (deprecated it instead)
+
+* 3.2.1 (2017-07-17)
+
+ * fixed PHP error
+
+* 3.2.0 (2017-07-17)
+
+ * added a PSR-11 service locator
+ * added a PSR-11 wrapper
+ * added ServiceIterator
+ * fixed extending a protected closure (now throws InvalidServiceIdentifierException)
+
+* 3.1.0 (2017-07-03)
+
+ * deprecated the C extension
+ * added support for PSR-11 exceptions
+
+* 3.0.2 (2015-09-11)
+
+ * refactored the C extension
+ * minor non-significant changes
+
+* 3.0.1 (2015-07-30)
+
+ * simplified some code
+ * fixed a segfault in the C extension
+
+* 3.0.0 (2014-07-24)
+
+ * removed the Pimple class alias (use Pimple\Container instead)
+
+* 2.1.1 (2014-07-24)
+
+ * fixed compiler warnings for the C extension
+ * fixed code when dealing with circular references
+
+* 2.1.0 (2014-06-24)
+
+ * moved the Pimple to Pimple\Container (with a BC layer -- Pimple is now a
+ deprecated alias which will be removed in Pimple 3.0)
+ * added Pimple\ServiceProviderInterface (and Pimple::register())
+
+* 2.0.0 (2014-02-10)
+
+ * changed extend to automatically re-assign the extended service and keep it as shared or factory
+ (to keep BC, extend still returns the extended service)
+ * changed services to be shared by default (use factory() for factory
+ services)
+
+* 1.0.0
+
+ * initial version
diff --git a/vendor/pimple/pimple/LICENSE b/vendor/pimple/pimple/LICENSE
new file mode 100755
index 0000000..e02dc5a
--- /dev/null
+++ b/vendor/pimple/pimple/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2009-2017 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/pimple/pimple/README.rst b/vendor/pimple/pimple/README.rst
new file mode 100755
index 0000000..a03b6d3
--- /dev/null
+++ b/vendor/pimple/pimple/README.rst
@@ -0,0 +1,326 @@
+Pimple
+======
+
+.. caution::
+
+ This is the documentation for Pimple 3.x. If you are using Pimple 1.x, read
+ the `Pimple 1.x documentation`_. Reading the Pimple 1.x code is also a good
+ way to learn more about how to create a simple Dependency Injection
+ Container (recent versions of Pimple are more focused on performance).
+
+Pimple is a small Dependency Injection Container for PHP.
+
+Installation
+------------
+
+Before using Pimple in your project, add it to your ``composer.json`` file:
+
+.. code-block:: bash
+
+ $ ./composer.phar require pimple/pimple "^3.0"
+
+Usage
+-----
+
+Creating a container is a matter of creating a ``Container`` instance:
+
+.. code-block:: php
+
+ use Pimple\Container;
+
+ $container = new Container();
+
+As many other dependency injection containers, Pimple manages two different
+kind of data: **services** and **parameters**.
+
+Defining Services
+~~~~~~~~~~~~~~~~~
+
+A service is an object that does something as part of a larger system. Examples
+of services: a database connection, a templating engine, or a mailer. Almost
+any **global** object can be a service.
+
+Services are defined by **anonymous functions** that return an instance of an
+object:
+
+.. code-block:: php
+
+ // define some services
+ $container['session_storage'] = function ($c) {
+ return new SessionStorage('SESSION_ID');
+ };
+
+ $container['session'] = function ($c) {
+ return new Session($c['session_storage']);
+ };
+
+Notice that the anonymous function has access to the current container
+instance, allowing references to other services or parameters.
+
+As objects are only created when you get them, the order of the definitions
+does not matter.
+
+Using the defined services is also very easy:
+
+.. code-block:: php
+
+ // get the session object
+ $session = $container['session'];
+
+ // the above call is roughly equivalent to the following code:
+ // $storage = new SessionStorage('SESSION_ID');
+ // $session = new Session($storage);
+
+Defining Factory Services
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, each time you get a service, Pimple returns the **same instance**
+of it. If you want a different instance to be returned for all calls, wrap your
+anonymous function with the ``factory()`` method
+
+.. code-block:: php
+
+ $container['session'] = $container->factory(function ($c) {
+ return new Session($c['session_storage']);
+ });
+
+Now, each call to ``$container['session']`` returns a new instance of the
+session.
+
+Defining Parameters
+~~~~~~~~~~~~~~~~~~~
+
+Defining a parameter allows to ease the configuration of your container from
+the outside and to store global values:
+
+.. code-block:: php
+
+ // define some parameters
+ $container['cookie_name'] = 'SESSION_ID';
+ $container['session_storage_class'] = 'SessionStorage';
+
+If you change the ``session_storage`` service definition like below:
+
+.. code-block:: php
+
+ $container['session_storage'] = function ($c) {
+ return new $c['session_storage_class']($c['cookie_name']);
+ };
+
+You can now easily change the cookie name by overriding the
+``cookie_name`` parameter instead of redefining the service
+definition.
+
+Protecting Parameters
+~~~~~~~~~~~~~~~~~~~~~
+
+Because Pimple sees anonymous functions as service definitions, you need to
+wrap anonymous functions with the ``protect()`` method to store them as
+parameters:
+
+.. code-block:: php
+
+ $container['random_func'] = $container->protect(function () {
+ return rand();
+ });
+
+Modifying Services after Definition
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In some cases you may want to modify a service definition after it has been
+defined. You can use the ``extend()`` method to define additional code to be
+run on your service just after it is created:
+
+.. code-block:: php
+
+ $container['session_storage'] = function ($c) {
+ return new $c['session_storage_class']($c['cookie_name']);
+ };
+
+ $container->extend('session_storage', function ($storage, $c) {
+ $storage->...();
+
+ return $storage;
+ });
+
+The first argument is the name of the service to extend, the second a function
+that gets access to the object instance and the container.
+
+Extending a Container
+~~~~~~~~~~~~~~~~~~~~~
+
+If you use the same libraries over and over, you might want to reuse some
+services from one project to the next one; package your services into a
+**provider** by implementing ``Pimple\ServiceProviderInterface``:
+
+.. code-block:: php
+
+ use Pimple\Container;
+
+ class FooProvider implements Pimple\ServiceProviderInterface
+ {
+ public function register(Container $pimple)
+ {
+ // register some services and parameters
+ // on $pimple
+ }
+ }
+
+Then, register the provider on a Container:
+
+.. code-block:: php
+
+ $pimple->register(new FooProvider());
+
+Fetching the Service Creation Function
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When you access an object, Pimple automatically calls the anonymous function
+that you defined, which creates the service object for you. If you want to get
+raw access to this function, you can use the ``raw()`` method:
+
+.. code-block:: php
+
+ $container['session'] = function ($c) {
+ return new Session($c['session_storage']);
+ };
+
+ $sessionFunction = $container->raw('session');
+
+PSR-11 compatibility
+--------------------
+
+For historical reasons, the ``Container`` class does not implement the PSR-11
+``ContainerInterface``. However, Pimple provides a helper class that will let
+you decouple your code from the Pimple container class.
+
+The PSR-11 container class
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``Pimple\Psr11\Container`` class lets you access the content of an
+underlying Pimple container using ``Psr\Container\ContainerInterface``
+methods:
+
+.. code-block:: php
+
+ use Pimple\Container;
+ use Pimple\Psr11\Container as PsrContainer;
+
+ $container = new Container();
+ $container['service'] = function ($c) {
+ return new Service();
+ };
+ $psr11 = new PsrContainer($container);
+
+ $controller = function (PsrContainer $container) {
+ $service = $container->get('service');
+ };
+ $controller($psr11);
+
+Using the PSR-11 ServiceLocator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometimes, a service needs access to several other services without being sure
+that all of them will actually be used. In those cases, you may want the
+instantiation of the services to be lazy.
+
+The traditional solution is to inject the entire service container to get only
+the services really needed. However, this is not recommended because it gives
+services a too broad access to the rest of the application and it hides their
+actual dependencies.
+
+The ``ServiceLocator`` is intended to solve this problem by giving access to a
+set of predefined services while instantiating them only when actually needed.
+
+It also allows you to make your services available under a different name than
+the one used to register them. For instance, you may want to use an object
+that expects an instance of ``EventDispatcherInterface`` to be available under
+the name ``event_dispatcher`` while your event dispatcher has been
+registered under the name ``dispatcher``:
+
+.. code-block:: php
+
+ use Monolog\Logger;
+ use Pimple\Psr11\ServiceLocator;
+ use Psr\Container\ContainerInterface;
+ use Symfony\Component\EventDispatcher\EventDispatcher;
+
+ class MyService
+ {
+ /**
+ * "logger" must be an instance of Psr\Log\LoggerInterface
+ * "event_dispatcher" must be an instance of Symfony\Component\EventDispatcher\EventDispatcherInterface
+ */
+ private $services;
+
+ public function __construct(ContainerInterface $services)
+ {
+ $this->services = $services;
+ }
+ }
+
+ $container['logger'] = function ($c) {
+ return new Monolog\Logger();
+ };
+ $container['dispatcher'] = function () {
+ return new EventDispatcher();
+ };
+
+ $container['service'] = function ($c) {
+ $locator = new ServiceLocator($c, array('logger', 'event_dispatcher' => 'dispatcher'));
+
+ return new MyService($locator);
+ };
+
+Referencing a Collection of Services Lazily
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Passing a collection of services instances in an array may prove inefficient
+if the class that consumes the collection only needs to iterate over it at a
+later stage, when one of its method is called. It can also lead to problems
+if there is a circular dependency between one of the services stored in the
+collection and the class that consumes it.
+
+The ``ServiceIterator`` class helps you solve these issues. It receives a
+list of service names during instantiation and will retrieve the services
+when iterated over:
+
+.. code-block:: php
+
+ use Pimple\Container;
+ use Pimple\ServiceIterator;
+
+ class AuthorizationService
+ {
+ private $voters;
+
+ public function __construct($voters)
+ {
+ $this->voters = $voters;
+ }
+
+ public function canAccess($resource)
+ {
+ foreach ($this->voters as $voter) {
+ if (true === $voter->canAccess($resource) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ $container = new Container();
+
+ $container['voter1'] = function ($c) {
+ return new SomeVoter();
+ }
+ $container['voter2'] = function ($c) {
+ return new SomeOtherVoter($c['auth']);
+ }
+ $container['auth'] = function ($c) {
+ return new AuthorizationService(new ServiceIterator($c, array('voter1', 'voter2'));
+ }
+
+.. _Pimple 1.x documentation: https://github.com/silexphp/Pimple/tree/1.1
diff --git a/vendor/pimple/pimple/composer.json b/vendor/pimple/pimple/composer.json
new file mode 100755
index 0000000..dabf190
--- /dev/null
+++ b/vendor/pimple/pimple/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "pimple/pimple",
+ "type": "library",
+ "description": "Pimple, a simple Dependency Injection Container",
+ "keywords": ["dependency injection", "container"],
+ "homepage": "http://pimple.sensiolabs.org",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0",
+ "psr/container": "^1.0"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "^3.2"
+ },
+ "autoload": {
+ "psr-0": { "Pimple": "src/" }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2.x-dev"
+ }
+ }
+}
diff --git a/vendor/pimple/pimple/ext/pimple/.gitignore b/vendor/pimple/pimple/ext/pimple/.gitignore
new file mode 100755
index 0000000..1861088
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/.gitignore
@@ -0,0 +1,30 @@
+*.sw*
+.deps
+Makefile
+Makefile.fragments
+Makefile.global
+Makefile.objects
+acinclude.m4
+aclocal.m4
+build/
+config.cache
+config.guess
+config.h
+config.h.in
+config.log
+config.nice
+config.status
+config.sub
+configure
+configure.in
+install-sh
+libtool
+ltmain.sh
+missing
+mkinstalldirs
+run-tests.php
+*.loT
+.libs/
+modules/
+*.la
+*.lo
diff --git a/vendor/pimple/pimple/ext/pimple/README.md b/vendor/pimple/pimple/ext/pimple/README.md
new file mode 100755
index 0000000..7b39eb2
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/README.md
@@ -0,0 +1,12 @@
+This is Pimple 2 implemented in C
+
+* PHP >= 5.3
+* Not tested under Windows, might work
+
+Install
+=======
+
+ > phpize
+ > ./configure
+ > make
+ > make install
diff --git a/vendor/pimple/pimple/ext/pimple/config.m4 b/vendor/pimple/pimple/ext/pimple/config.m4
new file mode 100755
index 0000000..3a6e9aa
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/config.m4
@@ -0,0 +1,63 @@
+dnl $Id$
+dnl config.m4 for extension pimple
+
+dnl Comments in this file start with the string 'dnl'.
+dnl Remove where necessary. This file will not work
+dnl without editing.
+
+dnl If your extension references something external, use with:
+
+dnl PHP_ARG_WITH(pimple, for pimple support,
+dnl Make sure that the comment is aligned:
+dnl [ --with-pimple Include pimple support])
+
+dnl Otherwise use enable:
+
+PHP_ARG_ENABLE(pimple, whether to enable pimple support,
+dnl Make sure that the comment is aligned:
+[ --enable-pimple Enable pimple support])
+
+if test "$PHP_PIMPLE" != "no"; then
+ dnl Write more examples of tests here...
+
+ dnl # --with-pimple -> check with-path
+ dnl SEARCH_PATH="/usr/local /usr" # you might want to change this
+ dnl SEARCH_FOR="/include/pimple.h" # you most likely want to change this
+ dnl if test -r $PHP_PIMPLE/$SEARCH_FOR; then # path given as parameter
+ dnl PIMPLE_DIR=$PHP_PIMPLE
+ dnl else # search default path list
+ dnl AC_MSG_CHECKING([for pimple files in default path])
+ dnl for i in $SEARCH_PATH ; do
+ dnl if test -r $i/$SEARCH_FOR; then
+ dnl PIMPLE_DIR=$i
+ dnl AC_MSG_RESULT(found in $i)
+ dnl fi
+ dnl done
+ dnl fi
+ dnl
+ dnl if test -z "$PIMPLE_DIR"; then
+ dnl AC_MSG_RESULT([not found])
+ dnl AC_MSG_ERROR([Please reinstall the pimple distribution])
+ dnl fi
+
+ dnl # --with-pimple -> add include path
+ dnl PHP_ADD_INCLUDE($PIMPLE_DIR/include)
+
+ dnl # --with-pimple -> check for lib and symbol presence
+ dnl LIBNAME=pimple # you may want to change this
+ dnl LIBSYMBOL=pimple # you most likely want to change this
+
+ dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
+ dnl [
+ dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $PIMPLE_DIR/lib, PIMPLE_SHARED_LIBADD)
+ dnl AC_DEFINE(HAVE_PIMPLELIB,1,[ ])
+ dnl ],[
+ dnl AC_MSG_ERROR([wrong pimple lib version or lib not found])
+ dnl ],[
+ dnl -L$PIMPLE_DIR/lib -lm
+ dnl ])
+ dnl
+ dnl PHP_SUBST(PIMPLE_SHARED_LIBADD)
+
+ PHP_NEW_EXTENSION(pimple, pimple.c, $ext_shared)
+fi
diff --git a/vendor/pimple/pimple/ext/pimple/config.w32 b/vendor/pimple/pimple/ext/pimple/config.w32
new file mode 100755
index 0000000..39857b3
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/config.w32
@@ -0,0 +1,13 @@
+// $Id$
+// vim:ft=javascript
+
+// If your extension references something external, use ARG_WITH
+// ARG_WITH("pimple", "for pimple support", "no");
+
+// Otherwise, use ARG_ENABLE
+// ARG_ENABLE("pimple", "enable pimple support", "no");
+
+if (PHP_PIMPLE != "no") {
+ EXTENSION("pimple", "pimple.c");
+}
+
diff --git a/vendor/pimple/pimple/ext/pimple/php_pimple.h b/vendor/pimple/pimple/ext/pimple/php_pimple.h
new file mode 100755
index 0000000..eed7c17
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/php_pimple.h
@@ -0,0 +1,137 @@
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2014 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef PHP_PIMPLE_H
+#define PHP_PIMPLE_H
+
+extern zend_module_entry pimple_module_entry;
+#define phpext_pimple_ptr &pimple_module_entry
+
+#ifdef PHP_WIN32
+# define PHP_PIMPLE_API __declspec(dllexport)
+#elif defined(__GNUC__) && __GNUC__ >= 4
+# define PHP_PIMPLE_API __attribute__ ((visibility("default")))
+#else
+# define PHP_PIMPLE_API
+#endif
+
+#ifdef ZTS
+#include "TSRM.h"
+#endif
+
+#define PIMPLE_VERSION "3.2.3-DEV"
+
+#define PIMPLE_NS "Pimple"
+#define PSR_CONTAINER_NS "Psr\\Container"
+#define PIMPLE_EXCEPTION_NS "Pimple\\Exception"
+
+#define PIMPLE_DEFAULT_ZVAL_CACHE_NUM 5
+#define PIMPLE_DEFAULT_ZVAL_VALUES_NUM 10
+
+#define PIMPLE_DEPRECATE do { \
+ int er = EG(error_reporting); \
+ EG(error_reporting) = 0;\
+ php_error(E_DEPRECATED, "The Pimple C extension is deprecated since version 3.1 and will be removed in 4.0."); \
+ EG(error_reporting) = er; \
+} while (0);
+
+zend_module_entry *get_module(void);
+
+PHP_MINIT_FUNCTION(pimple);
+PHP_MINFO_FUNCTION(pimple);
+
+PHP_METHOD(FrozenServiceException, __construct);
+PHP_METHOD(InvalidServiceIdentifierException, __construct);
+PHP_METHOD(UnknownIdentifierException, __construct);
+
+PHP_METHOD(Pimple, __construct);
+PHP_METHOD(Pimple, factory);
+PHP_METHOD(Pimple, protect);
+PHP_METHOD(Pimple, raw);
+PHP_METHOD(Pimple, extend);
+PHP_METHOD(Pimple, keys);
+PHP_METHOD(Pimple, register);
+PHP_METHOD(Pimple, offsetSet);
+PHP_METHOD(Pimple, offsetUnset);
+PHP_METHOD(Pimple, offsetGet);
+PHP_METHOD(Pimple, offsetExists);
+
+PHP_METHOD(PimpleClosure, invoker);
+
+typedef struct _pimple_bucket_value {
+ zval *value; /* Must be the first element */
+ zval *raw;
+ zend_object_handle handle_num;
+ enum {
+ PIMPLE_IS_PARAM = 0,
+ PIMPLE_IS_SERVICE = 2
+ } type;
+ zend_bool initialized;
+ zend_fcall_info_cache fcc;
+} pimple_bucket_value;
+
+typedef struct _pimple_object {
+ zend_object zobj;
+ HashTable values;
+ HashTable factories;
+ HashTable protected;
+} pimple_object;
+
+typedef struct _pimple_closure_object {
+ zend_object zobj;
+ zval *callable;
+ zval *factory;
+} pimple_closure_object;
+
+static const char sensiolabs_logo[] = "<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHYAAAAUCAMAAABvRTlyAAAAz1BMVEUAAAAAAAAAAAAsThWB5j4AAACD6T8AAACC6D+C6D6C6D+C6D4AAAAAAACC6D4AAAAAAACC6D8AAAAAAAAAAAAAAAAAAAAAAACC6D4AAAAAAAAAAACC6D4AAAAAAAAAAAAAAAAAAAAAAACC6D8AAACC6D4AAAAAAAAAAAAAAAAAAACC6D8AAACC6D6C6D+B6D+C6D+C6D+C6D8AAACC6D6C6D4AAACC6D/K/2KC6D+B6D6C6D6C6D+C6D8sTxUyWRhEeiEAAACC6D+C5z6B6D7drnEVAAAAQXRSTlMAE3oCNSUuDHFHzxaF9UFsu+irX+zlKzYimaJXktyOSFD6BolxqT7QGMMdarMIpuO28r9EolXKgR16OphfXYd4V14GtB4AAAMpSURBVEjHvVSJctowEF1jjME2RziMwUCoMfd9heZqG4n//6buLpJjkmYm03byZmxJa2nf6u2uQcG2bfhqRN4LoTKBzyGDm68M7mAwcOEdjo4zhA/Rf9Go/CVtTgiRhXfIC3EDH8F/eUX1/9KexRo+QgOdtHDsEe/sM7QT32/+K61Z1LFXcXJxN4pTbu1aTQUzuy2PIA0rDo0/0Aa5XFaJvKaVTrubywXvaa1Wq4Vu/Snr3Y7Aojh4VccwykW2N2oQ8wmjyut6+Q1t5ywIG5Npj1sh5E0B7YOzFDjfuRfaOh3O+MbbVNfTWS9COZk3Obd2su5d0a6IU9KLREbw8gEehWSr1r2sPWciXLG38r5NdW0xu9eioU87omjC9yNaMi5GNf6WppVSOqXCFkmCvMB3p9SROLoYQn5pDgQOujA1xjYvqH+plUdkwnmII8VxR/PKYkrfLLomhVlE3b/LhNbNr7hp0H2JaOc4v8dFB58HSsFTSafaqtY1sT3GO8wsy5rhokYPlRJdjPMajyYqTt1EHF/2uqSWQWmAjCUSmQ1MS3g8Btf1XOsy7YIC0CB1b5Xw1Vhba0zbxiCAQLH9TNPmHJXQUtJAN0KcDsoqLxsNvJrJExa7mKIdp2lRE2WexiS4pqWk/0jROlw6K6bV9YOBDGAuqMJ0bnuUKGB0L27bxgRhGEbzihbhxxXaQC88Vkwq8ldCi86RApWUb0Q+4VDosBCc+1s81lUdnBavH4Zp2mm3O44USwOfvSo9oBiwpFg71lMS1VKJLKljS3j9p+fOTvXXlsSNuEv6YPaZda9uRope0VJfKdo7fPiYfSmvFjXQbkhY0d9hCbBWIktRgEDieDhf1N3wbbkmNNgRy8hyl620yGQat/grV3HMpc2HDKTVmOPFz6ylPCKt/nXcAyV260jaAowwIW0YuBzrOgb/KrddZS9OmJaLgpWK4JX2DDuklcLZSDGcn8Vmx9YDNvT6UsjyBApRyFQVX7Vxm9TGxE16nmfRd8/zQoDmggQOTRh5Hv8pMt9Q/L2JmSwkMCE7dA4BuDjHJwfu0Om4QAhOjrN5XkIatglfiN/bUPdCQFjTYgAAAABJRU5ErkJggg==\">";
+
+static void pimple_exception_call_parent_constructor(zval *this_ptr, const char *format, const char *arg1 TSRMLS_DC);
+
+static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC);
+static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC);
+
+static void pimple_bucket_dtor(pimple_bucket_value *bucket);
+static void pimple_free_bucket(pimple_bucket_value *bucket);
+
+static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC);
+static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC);
+static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC);
+static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC);
+static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC);
+static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC);
+
+static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC);
+static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC);
+static zend_function *pimple_closure_get_constructor(zval * TSRMLS_DC);
+static int pimple_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, union _zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC);
+
+#ifdef ZTS
+#define PIMPLE_G(v) TSRMG(pimple_globals_id, zend_pimple_globals *, v)
+#else
+#define PIMPLE_G(v) (pimple_globals.v)
+#endif
+
+#endif /* PHP_PIMPLE_H */
+
diff --git a/vendor/pimple/pimple/ext/pimple/pimple.c b/vendor/pimple/pimple/ext/pimple/pimple.c
new file mode 100755
index 0000000..c80499b
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/pimple.c
@@ -0,0 +1,1114 @@
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2014 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "php_ini.h"
+#include "ext/standard/info.h"
+#include "php_pimple.h"
+#include "pimple_compat.h"
+#include "zend_interfaces.h"
+#include "zend.h"
+#include "Zend/zend_closures.h"
+#include "ext/spl/spl_exceptions.h"
+#include "Zend/zend_exceptions.h"
+#include "main/php_output.h"
+#include "SAPI.h"
+
+static zend_class_entry *pimple_ce_PsrContainerInterface;
+static zend_class_entry *pimple_ce_PsrContainerExceptionInterface;
+static zend_class_entry *pimple_ce_PsrNotFoundExceptionInterface;
+
+static zend_class_entry *pimple_ce_ExpectedInvokableException;
+static zend_class_entry *pimple_ce_FrozenServiceException;
+static zend_class_entry *pimple_ce_InvalidServiceIdentifierException;
+static zend_class_entry *pimple_ce_UnknownIdentifierException;
+
+static zend_class_entry *pimple_ce;
+static zend_object_handlers pimple_object_handlers;
+static zend_class_entry *pimple_closure_ce;
+static zend_class_entry *pimple_serviceprovider_ce;
+static zend_object_handlers pimple_closure_object_handlers;
+static zend_internal_function pimple_closure_invoker_function;
+
+#define FETCH_DIM_HANDLERS_VARS pimple_object *pimple_obj = NULL; \
+ ulong index; \
+ pimple_obj = (pimple_object *)zend_object_store_get_object(object TSRMLS_CC); \
+
+#define PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS do { \
+ if (ce != pimple_ce) { \
+ zend_hash_find(&ce->function_table, ZEND_STRS("offsetget"), (void **)&function); \
+ if (function->common.scope != ce) { /* if the function is not defined in this actual class */ \
+ pimple_object_handlers.read_dimension = pimple_object_read_dimension; /* then overwrite the handler to use custom one */ \
+ } \
+ zend_hash_find(&ce->function_table, ZEND_STRS("offsetset"), (void **)&function); \
+ if (function->common.scope != ce) { \
+ pimple_object_handlers.write_dimension = pimple_object_write_dimension; \
+ } \
+ zend_hash_find(&ce->function_table, ZEND_STRS("offsetexists"), (void **)&function); \
+ if (function->common.scope != ce) { \
+ pimple_object_handlers.has_dimension = pimple_object_has_dimension; \
+ } \
+ zend_hash_find(&ce->function_table, ZEND_STRS("offsetunset"), (void **)&function); \
+ if (function->common.scope != ce) { \
+ pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \
+ } \
+ } else { \
+ pimple_object_handlers.read_dimension = pimple_object_read_dimension; \
+ pimple_object_handlers.write_dimension = pimple_object_write_dimension; \
+ pimple_object_handlers.has_dimension = pimple_object_has_dimension; \
+ pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \
+ }\
+ } while(0);
+
+#define PIMPLE_CALL_CB do { \
+ zend_fcall_info_argn(&fci TSRMLS_CC, 1, &object); \
+ fci.size = sizeof(fci); \
+ fci.object_ptr = retval->fcc.object_ptr; \
+ fci.function_name = retval->value; \
+ fci.no_separation = 1; \
+ fci.retval_ptr_ptr = &retval_ptr_ptr; \
+\
+ zend_call_function(&fci, &retval->fcc TSRMLS_CC); \
+ efree(fci.params); \
+ if (EG(exception)) { \
+ return EG(uninitialized_zval_ptr); \
+ } \
+ } while(0);
+
+
+/* Psr\Container\ContainerInterface */
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pimple_PsrContainerInterface_get, 0, 0, 1)
+ZEND_ARG_INFO(0, id)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_pimple_PsrContainerInterface_has, 0, 0, 1)
+ZEND_ARG_INFO(0, id)
+ZEND_END_ARG_INFO()
+
+static const zend_function_entry pimple_ce_PsrContainerInterface_functions[] = {
+ PHP_ABSTRACT_ME(ContainerInterface, get, arginfo_pimple_PsrContainerInterface_get)
+ PHP_ABSTRACT_ME(ContainerInterface, has, arginfo_pimple_PsrContainerInterface_has)
+ PHP_FE_END
+};
+
+/* Psr\Container\ContainerExceptionInterface */
+static const zend_function_entry pimple_ce_PsrContainerExceptionInterface_functions[] = {
+ PHP_FE_END
+};
+
+/* Psr\Container\NotFoundExceptionInterface */
+static const zend_function_entry pimple_ce_PsrNotFoundExceptionInterface_functions[] = {
+ PHP_FE_END
+};
+
+/* Pimple\Exception\FrozenServiceException */
+ZEND_BEGIN_ARG_INFO_EX(arginfo_FrozenServiceException___construct, 0, 0, 1)
+ZEND_ARG_INFO(0, id)
+ZEND_END_ARG_INFO()
+
+static const zend_function_entry pimple_ce_FrozenServiceException_functions[] = {
+ PHP_ME(FrozenServiceException, __construct, arginfo_FrozenServiceException___construct, ZEND_ACC_PUBLIC)
+ PHP_FE_END
+};
+
+/* Pimple\Exception\InvalidServiceIdentifierException */
+ZEND_BEGIN_ARG_INFO_EX(arginfo_InvalidServiceIdentifierException___construct, 0, 0, 1)
+ZEND_ARG_INFO(0, id)
+ZEND_END_ARG_INFO()
+
+static const zend_function_entry pimple_ce_InvalidServiceIdentifierException_functions[] = {
+ PHP_ME(InvalidServiceIdentifierException, __construct, arginfo_InvalidServiceIdentifierException___construct, ZEND_ACC_PUBLIC)
+ PHP_FE_END
+};
+
+/* Pimple\Exception\UnknownIdentifierException */
+ZEND_BEGIN_ARG_INFO_EX(arginfo_UnknownIdentifierException___construct, 0, 0, 1)
+ZEND_ARG_INFO(0, id)
+ZEND_END_ARG_INFO()
+
+static const zend_function_entry pimple_ce_UnknownIdentifierException_functions[] = {
+ PHP_ME(UnknownIdentifierException, __construct, arginfo_UnknownIdentifierException___construct, ZEND_ACC_PUBLIC)
+ PHP_FE_END
+};
+
+/* Pimple\Container */
+ZEND_BEGIN_ARG_INFO_EX(arginfo___construct, 0, 0, 0)
+ZEND_ARG_ARRAY_INFO(0, value, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetset, 0, 0, 2)
+ZEND_ARG_INFO(0, offset)
+ZEND_ARG_INFO(0, value)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetget, 0, 0, 1)
+ZEND_ARG_INFO(0, offset)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetexists, 0, 0, 1)
+ZEND_ARG_INFO(0, offset)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetunset, 0, 0, 1)
+ZEND_ARG_INFO(0, offset)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_factory, 0, 0, 1)
+ZEND_ARG_INFO(0, callable)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_protect, 0, 0, 1)
+ZEND_ARG_INFO(0, callable)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_raw, 0, 0, 1)
+ZEND_ARG_INFO(0, id)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_extend, 0, 0, 2)
+ZEND_ARG_INFO(0, id)
+ZEND_ARG_INFO(0, callable)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_register, 0, 0, 1)
+ZEND_ARG_OBJ_INFO(0, provider, Pimple\\ServiceProviderInterface, 0)
+ZEND_ARG_ARRAY_INFO(0, values, 1)
+ZEND_END_ARG_INFO()
+
+static const zend_function_entry pimple_ce_functions[] = {
+ PHP_ME(Pimple, __construct, arginfo___construct, ZEND_ACC_PUBLIC)
+ PHP_ME(Pimple, factory, arginfo_factory, ZEND_ACC_PUBLIC)
+ PHP_ME(Pimple, protect, arginfo_protect, ZEND_ACC_PUBLIC)
+ PHP_ME(Pimple, raw, arginfo_raw, ZEND_ACC_PUBLIC)
+ PHP_ME(Pimple, extend, arginfo_extend, ZEND_ACC_PUBLIC)
+ PHP_ME(Pimple, keys, arginfo_keys, ZEND_ACC_PUBLIC)
+ PHP_ME(Pimple, register, arginfo_register, ZEND_ACC_PUBLIC)
+
+ PHP_ME(Pimple, offsetSet, arginfo_offsetset, ZEND_ACC_PUBLIC)
+ PHP_ME(Pimple, offsetGet, arginfo_offsetget, ZEND_ACC_PUBLIC)
+ PHP_ME(Pimple, offsetExists, arginfo_offsetexists, ZEND_ACC_PUBLIC)
+ PHP_ME(Pimple, offsetUnset, arginfo_offsetunset, ZEND_ACC_PUBLIC)
+ PHP_FE_END
+};
+
+/* Pimple\ServiceProviderInterface */
+ZEND_BEGIN_ARG_INFO_EX(arginfo_serviceprovider_register, 0, 0, 1)
+ZEND_ARG_OBJ_INFO(0, pimple, Pimple\\Container, 0)
+ZEND_END_ARG_INFO()
+
+static const zend_function_entry pimple_serviceprovider_iface_ce_functions[] = {
+ PHP_ABSTRACT_ME(ServiceProviderInterface, register, arginfo_serviceprovider_register)
+ PHP_FE_END
+};
+
+/* parent::__construct(sprintf("Something with %s", $arg1)) */
+static void pimple_exception_call_parent_constructor(zval *this_ptr, const char *format, const char *arg1 TSRMLS_DC)
+{
+ zend_class_entry *ce = Z_OBJCE_P(this_ptr);
+ char *message = NULL;
+ int message_len;
+ zval *constructor_arg;
+
+ message_len = spprintf(&message, 0, format, arg1);
+ ALLOC_INIT_ZVAL(constructor_arg);
+ ZVAL_STRINGL(constructor_arg, message, message_len, 1);
+
+ zend_call_method_with_1_params(&this_ptr, ce, &ce->parent->constructor, "__construct", NULL, constructor_arg);
+
+ efree(message);
+ zval_ptr_dtor(&constructor_arg);
+}
+
+/**
+ * Pass a single string parameter to exception constructor and throw
+ */
+static void pimple_throw_exception_string(zend_class_entry *ce, const char *message, zend_uint message_len TSRMLS_DC)
+{
+ zval *exception, *param;
+
+ ALLOC_INIT_ZVAL(exception);
+ object_init_ex(exception, ce);
+
+ ALLOC_INIT_ZVAL(param);
+ ZVAL_STRINGL(param, message, message_len, 1);
+
+ zend_call_method_with_1_params(&exception, ce, &ce->constructor, "__construct", NULL, param);
+
+ zend_throw_exception_object(exception TSRMLS_CC);
+
+ zval_ptr_dtor(¶m);
+}
+
+static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC)
+{
+ zend_object_std_dtor(&obj->zobj TSRMLS_CC);
+ if (obj->factory) {
+ zval_ptr_dtor(&obj->factory);
+ }
+ if (obj->callable) {
+ zval_ptr_dtor(&obj->callable);
+ }
+ efree(obj);
+}
+
+static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC)
+{
+ zend_hash_destroy(&obj->factories);
+ zend_hash_destroy(&obj->protected);
+ zend_hash_destroy(&obj->values);
+ zend_object_std_dtor(&obj->zobj TSRMLS_CC);
+ efree(obj);
+}
+
+static void pimple_free_bucket(pimple_bucket_value *bucket)
+{
+ if (bucket->raw) {
+ zval_ptr_dtor(&bucket->raw);
+ }
+}
+
+static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC)
+{
+ zend_object_value retval;
+ pimple_closure_object *pimple_closure_obj = NULL;
+
+ pimple_closure_obj = ecalloc(1, sizeof(pimple_closure_object));
+ ZEND_OBJ_INIT(&pimple_closure_obj->zobj, ce);
+
+ pimple_closure_object_handlers.get_constructor = pimple_closure_get_constructor;
+ retval.handlers = &pimple_closure_object_handlers;
+ retval.handle = zend_objects_store_put(pimple_closure_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_closure_free_object_storage, NULL TSRMLS_CC);
+
+ return retval;
+}
+
+static zend_function *pimple_closure_get_constructor(zval *obj TSRMLS_DC)
+{
+ zend_error(E_ERROR, "Pimple\\ContainerClosure is an internal class and cannot be instantiated");
+
+ return NULL;
+}
+
+static int pimple_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, union _zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC)
+{
+ *zobj_ptr = obj;
+ *ce_ptr = Z_OBJCE_P(obj);
+ *fptr_ptr = (zend_function *)&pimple_closure_invoker_function;
+
+ return SUCCESS;
+}
+
+static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC)
+{
+ zend_object_value retval;
+ pimple_object *pimple_obj = NULL;
+ zend_function *function = NULL;
+
+ pimple_obj = emalloc(sizeof(pimple_object));
+ ZEND_OBJ_INIT(&pimple_obj->zobj, ce);
+
+ PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS
+
+ retval.handlers = &pimple_object_handlers;
+ retval.handle = zend_objects_store_put(pimple_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_free_object_storage, NULL TSRMLS_CC);
+
+ zend_hash_init(&pimple_obj->factories, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0);
+ zend_hash_init(&pimple_obj->protected, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0);
+ zend_hash_init(&pimple_obj->values, PIMPLE_DEFAULT_ZVAL_VALUES_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0);
+
+ return retval;
+}
+
+static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC)
+{
+ FETCH_DIM_HANDLERS_VARS
+
+ pimple_bucket_value pimple_value = {0}, *found_value = NULL;
+ ulong hash;
+
+ pimple_zval_to_pimpleval(value, &pimple_value TSRMLS_CC);
+
+ if (!offset) {/* $p[] = 'foo' when not overloaded */
+ zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL);
+ Z_ADDREF_P(value);
+ return;
+ }
+
+ switch (Z_TYPE_P(offset)) {
+ case IS_STRING:
+ hash = zend_hash_func(Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1);
+ zend_hash_quick_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void **)&found_value);
+ if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) {
+ pimple_free_bucket(&pimple_value);
+ pimple_throw_exception_string(pimple_ce_FrozenServiceException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC);
+ return;
+ }
+ if (zend_hash_quick_update(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) {
+ pimple_free_bucket(&pimple_value);
+ return;
+ }
+ Z_ADDREF_P(value);
+ break;
+ case IS_DOUBLE:
+ case IS_BOOL:
+ case IS_LONG:
+ if (Z_TYPE_P(offset) == IS_DOUBLE) {
+ index = (ulong)Z_DVAL_P(offset);
+ } else {
+ index = Z_LVAL_P(offset);
+ }
+ zend_hash_index_find(&pimple_obj->values, index, (void **)&found_value);
+ if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) {
+ pimple_free_bucket(&pimple_value);
+ convert_to_string(offset);
+ pimple_throw_exception_string(pimple_ce_FrozenServiceException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC);
+ return;
+ }
+ if (zend_hash_index_update(&pimple_obj->values, index, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) {
+ pimple_free_bucket(&pimple_value);
+ return;
+ }
+ Z_ADDREF_P(value);
+ break;
+ case IS_NULL: /* $p[] = 'foo' when overloaded */
+ zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL);
+ Z_ADDREF_P(value);
+ break;
+ default:
+ pimple_free_bucket(&pimple_value);
+ zend_error(E_WARNING, "Unsupported offset type");
+ }
+}
+
+static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC)
+{
+ FETCH_DIM_HANDLERS_VARS
+
+ switch (Z_TYPE_P(offset)) {
+ case IS_STRING:
+ zend_symtable_del(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1);
+ zend_symtable_del(&pimple_obj->factories, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1);
+ zend_symtable_del(&pimple_obj->protected, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1);
+ break;
+ case IS_DOUBLE:
+ case IS_BOOL:
+ case IS_LONG:
+ if (Z_TYPE_P(offset) == IS_DOUBLE) {
+ index = (ulong)Z_DVAL_P(offset);
+ } else {
+ index = Z_LVAL_P(offset);
+ }
+ zend_hash_index_del(&pimple_obj->values, index);
+ zend_hash_index_del(&pimple_obj->factories, index);
+ zend_hash_index_del(&pimple_obj->protected, index);
+ break;
+ default:
+ zend_error(E_WARNING, "Unsupported offset type");
+ }
+}
+
+static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC)
+{
+ FETCH_DIM_HANDLERS_VARS
+
+ pimple_bucket_value *retval = NULL;
+
+ switch (Z_TYPE_P(offset)) {
+ case IS_STRING:
+ if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == SUCCESS) {
+ switch (check_empty) {
+ case 0: /* isset */
+ return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;) */
+ case 1: /* empty */
+ default:
+ return zend_is_true(retval->value);
+ }
+ }
+ return 0;
+ break;
+ case IS_DOUBLE:
+ case IS_BOOL:
+ case IS_LONG:
+ if (Z_TYPE_P(offset) == IS_DOUBLE) {
+ index = (ulong)Z_DVAL_P(offset);
+ } else {
+ index = Z_LVAL_P(offset);
+ }
+ if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == SUCCESS) {
+ switch (check_empty) {
+ case 0: /* isset */
+ return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;)*/
+ case 1: /* empty */
+ default:
+ return zend_is_true(retval->value);
+ }
+ }
+ return 0;
+ break;
+ default:
+ zend_error(E_WARNING, "Unsupported offset type");
+ return 0;
+ }
+}
+
+static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC)
+{
+ FETCH_DIM_HANDLERS_VARS
+
+ pimple_bucket_value *retval = NULL;
+ zend_fcall_info fci = {0};
+ zval *retval_ptr_ptr = NULL;
+
+ switch (Z_TYPE_P(offset)) {
+ case IS_STRING:
+ if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == FAILURE) {
+ pimple_throw_exception_string(pimple_ce_UnknownIdentifierException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC);
+
+ return EG(uninitialized_zval_ptr);
+ }
+ break;
+ case IS_DOUBLE:
+ case IS_BOOL:
+ case IS_LONG:
+ if (Z_TYPE_P(offset) == IS_DOUBLE) {
+ index = (ulong)Z_DVAL_P(offset);
+ } else {
+ index = Z_LVAL_P(offset);
+ }
+ if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == FAILURE) {
+ return EG(uninitialized_zval_ptr);
+ }
+ break;
+ case IS_NULL: /* $p[][3] = 'foo' first dim access */
+ return EG(uninitialized_zval_ptr);
+ break;
+ default:
+ zend_error(E_WARNING, "Unsupported offset type");
+ return EG(uninitialized_zval_ptr);
+ }
+
+ if(retval->type == PIMPLE_IS_PARAM) {
+ return retval->value;
+ }
+
+ if (zend_hash_index_exists(&pimple_obj->protected, retval->handle_num)) {
+ /* Service is protected, return the value every time */
+ return retval->value;
+ }
+
+ if (zend_hash_index_exists(&pimple_obj->factories, retval->handle_num)) {
+ /* Service is a factory, call it every time and never cache its result */
+ PIMPLE_CALL_CB
+ Z_DELREF_P(retval_ptr_ptr); /* fetch dim addr will increment refcount */
+ return retval_ptr_ptr;
+ }
+
+ if (retval->initialized == 1) {
+ /* Service has already been called, return its cached value */
+ return retval->value;
+ }
+
+ ALLOC_INIT_ZVAL(retval->raw);
+ MAKE_COPY_ZVAL(&retval->value, retval->raw);
+
+ PIMPLE_CALL_CB
+
+ retval->initialized = 1;
+ zval_ptr_dtor(&retval->value);
+ retval->value = retval_ptr_ptr;
+
+ return retval->value;
+}
+
+static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC)
+{
+ if (Z_TYPE_P(_zval) != IS_OBJECT) {
+ return FAILURE;
+ }
+
+ if (_pimple_bucket_value->fcc.called_scope) {
+ return SUCCESS;
+ }
+
+ if (Z_OBJ_HANDLER_P(_zval, get_closure) && Z_OBJ_HANDLER_P(_zval, get_closure)(_zval, &_pimple_bucket_value->fcc.calling_scope, &_pimple_bucket_value->fcc.function_handler, &_pimple_bucket_value->fcc.object_ptr TSRMLS_CC) == SUCCESS) {
+ _pimple_bucket_value->fcc.called_scope = _pimple_bucket_value->fcc.calling_scope;
+ return SUCCESS;
+ } else {
+ return FAILURE;
+ }
+}
+
+static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC)
+{
+ _pimple_bucket_value->value = _zval;
+
+ if (Z_TYPE_P(_zval) != IS_OBJECT) {
+ return PIMPLE_IS_PARAM;
+ }
+
+ if (pimple_zval_is_valid_callback(_zval, _pimple_bucket_value TSRMLS_CC) == SUCCESS) {
+ _pimple_bucket_value->type = PIMPLE_IS_SERVICE;
+ _pimple_bucket_value->handle_num = Z_OBJ_HANDLE_P(_zval);
+ }
+
+ return PIMPLE_IS_SERVICE;
+}
+
+static void pimple_bucket_dtor(pimple_bucket_value *bucket)
+{
+ zval_ptr_dtor(&bucket->value);
+ pimple_free_bucket(bucket);
+}
+
+PHP_METHOD(FrozenServiceException, __construct)
+{
+ char *id = NULL;
+ int id_len;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &id, &id_len) == FAILURE) {
+ return;
+ }
+ pimple_exception_call_parent_constructor(getThis(), "Cannot override frozen service \"%s\".", id TSRMLS_CC);
+}
+
+PHP_METHOD(InvalidServiceIdentifierException, __construct)
+{
+ char *id = NULL;
+ int id_len;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &id, &id_len) == FAILURE) {
+ return;
+ }
+ pimple_exception_call_parent_constructor(getThis(), "Identifier \"%s\" does not contain an object definition.", id TSRMLS_CC);
+}
+
+PHP_METHOD(UnknownIdentifierException, __construct)
+{
+ char *id = NULL;
+ int id_len;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &id, &id_len) == FAILURE) {
+ return;
+ }
+ pimple_exception_call_parent_constructor(getThis(), "Identifier \"%s\" is not defined.", id TSRMLS_CC);
+}
+
+PHP_METHOD(Pimple, protect)
+{
+ zval *protected = NULL;
+ pimple_object *pobj = NULL;
+ pimple_bucket_value bucket = {0};
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &protected) == FAILURE) {
+ return;
+ }
+
+ if (pimple_zval_is_valid_callback(protected, &bucket TSRMLS_CC) == FAILURE) {
+ pimple_free_bucket(&bucket);
+ zend_throw_exception(pimple_ce_ExpectedInvokableException, "Callable is not a Closure or invokable object.", 0 TSRMLS_CC);
+ return;
+ }
+
+ pimple_zval_to_pimpleval(protected, &bucket TSRMLS_CC);
+ pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC);
+
+ if (zend_hash_index_update(&pobj->protected, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) {
+ Z_ADDREF_P(protected);
+ RETURN_ZVAL(protected, 1 , 0);
+ } else {
+ pimple_free_bucket(&bucket);
+ }
+ RETURN_FALSE;
+}
+
+PHP_METHOD(Pimple, raw)
+{
+ zval *offset = NULL;
+ pimple_object *pobj = NULL;
+ pimple_bucket_value *value = NULL;
+ ulong index;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) {
+ return;
+ }
+
+ pobj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+ switch (Z_TYPE_P(offset)) {
+ case IS_STRING:
+ if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) {
+ pimple_throw_exception_string(pimple_ce_UnknownIdentifierException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC);
+ RETURN_NULL();
+ }
+ break;
+ case IS_DOUBLE:
+ case IS_BOOL:
+ case IS_LONG:
+ if (Z_TYPE_P(offset) == IS_DOUBLE) {
+ index = (ulong)Z_DVAL_P(offset);
+ } else {
+ index = Z_LVAL_P(offset);
+ }
+ if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) {
+ RETURN_NULL();
+ }
+ break;
+ case IS_NULL:
+ default:
+ zend_error(E_WARNING, "Unsupported offset type");
+ }
+
+ if (value->raw) {
+ RETVAL_ZVAL(value->raw, 1, 0);
+ } else {
+ RETVAL_ZVAL(value->value, 1, 0);
+ }
+}
+
+PHP_METHOD(Pimple, extend)
+{
+ zval *offset = NULL, *callable = NULL, *pimple_closure_obj = NULL;
+ pimple_bucket_value bucket = {0}, *value = NULL;
+ pimple_object *pobj = NULL;
+ pimple_closure_object *pcobj = NULL;
+ ulong index;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &callable) == FAILURE) {
+ return;
+ }
+
+ pobj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+ switch (Z_TYPE_P(offset)) {
+ case IS_STRING:
+ if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) {
+ pimple_throw_exception_string(pimple_ce_UnknownIdentifierException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC);
+ RETURN_NULL();
+ }
+
+ if (value->type != PIMPLE_IS_SERVICE) {
+ pimple_throw_exception_string(pimple_ce_InvalidServiceIdentifierException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC);
+ RETURN_NULL();
+ }
+ if (zend_hash_index_exists(&pobj->protected, value->handle_num)) {
+ int er = EG(error_reporting);
+ EG(error_reporting) = 0;
+ php_error(E_DEPRECATED, "How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure \"%s\" should be protected?", Z_STRVAL_P(offset));
+ EG(error_reporting) = er;
+ }
+ break;
+ case IS_DOUBLE:
+ case IS_BOOL:
+ case IS_LONG:
+ if (Z_TYPE_P(offset) == IS_DOUBLE) {
+ index = (ulong)Z_DVAL_P(offset);
+ } else {
+ index = Z_LVAL_P(offset);
+ }
+ if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) {
+ convert_to_string(offset);
+ pimple_throw_exception_string(pimple_ce_UnknownIdentifierException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC);
+ RETURN_NULL();
+ }
+ if (value->type != PIMPLE_IS_SERVICE) {
+ convert_to_string(offset);
+ pimple_throw_exception_string(pimple_ce_InvalidServiceIdentifierException, Z_STRVAL_P(offset), Z_STRLEN_P(offset) TSRMLS_CC);
+ RETURN_NULL();
+ }
+ if (zend_hash_index_exists(&pobj->protected, value->handle_num)) {
+ int er = EG(error_reporting);
+ EG(error_reporting) = 0;
+ php_error(E_DEPRECATED, "How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure \"%ld\" should be protected?", index);
+ EG(error_reporting) = er;
+ }
+ break;
+ case IS_NULL:
+ default:
+ zend_error(E_WARNING, "Unsupported offset type");
+ }
+
+ if (pimple_zval_is_valid_callback(callable, &bucket TSRMLS_CC) == FAILURE) {
+ pimple_free_bucket(&bucket);
+ zend_throw_exception(pimple_ce_ExpectedInvokableException, "Extension service definition is not a Closure or invokable object.", 0 TSRMLS_CC);
+ RETURN_NULL();
+ }
+ pimple_free_bucket(&bucket);
+
+ ALLOC_INIT_ZVAL(pimple_closure_obj);
+ object_init_ex(pimple_closure_obj, pimple_closure_ce);
+
+ pcobj = zend_object_store_get_object(pimple_closure_obj TSRMLS_CC);
+ pcobj->callable = callable;
+ pcobj->factory = value->value;
+ Z_ADDREF_P(callable);
+ Z_ADDREF_P(value->value);
+
+ if (zend_hash_index_exists(&pobj->factories, value->handle_num)) {
+ pimple_zval_to_pimpleval(pimple_closure_obj, &bucket TSRMLS_CC);
+ zend_hash_index_del(&pobj->factories, value->handle_num);
+ zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL);
+ Z_ADDREF_P(pimple_closure_obj);
+ }
+
+ pimple_object_write_dimension(getThis(), offset, pimple_closure_obj TSRMLS_CC);
+
+ RETVAL_ZVAL(pimple_closure_obj, 1, 1);
+}
+
+PHP_METHOD(Pimple, keys)
+{
+ HashPosition pos;
+ pimple_object *pobj = NULL;
+ zval **value = NULL;
+ zval *endval = NULL;
+ char *str_index = NULL;
+ int str_len;
+ ulong num_index;
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+
+ pobj = zend_object_store_get_object(getThis() TSRMLS_CC);
+ array_init_size(return_value, zend_hash_num_elements(&pobj->values));
+
+ zend_hash_internal_pointer_reset_ex(&pobj->values, &pos);
+
+ while(zend_hash_get_current_data_ex(&pobj->values, (void **)&value, &pos) == SUCCESS) {
+ MAKE_STD_ZVAL(endval);
+ switch (zend_hash_get_current_key_ex(&pobj->values, &str_index, (uint *)&str_len, &num_index, 0, &pos)) {
+ case HASH_KEY_IS_STRING:
+ ZVAL_STRINGL(endval, str_index, str_len - 1, 1);
+ zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL);
+ break;
+ case HASH_KEY_IS_LONG:
+ ZVAL_LONG(endval, num_index);
+ zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL);
+ break;
+ }
+ zend_hash_move_forward_ex(&pobj->values, &pos);
+ }
+}
+
+PHP_METHOD(Pimple, factory)
+{
+ zval *factory = NULL;
+ pimple_object *pobj = NULL;
+ pimple_bucket_value bucket = {0};
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &factory) == FAILURE) {
+ return;
+ }
+
+ if (pimple_zval_is_valid_callback(factory, &bucket TSRMLS_CC) == FAILURE) {
+ pimple_free_bucket(&bucket);
+ zend_throw_exception(pimple_ce_ExpectedInvokableException, "Service definition is not a Closure or invokable object.", 0 TSRMLS_CC);
+ return;
+ }
+
+ pimple_zval_to_pimpleval(factory, &bucket TSRMLS_CC);
+ pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC);
+
+ if (zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) {
+ Z_ADDREF_P(factory);
+ RETURN_ZVAL(factory, 1 , 0);
+ } else {
+ pimple_free_bucket(&bucket);
+ }
+
+ RETURN_FALSE;
+}
+
+PHP_METHOD(Pimple, offsetSet)
+{
+ zval *offset = NULL, *value = NULL;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &value) == FAILURE) {
+ return;
+ }
+
+ pimple_object_write_dimension(getThis(), offset, value TSRMLS_CC);
+}
+
+PHP_METHOD(Pimple, offsetGet)
+{
+ zval *offset = NULL, *retval = NULL;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) {
+ return;
+ }
+
+ retval = pimple_object_read_dimension(getThis(), offset, 0 TSRMLS_CC);
+
+ RETVAL_ZVAL(retval, 1, 0);
+}
+
+PHP_METHOD(Pimple, offsetUnset)
+{
+ zval *offset = NULL;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) {
+ return;
+ }
+
+ pimple_object_unset_dimension(getThis(), offset TSRMLS_CC);
+}
+
+PHP_METHOD(Pimple, offsetExists)
+{
+ zval *offset = NULL;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) {
+ return;
+ }
+
+ RETVAL_BOOL(pimple_object_has_dimension(getThis(), offset, 1 TSRMLS_CC));
+}
+
+PHP_METHOD(Pimple, register)
+{
+ zval *provider;
+ zval **data;
+ zval *retval = NULL;
+ zval key;
+
+ HashTable *array = NULL;
+ HashPosition pos;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|h", &provider, pimple_serviceprovider_ce, &array) == FAILURE) {
+ return;
+ }
+
+ RETVAL_ZVAL(getThis(), 1, 0);
+
+ zend_call_method_with_1_params(&provider, Z_OBJCE_P(provider), NULL, "register", &retval, getThis());
+
+ if (retval) {
+ zval_ptr_dtor(&retval);
+ }
+
+ if (!array) {
+ return;
+ }
+
+ zend_hash_internal_pointer_reset_ex(array, &pos);
+
+ while(zend_hash_get_current_data_ex(array, (void **)&data, &pos) == SUCCESS) {
+ zend_hash_get_current_key_zval_ex(array, &key, &pos);
+ pimple_object_write_dimension(getThis(), &key, *data TSRMLS_CC);
+ zend_hash_move_forward_ex(array, &pos);
+ }
+}
+
+PHP_METHOD(Pimple, __construct)
+{
+ zval *values = NULL, **pData = NULL, offset;
+ HashPosition pos;
+ char *str_index = NULL;
+ zend_uint str_length;
+ ulong num_index;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a!", &values) == FAILURE) {
+ return;
+ }
+
+ PIMPLE_DEPRECATE
+
+ if (!values) {
+ return;
+ }
+
+ zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(values), &pos);
+ while (zend_hash_has_more_elements_ex(Z_ARRVAL_P(values), &pos) == SUCCESS) {
+ zend_hash_get_current_data_ex(Z_ARRVAL_P(values), (void **)&pData, &pos);
+ zend_hash_get_current_key_ex(Z_ARRVAL_P(values), &str_index, &str_length, &num_index, 0, &pos);
+ INIT_ZVAL(offset);
+ if (zend_hash_get_current_key_type_ex(Z_ARRVAL_P(values), &pos) == HASH_KEY_IS_LONG) {
+ ZVAL_LONG(&offset, num_index);
+ } else {
+ ZVAL_STRINGL(&offset, str_index, (str_length - 1), 0);
+ }
+ pimple_object_write_dimension(getThis(), &offset, *pData TSRMLS_CC);
+ zend_hash_move_forward_ex(Z_ARRVAL_P(values), &pos);
+ }
+}
+
+/*
+ * This is PHP code snippet handling extend()s calls :
+
+ $extended = function ($c) use ($callable, $factory) {
+ return $callable($factory($c), $c);
+ };
+
+ */
+PHP_METHOD(PimpleClosure, invoker)
+{
+ pimple_closure_object *pcobj = NULL;
+ zval *arg = NULL, *retval = NULL, *newretval = NULL;
+ zend_fcall_info fci = {0};
+ zval **args[2];
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg) == FAILURE) {
+ return;
+ }
+
+ pcobj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+ fci.function_name = pcobj->factory;
+ args[0] = &arg;
+ zend_fcall_info_argp(&fci TSRMLS_CC, 1, args);
+ fci.retval_ptr_ptr = &retval;
+ fci.size = sizeof(fci);
+
+ if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) {
+ efree(fci.params);
+ return; /* Should here return default zval */
+ }
+
+ efree(fci.params);
+ memset(&fci, 0, sizeof(fci));
+ fci.size = sizeof(fci);
+
+ fci.function_name = pcobj->callable;
+ args[0] = &retval;
+ args[1] = &arg;
+ zend_fcall_info_argp(&fci TSRMLS_CC, 2, args);
+ fci.retval_ptr_ptr = &newretval;
+
+ if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) {
+ efree(fci.params);
+ zval_ptr_dtor(&retval);
+ return;
+ }
+
+ efree(fci.params);
+ zval_ptr_dtor(&retval);
+
+ RETVAL_ZVAL(newretval, 1 ,1);
+}
+
+PHP_MINIT_FUNCTION(pimple)
+{
+ zend_class_entry tmp_ce_PsrContainerInterface, tmp_ce_PsrContainerExceptionInterface, tmp_ce_PsrNotFoundExceptionInterface;
+ zend_class_entry tmp_ce_ExpectedInvokableException, tmp_ce_FrozenServiceException, tmp_ce_InvalidServiceIdentifierException, tmp_ce_UnknownIdentifierException;
+ zend_class_entry tmp_pimple_ce, tmp_pimple_closure_ce, tmp_pimple_serviceprovider_iface_ce;
+
+ /* Psr\Container namespace */
+ INIT_NS_CLASS_ENTRY(tmp_ce_PsrContainerInterface, PSR_CONTAINER_NS, "ContainerInterface", pimple_ce_PsrContainerInterface_functions);
+ INIT_NS_CLASS_ENTRY(tmp_ce_PsrContainerExceptionInterface, PSR_CONTAINER_NS, "ContainerExceptionInterface", pimple_ce_PsrContainerExceptionInterface_functions);
+ INIT_NS_CLASS_ENTRY(tmp_ce_PsrNotFoundExceptionInterface, PSR_CONTAINER_NS, "NotFoundExceptionInterface", pimple_ce_PsrNotFoundExceptionInterface_functions);
+
+ pimple_ce_PsrContainerInterface = zend_register_internal_interface(&tmp_ce_PsrContainerInterface TSRMLS_CC);
+ pimple_ce_PsrContainerExceptionInterface = zend_register_internal_interface(&tmp_ce_PsrContainerExceptionInterface TSRMLS_CC);
+ pimple_ce_PsrNotFoundExceptionInterface = zend_register_internal_interface(&tmp_ce_PsrNotFoundExceptionInterface TSRMLS_CC);
+
+ zend_class_implements(pimple_ce_PsrNotFoundExceptionInterface TSRMLS_CC, 1, pimple_ce_PsrContainerExceptionInterface);
+
+ /* Pimple\Exception namespace */
+ INIT_NS_CLASS_ENTRY(tmp_ce_ExpectedInvokableException, PIMPLE_EXCEPTION_NS, "ExpectedInvokableException", NULL);
+ INIT_NS_CLASS_ENTRY(tmp_ce_FrozenServiceException, PIMPLE_EXCEPTION_NS, "FrozenServiceException", pimple_ce_FrozenServiceException_functions);
+ INIT_NS_CLASS_ENTRY(tmp_ce_InvalidServiceIdentifierException, PIMPLE_EXCEPTION_NS, "InvalidServiceIdentifierException", pimple_ce_InvalidServiceIdentifierException_functions);
+ INIT_NS_CLASS_ENTRY(tmp_ce_UnknownIdentifierException, PIMPLE_EXCEPTION_NS, "UnknownIdentifierException", pimple_ce_UnknownIdentifierException_functions);
+
+ pimple_ce_ExpectedInvokableException = zend_register_internal_class_ex(&tmp_ce_ExpectedInvokableException, spl_ce_InvalidArgumentException, NULL TSRMLS_CC);
+ pimple_ce_FrozenServiceException = zend_register_internal_class_ex(&tmp_ce_FrozenServiceException, spl_ce_RuntimeException, NULL TSRMLS_CC);
+ pimple_ce_InvalidServiceIdentifierException = zend_register_internal_class_ex(&tmp_ce_InvalidServiceIdentifierException, spl_ce_InvalidArgumentException, NULL TSRMLS_CC);
+ pimple_ce_UnknownIdentifierException = zend_register_internal_class_ex(&tmp_ce_UnknownIdentifierException, spl_ce_InvalidArgumentException, NULL TSRMLS_CC);
+
+ zend_class_implements(pimple_ce_ExpectedInvokableException TSRMLS_CC, 1, pimple_ce_PsrContainerExceptionInterface);
+ zend_class_implements(pimple_ce_FrozenServiceException TSRMLS_CC, 1, pimple_ce_PsrContainerExceptionInterface);
+ zend_class_implements(pimple_ce_InvalidServiceIdentifierException TSRMLS_CC, 1, pimple_ce_PsrContainerExceptionInterface);
+ zend_class_implements(pimple_ce_UnknownIdentifierException TSRMLS_CC, 1, pimple_ce_PsrNotFoundExceptionInterface);
+
+ /* Pimple namespace */
+ INIT_NS_CLASS_ENTRY(tmp_pimple_ce, PIMPLE_NS, "Container", pimple_ce_functions);
+ INIT_NS_CLASS_ENTRY(tmp_pimple_closure_ce, PIMPLE_NS, "ContainerClosure", NULL);
+ INIT_NS_CLASS_ENTRY(tmp_pimple_serviceprovider_iface_ce, PIMPLE_NS, "ServiceProviderInterface", pimple_serviceprovider_iface_ce_functions);
+
+ tmp_pimple_ce.create_object = pimple_object_create;
+ tmp_pimple_closure_ce.create_object = pimple_closure_object_create;
+
+ pimple_ce = zend_register_internal_class(&tmp_pimple_ce TSRMLS_CC);
+ zend_class_implements(pimple_ce TSRMLS_CC, 1, zend_ce_arrayaccess);
+
+ pimple_closure_ce = zend_register_internal_class(&tmp_pimple_closure_ce TSRMLS_CC);
+ pimple_closure_ce->ce_flags |= ZEND_ACC_FINAL_CLASS;
+
+ pimple_serviceprovider_ce = zend_register_internal_interface(&tmp_pimple_serviceprovider_iface_ce TSRMLS_CC);
+
+ memcpy(&pimple_closure_object_handlers, zend_get_std_object_handlers(), sizeof(*zend_get_std_object_handlers()));
+ pimple_object_handlers = std_object_handlers;
+ pimple_closure_object_handlers.get_closure = pimple_closure_get_closure;
+
+ pimple_closure_invoker_function.function_name = "Pimple closure internal invoker";
+ pimple_closure_invoker_function.fn_flags |= ZEND_ACC_CLOSURE;
+ pimple_closure_invoker_function.handler = ZEND_MN(PimpleClosure_invoker);
+ pimple_closure_invoker_function.num_args = 1;
+ pimple_closure_invoker_function.required_num_args = 1;
+ pimple_closure_invoker_function.scope = pimple_closure_ce;
+ pimple_closure_invoker_function.type = ZEND_INTERNAL_FUNCTION;
+ pimple_closure_invoker_function.module = &pimple_module_entry;
+
+ return SUCCESS;
+}
+
+PHP_MINFO_FUNCTION(pimple)
+{
+ php_info_print_table_start();
+ php_info_print_table_header(2, "SensioLabs Pimple C support", "enabled");
+ php_info_print_table_row(2, "Pimple supported version", PIMPLE_VERSION);
+ php_info_print_table_end();
+
+ php_info_print_box_start(0);
+ php_write((void *)ZEND_STRL("SensioLabs Pimple C support developed by Julien Pauli") TSRMLS_CC);
+ if (!sapi_module.phpinfo_as_text) {
+ php_write((void *)ZEND_STRL(sensiolabs_logo) TSRMLS_CC);
+ }
+ php_info_print_box_end();
+}
+
+zend_module_entry pimple_module_entry = {
+ STANDARD_MODULE_HEADER,
+ "pimple",
+ NULL,
+ PHP_MINIT(pimple),
+ NULL,
+ NULL,
+ NULL,
+ PHP_MINFO(pimple),
+ PIMPLE_VERSION,
+ STANDARD_MODULE_PROPERTIES
+};
+
+#ifdef COMPILE_DL_PIMPLE
+ZEND_GET_MODULE(pimple)
+#endif
diff --git a/vendor/pimple/pimple/ext/pimple/pimple_compat.h b/vendor/pimple/pimple/ext/pimple/pimple_compat.h
new file mode 100755
index 0000000..d234e17
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/pimple_compat.h
@@ -0,0 +1,81 @@
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2014 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef PIMPLE_COMPAT_H_
+#define PIMPLE_COMPAT_H_
+
+#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */
+
+#define PHP_5_0_X_API_NO 220040412
+#define PHP_5_1_X_API_NO 220051025
+#define PHP_5_2_X_API_NO 220060519
+#define PHP_5_3_X_API_NO 220090626
+#define PHP_5_4_X_API_NO 220100525
+#define PHP_5_5_X_API_NO 220121212
+#define PHP_5_6_X_API_NO 220131226
+
+#define IS_PHP_56 ZEND_EXTENSION_API_NO == PHP_5_6_X_API_NO
+#define IS_AT_LEAST_PHP_56 ZEND_EXTENSION_API_NO >= PHP_5_6_X_API_NO
+
+#define IS_PHP_55 ZEND_EXTENSION_API_NO == PHP_5_5_X_API_NO
+#define IS_AT_LEAST_PHP_55 ZEND_EXTENSION_API_NO >= PHP_5_5_X_API_NO
+
+#define IS_PHP_54 ZEND_EXTENSION_API_NO == PHP_5_4_X_API_NO
+#define IS_AT_LEAST_PHP_54 ZEND_EXTENSION_API_NO >= PHP_5_4_X_API_NO
+
+#define IS_PHP_53 ZEND_EXTENSION_API_NO == PHP_5_3_X_API_NO
+#define IS_AT_LEAST_PHP_53 ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO
+
+#if IS_PHP_53
+#define object_properties_init(obj, ce) do { \
+ zend_hash_copy(obj->properties, &ce->default_properties, zval_copy_property_ctor(ce), NULL, sizeof(zval *)); \
+ } while (0);
+#endif
+
+#define ZEND_OBJ_INIT(obj, ce) do { \
+ zend_object_std_init(obj, ce TSRMLS_CC); \
+ object_properties_init((obj), (ce)); \
+ } while(0);
+
+#if IS_PHP_53 || IS_PHP_54
+static void zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos) {
+ Bucket *p;
+
+ p = pos ? (*pos) : ht->pInternalPointer;
+
+ if (!p) {
+ Z_TYPE_P(key) = IS_NULL;
+ } else if (p->nKeyLength) {
+ Z_TYPE_P(key) = IS_STRING;
+ Z_STRVAL_P(key) = estrndup(p->arKey, p->nKeyLength - 1);
+ Z_STRLEN_P(key) = p->nKeyLength - 1;
+ } else {
+ Z_TYPE_P(key) = IS_LONG;
+ Z_LVAL_P(key) = p->h;
+ }
+}
+#endif
+
+#endif /* PIMPLE_COMPAT_H_ */
diff --git a/vendor/pimple/pimple/ext/pimple/tests/001.phpt b/vendor/pimple/pimple/ext/pimple/tests/001.phpt
new file mode 100755
index 0000000..0809ea2
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/001.phpt
@@ -0,0 +1,45 @@
+--TEST--
+Test for read_dim/write_dim handlers
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+$p = new Pimple\Container();
+$p[42] = 'foo';
+$p['foo'] = 42;
+
+echo $p[42];
+echo "\n";
+echo $p['foo'];
+echo "\n";
+try {
+ var_dump($p['nonexistant']);
+ echo "Exception excpected";
+} catch (InvalidArgumentException $e) { }
+
+$p[54.2] = 'foo2';
+echo $p[54];
+echo "\n";
+$p[242.99] = 'foo99';
+echo $p[242];
+
+echo "\n";
+
+$p[5] = 'bar';
+$p[5] = 'baz';
+echo $p[5];
+
+echo "\n";
+
+$p['str'] = 'str';
+$p['str'] = 'strstr';
+echo $p['str'];
+?>
+
+--EXPECTF--
+foo
+42
+foo2
+foo99
+baz
+strstr
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/002.phpt b/vendor/pimple/pimple/ext/pimple/tests/002.phpt
new file mode 100755
index 0000000..7b56d2c
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/002.phpt
@@ -0,0 +1,15 @@
+--TEST--
+Test for constructor
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+$p = new Pimple\Container();
+var_dump($p[42]);
+
+$p = new Pimple\Container(array(42=>'foo'));
+var_dump($p[42]);
+?>
+--EXPECT--
+NULL
+string(3) "foo"
diff --git a/vendor/pimple/pimple/ext/pimple/tests/003.phpt b/vendor/pimple/pimple/ext/pimple/tests/003.phpt
new file mode 100755
index 0000000..a22cfa3
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/003.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Test empty dimensions
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+$p = new Pimple\Container();
+$p[] = 42;
+var_dump($p[0]);
+$p[41] = 'foo';
+$p[] = 'bar';
+var_dump($p[42]);
+?>
+--EXPECT--
+int(42)
+string(3) "bar"
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/004.phpt b/vendor/pimple/pimple/ext/pimple/tests/004.phpt
new file mode 100755
index 0000000..1e1d251
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/004.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Test has/unset dim handlers
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+$p = new Pimple\Container();
+$p[] = 42;
+var_dump($p[0]);
+unset($p[0]);
+var_dump($p[0]);
+$p['foo'] = 'bar';
+var_dump(isset($p['foo']));
+unset($p['foo']);
+try {
+ var_dump($p['foo']);
+ echo "Excpected exception";
+} catch (InvalidArgumentException $e) { }
+var_dump(isset($p['bar']));
+$p['bar'] = NULL;
+var_dump(isset($p['bar']));
+var_dump(empty($p['bar']));
+?>
+--EXPECT--
+int(42)
+NULL
+bool(true)
+bool(false)
+bool(true)
+bool(true)
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/005.phpt b/vendor/pimple/pimple/ext/pimple/tests/005.phpt
new file mode 100755
index 0000000..0479ee0
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/005.phpt
@@ -0,0 +1,27 @@
+--TEST--
+Test simple class inheritance
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+class MyPimple extends Pimple\Container
+{
+ public $someAttr = 'fooAttr';
+
+ public function offsetget($o)
+ {
+ var_dump("hit");
+ return parent::offsetget($o);
+ }
+}
+
+$p = new MyPimple;
+$p[42] = 'foo';
+echo $p[42];
+echo "\n";
+echo $p->someAttr;
+?>
+--EXPECT--
+string(3) "hit"
+foo
+fooAttr
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/006.phpt b/vendor/pimple/pimple/ext/pimple/tests/006.phpt
new file mode 100755
index 0000000..cfe8a11
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/006.phpt
@@ -0,0 +1,51 @@
+--TEST--
+Test complex class inheritance
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+class MyPimple extends Pimple\Container
+{
+ public function offsetget($o)
+ {
+ var_dump("hit offsetget in " . __CLASS__);
+ return parent::offsetget($o);
+ }
+}
+
+class TestPimple extends MyPimple
+{
+ public function __construct($values)
+ {
+ array_shift($values);
+ parent::__construct($values);
+ }
+
+ public function offsetget($o)
+ {
+ var_dump('hit offsetget in ' . __CLASS__);
+ return parent::offsetget($o);
+ }
+
+ public function offsetset($o, $v)
+ {
+ var_dump('hit offsetset');
+ return parent::offsetset($o, $v);
+ }
+}
+
+$defaultValues = array('foo' => 'bar', 88 => 'baz');
+
+$p = new TestPimple($defaultValues);
+$p[42] = 'foo';
+var_dump($p[42]);
+var_dump($p[0]);
+?>
+--EXPECT--
+string(13) "hit offsetset"
+string(27) "hit offsetget in TestPimple"
+string(25) "hit offsetget in MyPimple"
+string(3) "foo"
+string(27) "hit offsetget in TestPimple"
+string(25) "hit offsetget in MyPimple"
+string(3) "baz"
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/007.phpt b/vendor/pimple/pimple/ext/pimple/tests/007.phpt
new file mode 100755
index 0000000..5aac683
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/007.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Test for read_dim/write_dim handlers
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+$p = new Pimple\Container();
+$p[42] = 'foo';
+$p['foo'] = 42;
+
+echo $p[42];
+echo "\n";
+echo $p['foo'];
+echo "\n";
+try {
+ var_dump($p['nonexistant']);
+ echo "Exception excpected";
+} catch (InvalidArgumentException $e) { }
+?>
+--EXPECTF--
+foo
+42
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/008.phpt b/vendor/pimple/pimple/ext/pimple/tests/008.phpt
new file mode 100755
index 0000000..db7eeec
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/008.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Test frozen services
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+$p = new Pimple\Container();
+$p[42] = 'foo';
+$p[42] = 'bar';
+
+$p['foo'] = function () { };
+$p['foo'] = function () { };
+
+$a = $p['foo'];
+
+try {
+ $p['foo'] = function () { };
+ echo "Exception excpected";
+} catch (RuntimeException $e) { }
+
+$p[42] = function() { };
+$a = $p[42];
+
+try {
+ $p[42] = function () { };
+ echo "Exception excpected";
+} catch (RuntimeException $e) { }
+?>
+--EXPECTF--
diff --git a/vendor/pimple/pimple/ext/pimple/tests/009.phpt b/vendor/pimple/pimple/ext/pimple/tests/009.phpt
new file mode 100755
index 0000000..bb05ea2
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/009.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Test service is called as callback, and only once
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+$p = new Pimple\Container();
+$p['foo'] = function($arg) use ($p) { var_dump($p === $arg); };
+$a = $p['foo'];
+$b = $p['foo']; /* should return not calling the callback */
+?>
+--EXPECTF--
+bool(true)
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/010.phpt b/vendor/pimple/pimple/ext/pimple/tests/010.phpt
new file mode 100755
index 0000000..badce01
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/010.phpt
@@ -0,0 +1,45 @@
+--TEST--
+Test service is called as callback for every callback type
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+function callme()
+{
+ return 'called';
+}
+
+$a = function() { return 'called'; };
+
+class Foo
+{
+ public static function bar()
+ {
+ return 'called';
+ }
+}
+
+$p = new Pimple\Container();
+$p['foo'] = 'callme';
+echo $p['foo'] . "\n";
+
+$p['bar'] = $a;
+echo $p['bar'] . "\n";
+
+$p['baz'] = "Foo::bar";
+echo $p['baz'] . "\n";
+
+$p['foobar'] = array('Foo', 'bar');
+var_dump($p['foobar']);
+
+?>
+--EXPECTF--
+callme
+called
+Foo::bar
+array(2) {
+ [0]=>
+ string(3) "Foo"
+ [1]=>
+ string(3) "bar"
+}
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/011.phpt b/vendor/pimple/pimple/ext/pimple/tests/011.phpt
new file mode 100755
index 0000000..6682ab8
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/011.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Test service callback throwing an exception
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+class CallBackException extends RuntimeException { }
+
+$p = new Pimple\Container();
+$p['foo'] = function () { throw new CallBackException; };
+try {
+ echo $p['foo'] . "\n";
+ echo "should not come here";
+} catch (CallBackException $e) {
+ echo "all right!";
+}
+?>
+--EXPECTF--
+all right!
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/012.phpt b/vendor/pimple/pimple/ext/pimple/tests/012.phpt
new file mode 100755
index 0000000..4c6ac48
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/012.phpt
@@ -0,0 +1,28 @@
+--TEST--
+Test service factory
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+
+$p = new Pimple\Container();
+
+$p->factory($f = function() { var_dump('called-1'); return 'ret-1';});
+
+$p[] = $f;
+
+$p[] = function () { var_dump('called-2'); return 'ret-2'; };
+
+var_dump($p[0]);
+var_dump($p[0]);
+var_dump($p[1]);
+var_dump($p[1]);
+?>
+--EXPECTF--
+string(8) "called-1"
+string(5) "ret-1"
+string(8) "called-1"
+string(5) "ret-1"
+string(8) "called-2"
+string(5) "ret-2"
+string(5) "ret-2"
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/013.phpt b/vendor/pimple/pimple/ext/pimple/tests/013.phpt
new file mode 100755
index 0000000..f419958
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/013.phpt
@@ -0,0 +1,33 @@
+--TEST--
+Test keys()
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+
+$p = new Pimple\Container();
+
+var_dump($p->keys());
+
+$p['foo'] = 'bar';
+$p[] = 'foo';
+
+var_dump($p->keys());
+
+unset($p['foo']);
+
+var_dump($p->keys());
+?>
+--EXPECTF--
+array(0) {
+}
+array(2) {
+ [0]=>
+ string(3) "foo"
+ [1]=>
+ int(0)
+}
+array(1) {
+ [0]=>
+ int(0)
+}
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/014.phpt b/vendor/pimple/pimple/ext/pimple/tests/014.phpt
new file mode 100755
index 0000000..ac93721
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/014.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Test raw()
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+
+$p = new Pimple\Container();
+$f = function () { var_dump('called-2'); return 'ret-2'; };
+
+$p['foo'] = $f;
+$p[42] = $f;
+
+var_dump($p['foo']);
+var_dump($p->raw('foo'));
+var_dump($p[42]);
+
+unset($p['foo']);
+
+try {
+ $p->raw('foo');
+ echo "expected exception";
+} catch (InvalidArgumentException $e) { }
+--EXPECTF--
+string(8) "called-2"
+string(5) "ret-2"
+object(Closure)#%i (0) {
+}
+string(8) "called-2"
+string(5) "ret-2"
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/015.phpt b/vendor/pimple/pimple/ext/pimple/tests/015.phpt
new file mode 100755
index 0000000..314f008
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/015.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test protect()
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+
+$p = new Pimple\Container();
+$f = function () { return 'foo'; };
+$p['foo'] = $f;
+
+$p->protect($f);
+
+var_dump($p['foo']);
+--EXPECTF--
+object(Closure)#%i (0) {
+}
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/016.phpt b/vendor/pimple/pimple/ext/pimple/tests/016.phpt
new file mode 100755
index 0000000..e55edb0
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/016.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Test extend()
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+/*
+ This is part of Pimple::extend() code :
+
+ $extended = function ($c) use ($callable, $factory) {
+ return $callable($factory($c), $c);
+ };
+*/
+
+$p = new Pimple\Container();
+$p[12] = function ($v) { var_dump($v); return 'foo';}; /* $factory in code above */
+
+$c = $p->extend(12, function ($w) { var_dump($w); return 'bar'; }); /* $callable in code above */
+
+var_dump($c('param'));
+--EXPECTF--
+string(5) "param"
+string(3) "foo"
+string(3) "bar"
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/017.phpt b/vendor/pimple/pimple/ext/pimple/tests/017.phpt
new file mode 100755
index 0000000..bac23ce
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/017.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test extend() with exception in service extension
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+
+$p = new Pimple\Container();
+$p[12] = function ($v) { return 'foo';};
+
+$c = $p->extend(12, function ($w) { throw new BadMethodCallException; });
+
+try {
+ $p[12];
+ echo "Exception expected";
+} catch (BadMethodCallException $e) { }
+--EXPECTF--
diff --git a/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt b/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt
new file mode 100755
index 0000000..8f881d6
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test extend() with exception in service factory
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+
+$p = new Pimple\Container();
+$p[12] = function ($v) { throw new BadMethodCallException; };
+
+$c = $p->extend(12, function ($w) { return 'foobar'; });
+
+try {
+ $p[12];
+ echo "Exception expected";
+} catch (BadMethodCallException $e) { }
+--EXPECTF--
diff --git a/vendor/pimple/pimple/ext/pimple/tests/018.phpt b/vendor/pimple/pimple/ext/pimple/tests/018.phpt
new file mode 100755
index 0000000..27c12a1
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/018.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Test register()
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+
+class Foo implements Pimple\ServiceProviderInterface
+{
+ public function register(Pimple\Container $p)
+ {
+ var_dump($p);
+ }
+}
+
+$p = new Pimple\Container();
+$p->register(new Foo, array(42 => 'bar'));
+
+var_dump($p[42]);
+--EXPECTF--
+object(Pimple\Container)#1 (0) {
+}
+string(3) "bar"
\ No newline at end of file
diff --git a/vendor/pimple/pimple/ext/pimple/tests/019.phpt b/vendor/pimple/pimple/ext/pimple/tests/019.phpt
new file mode 100755
index 0000000..28a9aec
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/019.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Test register() returns static and is a fluent interface
+--SKIPIF--
+<?php if (!extension_loaded("pimple")) print "skip"; ?>
+--FILE--
+<?php
+
+class Foo implements Pimple\ServiceProviderInterface
+{
+ public function register(Pimple\Container $p)
+ {
+ }
+}
+
+$p = new Pimple\Container();
+var_dump($p === $p->register(new Foo));
+--EXPECTF--
+bool(true)
diff --git a/vendor/pimple/pimple/ext/pimple/tests/bench.phpb b/vendor/pimple/pimple/ext/pimple/tests/bench.phpb
new file mode 100755
index 0000000..8f983e6
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/bench.phpb
@@ -0,0 +1,51 @@
+<?php
+
+if (!class_exists('Pimple\Container')) {
+ require_once __DIR__ . '/../../../src/Pimple/Container.php';
+} else {
+ echo "pimple-c extension detected, using...\n\n";
+}
+
+$time = microtime(true);
+
+function foo() { }
+$factory = function () { };
+
+for ($i=0; $i<10000; $i++) {
+
+$p = new Pimple\Container;
+
+$p['foo'] = 'bar';
+
+if (!isset($p[3])) {
+ $p[3] = $p['foo'];
+ $p[] = 'bar';
+}
+
+$p[2] = 42;
+
+if (isset($p[2])) {
+ unset($p[2]);
+}
+
+$p[42] = $p['foo'];
+
+$p['cb'] = function($arg) { };
+
+$p[] = $p['cb'];
+
+echo $p['cb'];
+echo $p['cb'];
+echo $p['cb'];
+
+//$p->factory($factory);
+
+$p['factory'] = $factory;
+
+echo $p['factory'];
+echo $p['factory'];
+echo $p['factory'];
+
+}
+
+echo microtime(true) - $time;
diff --git a/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb b/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb
new file mode 100755
index 0000000..aec541f
--- /dev/null
+++ b/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb
@@ -0,0 +1,25 @@
+<?php
+
+if (!class_exists('Pimple\Container')) {
+ require_once __DIR__ . '/../../../src/Pimple/Container.php';
+} else {
+ echo "pimple-c extension detected, using...\n\n";
+}
+
+$time = microtime(true);
+
+
+$service = function ($arg) { return "I'm a service"; };
+
+for ($i=0; $i<10000; $i++) {
+
+$p = new Pimple\Container;
+$p['my_service'] = $service;
+
+$a = $p['my_service'];
+$b = $p['my_service'];
+
+}
+
+echo microtime(true) - $time;
+?>
diff --git a/vendor/pimple/pimple/phpunit.xml.dist b/vendor/pimple/pimple/phpunit.xml.dist
new file mode 100755
index 0000000..5c8d487
--- /dev/null
+++ b/vendor/pimple/pimple/phpunit.xml.dist
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+ backupGlobals="false"
+ colors="true"
+ bootstrap="vendor/autoload.php"
+>
+ <testsuites>
+ <testsuite name="Pimple Test Suite">
+ <directory>./src/Pimple/Tests</directory>
+ </testsuite>
+ </testsuites>
+</phpunit>
diff --git a/vendor/pimple/pimple/src/Pimple/Container.php b/vendor/pimple/pimple/src/Pimple/Container.php
new file mode 100755
index 0000000..707b92b
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Container.php
@@ -0,0 +1,298 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple;
+
+use Pimple\Exception\ExpectedInvokableException;
+use Pimple\Exception\FrozenServiceException;
+use Pimple\Exception\InvalidServiceIdentifierException;
+use Pimple\Exception\UnknownIdentifierException;
+
+/**
+ * Container main class.
+ *
+ * @author Fabien Potencier
+ */
+class Container implements \ArrayAccess
+{
+ private $values = array();
+ private $factories;
+ private $protected;
+ private $frozen = array();
+ private $raw = array();
+ private $keys = array();
+
+ /**
+ * Instantiates the container.
+ *
+ * Objects and parameters can be passed as argument to the constructor.
+ *
+ * @param array $values The parameters or objects
+ */
+ public function __construct(array $values = array())
+ {
+ $this->factories = new \SplObjectStorage();
+ $this->protected = new \SplObjectStorage();
+
+ foreach ($values as $key => $value) {
+ $this->offsetSet($key, $value);
+ }
+ }
+
+ /**
+ * Sets a parameter or an object.
+ *
+ * Objects must be defined as Closures.
+ *
+ * Allowing any PHP callable leads to difficult to debug problems
+ * as function names (strings) are callable (creating a function with
+ * the same name as an existing parameter would break your container).
+ *
+ * @param string $id The unique identifier for the parameter or object
+ * @param mixed $value The value of the parameter or a closure to define an object
+ *
+ * @throws FrozenServiceException Prevent override of a frozen service
+ */
+ public function offsetSet($id, $value)
+ {
+ if (isset($this->frozen[$id])) {
+ throw new FrozenServiceException($id);
+ }
+
+ $this->values[$id] = $value;
+ $this->keys[$id] = true;
+ }
+
+ /**
+ * Gets a parameter or an object.
+ *
+ * @param string $id The unique identifier for the parameter or object
+ *
+ * @return mixed The value of the parameter or an object
+ *
+ * @throws UnknownIdentifierException If the identifier is not defined
+ */
+ public function offsetGet($id)
+ {
+ if (!isset($this->keys[$id])) {
+ throw new UnknownIdentifierException($id);
+ }
+
+ if (
+ isset($this->raw[$id])
+ || !\is_object($this->values[$id])
+ || isset($this->protected[$this->values[$id]])
+ || !\method_exists($this->values[$id], '__invoke')
+ ) {
+ return $this->values[$id];
+ }
+
+ if (isset($this->factories[$this->values[$id]])) {
+ return $this->values[$id]($this);
+ }
+
+ $raw = $this->values[$id];
+ $val = $this->values[$id] = $raw($this);
+ $this->raw[$id] = $raw;
+
+ $this->frozen[$id] = true;
+
+ return $val;
+ }
+
+ /**
+ * Checks if a parameter or an object is set.
+ *
+ * @param string $id The unique identifier for the parameter or object
+ *
+ * @return bool
+ */
+ public function offsetExists($id)
+ {
+ return isset($this->keys[$id]);
+ }
+
+ /**
+ * Unsets a parameter or an object.
+ *
+ * @param string $id The unique identifier for the parameter or object
+ */
+ public function offsetUnset($id)
+ {
+ if (isset($this->keys[$id])) {
+ if (\is_object($this->values[$id])) {
+ unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]);
+ }
+
+ unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]);
+ }
+ }
+
+ /**
+ * Marks a callable as being a factory service.
+ *
+ * @param callable $callable A service definition to be used as a factory
+ *
+ * @return callable The passed callable
+ *
+ * @throws ExpectedInvokableException Service definition has to be a closure or an invokable object
+ */
+ public function factory($callable)
+ {
+ if (!\method_exists($callable, '__invoke')) {
+ throw new ExpectedInvokableException('Service definition is not a Closure or invokable object.');
+ }
+
+ $this->factories->attach($callable);
+
+ return $callable;
+ }
+
+ /**
+ * Protects a callable from being interpreted as a service.
+ *
+ * This is useful when you want to store a callable as a parameter.
+ *
+ * @param callable $callable A callable to protect from being evaluated
+ *
+ * @return callable The passed callable
+ *
+ * @throws ExpectedInvokableException Service definition has to be a closure or an invokable object
+ */
+ public function protect($callable)
+ {
+ if (!\method_exists($callable, '__invoke')) {
+ throw new ExpectedInvokableException('Callable is not a Closure or invokable object.');
+ }
+
+ $this->protected->attach($callable);
+
+ return $callable;
+ }
+
+ /**
+ * Gets a parameter or the closure defining an object.
+ *
+ * @param string $id The unique identifier for the parameter or object
+ *
+ * @return mixed The value of the parameter or the closure defining an object
+ *
+ * @throws UnknownIdentifierException If the identifier is not defined
+ */
+ public function raw($id)
+ {
+ if (!isset($this->keys[$id])) {
+ throw new UnknownIdentifierException($id);
+ }
+
+ if (isset($this->raw[$id])) {
+ return $this->raw[$id];
+ }
+
+ return $this->values[$id];
+ }
+
+ /**
+ * Extends an object definition.
+ *
+ * Useful when you want to extend an existing object definition,
+ * without necessarily loading that object.
+ *
+ * @param string $id The unique identifier for the object
+ * @param callable $callable A service definition to extend the original
+ *
+ * @return callable The wrapped callable
+ *
+ * @throws UnknownIdentifierException If the identifier is not defined
+ * @throws FrozenServiceException If the service is frozen
+ * @throws InvalidServiceIdentifierException If the identifier belongs to a parameter
+ * @throws ExpectedInvokableException If the extension callable is not a closure or an invokable object
+ */
+ public function extend($id, $callable)
+ {
+ if (!isset($this->keys[$id])) {
+ throw new UnknownIdentifierException($id);
+ }
+
+ if (isset($this->frozen[$id])) {
+ throw new FrozenServiceException($id);
+ }
+
+ if (!\is_object($this->values[$id]) || !\method_exists($this->values[$id], '__invoke')) {
+ throw new InvalidServiceIdentifierException($id);
+ }
+
+ if (isset($this->protected[$this->values[$id]])) {
+ @\trigger_error(\sprintf('How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "%s" should be protected?', $id), \E_USER_DEPRECATED);
+ }
+
+ if (!\is_object($callable) || !\method_exists($callable, '__invoke')) {
+ throw new ExpectedInvokableException('Extension service definition is not a Closure or invokable object.');
+ }
+
+ $factory = $this->values[$id];
+
+ $extended = function ($c) use ($callable, $factory) {
+ return $callable($factory($c), $c);
+ };
+
+ if (isset($this->factories[$factory])) {
+ $this->factories->detach($factory);
+ $this->factories->attach($extended);
+ }
+
+ return $this[$id] = $extended;
+ }
+
+ /**
+ * Returns all defined value names.
+ *
+ * @return array An array of value names
+ */
+ public function keys()
+ {
+ return \array_keys($this->values);
+ }
+
+ /**
+ * Registers a service provider.
+ *
+ * @param ServiceProviderInterface $provider A ServiceProviderInterface instance
+ * @param array $values An array of values that customizes the provider
+ *
+ * @return static
+ */
+ public function register(ServiceProviderInterface $provider, array $values = array())
+ {
+ $provider->register($this);
+
+ foreach ($values as $key => $value) {
+ $this[$key] = $value;
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php b/vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php
new file mode 100755
index 0000000..7228421
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Exception;
+
+use Psr\Container\ContainerExceptionInterface;
+
+/**
+ * A closure or invokable object was expected.
+ *
+ * @author Pascal Luna <skalpa@zetareticuli.org>
+ */
+class ExpectedInvokableException extends \InvalidArgumentException implements ContainerExceptionInterface
+{
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php b/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php
new file mode 100755
index 0000000..e4d2f6d
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Exception;
+
+use Psr\Container\ContainerExceptionInterface;
+
+/**
+ * An attempt to modify a frozen service was made.
+ *
+ * @author Pascal Luna <skalpa@zetareticuli.org>
+ */
+class FrozenServiceException extends \RuntimeException implements ContainerExceptionInterface
+{
+ /**
+ * @param string $id Identifier of the frozen service
+ */
+ public function __construct($id)
+ {
+ parent::__construct(\sprintf('Cannot override frozen service "%s".', $id));
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php b/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php
new file mode 100755
index 0000000..91e82f9
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Exception;
+
+use Psr\Container\NotFoundExceptionInterface;
+
+/**
+ * An attempt to perform an operation that requires a service identifier was made.
+ *
+ * @author Pascal Luna <skalpa@zetareticuli.org>
+ */
+class InvalidServiceIdentifierException extends \InvalidArgumentException implements NotFoundExceptionInterface
+{
+ /**
+ * @param string $id The invalid identifier
+ */
+ public function __construct($id)
+ {
+ parent::__construct(\sprintf('Identifier "%s" does not contain an object definition.', $id));
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php b/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php
new file mode 100755
index 0000000..fb6b626
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Exception;
+
+use Psr\Container\NotFoundExceptionInterface;
+
+/**
+ * The identifier of a valid service or parameter was expected.
+ *
+ * @author Pascal Luna <skalpa@zetareticuli.org>
+ */
+class UnknownIdentifierException extends \InvalidArgumentException implements NotFoundExceptionInterface
+{
+ /**
+ * @param string $id The unknown identifier
+ */
+ public function __construct($id)
+ {
+ parent::__construct(\sprintf('Identifier "%s" is not defined.', $id));
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Psr11/Container.php b/vendor/pimple/pimple/src/Pimple/Psr11/Container.php
new file mode 100755
index 0000000..cadbfff
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Psr11/Container.php
@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009-2017 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Psr11;
+
+use Pimple\Container as PimpleContainer;
+use Psr\Container\ContainerInterface;
+
+/**
+ * PSR-11 compliant wrapper.
+ *
+ * @author Pascal Luna <skalpa@zetareticuli.org>
+ */
+final class Container implements ContainerInterface
+{
+ private $pimple;
+
+ public function __construct(PimpleContainer $pimple)
+ {
+ $this->pimple = $pimple;
+ }
+
+ public function get($id)
+ {
+ return $this->pimple[$id];
+ }
+
+ public function has($id)
+ {
+ return isset($this->pimple[$id]);
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php b/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php
new file mode 100755
index 0000000..3361c6f
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php
@@ -0,0 +1,75 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Psr11;
+
+use Pimple\Container as PimpleContainer;
+use Pimple\Exception\UnknownIdentifierException;
+use Psr\Container\ContainerInterface;
+
+/**
+ * Pimple PSR-11 service locator.
+ *
+ * @author Pascal Luna <skalpa@zetareticuli.org>
+ */
+class ServiceLocator implements ContainerInterface
+{
+ private $container;
+ private $aliases = array();
+
+ /**
+ * @param PimpleContainer $container The Container instance used to locate services
+ * @param array $ids Array of service ids that can be located. String keys can be used to define aliases
+ */
+ public function __construct(PimpleContainer $container, array $ids)
+ {
+ $this->container = $container;
+
+ foreach ($ids as $key => $id) {
+ $this->aliases[\is_int($key) ? $id : $key] = $id;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($id)
+ {
+ if (!isset($this->aliases[$id])) {
+ throw new UnknownIdentifierException($id);
+ }
+
+ return $this->container[$this->aliases[$id]];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($id)
+ {
+ return isset($this->aliases[$id]) && isset($this->container[$this->aliases[$id]]);
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/ServiceIterator.php b/vendor/pimple/pimple/src/Pimple/ServiceIterator.php
new file mode 100755
index 0000000..5cde518
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/ServiceIterator.php
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple;
+
+/**
+ * Lazy service iterator.
+ *
+ * @author Pascal Luna <skalpa@zetareticuli.org>
+ */
+final class ServiceIterator implements \Iterator
+{
+ private $container;
+ private $ids;
+
+ public function __construct(Container $container, array $ids)
+ {
+ $this->container = $container;
+ $this->ids = $ids;
+ }
+
+ public function rewind()
+ {
+ \reset($this->ids);
+ }
+
+ public function current()
+ {
+ return $this->container[\current($this->ids)];
+ }
+
+ public function key()
+ {
+ return \current($this->ids);
+ }
+
+ public function next()
+ {
+ \next($this->ids);
+ }
+
+ public function valid()
+ {
+ return null !== \key($this->ids);
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php b/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php
new file mode 100755
index 0000000..c004594
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple;
+
+/**
+ * Pimple service provider interface.
+ *
+ * @author Fabien Potencier
+ * @author Dominik Zogg
+ */
+interface ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param Container $pimple A container instance
+ */
+ public function register(Container $pimple);
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php
new file mode 100755
index 0000000..aba453b
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Tests\Fixtures;
+
+class Invokable
+{
+ public function __invoke($value = null)
+ {
+ $service = new Service();
+ $service->value = $value;
+
+ return $service;
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php
new file mode 100755
index 0000000..33cd4e5
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php
@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Tests\Fixtures;
+
+class NonInvokable
+{
+ public function __call($a, $b)
+ {
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/PimpleServiceProvider.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/PimpleServiceProvider.php
new file mode 100755
index 0000000..0c910af
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/PimpleServiceProvider.php
@@ -0,0 +1,54 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Tests\Fixtures;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class PimpleServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param Container $pimple An Container instance
+ */
+ public function register(Container $pimple)
+ {
+ $pimple['param'] = 'value';
+
+ $pimple['service'] = function () {
+ return new Service();
+ };
+
+ $pimple['factory'] = $pimple->factory(function () {
+ return new Service();
+ });
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php
new file mode 100755
index 0000000..d71b184
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Tests\Fixtures;
+
+/**
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class Service
+{
+ public $value;
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php b/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php
new file mode 100755
index 0000000..8e5c4c7
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php
@@ -0,0 +1,76 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Tests;
+
+use Pimple\Container;
+
+/**
+ * @author Dominik Zogg <dominik.zogg@gmail.com>
+ */
+class PimpleServiceProviderInterfaceTest extends \PHPUnit_Framework_TestCase
+{
+ public function testProvider()
+ {
+ $pimple = new Container();
+
+ $pimpleServiceProvider = new Fixtures\PimpleServiceProvider();
+ $pimpleServiceProvider->register($pimple);
+
+ $this->assertEquals('value', $pimple['param']);
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']);
+
+ $serviceOne = $pimple['factory'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
+
+ $serviceTwo = $pimple['factory'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
+
+ $this->assertNotSame($serviceOne, $serviceTwo);
+ }
+
+ public function testProviderWithRegisterMethod()
+ {
+ $pimple = new Container();
+
+ $pimple->register(new Fixtures\PimpleServiceProvider(), array(
+ 'anotherParameter' => 'anotherValue',
+ ));
+
+ $this->assertEquals('value', $pimple['param']);
+ $this->assertEquals('anotherValue', $pimple['anotherParameter']);
+
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']);
+
+ $serviceOne = $pimple['factory'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
+
+ $serviceTwo = $pimple['factory'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
+
+ $this->assertNotSame($serviceOne, $serviceTwo);
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php b/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php
new file mode 100755
index 0000000..acb66e0
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php
@@ -0,0 +1,589 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Tests;
+
+use Pimple\Container;
+
+/**
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class PimpleTest extends \PHPUnit_Framework_TestCase
+{
+ public function testWithString()
+ {
+ $pimple = new Container();
+ $pimple['param'] = 'value';
+
+ $this->assertEquals('value', $pimple['param']);
+ }
+
+ public function testWithClosure()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']);
+ }
+
+ public function testServicesShouldBeDifferent()
+ {
+ $pimple = new Container();
+ $pimple['service'] = $pimple->factory(function () {
+ return new Fixtures\Service();
+ });
+
+ $serviceOne = $pimple['service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
+
+ $serviceTwo = $pimple['service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
+
+ $this->assertNotSame($serviceOne, $serviceTwo);
+ }
+
+ public function testShouldPassContainerAsParameter()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $pimple['container'] = function ($container) {
+ return $container;
+ };
+
+ $this->assertNotSame($pimple, $pimple['service']);
+ $this->assertSame($pimple, $pimple['container']);
+ }
+
+ public function testIsset()
+ {
+ $pimple = new Container();
+ $pimple['param'] = 'value';
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+
+ $pimple['null'] = null;
+
+ $this->assertTrue(isset($pimple['param']));
+ $this->assertTrue(isset($pimple['service']));
+ $this->assertTrue(isset($pimple['null']));
+ $this->assertFalse(isset($pimple['non_existent']));
+ }
+
+ public function testConstructorInjection()
+ {
+ $params = array('param' => 'value');
+ $pimple = new Container($params);
+
+ $this->assertSame($params['param'], $pimple['param']);
+ }
+
+ /**
+ * @expectedException \Pimple\Exception\UnknownIdentifierException
+ * @expectedExceptionMessage Identifier "foo" is not defined.
+ */
+ public function testOffsetGetValidatesKeyIsPresent()
+ {
+ $pimple = new Container();
+ echo $pimple['foo'];
+ }
+
+ /**
+ * @group legacy
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Identifier "foo" is not defined.
+ */
+ public function testLegacyOffsetGetValidatesKeyIsPresent()
+ {
+ $pimple = new Container();
+ echo $pimple['foo'];
+ }
+
+ public function testOffsetGetHonorsNullValues()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = null;
+ $this->assertNull($pimple['foo']);
+ }
+
+ public function testUnset()
+ {
+ $pimple = new Container();
+ $pimple['param'] = 'value';
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+
+ unset($pimple['param'], $pimple['service']);
+ $this->assertFalse(isset($pimple['param']));
+ $this->assertFalse(isset($pimple['service']));
+ }
+
+ /**
+ * @dataProvider serviceDefinitionProvider
+ */
+ public function testShare($service)
+ {
+ $pimple = new Container();
+ $pimple['shared_service'] = $service;
+
+ $serviceOne = $pimple['shared_service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
+
+ $serviceTwo = $pimple['shared_service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
+
+ $this->assertSame($serviceOne, $serviceTwo);
+ }
+
+ /**
+ * @dataProvider serviceDefinitionProvider
+ */
+ public function testProtect($service)
+ {
+ $pimple = new Container();
+ $pimple['protected'] = $pimple->protect($service);
+
+ $this->assertSame($service, $pimple['protected']);
+ }
+
+ public function testGlobalFunctionNameAsParameterValue()
+ {
+ $pimple = new Container();
+ $pimple['global_function'] = 'strlen';
+ $this->assertSame('strlen', $pimple['global_function']);
+ }
+
+ public function testRaw()
+ {
+ $pimple = new Container();
+ $pimple['service'] = $definition = $pimple->factory(function () { return 'foo'; });
+ $this->assertSame($definition, $pimple->raw('service'));
+ }
+
+ public function testRawHonorsNullValues()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = null;
+ $this->assertNull($pimple->raw('foo'));
+ }
+
+ public function testFluentRegister()
+ {
+ $pimple = new Container();
+ $this->assertSame($pimple, $pimple->register($this->getMockBuilder('Pimple\ServiceProviderInterface')->getMock()));
+ }
+
+ /**
+ * @expectedException \Pimple\Exception\UnknownIdentifierException
+ * @expectedExceptionMessage Identifier "foo" is not defined.
+ */
+ public function testRawValidatesKeyIsPresent()
+ {
+ $pimple = new Container();
+ $pimple->raw('foo');
+ }
+
+ /**
+ * @group legacy
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Identifier "foo" is not defined.
+ */
+ public function testLegacyRawValidatesKeyIsPresent()
+ {
+ $pimple = new Container();
+ $pimple->raw('foo');
+ }
+
+ /**
+ * @dataProvider serviceDefinitionProvider
+ */
+ public function testExtend($service)
+ {
+ $pimple = new Container();
+ $pimple['shared_service'] = function () {
+ return new Fixtures\Service();
+ };
+ $pimple['factory_service'] = $pimple->factory(function () {
+ return new Fixtures\Service();
+ });
+
+ $pimple->extend('shared_service', $service);
+ $serviceOne = $pimple['shared_service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
+ $serviceTwo = $pimple['shared_service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
+ $this->assertSame($serviceOne, $serviceTwo);
+ $this->assertSame($serviceOne->value, $serviceTwo->value);
+
+ $pimple->extend('factory_service', $service);
+ $serviceOne = $pimple['factory_service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
+ $serviceTwo = $pimple['factory_service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
+ $this->assertNotSame($serviceOne, $serviceTwo);
+ $this->assertNotSame($serviceOne->value, $serviceTwo->value);
+ }
+
+ public function testExtendDoesNotLeakWithFactories()
+ {
+ if (extension_loaded('pimple')) {
+ $this->markTestSkipped('Pimple extension does not support this test');
+ }
+ $pimple = new Container();
+
+ $pimple['foo'] = $pimple->factory(function () { return; });
+ $pimple['foo'] = $pimple->extend('foo', function ($foo, $pimple) { return; });
+ unset($pimple['foo']);
+
+ $p = new \ReflectionProperty($pimple, 'values');
+ $p->setAccessible(true);
+ $this->assertEmpty($p->getValue($pimple));
+
+ $p = new \ReflectionProperty($pimple, 'factories');
+ $p->setAccessible(true);
+ $this->assertCount(0, $p->getValue($pimple));
+ }
+
+ /**
+ * @expectedException \Pimple\Exception\UnknownIdentifierException
+ * @expectedExceptionMessage Identifier "foo" is not defined.
+ */
+ public function testExtendValidatesKeyIsPresent()
+ {
+ $pimple = new Container();
+ $pimple->extend('foo', function () {});
+ }
+
+ /**
+ * @group legacy
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Identifier "foo" is not defined.
+ */
+ public function testLegacyExtendValidatesKeyIsPresent()
+ {
+ $pimple = new Container();
+ $pimple->extend('foo', function () {});
+ }
+
+ public function testKeys()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = 123;
+ $pimple['bar'] = 123;
+
+ $this->assertEquals(array('foo', 'bar'), $pimple->keys());
+ }
+
+ /** @test */
+ public function settingAnInvokableObjectShouldTreatItAsFactory()
+ {
+ $pimple = new Container();
+ $pimple['invokable'] = new Fixtures\Invokable();
+
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['invokable']);
+ }
+
+ /** @test */
+ public function settingNonInvokableObjectShouldTreatItAsParameter()
+ {
+ $pimple = new Container();
+ $pimple['non_invokable'] = new Fixtures\NonInvokable();
+
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\NonInvokable', $pimple['non_invokable']);
+ }
+
+ /**
+ * @dataProvider badServiceDefinitionProvider
+ * @expectedException \Pimple\Exception\ExpectedInvokableException
+ * @expectedExceptionMessage Service definition is not a Closure or invokable object.
+ */
+ public function testFactoryFailsForInvalidServiceDefinitions($service)
+ {
+ $pimple = new Container();
+ $pimple->factory($service);
+ }
+
+ /**
+ * @group legacy
+ * @dataProvider badServiceDefinitionProvider
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Service definition is not a Closure or invokable object.
+ */
+ public function testLegacyFactoryFailsForInvalidServiceDefinitions($service)
+ {
+ $pimple = new Container();
+ $pimple->factory($service);
+ }
+
+ /**
+ * @dataProvider badServiceDefinitionProvider
+ * @expectedException \Pimple\Exception\ExpectedInvokableException
+ * @expectedExceptionMessage Callable is not a Closure or invokable object.
+ */
+ public function testProtectFailsForInvalidServiceDefinitions($service)
+ {
+ $pimple = new Container();
+ $pimple->protect($service);
+ }
+
+ /**
+ * @group legacy
+ * @dataProvider badServiceDefinitionProvider
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Callable is not a Closure or invokable object.
+ */
+ public function testLegacyProtectFailsForInvalidServiceDefinitions($service)
+ {
+ $pimple = new Container();
+ $pimple->protect($service);
+ }
+
+ /**
+ * @dataProvider badServiceDefinitionProvider
+ * @expectedException \Pimple\Exception\InvalidServiceIdentifierException
+ * @expectedExceptionMessage Identifier "foo" does not contain an object definition.
+ */
+ public function testExtendFailsForKeysNotContainingServiceDefinitions($service)
+ {
+ $pimple = new Container();
+ $pimple['foo'] = $service;
+ $pimple->extend('foo', function () {});
+ }
+
+ /**
+ * @group legacy
+ * @dataProvider badServiceDefinitionProvider
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Identifier "foo" does not contain an object definition.
+ */
+ public function testLegacyExtendFailsForKeysNotContainingServiceDefinitions($service)
+ {
+ $pimple = new Container();
+ $pimple['foo'] = $service;
+ $pimple->extend('foo', function () {});
+ }
+
+ /**
+ * @group legacy
+ * @expectedDeprecation How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "foo" should be protected?
+ */
+ public function testExtendingProtectedClosureDeprecation()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = $pimple->protect(function () {
+ return 'bar';
+ });
+
+ $pimple->extend('foo', function ($value) {
+ return $value.'-baz';
+ });
+
+ $this->assertSame('bar-baz', $pimple['foo']);
+ }
+
+ /**
+ * @dataProvider badServiceDefinitionProvider
+ * @expectedException \Pimple\Exception\ExpectedInvokableException
+ * @expectedExceptionMessage Extension service definition is not a Closure or invokable object.
+ */
+ public function testExtendFailsForInvalidServiceDefinitions($service)
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {};
+ $pimple->extend('foo', $service);
+ }
+
+ /**
+ * @group legacy
+ * @dataProvider badServiceDefinitionProvider
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Extension service definition is not a Closure or invokable object.
+ */
+ public function testLegacyExtendFailsForInvalidServiceDefinitions($service)
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {};
+ $pimple->extend('foo', $service);
+ }
+
+ /**
+ * @expectedException \Pimple\Exception\FrozenServiceException
+ * @expectedExceptionMessage Cannot override frozen service "foo".
+ */
+ public function testExtendFailsIfFrozenServiceIsNonInvokable()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return new Fixtures\NonInvokable();
+ };
+ $foo = $pimple['foo'];
+
+ $pimple->extend('foo', function () {});
+ }
+
+ /**
+ * @expectedException \Pimple\Exception\FrozenServiceException
+ * @expectedExceptionMessage Cannot override frozen service "foo".
+ */
+ public function testExtendFailsIfFrozenServiceIsInvokable()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return new Fixtures\Invokable();
+ };
+ $foo = $pimple['foo'];
+
+ $pimple->extend('foo', function () {});
+ }
+
+ /**
+ * Provider for invalid service definitions.
+ */
+ public function badServiceDefinitionProvider()
+ {
+ return array(
+ array(123),
+ array(new Fixtures\NonInvokable()),
+ );
+ }
+
+ /**
+ * Provider for service definitions.
+ */
+ public function serviceDefinitionProvider()
+ {
+ return array(
+ array(function ($value) {
+ $service = new Fixtures\Service();
+ $service->value = $value;
+
+ return $service;
+ }),
+ array(new Fixtures\Invokable()),
+ );
+ }
+
+ public function testDefiningNewServiceAfterFreeze()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return 'foo';
+ };
+ $foo = $pimple['foo'];
+
+ $pimple['bar'] = function () {
+ return 'bar';
+ };
+ $this->assertSame('bar', $pimple['bar']);
+ }
+
+ /**
+ * @expectedException \Pimple\Exception\FrozenServiceException
+ * @expectedExceptionMessage Cannot override frozen service "foo".
+ */
+ public function testOverridingServiceAfterFreeze()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return 'foo';
+ };
+ $foo = $pimple['foo'];
+
+ $pimple['foo'] = function () {
+ return 'bar';
+ };
+ }
+
+ /**
+ * @group legacy
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Cannot override frozen service "foo".
+ */
+ public function testLegacyOverridingServiceAfterFreeze()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return 'foo';
+ };
+ $foo = $pimple['foo'];
+
+ $pimple['foo'] = function () {
+ return 'bar';
+ };
+ }
+
+ public function testRemovingServiceAfterFreeze()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return 'foo';
+ };
+ $foo = $pimple['foo'];
+
+ unset($pimple['foo']);
+ $pimple['foo'] = function () {
+ return 'bar';
+ };
+ $this->assertSame('bar', $pimple['foo']);
+ }
+
+ public function testExtendingService()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return 'foo';
+ };
+ $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) {
+ return "$foo.bar";
+ });
+ $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) {
+ return "$foo.baz";
+ });
+ $this->assertSame('foo.bar.baz', $pimple['foo']);
+ }
+
+ public function testExtendingServiceAfterOtherServiceFreeze()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return 'foo';
+ };
+ $pimple['bar'] = function () {
+ return 'bar';
+ };
+ $foo = $pimple['foo'];
+
+ $pimple['bar'] = $pimple->extend('bar', function ($bar, $app) {
+ return "$bar.baz";
+ });
+ $this->assertSame('bar.baz', $pimple['bar']);
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php
new file mode 100755
index 0000000..7ca2d7f
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php
@@ -0,0 +1,77 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009-2017 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Tests\Psr11;
+
+use PHPUnit\Framework\TestCase;
+use Pimple\Container;
+use Pimple\Psr11\Container as PsrContainer;
+use Pimple\Tests\Fixtures\Service;
+
+class ContainerTest extends TestCase
+{
+ public function testGetReturnsExistingService()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Service();
+ };
+ $psr = new PsrContainer($pimple);
+
+ $this->assertSame($pimple['service'], $psr->get('service'));
+ }
+
+ /**
+ * @expectedException \Psr\Container\NotFoundExceptionInterface
+ * @expectedExceptionMessage Identifier "service" is not defined.
+ */
+ public function testGetThrowsExceptionIfServiceIsNotFound()
+ {
+ $pimple = new Container();
+ $psr = new PsrContainer($pimple);
+
+ $psr->get('service');
+ }
+
+ public function testHasReturnsTrueIfServiceExists()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Service();
+ };
+ $psr = new PsrContainer($pimple);
+
+ $this->assertTrue($psr->has('service'));
+ }
+
+ public function testHasReturnsFalseIfServiceDoesNotExist()
+ {
+ $pimple = new Container();
+ $psr = new PsrContainer($pimple);
+
+ $this->assertFalse($psr->has('service'));
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php
new file mode 100755
index 0000000..c9a0812
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php
@@ -0,0 +1,134 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Tests\Psr11;
+
+use PHPUnit\Framework\TestCase;
+use Pimple\Container;
+use Pimple\Psr11\ServiceLocator;
+use Pimple\Tests\Fixtures;
+
+/**
+ * ServiceLocator test case.
+ *
+ * @author Pascal Luna <skalpa@zetareticuli.org>
+ */
+class ServiceLocatorTest extends TestCase
+{
+ public function testCanAccessServices()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, array('service'));
+
+ $this->assertSame($pimple['service'], $locator->get('service'));
+ }
+
+ public function testCanAccessAliasedServices()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, array('alias' => 'service'));
+
+ $this->assertSame($pimple['service'], $locator->get('alias'));
+ }
+
+ /**
+ * @expectedException \Pimple\Exception\UnknownIdentifierException
+ * @expectedExceptionMessage Identifier "service" is not defined.
+ */
+ public function testCannotAccessAliasedServiceUsingRealIdentifier()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, array('alias' => 'service'));
+
+ $service = $locator->get('service');
+ }
+
+ /**
+ * @expectedException \Pimple\Exception\UnknownIdentifierException
+ * @expectedExceptionMessage Identifier "foo" is not defined.
+ */
+ public function testGetValidatesServiceCanBeLocated()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, array('alias' => 'service'));
+
+ $service = $locator->get('foo');
+ }
+
+ /**
+ * @expectedException \Pimple\Exception\UnknownIdentifierException
+ * @expectedExceptionMessage Identifier "invalid" is not defined.
+ */
+ public function testGetValidatesTargetServiceExists()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, array('alias' => 'invalid'));
+
+ $service = $locator->get('alias');
+ }
+
+ public function testHasValidatesServiceCanBeLocated()
+ {
+ $pimple = new Container();
+ $pimple['service1'] = function () {
+ return new Fixtures\Service();
+ };
+ $pimple['service2'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, array('service1'));
+
+ $this->assertTrue($locator->has('service1'));
+ $this->assertFalse($locator->has('service2'));
+ }
+
+ public function testHasChecksIfTargetServiceExists()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, array('foo' => 'service', 'bar' => 'invalid'));
+
+ $this->assertTrue($locator->has('foo'));
+ $this->assertFalse($locator->has('bar'));
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php b/vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php
new file mode 100755
index 0000000..5dd52f0
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of Pimple.
+ *
+ * Copyright (c) 2009 Fabien Potencier
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+namespace Pimple\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Pimple\Container;
+use Pimple\ServiceIterator;
+use Pimple\Tests\Fixtures\Service;
+
+class ServiceIteratorTest extends TestCase
+{
+ public function testIsIterable()
+ {
+ $pimple = new Container();
+ $pimple['service1'] = function () {
+ return new Service();
+ };
+ $pimple['service2'] = function () {
+ return new Service();
+ };
+ $pimple['service3'] = function () {
+ return new Service();
+ };
+ $iterator = new ServiceIterator($pimple, array('service1', 'service2'));
+
+ $this->assertSame(array('service1' => $pimple['service1'], 'service2' => $pimple['service2']), iterator_to_array($iterator));
+ }
+}
diff --git a/vendor/psr/cache/CHANGELOG.md b/vendor/psr/cache/CHANGELOG.md
new file mode 100755
index 0000000..58ddab0
--- /dev/null
+++ b/vendor/psr/cache/CHANGELOG.md
@@ -0,0 +1,16 @@
+# Changelog
+
+All notable changes to this project will be documented in this file, in reverse chronological order by release.
+
+## 1.0.1 - 2016-08-06
+
+### Fixed
+
+- Make spacing consistent in phpdoc annotations php-fig/cache#9 - chalasr
+- Fix grammar in phpdoc annotations php-fig/cache#10 - chalasr
+- Be more specific in docblocks that `getItems()` and `deleteItems()` take an array of strings (`string[]`) compared to just `array` php-fig/cache#8 - GrahamCampbell
+- For `expiresAt()` and `expiresAfter()` in CacheItemInterface fix docblock to specify null as a valid parameters as well as an implementation of DateTimeInterface php-fig/cache#7 - GrahamCampbell
+
+## 1.0.0 - 2015-12-11
+
+Initial stable release; reflects accepted PSR-6 specification
diff --git a/vendor/psr/cache/LICENSE.txt b/vendor/psr/cache/LICENSE.txt
new file mode 100755
index 0000000..b1c2c97
--- /dev/null
+++ b/vendor/psr/cache/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2015 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/psr/cache/README.md b/vendor/psr/cache/README.md
new file mode 100755
index 0000000..c8706ce
--- /dev/null
+++ b/vendor/psr/cache/README.md
@@ -0,0 +1,9 @@
+PSR Cache
+=========
+
+This repository holds all interfaces defined by
+[PSR-6](http://www.php-fig.org/psr/psr-6/).
+
+Note that this is not a Cache implementation of its own. It is merely an
+interface that describes a Cache implementation. See the specification for more
+details.
diff --git a/vendor/psr/cache/composer.json b/vendor/psr/cache/composer.json
new file mode 100755
index 0000000..e828fec
--- /dev/null
+++ b/vendor/psr/cache/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "psr/cache",
+ "description": "Common interface for caching libraries",
+ "keywords": ["psr", "psr-6", "cache"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Cache\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
diff --git a/vendor/psr/cache/src/CacheException.php b/vendor/psr/cache/src/CacheException.php
new file mode 100755
index 0000000..e27f22f
--- /dev/null
+++ b/vendor/psr/cache/src/CacheException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Psr\Cache;
+
+/**
+ * Exception interface for all exceptions thrown by an Implementing Library.
+ */
+interface CacheException
+{
+}
diff --git a/vendor/psr/cache/src/CacheItemInterface.php b/vendor/psr/cache/src/CacheItemInterface.php
new file mode 100755
index 0000000..63d05dd
--- /dev/null
+++ b/vendor/psr/cache/src/CacheItemInterface.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Psr\Cache;
+
+/**
+ * CacheItemInterface defines an interface for interacting with objects inside a cache.
+ *
+ * Each Item object MUST be associated with a specific key, which can be set
+ * according to the implementing system and is typically passed by the
+ * Cache\CacheItemPoolInterface object.
+ *
+ * The Cache\CacheItemInterface object encapsulates the storage and retrieval of
+ * cache items. Each Cache\CacheItemInterface is generated by a
+ * Cache\CacheItemPoolInterface object, which is responsible for any required
+ * setup as well as associating the object with a unique Key.
+ * Cache\CacheItemInterface objects MUST be able to store and retrieve any type
+ * of PHP value defined in the Data section of the specification.
+ *
+ * Calling Libraries MUST NOT instantiate Item objects themselves. They may only
+ * be requested from a Pool object via the getItem() method. Calling Libraries
+ * SHOULD NOT assume that an Item created by one Implementing Library is
+ * compatible with a Pool from another Implementing Library.
+ */
+interface CacheItemInterface
+{
+ /**
+ * Returns the key for the current cache item.
+ *
+ * The key is loaded by the Implementing Library, but should be available to
+ * the higher level callers when needed.
+ *
+ * @return string
+ * The key string for this cache item.
+ */
+ public function getKey();
+
+ /**
+ * Retrieves the value of the item from the cache associated with this object's key.
+ *
+ * The value returned must be identical to the value originally stored by set().
+ *
+ * If isHit() returns false, this method MUST return null. Note that null
+ * is a legitimate cached value, so the isHit() method SHOULD be used to
+ * differentiate between "null value was found" and "no value was found."
+ *
+ * @return mixed
+ * The value corresponding to this cache item's key, or null if not found.
+ */
+ public function get();
+
+ /**
+ * Confirms if the cache item lookup resulted in a cache hit.
+ *
+ * Note: This method MUST NOT have a race condition between calling isHit()
+ * and calling get().
+ *
+ * @return bool
+ * True if the request resulted in a cache hit. False otherwise.
+ */
+ public function isHit();
+
+ /**
+ * Sets the value represented by this cache item.
+ *
+ * The $value argument may be any item that can be serialized by PHP,
+ * although the method of serialization is left up to the Implementing
+ * Library.
+ *
+ * @param mixed $value
+ * The serializable value to be stored.
+ *
+ * @return static
+ * The invoked object.
+ */
+ public function set($value);
+
+ /**
+ * Sets the expiration time for this cache item.
+ *
+ * @param \DateTimeInterface|null $expiration
+ * The point in time after which the item MUST be considered expired.
+ * If null is passed explicitly, a default value MAY be used. If none is set,
+ * the value should be stored permanently or for as long as the
+ * implementation allows.
+ *
+ * @return static
+ * The called object.
+ */
+ public function expiresAt($expiration);
+
+ /**
+ * Sets the expiration time for this cache item.
+ *
+ * @param int|\DateInterval|null $time
+ * The period of time from the present after which the item MUST be considered
+ * expired. An integer parameter is understood to be the time in seconds until
+ * expiration. If null is passed explicitly, a default value MAY be used.
+ * If none is set, the value should be stored permanently or for as long as the
+ * implementation allows.
+ *
+ * @return static
+ * The called object.
+ */
+ public function expiresAfter($time);
+}
diff --git a/vendor/psr/cache/src/CacheItemPoolInterface.php b/vendor/psr/cache/src/CacheItemPoolInterface.php
new file mode 100755
index 0000000..0351419
--- /dev/null
+++ b/vendor/psr/cache/src/CacheItemPoolInterface.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace Psr\Cache;
+
+/**
+ * CacheItemPoolInterface generates CacheItemInterface objects.
+ *
+ * The primary purpose of Cache\CacheItemPoolInterface is to accept a key from
+ * the Calling Library and return the associated Cache\CacheItemInterface object.
+ * It is also the primary point of interaction with the entire cache collection.
+ * All configuration and initialization of the Pool is left up to an
+ * Implementing Library.
+ */
+interface CacheItemPoolInterface
+{
+ /**
+ * Returns a Cache Item representing the specified key.
+ *
+ * This method must always return a CacheItemInterface object, even in case of
+ * a cache miss. It MUST NOT return null.
+ *
+ * @param string $key
+ * The key for which to return the corresponding Cache Item.
+ *
+ * @throws InvalidArgumentException
+ * If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
+ * MUST be thrown.
+ *
+ * @return CacheItemInterface
+ * The corresponding Cache Item.
+ */
+ public function getItem($key);
+
+ /**
+ * Returns a traversable set of cache items.
+ *
+ * @param string[] $keys
+ * An indexed array of keys of items to retrieve.
+ *
+ * @throws InvalidArgumentException
+ * If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException
+ * MUST be thrown.
+ *
+ * @return array|\Traversable
+ * A traversable collection of Cache Items keyed by the cache keys of
+ * each item. A Cache item will be returned for each key, even if that
+ * key is not found. However, if no keys are specified then an empty
+ * traversable MUST be returned instead.
+ */
+ public function getItems(array $keys = array());
+
+ /**
+ * Confirms if the cache contains specified cache item.
+ *
+ * Note: This method MAY avoid retrieving the cached value for performance reasons.
+ * This could result in a race condition with CacheItemInterface::get(). To avoid
+ * such situation use CacheItemInterface::isHit() instead.
+ *
+ * @param string $key
+ * The key for which to check existence.
+ *
+ * @throws InvalidArgumentException
+ * If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
+ * MUST be thrown.
+ *
+ * @return bool
+ * True if item exists in the cache, false otherwise.
+ */
+ public function hasItem($key);
+
+ /**
+ * Deletes all items in the pool.
+ *
+ * @return bool
+ * True if the pool was successfully cleared. False if there was an error.
+ */
+ public function clear();
+
+ /**
+ * Removes the item from the pool.
+ *
+ * @param string $key
+ * The key to delete.
+ *
+ * @throws InvalidArgumentException
+ * If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
+ * MUST be thrown.
+ *
+ * @return bool
+ * True if the item was successfully removed. False if there was an error.
+ */
+ public function deleteItem($key);
+
+ /**
+ * Removes multiple items from the pool.
+ *
+ * @param string[] $keys
+ * An array of keys that should be removed from the pool.
+
+ * @throws InvalidArgumentException
+ * If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException
+ * MUST be thrown.
+ *
+ * @return bool
+ * True if the items were successfully removed. False if there was an error.
+ */
+ public function deleteItems(array $keys);
+
+ /**
+ * Persists a cache item immediately.
+ *
+ * @param CacheItemInterface $item
+ * The cache item to save.
+ *
+ * @return bool
+ * True if the item was successfully persisted. False if there was an error.
+ */
+ public function save(CacheItemInterface $item);
+
+ /**
+ * Sets a cache item to be persisted later.
+ *
+ * @param CacheItemInterface $item
+ * The cache item to save.
+ *
+ * @return bool
+ * False if the item could not be queued or if a commit was attempted and failed. True otherwise.
+ */
+ public function saveDeferred(CacheItemInterface $item);
+
+ /**
+ * Persists any deferred cache items.
+ *
+ * @return bool
+ * True if all not-yet-saved items were successfully saved or there were none. False otherwise.
+ */
+ public function commit();
+}
diff --git a/vendor/psr/cache/src/InvalidArgumentException.php b/vendor/psr/cache/src/InvalidArgumentException.php
new file mode 100755
index 0000000..be7c6fa
--- /dev/null
+++ b/vendor/psr/cache/src/InvalidArgumentException.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Psr\Cache;
+
+/**
+ * Exception interface for invalid cache arguments.
+ *
+ * Any time an invalid argument is passed into a method it must throw an
+ * exception class which implements Psr\Cache\InvalidArgumentException.
+ */
+interface InvalidArgumentException extends CacheException
+{
+}
diff --git a/vendor/psr/container/.gitignore b/vendor/psr/container/.gitignore
new file mode 100755
index 0000000..b2395aa
--- /dev/null
+++ b/vendor/psr/container/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+composer.phar
+/vendor/
diff --git a/vendor/psr/container/LICENSE b/vendor/psr/container/LICENSE
new file mode 100755
index 0000000..2877a48
--- /dev/null
+++ b/vendor/psr/container/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013-2016 container-interop
+Copyright (c) 2016 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/psr/container/README.md b/vendor/psr/container/README.md
new file mode 100755
index 0000000..084f6df
--- /dev/null
+++ b/vendor/psr/container/README.md
@@ -0,0 +1,5 @@
+# PSR Container
+
+This repository holds all interfaces/classes/traits related to [PSR-11](https://github.com/container-interop/fig-standards/blob/master/proposed/container.md).
+
+Note that this is not a container implementation of its own. See the specification for more details.
diff --git a/vendor/psr/container/composer.json b/vendor/psr/container/composer.json
new file mode 100755
index 0000000..b8ee012
--- /dev/null
+++ b/vendor/psr/container/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "psr/container",
+ "type": "library",
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "keywords": ["psr", "psr-11", "container", "container-interop", "container-interface"],
+ "homepage": "https://github.com/php-fig/container",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
diff --git a/vendor/psr/container/src/ContainerExceptionInterface.php b/vendor/psr/container/src/ContainerExceptionInterface.php
new file mode 100755
index 0000000..d35c6b4
--- /dev/null
+++ b/vendor/psr/container/src/ContainerExceptionInterface.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
+ */
+
+namespace Psr\Container;
+
+/**
+ * Base interface representing a generic exception in a container.
+ */
+interface ContainerExceptionInterface
+{
+}
diff --git a/vendor/psr/container/src/ContainerInterface.php b/vendor/psr/container/src/ContainerInterface.php
new file mode 100755
index 0000000..c3a7206
--- /dev/null
+++ b/vendor/psr/container/src/ContainerInterface.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
+ */
+
+namespace Psr\Container;
+
+/**
+ * Describes the interface of a container that exposes methods to read its entries.
+ */
+interface ContainerInterface
+{
+ /**
+ * Finds an entry of the container by its identifier and returns it.
+ *
+ * @param string $id Identifier of the entry to look for.
+ *
+ * @throws NotFoundExceptionInterface No entry was found for **this** identifier.
+ * @throws ContainerExceptionInterface Error while retrieving the entry.
+ *
+ * @return mixed Entry.
+ */
+ public function get($id);
+
+ /**
+ * Returns true if the container can return an entry for the given identifier.
+ * Returns false otherwise.
+ *
+ * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
+ * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
+ *
+ * @param string $id Identifier of the entry to look for.
+ *
+ * @return bool
+ */
+ public function has($id);
+}
diff --git a/vendor/psr/container/src/NotFoundExceptionInterface.php b/vendor/psr/container/src/NotFoundExceptionInterface.php
new file mode 100755
index 0000000..6566704
--- /dev/null
+++ b/vendor/psr/container/src/NotFoundExceptionInterface.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
+ */
+
+namespace Psr\Container;
+
+/**
+ * No entry was found in the container.
+ */
+interface NotFoundExceptionInterface extends ContainerExceptionInterface
+{
+}
diff --git a/vendor/psr/http-client/CHANGELOG.md b/vendor/psr/http-client/CHANGELOG.md
new file mode 100755
index 0000000..e2dc25f
--- /dev/null
+++ b/vendor/psr/http-client/CHANGELOG.md
@@ -0,0 +1,23 @@
+# Changelog
+
+All notable changes to this project will be documented in this file, in reverse chronological order by release.
+
+## 1.0.1
+
+Allow installation with PHP 8. No code changes.
+
+## 1.0.0
+
+First stable release. No changes since 0.3.0.
+
+## 0.3.0
+
+Added Interface suffix on exceptions
+
+## 0.2.0
+
+All exceptions are in `Psr\Http\Client` namespace
+
+## 0.1.0
+
+First release
diff --git a/vendor/psr/http-client/LICENSE b/vendor/psr/http-client/LICENSE
new file mode 100755
index 0000000..cd5e002
--- /dev/null
+++ b/vendor/psr/http-client/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2017 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/psr/http-client/README.md b/vendor/psr/http-client/README.md
new file mode 100755
index 0000000..6876b84
--- /dev/null
+++ b/vendor/psr/http-client/README.md
@@ -0,0 +1,12 @@
+HTTP Client
+===========
+
+This repository holds all the common code related to [PSR-18 (HTTP Client)][psr-url].
+
+Note that this is not a HTTP Client implementation of its own. It is merely abstractions that describe the components of a HTTP Client.
+
+The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist.
+
+[psr-url]: http://www.php-fig.org/psr/psr-18
+[package-url]: https://packagist.org/packages/psr/http-client
+[implementation-url]: https://packagist.org/providers/psr/http-client-implementation
diff --git a/vendor/psr/http-client/composer.json b/vendor/psr/http-client/composer.json
new file mode 100755
index 0000000..c195f8f
--- /dev/null
+++ b/vendor/psr/http-client/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "psr/http-client",
+ "description": "Common interface for HTTP clients",
+ "keywords": ["psr", "psr-18", "http", "http-client"],
+ "homepage": "https://github.com/php-fig/http-client",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": "^7.0 || ^8.0",
+ "psr/http-message": "^1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Client\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
diff --git a/vendor/psr/http-client/src/ClientExceptionInterface.php b/vendor/psr/http-client/src/ClientExceptionInterface.php
new file mode 100755
index 0000000..aa0b9cf
--- /dev/null
+++ b/vendor/psr/http-client/src/ClientExceptionInterface.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Psr\Http\Client;
+
+/**
+ * Every HTTP client related exception MUST implement this interface.
+ */
+interface ClientExceptionInterface extends \Throwable
+{
+}
diff --git a/vendor/psr/http-client/src/ClientInterface.php b/vendor/psr/http-client/src/ClientInterface.php
new file mode 100755
index 0000000..ad99fd4
--- /dev/null
+++ b/vendor/psr/http-client/src/ClientInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Psr\Http\Client;
+
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+interface ClientInterface
+{
+ /**
+ * Sends a PSR-7 request and returns a PSR-7 response.
+ *
+ * @param RequestInterface $request
+ *
+ * @return ResponseInterface
+ *
+ * @throws \Psr\Http\Client\ClientExceptionInterface If an error happens while processing the request.
+ */
+ public function sendRequest(RequestInterface $request): ResponseInterface;
+}
diff --git a/vendor/psr/http-client/src/NetworkExceptionInterface.php b/vendor/psr/http-client/src/NetworkExceptionInterface.php
new file mode 100755
index 0000000..4a11627
--- /dev/null
+++ b/vendor/psr/http-client/src/NetworkExceptionInterface.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Psr\Http\Client;
+
+use Psr\Http\Message\RequestInterface;
+
+/**
+ * Thrown when the request cannot be completed because of network issues.
+ *
+ * There is no response object as this exception is thrown when no response has been received.
+ *
+ * Example: the target host name can not be resolved or the connection failed.
+ */
+interface NetworkExceptionInterface extends ClientExceptionInterface
+{
+ /**
+ * Returns the request.
+ *
+ * The request object MAY be a different object from the one passed to ClientInterface::sendRequest()
+ *
+ * @return RequestInterface
+ */
+ public function getRequest(): RequestInterface;
+}
diff --git a/vendor/psr/http-client/src/RequestExceptionInterface.php b/vendor/psr/http-client/src/RequestExceptionInterface.php
new file mode 100755
index 0000000..b17fc34
--- /dev/null
+++ b/vendor/psr/http-client/src/RequestExceptionInterface.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Psr\Http\Client;
+
+use Psr\Http\Message\RequestInterface;
+
+/**
+ * Exception for when a request failed.
+ *
+ * Examples:
+ * - Request is invalid (e.g. method is missing)
+ * - Runtime request errors (e.g. the body stream is not seekable)
+ */
+interface RequestExceptionInterface extends ClientExceptionInterface
+{
+ /**
+ * Returns the request.
+ *
+ * The request object MAY be a different object from the one passed to ClientInterface::sendRequest()
+ *
+ * @return RequestInterface
+ */
+ public function getRequest(): RequestInterface;
+}
diff --git a/vendor/psr/http-factory/.gitignore b/vendor/psr/http-factory/.gitignore
new file mode 100755
index 0000000..d8a7996
--- /dev/null
+++ b/vendor/psr/http-factory/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor/
diff --git a/vendor/psr/http-factory/.pullapprove.yml b/vendor/psr/http-factory/.pullapprove.yml
new file mode 100755
index 0000000..8cf0819
--- /dev/null
+++ b/vendor/psr/http-factory/.pullapprove.yml
@@ -0,0 +1,7 @@
+extends: default
+reviewers:
+ -
+ name: contributors
+ required: 1
+ teams:
+ - http-factory-contributors
diff --git a/vendor/psr/http-factory/LICENSE b/vendor/psr/http-factory/LICENSE
new file mode 100755
index 0000000..3f1559b
--- /dev/null
+++ b/vendor/psr/http-factory/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 PHP-FIG
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/psr/http-factory/README.md b/vendor/psr/http-factory/README.md
new file mode 100755
index 0000000..41d362a
--- /dev/null
+++ b/vendor/psr/http-factory/README.md
@@ -0,0 +1,10 @@
+HTTP Factories
+==============
+
+This repository holds all interfaces related to [PSR-17 (HTTP Message Factories)][psr-17].
+Please refer to the specification for a description.
+
+You can find implementations of the specification by looking for packages providing the
+[psr/http-factory-implementation](https://packagist.org/providers/psr/http-factory-implementation) virtual package.
+
+[psr-17]: https://www.php-fig.org/psr/psr-17/
diff --git a/vendor/psr/http-factory/composer.json b/vendor/psr/http-factory/composer.json
new file mode 100755
index 0000000..af62b29
--- /dev/null
+++ b/vendor/psr/http-factory/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "psr/http-factory",
+ "description": "Common interfaces for PSR-7 HTTP message factories",
+ "keywords": [
+ "psr",
+ "psr-7",
+ "psr-17",
+ "http",
+ "factory",
+ "message",
+ "request",
+ "response"
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=7.0.0",
+ "psr/http-message": "^1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
diff --git a/vendor/psr/http-factory/src/RequestFactoryInterface.php b/vendor/psr/http-factory/src/RequestFactoryInterface.php
new file mode 100755
index 0000000..cb39a08
--- /dev/null
+++ b/vendor/psr/http-factory/src/RequestFactoryInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Psr\Http\Message;
+
+interface RequestFactoryInterface
+{
+ /**
+ * Create a new request.
+ *
+ * @param string $method The HTTP method associated with the request.
+ * @param UriInterface|string $uri The URI associated with the request. If
+ * the value is a string, the factory MUST create a UriInterface
+ * instance based on it.
+ *
+ * @return RequestInterface
+ */
+ public function createRequest(string $method, $uri): RequestInterface;
+}
diff --git a/vendor/psr/http-factory/src/ResponseFactoryInterface.php b/vendor/psr/http-factory/src/ResponseFactoryInterface.php
new file mode 100755
index 0000000..212562f
--- /dev/null
+++ b/vendor/psr/http-factory/src/ResponseFactoryInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Psr\Http\Message;
+
+interface ResponseFactoryInterface
+{
+ /**
+ * Create a new response.
+ *
+ * @param int $code HTTP status code; defaults to 200
+ * @param string $reasonPhrase Reason phrase to associate with status code
+ * in generated response; if none is provided implementations MAY use
+ * the defaults as suggested in the HTTP specification.
+ *
+ * @return ResponseInterface
+ */
+ public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface;
+}
diff --git a/vendor/psr/http-factory/src/ServerRequestFactoryInterface.php b/vendor/psr/http-factory/src/ServerRequestFactoryInterface.php
new file mode 100755
index 0000000..9fe031a
--- /dev/null
+++ b/vendor/psr/http-factory/src/ServerRequestFactoryInterface.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Psr\Http\Message;
+
+interface ServerRequestFactoryInterface
+{
+ /**
+ * Create a new server request.
+ *
+ * Note that server-params are taken precisely as given - no parsing/processing
+ * of the given values is performed, and, in particular, no attempt is made to
+ * determine the HTTP method or URI, which must be provided explicitly.
+ *
+ * @param string $method The HTTP method associated with the request.
+ * @param UriInterface|string $uri The URI associated with the request. If
+ * the value is a string, the factory MUST create a UriInterface
+ * instance based on it.
+ * @param array $serverParams Array of SAPI parameters with which to seed
+ * the generated request instance.
+ *
+ * @return ServerRequestInterface
+ */
+ public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface;
+}
diff --git a/vendor/psr/http-factory/src/StreamFactoryInterface.php b/vendor/psr/http-factory/src/StreamFactoryInterface.php
new file mode 100755
index 0000000..90e3240
--- /dev/null
+++ b/vendor/psr/http-factory/src/StreamFactoryInterface.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Psr\Http\Message;
+
+interface StreamFactoryInterface
+{
+ /**
+ * Create a new stream from a string.
+ *
+ * The stream SHOULD be created with a temporary resource.
+ *
+ * @param string $content String content with which to populate the stream.
+ *
+ * @return StreamInterface
+ */
+ public function createStream(string $content = ''): StreamInterface;
+
+ /**
+ * Create a stream from an existing file.
+ *
+ * The file MUST be opened using the given mode, which may be any mode
+ * supported by the `fopen` function.
+ *
+ * The `$filename` MAY be any string supported by `fopen()`.
+ *
+ * @param string $filename Filename or stream URI to use as basis of stream.
+ * @param string $mode Mode with which to open the underlying filename/stream.
+ *
+ * @return StreamInterface
+ * @throws \RuntimeException If the file cannot be opened.
+ * @throws \InvalidArgumentException If the mode is invalid.
+ */
+ public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface;
+
+ /**
+ * Create a new stream from an existing resource.
+ *
+ * The stream MUST be readable and may be writable.
+ *
+ * @param resource $resource PHP resource to use as basis of stream.
+ *
+ * @return StreamInterface
+ */
+ public function createStreamFromResource($resource): StreamInterface;
+}
diff --git a/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php b/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php
new file mode 100755
index 0000000..7db4e30
--- /dev/null
+++ b/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Psr\Http\Message;
+
+interface UploadedFileFactoryInterface
+{
+ /**
+ * Create a new uploaded file.
+ *
+ * If a size is not provided it will be determined by checking the size of
+ * the file.
+ *
+ * @see http://php.net/manual/features.file-upload.post-method.php
+ * @see http://php.net/manual/features.file-upload.errors.php
+ *
+ * @param StreamInterface $stream Underlying stream representing the
+ * uploaded file content.
+ * @param int $size in bytes
+ * @param int $error PHP file upload error
+ * @param string $clientFilename Filename as provided by the client, if any.
+ * @param string $clientMediaType Media type as provided by the client, if any.
+ *
+ * @return UploadedFileInterface
+ *
+ * @throws \InvalidArgumentException If the file resource is not readable.
+ */
+ public function createUploadedFile(
+ StreamInterface $stream,
+ int $size = null,
+ int $error = \UPLOAD_ERR_OK,
+ string $clientFilename = null,
+ string $clientMediaType = null
+ ): UploadedFileInterface;
+}
diff --git a/vendor/psr/http-factory/src/UriFactoryInterface.php b/vendor/psr/http-factory/src/UriFactoryInterface.php
new file mode 100755
index 0000000..06df0b4
--- /dev/null
+++ b/vendor/psr/http-factory/src/UriFactoryInterface.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Psr\Http\Message;
+
+interface UriFactoryInterface
+{
+ /**
+ * Create a new URI.
+ *
+ * @param string $uri
+ *
+ * @return UriInterface
+ *
+ * @throws \InvalidArgumentException If the given URI cannot be parsed.
+ */
+ public function createUri(string $uri = ''): UriInterface;
+}
diff --git a/vendor/psr/http-message/CHANGELOG.md b/vendor/psr/http-message/CHANGELOG.md
new file mode 100755
index 0000000..74b1ef9
--- /dev/null
+++ b/vendor/psr/http-message/CHANGELOG.md
@@ -0,0 +1,36 @@
+# Changelog
+
+All notable changes to this project will be documented in this file, in reverse chronological order by release.
+
+## 1.0.1 - 2016-08-06
+
+### Added
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Updated all `@return self` annotation references in interfaces to use
+ `@return static`, which more closelly follows the semantics of the
+ specification.
+- Updated the `MessageInterface::getHeaders()` return annotation to use the
+ value `string[][]`, indicating the format is a nested array of strings.
+- Updated the `@link` annotation for `RequestInterface::withRequestTarget()`
+ to point to the correct section of RFC 7230.
+- Updated the `ServerRequestInterface::withUploadedFiles()` parameter annotation
+ to add the parameter name (`$uploadedFiles`).
+- Updated a `@throws` annotation for the `UploadedFileInterface::moveTo()`
+ method to correctly reference the method parameter (it was referencing an
+ incorrect parameter name previously).
+
+## 1.0.0 - 2016-05-18
+
+Initial stable release; reflects accepted PSR-7 specification.
diff --git a/vendor/psr/http-message/LICENSE b/vendor/psr/http-message/LICENSE
new file mode 100755
index 0000000..c2d8e45
--- /dev/null
+++ b/vendor/psr/http-message/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/psr/http-message/README.md b/vendor/psr/http-message/README.md
new file mode 100755
index 0000000..2818533
--- /dev/null
+++ b/vendor/psr/http-message/README.md
@@ -0,0 +1,13 @@
+PSR Http Message
+================
+
+This repository holds all interfaces/classes/traits related to
+[PSR-7](http://www.php-fig.org/psr/psr-7/).
+
+Note that this is not a HTTP message implementation of its own. It is merely an
+interface that describes a HTTP message. See the specification for more details.
+
+Usage
+-----
+
+We'll certainly need some stuff in here.
\ No newline at end of file
diff --git a/vendor/psr/http-message/composer.json b/vendor/psr/http-message/composer.json
new file mode 100755
index 0000000..b0d2937
--- /dev/null
+++ b/vendor/psr/http-message/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "psr/http-message",
+ "description": "Common interface for HTTP messages",
+ "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
+ "homepage": "https://github.com/php-fig/http-message",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
diff --git a/vendor/psr/http-message/src/MessageInterface.php b/vendor/psr/http-message/src/MessageInterface.php
new file mode 100755
index 0000000..dd46e5e
--- /dev/null
+++ b/vendor/psr/http-message/src/MessageInterface.php
@@ -0,0 +1,187 @@
+<?php
+
+namespace Psr\Http\Message;
+
+/**
+ * HTTP messages consist of requests from a client to a server and responses
+ * from a server to a client. This interface defines the methods common to
+ * each.
+ *
+ * Messages are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ *
+ * @link http://www.ietf.org/rfc/rfc7230.txt
+ * @link http://www.ietf.org/rfc/rfc7231.txt
+ */
+interface MessageInterface
+{
+ /**
+ * Retrieves the HTTP protocol version as a string.
+ *
+ * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
+ *
+ * @return string HTTP protocol version.
+ */
+ public function getProtocolVersion();
+
+ /**
+ * Return an instance with the specified HTTP protocol version.
+ *
+ * The version string MUST contain only the HTTP version number (e.g.,
+ * "1.1", "1.0").
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new protocol version.
+ *
+ * @param string $version HTTP protocol version
+ * @return static
+ */
+ public function withProtocolVersion($version);
+
+ /**
+ * Retrieves all message header values.
+ *
+ * The keys represent the header name as it will be sent over the wire, and
+ * each value is an array of strings associated with the header.
+ *
+ * // Represent the headers as a string
+ * foreach ($message->getHeaders() as $name => $values) {
+ * echo $name . ": " . implode(", ", $values);
+ * }
+ *
+ * // Emit headers iteratively:
+ * foreach ($message->getHeaders() as $name => $values) {
+ * foreach ($values as $value) {
+ * header(sprintf('%s: %s', $name, $value), false);
+ * }
+ * }
+ *
+ * While header names are not case-sensitive, getHeaders() will preserve the
+ * exact case in which headers were originally specified.
+ *
+ * @return string[][] Returns an associative array of the message's headers. Each
+ * key MUST be a header name, and each value MUST be an array of strings
+ * for that header.
+ */
+ public function getHeaders();
+
+ /**
+ * Checks if a header exists by the given case-insensitive name.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return bool Returns true if any header names match the given header
+ * name using a case-insensitive string comparison. Returns false if
+ * no matching header name is found in the message.
+ */
+ public function hasHeader($name);
+
+ /**
+ * Retrieves a message header value by the given case-insensitive name.
+ *
+ * This method returns an array of all the header values of the given
+ * case-insensitive header name.
+ *
+ * If the header does not appear in the message, this method MUST return an
+ * empty array.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string[] An array of string values as provided for the given
+ * header. If the header does not appear in the message, this method MUST
+ * return an empty array.
+ */
+ public function getHeader($name);
+
+ /**
+ * Retrieves a comma-separated string of the values for a single header.
+ *
+ * This method returns all of the header values of the given
+ * case-insensitive header name as a string concatenated together using
+ * a comma.
+ *
+ * NOTE: Not all header values may be appropriately represented using
+ * comma concatenation. For such headers, use getHeader() instead
+ * and supply your own delimiter when concatenating.
+ *
+ * If the header does not appear in the message, this method MUST return
+ * an empty string.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string A string of values as provided for the given header
+ * concatenated together using a comma. If the header does not appear in
+ * the message, this method MUST return an empty string.
+ */
+ public function getHeaderLine($name);
+
+ /**
+ * Return an instance with the provided value replacing the specified header.
+ *
+ * While header names are case-insensitive, the casing of the header will
+ * be preserved by this function, and returned from getHeaders().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new and/or updated header and value.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withHeader($name, $value);
+
+ /**
+ * Return an instance with the specified header appended with the given value.
+ *
+ * Existing values for the specified header will be maintained. The new
+ * value(s) will be appended to the existing list. If the header did not
+ * exist previously, it will be added.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new header and/or value.
+ *
+ * @param string $name Case-insensitive header field name to add.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withAddedHeader($name, $value);
+
+ /**
+ * Return an instance without the specified header.
+ *
+ * Header resolution MUST be done without case-sensitivity.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the named header.
+ *
+ * @param string $name Case-insensitive header field name to remove.
+ * @return static
+ */
+ public function withoutHeader($name);
+
+ /**
+ * Gets the body of the message.
+ *
+ * @return StreamInterface Returns the body as a stream.
+ */
+ public function getBody();
+
+ /**
+ * Return an instance with the specified message body.
+ *
+ * The body MUST be a StreamInterface object.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return a new instance that has the
+ * new body stream.
+ *
+ * @param StreamInterface $body Body.
+ * @return static
+ * @throws \InvalidArgumentException When the body is not valid.
+ */
+ public function withBody(StreamInterface $body);
+}
diff --git a/vendor/psr/http-message/src/RequestInterface.php b/vendor/psr/http-message/src/RequestInterface.php
new file mode 100755
index 0000000..a96d4fd
--- /dev/null
+++ b/vendor/psr/http-message/src/RequestInterface.php
@@ -0,0 +1,129 @@
+<?php
+
+namespace Psr\Http\Message;
+
+/**
+ * Representation of an outgoing, client-side request.
+ *
+ * Per the HTTP specification, this interface includes properties for
+ * each of the following:
+ *
+ * - Protocol version
+ * - HTTP method
+ * - URI
+ * - Headers
+ * - Message body
+ *
+ * During construction, implementations MUST attempt to set the Host header from
+ * a provided URI if no Host header is provided.
+ *
+ * Requests are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ */
+interface RequestInterface extends MessageInterface
+{
+ /**
+ * Retrieves the message's request target.
+ *
+ * Retrieves the message's request-target either as it will appear (for
+ * clients), as it appeared at request (for servers), or as it was
+ * specified for the instance (see withRequestTarget()).
+ *
+ * In most cases, this will be the origin-form of the composed URI,
+ * unless a value was provided to the concrete implementation (see
+ * withRequestTarget() below).
+ *
+ * If no URI is available, and no request-target has been specifically
+ * provided, this method MUST return the string "/".
+ *
+ * @return string
+ */
+ public function getRequestTarget();
+
+ /**
+ * Return an instance with the specific request-target.
+ *
+ * If the request needs a non-origin-form request-target — e.g., for
+ * specifying an absolute-form, authority-form, or asterisk-form —
+ * this method may be used to create an instance with the specified
+ * request-target, verbatim.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * changed request target.
+ *
+ * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
+ * request-target forms allowed in request messages)
+ * @param mixed $requestTarget
+ * @return static
+ */
+ public function withRequestTarget($requestTarget);
+
+ /**
+ * Retrieves the HTTP method of the request.
+ *
+ * @return string Returns the request method.
+ */
+ public function getMethod();
+
+ /**
+ * Return an instance with the provided HTTP method.
+ *
+ * While HTTP method names are typically all uppercase characters, HTTP
+ * method names are case-sensitive and thus implementations SHOULD NOT
+ * modify the given string.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * changed request method.
+ *
+ * @param string $method Case-sensitive method.
+ * @return static
+ * @throws \InvalidArgumentException for invalid HTTP methods.
+ */
+ public function withMethod($method);
+
+ /**
+ * Retrieves the URI instance.
+ *
+ * This method MUST return a UriInterface instance.
+ *
+ * @link http://tools.ietf.org/html/rfc3986#section-4.3
+ * @return UriInterface Returns a UriInterface instance
+ * representing the URI of the request.
+ */
+ public function getUri();
+
+ /**
+ * Returns an instance with the provided URI.
+ *
+ * This method MUST update the Host header of the returned request by
+ * default if the URI contains a host component. If the URI does not
+ * contain a host component, any pre-existing Host header MUST be carried
+ * over to the returned request.
+ *
+ * You can opt-in to preserving the original state of the Host header by
+ * setting `$preserveHost` to `true`. When `$preserveHost` is set to
+ * `true`, this method interacts with the Host header in the following ways:
+ *
+ * - If the Host header is missing or empty, and the new URI contains
+ * a host component, this method MUST update the Host header in the returned
+ * request.
+ * - If the Host header is missing or empty, and the new URI does not contain a
+ * host component, this method MUST NOT update the Host header in the returned
+ * request.
+ * - If a Host header is present and non-empty, this method MUST NOT update
+ * the Host header in the returned request.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new UriInterface instance.
+ *
+ * @link http://tools.ietf.org/html/rfc3986#section-4.3
+ * @param UriInterface $uri New request URI to use.
+ * @param bool $preserveHost Preserve the original state of the Host header.
+ * @return static
+ */
+ public function withUri(UriInterface $uri, $preserveHost = false);
+}
diff --git a/vendor/psr/http-message/src/ResponseInterface.php b/vendor/psr/http-message/src/ResponseInterface.php
new file mode 100755
index 0000000..c306514
--- /dev/null
+++ b/vendor/psr/http-message/src/ResponseInterface.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Psr\Http\Message;
+
+/**
+ * Representation of an outgoing, server-side response.
+ *
+ * Per the HTTP specification, this interface includes properties for
+ * each of the following:
+ *
+ * - Protocol version
+ * - Status code and reason phrase
+ * - Headers
+ * - Message body
+ *
+ * Responses are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ */
+interface ResponseInterface extends MessageInterface
+{
+ /**
+ * Gets the response status code.
+ *
+ * The status code is a 3-digit integer result code of the server's attempt
+ * to understand and satisfy the request.
+ *
+ * @return int Status code.
+ */
+ public function getStatusCode();
+
+ /**
+ * Return an instance with the specified status code and, optionally, reason phrase.
+ *
+ * If no reason phrase is specified, implementations MAY choose to default
+ * to the RFC 7231 or IANA recommended reason phrase for the response's
+ * status code.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated status and reason phrase.
+ *
+ * @link http://tools.ietf.org/html/rfc7231#section-6
+ * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+ * @param int $code The 3-digit integer result code to set.
+ * @param string $reasonPhrase The reason phrase to use with the
+ * provided status code; if none is provided, implementations MAY
+ * use the defaults as suggested in the HTTP specification.
+ * @return static
+ * @throws \InvalidArgumentException For invalid status code arguments.
+ */
+ public function withStatus($code, $reasonPhrase = '');
+
+ /**
+ * Gets the response reason phrase associated with the status code.
+ *
+ * Because a reason phrase is not a required element in a response
+ * status line, the reason phrase value MAY be null. Implementations MAY
+ * choose to return the default RFC 7231 recommended reason phrase (or those
+ * listed in the IANA HTTP Status Code Registry) for the response's
+ * status code.
+ *
+ * @link http://tools.ietf.org/html/rfc7231#section-6
+ * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+ * @return string Reason phrase; must return an empty string if none present.
+ */
+ public function getReasonPhrase();
+}
diff --git a/vendor/psr/http-message/src/ServerRequestInterface.php b/vendor/psr/http-message/src/ServerRequestInterface.php
new file mode 100755
index 0000000..0251234
--- /dev/null
+++ b/vendor/psr/http-message/src/ServerRequestInterface.php
@@ -0,0 +1,261 @@
+<?php
+
+namespace Psr\Http\Message;
+
+/**
+ * Representation of an incoming, server-side HTTP request.
+ *
+ * Per the HTTP specification, this interface includes properties for
+ * each of the following:
+ *
+ * - Protocol version
+ * - HTTP method
+ * - URI
+ * - Headers
+ * - Message body
+ *
+ * Additionally, it encapsulates all data as it has arrived to the
+ * application from the CGI and/or PHP environment, including:
+ *
+ * - The values represented in $_SERVER.
+ * - Any cookies provided (generally via $_COOKIE)
+ * - Query string arguments (generally via $_GET, or as parsed via parse_str())
+ * - Upload files, if any (as represented by $_FILES)
+ * - Deserialized body parameters (generally from $_POST)
+ *
+ * $_SERVER values MUST be treated as immutable, as they represent application
+ * state at the time of request; as such, no methods are provided to allow
+ * modification of those values. The other values provide such methods, as they
+ * can be restored from $_SERVER or the request body, and may need treatment
+ * during the application (e.g., body parameters may be deserialized based on
+ * content type).
+ *
+ * Additionally, this interface recognizes the utility of introspecting a
+ * request to derive and match additional parameters (e.g., via URI path
+ * matching, decrypting cookie values, deserializing non-form-encoded body
+ * content, matching authorization headers to users, etc). These parameters
+ * are stored in an "attributes" property.
+ *
+ * Requests are considered immutable; all methods that might change state MUST
+ * be implemented such that they retain the internal state of the current
+ * message and return an instance that contains the changed state.
+ */
+interface ServerRequestInterface extends RequestInterface
+{
+ /**
+ * Retrieve server parameters.
+ *
+ * Retrieves data related to the incoming request environment,
+ * typically derived from PHP's $_SERVER superglobal. The data IS NOT
+ * REQUIRED to originate from $_SERVER.
+ *
+ * @return array
+ */
+ public function getServerParams();
+
+ /**
+ * Retrieve cookies.
+ *
+ * Retrieves cookies sent by the client to the server.
+ *
+ * The data MUST be compatible with the structure of the $_COOKIE
+ * superglobal.
+ *
+ * @return array
+ */
+ public function getCookieParams();
+
+ /**
+ * Return an instance with the specified cookies.
+ *
+ * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
+ * be compatible with the structure of $_COOKIE. Typically, this data will
+ * be injected at instantiation.
+ *
+ * This method MUST NOT update the related Cookie header of the request
+ * instance, nor related values in the server params.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated cookie values.
+ *
+ * @param array $cookies Array of key/value pairs representing cookies.
+ * @return static
+ */
+ public function withCookieParams(array $cookies);
+
+ /**
+ * Retrieve query string arguments.
+ *
+ * Retrieves the deserialized query string arguments, if any.
+ *
+ * Note: the query params might not be in sync with the URI or server
+ * params. If you need to ensure you are only getting the original
+ * values, you may need to parse the query string from `getUri()->getQuery()`
+ * or from the `QUERY_STRING` server param.
+ *
+ * @return array
+ */
+ public function getQueryParams();
+
+ /**
+ * Return an instance with the specified query string arguments.
+ *
+ * These values SHOULD remain immutable over the course of the incoming
+ * request. They MAY be injected during instantiation, such as from PHP's
+ * $_GET superglobal, or MAY be derived from some other value such as the
+ * URI. In cases where the arguments are parsed from the URI, the data
+ * MUST be compatible with what PHP's parse_str() would return for
+ * purposes of how duplicate query parameters are handled, and how nested
+ * sets are handled.
+ *
+ * Setting query string arguments MUST NOT change the URI stored by the
+ * request, nor the values in the server params.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated query string arguments.
+ *
+ * @param array $query Array of query string arguments, typically from
+ * $_GET.
+ * @return static
+ */
+ public function withQueryParams(array $query);
+
+ /**
+ * Retrieve normalized file upload data.
+ *
+ * This method returns upload metadata in a normalized tree, with each leaf
+ * an instance of Psr\Http\Message\UploadedFileInterface.
+ *
+ * These values MAY be prepared from $_FILES or the message body during
+ * instantiation, or MAY be injected via withUploadedFiles().
+ *
+ * @return array An array tree of UploadedFileInterface instances; an empty
+ * array MUST be returned if no data is present.
+ */
+ public function getUploadedFiles();
+
+ /**
+ * Create a new instance with the specified uploaded files.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
+ * @return static
+ * @throws \InvalidArgumentException if an invalid structure is provided.
+ */
+ public function withUploadedFiles(array $uploadedFiles);
+
+ /**
+ * Retrieve any parameters provided in the request body.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, this method MUST
+ * return the contents of $_POST.
+ *
+ * Otherwise, this method may return any results of deserializing
+ * the request body content; as parsing returns structured content, the
+ * potential types MUST be arrays or objects only. A null value indicates
+ * the absence of body content.
+ *
+ * @return null|array|object The deserialized body parameters, if any.
+ * These will typically be an array or object.
+ */
+ public function getParsedBody();
+
+ /**
+ * Return an instance with the specified body parameters.
+ *
+ * These MAY be injected during instantiation.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, use this method
+ * ONLY to inject the contents of $_POST.
+ *
+ * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
+ * deserializing the request body content. Deserialization/parsing returns
+ * structured data, and, as such, this method ONLY accepts arrays or objects,
+ * or a null value if nothing was available to parse.
+ *
+ * As an example, if content negotiation determines that the request data
+ * is a JSON payload, this method could be used to create a request
+ * instance with the deserialized parameters.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param null|array|object $data The deserialized body data. This will
+ * typically be in an array or object.
+ * @return static
+ * @throws \InvalidArgumentException if an unsupported argument type is
+ * provided.
+ */
+ public function withParsedBody($data);
+
+ /**
+ * Retrieve attributes derived from the request.
+ *
+ * The request "attributes" may be used to allow injection of any
+ * parameters derived from the request: e.g., the results of path
+ * match operations; the results of decrypting cookies; the results of
+ * deserializing non-form-encoded message bodies; etc. Attributes
+ * will be application and request specific, and CAN be mutable.
+ *
+ * @return array Attributes derived from the request.
+ */
+ public function getAttributes();
+
+ /**
+ * Retrieve a single derived request attribute.
+ *
+ * Retrieves a single derived request attribute as described in
+ * getAttributes(). If the attribute has not been previously set, returns
+ * the default value as provided.
+ *
+ * This method obviates the need for a hasAttribute() method, as it allows
+ * specifying a default value to return if the attribute is not found.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $default Default value to return if the attribute does not exist.
+ * @return mixed
+ */
+ public function getAttribute($name, $default = null);
+
+ /**
+ * Return an instance with the specified derived request attribute.
+ *
+ * This method allows setting a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $value The value of the attribute.
+ * @return static
+ */
+ public function withAttribute($name, $value);
+
+ /**
+ * Return an instance that removes the specified derived request attribute.
+ *
+ * This method allows removing a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @return static
+ */
+ public function withoutAttribute($name);
+}
diff --git a/vendor/psr/http-message/src/StreamInterface.php b/vendor/psr/http-message/src/StreamInterface.php
new file mode 100755
index 0000000..f68f391
--- /dev/null
+++ b/vendor/psr/http-message/src/StreamInterface.php
@@ -0,0 +1,158 @@
+<?php
+
+namespace Psr\Http\Message;
+
+/**
+ * Describes a data stream.
+ *
+ * Typically, an instance will wrap a PHP stream; this interface provides
+ * a wrapper around the most common operations, including serialization of
+ * the entire stream to a string.
+ */
+interface StreamInterface
+{
+ /**
+ * Reads all data from the stream into a string, from the beginning to end.
+ *
+ * This method MUST attempt to seek to the beginning of the stream before
+ * reading data and read the stream until the end is reached.
+ *
+ * Warning: This could attempt to load a large amount of data into memory.
+ *
+ * This method MUST NOT raise an exception in order to conform with PHP's
+ * string casting operations.
+ *
+ * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
+ * @return string
+ */
+ public function __toString();
+
+ /**
+ * Closes the stream and any underlying resources.
+ *
+ * @return void
+ */
+ public function close();
+
+ /**
+ * Separates any underlying resources from the stream.
+ *
+ * After the stream has been detached, the stream is in an unusable state.
+ *
+ * @return resource|null Underlying PHP stream, if any
+ */
+ public function detach();
+
+ /**
+ * Get the size of the stream if known.
+ *
+ * @return int|null Returns the size in bytes if known, or null if unknown.
+ */
+ public function getSize();
+
+ /**
+ * Returns the current position of the file read/write pointer
+ *
+ * @return int Position of the file pointer
+ * @throws \RuntimeException on error.
+ */
+ public function tell();
+
+ /**
+ * Returns true if the stream is at the end of the stream.
+ *
+ * @return bool
+ */
+ public function eof();
+
+ /**
+ * Returns whether or not the stream is seekable.
+ *
+ * @return bool
+ */
+ public function isSeekable();
+
+ /**
+ * Seek to a position in the stream.
+ *
+ * @link http://www.php.net/manual/en/function.fseek.php
+ * @param int $offset Stream offset
+ * @param int $whence Specifies how the cursor position will be calculated
+ * based on the seek offset. Valid values are identical to the built-in
+ * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
+ * offset bytes SEEK_CUR: Set position to current location plus offset
+ * SEEK_END: Set position to end-of-stream plus offset.
+ * @throws \RuntimeException on failure.
+ */
+ public function seek($offset, $whence = SEEK_SET);
+
+ /**
+ * Seek to the beginning of the stream.
+ *
+ * If the stream is not seekable, this method will raise an exception;
+ * otherwise, it will perform a seek(0).
+ *
+ * @see seek()
+ * @link http://www.php.net/manual/en/function.fseek.php
+ * @throws \RuntimeException on failure.
+ */
+ public function rewind();
+
+ /**
+ * Returns whether or not the stream is writable.
+ *
+ * @return bool
+ */
+ public function isWritable();
+
+ /**
+ * Write data to the stream.
+ *
+ * @param string $string The string that is to be written.
+ * @return int Returns the number of bytes written to the stream.
+ * @throws \RuntimeException on failure.
+ */
+ public function write($string);
+
+ /**
+ * Returns whether or not the stream is readable.
+ *
+ * @return bool
+ */
+ public function isReadable();
+
+ /**
+ * Read data from the stream.
+ *
+ * @param int $length Read up to $length bytes from the object and return
+ * them. Fewer than $length bytes may be returned if underlying stream
+ * call returns fewer bytes.
+ * @return string Returns the data read from the stream, or an empty string
+ * if no bytes are available.
+ * @throws \RuntimeException if an error occurs.
+ */
+ public function read($length);
+
+ /**
+ * Returns the remaining contents in a string
+ *
+ * @return string
+ * @throws \RuntimeException if unable to read or an error occurs while
+ * reading.
+ */
+ public function getContents();
+
+ /**
+ * Get stream metadata as an associative array or retrieve a specific key.
+ *
+ * The keys returned are identical to the keys returned from PHP's
+ * stream_get_meta_data() function.
+ *
+ * @link http://php.net/manual/en/function.stream-get-meta-data.php
+ * @param string $key Specific metadata to retrieve.
+ * @return array|mixed|null Returns an associative array if no key is
+ * provided. Returns a specific key value if a key is provided and the
+ * value is found, or null if the key is not found.
+ */
+ public function getMetadata($key = null);
+}
diff --git a/vendor/psr/http-message/src/UploadedFileInterface.php b/vendor/psr/http-message/src/UploadedFileInterface.php
new file mode 100755
index 0000000..f8a6901
--- /dev/null
+++ b/vendor/psr/http-message/src/UploadedFileInterface.php
@@ -0,0 +1,123 @@
+<?php
+
+namespace Psr\Http\Message;
+
+/**
+ * Value object representing a file uploaded through an HTTP request.
+ *
+ * Instances of this interface are considered immutable; all methods that
+ * might change state MUST be implemented such that they retain the internal
+ * state of the current instance and return an instance that contains the
+ * changed state.
+ */
+interface UploadedFileInterface
+{
+ /**
+ * Retrieve a stream representing the uploaded file.
+ *
+ * This method MUST return a StreamInterface instance, representing the
+ * uploaded file. The purpose of this method is to allow utilizing native PHP
+ * stream functionality to manipulate the file upload, such as
+ * stream_copy_to_stream() (though the result will need to be decorated in a
+ * native PHP stream wrapper to work with such functions).
+ *
+ * If the moveTo() method has been called previously, this method MUST raise
+ * an exception.
+ *
+ * @return StreamInterface Stream representation of the uploaded file.
+ * @throws \RuntimeException in cases when no stream is available or can be
+ * created.
+ */
+ public function getStream();
+
+ /**
+ * Move the uploaded file to a new location.
+ *
+ * Use this method as an alternative to move_uploaded_file(). This method is
+ * guaranteed to work in both SAPI and non-SAPI environments.
+ * Implementations must determine which environment they are in, and use the
+ * appropriate method (move_uploaded_file(), rename(), or a stream
+ * operation) to perform the operation.
+ *
+ * $targetPath may be an absolute path, or a relative path. If it is a
+ * relative path, resolution should be the same as used by PHP's rename()
+ * function.
+ *
+ * The original file or stream MUST be removed on completion.
+ *
+ * If this method is called more than once, any subsequent calls MUST raise
+ * an exception.
+ *
+ * When used in an SAPI environment where $_FILES is populated, when writing
+ * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be
+ * used to ensure permissions and upload status are verified correctly.
+ *
+ * If you wish to move to a stream, use getStream(), as SAPI operations
+ * cannot guarantee writing to stream destinations.
+ *
+ * @see http://php.net/is_uploaded_file
+ * @see http://php.net/move_uploaded_file
+ * @param string $targetPath Path to which to move the uploaded file.
+ * @throws \InvalidArgumentException if the $targetPath specified is invalid.
+ * @throws \RuntimeException on any error during the move operation, or on
+ * the second or subsequent call to the method.
+ */
+ public function moveTo($targetPath);
+
+ /**
+ * Retrieve the file size.
+ *
+ * Implementations SHOULD return the value stored in the "size" key of
+ * the file in the $_FILES array if available, as PHP calculates this based
+ * on the actual size transmitted.
+ *
+ * @return int|null The file size in bytes or null if unknown.
+ */
+ public function getSize();
+
+ /**
+ * Retrieve the error associated with the uploaded file.
+ *
+ * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants.
+ *
+ * If the file was uploaded successfully, this method MUST return
+ * UPLOAD_ERR_OK.
+ *
+ * Implementations SHOULD return the value stored in the "error" key of
+ * the file in the $_FILES array.
+ *
+ * @see http://php.net/manual/en/features.file-upload.errors.php
+ * @return int One of PHP's UPLOAD_ERR_XXX constants.
+ */
+ public function getError();
+
+ /**
+ * Retrieve the filename sent by the client.
+ *
+ * Do not trust the value returned by this method. A client could send
+ * a malicious filename with the intention to corrupt or hack your
+ * application.
+ *
+ * Implementations SHOULD return the value stored in the "name" key of
+ * the file in the $_FILES array.
+ *
+ * @return string|null The filename sent by the client or null if none
+ * was provided.
+ */
+ public function getClientFilename();
+
+ /**
+ * Retrieve the media type sent by the client.
+ *
+ * Do not trust the value returned by this method. A client could send
+ * a malicious media type with the intention to corrupt or hack your
+ * application.
+ *
+ * Implementations SHOULD return the value stored in the "type" key of
+ * the file in the $_FILES array.
+ *
+ * @return string|null The media type sent by the client or null if none
+ * was provided.
+ */
+ public function getClientMediaType();
+}
diff --git a/vendor/psr/http-message/src/UriInterface.php b/vendor/psr/http-message/src/UriInterface.php
new file mode 100755
index 0000000..9d7ab9e
--- /dev/null
+++ b/vendor/psr/http-message/src/UriInterface.php
@@ -0,0 +1,323 @@
+<?php
+namespace Psr\Http\Message;
+
+/**
+ * Value object representing a URI.
+ *
+ * This interface is meant to represent URIs according to RFC 3986 and to
+ * provide methods for most common operations. Additional functionality for
+ * working with URIs can be provided on top of the interface or externally.
+ * Its primary use is for HTTP requests, but may also be used in other
+ * contexts.
+ *
+ * Instances of this interface are considered immutable; all methods that
+ * might change state MUST be implemented such that they retain the internal
+ * state of the current instance and return an instance that contains the
+ * changed state.
+ *
+ * Typically the Host header will be also be present in the request message.
+ * For server-side requests, the scheme will typically be discoverable in the
+ * server parameters.
+ *
+ * @link http://tools.ietf.org/html/rfc3986 (the URI specification)
+ */
+interface UriInterface
+{
+ /**
+ * Retrieve the scheme component of the URI.
+ *
+ * If no scheme is present, this method MUST return an empty string.
+ *
+ * The value returned MUST be normalized to lowercase, per RFC 3986
+ * Section 3.1.
+ *
+ * The trailing ":" character is not part of the scheme and MUST NOT be
+ * added.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.1
+ * @return string The URI scheme.
+ */
+ public function getScheme();
+
+ /**
+ * Retrieve the authority component of the URI.
+ *
+ * If no authority information is present, this method MUST return an empty
+ * string.
+ *
+ * The authority syntax of the URI is:
+ *
+ * <pre>
+ * [user-info@]host[:port]
+ * </pre>
+ *
+ * If the port component is not set or is the standard port for the current
+ * scheme, it SHOULD NOT be included.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.2
+ * @return string The URI authority, in "[user-info@]host[:port]" format.
+ */
+ public function getAuthority();
+
+ /**
+ * Retrieve the user information component of the URI.
+ *
+ * If no user information is present, this method MUST return an empty
+ * string.
+ *
+ * If a user is present in the URI, this will return that value;
+ * additionally, if the password is also present, it will be appended to the
+ * user value, with a colon (":") separating the values.
+ *
+ * The trailing "@" character is not part of the user information and MUST
+ * NOT be added.
+ *
+ * @return string The URI user information, in "username[:password]" format.
+ */
+ public function getUserInfo();
+
+ /**
+ * Retrieve the host component of the URI.
+ *
+ * If no host is present, this method MUST return an empty string.
+ *
+ * The value returned MUST be normalized to lowercase, per RFC 3986
+ * Section 3.2.2.
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
+ * @return string The URI host.
+ */
+ public function getHost();
+
+ /**
+ * Retrieve the port component of the URI.
+ *
+ * If a port is present, and it is non-standard for the current scheme,
+ * this method MUST return it as an integer. If the port is the standard port
+ * used with the current scheme, this method SHOULD return null.
+ *
+ * If no port is present, and no scheme is present, this method MUST return
+ * a null value.
+ *
+ * If no port is present, but a scheme is present, this method MAY return
+ * the standard port for that scheme, but SHOULD return null.
+ *
+ * @return null|int The URI port.
+ */
+ public function getPort();
+
+ /**
+ * Retrieve the path component of the URI.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * Normally, the empty path "" and absolute path "/" are considered equal as
+ * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
+ * do this normalization because in contexts with a trimmed base path, e.g.
+ * the front controller, this difference becomes significant. It's the task
+ * of the user to handle both "" and "/".
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.3.
+ *
+ * As an example, if the value should include a slash ("/") not intended as
+ * delimiter between path segments, that value MUST be passed in encoded
+ * form (e.g., "%2F") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.3
+ * @return string The URI path.
+ */
+ public function getPath();
+
+ /**
+ * Retrieve the query string of the URI.
+ *
+ * If no query string is present, this method MUST return an empty string.
+ *
+ * The leading "?" character is not part of the query and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.4.
+ *
+ * As an example, if a value in a key/value pair of the query string should
+ * include an ampersand ("&") not intended as a delimiter between values,
+ * that value MUST be passed in encoded form (e.g., "%26") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.4
+ * @return string The URI query string.
+ */
+ public function getQuery();
+
+ /**
+ * Retrieve the fragment component of the URI.
+ *
+ * If no fragment is present, this method MUST return an empty string.
+ *
+ * The leading "#" character is not part of the fragment and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.5.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.5
+ * @return string The URI fragment.
+ */
+ public function getFragment();
+
+ /**
+ * Return an instance with the specified scheme.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified scheme.
+ *
+ * Implementations MUST support the schemes "http" and "https" case
+ * insensitively, and MAY accommodate other schemes if required.
+ *
+ * An empty scheme is equivalent to removing the scheme.
+ *
+ * @param string $scheme The scheme to use with the new instance.
+ * @return static A new instance with the specified scheme.
+ * @throws \InvalidArgumentException for invalid or unsupported schemes.
+ */
+ public function withScheme($scheme);
+
+ /**
+ * Return an instance with the specified user information.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified user information.
+ *
+ * Password is optional, but the user information MUST include the
+ * user; an empty string for the user is equivalent to removing user
+ * information.
+ *
+ * @param string $user The user name to use for authority.
+ * @param null|string $password The password associated with $user.
+ * @return static A new instance with the specified user information.
+ */
+ public function withUserInfo($user, $password = null);
+
+ /**
+ * Return an instance with the specified host.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified host.
+ *
+ * An empty host value is equivalent to removing the host.
+ *
+ * @param string $host The hostname to use with the new instance.
+ * @return static A new instance with the specified host.
+ * @throws \InvalidArgumentException for invalid hostnames.
+ */
+ public function withHost($host);
+
+ /**
+ * Return an instance with the specified port.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified port.
+ *
+ * Implementations MUST raise an exception for ports outside the
+ * established TCP and UDP port ranges.
+ *
+ * A null value provided for the port is equivalent to removing the port
+ * information.
+ *
+ * @param null|int $port The port to use with the new instance; a null value
+ * removes the port information.
+ * @return static A new instance with the specified port.
+ * @throws \InvalidArgumentException for invalid ports.
+ */
+ public function withPort($port);
+
+ /**
+ * Return an instance with the specified path.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified path.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * If the path is intended to be domain-relative rather than path relative then
+ * it must begin with a slash ("/"). Paths not starting with a slash ("/")
+ * are assumed to be relative to some base path known to the application or
+ * consumer.
+ *
+ * Users can provide both encoded and decoded path characters.
+ * Implementations ensure the correct encoding as outlined in getPath().
+ *
+ * @param string $path The path to use with the new instance.
+ * @return static A new instance with the specified path.
+ * @throws \InvalidArgumentException for invalid paths.
+ */
+ public function withPath($path);
+
+ /**
+ * Return an instance with the specified query string.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified query string.
+ *
+ * Users can provide both encoded and decoded query characters.
+ * Implementations ensure the correct encoding as outlined in getQuery().
+ *
+ * An empty query string value is equivalent to removing the query string.
+ *
+ * @param string $query The query string to use with the new instance.
+ * @return static A new instance with the specified query string.
+ * @throws \InvalidArgumentException for invalid query strings.
+ */
+ public function withQuery($query);
+
+ /**
+ * Return an instance with the specified URI fragment.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified URI fragment.
+ *
+ * Users can provide both encoded and decoded fragment characters.
+ * Implementations ensure the correct encoding as outlined in getFragment().
+ *
+ * An empty fragment value is equivalent to removing the fragment.
+ *
+ * @param string $fragment The fragment to use with the new instance.
+ * @return static A new instance with the specified fragment.
+ */
+ public function withFragment($fragment);
+
+ /**
+ * Return the string representation as a URI reference.
+ *
+ * Depending on which components of the URI are present, the resulting
+ * string is either a full URI or relative reference according to RFC 3986,
+ * Section 4.1. The method concatenates the various components of the URI,
+ * using the appropriate delimiters:
+ *
+ * - If a scheme is present, it MUST be suffixed by ":".
+ * - If an authority is present, it MUST be prefixed by "//".
+ * - The path can be concatenated without delimiters. But there are two
+ * cases where the path has to be adjusted to make the URI reference
+ * valid as PHP does not allow to throw an exception in __toString():
+ * - If the path is rootless and an authority is present, the path MUST
+ * be prefixed by "/".
+ * - If the path is starting with more than one "/" and no authority is
+ * present, the starting slashes MUST be reduced to one.
+ * - If a query is present, it MUST be prefixed by "?".
+ * - If a fragment is present, it MUST be prefixed by "#".
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-4.1
+ * @return string
+ */
+ public function __toString();
+}
diff --git a/vendor/psr/log/.gitignore b/vendor/psr/log/.gitignore
new file mode 100755
index 0000000..22d0d82
--- /dev/null
+++ b/vendor/psr/log/.gitignore
@@ -0,0 +1 @@
+vendor
diff --git a/vendor/psr/log/LICENSE b/vendor/psr/log/LICENSE
new file mode 100755
index 0000000..474c952
--- /dev/null
+++ b/vendor/psr/log/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/psr/log/Psr/Log/AbstractLogger.php b/vendor/psr/log/Psr/Log/AbstractLogger.php
new file mode 100755
index 0000000..90e721a
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/AbstractLogger.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * This is a simple Logger implementation that other Loggers can inherit from.
+ *
+ * It simply delegates all log-level-specific methods to the `log` method to
+ * reduce boilerplate code that a simple Logger that does the same thing with
+ * messages regardless of the error level has to implement.
+ */
+abstract class AbstractLogger implements LoggerInterface
+{
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function emergency($message, array $context = array())
+ {
+ $this->log(LogLevel::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array())
+ {
+ $this->log(LogLevel::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+}
diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php
new file mode 100755
index 0000000..67f852d
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Psr\Log;
+
+class InvalidArgumentException extends \InvalidArgumentException
+{
+}
diff --git a/vendor/psr/log/Psr/Log/LogLevel.php b/vendor/psr/log/Psr/Log/LogLevel.php
new file mode 100755
index 0000000..9cebcac
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/LogLevel.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes log levels.
+ */
+class LogLevel
+{
+ const EMERGENCY = 'emergency';
+ const ALERT = 'alert';
+ const CRITICAL = 'critical';
+ const ERROR = 'error';
+ const WARNING = 'warning';
+ const NOTICE = 'notice';
+ const INFO = 'info';
+ const DEBUG = 'debug';
+}
diff --git a/vendor/psr/log/Psr/Log/LoggerAwareInterface.php b/vendor/psr/log/Psr/Log/LoggerAwareInterface.php
new file mode 100755
index 0000000..4d64f47
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/LoggerAwareInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes a logger-aware instance.
+ */
+interface LoggerAwareInterface
+{
+ /**
+ * Sets a logger instance on the object.
+ *
+ * @param LoggerInterface $logger
+ *
+ * @return void
+ */
+ public function setLogger(LoggerInterface $logger);
+}
diff --git a/vendor/psr/log/Psr/Log/LoggerAwareTrait.php b/vendor/psr/log/Psr/Log/LoggerAwareTrait.php
new file mode 100755
index 0000000..639f79b
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/LoggerAwareTrait.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Basic Implementation of LoggerAwareInterface.
+ */
+trait LoggerAwareTrait
+{
+ /**
+ * The logger instance.
+ *
+ * @var LoggerInterface
+ */
+ protected $logger;
+
+ /**
+ * Sets a logger.
+ *
+ * @param LoggerInterface $logger
+ */
+ public function setLogger(LoggerInterface $logger)
+ {
+ $this->logger = $logger;
+ }
+}
diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php
new file mode 100755
index 0000000..e695046
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/LoggerInterface.php
@@ -0,0 +1,125 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes a logger instance.
+ *
+ * The message MUST be a string or object implementing __toString().
+ *
+ * The message MAY contain placeholders in the form: {foo} where foo
+ * will be replaced by the context data in key "foo".
+ *
+ * The context array can contain arbitrary data. The only assumption that
+ * can be made by implementors is that if an Exception instance is given
+ * to produce a stack trace, it MUST be in a key named "exception".
+ *
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
+ * for the full interface specification.
+ */
+interface LoggerInterface
+{
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function emergency($message, array $context = array());
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array());
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array());
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array());
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array());
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array());
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array());
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array());
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ public function log($level, $message, array $context = array());
+}
diff --git a/vendor/psr/log/Psr/Log/LoggerTrait.php b/vendor/psr/log/Psr/Log/LoggerTrait.php
new file mode 100755
index 0000000..e392fef
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/LoggerTrait.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * This is a simple Logger trait that classes unable to extend AbstractLogger
+ * (because they extend another class, etc) can include.
+ *
+ * It simply delegates all log-level-specific methods to the `log` method to
+ * reduce boilerplate code that a simple Logger that does the same thing with
+ * messages regardless of the error level has to implement.
+ */
+trait LoggerTrait
+{
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function emergency($message, array $context = array())
+ {
+ $this->log(LogLevel::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array())
+ {
+ $this->log(LogLevel::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ abstract public function log($level, $message, array $context = array());
+}
diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php
new file mode 100755
index 0000000..c8f7293
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/NullLogger.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * This Logger can be used to avoid conditional log calls.
+ *
+ * Logging should always be optional, and if no logger is provided to your
+ * library creating a NullLogger instance to have something to throw logs at
+ * is a good way to avoid littering your code with `if ($this->logger) { }`
+ * blocks.
+ */
+class NullLogger extends AbstractLogger
+{
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ public function log($level, $message, array $context = array())
+ {
+ // noop
+ }
+}
diff --git a/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
new file mode 100755
index 0000000..9ecb6c4
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
@@ -0,0 +1,146 @@
+<?php
+
+namespace Psr\Log\Test;
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Provides a base test class for ensuring compliance with the LoggerInterface.
+ *
+ * Implementors can extend the class and implement abstract methods to run this
+ * as part of their test suite.
+ */
+abstract class LoggerInterfaceTest extends TestCase
+{
+ /**
+ * @return LoggerInterface
+ */
+ abstract public function getLogger();
+
+ /**
+ * This must return the log messages in order.
+ *
+ * The simple formatting of the messages is: "<LOG LEVEL> <MESSAGE>".
+ *
+ * Example ->error('Foo') would yield "error Foo".
+ *
+ * @return string[]
+ */
+ abstract public function getLogs();
+
+ public function testImplements()
+ {
+ $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
+ }
+
+ /**
+ * @dataProvider provideLevelsAndMessages
+ */
+ public function testLogsAtAllLevels($level, $message)
+ {
+ $logger = $this->getLogger();
+ $logger->{$level}($message, array('user' => 'Bob'));
+ $logger->log($level, $message, array('user' => 'Bob'));
+
+ $expected = array(
+ $level.' message of level '.$level.' with context: Bob',
+ $level.' message of level '.$level.' with context: Bob',
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function provideLevelsAndMessages()
+ {
+ return array(
+ LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
+ LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
+ LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
+ LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
+ LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
+ LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
+ LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
+ LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
+ );
+ }
+
+ /**
+ * @expectedException \Psr\Log\InvalidArgumentException
+ */
+ public function testThrowsOnInvalidLevel()
+ {
+ $logger = $this->getLogger();
+ $logger->log('invalid level', 'Foo');
+ }
+
+ public function testContextReplacement()
+ {
+ $logger = $this->getLogger();
+ $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
+
+ $expected = array('info {Message {nothing} Bob Bar a}');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testObjectCastToString()
+ {
+ if (method_exists($this, 'createPartialMock')) {
+ $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
+ } else {
+ $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
+ }
+ $dummy->expects($this->once())
+ ->method('__toString')
+ ->will($this->returnValue('DUMMY'));
+
+ $this->getLogger()->warning($dummy);
+
+ $expected = array('warning DUMMY');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextCanContainAnything()
+ {
+ $closed = fopen('php://memory', 'r');
+ fclose($closed);
+
+ $context = array(
+ 'bool' => true,
+ 'null' => null,
+ 'string' => 'Foo',
+ 'int' => 0,
+ 'float' => 0.5,
+ 'nested' => array('with object' => new DummyTest),
+ 'object' => new \DateTime,
+ 'resource' => fopen('php://memory', 'r'),
+ 'closed' => $closed,
+ );
+
+ $this->getLogger()->warning('Crazy context data', $context);
+
+ $expected = array('warning Crazy context data');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextExceptionKeyCanBeExceptionOrOtherValues()
+ {
+ $logger = $this->getLogger();
+ $logger->warning('Random message', array('exception' => 'oops'));
+ $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
+
+ $expected = array(
+ 'warning Random message',
+ 'critical Uncaught Exception!'
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+}
+
+class DummyTest
+{
+ public function __toString()
+ {
+ return 'DummyTest';
+ }
+}
diff --git a/vendor/psr/log/Psr/Log/Test/TestLogger.php b/vendor/psr/log/Psr/Log/Test/TestLogger.php
new file mode 100755
index 0000000..1be3230
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/Test/TestLogger.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace Psr\Log\Test;
+
+use Psr\Log\AbstractLogger;
+
+/**
+ * Used for testing purposes.
+ *
+ * It records all records and gives you access to them for verification.
+ *
+ * @method bool hasEmergency($record)
+ * @method bool hasAlert($record)
+ * @method bool hasCritical($record)
+ * @method bool hasError($record)
+ * @method bool hasWarning($record)
+ * @method bool hasNotice($record)
+ * @method bool hasInfo($record)
+ * @method bool hasDebug($record)
+ *
+ * @method bool hasEmergencyRecords()
+ * @method bool hasAlertRecords()
+ * @method bool hasCriticalRecords()
+ * @method bool hasErrorRecords()
+ * @method bool hasWarningRecords()
+ * @method bool hasNoticeRecords()
+ * @method bool hasInfoRecords()
+ * @method bool hasDebugRecords()
+ *
+ * @method bool hasEmergencyThatContains($message)
+ * @method bool hasAlertThatContains($message)
+ * @method bool hasCriticalThatContains($message)
+ * @method bool hasErrorThatContains($message)
+ * @method bool hasWarningThatContains($message)
+ * @method bool hasNoticeThatContains($message)
+ * @method bool hasInfoThatContains($message)
+ * @method bool hasDebugThatContains($message)
+ *
+ * @method bool hasEmergencyThatMatches($message)
+ * @method bool hasAlertThatMatches($message)
+ * @method bool hasCriticalThatMatches($message)
+ * @method bool hasErrorThatMatches($message)
+ * @method bool hasWarningThatMatches($message)
+ * @method bool hasNoticeThatMatches($message)
+ * @method bool hasInfoThatMatches($message)
+ * @method bool hasDebugThatMatches($message)
+ *
+ * @method bool hasEmergencyThatPasses($message)
+ * @method bool hasAlertThatPasses($message)
+ * @method bool hasCriticalThatPasses($message)
+ * @method bool hasErrorThatPasses($message)
+ * @method bool hasWarningThatPasses($message)
+ * @method bool hasNoticeThatPasses($message)
+ * @method bool hasInfoThatPasses($message)
+ * @method bool hasDebugThatPasses($message)
+ */
+class TestLogger extends AbstractLogger
+{
+ /**
+ * @var array
+ */
+ public $records = [];
+
+ public $recordsByLevel = [];
+
+ /**
+ * @inheritdoc
+ */
+ public function log($level, $message, array $context = [])
+ {
+ $record = [
+ 'level' => $level,
+ 'message' => $message,
+ 'context' => $context,
+ ];
+
+ $this->recordsByLevel[$record['level']][] = $record;
+ $this->records[] = $record;
+ }
+
+ public function hasRecords($level)
+ {
+ return isset($this->recordsByLevel[$level]);
+ }
+
+ public function hasRecord($record, $level)
+ {
+ if (is_string($record)) {
+ $record = ['message' => $record];
+ }
+ return $this->hasRecordThatPasses(function ($rec) use ($record) {
+ if ($rec['message'] !== $record['message']) {
+ return false;
+ }
+ if (isset($record['context']) && $rec['context'] !== $record['context']) {
+ return false;
+ }
+ return true;
+ }, $level);
+ }
+
+ public function hasRecordThatContains($message, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($message) {
+ return strpos($rec['message'], $message) !== false;
+ }, $level);
+ }
+
+ public function hasRecordThatMatches($regex, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($regex) {
+ return preg_match($regex, $rec['message']) > 0;
+ }, $level);
+ }
+
+ public function hasRecordThatPasses(callable $predicate, $level)
+ {
+ if (!isset($this->recordsByLevel[$level])) {
+ return false;
+ }
+ foreach ($this->recordsByLevel[$level] as $i => $rec) {
+ if (call_user_func($predicate, $rec, $i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function __call($method, $args)
+ {
+ if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
+ $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
+ $level = strtolower($matches[2]);
+ if (method_exists($this, $genericMethod)) {
+ $args[] = $level;
+ return call_user_func_array([$this, $genericMethod], $args);
+ }
+ }
+ throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
+ }
+
+ public function reset()
+ {
+ $this->records = [];
+ $this->recordsByLevel = [];
+ }
+}
diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md
new file mode 100755
index 0000000..a9f20c4
--- /dev/null
+++ b/vendor/psr/log/README.md
@@ -0,0 +1,58 @@
+PSR Log
+=======
+
+This repository holds all interfaces/classes/traits related to
+[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
+
+Note that this is not a logger of its own. It is merely an interface that
+describes a logger. See the specification for more details.
+
+Installation
+------------
+
+```bash
+composer require psr/log
+```
+
+Usage
+-----
+
+If you need a logger, you can use the interface like this:
+
+```php
+<?php
+
+use Psr\Log\LoggerInterface;
+
+class Foo
+{
+ private $logger;
+
+ public function __construct(LoggerInterface $logger = null)
+ {
+ $this->logger = $logger;
+ }
+
+ public function doSomething()
+ {
+ if ($this->logger) {
+ $this->logger->info('Doing work');
+ }
+
+ try {
+ $this->doSomethingElse();
+ } catch (Exception $exception) {
+ $this->logger->error('Oh no!', array('exception' => $exception));
+ }
+
+ // do something useful
+ }
+}
+```
+
+You can then pick one of the implementations of the interface to get a logger.
+
+If you want to implement the interface, you can require this package and
+implement `Psr\Log\LoggerInterface` in your code. Please read the
+[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
+for details.
diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json
new file mode 100755
index 0000000..3f6d4ee
--- /dev/null
+++ b/vendor/psr/log/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "psr/log",
+ "description": "Common interface for logging libraries",
+ "keywords": ["psr", "psr-3", "log"],
+ "homepage": "https://github.com/php-fig/log",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ }
+}
diff --git a/vendor/psr/simple-cache/.editorconfig b/vendor/psr/simple-cache/.editorconfig
new file mode 100755
index 0000000..48542cb
--- /dev/null
+++ b/vendor/psr/simple-cache/.editorconfig
@@ -0,0 +1,12 @@
+; This file is for unifying the coding style for different editors and IDEs.
+; More information at http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+indent_size = 4
+indent_style = space
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/vendor/psr/simple-cache/LICENSE.md b/vendor/psr/simple-cache/LICENSE.md
new file mode 100755
index 0000000..e49a7c8
--- /dev/null
+++ b/vendor/psr/simple-cache/LICENSE.md
@@ -0,0 +1,21 @@
+# The MIT License (MIT)
+
+Copyright (c) 2016 PHP Framework Interoperability Group
+
+> Permission is hereby granted, free of charge, to any person obtaining a copy
+> of this software and associated documentation files (the "Software"), to deal
+> in the Software without restriction, including without limitation the rights
+> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+> copies of the Software, and to permit persons to whom the Software is
+> furnished to do so, subject to the following conditions:
+>
+> The above copyright notice and this permission notice shall be included in
+> all copies or substantial portions of the Software.
+>
+> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+> THE SOFTWARE.
diff --git a/vendor/psr/simple-cache/README.md b/vendor/psr/simple-cache/README.md
new file mode 100755
index 0000000..43641d1
--- /dev/null
+++ b/vendor/psr/simple-cache/README.md
@@ -0,0 +1,8 @@
+PHP FIG Simple Cache PSR
+========================
+
+This repository holds all interfaces related to PSR-16.
+
+Note that this is not a cache implementation of its own. It is merely an interface that describes a cache implementation. See [the specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) for more details.
+
+You can find implementations of the specification by looking for packages providing the [psr/simple-cache-implementation](https://packagist.org/providers/psr/simple-cache-implementation) virtual package.
diff --git a/vendor/psr/simple-cache/composer.json b/vendor/psr/simple-cache/composer.json
new file mode 100755
index 0000000..2978fa5
--- /dev/null
+++ b/vendor/psr/simple-cache/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "psr/simple-cache",
+ "description": "Common interfaces for simple caching",
+ "keywords": ["psr", "psr-16", "cache", "simple-cache", "caching"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\SimpleCache\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
diff --git a/vendor/psr/simple-cache/src/CacheException.php b/vendor/psr/simple-cache/src/CacheException.php
new file mode 100755
index 0000000..eba5381
--- /dev/null
+++ b/vendor/psr/simple-cache/src/CacheException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Psr\SimpleCache;
+
+/**
+ * Interface used for all types of exceptions thrown by the implementing library.
+ */
+interface CacheException
+{
+}
diff --git a/vendor/psr/simple-cache/src/CacheInterface.php b/vendor/psr/simple-cache/src/CacheInterface.php
new file mode 100755
index 0000000..99e8d95
--- /dev/null
+++ b/vendor/psr/simple-cache/src/CacheInterface.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Psr\SimpleCache;
+
+interface CacheInterface
+{
+ /**
+ * Fetches a value from the cache.
+ *
+ * @param string $key The unique key of this item in the cache.
+ * @param mixed $default Default value to return if the key does not exist.
+ *
+ * @return mixed The value of the item from the cache, or $default in case of cache miss.
+ *
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * MUST be thrown if the $key string is not a legal value.
+ */
+ public function get($key, $default = null);
+
+ /**
+ * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
+ *
+ * @param string $key The key of the item to store.
+ * @param mixed $value The value of the item to store, must be serializable.
+ * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and
+ * the driver supports TTL then the library may set a default value
+ * for it or let the driver take care of that.
+ *
+ * @return bool True on success and false on failure.
+ *
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * MUST be thrown if the $key string is not a legal value.
+ */
+ public function set($key, $value, $ttl = null);
+
+ /**
+ * Delete an item from the cache by its unique key.
+ *
+ * @param string $key The unique cache key of the item to delete.
+ *
+ * @return bool True if the item was successfully removed. False if there was an error.
+ *
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * MUST be thrown if the $key string is not a legal value.
+ */
+ public function delete($key);
+
+ /**
+ * Wipes clean the entire cache's keys.
+ *
+ * @return bool True on success and false on failure.
+ */
+ public function clear();
+
+ /**
+ * Obtains multiple cache items by their unique keys.
+ *
+ * @param iterable $keys A list of keys that can obtained in a single operation.
+ * @param mixed $default Default value to return for keys that do not exist.
+ *
+ * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value.
+ *
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * MUST be thrown if $keys is neither an array nor a Traversable,
+ * or if any of the $keys are not a legal value.
+ */
+ public function getMultiple($keys, $default = null);
+
+ /**
+ * Persists a set of key => value pairs in the cache, with an optional TTL.
+ *
+ * @param iterable $values A list of key => value pairs for a multiple-set operation.
+ * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and
+ * the driver supports TTL then the library may set a default value
+ * for it or let the driver take care of that.
+ *
+ * @return bool True on success and false on failure.
+ *
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * MUST be thrown if $values is neither an array nor a Traversable,
+ * or if any of the $values are not a legal value.
+ */
+ public function setMultiple($values, $ttl = null);
+
+ /**
+ * Deletes multiple cache items in a single operation.
+ *
+ * @param iterable $keys A list of string-based keys to be deleted.
+ *
+ * @return bool True if the items were successfully removed. False if there was an error.
+ *
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * MUST be thrown if $keys is neither an array nor a Traversable,
+ * or if any of the $keys are not a legal value.
+ */
+ public function deleteMultiple($keys);
+
+ /**
+ * Determines whether an item is present in the cache.
+ *
+ * NOTE: It is recommended that has() is only to be used for cache warming type purposes
+ * and not to be used within your live applications operations for get/set, as this method
+ * is subject to a race condition where your has() will return true and immediately after,
+ * another script can remove it making the state of your app out of date.
+ *
+ * @param string $key The cache item key.
+ *
+ * @return bool
+ *
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * MUST be thrown if the $key string is not a legal value.
+ */
+ public function has($key);
+}
diff --git a/vendor/psr/simple-cache/src/InvalidArgumentException.php b/vendor/psr/simple-cache/src/InvalidArgumentException.php
new file mode 100755
index 0000000..6a9524a
--- /dev/null
+++ b/vendor/psr/simple-cache/src/InvalidArgumentException.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Psr\SimpleCache;
+
+/**
+ * Exception interface for invalid cache arguments.
+ *
+ * When an invalid argument is passed it must throw an exception which implements
+ * this interface
+ */
+interface InvalidArgumentException extends CacheException
+{
+}
diff --git a/vendor/qcloud/cos-sdk-v5/.gitignore b/vendor/qcloud/cos-sdk-v5/.gitignore
new file mode 100755
index 0000000..1c0b8d4
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/.gitignore
@@ -0,0 +1,2 @@
+.vscode/
+.idea/
\ No newline at end of file
diff --git a/vendor/qcloud/cos-sdk-v5/.travis.yml b/vendor/qcloud/cos-sdk-v5/.travis.yml
new file mode 100755
index 0000000..99220f2
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/.travis.yml
@@ -0,0 +1,6 @@
+language: php
+php:
+ - 5.6
+script:
+ - composer install
+ - phpunit -v
diff --git a/vendor/qcloud/cos-sdk-v5/LICENSE b/vendor/qcloud/cos-sdk-v5/LICENSE
new file mode 100755
index 0000000..2c948c8
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 腾讯云
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/qcloud/cos-sdk-v5/README.md b/vendor/qcloud/cos-sdk-v5/README.md
new file mode 100755
index 0000000..87bc395
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/README.md
@@ -0,0 +1,264 @@
+# COS-PHP-SDK-V5
+腾讯云COS-PHP-SDK-V5([XML API](https://cloud.tencent.com/document/product/436/7751))
+
+[](https://packagist.org/packages/qcloud/cos-sdk-v5)
+[](https://packagist.org/packages/qcloud/cos-sdk-v5)
+[](https://travis-ci.org/tencentyun/cos-php-sdk-v5)
+
+## 环境准备
+* PHP 5.3+
+ 您可以通过`php -v`命令查看当前的 PHP 版本。
+* cURL 扩展
+ 您可以通过`php -m`命令查看 cURL 扩展是否已经安装好。
+
+>- Ubuntu 系统中,您可以使用 apt-get 包管理器安装 PHP 的 cURL 扩展,安装命令如下。
+```
+sudo apt-get install php-curl
+```
+>- CentOS 系统中,您可以使用 yum 包管理器安装 PHP 的 cURL 扩展。
+```
+sudo yum install php-curl
+```
+
+## SDK 安装
+SDK 安装有三种方式:
+* Composer 方式
+* Phar 方式
+* 源码方式
+
+### Composer 方式
+推荐使用 Composer 安装 cos-php-sdk-v5,Composer 是 PHP 的依赖管理工具,允许您声明项目所需的依赖,然后自动将它们安装到您的项目中。
+> 您可以在 [Composer 官网](https://getcomposer.org/) 上找到更多关于如何安装 Composer,配置自动加载以及用于定义依赖项的其他最佳实践等相关信息。
+
+#### 安装步骤:
+1. 打开终端。
+2. 下载 Composer,执行以下命令。
+```
+curl -sS https://getcomposer.org/installer | php
+```
+3. 创建一个名为`composer.json`的文件,内容如下。
+```
+{
+ "require": {
+ "qcloud/cos-sdk-v5": "1.*"
+ }
+}
+```
+4. 使用 Composer 安装,执行以下命令。
+```
+php composer.phar install
+```
+使用该命令后会在当前目录中创建一个 vendor 文件夹,里面包含 SDK 的依赖库和一个 autoload.php 脚本,方便在项目中调用。
+5. 通过 autoloader 脚本调用 cos-php-sdk-v5。
+```
+require '/path/to/sdk/vendor/autoload.php';
+```
+
+现在您的项目已经可以使用 COS 的 V5 版本 SDK 了。
+
+### Phar 方式
+Phar 方式安装 SDK 的步骤如下:
+1. 在 [GitHub 发布页面](https://github.com/tencentyun/cos-php-sdk-v5/releases) 下载相应的 phar 文件。
+2. 在代码中引入 phar 文件:
+```
+require '/path/to/cos-sdk-v5.phar';
+```
+
+### 源码方式
+源码方式安装 SDK 的步骤如下:
+1. 在 [GitHub 发布页面](https://github.com/tencentyun/cos-php-sdk-v5/releases) 下载相应的 zip 文件。
+2. 解压通过 autoload.php 脚本加载 SDK:
+```
+require '/path/to/sdk/vendor/autoload.php';
+```
+
+## 快速入门
+可参照 Demo 程序,详见 [sample.php](https://github.com/tencentyun/cos-php-sdk-v5/blob/master/sample.php)。
+
+## 接口文档
+php sdk 接口文档,详见https://cloud.tencent.com/document/product/436/12267
+
+### 配置文件
+```php
+$cosClient = new Qcloud\Cos\Client(array('region' => '<Region>',
+ 'credentials'=> array(
+ 'secretId' => '<SecretId>',
+ 'secretKey' => '<SecretKey>')));
+```
+
+若您使用 [临时密钥](https://cloud.tencent.com/document/product/436/14048) 初始化,请用下面方式创建实例。
+
+```
+$cosClient = new Qcloud\Cos\Client(array('region' => '<Region>',
+ 'credentials'=> array(
+ 'secretId' => '<SecretId>',
+ 'secretKey' => '<SecretKey>',
+ 'token' => '<XCosSecurityToken>')));
+```
+
+### 上传文件
+* 使用putObject接口上传文件(最大5G)
+* 使用Upload接口分块上传文件
+```php
+# 上传文件
+## putObject(上传接口,最大支持上传5G文件)
+### 上传内存中的字符串
+//bucket 的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式
+try {
+ $result = $cosClient->putObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Body' => 'Hello World!'));
+ print_r($result);
+} catch (\Exception $e) {
+ echo "$e\n";
+}
+
+### 上传文件流
+try {
+ $result = $cosClient->putObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Body' => fopen($local_path, 'rb')));
+ print_r($result);
+} catch (\Exception $e) {
+ echo "$e\n";
+}
+
+### 设置header和meta
+try {
+ $result = $cosClient->putObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Body' => fopen($local_path, 'rb'),
+ 'ACL' => 'string',
+ 'CacheControl' => 'string',
+ 'ContentDisposition' => 'string',
+ 'ContentEncoding' => 'string',
+ 'ContentLanguage' => 'string',
+ 'ContentLength' => integer,
+ 'ContentType' => 'string',
+ 'Expires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime',
+ 'Metadata' => array(
+ 'string' => 'string',
+ ),
+ 'StorageClass' => 'string'));
+ print_r($result);
+} catch (\Exception $e) {
+ echo "$e\n";
+}
+
+## Upload(高级上传接口,默认使用分块上传最大支持50T)
+### 上传内存中的字符串
+try {
+ $result = $cosClient->Upload(
+ $bucket = $bucket,
+ $key = $key,
+ $body = 'Hello World!');
+ print_r($result);
+} catch (\Exception $e) {
+ echo "$e\n";
+}
+
+### 上传文件流
+try {
+ $result = $cosClient->Upload(
+ $bucket = $bucket,
+ $key = $key,
+ $body = fopen($local_path, 'rb'));
+ print_r($result);
+} catch (\Exception $e) {
+ echo "$e\n";
+}
+
+### 设置header和meta
+try {
+ $result = $cosClient->Upload(
+ $bucket= $bucket,
+ $key = $key,
+ $body = fopen($local_path, 'rb'),
+ $options = array(
+ 'ACL' => 'string',
+ 'CacheControl' => 'string',
+ 'ContentDisposition' => 'string',
+ 'ContentEncoding' => 'string',
+ 'ContentLanguage' => 'string',
+ 'ContentLength' => integer,
+ 'ContentType' => 'string',
+ 'Expires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime',
+ 'Metadata' => array(
+ 'string' => 'string',
+ ),
+ 'StorageClass' => 'string'));
+ print_r($result);
+} catch (\Exception $e) {
+ echo "$e\n";
+}
+```
+
+### 下载文件
+* 使用getObject接口下载文件
+* 使用getObjectUrl接口获取文件下载URL
+```php
+# 下载文件
+## getObject(下载文件)
+### 下载到内存
+//bucket 的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式
+try {
+ $result = $cosClient->getObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key));
+ echo($result['Body']);
+} catch (\Exception $e) {
+ echo "$e\n";
+}
+
+### 下载到本地
+try {
+ $result = $cosClient->getObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'SaveAs' => $local_path));
+} catch (\Exception $e) {
+ echo "$e\n";
+}
+
+### 指定下载范围
+/*
+ * Range 字段格式为 'bytes=a-b'
+ */
+try {
+ $result = $cosClient->getObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Range' => 'bytes=0-10',
+ 'SaveAs' => $local_path));
+} catch (\Exception $e) {
+ echo "$e\n";
+}
+
+### 设置返回header
+try {
+ $result = $cosClient->getObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'ResponseCacheControl' => 'string',
+ 'ResponseContentDisposition' => 'string',
+ 'ResponseContentEncoding' => 'string',
+ 'ResponseContentLanguage' => 'string',
+ 'ResponseContentType' => 'string',
+ 'ResponseExpires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime',
+ 'SaveAs' => $local_path));
+} catch (\Exception $e) {
+ echo "$e\n";
+}
+
+## getObjectUrl(获取文件UrL)
+try {
+ $signedUrl = $cosClient->getObjectUrl($bucket, $key, '+10 minutes');
+ echo $signedUrl;
+} catch (\Exception $e) {
+ print_r($e);
+}
+```
+
diff --git a/vendor/qcloud/cos-sdk-v5/composer.json b/vendor/qcloud/cos-sdk-v5/composer.json
new file mode 100755
index 0000000..bdeff53
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "qcloud/cos-sdk-v5",
+ "description": "PHP SDK for QCloud COS",
+ "keywords": [
+ "qcloud", "cos", "php"
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "yaozongyou",
+ "email": "yaozongyou@vip.qq.com"
+ },
+ {
+ "name": "lewzylu",
+ "email": "327874225@qq.com"
+ }
+ ],
+ "autoload": {
+ "psr-0": {
+ "Qcloud\\Cos\\": "src/"
+ }
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "guzzle/guzzle": "~3.7"
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/phpunit.xml b/vendor/qcloud/cos-sdk-v5/phpunit.xml
new file mode 100755
index 0000000..3c7a4c1
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/phpunit.xml
@@ -0,0 +1,7 @@
+<phpunit bootstrap="vendor/autoload.php">
+ <testsuites>
+ <testsuite name="cos">
+ <directory>src/Qcloud/Cos/Tests</directory>
+ </testsuite>
+ </testsuites>
+</phpunit>
diff --git a/vendor/qcloud/cos-sdk-v5/sample.php b/vendor/qcloud/cos-sdk-v5/sample.php
new file mode 100755
index 0000000..4c98c86
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample.php
@@ -0,0 +1,824 @@
+<?php
+
+require 'vendor/autoload.php';
+
+$cosClient = new Qcloud\Cos\Client(array(
+ 'region' => 'COS_REGION', #地域,如ap-guangzhou,ap-beijing-1
+ 'credentials' => array(
+ 'secretId' => 'COS_KEY',
+ 'secretKey' => 'COS_SECRET',
+ ),
+));
+
+// 若初始化 Client 时未填写 appId,则 bucket 的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式
+$bucket = 'test2-1252448703';
+$key = 'a.txt';
+$local_path = "E:/a.txt";
+
+# 上传文件
+## putObject(上传接口,最大支持上传5G文件)
+### 上传内存中的字符串
+try {
+ $result = $cosClient->putObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Body' => 'Hello World!'
+ ));
+ print_r($result);
+ # 可以直接通过$result读出返回结果
+ echo ($result['ETag']);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+### 上传文件流
+try {
+ $result = $cosClient->putObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Body' => fopen($local_path, 'rb')
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+### 设置header和meta
+try {
+ $result = $cosClient->putObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Body' => fopen($local_path, 'rb'),
+ 'ACL' => 'string',
+ 'CacheControl' => 'string',
+ 'ContentDisposition' => 'string',
+ 'ContentEncoding' => 'string',
+ 'ContentLanguage' => 'string',
+ 'ContentLength' => integer,
+ 'cONTENTType' => 'string',
+ 'Expires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime',
+ 'GrantFullControl' => 'string',
+ 'GrantRead' => 'string',
+ 'GrantWrite' => 'string',
+ 'Metadata' => array(
+ 'string' => 'string',
+ ),
+ 'StorageClass' => 'string'
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## Upload(高级上传接口,默认使用分块上传最大支持50T)
+### 上传内存中的字符串
+try {
+ $result = $cosClient->upload(
+ $bucket = $bucket,
+ $key = $key,
+ $body = 'Hello World!'
+ );
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+### 上传文件流
+try {
+ $result = $cosClient->upload(
+ $bucket = $bucket,
+ $key = $key,
+ $body = fopen($local_path, 'rb')
+ );
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+### 设置header和meta
+try {
+ $result = $cosClient->upload(
+ $bucket = $bucket,
+ $key = $key,
+ $body = fopen($local_path, 'rb'),
+ $options = array(
+ 'ACL' => 'string',
+ 'CacheControl' => 'string',
+ 'ContentDisposition' => 'string',
+ 'ContentEncoding' => 'string',
+ 'ContentLanguage' => 'string',
+ 'ContentLength' => integer,
+ 'ContentType' => 'string',
+ 'Expires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime',
+ 'GrantFullControl' => 'string',
+ 'GrantRead' => 'string',
+ 'GrantWrite' => 'string',
+ 'Metadata' => array(
+ 'string' => 'string',
+ ),
+ 'StorageClass' => 'string'
+ )
+ );
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## 预签名上传createPresignedUrl
+## 获取带有签名的url
+### 简单上传预签名
+try {
+ #此处可以替换为其他上传接口
+ $command = $cosClient->getCommand('putObject', array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Body' => '', //Body可以任意
+ ));
+ $signedUrl = $command->createPresignedUrl('+10 minutes');
+ echo ($signedUrl);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+### 分块上传预签名
+try {
+ #此处可以替换为其他上传接口
+ $command = $cosClient->getCommand('uploadPart', array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'UploadId' => '',
+ 'PartNumber' => '1',
+ 'Body' => '', //Body可以任意
+ ));
+ $signedUrl = $command->createPresignedUrl('+10 minutes');
+ echo ($signedUrl);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+### 获取签名
+try {
+ #此处可以替换为其他上传接口
+ $command = $cosClient->getCommand('putObject', array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Body' => '', //Body可以任意
+ ));
+ $signedUrl = $command->createAuthorization('+10 minutes');
+ echo ($signedUrl);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+
+# 下载文件
+## getObject(下载文件)
+### 下载到内存
+try {
+ $result = $cosClient->getObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key
+ ));
+ echo $result['Body'];
+} catch (\Exception $e) {
+ echo($e);
+}
+
+### 下载到本地
+try {
+ $result = $cosClient->getObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'SaveAs' => $local_path
+ ));
+} catch (\Exception $e) {
+ echo($e);
+}
+
+### 指定下载范围
+/*
+ * Range 字段格式为 'bytes=a-b'
+ */
+try {
+ $result = $cosClient->getObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Range' => 'bytes=0-10',
+ 'SaveAs' => $local_path
+ ));
+} catch (\Exception $e) {
+ echo($e);
+}
+
+### 设置返回header
+try {
+ $result = $cosClient->getObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'ResponseCacheControl' => 'string',
+ 'ResponseContentDisposition' => 'string',
+ 'ResponseContentEncoding' => 'string',
+ 'ResponseContentLanguage' => 'string',
+ 'ResponseContentType' => 'string',
+ 'ResponseExpires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime',
+ 'SaveAs' => $local_path
+ ));
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## getObjectUrl(获取文件UrL)
+try {
+ $signedUrl = $cosClient->getObjectUrl($bucket, $key, '+10 minutes');
+ echo $signedUrl;
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 删除object
+## deleteObject
+try {
+ $result = $cosClient->deleteObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'VersionId' => 'string'
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 删除多个object
+## deleteObjects
+try {
+ $result = $cosClient->deleteObjects(array(
+ 'Bucket' => 'string',
+ 'Objects' => array(
+ array(
+ 'Key' => $key,
+ 'VersionId' => 'string',
+ ),
+ // ... repeated
+ ),
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 获取object信息
+## headObject
+/*
+ * 可代替isObjectExist接口,查询object是否存在
+ */
+try {
+ $result = $cosClient->headObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => '11',
+ 'VersionId' => '111',
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 获取bucket列表
+## listBuckets
+try {
+ $result = $cosClient->listBuckets();
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 创建bucket
+## createBucket
+try {
+ $result = $cosClient->createBucket(array('Bucket' => $bucket));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 删除bucket
+## deleteBucket
+try {
+ $result = $cosClient->deleteBucket(array(
+ 'Bucket' => $bucket
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 获取bucket信息
+## headBucket
+/*
+ * 可代替isBucketExist接口,查询bucket是否存在
+ */
+try {
+ $result = $cosClient->headBucket(array(
+ 'Bucket' => $bucket
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 列出bucket下的object
+## listObjects
+### 列出所有object
+/*
+ * 该接口一次最多列出1000个,需要列出所有请参考其他服务中的清空并删除bucket接口
+ */
+try {
+ $result = $cosClient->listObjects(array(
+ 'Bucket' => $bucket
+ ));
+ foreach ($result['Contents'] as $rt) {
+ print_r($rt);
+ }
+} catch (\Exception $e) {
+ echo($e);
+}
+
+### 列出带有前缀的object
+try {
+ $result = $cosClient->listObjects(array(
+ 'Bucket' => $bucket,
+ 'Prefix' => 'string'
+ ));
+ foreach ($result['Contents'] as $rt) {
+ print_r($rt);
+ }
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 获取bucket地域
+## getBucketLocation
+try {
+ $result = $cosClient->getBucketLocation(array(
+ 'Bucket' => 'lewzylu02',
+ ));
+} catch (\Exception $e) {
+ echo($e);
+};
+
+# 多版本相关
+## putBucketVersioning(开启关闭某个bucket的多版本)
+try {
+ $result = $cosClient->putBucketVersioning(array(
+ 'Bucket' => $bucket,
+ 'Status' => 'Enabled'
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## ListObjectVersions(列出多版本object)
+/*
+ * 同名文件会出现多个版本
+ */
+try {
+ $result = $cosClient->listObjectVersions(array(
+ 'Bucket' => $bucket,
+ 'Prefix' => 'string'
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## getBucketVersioning(获取某个bucket多版本属性)
+try {
+ $result = $cosClient->getBucketVersioning(
+ array('Bucket' => $bucket));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# ACL相关
+## PutBucketAcl(设置bucketACL)
+try {
+ $result = $cosClient->putBucketAcl(array(
+ 'Bucket' => $bucket,
+ 'Grants' => array(
+ array(
+ 'Grantee' => array(
+ 'DisplayName' => 'qcs::cam::uin/327874225:uin/327874225',
+ 'ID' => 'qcs::cam::uin/327874225:uin/327874225',
+ 'Type' => 'CanonicalUser',
+ ),
+ 'Permission' => 'FULL_CONTROL',
+ ),
+ // ... repeated
+ ),
+ 'Owner' => array(
+ 'DisplayName' => 'qcs::cam::uin/3210232098:uin/3210232098',
+ 'ID' => 'qcs::cam::uin/3210232098:uin/3210232098',
+ )));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## getBucketAcl(获取bucketACL)
+try {
+ $result = $cosClient->getBucketAcl(array(
+ 'Bucket' => $bucket));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## putObjectAcl(设置objectACL)
+try {
+ $result = $cosClient->putObjectAcl(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Grants' => array(
+ array(
+ 'Grantee' => array(
+ 'DisplayName' => 'qcs::cam::uin/327874225:uin/327874225',
+ 'ID' => 'qcs::cam::uin/327874225:uin/327874225',
+ 'Type' => 'CanonicalUser',
+ ),
+ 'Permission' => 'FULL_CONTROL',
+ ),
+ // ... repeated
+ ),
+ 'Owner' => array(
+ 'DisplayName' => 'qcs::cam::uin/3210232098:uin/3210232098',
+ 'ID' => 'qcs::cam::uin/3210232098:uin/3210232098',
+ )));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## GetObjectAcl(获取objectACL)
+try {
+ $result = $cosClient->getObjectAcl(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 生命周期相关
+## putBucketLifecycle(设置bucket生命周期)
+try {
+ $result = $cosClient->putBucketLifecycle(array(
+ 'Bucket' => $bucket,
+ 'Rules' => array(
+ array(
+ 'Expiration' => array(
+ 'Days' => 1000,
+ ),
+ 'ID' => 'id1',
+ 'Filter' => array(
+ 'Prefix' => 'documents/',
+ ),
+ 'Status' => 'Enabled',
+ 'Transitions' => array(
+ array(
+ 'Days' => 200,
+ 'StorageClass' => 'NEARLINE'),
+ ),
+ ),
+ )));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## getBucketLifecycle(获取bucket生命周期)
+try {
+ $result = $cosClient->getBucketLifecycle(array(
+ 'Bucket' => $bucket,
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## deleteBucketLifecycle(删除bucket生命周期)
+try {
+ $result = $cosClient->deleteBucketLifecycle(array(
+ 'Bucket' => $bucket,
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 跨域相关
+## putBucketCors(设置bucket跨域)
+try {
+ $result = $cosClient->putBucketCors(array(
+ 'Bucket' => $bucket,
+ 'CORSRules' => array(
+ array(
+ 'ID' => '1234',
+ 'AllowedHeaders' => array('*'),
+ 'AllowedMethods' => array('PUT'),
+ 'AllowedOrigins' => array('http://www.qq.com'),
+ ),
+ ),
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## getBucketCors(获取bucket跨域信息)
+try {
+ $result = $cosClient->getBucketCors(array());
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## deleteBucketCors(删除bucket跨域)
+try {
+ $result = $cosClient->deleteBucketCors(array(
+ // Bucket is required
+ 'Bucket' => $bucket,
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 跨区域复制相关
+## PutBucketReplication(设置bucket跨区域复制)
+### 注意:目标bucket和源bucket都需要开启多版本
+try {
+ $result = $cosClient->putBucketReplication(array(
+ 'Bucket' => $bucket,
+ 'Role' => 'qcs::cam::uin/327874225:uin/327874225',
+ 'Rules'=>array(
+ array(
+ 'Status' => 'Enabled',
+ 'ID' => 'string',
+ 'Prefix' => 'string',
+ 'Destination' => array(
+ 'Bucket' => 'qcs::cos:ap-guangzhou::lewzylu01-1252448703',
+ 'StorageClass' => 'standard',
+ ),
+ // ...repeated
+ ),
+ ),
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## GetBucketReplication(获取bucket跨区域复制信息)
+try {
+ $result = $cosClient->getBucketReplication(array(
+ 'Bucket' => $bucket
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## DeleteBucketReplication(删除bucket跨区域复制信息)
+try {
+ $result = $cosClient->deleteBucketReplication(array(
+ 'Bucket' => $bucket
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 回调相关
+## PutBucketNotification
+try {
+ $result = $cosClient->putBucketNotification(array(
+ "Bucket" => $bucket,
+ "CloudFunctionConfigurations"=> array(
+ array(
+ "Id" => "test-1",
+ "Filter" => array(
+ "Key" => array(
+ "FilterRules" => array(
+ array(
+ "Name" => "Prefix",
+ "Value" => "111"
+ ),
+ array(
+ "Name" => "Suffix",
+ "Value" => "111"
+ ),
+ ),
+ )
+ ),
+ "CloudFunction" => "qcs:0:video:sh:appid/1253125191:video/10010",
+ "Events" => array(
+ 'Event' => "cos:ObjectCreated:*"
+ )
+ ),
+ array(
+ "Id" => "test-2",
+ "Filter" => array(
+ "Key" => array(
+ "FilterRules" => array(
+ array(
+ "Name" => "Prefix",
+ "Value" => "111"
+ ),
+ array(
+ "Name" => "Suffix",
+ "Value" => "111"
+ ),
+ ),
+ )
+ ),
+ "CloudFunction" => "qcs:0:video:sh:appid/1253125191:video/10010",
+ "Events" => array(
+ 'Event' => "cos:ObjectRemove:*"
+ )
+ ),
+ ))
+ );
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## GetBucketNotification
+try {
+ $result = $cosClient->getBucketNotification(array(
+ 'Bucket' => $bucket
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 复制
+## copyobject(简单复制)
+/*
+ * 将{bucket},{region},{cos_path},{versionId}替换成复制源的真实信息
+ */
+try {
+ $result = $cosClient->copyObject(array(
+ 'Bucket' => $bucket,
+ 'CopySource' => '{bucket}.cos.{region}.myqcloud.com/{cos_path}?versionId={versionId}',
+ 'Key' => 'string',
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## Copy(分块并发复制)
+/*
+ * 将{bucket},{region},{cos_path},{versionId}替换成复制源的真实信息
+ */
+try {
+ $result = $cosClient->copy(
+ $bucket = $bucket,
+ $key = $key,
+ $copysource = '{bucket}.cos.{region}.myqcloud.com/{cos_path}',
+ $options = array('VersionId' => '{versionId}'
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 恢复归档文件
+## restoreObject
+try {
+ $result = $cosClient->restoreObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Days' => 7,
+ 'CASJobParameters' => array(
+ 'Tier' => 'Bulk',
+ ),
+ ));
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+# 其他服务
+## 列出某bucket下所有的object
+try {
+ $prefix = '';
+ $marker = '';
+ while (true) {
+ $result = $cosClient->listObjects(array(
+ 'Bucket' => $bucket,
+ 'Marker' => $marker,
+ 'MaxKeys' => 1000
+ ));
+ foreach ($result['Contents'] as $rt) {
+ print_r($rt['Key'] . " ");
+ /*
+ * 使用下面的代码可以删除全部object
+ */
+ // try {
+ // $result = $cosClient->deleteobjects(array(
+ // 'Bucket' => $bucket,
+ // 'Key' => $rt['Key']));
+ // print_r($result);
+ // } catch (\Exception $e) {
+ // echo($e);
+ // }
+ }
+ $marker = $result['NextMarker'];
+ if (!$result['IsTruncated']) {
+ break;
+ }
+ }
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## 删除所有因上传失败而产生的分块
+/*
+ * 可以清理掉因分块上传失败
+ */
+try {
+ while (true) {
+ $result = $cosClient->listMultipartUploads(
+ array('Bucket' => $bucket,
+ 'Prefix' => ''));
+ if (count($result['Uploads']) == 0) {
+ break;
+ }
+ foreach ($result['Uploads'] as $upload) {
+ try {
+ $rt = $cosClient->abortMultipartUpload(array(
+ 'Bucket' => $bucket,
+ 'Key' => $upload['Key'],
+ 'UploadId' => $upload['UploadId']
+ ));
+ print_r($rt);
+ } catch (\Exception $e) {
+ echo($e);
+ }
+ }
+ }
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## 分块上传断点重传
+/*
+ * 仅适用于分块上传失败的情况
+ * 需要填写上传失败的uploadId
+ */
+try {
+ $result = $cosClient->resumeUpload(
+ $bucket = $bucket,
+ $key = $key,
+ $body = fopen("E:/test.txt", 'rb'),
+ $uploadId = '152448808231afdf221eb558ab15d1e455d2afd025c5663936142fdf5614ebf6d1668e2eda'
+ );
+ print_r($result);
+} catch (\Exception $e) {
+ echo($e);
+}
+
+## 删除某些前缀的空bucket
+function startsWith($haystack, $needle)
+{
+ $length = strlen($needle);
+ return (substr($haystack, 0, $length) === $needle);
+}
+
+try {
+ $result = $cosClient->listBuckets();
+ foreach ($result['Buckets'] as $bucket) {
+ $region = $bucket['Location'];
+ $name = $bucket['Name'];
+ if (startsWith($name, 'lewzylu')) {
+ try {
+ $cosClient2 = new Qcloud\Cos\Client(array(
+ 'region' => $region,
+ 'credentials' => array(
+ //getenv为获取本地环境变量,请替换为真实密钥
+ 'secretId' => getenv('COS_KEY'),
+ 'secretKey' => getenv('COS_SECRET'))
+ ));
+ $rt = $cosClient2->deleteBucket(array('Bucket' => $name));
+ print_r($rt);
+ } catch (\Exception $e) {
+ }
+ }
+ }
+} catch (\Exception $e) {
+ echo($e);
+}
diff --git a/vendor/qcloud/cos-sdk-v5/sample/abortMultipartUpload.php b/vendor/qcloud/cos-sdk-v5/sample/abortMultipartUpload.php
new file mode 100755
index 0000000..50cd6da
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/abortMultipartUpload.php
@@ -0,0 +1,27 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->abortMultipartUpload(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'Key' => 'exampleobject',
+ 'UploadId' => 'string',
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/catchException.php b/vendor/qcloud/cos-sdk-v5/sample/catchException.php
new file mode 100755
index 0000000..b44e6f1
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/catchException.php
@@ -0,0 +1,30 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->getBucketAcl(array(
+ 'Bucket' => 'examplebucket-125000000' //格式:BucketName-APPID
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ $statusCode = $e->getStatusCode(); // 获取错误码
+ $errorMessage = $e->getMessage(); // 获取错误信息
+ $requestId = $e->getRequestId(); // 获取错误的requestId
+ $errorCode = $e->getCosErrorCode(); // 获取错误名称
+ $request = $e->getRequest(); // 获取完整的请求
+ $response = $e->getResponse(); // 获取完整的响应
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/completeMultipartUpload.php b/vendor/qcloud/cos-sdk-v5/sample/completeMultipartUpload.php
new file mode 100755
index 0000000..35e0e04
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/completeMultipartUpload.php
@@ -0,0 +1,37 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->completeMultipartUpload(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'Key' => 'exampleobject',
+ 'UploadId' => 'string',
+ 'Parts' => array(
+ array(
+ 'ETag' => 'string',
+ 'PartNumber' => integer,
+ ),
+ array(
+ 'ETag' => 'string',
+ 'PartNumber' => integer,
+ )),
+ // ... repeated
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/copyObject.php b/vendor/qcloud/cos-sdk-v5/sample/copyObject.php
new file mode 100755
index 0000000..813fc46
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/copyObject.php
@@ -0,0 +1,28 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->copyObject(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'Key' => 'exampleobject',
+ 'CopySource' => 'examplebucket2-125000000.cos.ap-guangzhou.myqcloud.com/exampleobject',
+ 'MetadataDirective' => 'Replaced',
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/createBucket.php b/vendor/qcloud/cos-sdk-v5/sample/createBucket.php
new file mode 100755
index 0000000..eafad49
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/createBucket.php
@@ -0,0 +1,25 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->createBucket(array(
+ 'Bucket' => 'examplebucket-125000000' //格式:BucketName-APPID
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/createMultipartUpload.php b/vendor/qcloud/cos-sdk-v5/sample/createMultipartUpload.php
new file mode 100755
index 0000000..bedca7e
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/createMultipartUpload.php
@@ -0,0 +1,39 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->createMultipartUpload(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'Key' => 'exampleobject',
+ /*
+ 'CacheControl' => 'string',
+ 'ContentDisposition' => 'string',
+ 'ContentEncoding' => 'string',
+ 'ContentLanguage' => 'string',
+ 'ContentLength' => integer,
+ 'ContentType' => 'string',
+ 'Expires' => 'string',
+ 'Metadata' => array(
+ 'string' => 'string',
+ ),
+ 'StorageClass' => 'string'
+ */
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/deleteBucket.php b/vendor/qcloud/cos-sdk-v5/sample/deleteBucket.php
new file mode 100755
index 0000000..a3d1fc2
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/deleteBucket.php
@@ -0,0 +1,25 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->deleteBucket(array(
+ 'Bucket' => 'examplebucket-125000000' //格式:BucketName-APPID
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/deleteBucketCors.php b/vendor/qcloud/cos-sdk-v5/sample/deleteBucketCors.php
new file mode 100755
index 0000000..fc958e1
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/deleteBucketCors.php
@@ -0,0 +1,25 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->deleteBucketCors(array(
+ 'Bucket' => 'examplebucket-125000000' //格式:BucketName-APPID
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/deleteBucketLifecycle.php b/vendor/qcloud/cos-sdk-v5/sample/deleteBucketLifecycle.php
new file mode 100755
index 0000000..dbe3183
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/deleteBucketLifecycle.php
@@ -0,0 +1,25 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->deleteBucketLifecycle(array(
+ 'Bucket' => 'examplebucket-125000000' //格式:BucketName-APPID
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/deleteObject.php b/vendor/qcloud/cos-sdk-v5/sample/deleteObject.php
new file mode 100755
index 0000000..d89655e
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/deleteObject.php
@@ -0,0 +1,26 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->deleteObject(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'Key' => 'exampleobject',
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/getBucketAcl.php b/vendor/qcloud/cos-sdk-v5/sample/getBucketAcl.php
new file mode 100755
index 0000000..997c56f
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/getBucketAcl.php
@@ -0,0 +1,25 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->getBucketAcl(array(
+ 'Bucket' => 'examplebucket-125000000' //格式:BucketName-APPID
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/getBucketCors.php b/vendor/qcloud/cos-sdk-v5/sample/getBucketCors.php
new file mode 100755
index 0000000..55b347f
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/getBucketCors.php
@@ -0,0 +1,25 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->getBucketCors(array(
+ 'Bucket' => 'examplebucket-125000000' //格式:BucketName-APPID
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/getBucketLifecycle.php b/vendor/qcloud/cos-sdk-v5/sample/getBucketLifecycle.php
new file mode 100755
index 0000000..aef6702
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/getBucketLifecycle.php
@@ -0,0 +1,25 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->getBucketLifecycle(array(
+ 'Bucket' => 'examplebucket-125000000' //格式:BucketName-APPID
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/getObject.php b/vendor/qcloud/cos-sdk-v5/sample/getObject.php
new file mode 100755
index 0000000..cbc0da7
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/getObject.php
@@ -0,0 +1,36 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->getObject(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'Key' => 'exampleobject',
+ 'SaveAs' => '/data/exampleobject',
+ /*
+ 'Range' => 'bytes=0-10',
+ 'ResponseCacheControl' => 'string',
+ 'ResponseContentDisposition' => 'string',
+ 'ResponseContentEncoding' => 'string',
+ 'ResponseContentLanguage' => 'string',
+ 'ResponseContentType' => 'string',
+ 'ResponseExpires' => 'string',
+ */
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/headBucket.php b/vendor/qcloud/cos-sdk-v5/sample/headBucket.php
new file mode 100755
index 0000000..2f8d0c7
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/headBucket.php
@@ -0,0 +1,25 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->headBucket(array(
+ 'Bucket' => 'examplebucket-125000000' //格式:BucketName-APPID
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/headObject.php b/vendor/qcloud/cos-sdk-v5/sample/headObject.php
new file mode 100755
index 0000000..0d6ba2b
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/headObject.php
@@ -0,0 +1,26 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->headObject(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'Key' => 'exampleobject',
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/listBucket.php b/vendor/qcloud/cos-sdk-v5/sample/listBucket.php
new file mode 100755
index 0000000..6dfcf9a
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/listBucket.php
@@ -0,0 +1,24 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+
+try {
+ $result = $cosClient->listBuckets();
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/listMultipartUploads.php b/vendor/qcloud/cos-sdk-v5/sample/listMultipartUploads.php
new file mode 100755
index 0000000..35c8100
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/listMultipartUploads.php
@@ -0,0 +1,31 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->listMultipartUploads(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'Delimiter' => '/',
+ 'EncodingType' => 'url',
+ 'KeyMarker' => 'string',
+ 'UploadIdMarker' => 'string',
+ 'Prefix' => 'prfix',
+ 'MaxUploads' => 1000,
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/listObjects.php b/vendor/qcloud/cos-sdk-v5/sample/listObjects.php
new file mode 100755
index 0000000..6a4f2a1
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/listObjects.php
@@ -0,0 +1,30 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->listObjects(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'Delimiter' => '/',
+ 'EncodingType' => 'url',
+ 'Marker' => 'prefix/picture.jpg',
+ 'Prefix' => 'prfix',
+ 'MaxKeys' => 1000,
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/listParts.php b/vendor/qcloud/cos-sdk-v5/sample/listParts.php
new file mode 100755
index 0000000..6b18478
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/listParts.php
@@ -0,0 +1,29 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->listParts(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'Key' => 'exampleobject',
+ 'UploadId' => 'NWNhNDY0YzFfMmZiNTM1MGFfNTM2YV8xYjliMTg',
+ 'PartNumberMarker' => 1,
+ 'MaxParts' => 1000,
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/putBucketAcl.php b/vendor/qcloud/cos-sdk-v5/sample/putBucketAcl.php
new file mode 100755
index 0000000..c5b475f
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/putBucketAcl.php
@@ -0,0 +1,41 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->putBucketAcl(array(
+ //bucket的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式
+ 'Bucket' => 'examplebucket-125000000',
+ 'ACL' => 'private',
+ 'Grants' => array(
+ array(
+ 'Grantee' => array(
+ 'DisplayName' => 'qcs::cam::uin/100000000001:uin/100000000001',
+ 'ID' => 'qcs::cam::uin/100000000001:uin/100000000001',
+ 'Type' => 'CanonicalUser',
+ ),
+ 'Permission' => 'FULL_CONTROL',
+ ),
+ // ... repeated
+ ),
+ 'Owner' => array(
+ 'DisplayName' => 'qcs::cam::uin/3210232098:uin/3210232098',
+ 'ID' => 'qcs::cam::uin/3210232098:uin/3210232098',
+ )));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo "$e\n";
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/putBucketCors.php b/vendor/qcloud/cos-sdk-v5/sample/putBucketCors.php
new file mode 100755
index 0000000..375077b
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/putBucketCors.php
@@ -0,0 +1,33 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->putBucketCors(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'CORSRules' => array(
+ array(
+ 'ID' => '1234',
+ 'AllowedHeaders' => array('*'),
+ 'AllowedMethods' => array('PUT'),
+ 'AllowedOrigins' => array('http://www.qq.com'),
+ ),
+ ),
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo "$e\n";
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/putBucketLifecycle.php b/vendor/qcloud/cos-sdk-v5/sample/putBucketLifecycle.php
new file mode 100755
index 0000000..5f7d818
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/putBucketLifecycle.php
@@ -0,0 +1,35 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->putBucketCors(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'CORSRules' => array(
+ array(
+ 'AllowedHeaders' => array('*',),
+ 'AllowedMethods' => array('Put', ),
+ 'AllowedOrigins' => array('*', ),
+ 'ExposeHeaders' => array('*', ),
+ 'MaxAgeSeconds' => 1,
+ ),
+ // ... repeated
+ )
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo "$e\n";
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/putObject.php b/vendor/qcloud/cos-sdk-v5/sample/putObject.php
new file mode 100755
index 0000000..b97c5d0
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/putObject.php
@@ -0,0 +1,41 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+$local_path = "/data/exampleobject";
+try {
+ $result = $cosClient->putObject(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'Key' => 'exampleobject',
+ 'Body' => fopen($local_path, 'rb'),
+ /*
+ 'CacheControl' => 'string',
+ 'ContentDisposition' => 'string',
+ 'ContentEncoding' => 'string',
+ 'ContentLanguage' => 'string',
+ 'ContentLength' => integer,
+ 'ContentType' => 'string',
+ 'Expires' => 'string',
+ 'Metadata' => array(
+ 'string' => 'string',
+ ),
+ 'StorageClass' => 'string'
+ */
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/restoreObject.php b/vendor/qcloud/cos-sdk-v5/sample/restoreObject.php
new file mode 100755
index 0000000..79eeffb
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/restoreObject.php
@@ -0,0 +1,30 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->putObject(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'Key' => 'exampleobject',
+ 'Days' => integer,
+ 'CASJobParameters' => array(
+ 'Tier' =>'string'
+ )
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/upload.php b/vendor/qcloud/cos-sdk-v5/sample/upload.php
new file mode 100755
index 0000000..70a24a2
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/upload.php
@@ -0,0 +1,49 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+$local_path = "/data/exampleobject";
+try {
+ $result = $cosClient->upload(
+ $bucket = 'examplebucket-125000000', //格式:BucketName-APPID
+ $key = 'exampleobject',
+ $body = fopen($local_path, 'rb')
+ /*
+ $options = array(
+ 'ACL' => 'string',
+ 'CacheControl' => 'string',
+ 'ContentDisposition' => 'string',
+ 'ContentEncoding' => 'string',
+ 'ContentLanguage' => 'string',
+ 'ContentLength' => integer,
+ 'ContentType' => 'string',
+ 'Expires' => 'string',
+ 'GrantFullControl' => 'string',
+ 'GrantRead' => 'string',
+ 'GrantWrite' => 'string',
+ 'Metadata' => array(
+ 'string' => 'string',
+ ),
+ 'ContentMD5' => 'string',
+ 'ServerSideEncryption' => 'string',
+ 'StorageClass' => 'string'
+ )
+ */
+ );
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/sample/uploadPart.php b/vendor/qcloud/cos-sdk-v5/sample/uploadPart.php
new file mode 100755
index 0000000..d255807
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/sample/uploadPart.php
@@ -0,0 +1,31 @@
+<?php
+
+require dirname(__FILE__) . '/../vendor/autoload.php';
+
+$secretId = "COS_SECRETID"; //"云 API 密钥 SecretId";
+$secretKey = "COS_SECRETKEY"; //"云 API 密钥 SecretKey";
+$region = "ap-beijing"; //设置一个默认的存储桶地域
+$cosClient = new Qcloud\Cos\Client(
+ array(
+ 'region' => $region,
+ 'schema' => 'https', //协议头部,默认为http
+ 'credentials'=> array(
+ 'secretId' => $secretId ,
+ 'secretKey' => $secretKey)));
+try {
+ $result = $cosClient->uploadPart(array(
+ 'Bucket' => 'examplebucket-125000000', //格式:BucketName-APPID
+ 'Key' => 'exampleobject',
+ 'Body' => 'string',
+ 'UploadId' => 'NWNhNDY0YzFfMmZiNTM1MGFfNTM2YV8xYjliMTg',
+ 'PartNumber' => 1,
+ 'ContentMD5' => 'string',
+ 'ContentLength' => 100,
+ ));
+ // 请求成功
+ print_r($result);
+} catch (\Exception $e) {
+ // 请求失败
+ echo($e);
+}
+
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/BucketStyleListener.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/BucketStyleListener.php
new file mode 100755
index 0000000..a7f29b8
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/BucketStyleListener.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Qcloud\Cos;
+
+use Guzzle\Common\Event;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Change from path style to host style, currently only host style is supported in cos.
+ */
+function endWith($haystack, $needle) {
+ $length = strlen($needle);
+ if($length == 0)
+ {
+ return true;
+ }
+ return (substr($haystack, -$length) === $needle);
+}
+class BucketStyleListener implements EventSubscriberInterface {
+
+ private $appId; // string: application id.
+ private $ip;
+ private $port;
+ private $ipport;
+ private $endpoint;
+
+ public function __construct($appId, $ip=null, $port=null, $endpoint=null) {
+ $this->appId = $appId;
+ $this->ip = $ip;
+ $this->port = $port;
+ $this->ipport = null;
+ $this->endpoint = $endpoint;
+ if ($ip != null) {
+ $this->ipport = $ip;
+ if ($port != null) {
+ $this->ipport = $ip.":".$port;
+ }
+ }
+ }
+
+ public static function getSubscribedEvents() {
+ return array('command.after_prepare' => array('onCommandAfterPrepare', -230));
+ }
+
+ /**
+ * Change from path style to host style.
+ * @param Event $event Event emitted.
+ */
+ public function onCommandAfterPrepare(Event $event) {
+
+ $command = $event['command'];
+ $bucket = $command['Bucket'];
+ $request = $command->getRequest();
+ if ($command->getName() == 'ListBuckets')
+ {
+ if ($this->ipport != null) {
+ $request->setHost($this->ipport);
+ $request->setHeader('Host', 'service.cos.myqcloud.com');
+ } else if ($this->endpoint != null) {
+ $request->setHost($this->endpoint);
+ $request->setHeader('Host', 'service.cos.myqcloud.com');
+ }
+ else {
+
+ $request->setHost('service.cos.myqcloud.com');
+ }
+ return ;
+ }
+ if ($key = $command['Key']) {
+ // Modify the command Key to account for the {/Key*} explosion into an array
+ if (is_array($key)) {
+ $command['Key'] = $key = implode('/', $key);
+ }
+ }
+ $request->setHeader('Date', gmdate('D, d M Y H:i:s T'));
+
+ $url_bucket = rawurlencode($bucket);
+ $request->setPath(preg_replace("#^/{$url_bucket}#", '', $request->getPath()));
+ if ($this->appId != null && endWith($bucket,'-'.$this->appId) == False)
+ {
+ $bucket = $bucket.'-'.$this->appId;
+ }
+ $request->getParams()->set('bucket', $bucket)->set('key', $key);
+
+ $realHost = $bucket. '.' . $request->getHost();
+ if($this->ipport != null) {
+ $request->setHost($this->ipport);
+ $request->setHeader('Host', $realHost);
+ } else {
+ if($this->endpoint != null) {
+ $tmp = $bucket. '.' . $this->endpoint;
+ $request->setHost($tmp);
+ } else {
+ $request->setHost($realHost);
+ }
+ }
+ if (!$bucket) {
+ $request->getParams()->set('cos.resource', '/');
+ } else {
+ // Bucket style needs a trailing slash
+ $request->getParams()->set(
+ 'cos.resource',
+ '/' . rawurlencode($bucket) . ($key ? ('/' . Client::encodeKey($key)) : '/')
+ );
+ }
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Client.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Client.php
new file mode 100755
index 0000000..53bb78d
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Client.php
@@ -0,0 +1,250 @@
+<?php
+
+namespace Qcloud\Cos;
+
+use Guzzle\Service\Description\Parameter;
+use Guzzle\Service\Description\ServiceDescription;
+use Guzzle\Service\Client as GSClient;
+use Guzzle\Common\Collection;
+use Guzzle\Http\EntityBody;
+use Guzzle\Http\Message\RequestInterface;
+use Qcloud\Cos\Signature;
+use Qcloud\Cos\TokenListener;
+
+class Client extends GSClient {
+ const VERSION = '1.3.3';
+
+ private $region; // string: region.
+ private $credentials;
+ private $appId; // string: application id.
+ private $secretId; // string: secret id.
+ private $secretKey; // string: secret key.
+ private $timeout; // int: timeout
+ private $connect_timeout; // int: connect_timeout
+ private $signature;
+ private $schema;
+ private $ip;
+ private $port;
+ private $config;
+ public function __construct($config) {
+ $this->config = $config;
+ $this->region = $config['region'];
+ $regionmap = array('cn-east'=>'ap-shanghai',
+ 'cn-south'=>'ap-guangzhou',
+ 'cn-north'=>'ap-beijing-1',
+ 'cn-south-2'=>'ap-guangzhou-2',
+ 'cn-southwest'=>'ap-chengdu',
+ 'sg'=>'ap-singapore',
+ 'tj'=>'ap-beijing-1',
+ 'bj'=>'ap-beijing',
+ 'sh'=>'ap-shanghai',
+ 'gz'=>'ap-guangzhou',
+ 'cd'=>'ap-chengdu',
+ 'sgp'=>'ap-singapore',);
+ $this->schema = isset($config['schema']) ? $config['schema'] : 'http';
+ $this->ip = isset($config['ip']) ? $config['ip'] : null;
+ $this->port = isset($config['port']) ? $config['port'] : null;
+ $this->endpoint = isset($config['endpoint']) ? $config['endpoint'] : null;
+ $this->region = isset($regionmap[$this->region]) ? $regionmap[$this->region] : $this->region;
+ $this->credentials = $config['credentials'];
+ $this->appId = isset($config['credentials']['appId']) ? $config['credentials']['appId'] : null;
+ $this->secretId = $config['credentials']['secretId'];
+ $this->secretKey = $config['credentials']['secretKey'];
+ $this->token = isset($config['credentials']['token']) ? $config['credentials']['token'] : null;
+ $this->timeout = isset($config['timeout']) ? $config['timeout'] : 3600;
+ $this->connect_timeout = isset($config['connect_timeout']) ? $config['connect_timeout'] : 3600;
+ $this->signature = new signature($this->secretId, $this->secretKey);
+ parent::__construct(
+ $this->schema.'://cos.' . $this->region . '.myqcloud.com/', // base url
+ array('request.options' => array('timeout' => $this->timeout, 'connect_timeout' => $this->connect_timeout),
+ )); // show curl verbose or not
+ $desc = ServiceDescription::factory(Service::getService());
+ $this->setDescription($desc);
+ $this->setUserAgent('cos-php-sdk-v5.' . Client::VERSION, true);
+ $this->addSubscriber(new ExceptionListener());
+ $this->addSubscriber(new Md5Listener($this->signature));
+ $this->addSubscriber(new TokenListener($this->token));
+ $this->addSubscriber(new SignatureListener($this->secretId, $this->secretKey));
+ $this->addSubscriber(new BucketStyleListener($this->appId, $this->ip, $this->port, $this->endpoint));
+ $this->addSubscriber(new UploadBodyListener(array('PutObject', 'UploadPart')));
+ }
+
+ public function __destruct() {
+ }
+
+ public function __call($method, $args) {
+ return parent::__call(ucfirst($method), $args);
+ }
+
+ public function createAuthorization(RequestInterface $request, $expires)
+ {
+ if ($request->getClient() !== $this) {
+ throw new InvalidArgumentException('The request object must be associated with the client. Use the '
+ . '$client->get(), $client->head(), $client->post(), $client->put(), etc. methods when passing in a '
+ . 'request object');
+ }
+ return $this->signature->createAuthorization($request, $expires);
+ }
+ public function createPresignedUrl(RequestInterface $request, $expires)
+ {
+ if ($request->getClient() !== $this) {
+ throw new InvalidArgumentException('The request object must be associated with the client. Use the '
+ . '$client->get(), $client->head(), $client->post(), $client->put(), etc. methods when passing in a '
+ . 'request object');
+ }
+ return $this->signature->createPresignedUrl($request, $expires);
+ }
+ public function getObjectUrl($bucket, $key, $expires = null, array $args = array())
+ {
+ $command = $this->getCommand('GetObject', $args + array('Bucket' => $bucket, 'Key' => $key));
+
+ if ($command->hasKey('Scheme')) {
+ $scheme = $command['Scheme'];
+ $request = $command->remove('Scheme')->prepare()->setScheme($scheme)->setPort(null);
+ } else {
+ $request = $command->prepare();
+ }
+
+ return $expires ? $this->createPresignedUrl($request, $expires) : $request->getUrl();
+ }
+ public function Upload($bucket, $key, $body, $options = array()) {
+ $body = EntityBody::factory($body);
+ $options = Collection::fromConfig(array_change_key_case($options), array(
+ 'min_part_size' => MultipartUpload::MIN_PART_SIZE,
+ 'params' => $options));
+ if ($body->getSize() < $options['min_part_size']) {
+ // Perform a simple PutObject operation
+ $rt = $this->putObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Body' => $body,
+ ) + $options['params']);
+
+ $rt['Location'] = $rt['ObjectURL'];
+ $rt['Location'] = ltrim($rt['Location'], $this->schema. "://");
+ unset($rt['ObjectURL']);
+ }
+ else {
+ $multipartUpload = new MultipartUpload($this, $body, $options['min_part_size'], array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Body' => $body,
+ ) + $options['params']);
+
+ $rt = $multipartUpload->performUploading();
+ unset($rt['Bucket']);
+ unset($rt['Key']);
+ }
+ return $rt;
+ }
+
+ public function resumeUpload($bucket, $key, $body, $uploadId, $options = array()) {
+ $body = EntityBody::factory($body);
+ $options = Collection::fromConfig(array_change_key_case($options), array(
+ 'min_part_size' => MultipartUpload::MIN_PART_SIZE,
+ 'params' => $options));
+ $multipartUpload = new MultipartUpload($this, $body, $options['min_part_size'], array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'Body' => $body,
+ 'UploadId' => $uploadId,
+ ) + $options['params']);
+
+ $rt = $multipartUpload->resumeUploading();
+ return $rt;
+ }
+
+ public function Copy($bucket, $key, $copysource, $options = array()) {
+
+ $options = Collection::fromConfig(array_change_key_case($options), array(
+ 'min_part_size' => Copy::MIN_PART_SIZE,
+ 'params' => $options));
+ $sourcelistdot = explode('.',$copysource);
+ $sourcelistline = explode('-',$sourcelistdot[0]);
+ $sourceappid = array_pop($sourcelistline);
+ $sourcebucket = implode('-', $sourcelistline);
+ $sourceregion = $sourcelistdot[2];
+ $sourcekey = substr(strstr($copysource,'/'),1);
+ $sourceversion = "";
+ $sourceconfig = $this->config;
+ $sourceconfig['region'] = $sourceregion;
+ $sourceconfig['credentials']['appId'] = $sourceappid;
+ $cosClient = new Client($sourceconfig);
+ if (!key_exists('VersionId',$options['params'])) {
+ $sourceversion = "";
+ }
+ else {
+ $sourceversion = $options['params']['VersionId'];
+ }
+ $rt = $cosClient->headObject(array('Bucket'=>$sourcebucket,
+ 'Key'=>$sourcekey,
+ 'VersionId'=>$sourceversion));
+ $contentlength =$rt['ContentLength'];
+
+ if ($contentlength < $options['min_part_size']) {
+ return $this->copyObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key,
+ 'CopySource' => $copysource."?versionId=".$sourceversion,
+ ) + $options['params']);
+ }
+ $copy = new Copy($this, $contentlength, $copysource."?versionId=".$sourceversion, $options['min_part_size'], array(
+ 'Bucket' => $bucket,
+ 'Key' => $key
+ ) + $options['params']);
+
+ return $copy->copy();
+ }
+
+ /**
+ * Determines whether or not a bucket exists by name
+ *
+ * @param string $bucket The name of the bucket
+ * @param bool $accept403 Set to true if 403s are acceptable
+ * @param array $options Additional options to add to the executed command
+ *
+ * @return bool
+ */
+ public function doesBucketExist($bucket, $accept403 = true, array $options = array())
+ {
+ try {
+ $this->HeadBucket(array(
+ 'Bucket' => $bucket));
+ return True;
+ }catch (\Exception $e){
+ return False;
+ }
+ }
+
+ /**
+ * Determines whether or not an object exists by name
+ *
+ * @param string $bucket The name of the bucket
+ * @param string $key The key of the object
+ * @param array $options Additional options to add to the executed command
+ *
+ * @return bool
+ */
+ public function doesObjectExist($bucket, $key, array $options = array())
+ {
+ try {
+ $this->HeadObject(array(
+ 'Bucket' => $bucket,
+ 'Key' => $key));
+ return True;
+ }catch (\Exception $e){
+ return False;
+ }
+ }
+ public static function encodeKey($key) {
+// return $key;
+ return str_replace('%2F', '/', rawurlencode($key));
+ }
+
+ public static function explodeKey($key) {
+ // Remove a leading slash if one is found
+ return explode('/', $key && $key[0] == '/' ? substr($key, 1) : $key);
+// return $key;
+// return ltrim($key, "/");
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Command.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Command.php
new file mode 100755
index 0000000..647bb52
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Command.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Qcloud\Cos;
+
+use Guzzle\Service\Command\OperationCommand;
+use Guzzle\Service\Resource\Model;
+use Guzzle\Common\Event;
+
+/**
+ * Adds functionality to Qcloud Cos commands:
+ * - Adds the PutObject URL to a response
+ * - Allows creating a Pre-signed URL from any command
+ */
+class Command extends OperationCommand {
+ /**
+ * Create a pre-signed URL for the operation
+ *
+ * @param int|string $expires The Unix timestamp to expire at or a string that can be evaluated by strtotime
+ *
+ * @return string
+ */
+ public function createPresignedUrl($expires)
+ {
+ return $this->client->createPresignedUrl($this->prepare(), $expires);
+ }
+ public function createAuthorization($expires)
+ {
+ return $this->client->createAuthorization($this->prepare(), $expires);
+ }
+
+ protected function process() {
+ parent::process();
+ // Set the GetObject URL if using the PutObject operation
+ if ($this->result instanceof Model && $this->getName() == 'PutObject') {
+ $request = $this->getRequest();;
+ $this->result->set('ObjectURL', $request->getUrl());
+ }
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Copy.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Copy.php
new file mode 100755
index 0000000..06d464a
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Copy.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Qcloud\Cos;
+
+use Guzzle\Http\ReadLimitEntityBody;
+
+
+class Copy {
+ /**
+ * const var: part size from 5MB to 5GB, and max parts of 10000 are allowed for each upload.
+ */
+ const MIN_PART_SIZE = 5242880;
+ const MAX_PART_SIZE = 5368709120;
+ const MAX_PARTS = 10000;
+
+ private $client;
+ private $source;
+ private $options;
+ private $partSize;
+ private $size;
+
+ public function __construct($client, $contentlength, $source, $minPartSize, $options = array()) {
+ $this->client = $client;
+ $this->source = $source;
+ $this->options = $options;
+ $this->size = $contentlength;
+ $this->partSize = $this->calculatePartSize($minPartSize);
+ $this->concurrency = isset($options['concurrency']) ? $options['concurrency'] : 10;
+ $this->retry = isset($options['retry']) ? $options['retry'] : 5;
+ }
+ public function copy() {
+ $uploadId= $this->initiateMultipartUpload();
+ for ($i = 0; $i < 5; $i += 1) {
+ $rt = $this->uploadParts($uploadId);
+ if ($rt == 0) {
+ break;
+ }
+ sleep(1 << $i);
+ }
+ return $this->client->completeMultipartUpload(array(
+ 'Bucket' => $this->options['Bucket'],
+ 'Key' => $this->options['Key'],
+ 'UploadId' => $uploadId,
+ 'Parts' => $this->parts));
+
+ }
+ public function uploadParts($uploadId) {
+ $commands = array();
+ $offset = 0;
+ $partNumber = 1;
+ $partSize = $this->partSize;
+ $finishedNum = 0;
+ $this->parts = array();
+ for (;;) {
+
+ if ($offset + $partSize >= $this->size)
+ {
+ $partSize = $this->size - $offset;
+ }
+ $params = array(
+ 'Bucket' => $this->options['Bucket'],
+ 'Key' => $this->options['Key'],
+ 'UploadId' => $uploadId,
+ 'PartNumber' => $partNumber,
+ 'CopySource'=> $this->source,
+ 'CopySourceRange' => 'bytes='.((string)$offset).'-'.(string)($offset+$partSize - 1),
+ );
+ if(!isset($parts[$partNumber])) {
+ $commands[] = $this->client->getCommand('UploadPartCopy', $params);
+ }
+ if ($partNumber % $this->concurrency == 0) {
+ $this->client->execute($commands);
+ $commands = array();
+ }
+ ++$partNumber;
+ $offset += $partSize;
+ if ($this->size == $offset)
+ {
+ break;
+ }
+ }
+ if (!empty($commands)) {
+ $this->client->execute($commands);
+ }
+ try {
+ $marker = 0;
+ $finishedNum = 1;
+ while (true) {
+ $rt = $this->client->listParts(array(
+ 'Bucket' => $this->options['Bucket'],
+ 'Key' => $this->options['Key'],
+ 'PartNumberMarker' => $marker,
+ 'MaxParts' => 1000,
+ 'UploadId' => $uploadId));
+ if (!empty($rt['Parts'])) {
+ foreach ($rt['Parts'] as $part) {
+ $part = array('PartNumber' => $finishedNum, 'ETag' => $part['ETag']);
+ $this->parts[$finishedNum] = $part;
+ $finishedNum++;
+ }
+ }
+ $marker = $rt['NextPartNumberMarker'];
+ if (!$rt['IsTruncated']) {
+ break;
+ }
+ }
+ } catch (\Exception $e) {
+ echo($e);
+ }
+ if ($finishedNum == $partNumber) {
+ return 0;
+ } else {
+ return -1;
+ }
+
+ }
+
+
+ private function calculatePartSize($minPartSize)
+ {
+ $partSize = intval(ceil(($this->size / self::MAX_PARTS)));
+ $partSize = max($minPartSize, $partSize);
+ $partSize = min($partSize, self::MAX_PART_SIZE);
+ $partSize = max($partSize, self::MIN_PART_SIZE);
+
+ return $partSize;
+ }
+
+ private function initiateMultipartUpload() {
+ $result = $this->client->createMultipartUpload($this->options);
+ return $result['UploadId'];
+ }
+
+}
+function partUploadCopy($client, $params) {
+ $rt = $client->uploadPartCopy($params);
+// $part = array('PartNumber' => $params['PartNumber'], 'ETag' => $rt['ETag']);
+ $rt['PartNumber'] = $params['PartNumber'];
+ return $rt;
+}
\ No newline at end of file
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/BucketAlreadyExistsException.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/BucketAlreadyExistsException.php
new file mode 100755
index 0000000..08de5ac
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/BucketAlreadyExistsException.php
@@ -0,0 +1,5 @@
+<?php
+
+namespace Qcloud\Cos\Exception;
+
+class BucketAlreadyExistsException extends CosException {}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/BucketNotEmptyException.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/BucketNotEmptyException.php
new file mode 100755
index 0000000..867d2b0
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/BucketNotEmptyException.php
@@ -0,0 +1,6 @@
+<?php
+
+namespace Qcloud\Cos\Exception;
+
+// The bucket you tried to delete is not empty.
+class BucketNotEmptyException extends CosException {}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/CosException.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/CosException.php
new file mode 100755
index 0000000..bcf0628
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/CosException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Qcloud\Cos\Exception;
+
+use Qcloud\Cos\Exception\ServiceResponseException;
+
+class CosException extends ServiceResponseException {}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/CurlException.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/CurlException.php
new file mode 100755
index 0000000..67e8469
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/CurlException.php
@@ -0,0 +1,5 @@
+<?php
+
+namespace Qcloud\Cos\Exception;
+
+class CurlException extends CosException {}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/InvalidArgumentException.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/InvalidArgumentException.php
new file mode 100755
index 0000000..b747131
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/InvalidArgumentException.php
@@ -0,0 +1,5 @@
+<?php
+
+namespace Qcloud\Cos\Exception;
+
+class InvalidArgumentException extends CosException {}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/NoSuchBucketException.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/NoSuchBucketException.php
new file mode 100755
index 0000000..d05a9b9
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/NoSuchBucketException.php
@@ -0,0 +1,6 @@
+<?php
+
+namespace Qcloud\Cos\Exception;
+
+// The specified bucket does not exist.
+class NoSuchBucketException extends CosException {}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/NoSuchKeyException.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/NoSuchKeyException.php
new file mode 100755
index 0000000..1e5987c
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/NoSuchKeyException.php
@@ -0,0 +1,6 @@
+<?php
+
+namespace Qcloud\Cos\Exception;
+
+// The specified key does not exist.
+class NoSuchKeyException extends CosException {}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/NoSuchUploadException.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/NoSuchUploadException.php
new file mode 100755
index 0000000..ca4ccb9
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/NoSuchUploadException.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Qcloud\Cos\Exception;
+
+/**
+ * The specified multipart upload does not exist.
+ */
+class NoSuchUploadException extends CosException {}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/ServiceResponseException.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/ServiceResponseException.php
new file mode 100755
index 0000000..25718ef
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/ServiceResponseException.php
@@ -0,0 +1,189 @@
+<?php
+
+namespace Qcloud\Cos\Exception;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+class ServiceResponseException extends \RuntimeException {
+
+ /**
+ * @var Response Response
+ */
+ protected $response;
+
+ /**
+ * @var RequestInterface Request
+ */
+ protected $request;
+
+ /**
+ * @var string Request ID
+ */
+ protected $requestId;
+
+ /**
+ * @var string Exception type (client / server)
+ */
+ protected $exceptionType;
+
+ /**
+ * @var string Exception code
+ */
+ protected $exceptionCode;
+
+ /**
+ * Set the exception code
+ *
+ * @param string $code Exception code
+ */
+ public function setExceptionCode($code) {
+ $this->exceptionCode = $code;
+ }
+
+ /**
+ * Get the exception code
+ *
+ * @return string|null
+ */
+ public function getExceptionCode() {
+ return $this->exceptionCode;
+ }
+
+ /**
+ * Set the exception type
+ *
+ * @param string $type Exception type
+ */
+ public function setExceptionType($type) {
+ $this->exceptionType = $type;
+ }
+
+ /**
+ * Get the exception type (one of client or server)
+ *
+ * @return string|null
+ */
+ public function getExceptionType() {
+ return $this->exceptionType;
+ }
+
+ /**
+ * Set the request ID
+ *
+ * @param string $id Request ID
+ */
+ public function setRequestId($id) {
+ $this->requestId = $id;
+ }
+
+ /**
+ * Get the Request ID
+ *
+ * @return string|null
+ */
+ public function getRequestId() {
+ return $this->requestId;
+ }
+
+ /**
+ * Set the associated response
+ *
+ * @param Response $response Response
+ */
+ public function setResponse(Response $response) {
+ $this->response = $response;
+ }
+
+ /**
+ * Get the associated response object
+ *
+ * @return Response|null
+ */
+ public function getResponse() {
+ return $this->response;
+ }
+
+ /**
+ * Set the associated request
+ *
+ * @param RequestInterface $request
+ */
+ public function setRequest(RequestInterface $request) {
+ $this->request = $request;
+ }
+
+ /**
+ * Get the associated request object
+ *
+ * @return RequestInterface|null
+ */
+ public function getRequest() {
+ return $this->request;
+ }
+
+ /**
+ * Get the status code of the response
+ *
+ * @return int|null
+ */
+ public function getStatusCode() {
+ return $this->response ? $this->response->getStatusCode() : null;
+ }
+
+ /**
+ * Cast to a string
+ *
+ * @return string
+ */
+ public function __toString() {
+ $message = get_class($this) . ': '
+ . 'Cos Error Code: ' . $this->getExceptionCode() . ', '
+ . 'Status Code: ' . $this->getStatusCode() . ', '
+ . 'Cos Request ID: ' . $this->getRequestId() . ', '
+ . 'Cos Error Type: ' . $this->getExceptionType() . ', '
+ . 'Cos Error Message: ' . $this->getMessage();
+
+ // Add the User-Agent if available
+ if ($this->request) {
+ $message .= ', ' . 'User-Agent: ' . $this->request->getHeader('User-Agent');
+ }
+
+ return $message;
+ }
+
+ /**
+ * Get the request ID of the error. This value is only present if a
+ * response was received, and is not present in the event of a networking
+ * error.
+ *
+ * Same as `getRequestId()` method, but matches the interface for SDKv3.
+ *
+ * @return string|null Returns null if no response was received
+ */
+ public function getCosRequestId() {
+ return $this->requestId;
+ }
+
+ /**
+ * Get the Cos error type.
+ *
+ * Same as `getExceptionType()` method, but matches the interface for SDKv3.
+ *
+ * @return string|null Returns null if no response was received
+ */
+ public function getCosErrorType() {
+ return $this->exceptionType;
+ }
+
+ /**
+ * Get the Cos error code.
+ *
+ * Same as `getExceptionCode()` method, but matches the interface for SDKv3.
+ *
+ * @return string|null Returns null if no response was received
+ */
+ public function getCosErrorCode() {
+ return $this->exceptionCode;
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionListener.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionListener.php
new file mode 100755
index 0000000..a7c0b69
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionListener.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Qcloud\Cos;
+
+use Qcloud\Cos\Exception\ServiceResponseException;
+use Qcloud\Cos\Exception\NoSuchKeyException;
+
+use Guzzle\Common\Event;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Converts generic Guzzle response exceptions into cos specific exceptions
+ */
+class ExceptionListener implements EventSubscriberInterface {
+ protected $parser;
+ protected $defaultException;
+
+ public function __construct() {
+ $this->parser = new ExceptionParser();
+ $this->defaultException = 'Qcloud\Cos\Exception\ServiceResponseException';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents() {
+ return array('request.error' => array('onRequestError', -1));
+ }
+
+ /**
+ * Throws a more meaningful request exception if available
+ *
+ * @param Event $event Event emitted
+ */
+ public function onRequestError(Event $event) {
+ $e = $this->fromResponse($event['request'], $event['response']);
+ $event->stopPropagation();
+ throw $e;
+ }
+
+ public function fromResponse(RequestInterface $request, Response $response) {
+ $parts = $this->parser->parse($request, $response);
+
+ $className = 'Qcloud\\Cos\\Exception\\' . $parts['code'];
+ if (substr($className, -9) !== 'Exception') {
+ $className .= 'Exception';
+ }
+
+ $className = class_exists($className) ? $className : $this->defaultException;
+
+ return $this->createException($className, $request, $response, $parts);
+ }
+
+ protected function createException($className, RequestInterface $request, Response $response, array $parts) {
+ $class = new $className($parts['message']);
+
+ if ($class instanceof ServiceResponseException) {
+ $class->setExceptionCode($parts['code']);
+ $class->setExceptionType($parts['type']);
+ $class->setResponse($response);
+ $class->setRequest($request);
+ $class->setRequestId($parts['request_id']);
+ }
+
+ return $class;
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionParser.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionParser.php
new file mode 100755
index 0000000..be65eed
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionParser.php
@@ -0,0 +1,109 @@
+<?php
+
+namespace Qcloud\Cos;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Parses default XML exception responses
+ */
+class ExceptionParser {
+
+ public function parse(RequestInterface $request, Response $response) {
+ $data = array(
+ 'code' => null,
+ 'message' => null,
+ 'type' => $response->isClientError() ? 'client' : 'server',
+ 'request_id' => null,
+ 'parsed' => null
+ );
+
+ $body = $response->getBody(true);
+
+ if (!$body) {
+ $this->parseHeaders($request, $response, $data);
+ return $data;
+ }
+
+ try {
+ $xml = new \SimpleXMLElement(utf8_encode($body));
+ $this->parseBody($xml, $data);
+ return $data;
+ } catch (\Exception $e) {
+ $data['code'] = 'PhpInternalXmlParseError';
+ $data['message'] = 'A non-XML response was received';
+ return $data;
+ }
+ }
+
+ /**
+ * Parses additional exception information from the response headers
+ *
+ * @param RequestInterface $request Request that was issued
+ * @param Response $response The response from the request
+ * @param array $data The current set of exception data
+ */
+ protected function parseHeaders(RequestInterface $request, Response $response, array &$data) {
+ $data['message'] = $response->getStatusCode() . ' ' . $response->getReasonPhrase();
+ if ($requestId = $response->getHeader('x-cos-request-id')) {
+ $data['request_id'] = $requestId;
+ $data['message'] .= " (Request-ID: $requestId)";
+ }
+
+ // Get the request
+ $status = $response->getStatusCode();
+ $method = $request->getMethod();
+
+ // Attempt to determine code for 403s and 404s
+ if ($status === 403) {
+ $data['code'] = 'AccessDenied';
+ } elseif ($method === 'HEAD' && $status === 404) {
+ $path = explode('/', trim($request->getPath(), '/'));
+ $host = explode('.', $request->getHost());
+ $bucket = (count($host) >= 4) ? $host[0] : array_shift($path);
+ $object = array_shift($path);
+
+ if ($bucket && $object) {
+ $data['code'] = 'NoSuchKey';
+ } elseif ($bucket) {
+ $data['code'] = 'NoSuchBucket';
+ }
+ }
+ }
+
+ /**
+ * Parses additional exception information from the response body
+ *
+ * @param \SimpleXMLElement $body The response body as XML
+ * @param array $data The current set of exception data
+ */
+ protected function parseBody(\SimpleXMLElement $body, array &$data) {
+ $data['parsed'] = $body;
+
+ $namespaces = $body->getDocNamespaces();
+ if (isset($namespaces[''])) {
+ // Account for the default namespace being defined and PHP not being able to handle it :(
+ $body->registerXPathNamespace('ns', $namespaces['']);
+ $prefix = 'ns:';
+ } else {
+ $prefix = '';
+ }
+
+ if ($tempXml = $body->xpath("//{$prefix}Code[1]")) {
+ $data['code'] = (string) $tempXml[0];
+ }
+
+ if ($tempXml = $body->xpath("//{$prefix}Message[1]")) {
+ $data['message'] = (string) $tempXml[0];
+ }
+
+ $tempXml = $body->xpath("//{$prefix}RequestId[1]");
+ if (empty($tempXml)) {
+ $tempXml = $body->xpath("//{$prefix}RequestID[1]");
+ }
+ if (isset($tempXml[0])) {
+ $data['request_id'] = (string) $tempXml[0];
+ }
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Md5Listener.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Md5Listener.php
new file mode 100755
index 0000000..9595600
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Md5Listener.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Qcloud\Cos;
+
+use Guzzle\Common\Event;
+use Guzzle\Service\Command\CommandInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Adds required and optional Content-MD5 headers
+ */
+class Md5Listener implements EventSubscriberInterface
+{
+ /** @var S3SignatureInterface */
+ private $signature;
+
+ public static function getSubscribedEvents()
+ {
+ return array('command.after_prepare' => 'onCommandAfterPrepare');
+ }
+
+ public function __construct(Signature $signature)
+ {
+ $this->signature = $signature;
+ }
+
+ public function onCommandAfterPrepare(Event $event)
+ {
+ $command = $event['command'];
+ $operation = $command->getOperation();
+
+ if ($operation->getData('contentMd5')) {
+ // Add the MD5 if it is required for all signers
+ $this->addMd5($command);
+ } elseif ($operation->hasParam('ContentMD5')) {
+ $value = $command['ContentMD5'];
+ // Add a computed MD5 if the parameter is set to true or if
+ // not using Signature V4 and the value is not set (null).
+ if ($value === true ||
+ ($value === null && !($this->signature instanceof SignatureV4))
+ ) {
+ $this->addMd5($command);
+ }
+ }
+ }
+
+ private function addMd5(CommandInterface $command)
+ {
+ $request = $command->getRequest();
+ $body = $request->getBody();
+ if ($body && $body->getSize() > 0) {
+ if (false !== ($md5 = $body->getContentMd5(true, true))) {
+ $request->setHeader('Content-MD5', $md5);
+ }
+ }
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/MultipartUpload.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/MultipartUpload.php
new file mode 100755
index 0000000..30a942d
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/MultipartUpload.php
@@ -0,0 +1,132 @@
+<?php
+
+namespace Qcloud\Cos;
+
+use Guzzle\Http\ReadLimitEntityBody;
+use Qcloud\Cos\Exception\CosException;
+class MultipartUpload {
+ /**
+ * const var: part size from 5MB to 5GB, and max parts of 10000 are allowed for each upload.
+ */
+ const MIN_PART_SIZE = 5242880;
+ const MAX_PART_SIZE = 5368709120;
+ const MAX_PARTS = 10000;
+
+ private $client;
+ private $source;
+ private $options;
+ private $partSize;
+
+ public function __construct($client, $source, $minPartSize, $options = array()) {
+ $this->client = $client;
+ $this->source = $source;
+ $this->options = $options;
+ $this->partSize = $this->calculatePartSize($minPartSize);
+ }
+
+ public function performUploading() {
+ $uploadId = $this->initiateMultipartUpload();
+
+ $partNumber = 1;
+ $parts = array();
+ for (;;) {
+ if ($this->source->isConsumed()) {
+ break;
+ }
+
+ $body = new ReadLimitEntityBody($this->source, $this->partSize, $this->source->ftell());
+ if ($body->getContentLength() == 0) {
+ break;
+ }
+ $result = $this->client->uploadPart(array(
+ 'Bucket' => $this->options['Bucket'],
+ 'Key' => $this->options['Key'],
+ 'Body' => $body,
+ 'UploadId' => $uploadId,
+ 'PartNumber' => $partNumber));
+ if (md5($body) != substr($result['ETag'], 1, -1)){
+ throw new CosException("ETag check inconsistency");
+ }
+ $part = array('PartNumber' => $partNumber, 'ETag' => $result['ETag']);
+ array_push($parts, $part);
+ ++$partNumber;
+ }
+ try {
+ $rt = $this->client->completeMultipartUpload(array(
+ 'Bucket' => $this->options['Bucket'],
+ 'Key' => $this->options['Key'],
+ 'UploadId' => $uploadId,
+ 'Parts' => $parts));
+ } catch(\Exception $e){
+ throw $e;
+ }
+ return $rt;
+ }
+
+ public function resumeUploading() {
+ $uploadId = $this->options['UploadId'];
+ $rt = $this->client->ListParts(
+ array('UploadId' => $uploadId,
+ 'Bucket'=>$this->options['Bucket'],
+ 'Key'=>$this->options['Key']));
+ $parts = array();
+ $offset = $this->partSize;
+ if (count($rt['Parts']) > 0) {
+ foreach ($rt['Parts'] as $part) {
+ $parts[$part['PartNumber'] - 1] = array('PartNumber' => $part['PartNumber'], 'ETag' => $part['ETag']);
+ }
+ }
+ for ($partNumber = 1;;++$partNumber,$offset+=$body->getContentLength()) {
+ if ($this->source->isConsumed()) {
+ break;
+ }
+
+ $body = new ReadLimitEntityBody($this->source, $this->partSize, $this->source->ftell());
+ if ($body->getContentLength() == 0) {
+ break;
+ }
+
+
+ if (array_key_exists($partNumber-1,$parts)){
+
+ if (md5($body) != substr($parts[$partNumber-1]['ETag'], 1, -1)){
+ throw new CosException("ETag check inconsistency");
+ }
+ $body->setOffset($offset);
+ continue;
+ }
+
+ $result = $this->client->uploadPart(array(
+ 'Bucket' => $this->options['Bucket'],
+ 'Key' => $this->options['Key'],
+ 'Body' => $body,
+ 'UploadId' => $uploadId,
+ 'PartNumber' => $partNumber));
+ if (md5($body) != substr($result['ETag'], 1, -1)){
+ throw new CosException("ETag check inconsistency");
+ }
+ $parts[$partNumber-1] = array('PartNumber' => $partNumber, 'ETag' => $result['ETag']);
+
+ }
+ $rt = $this->client->completeMultipartUpload(array(
+ 'Bucket' => $this->options['Bucket'],
+ 'Key' => $this->options['Key'],
+ 'UploadId' => $uploadId,
+ 'Parts' => $parts));
+ return $rt;
+ }
+
+ private function calculatePartSize($minPartSize) {
+ $partSize = intval(ceil(($this->source->getContentLength() / self::MAX_PARTS)));
+ $partSize = max($minPartSize, $partSize);
+ $partSize = min($partSize, self::MAX_PART_SIZE);
+ $partSize = max($partSize, self::MIN_PART_SIZE);
+
+ return $partSize;
+ }
+
+ private function initiateMultipartUpload() {
+ $result = $this->client->createMultipartUpload($this->options);
+ return $result['UploadId'];
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Service.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Service.php
new file mode 100755
index 0000000..75037c6
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Service.php
@@ -0,0 +1,4360 @@
+<?php
+
+namespace Qcloud\Cos;
+
+// http://guzzle3.readthedocs.io/webservice-client/guzzle-service-descriptions.html
+class Service {
+ public static function getService() {
+ return array(
+ 'name' => 'Cos Service',
+ 'apiVersion' => 'V5',
+ 'description' => 'Cos V5 API Service',
+
+ 'operations' => array(
+ /**
+ 舍弃一个分块上传且删除已上传的分片块的方法.
+
+ COS 支持舍弃一个分块上传且删除已上传的分片块. 注意,已上传但是未终止的分片块会占用存储空间进 而产生存储费用.因此,建议及时完成分块上传 或者舍弃分块上传.
+
+ 关于分块上传的具体描述,请查看 https://cloud.tencent.com/document/product/436/14112.
+
+ 关于舍弃一个分块上传且删除已上传的分片块接口的描述,请查看 https://cloud.tencent.com/document/product/436/7740.
+
+ cos php SDK 中舍弃一个分块上传且删除已上传的分片块请求的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 AbortMultipfartUpload 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,则操作成功。
+ */
+ 'AbortMultipartUpload' => array(
+ 'httpMethod' => 'DELETE',
+ 'uri' => '/{Bucket}{/Key*}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'AbortMultipartUploadOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri'),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')),
+ 'UploadId' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'uploadId')),
+ 'errorResponses' => array(
+ array(
+ 'reason' => 'The specified multipart upload does not exist.',
+ 'class' => 'NoSuchUploadException'))),
+ /**
+ 创建存储桶(Bucket)的方法.
+
+ 在开始使用 COS 时,需要在指定的账号下先创建一个 Bucket 以便于对象的使用和管理. 并指定 Bucket 所属的地域.创建 Bucket 的用户默认成为 Bucket 的持有者.若创建 Bucket 时没有指定访问权限,则默认 为私有读写(private)权限.
+
+ 可用地域,可以查看https://cloud.tencent.com/document/product/436/6224.
+
+ 关于创建 Bucket 描述,请查看 https://cloud.tencent.com/document/product/436/14106.
+
+ 关于创建存储桶(Bucket)接口的具体 描述,请查看 https://cloud.tencent.com/document/product/436/7738.
+
+ cos php SDK 中创建 Bucket的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 CreateBucket 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,则创建成功。
+
+ 示例:
+ $result = $cosClient->createBucket(array('Bucket' => 'testbucket-1252448703'));
+ */
+ 'CreateBucket' => array(
+ 'httpMethod' => 'PUT',
+ 'uri' => '/{Bucket}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'CreateBucketOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'CreateBucketConfiguration')),
+ 'parameters' => array(
+ 'ACL' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-acl'),
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri')),
+ 'errorResponses' => array(
+ array(
+ 'reason' => 'The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again.',
+ 'class' => 'BucketAlreadyExistsException'))),
+ /**
+ 完成整个分块上传的方法.
+
+ 当使用分块上传(uploadPart(UploadPartRequest))完对象的所有块以后,必须调用该 completeMultiUpload(CompleteMultiUploadRequest) 或者 completeMultiUploadAsync(CompleteMultiUploadRequest, CosXmlResultListener) 来完成整个文件的分块上传.且在该请求的 Body 中需要给出每一个块的 PartNumber 和 ETag,用来校验块的准 确性.
+
+ 分块上传适合于在弱网络或高带宽环境下上传较大的对象.SDK 支持自行切分对象并分别调用uploadPart(UploadPartRequest)上传各 个分块.
+
+ 关于分块上传的描述,请查看 https://cloud.tencent.com/document/product/436/14112.
+
+ 关于完成整个分片上传接口的描述,请查看 https://cloud.tencent.com/document/product/436/7742.
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 CompleteMultipartUpload 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,则操作成功。
+
+ */
+ 'CompleteMultipartUpload' => array(
+ 'httpMethod' => 'POST',
+ 'uri' => '/{Bucket}{/Key*}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'CompleteMultipartUploadOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'CompleteMultipartUpload')),
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri'),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')),
+ 'Parts' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'data' => array(
+ 'xmlFlattened' => true),
+ 'items' => array(
+ 'name' => 'CompletedPart',
+ 'type' => 'object',
+ 'sentAs' => 'Part',
+ 'properties' => array(
+ 'ETag' => array(
+ 'type' => 'string'),
+ 'PartNumber' => array(
+ 'type' => 'numeric')))),
+ 'UploadId' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'uploadId'),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml'))),
+ 'CreateMultipartUpload' => array(
+ 'httpMethod' => 'POST',
+ 'uri' => '/{Bucket}{/Key*}?uploads',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'CreateMultipartUploadOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'CreateMultipartUploadRequest')),
+ 'parameters' => array(
+ 'ACL' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-acl',
+ ),
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'CacheControl' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Cache-Control',
+ ),
+ 'ContentDisposition' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Disposition',
+ ),
+ 'ContentEncoding' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Encoding',
+ ),
+ 'ContentLanguage' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Language',
+ ),
+ 'ContentType' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Type',
+ ),
+ 'Expires' => array(
+ 'type' => array(
+ 'object',
+ 'string',
+ 'integer',
+ ),
+ 'format' => 'date-time-http',
+ 'location' => 'header',
+ ),
+ 'GrantFullControl' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-full-control',
+ ),
+ 'GrantRead' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-read',
+ ),
+ 'GrantReadACP' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-read-acp',
+ ),
+ 'GrantWriteACP' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-write-acp',
+ ),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')
+ ),
+ 'Metadata' => array(
+ 'type' => 'object',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-meta-',
+ 'additionalProperties' => array(
+ 'type' => 'string',
+ ),
+ ),
+ 'ServerSideEncryption' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption',
+ ),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-storage-class',
+ ),
+ 'WebsiteRedirectLocation' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-website-redirect-location',
+ ),
+ 'SSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm',
+ ),
+ 'SSECustomerKey' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key',
+ ),
+ 'SSECustomerKeyMD5' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5',
+ ),
+ 'SSEKMSKeyId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id',
+ ),
+ 'RequestPayer' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-payer',
+ ),
+ 'ACP' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ ),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml',
+ ))),
+ 'CopyObject' => array(
+ 'httpMethod' => 'PUT',
+ 'uri' => '/{Bucket}{/Key*}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'CopyObjectOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'CopyObjectRequest',
+ ),
+ ),
+ 'parameters' => array(
+ 'ACL' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-acl',
+ ),
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'CacheControl' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Cache-Control',
+ ),
+ 'ContentDisposition' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Disposition',
+ ),
+ 'ContentEncoding' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Encoding',
+ ),
+ 'ContentLanguage' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Language',
+ ),
+ 'ContentType' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Type',
+ ),
+ 'CopySource' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source',
+ ),
+ 'CopySourceIfMatch' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-if-match',
+ ),
+ 'CopySourceIfModifiedSince' => array(
+ 'type' => array(
+ 'object',
+ 'string',
+ 'integer',
+ ),
+ 'format' => 'date-time-http',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-if-modified-since',
+ ),
+ 'CopySourceIfNoneMatch' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-if-none-match',
+ ),
+ 'CopySourceIfUnmodifiedSince' => array(
+ 'type' => array(
+ 'object',
+ 'string',
+ 'integer',
+ ),
+ 'format' => 'date-time-http',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-if-unmodified-since',
+ ),
+ 'Expires' => array(
+ 'type' => array(
+ 'object',
+ 'string',
+ 'integer',
+ ),
+ 'format' => 'date-time-http',
+ 'location' => 'header',
+ ),
+ 'GrantFullControl' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-full-control',
+ ),
+ 'GrantRead' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-read',
+ ),
+ 'GrantReadACP' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-read-acp',
+ ),
+ 'GrantWriteACP' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-write-acp',
+ ),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')
+ ),
+ 'Metadata' => array(
+ 'type' => 'object',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-meta-',
+ 'additionalProperties' => array(
+ 'type' => 'string',
+ ),
+ ),
+ 'MetadataDirective' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-metadata-directive',
+ ),
+ 'ServerSideEncryption' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption',
+ ),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-storage-class',
+ ),
+ 'WebsiteRedirectLocation' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-website-redirect-location',
+ ),
+ 'SSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm',
+ ),
+ 'SSECustomerKey' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key',
+ ),
+ 'CopySourceSSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-server-side-encryption-customer-algorithm',
+ ),
+ 'CopySourceSSECustomerKey' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-server-side-encryption-customer-key',
+ ),
+ 'CopySourceSSECustomerKeyMD5' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-server-side-encryption-customer-key-MD5',
+ ),
+ 'RequestPayer' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-payer',
+ ),
+ 'ACP' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ ),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml',
+ ),
+ ),
+ 'errorResponses' => array(
+ array(
+ 'reason' => 'The source object of the COPY operation is not in the active tier.',
+ 'class' => 'ObjectNotInActiveTierErrorException',
+ ),
+ ),
+ ),
+ /**
+ 删除存储桶 (Bucket)的方法.
+
+ COS 目前仅支持删除已经清空的 Bucket,如果 Bucket 中仍有对象,将会删除失败. 因此,在执行删除 Bucket 前,需确保 Bucket 内已经没有对象. 删除 Bucket 时,还需要确保操作的身份已被授权该操作,并确认 传入了正确的存储桶名称和地域参数, 请参阅 putBucket(PutBucketRequest).
+
+ 关于删除 Bucket 的描述,请查看 https://cloud.tencent.com/document/product/436/14105.
+
+ 关于删除 Bucket 接口的具体描述,请查看https://cloud.tencent.com/document/product/436/7732.
+
+ cos php SDK 中删除 Bucket 的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 DeleteBucket 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,删除成功。
+
+ 示例:
+ $result = $cosClient->deleteBucket(array(
+ 'Bucket' => 'testbucket-1252448703'));
+ print_r($result);
+ */
+ 'DeleteBucket' => array(
+ 'httpMethod' => 'DELETE',
+ 'uri' => '/{Bucket}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'DeleteBucketOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri'))),
+ /**
+ 删除跨域访问配置信息的方法.
+
+ 若是 Bucket 不需要支持跨域访问配置,可以调用此接口删除已配置的跨域访问信息. 跨域访问配置可以通过 putBucketCORS(PutBucketCORSRequest) 或者 putBucketCORSAsync(PutBucketCORSRequest, CosXmlResultListener) 方法来开启 Bucket 的跨域访问 支持.
+
+ 关于删除跨域访问配置信息接口的具体描述,请查看https://cloud.tencent.com/document/product/436/8283.
+
+ cos php SDK 中删除跨域访问配置信息的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 DeleteBucketCORS 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,删除成功。
+
+ 示例:
+ $result = $cosClient->deleteBucketCors(array(
+ // Bucket is required
+ 'Bucket' => 'testbucket-1252448703',
+ ));
+ */
+ 'DeleteBucketCors' => array(
+ 'httpMethod' => 'DELETE',
+ 'uri' => '/{Bucket}?cors',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'DeleteBucketCorsOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ ),
+ ),
+ /**
+ 删除 COS 上单个对象的方法.
+
+ COS 支持直接删除一个或多个对象,当仅需要删除一个对象时,只需要提供对象的名称(即对象键)即可.
+
+ 关于删除 COS 上单个对象的具体描述,请查看 https://cloud.tencent.com/document/product/436/14119.
+
+ 关于删除 COS 上单个对象接口的具体描述,请查看 https://cloud.tencent.com/document/product/436/7743.
+
+ cos php SDK 中删除 COS 上单个对象请求的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 DeleteObject 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,则删除成功。
+
+ 示例:
+ $result = $cosClient->deleteObject(array(
+ 'Bucket' => 'testbucket-1252448703',
+ 'Key' => '111.txt',
+ 'VersionId' => 'string'));
+ */
+ 'DeleteObject' => array(
+ 'httpMethod' => 'DELETE',
+ 'uri' => '/{Bucket}{/Key*}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'DeleteObjectOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri'),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')),
+ 'MFA' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-mfa',
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'versionId',
+ ),
+ 'RequestPayer' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-payer',
+ ),)),
+ /**
+ 批量删除 COS 对象的方法.
+
+ COS 支持批量删除指定 Bucket 中 对象,单次请求最大支持批量删除 1000 个 对象. 请求中删除一个不存在的对象,仍然认为是成功的. 对于响应结果,COS提供 Verbose 和 Quiet 两种模式:Verbose 模式将返回每个对象的删除结果;Quiet 模式只返回删除报错的对象信息. 请求必须携带 Content-MD5 用来校验请求Body 的完整性.
+
+ 关于批量删除 COS 对象接口的描述,请查看https://cloud.tencent.com/document/product/436/8289.
+
+ cos php SDK 中批量删除 COS 对象的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 DeleteObjects 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,则删除成功。
+
+ 示例:
+ $result = $cosClient->deleteObjects(array(
+ // Bucket is required
+ 'Bucket' => 'testbucket-1252448703',
+ // Objects is required
+ 'Objects' => array(
+ array(
+ // Key is required
+ 'Key' => 'string',
+ 'VersionId' => 'string',
+ ),
+ // ... repeated
+ ),
+ ));
+ */
+ 'DeleteObjects' => array(
+ 'httpMethod' => 'POST',
+ 'uri' => '/{Bucket}?delete',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'DeleteObjectsOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'Delete',
+ ),
+ 'contentMd5' => true,
+ ),
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'Objects' => array(
+ 'required' => true,
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'ObjectIdentifier',
+ 'type' => 'object',
+ 'sentAs' => 'Object',
+ 'properties' => array(
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'minLength' => 1,
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ 'Quiet' => array(
+ 'type' => 'boolean',
+ 'format' => 'boolean-string',
+ 'location' => 'xml',
+ ),
+ 'MFA' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-mfa',
+ ),
+ 'RequestPayer' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-payer',
+ ),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml',
+ ),
+ ),
+ ),
+ /**
+ 删除存储桶(Bucket) 的生命周期配置的方法.
+
+ COS 支持删除已配置的 Bucket 的生命周期列表. COS 支持以生命周期配置的方式来管理 Bucket 中 对象的生命周期,生命周期配置包含一个或多个将 应用于一组对象规则的规则集 (其中每个规则为 COS 定义一个操作),请参阅 putBucketLifecycle(PutBucketLifecycleRequest).
+
+ 关于删除 Bucket 的生命周期配置接口的具体描述,请查看https://cloud.tencent.com/document/product/436/8284.
+
+ cos php SDK 中删除 Bucket 的生命周期配置的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 DeleteBucketLifeCycle 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,删除成功。
+
+ 示例:
+ $result = $cosClient->deleteBucketLifecycle(array(
+ // Bucket is required
+ 'Bucket' =>'testbucket-1252448703',
+ ));
+ */
+ 'DeleteBucketLifecycle' => array(
+ 'httpMethod' => 'DELETE',
+ 'uri' => '/{Bucket}?lifecycle',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'DeleteBucketLifecycleOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ ),
+ ),
+ /**
+ 删除跨区域复制配置的方法.
+
+ 当不需要进行跨区域复制时,可以删除 Bucket 的跨区域复制配置. 跨区域复制,可以查阅putBucketReplication(PutBucketReplicationRequest)
+
+ cos php SDK 中删除跨区域复制配置的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 DeleteBucketReplication 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,获取成功。
+
+ */
+ 'DeleteBucketReplication' => array(
+ 'httpMethod' => 'DELETE',
+ 'uri' => '/{Bucket}?replication',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'DeleteBucketReplicationOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ ),
+ ),
+ 'GetObject' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/{Bucket}{/Key*}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'GetObjectOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri'),
+ 'IfMatch' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'If-Match'),
+ 'IfModifiedSince' => array(
+ 'type' => array(
+ 'object',
+ 'string',
+ 'integer'),
+ 'format' => 'date-time-http',
+ 'location' => 'header',
+ 'sentAs' => 'If-Modified-Since'),
+ 'IfNoneMatch' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'If-None-Match'),
+ 'IfUnmodifiedSince' => array(
+ 'type' => array(
+ 'object',
+ 'string',
+ 'integer'),
+ 'format' => 'date-time-http',
+ 'location' => 'header',
+ 'sentAs' => 'If-Unmodified-Since'),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')),
+ 'Range' => array(
+ 'type' => 'string',
+ 'location' => 'header'),
+ 'ResponseCacheControl' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'response-cache-control'),
+ 'ResponseContentDisposition' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'response-content-disposition'),
+ 'ResponseContentEncoding' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'response-content-encoding'),
+ 'ResponseContentLanguage' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'response-content-language'),
+ 'ResponseContentType' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'response-content-type'),
+ 'ResponseExpires' => array(
+ 'type' => array(
+ 'object',
+ 'string',
+ 'integer'),
+ 'format' => 'date-time-http',
+ 'location' => 'query',
+ 'sentAs' => 'response-expires'),
+ 'VersionId' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'versionId',
+ ),
+ 'SaveAs' => array(
+ 'location' => 'response_body')),
+ 'errorResponses' => array(
+ array(
+ 'reason' => 'The specified key does not exist.',
+ 'class' => 'NoSuchKeyException'))),
+ /**
+ 获取 COS 对象的访问权限信息(Access Control List, ACL)的方法.
+
+ Bucket 的持有者可获取该 Bucket 下的某个对象的 ACL 信息,如被授权者以及被授权的信息. ACL 权限包括读、写、读写权限.
+
+ 关于获取 COS 对象的 ACL 接口的具体描述,请查看https://cloud.tencent.com/document/product/436/7744.
+
+ cos php SDK 中获取 COS 对象的 ACL 的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 GetObjectAcl 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,则获取成功。
+
+ 示例:
+ $result = $cosClient->getObjectAcl(array(
+ 'Bucket' => 'testbucket-1252448703',
+ 'Key' => '11'));
+ */
+ 'GetObjectAcl' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/{Bucket}{/Key*}?acl',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'GetObjectAclOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'versionId',
+ ),
+ 'RequestPayer' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-payer',
+ ),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml',
+ ),
+ ),
+ 'errorResponses' => array(
+ array(
+ 'reason' => 'The specified key does not exist.',
+ 'class' => 'NoSuchKeyException',
+ ),
+ ),
+ ),
+ /**
+ 获取存储桶(Bucket) 的访问权限信息(Access Control List, ACL)的方法.
+
+ ACL 权限包括读、写、读写权限. COS 中 Bucket 是有访问权限控制的.可以通过获取 Bucket 的 ACL 表(putBucketACL(PutBucketACLRequest)),来查看那些用户拥有 Bucket 访 问权限.
+
+ 关于获取 Bucket 的 ACL 接口的具体描述,请查看 https://cloud.tencent.com/document/product/436/7733.
+
+ cos php SDK 中获取 Bucket 的 ACL 的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 GetBucketACL 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,则获取成功。
+
+
+ 示例:
+ $result = $cosClient->GetBucketAcl(array(
+ 'Bucket' => 'testbucket-1252448703',));
+ */
+ 'GetBucketAcl' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/{Bucket}?acl',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'GetBucketAclOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri'),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml'))),
+ /**
+ 查询存储桶(Bucket) 跨域访问配置信息的方法.
+
+ COS 支持查询当前 Bucket 跨域访问配置信息,以确定是否配置跨域信息.当跨域访问配置不存在时,请求 返回403 Forbidden. 跨域访问配置可以通过 putBucketCORS(PutBucketCORSRequest) 或者 putBucketCORSAsync(PutBucketCORSRequest, CosXmlResultListener) 方法来开启 Bucket 的跨域访问 支持.
+
+ 关于查询 Bucket 跨域访问配置信息接口的具体描述, 请查看 https://cloud.tencent.com/document/product/436/8274.
+
+ cos php SDK 中查询 Bucket 跨域访问配置信息的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 GetBucketCORS 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,则获取成功。
+
+
+ 示例:
+ $result = $cosClient->getBucketCors(array(
+ // Bucket is required
+ 'Bucket' => 'testbucket-1252448703',
+ ));
+ */
+ 'GetBucketCors' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/{Bucket}?cors',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'GetBucketCorsOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml',
+ ),
+ ),
+ ),
+ /**
+ 查询存储桶(Bucket) 的生命周期配置的方法.
+
+ COS 支持以生命周期配置的方式来管理 Bucket 中对象的生命周期,生命周期配置包含一个或多个将 应用于一组对象规则的规则集 (其中每个规则为 COS 定义一个操作),请参阅 putBucketLifecycle(PutBucketLifecycleRequest).
+
+ 关于查询 Bucket 的生命周期配置接口的具体描述,请查看https://cloud.tencent.com/document/product/436/8278.
+
+ cos php SDK 中查询 Bucket 的生命周期配置的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 GetBucketLifecycle 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,则获取成功。
+
+
+ 示例:
+ $result = $cosClient->getBucketLifecycle(array(
+ 'Bucket' => 'testbucket-1252448703',
+ ));
+ */
+ 'GetBucketLifecycle' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/{Bucket}?lifecycle',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'GetBucketLifecycleOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml',
+ ),
+ ),
+ ),
+ /**
+ 获取存储桶(Bucket)版本控制信息的方法.
+
+ 通过查询版本控制信息,可以得知该 Bucket 的版本控制功能是处于禁用状态还是启用状态(Enabled 或者 Suspended), 开启版本控制功能,可参考putBucketVersioning(PutBucketVersioningRequest).
+
+ cos php SDK 中获取 Bucket 版本控制信息的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 GetBucketVersioning 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,获取成功。
+
+ 示例:
+ $result = $cosClient->getBucketVersioning(
+ array('Bucket' => 'lewzylu02-1252448703'));
+ */
+ 'GetBucketVersioning' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/{Bucket}?versioning',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'GetBucketVersioningOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml',
+ ),
+ ),
+ ),
+ /**
+ 获取跨区域复制配置信息的方法.
+
+ 跨区域复制是支持不同区域 Bucket 自动复制对象, 请查阅putBucketReplication(PutBucketReplicationRequest).
+
+ cos php SDK 中获取跨区域复制配置信息的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 GetBucketReplication 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,设置成功。
+
+ */
+ 'GetBucketReplication' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/{Bucket}?replication',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'GetBucketReplicationOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml',
+ ),
+ ),
+ ),
+ /**
+ 获取存储桶(Bucket) 所在的地域信息的方法.
+
+ 在创建 Bucket 时,需要指定所属该 Bucket 所属地域信息.
+
+ COS 支持的地域信息,可查看https://cloud.tencent.com/document/product/436/6224.
+
+ 关于获取 Bucket 所在的地域信息接口的具体描述,请查看https://cloud.tencent.com/document/product/436/8275.
+
+ cos php SDK 中获取 Bucket 所在的地域信息的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 GetBucketLocation 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,则获取成功。
+
+
+ 示例:
+ $result = $cosClient->getBucketLocation(array(
+ 'Bucket' => 'testbucket-1252448703',
+ ));
+ */
+ 'GetBucketLocation' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/{Bucket}?location',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'GetBucketLocationOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ ),
+ ),
+ 'GetBucketNotification' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/{Bucket}?notification',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'GetBucketNotificationOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml',
+ ),
+ ),
+ ),
+ 'UploadPart' => array(
+ 'httpMethod' => 'PUT',
+ 'uri' => '/{Bucket}{/Key*}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'UploadPartOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'UploadPartRequest')),
+ 'parameters' => array(
+ 'Body' => array(
+ 'type' => array(
+ 'string',
+ 'object'),
+ 'location' => 'body'),
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri'),
+ 'ContentLength' => array(
+ 'type' => 'numeric',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Length'),
+ 'ContentMD5' => array(
+ 'type' => array(
+ 'string',
+ 'boolean'),
+ 'location' => 'header',
+ 'sentAs' => 'Content-MD5'),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')),
+ 'PartNumber' => array(
+ 'required' => true,
+ 'type' => 'numeric',
+ 'location' => 'query',
+ 'sentAs' => 'partNumber'),
+ 'UploadId' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'uploadId'),
+ 'ServerSideEncryption' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption',
+ ),
+ 'SSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm',
+ ),
+ 'SSECustomerKey' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key',
+ ),
+ 'SSECustomerKeyMD5' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5',
+ ),
+ 'RequestPayer' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-payer',
+ ))),
+ 'PutObject' => array(
+ 'httpMethod' => 'PUT',
+ 'uri' => '/{Bucket}{/Key*}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'PutObjectOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'PutObjectRequest')),
+ 'parameters' => array(
+ 'ACL' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-acl'),
+ 'Body' => array(
+ 'type' => array(
+ 'string',
+ 'object'),
+ 'location' => 'body'),
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri'),
+ 'CacheControl' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Cache-Control'),
+ 'ContentDisposition' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Disposition'),
+ 'ContentEncoding' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Encoding'),
+ 'ContentLanguage' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Language'),
+ 'ContentLength' => array(
+ 'type' => 'numeric',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Length'),
+ 'ContentMD5' => array(
+ 'type' => array(
+ 'string',
+ 'boolean'),
+ 'location' => 'header',
+ 'sentAs' => 'Content-MD5'),
+ 'ContentType' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Type'),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')),
+ 'Metadata' => array(
+ 'type' => 'object',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-meta-',
+ 'additionalProperties' => array(
+ 'type' => 'string')
+ ),
+ 'ServerSideEncryption' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption',
+ ),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-storage-class',
+ ),
+ 'WebsiteRedirectLocation' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-website-redirect-location',
+ ),
+ 'SSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm',
+ ),
+ 'SSECustomerKey' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key',
+ ),
+ 'SSECustomerKeyMD5' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5',
+ ),
+ 'SSEKMSKeyId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id',
+ ),
+ 'RequestPayer' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-payer',
+ ),
+ 'ACP' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ ))),
+ /**
+ 设置 COS 对象的访问权限信息(Access Control List, ACL)的方法.
+
+ ACL权限包括读、写、读写权限. COS 对象的 ACL 可以通过 header头部:"x-cos-acl","x-cos-grant-read","x-cos-grant-write", "x-cos-grant-full-control" 传入 ACL 信息,或者通过 Body 以 XML 格式传入 ACL 信息.这两种方式只 能选择其中一种,否则引起冲突. 传入新的 ACL 将覆盖原有 ACL信息.ACL策略数上限1000,建议用户不要每个上传文件都设置 ACL.
+
+ 关于设置 COS 对象的ACL接口的具体描述,请查看https://cloud.tencent.com/document/product/436/7748.
+
+ cos PHP SDK 中设置 COS 对象的 ACL 的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 PutObjectAcl 对象中的方法发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,则设置成功。
+
+ 示例:
+ $cosClient->PutObjectAcl(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '11',
+ 'Grants' => array(
+ array(
+ 'Grantee' => array(
+ 'DisplayName' => 'qcs::cam::uin/327874225:uin/327874225',
+ 'ID' => 'qcs::cam::uin/327874225:uin/327874225',
+ 'Type' => 'CanonicalUser',
+ ),
+ 'Permission' => 'FULL_CONTROL',
+ ),
+ // ... repeated
+ ),
+ 'Owner' => array(
+ 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ )));
+ */
+ 'PutObjectAcl' => array(
+ 'httpMethod' => 'PUT',
+ 'uri' => '/{Bucket}{/Key*}?acl',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'PutObjectAclOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'AccessControlPolicy',
+ ),
+ ),
+ 'parameters' => array(
+ 'ACL' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-acl',
+ ),
+ 'Grants' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'AccessControlList',
+ 'items' => array(
+ 'name' => 'Grant',
+ 'type' => 'object',
+ 'properties' => array(
+ 'Grantee' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string'),
+ 'ID' => array(
+ 'type' => 'string'),
+ 'Type' => array(
+ 'type' => 'string',
+ 'sentAs' => 'xsi:type',
+ 'data' => array(
+ 'xmlAttribute' => true,
+ 'xmlNamespace' => 'http://www.w3.org/2001/XMLSchema-instance')),
+ 'URI' => array(
+ 'type' => 'string') )),
+ 'Permission' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ 'Owner' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string',
+ ),
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'GrantFullControl' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-full-control',
+ ),
+ 'GrantRead' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-read',
+ ),
+ 'GrantReadACP' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-read-acp',
+ ),
+ 'GrantWrite' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-write',
+ ),
+ 'GrantWriteACP' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-write-acp',
+ ),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')
+ ),
+ 'RequestPayer' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-payer',
+ ),
+ 'ACP' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ ),
+ ),
+ 'errorResponses' => array(
+ array(
+ 'reason' => 'The specified key does not exist.',
+ 'class' => 'NoSuchKeyException',
+ ),
+ ),
+ ),
+ /**
+ 设置存储桶(Bucket) 的访问权限(Access Control List, ACL)的方法.
+
+ ACL 权限包括读、写、读写权限. 写入 Bucket 的 ACL 可以通过 header头部:"x-cos-acl","x-cos-grant-read","x-cos-grant-write", "x-cos-grant-full-control" 传入 ACL 信息,或者通过 Body 以 XML 格式传入 ACL 信息.这两种方式只 能选择其中一种,否则引起冲突. 传入新的 ACL 将覆盖原有 ACL信息. 私有 Bucket 可以下可以给某个文件夹设置成公有,那么该文件夹下的文件都是公有;但是把文件夹设置成私有后,在该文件夹下的文件设置 的公有属性,不会生效.
+
+ 关于设置 Bucket 的ACL接口的具体描述,请查看 https://cloud.tencent.com/document/product/436/7737.
+
+ cos php SDK 中设置 Bucket 的ACL的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 PutObjectAcl 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,设置成功。
+
+
+ 示例:
+ $result = $cosClient->PutObjectAcl(array(
+ 'Bucket' => 'testbucket-1252448703',
+ 'Key' => '111',
+ 'Grants' => array(
+ array(
+ 'Grantee' => array(
+ 'DisplayName' => 'qcs::cam::uin/327874225:uin/327874225',
+ 'ID' => 'qcs::cam::uin/327874225:uin/327874225',
+ 'Type' => 'CanonicalUser',
+ ),
+ 'Permission' => 'FULL_CONTROL',
+ ),
+ // ... repeated
+ ),
+ 'Owner' => array(
+ 'DisplayName' => 'qcs::cam::uin/3210232098:uin/3210232098',
+ 'ID' => 'qcs::cam::uin/3210232098:uin/3210232098',
+ ),));
+ */
+ 'PutBucketAcl' => array(
+ 'httpMethod' => 'PUT',
+ 'uri' => '/{Bucket}?acl',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'PutBucketAclOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'AccessControlPolicy',
+ ),
+ ),
+ 'parameters' => array(
+ 'ACL' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-acl',
+ ),
+ 'Grants' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'AccessControlList',
+ 'items' => array(
+ 'name' => 'Grant',
+ 'type' => 'object',
+ 'properties' => array(
+ 'Grantee' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string',
+ ),
+ 'EmailAddress' => array(
+ 'type' => 'string',
+ ),
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ 'Type' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'sentAs' => 'xsi:type',
+ 'data' => array(
+ 'xmlAttribute' => true,
+ 'xmlNamespace' => 'http://www.w3.org/2001/XMLSchema-instance',
+ ),
+ ),
+ 'URI' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ 'Permission' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ 'Owner' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string',
+ ),
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'GrantFullControl' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-full-control',
+ ),
+ 'GrantRead' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-read',
+ ),
+ 'GrantReadACP' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-read-acp',
+ ),
+ 'GrantWrite' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-write',
+ ),
+ 'GrantWriteACP' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-grant-write-acp',
+ ),
+ 'ACP' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ ),
+ ),
+ ),
+ /**
+ 设置存储桶(Bucket) 的跨域配置信息的方法.
+
+ 跨域访问配置的预请求是指在发送跨域请求之前会发送一个 OPTIONS 请求并带上特定的来源域,HTTP 方 法和 header 信息等给 COS,以决定是否可以发送真正的跨域请求. 当跨域访问配置不存在时,请求返回403 Forbidden.
+
+ 默认情况下,Bucket的持有者可以直接配置 Bucket的跨域信息 ,Bucket 持有者也可以将配置权限授予其他用户.新的配置是覆盖当前的所有配置信 息,而不是新增一条配置.可以通过传入 XML 格式的配置文件来实现配置,文件大小限制为64 KB.
+
+ 关于设置 Bucket 的跨域配置信息接口的具体描述,请查看 https://cloud.tencent.com/document/product/436/8279.
+
+ cos php SDK 中设置 Bucket 的跨域配置信息的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 PutBucketCORS 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,设置成功。
+
+ 示例:
+ $result = $cosClient->putBucketCors(array(
+ // Bucket is required
+ 'Bucket' => 'testbucket-1252448703',
+ // CORSRules is required
+ 'CORSRules' => array(
+ array(
+ 'ID' => '1234',
+ 'AllowedHeaders' => array('*'),
+ // AllowedMethods is required
+ 'AllowedMethods' => array('PUT'),
+ // AllowedOrigins is required
+ 'AllowedOrigins' => array('http://www.qq.com', ),
+ ),
+ // ... repeated
+ ),
+ ));
+ */
+ 'PutBucketCors' => array(
+ 'httpMethod' => 'PUT',
+ 'uri' => '/{Bucket}?cors',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'PutBucketCorsOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'CORSConfiguration',
+ ),
+ 'contentMd5' => true,
+ ),
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'CORSRules' => array(
+ 'required' => true,
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'CORSRule',
+ 'type' => 'object',
+ 'sentAs' => 'CORSRule',
+ 'properties' => array(
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ 'AllowedHeaders' => array(
+ 'type' => 'array',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'AllowedHeader',
+ 'type' => 'string',
+ 'sentAs' => 'AllowedHeader',
+ ),
+ ),
+ 'AllowedMethods' => array(
+ 'required' => true,
+ 'type' => 'array',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'AllowedMethod',
+ 'type' => 'string',
+ 'sentAs' => 'AllowedMethod',
+ ),
+ ),
+ 'AllowedOrigins' => array(
+ 'required' => true,
+ 'type' => 'array',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'AllowedOrigin',
+ 'type' => 'string',
+ 'sentAs' => 'AllowedOrigin',
+ ),
+ ),
+ 'ExposeHeaders' => array(
+ 'type' => 'array',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'ExposeHeader',
+ 'type' => 'string',
+ 'sentAs' => 'ExposeHeader',
+ ),
+ ),
+ 'MaxAgeSeconds' => array(
+ 'type' => 'numeric',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ /**
+ 设置存储桶(Bucket) 生命周期配置的方法.
+
+ COS 支持以生命周期配置的方式来管理 Bucket 中对象的生命周期. 如果该 Bucket 已配置生命周期,新的配置的同时则会覆盖原有的配置. 生命周期配置包含一个或多个将应用于一组对象规则的规则集 (其中每个规则为 COS 定义一个操作)。这些操作分为以下两种:转换操作,过期操作.
+
+ 转换操作,定义对象转换为另一个存储类的时间(例如,您可以选择在对象创建 30 天后将其转换为低频存储类别,同 时也支持将数据沉降到归档存储类别.
+
+ 过期操作,指定 Object 的过期时间,COS 将会自动为用户删除过期的 Object.
+
+ 关于Bucket 生命周期配置接口的具体描述,请查看 https://cloud.tencent.com/document/product/436/8280
+
+ cos php SDK 中Bucket 生命周期配置的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 PutBucketLifecycle 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,设置成功。
+
+ 示例:
+ $result = $cosClient->putBucketLifecycle(array(
+ // Bucket is required
+ 'Bucket' => 'lewzylu06-1252448703',
+ // Rules is required
+ 'Rules' => array(
+ array(
+ 'Expiration' => array(
+ 'Days' => 1000,
+ ),
+ 'ID' => 'id1',
+ 'Filter' => array(
+ 'Prefix' => 'documents/'
+ ),
+ // Status is required
+ 'Status' => 'Enabled',
+ 'Transitions' => array(
+ array(
+ 'Days' => 200,
+ 'StorageClass' => 'NEARLINE'),
+ ),
+ // ... repeated
+ ),
+ )));
+ */
+ 'PutBucketLifecycle' => array(
+ 'httpMethod' => 'PUT',
+ 'uri' => '/{Bucket}?lifecycle',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'PutBucketLifecycleOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'LifecycleConfiguration',
+ ),
+ 'contentMd5' => true,
+ ),
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'Rules' => array(
+ 'required' => true,
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'Rule',
+ 'type' => 'object',
+ 'sentAs' => 'Rule',
+ 'properties' => array(
+ 'Expiration' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Date' => array(
+ 'type' => array(
+ 'object',
+ 'string',
+ 'integer',
+ ),
+ 'format' => 'date-time',
+ ),
+ 'Days' => array(
+ 'type' => 'numeric',
+ ),
+ ),
+ ),
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ 'Filter' => array(
+ 'type' => 'object',
+ 'require' => true,
+ 'properties' => array(
+ 'Prefix' => array(
+ 'type' => 'string',
+ 'require' => true,
+ ),
+ 'Tag' => array(
+ 'type' => 'object',
+ 'require' => true,
+ 'properties' => array(
+ 'Key' => array(
+ 'type' => 'string'
+ ),
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey'),
+ 'Value' => array(
+ 'type' => 'string'
+ ),
+ )
+ )
+ ),
+ ),
+ 'Status' => array(
+ 'required' => true,
+ 'type' => 'string',
+ ),
+ 'Transitions' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'Transition',
+ 'type' => 'object',
+ 'sentAs' => 'Transition',
+ 'properties' => array(
+ 'Date' => array(
+ 'type' => array(
+ 'object',
+ 'string',
+ 'integer',
+ ),
+ 'format' => 'date-time',
+ ),
+ 'Days' => array(
+ 'type' => 'numeric',
+ ),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ )))),
+ 'NoncurrentVersionTransition' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'NoncurrentDays' => array(
+ 'type' => 'numeric',
+ ),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ 'NoncurrentVersionExpiration' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'NoncurrentDays' => array(
+ 'type' => 'numeric',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ /**
+ 存储桶(Bucket)版本控制的方法.
+
+ 版本管理功能一经打开,只能暂停,不能关闭. 通过版本控制,可以在一个 Bucket 中保留一个对象的多个版本. 版本控制可以防止意外覆盖和删除对象,以便检索早期版本的对象. 默认情况下,版本控制功能处于禁用状态,需要主动去启用或者暂停(Enabled 或者 Suspended).
+
+ cos php SDK 中 Bucket 版本控制启用或者暂停的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 PutBucketVersioning 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,设置成功。
+
+ 示例:
+ $result = $cosClient->putBucketVersioning(
+ array('Bucket' => 'testbucket-1252448703',
+ 'Status' => 'Enabled'));
+ */
+ 'PutBucketVersioning' => array(
+ 'httpMethod' => 'PUT',
+ 'uri' => '/{Bucket}?versioning',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'PutBucketVersioningOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'VersioningConfiguration',
+ ),
+ ),
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'MFA' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-mfa',
+ ),
+ 'MFADelete' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ 'sentAs' => 'MfaDelete',
+ ),
+ 'Status' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ ),
+ ),
+ /**
+ 配置跨区域复制的方法.
+
+ 跨区域复制是支持不同区域 Bucket 自动异步复制对象.注意,不能是同区域的 Bucket, 且源 Bucket 和目 标 Bucket 必须已启用版本控制putBucketVersioning(PutBucketVersioningRequest).
+
+ cos php SDK 中配置跨区域复制的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 PutBucketRelication 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,设置成功。
+
+ 示例:
+
+ */
+ 'PutBucketReplication' => array(
+ 'httpMethod' => 'PUT',
+ 'uri' => '/{Bucket}?replication',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'PutBucketReplicationOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'ReplicationConfiguration',
+ ),
+ 'contentMd5' => true,
+ ),
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'Role' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'Rules' => array(
+ 'required' => true,
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'ReplicationRule',
+ 'type' => 'object',
+ 'sentAs' => 'Rule',
+ 'properties' => array(
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ 'Prefix' => array(
+ 'required' => true,
+ 'type' => 'string',
+ ),
+ 'Status' => array(
+ 'required' => true,
+ 'type' => 'string',
+ ),
+ 'Destination' => array(
+ 'required' => true,
+ 'type' => 'object',
+ 'properties' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ ),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ /**
+ 设置存储桶(Bucket) 的回调设置的方法.
+ */
+ 'PutBucketNotification' => array(
+ 'httpMethod' => 'PUT',
+ 'uri' => '/{Bucket}?notification',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'PutBucketNotificationOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'NotificationConfiguration',
+ ),
+ ),
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'CloudFunctionConfigurations' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'CloudFunctionConfiguration',
+ 'type' => 'object',
+ 'sentAs' => 'CloudFunctionConfiguration',
+ 'properties' => array(
+ 'Id' => array(
+ 'type' => 'string',
+ ),
+ 'CloudFunction' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'sentAs' => 'CloudFunction',
+ ),
+ 'Events' => array(
+ 'required' => true,
+ 'type' => 'array',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'Event',
+ 'type' => 'string',
+ 'sentAs' => 'Event',
+ ),
+ ),
+ 'Filter' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Key' => array(
+ 'type' => 'object',
+ 'sentAs' => 'Key',
+ 'properties' => array(
+ 'FilterRules' => array(
+ 'type' => 'array',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'FilterRule',
+ 'type' => 'object',
+ 'sentAs' => 'FilterRule',
+ 'properties' => array(
+ 'Name' => array(
+ 'type' => 'string',
+ ),
+ 'Value' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ 'RestoreObject' => array(
+ 'httpMethod' => 'POST',
+ 'uri' => '/{Bucket}{/Key*}?restore',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'RestoreObjectOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'RestoreRequest',
+ ),
+ ),
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'versionId',
+ ),
+ 'Days' => array(
+ 'required' => true,
+ 'type' => 'numeric',
+ 'location' => 'xml',
+ ),
+ 'CASJobParameters' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'Tier' => array(
+ 'type' => 'string',
+ 'required' => true,
+ ),
+ ),
+ ),
+ 'RequestPayer' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-payer',
+ ),
+ ),
+ 'errorResponses' => array(
+ array(
+ 'reason' => 'This operation is not allowed against this storage tier',
+ 'class' => 'ObjectAlreadyInActiveTierErrorException',
+ ),
+ ),
+ ),
+ /**
+ 查询存储桶(Bucket)中正在进行中的分块上传对象的方法.
+
+ COS 支持查询 Bucket 中有哪些正在进行中的分块上传对象,单次请求操作最多列出 1000 个正在进行中的 分块上传对象.
+
+ 关于查询 Bucket 中正在进行中的分块上传对象接口的具体描述,请查看 https://cloud.tencent.com/document/product/436/7736.
+
+ cos php SDK 中查询 Bucket 中正在进行中的分块上传对象的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 ListParts 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,获取成功。
+
+ */
+ 'ListParts' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/{Bucket}{/Key*}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'ListPartsOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri'),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')),
+ 'MaxParts' => array(
+ 'type' => 'numeric',
+ 'location' => 'query',
+ 'sentAs' => 'max-parts'),
+ 'PartNumberMarker' => array(
+ 'type' => 'numeric',
+ 'location' => 'query',
+ 'sentAs' => 'part-number-marker'),
+ 'UploadId' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'uploadId'),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml'))),
+ /**
+ 查询存储桶(Bucket) 下的部分或者全部对象的方法.
+
+ COS 支持列出指定 Bucket 下的部分或者全部对象.
+
+ 每次默认返回的最大条目数为 1000 条.
+
+ 如果无法一次返回所有的对象,则返回结果中的 IsTruncated 为 true,同时会附加一个 NextMarker 字段,提示下 一个条目的起点.
+
+ 若一次请求,已经返回了全部对象,则不会有 NextMarker 这个字段,同时 IsTruncated 为 false.
+
+ 若把 prefix 设置为某个文件夹的全路径名,则可以列出以此 prefix 为开头的文件,即该文件 夹下递归的所有文件和子文件夹.
+
+ 如果再设置 delimiter 定界符为 “/”,则只列出该文件夹下的文件,子文件夹下递归的文件和文件夹名 将不被列出.而子文件夹名将会以 CommonPrefix 的形式给出.
+
+ 关于查询Bucket 下的部分或者全部对象接口的具体描述,请查看https://cloud.tencent.com/document/product/436/7734.
+
+ cos php SDK 中查询 Bucket 下的部分或者全部对象的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 ListObjects 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,则list成功。
+
+ 示例:
+ $result = $cosClient->ListObjects(array(
+ 'Bucket' => 'testbucket-1252448703'));
+ */
+ 'ListObjects' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/{Bucket}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'ListObjectsOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri'),
+ 'Delimiter' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'delimiter'),
+ 'EncodingType' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'encoding-type'),
+ 'Marker' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'marker'),
+ 'MaxKeys' => array(
+ 'type' => 'numeric',
+ 'location' => 'query',
+ 'sentAs' => 'max-keys'),
+ 'Prefix' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'prefix'),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml')),
+ 'errorResponses' => array(
+ array(
+ 'reason' => 'The specified bucket does not exist.',
+ 'class' => 'NoSuchBucketException'))),
+ /**
+ 获取所属账户的所有存储空间列表的方法.
+
+ 通过使用帯 Authorization 签名认证的请求,可以获取签名中 APPID 所属账户的所有存储空间列表 (Bucket list).
+
+ 关于获取所有存储空间列表接口的具体描述,请查看https://cloud.tencent.com/document/product/436/8291.
+
+ cos php SDK 中获取所属账户的所有存储空间列表的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 ListBuckets 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,获取成功。
+
+ 示例:
+ $result = $cosClient->listBuckets();
+ print_r($result);
+ */
+ 'ListBuckets' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'ListBucketsOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml',
+ ),
+ ),
+ ),
+ 'ListObjectVersions' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/{Bucket}?versions',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'ListObjectVersionsOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'Delimiter' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'delimiter',
+ ),
+ 'EncodingType' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'encoding-type',
+ ),
+ 'KeyMarker' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'key-marker',
+ ),
+ 'MaxKeys' => array(
+ 'type' => 'numeric',
+ 'location' => 'query',
+ 'sentAs' => 'max-keys',
+ ),
+ 'Prefix' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'prefix',
+ ),
+ 'VersionIdMarker' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'version-id-marker',
+ ),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml',
+ ),
+ ),
+ ),
+ 'ListMultipartUploads' => array(
+ 'httpMethod' => 'GET',
+ 'uri' => '/{Bucket}?uploads',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'ListMultipartUploadsOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'Delimiter' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'delimiter',
+ ),
+ 'EncodingType' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'encoding-type',
+ ),
+ 'KeyMarker' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'key-marker',
+ ),
+ 'MaxUploads' => array(
+ 'type' => 'numeric',
+ 'location' => 'query',
+ 'sentAs' => 'max-uploads',
+ ),
+ 'Prefix' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'prefix',
+ ),
+ 'UploadIdMarker' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'upload-id-marker',
+ ),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml',
+ ),
+ ),
+ ),
+ 'HeadObject' => array(
+ 'httpMethod' => 'HEAD',
+ 'uri' => '/{Bucket}{/Key*}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'HeadObjectOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'IfMatch' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'If-Match',
+ ),
+ 'IfModifiedSince' => array(
+ 'type' => array(
+ 'object',
+ 'string',
+ 'integer',
+ ),
+ 'format' => 'date-time-http',
+ 'location' => 'header',
+ 'sentAs' => 'If-Modified-Since',
+ ),
+ 'IfNoneMatch' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'If-None-Match',
+ ),
+ 'IfUnmodifiedSince' => array(
+ 'type' => array(
+ 'object',
+ 'string',
+ 'integer',
+ ),
+ 'format' => 'date-time-http',
+ 'location' => 'header',
+ 'sentAs' => 'If-Unmodified-Since',
+ ),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')
+ ),
+ 'Range' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'versionId',
+ ),
+ 'SSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm',
+ ),
+ 'SSECustomerKey' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key',
+ ),
+ 'SSECustomerKeyMD5' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5',
+ ),
+ 'RequestPayer' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-payer',
+ ),
+ ),
+ 'errorResponses' => array(
+ array(
+ 'reason' => 'The specified key does not exist.',
+ 'class' => 'NoSuchKeyException',
+ ),
+ ),
+ ),
+ /**
+ 存储桶(Bucket) 是否存在的方法.
+
+ 在开始使用 COS 时,需要确认该 Bucket 是否存在,是否有权限访问.若不存在,则可以调用putBucket(PutBucketRequest) 创建.
+
+ 关于确认该 Bucket 是否存在,是否有权限访问接口的具体描述,请查看https://cloud.tencent.com/document/product/436/7735.
+
+ cos php SDK 中Bucket 是否存在的方法具体步骤如下:
+
+ 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。
+
+ 2. 调用 HeadBucket 接口发出请求。
+
+ 3. 接收该接口的返回数据,若没有抛出异常,获取成功。
+
+ 示例:
+ $result = $cosClient->headObject(array(
+ 'Bucket' => 'testbucket-1252448703',
+ 'Key' => '11',
+ 'VersionId' =>'111',
+ 'ServerSideEncryption' => 'AES256'));
+ */
+ 'HeadBucket' => array(
+ 'httpMethod' => 'HEAD',
+ 'uri' => '/{Bucket}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'HeadBucketOutput',
+ 'responseType' => 'model',
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ ),
+ 'errorResponses' => array(
+ array(
+ 'reason' => 'The specified bucket does not exist.',
+ 'class' => 'NoSuchBucketException',
+ ),
+ ),
+ ),
+ 'UploadPartCopy' => array(
+ 'httpMethod' => 'PUT',
+ 'uri' => '/{Bucket}{/Key*}',
+ 'class' => 'Qcloud\\Cos\\Command',
+ 'responseClass' => 'UploadPartCopyOutput',
+ 'responseType' => 'model',
+ 'data' => array(
+ 'xmlRoot' => array(
+ 'name' => 'UploadPartCopyRequest',
+ ),
+ ),
+ 'parameters' => array(
+ 'Bucket' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ ),
+ 'CopySource' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source',
+ ),
+ 'CopySourceIfMatch' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-if-match',
+ ),
+ 'CopySourceIfModifiedSince' => array(
+ 'type' => array(
+ 'object',
+ 'string',
+ 'integer',
+ ),
+ 'format' => 'date-time-http',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-if-modified-since',
+ ),
+ 'CopySourceIfNoneMatch' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-if-none-match',
+ ),
+ 'CopySourceIfUnmodifiedSince' => array(
+ 'type' => array(
+ 'object',
+ 'string',
+ 'integer',
+ ),
+ 'format' => 'date-time-http',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-if-unmodified-since',
+ ),
+ 'CopySourceRange' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-range',
+ ),
+ 'Key' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'uri',
+ 'minLength' => 1,
+ 'filters' => array(
+ 'Qcloud\\Cos\\Client::explodeKey')
+ ),
+ 'PartNumber' => array(
+ 'required' => true,
+ 'type' => 'numeric',
+ 'location' => 'query',
+ 'sentAs' => 'partNumber',
+ ),
+ 'UploadId' => array(
+ 'required' => true,
+ 'type' => 'string',
+ 'location' => 'query',
+ 'sentAs' => 'uploadId',
+ ),
+ 'SSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm',
+ ),
+ 'SSECustomerKey' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key',
+ ),
+ 'SSECustomerKeyMD5' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5',
+ ),
+ 'CopySourceSSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-server-side-encryption-customer-algorithm',
+ ),
+ 'CopySourceSSECustomerKey' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-server-side-encryption-customer-key',
+ ),
+ 'CopySourceSSECustomerKeyMD5' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-server-side-encryption-customer-key-MD5',
+ ),
+ 'RequestPayer' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-payer',
+ ),
+ 'command.expects' => array(
+ 'static' => true,
+ 'default' => 'application/xml',
+ ),
+ ),
+ ),),
+ 'models' => array(
+ 'AbortMultipartUploadOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id'))),
+ 'CreateBucketOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Location' => array(
+ 'type' => 'string',
+ 'location' => 'header'),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id'))),
+ 'CompleteMultipartUploadOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Location' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'Bucket' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'Key' => array(
+ 'type' => 'string',
+ 'location' => 'xml'
+ ),
+ 'Expiration' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-expiration',
+ ),
+ 'ETag' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'ServerSideEncryption' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption',
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-version-id',
+ ),
+ 'SSEKMSKeyId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id',
+ ),
+ 'RequestCharged' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-charged',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'CreateMultipartUploadOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Bucket' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ 'sentAs' => 'Bucket'),
+ 'Key' => array(
+ 'type' => 'string',
+ 'location' => 'xml'),
+ 'UploadId' => array(
+ 'type' => 'string',
+ 'location' => 'xml'),
+ 'ServerSideEncryption' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption',
+ ),
+ 'SSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm',
+ ),
+ 'SSECustomerKeyMD5' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5',
+ ),
+ 'SSEKMSKeyId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id',
+ ),
+ 'RequestCharged' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-charged',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ))),
+ 'CopyObjectOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'ETag' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'LastModified' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'Expiration' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-expiration',
+ ),
+ 'CopySourceVersionId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-version-id',
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-version-id',
+ ),
+ 'ServerSideEncryption' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption',
+ ),
+ 'SSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm',
+ ),
+ 'RequestCharged' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-charged',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'DeleteBucketOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id'))),
+ 'DeleteBucketCorsOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'DeleteObjectOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'DeleteMarker' => array(
+ 'type' => 'boolean',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-delete-marker',
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-version-id',
+ ),
+ 'RequestCharged' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-charged',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'DeleteObjectsOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Deleted' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'DeletedObject',
+ 'type' => 'object',
+ 'properties' => array(
+ 'Key' => array(
+ 'type' => 'string',
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ ),
+ 'DeleteMarker' => array(
+ 'type' => 'boolean',
+ ),
+ 'DeleteMarkerVersionId' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ 'RequestCharged' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-charged',
+ ),
+ 'Errors' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'Error',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'Error',
+ 'type' => 'object',
+ 'sentAs' => 'Error',
+ 'properties' => array(
+ 'Key' => array(
+ 'type' => 'string',
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ ),
+ 'Code' => array(
+ 'type' => 'string',
+ ),
+ 'Message' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'DeleteBucketLifecycleOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'DeleteBucketReplicationOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'GetObjectOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Body' => array(
+ 'type' => 'string',
+ 'instanceOf' => 'Guzzle\\Http\\EntityBody',
+ 'location' => 'body',
+ ),
+ 'DeleteMarker' => array(
+ 'type' => 'boolean',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-delete-marker',
+ ),
+ 'AcceptRanges' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'accept-ranges',
+ ),
+ 'Expiration' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-expiration',
+ ),
+ 'Restore' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-restore',
+ ),
+ 'LastModified' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Last-Modified',
+ ),
+ 'ContentLength' => array(
+ 'type' => 'numeric',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Length',
+ ),
+ 'ETag' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ ),
+ 'MissingMeta' => array(
+ 'type' => 'numeric',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-missing-meta',
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-version-id',
+ ),
+ 'CacheControl' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Cache-Control',
+ ),
+ 'ContentDisposition' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Disposition',
+ ),
+ 'ContentEncoding' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Encoding',
+ ),
+ 'ContentLanguage' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Language',
+ ),
+ 'ContentRange' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Range',
+ ),
+ 'ContentType' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Type',
+ ),
+ 'Expires' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ ),
+ 'WebsiteRedirectLocation' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-website-redirect-location',
+ ),
+ 'ServerSideEncryption' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption',
+ ),
+ 'Metadata' => array(
+ 'type' => 'object',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-meta-',
+ 'additionalProperties' => array(
+ 'type' => 'string',
+ ),
+ ),
+ 'SSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm',
+ ),
+ 'SSECustomerKeyMD5' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5',
+ ),
+ 'SSEKMSKeyId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id',
+ ),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-storage-class',
+ ),
+ 'RequestCharged' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-charged',
+ ),
+ 'ReplicationStatus' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-replication-status',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'GetObjectAclOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Owner' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string',
+ ),
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ 'Grants' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'AccessControlList',
+ 'items' => array(
+ 'name' => 'Grant',
+ 'type' => 'object',
+ 'sentAs' => 'Grant',
+ 'properties' => array(
+ 'Grantee' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string'),
+ /*
+ 'EmailAddress' => array(
+ 'type' => 'string'),
+ */
+ 'ID' => array(
+ 'type' => 'string'),
+ /*
+ 'Type' => array(
+ 'type' => 'string',
+ 'sentAs' => 'xsi:type',
+ 'data' => array(
+ 'xmlAttribute' => true,
+ 'xmlNamespace' => 'http://www.w3.org/2001/XMLSchema-instance')),
+ */
+ /*'URI' => array(
+ 'type' => 'string') */)),
+ 'Permission' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ 'RequestCharged' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-charged',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'GetBucketAclOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Owner' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string'),
+ 'ID' => array(
+ 'type' => 'string'))),
+ 'Grants' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'AccessControlList',
+ 'items' => array(
+ 'name' => 'Grant',
+ 'type' => 'object',
+ 'sentAs' => 'Grant',
+ 'properties' => array(
+ 'Grantee' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string'),
+ /*
+ 'EmailAddress' => array(
+ 'type' => 'string'),
+ */
+ 'ID' => array(
+ 'type' => 'string'),
+ /*
+ 'Type' => array(
+ 'type' => 'string',
+ 'sentAs' => 'xsi:type',
+ 'data' => array(
+ 'xmlAttribute' => true,
+ 'xmlNamespace' => 'http://www.w3.org/2001/XMLSchema-instance')),
+ */
+ /*'URI' => array(
+ 'type' => 'string') */)),
+ 'Permission' => array(
+ 'type' => 'string')))),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id'))),
+ 'GetBucketCorsOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'CORSRules' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'CORSRule',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'CORSRule',
+ 'type' => 'object',
+ 'sentAs' => 'CORSRule',
+ 'properties' => array(
+ 'ID' => array(
+ 'type' => 'string'),
+ 'AllowedHeaders' => array(
+ 'type' => 'array',
+ 'sentAs' => 'AllowedHeader',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'AllowedHeader',
+ 'type' => 'string',
+ 'sentAs' => 'AllowedHeader',
+ ),
+ ),
+ 'AllowedMethods' => array(
+ 'type' => 'array',
+ 'sentAs' => 'AllowedMethod',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'AllowedMethod',
+ 'type' => 'string',
+ 'sentAs' => 'AllowedMethod',
+ ),
+ ),
+ 'AllowedOrigins' => array(
+ 'type' => 'array',
+ 'sentAs' => 'AllowedOrigin',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'AllowedOrigin',
+ 'type' => 'string',
+ 'sentAs' => 'AllowedOrigin',
+ ),
+ ),
+ 'ExposeHeaders' => array(
+ 'type' => 'array',
+ 'sentAs' => 'ExposeHeader',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'ExposeHeader',
+ 'type' => 'string',
+ 'sentAs' => 'ExposeHeader',
+ ),
+ ),
+ 'MaxAgeSeconds' => array(
+ 'type' => 'numeric',
+ ),
+ ),
+ ),
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'GetBucketLifecycleOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Rules' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'Rule',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'Rule',
+ 'type' => 'object',
+ 'sentAs' => 'Rule',
+ 'properties' => array(
+ 'Expiration' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Date' => array(
+ 'type' => 'string',
+ ),
+ 'Days' => array(
+ 'type' => 'numeric',
+ ),
+ ),
+ ),
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ 'Filter' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Prefix' => array(
+ 'type' => 'string',
+ ),
+ 'Tag' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Key' => array(
+ 'type' => 'string'
+ ),
+ 'Value' => array(
+ 'type' => 'string'
+ ),
+ )
+ )
+ ),
+ ),
+ 'Status' => array(
+ 'type' => 'string',
+ ),
+ 'Transition' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Date' => array(
+ 'type' => 'string',
+ ),
+ 'Days' => array(
+ 'type' => 'numeric',
+ ),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ 'NoncurrentVersionTransition' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'NoncurrentDays' => array(
+ 'type' => 'numeric',
+ ),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ 'NoncurrentVersionExpiration' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'NoncurrentDays' => array(
+ 'type' => 'numeric',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'GetBucketVersioningOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Status' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'MFADelete' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ 'sentAs' => 'MfaDelete',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'GetBucketReplicationOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Role' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'Rules' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'Rule',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'ReplicationRule',
+ 'type' => 'object',
+ 'sentAs' => 'Rule',
+ 'properties' => array(
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ 'Prefix' => array(
+ 'type' => 'string',
+ ),
+ 'Status' => array(
+ 'type' => 'string',
+ ),
+ 'Destination' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Bucket' => array(
+ 'type' => 'string',
+ ),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'GetBucketLocationOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Location' => array(
+ 'type' => 'string',
+ 'location' => 'body',
+ 'filters' => array(
+ 'strval',
+ 'strip_tags',
+ 'trim',
+ ),
+ ),
+ ),
+ ),
+ 'UploadPartOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'ServerSideEncryption' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption',
+ ),
+ 'ETag' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ ),
+ 'SSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm',
+ ),
+ 'SSECustomerKeyMD5' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5',
+ ),
+ 'SSEKMSKeyId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id',
+ ),
+ 'RequestCharged' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-charged',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'UploadPartCopyOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'CopySourceVersionId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-copy-source-version-id',
+ ),
+ 'ETag' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'LastModified' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'ServerSideEncryption' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption',
+ ),
+ 'SSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm',
+ ),
+ 'SSECustomerKeyMD5' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5',
+ ),
+ 'SSEKMSKeyId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id',
+ ),
+ 'RequestCharged' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-charged',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'PutBucketAclOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id'))),
+ 'PutObjectOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Expiration' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-expiration',
+ ),
+ 'ETag' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ ),
+ 'ServerSideEncryption' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption',
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-version-id',
+ ),
+ 'SSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm',
+ ),
+ 'SSECustomerKeyMD5' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5',
+ ),
+ 'SSEKMSKeyId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id',
+ ),
+ 'RequestCharged' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-charged',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'PutObjectAclOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestCharged' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-charged',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'PutBucketCorsOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'PutBucketLifecycleOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'PutBucketVersioningOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'PutBucketReplicationOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'PutBucketNotificationOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'RestoreObjectOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestCharged' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-charged',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'ListPartsOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Bucket' => array(
+ 'type' => 'string',
+ 'location' => 'xml'),
+ 'Key' => array(
+ 'type' => 'string',
+ 'location' => 'xml'),
+ 'UploadId' => array(
+ 'type' => 'string',
+ 'location' => 'xml'),
+ 'PartNumberMarker' => array(
+ 'type' => 'numeric',
+ 'location' => 'xml'),
+ 'NextPartNumberMarker' => array(
+ 'type' => 'numeric',
+ 'location' => 'xml'),
+ 'MaxParts' => array(
+ 'type' => 'numeric',
+ 'location' => 'xml'),
+ 'IsTruncated' => array(
+ 'type' => 'boolean',
+ 'location' => 'xml'),
+ 'Parts' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'Part',
+ 'data' => array(
+ 'xmlFlattened' => true),
+ 'items' => array(
+ 'name' => 'Part',
+ 'type' => 'object',
+ 'sentAs' => 'Part',
+ 'properties' => array(
+ 'PartNumber' => array(
+ 'type' => 'numeric'),
+ 'LastModified' => array(
+ 'type' => 'string'),
+ 'ETag' => array(
+ 'type' => 'string'),
+ 'Size' => array(
+ 'type' => 'numeric')))),
+ 'Initiator' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'ID' => array(
+ 'type' => 'string'),
+ 'DisplayName' => array(
+ 'type' => 'string'))),
+ 'Owner' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string'),
+ 'ID' => array(
+ 'type' => 'string'))),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ 'location' => 'xml'),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id'))),
+ 'ListObjectsOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'IsTruncated' => array(
+ 'type' => 'boolean',
+ 'location' => 'xml'),
+ 'Marker' => array(
+ 'type' => 'string',
+ 'location' => 'xml'),
+ 'NextMarker' => array(
+ 'type' => 'string',
+ 'location' => 'xml'),
+ 'Contents' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'data' => array(
+ 'xmlFlattened' => true),
+ 'items' => array(
+ 'name' => 'Object',
+ 'type' => 'object',
+ 'properties' => array(
+ 'Key' => array(
+ 'type' => 'string'),
+ 'LastModified' => array(
+ 'type' => 'string'),
+ 'ETag' => array(
+ 'type' => 'string'),
+ 'Size' => array(
+ 'type' => 'numeric'),
+ 'StorageClass' => array(
+ 'type' => 'string'),
+ 'Owner' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string'),
+ 'ID' => array(
+ 'type' => 'string')))))),
+ 'Name' => array(
+ 'type' => 'string',
+ 'location' => 'xml'),
+ 'Prefix' => array(
+ 'type' => 'string',
+ 'location' => 'xml'),
+ 'Delimiter' => array(
+ 'type' => 'string',
+ 'location' => 'xml'),
+ 'MaxKeys' => array(
+ 'type' => 'numeric',
+ 'location' => 'xml'),
+ 'CommonPrefixes' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'data' => array(
+ 'xmlFlattened' => true),
+ 'items' => array(
+ 'name' => 'CommonPrefix',
+ 'type' => 'object',
+ 'properties' => array(
+ 'Prefix' => array(
+ 'type' => 'string')))),
+ 'EncodingType' => array(
+ 'type' => 'string',
+ 'location' => 'xml'),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id'))),
+ 'ListBucketsOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Buckets' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'items' => array(
+ 'name' => 'Bucket',
+ 'type' => 'object',
+ 'sentAs' => 'Bucket',
+ 'properties' => array(
+ 'Name' => array(
+ 'type' => 'string',
+ ),
+ 'CreationDate' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ 'Owner' => array(
+ 'type' => 'object',
+ 'location' => 'xml',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string',
+ ),
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'ListObjectVersionsOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'IsTruncated' => array(
+ 'type' => 'boolean',
+ 'location' => 'xml',
+ ),
+ 'KeyMarker' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'VersionIdMarker' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'NextKeyMarker' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'NextVersionIdMarker' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'Versions' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'Version',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'ObjectVersion',
+ 'type' => 'object',
+ 'sentAs' => 'Version',
+ 'properties' => array(
+ 'ETag' => array(
+ 'type' => 'string',
+ ),
+ 'Size' => array(
+ 'type' => 'numeric',
+ ),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ ),
+ 'Key' => array(
+ 'type' => 'string',
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ ),
+ 'IsLatest' => array(
+ 'type' => 'boolean',
+ ),
+ 'LastModified' => array(
+ 'type' => 'string',
+ ),
+ 'Owner' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string',
+ ),
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ 'DeleteMarkers' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'DeleteMarker',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'DeleteMarkerEntry',
+ 'type' => 'object',
+ 'sentAs' => 'DeleteMarker',
+ 'properties' => array(
+ 'Owner' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string',
+ ),
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ 'Key' => array(
+ 'type' => 'string',
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ ),
+ 'IsLatest' => array(
+ 'type' => 'boolean',
+ ),
+ 'LastModified' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ 'Name' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'Prefix' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'Delimiter' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'MaxKeys' => array(
+ 'type' => 'numeric',
+ 'location' => 'xml',
+ ),
+ 'CommonPrefixes' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'CommonPrefix',
+ 'type' => 'object',
+ 'properties' => array(
+ 'Prefix' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ 'EncodingType' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'ListMultipartUploadsOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'Bucket' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'KeyMarker' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'UploadIdMarker' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'NextKeyMarker' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'Prefix' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'Delimiter' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'NextUploadIdMarker' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'MaxUploads' => array(
+ 'type' => 'numeric',
+ 'location' => 'xml',
+ ),
+ 'IsTruncated' => array(
+ 'type' => 'boolean',
+ 'location' => 'xml',
+ ),
+ 'Uploads' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'Upload',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'MultipartUpload',
+ 'type' => 'object',
+ 'sentAs' => 'Upload',
+ 'properties' => array(
+ 'UploadId' => array(
+ 'type' => 'string',
+ ),
+ 'Key' => array(
+ 'type' => 'string',
+ ),
+ 'Initiated' => array(
+ 'type' => 'string',
+ ),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ ),
+ 'Owner' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'DisplayName' => array(
+ 'type' => 'string',
+ ),
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ 'Initiator' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'ID' => array(
+ 'type' => 'string',
+ ),
+ 'DisplayName' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ 'CommonPrefixes' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'CommonPrefix',
+ 'type' => 'object',
+ 'properties' => array(
+ 'Prefix' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ 'EncodingType' => array(
+ 'type' => 'string',
+ 'location' => 'xml',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'HeadObjectOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'DeleteMarker' => array(
+ 'type' => 'boolean',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-delete-marker',
+ ),
+ 'AcceptRanges' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'accept-ranges',
+ ),
+ 'Expiration' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-expiration',
+ ),
+ 'Restore' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-restore',
+ ),
+ 'LastModified' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Last-Modified',
+ ),
+ 'ContentLength' => array(
+ 'type' => 'numeric',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Length',
+ ),
+ 'ETag' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ ),
+ 'MissingMeta' => array(
+ 'type' => 'numeric',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-missing-meta',
+ ),
+ 'VersionId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-version-id',
+ ),
+ 'CacheControl' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Cache-Control',
+ ),
+ 'ContentDisposition' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Disposition',
+ ),
+ 'ContentEncoding' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Encoding',
+ ),
+ 'ContentLanguage' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Language',
+ ),
+ 'ContentType' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'Content-Type',
+ ),
+ 'Expires' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ ),
+ 'WebsiteRedirectLocation' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-website-redirect-location',
+ ),
+ 'ServerSideEncryption' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption',
+ ),
+ 'Metadata' => array(
+ 'type' => 'object',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-meta-',
+ 'additionalProperties' => array(
+ 'type' => 'string',
+ ),
+ ),
+ 'SSECustomerAlgorithm' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm',
+ ),
+ 'SSECustomerKeyMD5' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5',
+ ),
+ 'SSEKMSKeyId' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id',
+ ),
+ 'StorageClass' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-storage-class',
+ ),
+ 'RequestCharged' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-charged',
+ ),
+ 'ReplicationStatus' => array(
+ 'type' => 'string',
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-replication-status',
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ))),
+ 'HeadBucketOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ ),
+ 'GetBucketNotificationOutput' => array(
+ 'type' => 'object',
+ 'additionalProperties' => true,
+ 'properties' => array(
+ 'CloudFunctionConfigurations' => array(
+ 'type' => 'array',
+ 'location' => 'xml',
+ 'sentAs' => 'CloudFunctionConfiguration',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'CloudFunctionConfiguration',
+ 'type' => 'object',
+ 'sentAs' => 'CloudFunctionConfiguration',
+ 'properties' => array(
+ 'Id' => array(
+ 'type' => 'string',
+ ),
+ 'CloudFunction' => array(
+ 'type' => 'string',
+ 'sentAs' => 'CloudFunction',
+ ),
+ 'Events' => array(
+ 'type' => 'array',
+ 'sentAs' => 'Event',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'Event',
+ 'type' => 'string',
+ 'sentAs' => 'Event',
+ ),
+ ),
+ 'Filter' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'Key' => array(
+ 'type' => 'object',
+ 'sentAs' => 'Key',
+ 'properties' => array(
+ 'FilterRules' => array(
+ 'type' => 'array',
+ 'sentAs' => 'FilterRule',
+ 'data' => array(
+ 'xmlFlattened' => true,
+ ),
+ 'items' => array(
+ 'name' => 'FilterRule',
+ 'type' => 'object',
+ 'sentAs' => 'FilterRule',
+ 'properties' => array(
+ 'Name' => array(
+ 'type' => 'string',
+ ),
+ 'Value' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ 'RequestId' => array(
+ 'location' => 'header',
+ 'sentAs' => 'x-cos-request-id',
+ ),
+ ),
+ )));
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Signature.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Signature.php
new file mode 100755
index 0000000..2b1b46c
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Signature.php
@@ -0,0 +1,50 @@
+<?php
+namespace Qcloud\Cos;
+use Guzzle\Http\Message\RequestInterface;
+class Signature {
+ private $accessKey; // string: access key.
+ private $secretKey; // string: secret key.
+ public function __construct($accessKey, $secretKey) {
+ $this->accessKey = $accessKey;
+ $this->secretKey = $secretKey;
+ }
+ public function __destruct() {
+ }
+ public function signRequest(RequestInterface $request) {
+ $signTime = (string)(time() - 60) . ';' . (string)(time() + 3600);
+ $httpString = strtolower($request->getMethod()) . "\n" . urldecode($request->getPath()) .
+ "\n\nhost=" . $request->getHost() . "\n";
+ $sha1edHttpString = sha1($httpString);
+ $stringToSign = "sha1\n$signTime\n$sha1edHttpString\n";
+ $signKey = hash_hmac('sha1', $signTime, $this->secretKey);
+ $signature = hash_hmac('sha1', $stringToSign, $signKey);
+ $authorization = 'q-sign-algorithm=sha1&q-ak='. $this->accessKey .
+ "&q-sign-time=$signTime&q-key-time=$signTime&q-header-list=host&q-url-param-list=&" .
+ "q-signature=$signature";
+ $request->setHeader('Authorization', $authorization);
+ }
+ public function createAuthorization(
+ RequestInterface $request,
+ $expires = "10 minutes"
+ ) {
+ $signTime = (string)(time() - 60) . ';' . (string)(strtotime($expires));
+ $httpString = strtolower($request->getMethod()) . "\n" . urldecode($request->getPath()) .
+ "\n\nhost=" . $request->getHost() . "\n";
+ $sha1edHttpString = sha1($httpString);
+ $stringToSign = "sha1\n$signTime\n$sha1edHttpString\n";
+ $signKey = hash_hmac('sha1', $signTime, $this->secretKey);
+ $signature = hash_hmac('sha1', $stringToSign, $signKey);
+ $authorization = 'q-sign-algorithm=sha1&q-ak='. $this->accessKey .
+ "&q-sign-time=$signTime&q-key-time=$signTime&q-header-list=host&q-url-param-list=&" .
+ "q-signature=$signature";
+ return $authorization;
+ }
+ public function createPresignedUrl(
+ RequestInterface $request,
+ $expires = "10 minutes"
+ ) {
+ $authorization = $this->createAuthorization($request, $expires);
+ $request->getQuery()->add('sign', $authorization);
+ return $request->getUrl();
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/SignatureListener.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/SignatureListener.php
new file mode 100755
index 0000000..a979583
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/SignatureListener.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Qcloud\Cos;
+
+use Guzzle\Common\Event;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Listener used to sign requests before they are sent over the wire.
+ */
+class SignatureListener implements EventSubscriberInterface {
+ // cos signature.
+ protected $signature;
+
+ /**
+ * Construct a new request signing plugin
+ */
+ public function __construct($accessKey, $secretKey) {
+ $this->signature = new Signature($accessKey, $secretKey);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'request.before_send' => array('onRequestBeforeSend', -255));
+ }
+
+ /**
+ * Signs requests before they are sent
+ *
+ * @param Event $event Event emitted
+ */
+ public function onRequestBeforeSend(Event $event) {
+
+ $this->signature->signRequest($event['request']);
+/*
+ if(!$this->credentials instanceof NullCredentials) {
+ $this->signature->signRequest($event['request'], $this->credentials);
+ }
+*/
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/Test.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/Test.php
new file mode 100755
index 0000000..8b50b18
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/Test.php
@@ -0,0 +1,1347 @@
+<?php
+
+namespace Qcloud\Cos\Tests;
+
+use Qcloud\Cos\Client;
+use Qcloud\Cos\Exception\ServiceResponseException;
+class BucketTest extends \PHPUnit_Framework_TestCase
+{
+ private $cosClient;
+ private $bucket;
+ protected function setUp()
+ {
+ $this->bucket = getenv('COS_BUCKET');
+ TestHelper::nuke($this->bucket);
+ $this->cosClient = new Client(array('region' => getenv('COS_REGION'),
+ 'credentials' => array(
+ 'appId' => getenv('COS_APPID'),
+ 'secretId' => getenv('COS_KEY'),
+ 'secretKey' => getenv('COS_SECRET'))));
+ sleep(5);
+ }
+
+ protected function tearDown()
+ {
+ TestHelper::nuke($this->bucket);
+ }
+
+ /**********************************
+ * TestBucket
+ **********************************/
+
+ /*
+ * put bucket,bucket已经存在
+ * BucketAlreadyOwnedByYou
+ * 409
+ */
+ public function testCreateExistingBucket()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ } catch (ServiceResponseException $e) {
+ $this->assertTrue($e->getExceptionCode() === 'BucketAlreadyOwnedByYou' && $e->getStatusCode() === 409);
+ }
+ }
+
+ /*
+ * put bucket,bucket名称非法
+ * InvalidBucketName
+ * 400
+ */
+ public function testCreateInvalidBucket()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => 'qwe_213'));
+ } catch (ServiceResponseException $e) {
+ $this->assertTrue($e->getExceptionCode() === 'InvalidBucketName' && $e->getStatusCode() === 400);
+ }
+ }
+
+ /*
+ * put bucket,设置bucket公公权限为private
+ * 200
+ */
+ public function testCreatePrivateBucket()
+ {
+ try {
+ $this->cosClient->createBucket(
+ array(
+ 'Bucket' => $this->bucket,
+ 'ACL'=>'private'
+ ));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket,设置bucket公公权限为public-read
+ * 200
+ */
+ public function testCreatePublicReadBucket()
+ {
+ try {
+ $this->cosClient->createBucket(
+ array(
+ 'Bucket' => $this->bucket,
+ 'ACL'=>'public-read'
+ ));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket,公共权限非法
+ * InvalidArgument
+ * 400
+ */
+ public function testCreateInvalidACLBucket()
+ {
+ try {
+ $this->cosClient->createBucket(
+ array(
+ 'Bucket' => $this->bucket,
+ 'ACL'=>'public'
+ ));
+ } catch (ServiceResponseException $e) {
+ $this->assertTrue($e->getExceptionCode() === 'InvalidArgument' && $e->getStatusCode() === 400);
+ }
+ }
+
+ /*
+ * put bucket acl,设置bucket公共权限为private
+ * 200
+ */
+ public function testPutBucketAclPrivate()
+ {
+ try {
+
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->PutBucketAcl(
+ array(
+ 'Bucket' => $this->bucket,
+ 'ACL'=>'private'
+ ));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket acl,设置bucket公共权限为public-read
+ * 200
+ */
+ public function testPutBucketAclPublicRead()
+ {
+ try {
+
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->PutBucketAcl(
+ array(
+ 'Bucket' => $this->bucket,
+ 'ACL'=>'public-read'
+ ));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket acl,公共权限非法
+ * InvalidArgument
+ * 400
+ */
+ public function testPutBucketAclInvalid()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->PutBucketAcl(
+ array(
+ 'Bucket' => $this->bucket,
+ 'ACL'=>'public'
+ ));
+ } catch (ServiceResponseException $e) {
+ $this->assertTrue($e->getExceptionCode() === 'InvalidArgument' && $e->getStatusCode() === 400);
+ }
+ }
+
+ /*
+ * put bucket acl,设置bucket账号权限为grant-read
+ * 200
+ */
+ public function testPutBucketAclReadToUser()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->PutBucketAcl(array(
+ 'Bucket' => $this->bucket,
+ 'GrantRead' => 'id="qcs::cam::uin/2779643970:uin/2779643970"'));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket acl,设置bucket账号权限为grant-write
+ * 200
+ */
+ public function testPutBucketAclWriteToUser()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->PutBucketAcl(array(
+ 'Bucket' => $this->bucket,
+ 'GrantWrite' => 'id="qcs::cam::uin/2779643970:uin/2779643970"'));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket acl,设置bucket账号权限为grant-full-control
+ * 200
+ */
+ public function testPutBucketAclFullToUser()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->PutBucketAcl(array(
+ 'Bucket' => $this->bucket,
+ 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"'));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket acl,设置bucket账号权限,同时授权给多个账户
+ * 200
+ */
+ public function testPutBucketAclToUsers()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->PutBucketAcl(array(
+ 'Bucket' => $this->bucket,
+ 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970",id="qcs::cam::uin/2779643970:uin/2779643970",id="qcs::cam::uin/2779643970:uin/2779643970"'));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket acl,设置bucket账号权限,授权给子账号
+ * 200
+ */
+ public function testPutBucketAclToSubuser()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->PutBucketAcl(array(
+ 'Bucket' => $this->bucket,
+ 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"'));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket acl,设置bucket账号权限,同时指定read、write和fullcontrol
+ * 200
+ */
+ public function testPutBucketAclReadWriteFull()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->PutBucketAcl(array(
+ 'Bucket' => $this->bucket,
+ 'GrantRead' => 'id="qcs::cam::uin/123:uin/123"',
+ 'GrantWrite' => 'id="qcs::cam::uin/2779643970:uin/2779643970"',
+ 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"',));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket acl,设置bucket账号权限,grant值非法
+ * InvalidArgument
+ * 400
+ */
+ public function testPutBucketAclInvalidGrant()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->PutBucketAcl(array(
+ 'Bucket' => $this->bucket,
+ 'GrantFullControl' => 'id="qcs::camuin/321023:uin/2779643970"',));
+ } catch (ServiceResponseException $e) {
+ $this->assertTrue($e->getExceptionCode() === 'InvalidArgument' && $e->getStatusCode() === 400);
+ }
+ }
+
+ /*
+ * put bucket acl,设置bucket账号权限,通过body方式授权
+ * 200
+ */
+ public function testPutBucketAclByBody()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->PutBucketAcl(array(
+ 'Bucket' => $this->bucket,
+ 'Grants' => array(
+ array(
+ 'Grantee' => array(
+ 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'Type' => 'CanonicalUser',
+ ),
+ 'Permission' => 'FULL_CONTROL',
+ ),
+ // ... repeated
+ ),
+ 'Owner' => array(
+ 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ )));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket acl,设置bucket账号权限,通过body方式授权给anyone
+ * 200
+ */
+ public function testPutBucketAclByBodyToAnyone()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->PutBucketAcl(array(
+ 'Bucket' => $this->bucket,
+ 'Grants' => array(
+ array(
+ 'Grantee' => array(
+ 'DisplayName' => 'qcs::cam::anyone:anyone',
+ 'ID' => 'qcs::cam::anyone:anyone',
+ 'Type' => 'CanonicalUser',
+ ),
+ 'Permission' => 'FULL_CONTROL',
+ ),
+ // ... repeated
+ ),
+ 'Owner' => array(
+ 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ )));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket acl,bucket不存在
+ * NoSuchBucket
+ * 404
+ */
+ public function testPutBucketAclBucketNonexisted()
+ {
+ try {
+ $this->cosClient->PutBucketAcl(array(
+ 'Bucket' => $this->bucket,
+ 'GrantFullControl' => 'id="qcs::cam::uin/321023:uin/2779643970"',));
+ } catch (ServiceResponseException $e) {
+// echo($e->getExceptionCode());
+// echo($e->getStatusCode());
+ $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket' && $e->getStatusCode() === 404);
+ }
+ }
+
+ /*
+ * put bucket acl,覆盖设置
+ * x200
+ */
+ public function testPutBucketAclCover()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->PutBucketAcl(array(
+ 'Bucket' => $this->bucket,
+ 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"',
+ 'GrantRead' => 'id="qcs::cam::uin/2779643970:uin/2779643970"',
+ 'GrantWrite' => 'id="qcs::cam::uin/2779643970:uin/2779643970"'));
+ $this->cosClient->PutBucketAcl(array(
+ 'Bucket' => $this->bucket,
+ 'GrantWrite' => 'id="qcs::cam::uin/2779643970:uin/2779643970"'));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * 正常head bucket
+ * 200
+ */
+ public function testHeadBucket()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->HeadBucket(array(
+ 'Bucket' => $this->bucket));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * head bucket,bucket不存在
+ * NoSuchBucket
+ * 404
+ */
+ public function testHeadBucketNonexisted()
+ {
+ try {
+ $this->cosClient->HeadBucket(array(
+ 'Bucket' => $this->bucket,));
+ } catch (ServiceResponseException $e) {
+// echo($e->getExceptionCode());
+// echo($e->getStatusCode());
+ $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket' && $e->getStatusCode() === 404);
+ }
+ }
+
+ /*
+ * get bucket,bucket为空
+ * 200
+ */
+ public function testGetBucketEmpty()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->ListObjects(array(
+ 'Bucket' => $this->bucket));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * get bucket,bucket不存在
+ * NoSuchBucket
+ * 404
+ */
+ public function testGetBucketNonexisted()
+ {
+ try {
+ $this->cosClient->ListObjects(array(
+ 'Bucket' => $this->bucket,));
+ } catch (ServiceResponseException $e) {
+ $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket' && $e->getStatusCode() === 404);
+ }
+ }
+
+
+ /*
+ * put bucket cors,cors规则包含多条
+ * 200
+ */
+ public function testPutBucketCors()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putBucketCors(array(
+ // Bucket is required
+ 'Bucket' => $this->bucket,
+ // CORSRules is required
+ 'CORSRules' => array(
+ array(
+ 'ID' => '1234',
+ 'AllowedHeaders' => array('*',),
+ // AllowedMethods is required
+ 'AllowedMethods' => array('PUT',),
+ // AllowedOrigins is required
+ 'AllowedOrigins' => array('*',),
+ 'ExposeHeaders' => array('*',),
+ 'MaxAgeSeconds' => 1,
+ ),
+ array(
+ 'ID' => '12345',
+ 'AllowedHeaders' => array('*',),
+ // AllowedMethods is required
+ 'AllowedMethods' => array('PUT',),
+ // AllowedOrigins is required
+ 'AllowedOrigins' => array('*',),
+ 'ExposeHeaders' => array('*',),
+ 'MaxAgeSeconds' => 1,
+ ),
+ // ... repeated
+ ),
+ ));
+ $this->cosClient->getBucketCors(array(
+ // Bucket is required
+ 'Bucket' => $this->bucket,));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+
+ /*
+ * 正常get bucket cors
+ * 200
+ */
+ public function testGetBucketCors()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putBucketCors(array(
+ // Bucket is required
+ 'Bucket' => $this->bucket,
+ // CORSRules is required
+ 'CORSRules' => array(
+ array(
+ 'ID' => '1234',
+ 'AllowedHeaders' => array('*',),
+ // AllowedMethods is required
+ 'AllowedMethods' => array('PUT',),
+ // AllowedOrigins is required
+ 'AllowedOrigins' => array('*',),
+ 'ExposeHeaders' => array('*',),
+ 'MaxAgeSeconds' => 1,
+ ),
+ array(
+ 'ID' => '12345',
+ 'AllowedHeaders' => array('*',),
+ // AllowedMethods is required
+ 'AllowedMethods' => array('PUT',),
+ // AllowedOrigins is required
+ 'AllowedOrigins' => array('*',),
+ 'ExposeHeaders' => array('*',),
+ 'MaxAgeSeconds' => 1,
+ ),
+ // ... repeated
+ ),
+ ));
+ $this->cosClient->getBucketCors(array(
+ // Bucket is required
+ 'Bucket' => $this->bucket,));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * bucket未设置cors规则,发送get bucket cors
+ * NoSuchCORSConfiguration
+ * 404
+ */
+ public function testGetBucketCorsNull()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->getBucketCors(array(
+ // Bucket is required
+ 'Bucket' => $this->bucket,));
+ } catch (ServiceResponseException $e) {
+// echo($e->getExceptionCode());
+// echo($e->getStatusCode());
+ $this->assertTrue($e->getExceptionCode() === 'NoSuchCORSConfiguration' && $e->getStatusCode() === 404);
+ }
+ }
+
+ /*
+ * bucket未设置cors规则,发送get bucket cors
+ * NoSuchCORSConfiguration
+ * 404
+ */
+ public function testGetBucketCorsNonExisted()
+ {
+ try {
+ $this->cosClient->getBucketCors(array(
+ // Bucket is required
+ 'Bucket' => $this->bucket,));
+ } catch (ServiceResponseException $e) {
+// echo($e->getExceptionCode());
+// echo($e->getStatusCode());
+ $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket' && $e->getStatusCode() === 404);
+ }
+ }
+
+ /*
+ * 正常get bucket lifecycle
+ * 200
+ */
+ public function testGetBucketLifecycle()
+ {
+ try {
+ $result = $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $result = $this->cosClient->putBucketLifecycle(array(
+ 'Bucket' => $this->bucket,
+ 'Rules' => array(
+ array(
+ 'Status' => 'Enabled',
+ 'Filter' => array(
+ 'Tag' => array(
+ 'Key' => 'datalevel',
+ 'Value' => 'backup'
+ )
+ ),
+ 'Transitions' => array(
+ array(
+ # 30天后转换为Standard_IA
+ 'Days' => 30,
+ 'StorageClass' => 'Standard_IA'),
+ array(
+ # 365天后转换为Archive
+ 'Days' => 365,
+ 'StorageClass' => 'Archive')
+ ),
+ 'Expiration' => array(
+ # 3650天后过期删除
+ 'Days' => 3650,
+ )
+ )
+ )
+ ));
+ sleep(3);
+ $result = $this->cosClient->getBucketLifecycle(array(
+ // Bucket is required
+ 'Bucket' => $this->bucket,
+ ));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * 正常delete bucket lifecycle
+ * 200
+ */
+ public function testDeleteBucketLifecycle()
+ {
+ try {
+ $result = $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $result = $this->cosClient->putBucketLifecycle(array(
+ 'Bucket' => $this->bucket,
+ 'Rules' => array(
+ array(
+ 'Status' => 'Enabled',
+ 'Filter' => array(
+ 'Tag' => array(
+ 'Key' => 'datalevel',
+ 'Value' => 'backup'
+ )
+ ),
+ 'Transitions' => array(
+ array(
+ # 30天后转换为Standard_IA
+ 'Days' => 30,
+ 'StorageClass' => 'Standard_IA'),
+ array(
+ # 365天后转换为Archive
+ 'Days' => 365,
+ 'StorageClass' => 'Archive')
+ ),
+ 'Expiration' => array(
+ # 3650天后过期删除
+ 'Days' => 3650,
+ )
+ )
+ )
+ ));
+ $result = $this->cosClient->deleteBucketLifecycle(array(
+ // Bucket is required
+ 'Bucket' => $this->bucket,
+ ));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket lifecycle,请求body中不指定filter
+ * 200
+ */
+ public function testPutBucketLifecycleNonFilter()
+ {
+ try {
+ $result = $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $result = $this->cosClient->putBucketLifecycle(array(
+ // Bucket is required
+ 'Bucket' => $this->bucket,
+ // Rules is required
+ 'Rules' => array(
+ array(
+ 'Expiration' => array(
+ 'Days' => 1000,
+ ),
+ 'ID' => 'id1',
+ // Status is required
+ 'Status' => 'Enabled',
+ 'Transitions' => array(
+ array(
+ 'Days' => 100,
+ 'StorageClass' => 'Standard_IA'),
+ ),
+ // ... repeated
+ ),
+ )));
+ } catch (ServiceResponseException $e) {
+ $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket' && $e->getStatusCode() === 404);
+
+ }
+ }
+
+ /*
+ * put bucket,bucket名称带有-
+ * 200
+ */
+ public function testPutBucket2()
+ {
+ try {
+ try{
+ $this->cosClient->deleteBucket(array('Bucket' => '12345-'.$this->bucket));
+ } catch (\Exception $e) {
+ }
+ $this->cosClient->createBucket(array('Bucket' => '12345-'.$this->bucket));
+ $this->cosClient->deleteBucket(array('Bucket' => '12345-'.$this->bucket));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put bucket,bucket名称带有两个-
+ * 200
+ */
+ public function testPutBucket3()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket.'-12333-4445'));
+ $this->cosClient->deleteBucket(array('Bucket' => $this->bucket.'-12333-4445'));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * 正常get bucket location
+ * 200
+ */
+ public function testGetBucketLocation()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->getBucketLocation(array('Bucket' => $this->bucket));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * bucket不存在,发送get bucket location请求
+ * NoSuchBucket
+ * 404
+ */
+ public function testGetBucketLocationNonExisted()
+ {
+ try {
+ $this->cosClient->getBucketLocation(array('Bucket' => $this->bucket));
+ } catch (ServiceResponseException $e) {
+ // echo($e->getExceptionCode());
+ // echo($e->getStatusCode());
+ $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket' && $e->getStatusCode() === 404);
+ }
+ }
+
+ /**********************************
+ * TestObject
+ **********************************/
+
+ /*
+ * put object,请求头部携带服务端加密参数
+ * 200
+ */
+ public function testPutObjectEncryption()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putObject(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '11//32//43',
+ 'Body' => 'Hello World!',
+ 'ServerSideEncryption' => 'AES256'));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * 上传文件Bucket不存在
+ * NoSuchBucket
+ * 404
+ */
+ public function testPutObjectIntoNonexistedBucket() {
+ try {
+ $this->cosClient->putObject(array(
+ 'Bucket' => $this->bucket, 'Key' => 'hello.txt', 'Body' => 'Hello World'));
+ } catch (ServiceResponseException $e) {
+ $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket');
+ $this->assertTrue($e->getStatusCode() === 404);
+ }
+ }
+
+
+ /*
+ * 上传小文件
+ * 200
+ */
+ public function testUploadSmallObject() {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->upload($this->bucket, '你好.txt', 'Hello World');
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * 上传空文件
+ * 200
+ */
+ public function testPutObjectEmpty() {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->upload($this->bucket, '你好.txt', '123');
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * 上传已存在的文件
+ * 200
+ */
+ public function testPutObjectExisted() {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->upload($this->bucket, '你好.txt', '1234124');
+ $this->cosClient->upload($this->bucket, '你好.txt', '请二位qwe');
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put object,请求头部携带自定义头部x-cos-meta-
+ * 200
+ */
+ public function testPutObjectMeta() {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putObject(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'Body' => '1234124',
+ 'Metadata' => array(
+ 'lew' => str_repeat('a', 1 * 1024),
+ )));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put object,请求头部携带自定义头部x-cos-meta-
+ * KeyTooLong
+ * 400
+ */
+ public function testPutObjectMeta2K() {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putObject(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'Body' => '1234124',
+ 'Metadata' => array(
+ 'lew' => str_repeat('a', 3 * 1024),
+ )));
+ } catch (ServiceResponseException $e) {
+// echo($e->getExceptionCode());
+// echo($e->getStatusCode());
+ $this->assertTrue($e->getExceptionCode() === 'KeyTooLong' && $e->getStatusCode() === 400);
+ }
+ }
+
+ /*
+ * 上传复杂文件名的文件
+ * 200
+ */
+ public function testUploadComplexObject() {
+ try {
+ $result = $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->upload($this->bucket, '→↓←→↖↗↙↘! \"#$%&\'()*+,-./0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~', 'Hello World');
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * 上传大文件
+ * 200
+ */
+ public function testUploadLargeObject() {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->upload($this->bucket, 'hello.txt', str_repeat('a', 9 * 1024 * 1024));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * 下载文件
+ * 200
+ */
+ public function testGetObject() {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->upload($this->bucket, '你好.txt', 'Hello World');
+ $this->cosClient->getObject(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * get object,object名称包含特殊字符
+ * 200
+ */
+ public function testGetObjectSpecialName() {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->upload($this->bucket, '你好<>!@#^%^&*&(&^!@#@!.txt', 'Hello World');
+ $this->cosClient->getObject(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好<>!@#^%^&*&(&^!@#@!.txt',));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * get object,请求头部带if-match,参数值为true
+ * 200
+ */
+ public function testGetObjectIfMatchTrue() {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->upload($this->bucket, '你好.txt', 'Hello World');
+ $this->cosClient->getObject(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'IfMatch' => '"b10a8db164e0754105b7a99be72e3fe5"'));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+
+ /*
+ * get object,请求头部带if-match,参数值为false
+ * PreconditionFailed
+ * 412
+ */
+ public function testGetObjectIfMatchFalse() {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->upload($this->bucket, '你好.txt', 'Hello World');
+ $this->cosClient->getObject(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'IfMatch' => '""'));
+ } catch (ServiceResponseException $e) {
+// echo($e->getExceptionCode());
+// echo($e->getStatusCode());
+ $this->assertTrue($e->getExceptionCode() === 'PreconditionFailed' && $e->getStatusCode() === 412);
+ }
+ }
+
+ /*
+ * get object,请求头部带if-none-match,参数值为true
+ * 200
+ */
+ public function testGetObjectIfNoneMatchTrue() {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->upload($this->bucket, '你好.txt', 'Hello World');
+ $this->cosClient->getObject(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'IfNoneMatch' => '"b10a8db164e0754105b7a99be72e3fe5"'));
+ } catch (ServiceResponseException $e) {
+ $this->assertTrue($e->getExceptionCode() === 'NotModified' && $e->getStatusCode() === 304);
+ }
+ }
+
+
+ /*
+ * get object,请求头部带if-none-match,参数值为false
+ * PreconditionFailed
+ * 412
+ */
+ public function testGetObjectIfNoneMatchFalse() {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->upload($this->bucket, '你好.txt', 'Hello World');
+ $this->cosClient->getObject(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'IfNoneMatch' => '""'));
+
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * 获取文件url
+ * 200
+ */
+ public function testGetObjectUrl() {
+ try{
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->getObjectUrl($this->bucket, 'hello.txt', '+10 minutes');
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * 设置objectacl
+ * 200
+ */
+ public function testPutObjectACL() {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->upload($this->bucket, '11', 'hello.txt');
+ $this->cosClient->PutObjectAcl(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '11',
+ 'Grants' => array(
+ array(
+ 'Grantee' => array(
+ 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'Type' => 'CanonicalUser',
+ ),
+ 'Permission' => 'FULL_CONTROL',
+ ),
+ // ... repeated
+ ),
+ 'Owner' => array(
+ 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ )));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+
+ }
+
+
+ /*
+ * 获取objectacl
+ * 200
+ */
+ public function testGetObjectACL()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->upload($this->bucket, '11', 'hello.txt');
+ $this->cosClient->PutObjectAcl(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '11',
+ 'Grants' => array(
+ array(
+ 'Grantee' => array(
+ 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'Type' => 'CanonicalUser',
+ ),
+ 'Permission' => 'FULL_CONTROL',
+ ),
+ // ... repeated
+ ),
+ 'Owner' => array(
+ 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ )));
+
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put object acl,设置object公共权限为private
+ * 200
+ */
+ public function testPutObjectAclPrivate()
+ {
+ try {
+
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123'));
+ $this->cosClient->PutObjectAcl(
+ array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'ACL'=>'private'
+ ));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put object acl,设置object公共权限为public-read
+ * 200
+ */
+ public function testPutObjectAclPublicRead()
+ {
+ try {
+
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123'));
+ $this->cosClient->PutObjectAcl(
+ array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'ACL'=>'public-read'
+ ));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put object acl,公共权限非法
+ * InvalidArgument
+ * 400
+ */
+ public function testPutObjectAclInvalid()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123'));
+ $this->cosClient->PutObjectAcl(
+ array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'ACL'=>'public'
+ ));
+ } catch (ServiceResponseException $e) {
+ $this->assertTrue($e->getExceptionCode() === 'InvalidArgument' && $e->getStatusCode() === 400);
+ }
+ }
+
+ /*
+ * put object acl,设置object账号权限为grant-read
+ * 200
+ */
+ public function testPutObjectAclReadToUser()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123'));
+ $this->cosClient->PutObjectAcl(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'GrantRead' => 'id="qcs::cam::uin/2779643970:uin/2779643970"'));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put object acl,设置object账号权限为grant-write
+ * 200
+ */
+// public function testPutObjectAclWriteToUser()
+// {
+// try {
+// $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+// $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123'));
+// $this->cosClient->PutObjectAcl(array(
+// 'Bucket' => $this->bucket,
+// 'Key' => '你好.txt',
+// 'GrantWrite' => 'id="qcs::cam::uin/2779643970:uin/2779643970"'));
+// } catch (ServiceResponseException $e) {
+// $this->assertFalse(true, $e);
+// }
+// }
+
+ /*
+ * put object acl,设置object账号权限为grant-full-control
+ * 200
+ */
+ public function testPutObjectAclFullToUser()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123'));
+ $this->cosClient->PutObjectAcl(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"'));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put object acl,设置object账号权限,同时授权给多个账户
+ * 200
+ */
+ public function testPutObjectAclToUsers()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123'));
+ $this->cosClient->PutObjectAcl(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970",id="qcs::cam::uin/2779643970:uin/2779643970",id="qcs::cam::uin/2779643970:uin/2779643970"'));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put object acl,设置object账号权限,授权给子账号
+ * 200
+ */
+ public function testPutObjectAclToSubuser()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123'));
+ $this->cosClient->PutObjectAcl(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"'));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put object acl,设置object账号权限,同时指定read、write和fullcontrol
+ * 200
+ */
+// public function testPutObjectAclReadWriteFull()
+// {
+// try {
+// $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+// $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123'));
+// $this->cosClient->PutObjectAcl(array(
+// 'Bucket' => $this->bucket,
+// 'Key' => '你好.txt',
+// 'GrantRead' => 'id="qcs::cam::uin/123:uin/123"',
+// 'GrantWrite' => 'id="qcs::cam::uin/2779643970:uin/2779643970"',
+// 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"',));
+// } catch (ServiceResponseException $e) {
+// $this->assertFalse(true, $e);
+// }
+// }
+
+ /*
+ * put object acl,设置object账号权限,grant值非法
+ * InvalidArgument
+ * 400
+ */
+ public function testPutObjectAclInvalidGrant()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123'));
+ $this->cosClient->PutObjectAcl(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'GrantFullControl' => 'id="qcs::camuin/321023:uin/2779643970"',));
+ } catch (ServiceResponseException $e) {
+ $this->assertTrue($e->getExceptionCode() === 'InvalidArgument' && $e->getStatusCode() === 400);
+ }
+ }
+
+ /*
+ * put object acl,设置object账号权限,通过body方式授权
+ * 200
+ */
+ public function testPutObjectAclByBody()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123'));
+ $this->cosClient->PutObjectAcl(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'Grants' => array(
+ array(
+ 'Grantee' => array(
+ 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'Type' => 'CanonicalUser',
+ ),
+ 'Permission' => 'FULL_CONTROL',
+ ),
+ // ... repeated
+ ),
+ 'Owner' => array(
+ 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ )));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+ /*
+ * put object acl,设置object账号权限,通过body方式授权给anyone
+ * 200
+ */
+ public function testPutObjectAclByBodyToAnyone()
+ {
+ try {
+ $this->cosClient->createBucket(array('Bucket' => $this->bucket));
+ $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123'));
+ $this->cosClient->putObjectAcl(array(
+ 'Bucket' => $this->bucket,
+ 'Key' => '你好.txt',
+ 'Grants' => array(
+ array(
+ 'Grantee' => array(
+ 'DisplayName' => 'qcs::cam::anyone:anyone',
+ 'ID' => 'qcs::cam::anyone:anyone',
+ 'Type' => 'CanonicalUser',
+ ),
+ 'Permission' => 'FULL_CONTROL',
+ ),
+ // ... repeated
+ ),
+ 'Owner' => array(
+ 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970',
+ )));
+ } catch (ServiceResponseException $e) {
+ $this->assertFalse(true, $e);
+ }
+ }
+
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/TestHelper.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/TestHelper.php
new file mode 100755
index 0000000..367b887
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/TestHelper.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Qcloud\Cos\Tests;
+
+use Qcloud\Cos\Client;
+
+class TestHelper {
+
+ public static function nuke($bucket) {
+ try {
+ $cosClient = new Client(array('region' => getenv('COS_REGION'),
+ 'credentials'=> array(
+ 'appId' => getenv('COS_APPID'),
+ 'secretId' => getenv('COS_KEY'),
+ 'secretKey' => getenv('COS_SECRET'))));
+ $result = $cosClient->listObjects(array('Bucket' => $bucket));
+ if ($result->get('Contents')) {
+ foreach ($result ->get('Contents') as $content) {
+ $cosClient->deleteObject(array('Bucket' => $bucket, 'Key' => $content['Key']));
+ }
+ }
+ $cosClient->deleteBucket(array('Bucket' => $bucket));
+
+ while(True){
+ $result = $cosClient->ListMultipartUploads(
+ array('Bucket' => $bucket,
+ 'Prefix' => ''));
+ if (count($result['Uploads']) == 0){
+ break;
+ }
+ foreach ($result['Uploads'] as $upload) {
+ try {
+ $rt = $cosClient->AbortMultipartUpload(
+ array('Bucket' => $bucket,
+ 'Key' => $upload['Key'],
+ 'UploadId' => $upload['UploadId']));
+ print_r($rt);
+ } catch (\Exception $e) {
+ print_r($e);
+ }
+ }
+ }
+ } catch (\Exception $e) {
+ //echo "$e\n";
+ // Ignore
+ }
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/TokenListener.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/TokenListener.php
new file mode 100755
index 0000000..8bde999
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/TokenListener.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Qcloud\Cos;
+
+use Aws\Common\Credentials\CredentialsInterface;
+use Aws\Common\Credentials\NullCredentials;
+use Guzzle\Common\Event;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Listener used to sign requests before they are sent over the wire.
+ */
+class TokenListener implements EventSubscriberInterface {
+ // cos signature.
+ protected $token;
+
+ /**
+ * Construct a new request signing plugin
+ */
+ public function __construct($token) {
+ $this->token = $token;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'request.before_send' => array('onRequestBeforeSend', -240));
+ }
+
+ /**
+ * Signs requests before they are sent
+ *
+ * @param Event $event Event emitted
+ */
+ public function onRequestBeforeSend(Event $event) {
+ if ($this->token != null) {
+ $event['request']->setHeader('x-cos-security-token', $this->token);
+ }
+/*
+ if(!$this->credentials instanceof NullCredentials) {
+ $this->signature->signRequest($event['request'], $this->credentials);
+ }
+*/
+ }
+}
diff --git a/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/UploadBodyListener.php b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/UploadBodyListener.php
new file mode 100755
index 0000000..f318029
--- /dev/null
+++ b/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/UploadBodyListener.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Qcloud\Cos;
+
+use Qcloud\Cos\Exception\InvalidArgumentException;
+use Guzzle\Common\Event;
+use Guzzle\Http\EntityBody;
+use Guzzle\Service\Command\AbstractCommand as Command;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Prepares the body parameter of a command such that the parameter is more flexible
+ * (e.g. accepts file handles) with the value it accepts but converts it to the correct format
+ * for the command. Also looks for a "Filename" parameter.
+ */
+class UploadBodyListener implements EventSubscriberInterface {
+ /**
+ * @var array The names of the commands of which to modify the body parameter
+ */
+ protected $commands;
+
+ /**
+ * @var string The key for the upload body parameter
+ */
+ protected $bodyParameter;
+
+ /**
+ * @var string The key for the source file parameter
+ */
+ protected $sourceParameter;
+
+ /**
+ * @param array $commands The commands to modify
+ * @param string $bodyParameter The key for the body parameter
+ * @param string $sourceParameter The key for the source file parameter
+ */
+ public function __construct(array $commands, $bodyParameter = 'Body', $sourceParameter = 'SourceFile') {
+ $this->commands = $commands;
+ $this->bodyParameter = (string) $bodyParameter;
+ $this->sourceParameter = (string) $sourceParameter;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents() {
+ return array('command.before_prepare' => array('onCommandBeforePrepare'));
+ }
+
+ /**
+ * Converts filenames and file handles into EntityBody objects before the command is validated
+ *
+ * @param Event $event Event emitted
+ * @throws InvalidArgumentException
+ */
+ public function onCommandBeforePrepare(Event $event) {
+ /** @var Command $command */
+ $command = $event['command'];
+ if (in_array($command->getName(), $this->commands)) {
+ // Get the interesting parameters
+ $source = $command->get($this->sourceParameter);
+ $body = $command->get($this->bodyParameter);
+
+ // If a file path is passed in then get the file handle
+ if (is_string($source) && file_exists($source)) {
+ $body = fopen($source, 'rb');
+ }
+
+ // Prepare the body parameter and remove the source file parameter
+ if (null !== $body) {
+ $command->remove($this->sourceParameter);
+ $command->set($this->bodyParameter, EntityBody::factory($body));
+ } else {
+ throw new InvalidArgumentException(
+ "You must specify a non-null value for the {$this->bodyParameter} or {$this->sourceParameter} parameters.");
+ }
+ }
+ }
+}
diff --git a/vendor/qcloud_sts/qcloud-sts-sdk/.gitignore b/vendor/qcloud_sts/qcloud-sts-sdk/.gitignore
new file mode 100755
index 0000000..a67d42b
--- /dev/null
+++ b/vendor/qcloud_sts/qcloud-sts-sdk/.gitignore
@@ -0,0 +1,6 @@
+composer.phar
+/vendor/
+
+# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
+# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
+# composer.lock
diff --git a/vendor/qcloud_sts/qcloud-sts-sdk/LICENSE b/vendor/qcloud_sts/qcloud-sts-sdk/LICENSE
new file mode 100755
index 0000000..2a0387f
--- /dev/null
+++ b/vendor/qcloud_sts/qcloud-sts-sdk/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 腾讯云
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/qcloud_sts/qcloud-sts-sdk/README.md b/vendor/qcloud_sts/qcloud-sts-sdk/README.md
new file mode 100755
index 0000000..a4fdc66
--- /dev/null
+++ b/vendor/qcloud_sts/qcloud-sts-sdk/README.md
@@ -0,0 +1,61 @@
+## 获取 SDK
+
+- composer 安装
+```
+创建composer.json的文件,内容如下:
+{
+ "require":{
+ "qcloud_sts/qcloud-sts-sdk": "3.0.*"
+ }
+}
+```
+
+## 查看示例
+
+请查看 [demo 示例](https://github.com/tencentyun/qcloud-cos-sts-sdk/tree/master/php/demo),里面描述了如何调用SDK。
+
+## 接口说明
+
+### getTempKeys
+
+获取临时密钥接口
+
+### 参数说明
+
+|字段|类型|描述|
+| ---- | ---- | ---- |
+|secretId|string| 云 API 密钥 Id|
+|secretKey|string| 云 API 密钥 key|
+|durationSeconds|long| 要申请的临时密钥最长有效时间,单位秒,默认 1800,最大可设置 7200 |
+|bucket|string| 存储桶名称:bucketName-appid, 如 test-125000000|
+|region|string| 存储桶所属地域,如 ap-guangzhou|
+|allowPrefix|string|资源的前缀,如授予操作所有资源,则为`*`;如授予操作某个路径a下的所有资源,则为 `a/*`,如授予只能操作特定的文件a/test.jpg, 则为`a/test.jpg`|
+|allowActions|array| 授予 COS API 权限集合, 如简单上传操作:name/cos:PutObject|
+|policy|array| 策略:由 allowActions、bucket、region、allowPrefix字段组成的描述授权的具体信息|
+
+### 返回值说明
+
+|字段|类型|描述|
+| ---- | ---- | ---- |
+|credentials | string | 临时密钥信息 |
+|tmpSecretId | string | 临时密钥 Id,可用于计算签名 |
+|tmpSecretKey | string | 临时密钥 Key,可用于计算签名 |
+|sessionToken | string | 请求时需要用的 token 字符串,最终请求 COS API 时,需要放在 Header 的 x-cos-security-token 字段 |
+|startTime | string | 密钥的起止时间,是 UNIX 时间戳 |
+|expiredTime | string | 密钥的失效时间,是 UNIX 时间戳 |
+
+### 返回数据示例
+
+```
+{
+ "expiredTime": 1589258683,
+ "expiration": "2020-05-12T04:44:43Z",
+ "credentials": {
+ "sessionToken": "Biypn6exa48PpMe7wFerEnNMpBKKPQo180c57e0a5275ebae506d7851a85f36a4P0TV5UFR3FYJjsoZA1tk6uRKoDRzc6-60BmwLqdS75OhjHEa7GlVYpL_ofKQJTpPKziKX7FnI10D_6qtLdjzf2NdsyUtQEd5kPpDCOQJZn9-BpleqWQe8oyH_2u7xi2f0FtjCYaoGIZ_lUqlILXQwr0B0t3hLfL4xNE-EmVjUlUXa16HxVCn4_hJetqo9LmI0AgLOjCbYx9aVrsV10eDsRta-TQSIXmJNP3aJ6oz8d8GBTgTE1I2qSFDnv9pjtQKW8HZWI_glPIfmHXCCwAESxEFL_owGz839Va0qYhF6LkfVmsuoU1zNcvJR1w3cIE6izV3SKHaOtWaew3IOervuOPoN3S2oYGNwv2EavtDAWyUBIeI7X6nMVzlpnyJ-3bkIhOq9QVIQAs8wh5A0u9mvKWugT5t6qgyEgvEZSj9k6p-JjwxMgLC6s5uK1i_nnf4fN7ZQ6I-JAfHnH4jEDiVtJgXqfuWPX_vnzskyR2Co6E",
+ "tmpSecretId": "AKIDTRPc-oe6c_avPSRwFVsPDyy3IoAr3szMajlOGuoEXY1232YLy6j4f-xZ5zL-NBMG",
+ "tmpSecretKey": "2v29SZztGYk6SGwHYm\/chJXdD3zPRFasmPoJiCmlR\/I="
+ },
+ "requestId": "69ef6295-b981-464d-9816-9c2ef92189d1",
+ "startTime": 1589256883
+}
+```
diff --git a/vendor/qcloud_sts/qcloud-sts-sdk/composer.json b/vendor/qcloud_sts/qcloud-sts-sdk/composer.json
new file mode 100755
index 0000000..22509b4
--- /dev/null
+++ b/vendor/qcloud_sts/qcloud-sts-sdk/composer.json
@@ -0,0 +1,23 @@
+{
+ "name": "tencent/qcloud-sts-sdk",
+ "description": "PHP SDK for QCloud STS",
+ "keywords": [
+ "qcloud", "sts", "cos", "php"
+ ],
+ "homepage": "https://github.com/tencentyun/qcloud-cos-sts-sdk",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "qcloudterminal",
+ "email": "qcloudterminal@gmail.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "QCloud\\COSSTS\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.3.0"
+ }
+}
diff --git a/vendor/qcloud_sts/qcloud-sts-sdk/src/Scope.php b/vendor/qcloud_sts/qcloud-sts-sdk/src/Scope.php
new file mode 100755
index 0000000..794fef3
--- /dev/null
+++ b/vendor/qcloud_sts/qcloud-sts-sdk/src/Scope.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace QCloud\COSSTS;
+
+class Scope{
+ var $action;
+ var $bucket;
+ var $region;
+ var $resourcePrefix;
+ var $effect = 'allow';
+ function __construct($action, $bucket, $region, $resourcePrefix){
+ $this->action = $action;
+ $this->bucket = $bucket;
+ $this->region = $region;
+ $this->resourcePrefix = $resourcePrefix;
+ }
+
+ function set_effect($isAllow){
+ if($isAllow){
+ $this->effect = 'allow';
+ }else{
+ $this->effect = 'deny';
+ }
+ }
+
+ function get_action(){
+ if($this->action == null){
+ throw new \Exception("action == null");
+ }
+ return $this->action;
+ }
+
+ function get_resource(){
+ if($this->bucket == null){
+ throw new \Exception("bucket == null");
+ }
+ if($this->resourcePrefix == null){
+ throw new \Exception("resourcePrefix == null");
+ }
+ $index = strripos($this->bucket, '-');
+ if($index < 0){
+ throw new Exception("bucket is invalid: " + $this->bucket);
+ }
+ $appid = substr($this->bucket, $index + 1);
+ if(!(strpos($this->resourcePrefix, '/') === 0)){
+ $this->resourcePrefix = '/' . $this->resourcePrefix;
+ }
+ return 'qcs::cos:' . $this->region . ':uid/' . $appid . ':' . $this->bucket . $this->resourcePrefix;
+ }
+
+ function get_effect(){
+ return $this->effect;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/vendor/qcloud_sts/qcloud-sts-sdk/src/Sts.php b/vendor/qcloud_sts/qcloud-sts-sdk/src/Sts.php
new file mode 100755
index 0000000..4c3322d
--- /dev/null
+++ b/vendor/qcloud_sts/qcloud-sts-sdk/src/Sts.php
@@ -0,0 +1,166 @@
+<?php
+
+namespace QCloud\COSSTS;
+
+class Sts{
+ // 临时密钥计算样例
+
+ function _hex2bin($data) {
+ $len = strlen($data);
+ return pack("H" . $len, $data);
+ }
+ // obj 转 query string
+ function json2str($obj, $notEncode = false) {
+ ksort($obj);
+ $arr = array();
+ if(!is_array($obj)){
+ throw new \Exception($obj + " must be a array");
+ }
+ foreach ($obj as $key => $val) {
+ array_push($arr, $key . '=' . ($notEncode ? $val : rawurlencode($val)));
+ }
+ return join('&', $arr);
+ }
+ // 计算临时密钥用的签名
+ function getSignature($opt, $key, $method, $config) {
+ $formatString = $method . $config['domain'] . '/?' . $this->json2str($opt, 1);
+ $sign = hash_hmac('sha1', $formatString, $key);
+ $sign = base64_encode($this->_hex2bin($sign));
+ return $sign;
+ }
+ // v2接口的key首字母小写,v3改成大写,此处做了向下兼容
+ function backwardCompat($result) {
+ if(!is_array($result)){
+ throw new \Exception($result + " must be a array");
+ }
+ $compat = array();
+ foreach ($result as $key => $value) {
+ if(is_array($value)) {
+ $compat[lcfirst($key)] = $this->backwardCompat($value);
+ } elseif ($key == 'Token') {
+ $compat['sessionToken'] = $value;
+ } else {
+ $compat[lcfirst($key)] = $value;
+ }
+ }
+ return $compat;
+ }
+ // 获取临时密钥
+ function getTempKeys($config) {
+ $result = null;
+ try{
+ if(array_key_exists('policy', $config)){
+ $policy = $config['policy'];
+ }else{
+ if(array_key_exists('bucket', $config)){
+ $ShortBucketName = substr($config['bucket'],0, strripos($config['bucket'], '-'));
+ $AppId = substr($config['bucket'], 1 + strripos($config['bucket'], '-'));
+ }else{
+ throw new \Exception("bucket== null");
+ }
+ if(array_key_exists('allowPrefix', $config)){
+ if(!(strpos($config['allowPrefix'], '/') === 0)){
+ $config['allowPrefix'] = '/' . $config['allowPrefix'];
+ }
+ }else{
+ throw new \Exception("allowPrefix == null");
+ }
+ $policy = array(
+ 'version'=> '2.0',
+ 'statement'=> array(
+ array(
+ 'action'=> $config['allowActions'],
+ 'effect'=> 'allow',
+ 'principal'=> array('qcs'=> array('*')),
+ 'resource'=> array(
+ 'qcs::cos:' . $config['region'] . ':uid/' . $AppId . ':' . $config['bucket'] . $config['allowPrefix']
+ )
+ )
+ )
+ );
+ }
+ $policyStr = str_replace('\\/', '/', json_encode($policy));
+ $Action = 'GetFederationToken';
+ $Nonce = rand(10000, 20000);
+ $Timestamp = time();
+ $Method = 'POST';
+ if(array_key_exists('durationSeconds', $config)){
+ if(!(is_integer($config['durationSeconds']))){
+ throw new \Exception("durationSeconds must be a int type");
+ }
+ }
+ $params = array(
+ 'SecretId'=> $config['secretId'],
+ 'Timestamp'=> $Timestamp,
+ 'Nonce'=> $Nonce,
+ 'Action'=> $Action,
+ 'DurationSeconds'=> $config['durationSeconds'],
+ 'Version'=>'2018-08-13',
+ 'Name'=> 'cos',
+ 'Region'=> $config['region'],
+ 'Policy'=> urlencode($policyStr)
+ );
+ $params['Signature'] = $this->getSignature($params, $config['secretKey'], $Method, $config);
+ $url = $config['url'];
+ $ch = curl_init($url);
+ if(array_key_exists('proxy', $config)){
+ $config['proxy'] && curl_setopt($ch, CURLOPT_PROXY, $config['proxy']);
+ }
+ curl_setopt($ch, CURLOPT_HEADER, 0);
+ curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,0);
+ curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,0);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $this->json2str($params));
+ $result = curl_exec($ch);
+ if(curl_errno($ch)) $result = curl_error($ch);
+ curl_close($ch);
+ $result = json_decode($result, 1);
+ if (isset($result['Response'])) {
+ $result = $result['Response'];
+ if(isset($result['Error'])){
+ throw new \Exception("get cam failed");
+ }
+ $result['startTime'] = $result['ExpiredTime'] - $config['durationSeconds'];
+ }
+ $result = $this->backwardCompat($result);
+ return $result;
+ }catch(\Exception $e){
+ if($result == null){
+ $result = "error: " . + $e->getMessage();
+ }else{
+ $result = json_encode($result);
+ }
+ throw new \Exception($result);
+ }
+ }
+
+ // get policy
+ function getPolicy($scopes){
+ if (!is_array($scopes)){
+ return null;
+ }
+ $statements = array();
+
+ for($i=0, $counts=count($scopes); $i < $counts; $i++){
+ $actions=array();
+ $resources = array();
+ array_push($actions, $scopes[$i]->get_action());
+ array_push($resources, $scopes[$i]->get_resource());
+
+ $statement = array(
+ 'actions' => $actions,
+ 'effect' => $scopes[$i]->get_effect(),
+ 'resource' => $resources
+ );
+ array_push($statements, $statement);
+ }
+
+ $policy = array(
+ 'version' => '2.0',
+ 'statement' => $statements
+ );
+ return $policy;
+ }
+}
+?>
\ No newline at end of file
diff --git a/vendor/qiniu/php-sdk/.gitignore b/vendor/qiniu/php-sdk/.gitignore
new file mode 100755
index 0000000..4c842c8
--- /dev/null
+++ b/vendor/qiniu/php-sdk/.gitignore
@@ -0,0 +1,12 @@
+*.phar
+*.zip
+build/artifacts
+phpunit.xml
+phpunit.functional.xml
+.DS_Store
+.swp
+.build
+composer.lock
+vendor
+src/package.xml
+.idea/
diff --git a/vendor/qiniu/php-sdk/.scrutinizer.yml b/vendor/qiniu/php-sdk/.scrutinizer.yml
new file mode 100755
index 0000000..8d9304c
--- /dev/null
+++ b/vendor/qiniu/php-sdk/.scrutinizer.yml
@@ -0,0 +1,35 @@
+filter:
+ excluded_paths: [tests/*]
+checks:
+ php:
+ code_rating: true
+ remove_extra_empty_lines: true
+ remove_php_closing_tag: true
+ remove_trailing_whitespace: true
+ fix_use_statements:
+ remove_unused: true
+ preserve_multiple: false
+ preserve_blanklines: true
+ order_alphabetically: true
+ fix_php_opening_tag: true
+ fix_linefeed: true
+ fix_line_ending: true
+ fix_identation_4spaces: true
+ fix_doc_comments: true
+tools:
+ external_code_coverage:
+ timeout: 1200
+ runs: 3
+ php_analyzer: true
+ php_code_coverage: false
+ php_code_sniffer:
+ config:
+ standard: PSR2
+ filter:
+ paths: ['src']
+ php_loc:
+ enabled: true
+ excluded_dirs: [vendor, tests]
+ php_cpd:
+ enabled: true
+ excluded_dirs: [vendor, tests]
diff --git a/vendor/qiniu/php-sdk/.travis.yml b/vendor/qiniu/php-sdk/.travis.yml
new file mode 100755
index 0000000..cada8cd
--- /dev/null
+++ b/vendor/qiniu/php-sdk/.travis.yml
@@ -0,0 +1,28 @@
+sudo: false
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+
+before_script:
+ - export QINIU_TEST_ENV="travis"
+ - travis_retry composer self-update
+ - travis_retry composer install --no-interaction --prefer-source --dev
+
+script:
+ - ./vendor/bin/phpcs --standard=PSR2 src
+ - ./vendor/bin/phpcs --standard=PSR2 examples
+ - ./vendor/bin/phpcs --standard=PSR2 tests
+ - ./vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover tests/Qiniu/Tests/
+
+after_script:
+ - wget https://scrutinizer-ci.com/ocular.phar
+ - php ocular.phar code-coverage:upload --format=php-clover coverage.clover
+
+env:
+ global:
+ - secure: "V9BsntXQZwvO9EOD6itzaae2uq+GemzyTUTxMTJx1/jFoUNpCU2O2UAgjA2XSEr5sgci0KWDV4Krbzv3EBB4uplOFLMI3w32256UHbT9E0x3YjhfPJZk68MH1iS1be7X81LDHON7yveavK8987s3qzjeUcbfLSPgccT+cvf7+dc="
+ - QINIU_ACCESS_KEY=vHg2e7nOh7Jsucv2Azr5FH6omPgX22zoJRWa0FN5
\ No newline at end of file
diff --git a/vendor/qiniu/php-sdk/CHANGELOG.md b/vendor/qiniu/php-sdk/CHANGELOG.md
new file mode 100755
index 0000000..8a284bd
--- /dev/null
+++ b/vendor/qiniu/php-sdk/CHANGELOG.md
@@ -0,0 +1,105 @@
+# Changelog
+
+## 7.2.7 (2018-11-06)
+* 添加 QVM 内网上传到 KODO 的 zone 设置
+
+## 7.2.6 (2018-05-18)
+* 修复rs,rsf在不同机房默认的https域名
+
+## 7.2.5 (2018-05-10)
+* 修复表单上传中多余的参数checkCrc导致的fname错位问题
+
+## 7.2.4 (2018-05-09)
+### 增加
+* 连麦功能
+
+## 7.2.3 (2018-01-20)
+### 增加
+* 新加坡机房
+### 修正
+* 获取域名的入口域名
+* http回复头部兼容大小写
+
+## 7.2.2 (2017-11-06)
+### 增加
+* Qiniu算法的鉴权方法
+
+## 7.1.4 (2017-06-21)
+### 增加
+* cdn 文件/目录 刷新
+* cdn 获取 流量/带宽
+* cdn 获取域名的访问日志列表
+* cdn 对资源链接进行时间戳防盗链签名
+
+## 7.1.3 (2016-11-18)
+### 增加
+* move, copy操作增加force参数
+
+## 7.1.2 (2016-11-12)
+### 修正
+* 明确抛出获取各区域域名失败时的报错
+
+## 7.1.1 (2016-11-02)
+### 修正
+* 多区域配置文件存储目录从home修改到tmp目录
+
+
+## 7.1.0 (2016-10-22)
+### 增加
+* 多存储区域的支持
+
+## 7.0.8 (2016-07-19)
+### 增加
+* demo
+* https url 支持
+* deleteAfterDays 策略
+* 添加图片处理链接统一拼接方法 by @SherlockRen
+
+## 7.0.7 (2016-01-12)
+### 修正
+* PersistentFop参数pipeline和notify_url失效
+* resume 模式 close file inputstream
+
+## 7.0.6 (2015-12-05)
+### 修正
+* php7.0 Json 对空字符串解析单元测试报错
+* 开启安全模式或者设置可操作目录树时,设置CURLOPT_FOLLOWLOCATION报错, by @twocabbages
+* fetch 支持不指定key, by @sinkcup
+
+## 7.0.5 (2015-10-29)
+### 增加
+* 增加上传策略最小文件大小限制 fsizeMin
+* 增加常见examples
+
+## 7.0.4 (2015-07-23)
+### 修正
+* 一些地方的严格比较检查
+* resumeupload 备用地址失效
+
+## 7.0.3 (2015-07-10)
+### 修改
+* 多zone 支持
+
+## 7.0.2 (2015-04-18)
+### 修改
+* fetch 接口返回内容调整
+* pfop 接口调整
+
+###修正
+* exception 类调用
+
+## 7.0.1 (2015-03-27)
+### 增加
+* 增加代码注释
+
+## 7.0.0 (2015-02-03)
+
+### 增加
+* 简化上传接口
+* 自动选择断点续上传还是直传
+* 重构代码,接口和内部结构更清晰
+* 改变mime
+* 代码覆盖度报告
+* policy改为array, 便于灵活增加,并加入过期字段检查
+* 文件列表支持目录形式
+* 利用元编程方式支持 fop 和 pfop
diff --git a/vendor/qiniu/php-sdk/CONTRIBUTING.md b/vendor/qiniu/php-sdk/CONTRIBUTING.md
new file mode 100755
index 0000000..0466bf9
--- /dev/null
+++ b/vendor/qiniu/php-sdk/CONTRIBUTING.md
@@ -0,0 +1,30 @@
+# 贡献代码指南
+
+我们非常欢迎大家来贡献代码,我们会向贡献者致以最诚挚的敬意。
+
+一般可以通过在Github上提交[Pull Request](https://github.com/qiniu/php-sdk)来贡献代码。
+
+## Pull Request要求
+
+- **[PSR-2 编码风格标准](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** 。要通过项目中的code sniffer检查。
+
+- **代码格式** 提交前 请按 ./vendor/bin/phpcbf --standard=PSR2 进行格式化。
+
+- **必须添加测试!** - 如果没有测试(单元测试、集成测试都可以),那么提交的补丁是不会通过的。
+
+- **记得更新文档** - 保证`README.md`以及其他相关文档及时更新,和代码的变更保持一致性。
+
+- **考虑我们的发布周期** - 我们的版本号会服从[SemVer v2.0.0](http://semver.org/),我们绝对不会随意变更对外的API。
+
+- **创建feature分支** - 最好不要从你的master分支提交 pull request。
+
+- **一个feature提交一个pull请求** - 如果你的代码变更了多个操作,那就提交多个pull请求吧。
+
+- **清晰的commit历史** - 保证你的pull请求的每次commit操作都是有意义的。如果你开发中需要执行多次的即时commit操作,那么请把它们放到一起再提交pull请求。
+
+## 运行测试
+
+``` bash
+./vendor/bin/phpunit tests/Qiniu/Tests/
+
+```
diff --git a/vendor/qiniu/php-sdk/LICENSE b/vendor/qiniu/php-sdk/LICENSE
new file mode 100755
index 0000000..ba646be
--- /dev/null
+++ b/vendor/qiniu/php-sdk/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Qiniu, Ltd.<sdk@qiniu.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/vendor/qiniu/php-sdk/README.md b/vendor/qiniu/php-sdk/README.md
new file mode 100755
index 0000000..16c8b49
--- /dev/null
+++ b/vendor/qiniu/php-sdk/README.md
@@ -0,0 +1,75 @@
+# Qiniu Cloud SDK for PHP
+[](http://doxygen.io/github.com/qiniu/php-sdk/)
+[](LICENSE)
+[](https://travis-ci.org/qiniu/php-sdk)
+[](https://packagist.org/packages/qiniu/php-sdk)
+[](https://packagist.org/packages/qiniu/php-sdk)
+[](https://scrutinizer-ci.com/g/qiniu/php-sdk/?branch=master)
+[](https://scrutinizer-ci.com/g/qiniu/php-sdk/?branch=master)
+[](https://gitter.im/qiniu/php-sdk?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[](http://weibo.com/qiniutek)
+
+## 安装
+
+* 通过composer,这是推荐的方式,可以使用composer.json 声明依赖,或者运行下面的命令。SDK 包已经放到这里 [`qiniu/php-sdk`][install-packagist] 。
+```bash
+$ composer require qiniu/php-sdk
+```
+* 直接下载安装,SDK 没有依赖其他第三方库,但需要参照 composer的autoloader,增加一个自己的autoloader程序。
+
+## 运行环境
+
+| Qiniu SDK版本 | PHP 版本 |
+|:--------------------:|:---------------------------:|
+| 7.x | cURL extension, 5.3 - 5.6,7.0 |
+| 6.x | cURL extension, 5.2 - 5.6 |
+
+## 使用方法
+
+### 上传
+```php
+use Qiniu\Storage\UploadManager;
+use Qiniu\Auth;
+...
+ $upManager = new UploadManager();
+ $auth = new Auth($accessKey, $secretKey);
+ $token = $auth->uploadToken($bucketName);
+ list($ret, $error) = $upManager->put($token, 'formput', 'hello world');
+...
+```
+
+## 测试
+
+``` bash
+$ ./vendor/bin/phpunit tests/Qiniu/Tests/
+```
+
+## 常见问题
+
+- $error保留了请求响应的信息,失败情况下ret 为none, 将$error可以打印出来,提交给我们。
+- API 的使用 demo 可以参考 [单元测试](https://github.com/qiniu/php-sdk/blob/master/tests)。
+
+## 代码贡献
+
+详情参考[代码提交指南](https://github.com/qiniu/php-sdk/blob/master/CONTRIBUTING.md)。
+
+## 贡献记录
+
+- [所有贡献者](https://github.com/qiniu/php-sdk/contributors)
+
+## 联系我们
+
+- 如果需要帮助,请提交工单(在portal右侧点击咨询和建议提交工单,或者直接向 support@qiniu.com 发送邮件)
+- 如果有什么问题,可以到问答社区提问,[问答社区](http://qiniu.segmentfault.com/)
+- 更详细的文档,见[官方文档站](http://developer.qiniu.com/)
+- 如果发现了bug, 欢迎提交 [issue](https://github.com/qiniu/php-sdk/issues)
+- 如果有功能需求,欢迎提交 [issue](https://github.com/qiniu/php-sdk/issues)
+- 如果要提交代码,欢迎提交 pull request
+- 欢迎关注我们的[微信](http://www.qiniu.com/#weixin) [微博](http://weibo.com/qiniutek),及时获取动态信息。
+
+## 代码许可
+
+The MIT License (MIT).详情见 [License文件](https://github.com/qiniu/php-sdk/blob/master/LICENSE).
+
+[packagist]: http://packagist.org
+[install-packagist]: https://packagist.org/packages/qiniu/php-sdk
diff --git a/vendor/qiniu/php-sdk/autoload.php b/vendor/qiniu/php-sdk/autoload.php
new file mode 100755
index 0000000..4379b91
--- /dev/null
+++ b/vendor/qiniu/php-sdk/autoload.php
@@ -0,0 +1,14 @@
+<?php
+
+function classLoader($class)
+{
+ $path = str_replace('\\', DIRECTORY_SEPARATOR, $class);
+ $file = __DIR__ . '/src/' . $path . '.php';
+
+ if (file_exists($file)) {
+ require_once $file;
+ }
+}
+spl_autoload_register('classLoader');
+
+require_once __DIR__ . '/src/Qiniu/functions.php';
diff --git a/vendor/qiniu/php-sdk/composer.json b/vendor/qiniu/php-sdk/composer.json
new file mode 100755
index 0000000..52544fe
--- /dev/null
+++ b/vendor/qiniu/php-sdk/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "qiniu/php-sdk",
+ "type": "library",
+ "description": "Qiniu Resource (Cloud) Storage SDK for PHP",
+ "keywords": ["qiniu", "storage", "sdk", "cloud"],
+ "homepage": "http://developer.qiniu.com/",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Qiniu",
+ "email": "sdk@qiniu.com",
+ "homepage": "http://www.qiniu.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0",
+ "squizlabs/php_codesniffer": "~2.3"
+ },
+ "autoload": {
+ "psr-4": {"Qiniu\\": "src/Qiniu"},
+ "files": ["src/Qiniu/functions.php"]
+ }
+}
diff --git a/vendor/qiniu/php-sdk/docs/rtc/README.md b/vendor/qiniu/php-sdk/docs/rtc/README.md
new file mode 100755
index 0000000..7b98ca0
--- /dev/null
+++ b/vendor/qiniu/php-sdk/docs/rtc/README.md
@@ -0,0 +1,71 @@
+# Rtc Streaming Cloud Server-Side Library For PHP
+
+## Features
+
+- Appclient
+ - [x] 创建房间: client->createApp()
+ - [x] 查看房间: client->getApp()
+ - [x] 删除房间: client->deleteApp()
+ - [x] 生成房间token: client->appToken()
+
+
+
+## Contents
+
+- [Installation](#installation)
+- [Usage](#usage)
+ - [Configuration](#configuration)
+ - [App](#app)
+ - [Create a app](#create-a-app)
+ - [Get a app](#get-a-app)
+ - [Delete a app](#delete-a-app)
+ - [Generate a app token](#generate-a-app-token)
+
+
+## Usage
+
+### App
+
+#### Create a app
+
+```php
+$ak = "gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5";
+$sk = "xxxx";
+$auth = new Auth($ak, $sk);
+$client = new Qiniu\Rtc\AppClient($auth);
+$resp=$client->createApp("901","testApp");
+print_r($resp);
+```
+
+#### Get an app
+
+```php
+$ak = "gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5";
+$sk = "xxxx";
+$auth = new Auth($ak, $sk);
+$client = new Qiniu\Rtc\AppClient($auth);
+$resp=$client->getApp("deq02uhb6");
+print_r($resp);
+```
+
+#### Delete an app
+
+```php
+$ak = "gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5";
+$sk = "xxxx";
+$auth = new Auth($ak, $sk);
+$client = new Qiniu\Rtc\AppClient($auth);
+$resp=$client->deleteApp("deq02uhb6");
+print_r($resp);
+```
+
+#### Generate an app token
+
+```php
+$ak = "gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5";
+$sk = "xxxx";
+$auth = new Auth($ak, $sk);
+$client = new Qiniu\Rtc\AppClient($auth);
+$resp=$client->appToken("deq02uhb6", "lfx", '1111', (time()+3600), 'user');
+print_r($resp);
+```
\ No newline at end of file
diff --git a/vendor/qiniu/php-sdk/docs/rtc/example.php b/vendor/qiniu/php-sdk/docs/rtc/example.php
new file mode 100755
index 0000000..f30b9b4
--- /dev/null
+++ b/vendor/qiniu/php-sdk/docs/rtc/example.php
@@ -0,0 +1,42 @@
+<?php
+require_once("../../autoload.php");
+
+use \Qiniu\Auth;
+
+$ak = 'gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5';
+$sk = 'xxxx';
+
+$auth = new Auth($ak, $sk);
+$client = new Qiniu\Rtc\AppClient($auth);
+$hub = 'lfxlive';
+$title = 'lfxl';
+try {
+ //创建app
+ $resp = $client->createApp($hub, $title, $maxUsers);
+ print_r($resp);
+ // 获取app状态
+ $resp = $client->getApp('dgdl5ge8y');
+ print_r($resp);
+ //修改app状态
+ $mergePublishRtmp = null;
+ $mergePublishRtmp['enable'] = true;
+ $resp = $client->updateApp('dgdl5ge8y', $hub, $title, $maxUsers, $mergePublishRtmp);
+ print_r($resp);
+ //删除app
+ $resp = $client->deleteApp('dgdl5ge8y');
+ print_r($resp);
+ //获取房间连麦的成员
+ $resp=$client->listUser("dgbfvvzid", 'lfxl');
+ print_r($resp);
+ //剔除房间的连麦成员
+ $resp=$client->kickUser("dgbfvvzid", 'lfx', "qiniu-f6e07b78-4dc8-45fb-a701-a9e158abb8e6");
+ print_r($resp);
+ // 列举房间
+ $resp=$client->listActiveRooms("dgbfvvzid", 'lfx', null, null);
+ print_r($resp);
+ //鉴权的有效时间: 1个小时.
+ $resp = $client->appToken("dgd4vecde", "lfxl", '1111', (time()+3600), 'user');
+ print_r($resp);
+} catch (\Exception $e) {
+ echo "Error:", $e, "\n";
+}
diff --git a/vendor/qiniu/php-sdk/docs/sms/example.php b/vendor/qiniu/php-sdk/docs/sms/example.php
new file mode 100755
index 0000000..541c0d6
--- /dev/null
+++ b/vendor/qiniu/php-sdk/docs/sms/example.php
@@ -0,0 +1,70 @@
+<?php
+require_once("../../autoload.php");
+
+use \Qiniu\Auth;
+
+$ak="xxxx";
+$sk="xxxx";
+
+$auth = new Auth($ak, $sk);
+$client = new Qiniu\Sms\Sms($auth);
+
+//发送信息模块
+$template_id="1131792074274775040";
+$mobiles=array("18011111111","18011111111");
+$code = array('code' => 'code' );
+try {
+ //发送短信
+ $resp = $client->sendMessage($template_id, $mobiles, $code);
+ print_r($resp);
+} catch (\Exception $e) {
+ echo "Error:", $e, "\n";
+}exit;
+//模板模块
+$name="tstest001";
+$template="tesy001 ${code}";
+$type="notification";
+$description="tstest001";
+$signature_id="1131464448834277376";
+$id="1131810682442883072";
+
+try {
+ //创建模板
+ $resp = $client->createTemplate($name, $template, $type, $description, $signature_id);
+ print_r($resp);
+ //查询模板
+ $resp = $client->queryTemplate();
+ print_r($resp);
+ //修改模板
+ $resp = $client->updateTemplate($id, $name, $template, $description, $signature_id);
+ print_r($resp);
+ //删除模板
+ $resp = $client->deleteTemplate($id);
+ print_r($resp);
+} catch (\Exception $e) {
+ echo "Error:", $e, "\n";
+}
+//签名模块
+$signature = 'lfxlive2';
+$source = 'enterprises_and_institutions';
+$pic="/Users/Desktop/sss.jpg";
+$audit_status="passed";
+$page=1;
+$page_size=1;
+$id="1131464448834277376";
+
+try {
+ //创建签名
+ $resp = $client->createSignature($signature, $source, $pic);
+ print_r($resp);
+ //查询签名
+ $resp = $client->checkSignature($audit_status);
+ //修改签名
+ $resp = $client->updateSignature($id, $signature, $source, $pic);
+ print_r($resp);
+ //删除ID
+ $resp = $client->deleteSignature($id);
+ print_r($resp);
+} catch (\Exception $e) {
+ echo "Error:", $e, "\n";
+}
diff --git a/vendor/qiniu/php-sdk/examples/README.md b/vendor/qiniu/php-sdk/examples/README.md
new file mode 100755
index 0000000..6cf8e30
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/README.md
@@ -0,0 +1,10 @@
+# examples
+
+这些 examples 旨在帮助你快速了解使用七牛的sdk。这些demo都是可以直接运行的, 但是在运行之前需要填上您自己的参数。
+
+比如:
+
+* `$bucket` 需要填上您想操作的 [bucket名字](http://developer.qiniu.com/docs/v6/api/overview/concepts.html#bucket)。
+* `$accessKey` 和 `$secretKey` 可以在我们的[管理后台](https://portal.qiniu.com/setting/key)找到。
+* 在进行`视频转码`, `压缩文件`等异步操作时 需要使用到的队列名称也可以在我们[管理后台](https://portal.qiniu.com/mps/pipeline)新建。
+
diff --git a/vendor/qiniu/php-sdk/examples/bucket_lifecycleRule.php b/vendor/qiniu/php-sdk/examples/bucket_lifecycleRule.php
new file mode 100755
index 0000000..3f7fefd
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/bucket_lifecycleRule.php
@@ -0,0 +1,30 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+$name = 'demo';
+$prefix = 'test';
+$delete_after_days = 80;
+$to_line_after_days =70;
+
+list($Info, $err) = $bucketManager->bucketLifecycleRule(
+ $bucket,
+ $name,
+ $prefix,
+ $delete_after_days,
+ $to_line_after_days
+);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/cdn_get_bandwidth.php b/vendor/qiniu/php-sdk/examples/cdn_get_bandwidth.php
new file mode 100755
index 0000000..4d2ccf6
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/cdn_get_bandwidth.php
@@ -0,0 +1,40 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Cdn\CdnManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Qiniu\Auth($accessKey, $secretKey);
+$cdnManager = new CdnManager($auth);
+
+//获取流量和带宽数据
+//参考文档:http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
+
+$domains = array(
+ "javasdk.qiniudn.com",
+ "phpsdk.qiniudn.com"
+);
+
+$startDate = "2017-08-20";
+$endDate = "2017-08-21";
+
+//5min or hour or day
+$granularity = "day";
+
+//获取带宽数据
+list($bandwidthData, $getBandwidthErr) = $cdnManager->getBandwidthData(
+ $domains,
+ $startDate,
+ $endDate,
+ $granularity
+);
+
+if ($getBandwidthErr != null) {
+ var_dump($getBandwidthErr);
+} else {
+ echo "get bandwidth data success\n";
+ print_r($bandwidthData);
+}
diff --git a/vendor/qiniu/php-sdk/examples/cdn_get_flux.php b/vendor/qiniu/php-sdk/examples/cdn_get_flux.php
new file mode 100755
index 0000000..56da550
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/cdn_get_flux.php
@@ -0,0 +1,34 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Cdn\CdnManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Qiniu\Auth($accessKey, $secretKey);
+$cdnManager = new CdnManager($auth);
+
+//获取流量和带宽数据
+//参考文档:http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
+
+$domains = array(
+ "javasdk.qiniudn.com",
+ "phpsdk.qiniudn.com"
+);
+
+$startDate = "2017-08-20";
+$endDate = "2017-08-21";
+
+//5min or hour or day
+$granularity = "day";
+
+//获取流量数据
+list($fluxData, $getFluxErr) = $cdnManager->getFluxData($domains, $startDate, $endDate, $granularity);
+if ($getFluxErr != null) {
+ var_dump($getFluxErr);
+} else {
+ echo "get flux data success\n";
+ print_r($fluxData);
+}
diff --git a/vendor/qiniu/php-sdk/examples/cdn_get_log_list.php b/vendor/qiniu/php-sdk/examples/cdn_get_log_list.php
new file mode 100755
index 0000000..4e5c942
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/cdn_get_log_list.php
@@ -0,0 +1,29 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Cdn\CdnManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Qiniu\Auth($accessKey, $secretKey);
+$cdnManager = new CdnManager($auth);
+
+$domains = array(
+ "javasdk.qiniudn.com",
+ "phpsdk.qiniudn.com"
+);
+
+$logDate = '2017-08-20';
+
+//获取日志下载链接
+//参考文档:http://developer.qiniu.com/article/fusion/api/log.html
+
+list($logListData, $getLogErr) = $cdnManager->getCdnLogList($domains, $logDate);
+if ($getLogErr != null) {
+ var_dump($getLogErr);
+} else {
+ echo "get cdn log list success\n";
+ print_r($logListData);
+}
diff --git a/vendor/qiniu/php-sdk/examples/cdn_refresh_urls_dirs.php b/vendor/qiniu/php-sdk/examples/cdn_refresh_urls_dirs.php
new file mode 100755
index 0000000..c05e75f
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/cdn_refresh_urls_dirs.php
@@ -0,0 +1,52 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Cdn\CdnManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Qiniu\Auth($accessKey, $secretKey);
+
+//待刷新的文件列表和目录,文件列表最多一次100个,目录最多一次10个
+//参考文档:http://developer.qiniu.com/article/fusion/api/refresh.html
+$urls = array(
+ "http://phpsdk.qiniudn.com/qiniu.jpg",
+ "http://phpsdk.qiniudn.com/qiniu2.jpg",
+);
+
+//刷新目录需要联系七牛技术支持开通账户权限
+$dirs = array(
+ "http://phpsdk.qiniudn.com/test/"
+);
+
+$cdnManager = new CdnManager($auth);
+
+// 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh
+// 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category
+list($refreshResult, $refreshErr) = $cdnManager->refreshUrlsAndDirs($urls, $dirs);
+if ($refreshErr != null) {
+ var_dump($refreshErr);
+} else {
+ echo "refresh request sent\n";
+ print_r($refreshResult);
+}
+
+//如果只有刷新链接或者目录的需求,可以分布使用
+
+list($refreshResult, $refreshErr) = $cdnManager->refreshUrls($urls);
+if ($refreshErr != null) {
+ var_dump($refreshErr);
+} else {
+ echo "refresh request sent\n";
+ print_r($refreshResult);
+}
+
+list($refreshResult, $refreshErr) = $cdnManager->refreshDirs($dirs);
+if ($refreshErr != null) {
+ var_dump($refreshErr);
+} else {
+ echo "refresh request sent\n";
+ print_r($refreshResult);
+}
diff --git a/vendor/qiniu/php-sdk/examples/cdn_timestamp_antileech.php b/vendor/qiniu/php-sdk/examples/cdn_timestamp_antileech.php
new file mode 100755
index 0000000..d9fd023
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/cdn_timestamp_antileech.php
@@ -0,0 +1,19 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Cdn\CdnManager;
+
+//创建时间戳防盗链
+//时间戳防盗链密钥,后台获取
+$encryptKey = 'your_domain_timestamp_antileech_encryptkey';
+
+//带访问协议的域名
+$url1 = 'http://phpsdk.qiniuts.com/24.jpg?avinfo';
+$url2 = 'http://phpsdk.qiniuts.com/24.jpg';
+
+//有效期时间(单位秒)
+$durationInSeconds = 3600;
+
+$signedUrl = CdnManager::createTimestampAntiLeechUrl($url1, $encryptKey, $durationInSeconds);
+print($signedUrl);
diff --git a/vendor/qiniu/php-sdk/examples/delete_bucket.php b/vendor/qiniu/php-sdk/examples/delete_bucket.php
new file mode 100755
index 0000000..dc2e305
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/delete_bucket.php
@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$name = 'xxxx';
+
+list($Info, $err) = $bucketManager->deleteBucket($name);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/delete_bucketEvent.php b/vendor/qiniu/php-sdk/examples/delete_bucketEvent.php
new file mode 100755
index 0000000..00df6ca
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/delete_bucketEvent.php
@@ -0,0 +1,21 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+$name = 'demo';
+
+list($Info, $err) = $bucketManager->deleteBucketEvent($bucket, $name);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/delete_bucketLifecycleRule.php b/vendor/qiniu/php-sdk/examples/delete_bucketLifecycleRule.php
new file mode 100755
index 0000000..a536992
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/delete_bucketLifecycleRule.php
@@ -0,0 +1,21 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+$name = 'demo';
+
+list($Info, $err) = $bucketManager->deleteBucketLifecycleRule($bucket, $name);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/get_bucketEvents.php b/vendor/qiniu/php-sdk/examples/get_bucketEvents.php
new file mode 100755
index 0000000..53a5c88
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/get_bucketEvents.php
@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+
+list($Info, $err) = $bucketManager->getBucketEvents($bucket);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/get_bucketLifecycleRules.php b/vendor/qiniu/php-sdk/examples/get_bucketLifecycleRules.php
new file mode 100755
index 0000000..652bee8
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/get_bucketLifecycleRules.php
@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+
+list($Info, $err) = $bucketManager->getBucketLifecycleRules($bucket);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/get_bucketList.php b/vendor/qiniu/php-sdk/examples/get_bucketList.php
new file mode 100755
index 0000000..74aaa65
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/get_bucketList.php
@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$region = 'z1';
+
+list($Info, $err) = $bucketManager->listbuckets($region);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/get_bucketQuota.php b/vendor/qiniu/php-sdk/examples/get_bucketQuota.php
new file mode 100755
index 0000000..563395d
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/get_bucketQuota.php
@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+
+list($Info, $err) = $bucketManager->getBucketQuota($bucket);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/get_bucketinfo.php b/vendor/qiniu/php-sdk/examples/get_bucketinfo.php
new file mode 100755
index 0000000..ff052a4
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/get_bucketinfo.php
@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+
+list($Info, $err) = $bucketManager->bucketInfo($bucket);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/get_bucketinfos.php b/vendor/qiniu/php-sdk/examples/get_bucketinfos.php
new file mode 100755
index 0000000..0ad6503
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/get_bucketinfos.php
@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$region = 'z1';
+
+list($Info, $err) = $bucketManager->bucketInfos($region);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/get_corsRules.php b/vendor/qiniu/php-sdk/examples/get_corsRules.php
new file mode 100755
index 0000000..fbfde2d
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/get_corsRules.php
@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+
+list($Info, $err) = $bucketManager->getCorsRules($bucket);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/image_url_builder.php b/vendor/qiniu/php-sdk/examples/image_url_builder.php
new file mode 100755
index 0000000..e99a728
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/image_url_builder.php
@@ -0,0 +1,74 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+// 引入图片处理类
+use Qiniu\Processing\ImageUrlBuilder;
+
+$imageUrlBuilder = new ImageUrlBuilder();
+
+// 要处理图片
+$url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg';
+$url2 = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg?watermark/1/gravity/SouthEast/dx/0/dy/0/image/'
+ . 'aHR0cDovL2Fkcy1jZG4uY2h1Y2h1amllLmNvbS9Ga1R6bnpIY2RLdmRBUFc5cHZZZ3pTc21UY0tB';
+$waterImage = 'http://developer.qiniu.com/resource/logo-2.jpg';
+
+/**
+ * 缩略图链接拼接
+ *
+ * @param string $url 图片链接
+ * @param int $mode 缩略模式
+ * @param int $width 宽度
+ * @param int $height 长度
+ * @param string $format 输出类型 [可选]
+ * @param int $quality 图片质量 [可选]
+ * @param int $interlace 是否支持渐进显示 [可选]
+ * @param int $ignoreError 忽略结果 [可选]
+ * @return string
+ * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+$thumbLink = $imageUrlBuilder->thumbnail($url, 1, 100, 100);
+
+// 函数方式调用 也可拼接多个操作参数 图片+水印
+$thumbLink2 = \Qiniu\thumbnail($url2, 1, 100, 100);
+var_dump($thumbLink, $thumbLink2);
+
+/**
+ * 图片水印
+ *
+ * @param string $url 图片链接
+ * @param string $image 水印图片链接
+ * @param numeric $dissolve 透明度 [可选]
+ * @param string $gravity 水印位置 [可选]
+ * @param numeric $dx 横轴边距 [可选]
+ * @param numeric $dy 纵轴边距 [可选]
+ * @param numeric $watermarkScale 自适应原图的短边比例 [可选]
+ * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html
+ * @return string
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+$waterLink = $imageUrlBuilder->waterImg($url, $waterImage);
+// 函数调用方法
+//$waterLink = \Qiniu\waterImg($url, $waterImage);
+var_dump($waterLink);
+
+/**
+ * 文字水印
+ *
+ * @param string $url 图片链接
+ * @param string $text 文字
+ * @param string $font 文字字体
+ * @param string $fontSize 文字字号
+ * @param string $fontColor 文字颜色 [可选]
+ * @param numeric $dissolve 透明度 [可选]
+ * @param string $gravity 水印位置 [可选]
+ * @param numeric $dx 横轴边距 [可选]
+ * @param numeric $dy 纵轴边距 [可选]
+ * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark
+ * @return string
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+$textLink = $imageUrlBuilder->waterText($url, '你瞅啥', '微软雅黑', 300);
+// 函数调用方法
+// $textLink = \Qiniu\waterText($url, '你瞅啥', '微软雅黑', 300);
+var_dump($textLink);
diff --git a/vendor/qiniu/php-sdk/examples/persistent_fop_init.php b/vendor/qiniu/php-sdk/examples/persistent_fop_init.php
new file mode 100755
index 0000000..2df01e9
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/persistent_fop_init.php
@@ -0,0 +1,19 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+$accessKey = 'Access_Key';
+$secretKey = 'Secret_Key';
+$auth = new Auth($accessKey, $secretKey);
+
+// 要转码的文件所在的空间。
+$bucket = 'Bucket_Name';
+
+// 转码是使用的队列名称。 https://portal.qiniu.com/mps/pipeline
+$pipeline = 'pipeline_name';
+
+// 初始化
+$pfop = new PersistentFop($auth, $bucket, $pipeline);
diff --git a/vendor/qiniu/php-sdk/examples/persistent_fop_status.php b/vendor/qiniu/php-sdk/examples/persistent_fop_status.php
new file mode 100755
index 0000000..0c26f7a
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/persistent_fop_status.php
@@ -0,0 +1,18 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Processing\PersistentFop;
+
+$pfop = new Qiniu\Processing\PersistentFop(null, null);
+
+// 触发持久化处理后返回的 Id
+$persistentId = 'z1.5b8a48e5856db843bc24cfc3';
+
+// 通过persistentId查询该 触发持久化处理的状态
+list($ret, $err) = $pfop->status($persistentId);
+
+if ($err) {
+ print_r($err);
+} else {
+ print_r($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/pfop_mkzip.php b/vendor/qiniu/php-sdk/examples/pfop_mkzip.php
new file mode 100755
index 0000000..746b356
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/pfop_mkzip.php
@@ -0,0 +1,43 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+// 去我们的portal 后台来获取AK, SK
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$key = 'qiniu.png';
+
+$auth = new Auth($accessKey, $secretKey);
+// 异步任务的队列, 去后台新建: https://portal.qiniu.com/mps/pipeline
+$pipeline = 'sdktest';
+
+$pfop = new PersistentFop($auth, null);
+
+// 进行zip压缩的url
+$url1 = 'http://phpsdk.qiniudn.com/php-logo.png';
+$url2 = 'http://phpsdk.qiniudn.com/1.png';
+
+//压缩后的key
+$zipKey = 'test.zip';
+
+$fops = 'mkzip/2/url/' . \Qiniu\base64_urlSafeEncode($url1);
+$fops .= '/url/' . \Qiniu\base64_urlSafeEncode($url2);
+$fops .= '|saveas/' . \Qiniu\base64_urlSafeEncode("$bucket:$zipKey");
+
+$notify_url = null;
+$force = false;
+
+list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notify_url, $force);
+
+echo "\n====> pfop mkzip result: \n";
+if ($err != null) {
+ var_dump($err);
+} else {
+ echo "PersistentFop Id: $id\n";
+
+ $res = "http://api.qiniu.com/status/get/prefop?id=$id";
+ echo "Processing result: $res";
+}
diff --git a/vendor/qiniu/php-sdk/examples/pfop_vframe.php b/vendor/qiniu/php-sdk/examples/pfop_vframe.php
new file mode 100755
index 0000000..e1df2d5
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/pfop_vframe.php
@@ -0,0 +1,46 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+//对已经上传到七牛的视频发起异步转码操作
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$auth = new Auth($accessKey, $secretKey);
+
+//要转码的文件所在的空间和文件名。
+$key = 'qiniu.mp4';
+
+//转码是使用的队列名称。 https://portal.qiniu.com/mps/pipeline
+$pipeline = 'sdktest';
+
+//转码完成后通知到你的业务服务器。
+$notifyUrl = 'http://375dec79.ngrok.com/notify.php';
+$force = false;
+
+$config = new \Qiniu\Config();
+$config->useHTTPS = true;
+$pfop = new PersistentFop($auth, $config);
+
+//要进行视频截图操作
+$fops = "vframe/jpg/offset/1/w/480/h/360/rotate/90|saveas/" .
+ \Qiniu\base64_urlSafeEncode($bucket . ":qiniu_480x360.jpg");
+
+list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force);
+echo "\n====> pfop avthumb result: \n";
+if ($err != null) {
+ var_dump($err);
+} else {
+ echo "PersistentFop Id: $id\n";
+}
+
+//查询转码的进度和状态
+list($ret, $err) = $pfop->status($id);
+echo "\n====> pfop avthumb status: \n";
+if ($err != null) {
+ var_dump($err);
+} else {
+ var_dump($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/pfop_video_avthumb.php b/vendor/qiniu/php-sdk/examples/pfop_video_avthumb.php
new file mode 100755
index 0000000..aebe815
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/pfop_video_avthumb.php
@@ -0,0 +1,47 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+//对已经上传到七牛的视频发起异步转码操作
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$auth = new Auth($accessKey, $secretKey);
+
+//要转码的文件所在的空间和文件名。
+$key = 'qiniu.mp4';
+
+//转码是使用的队列名称。 https://portal.qiniu.com/mps/pipeline
+$pipeline = 'sdktest';
+$force = false;
+
+//转码完成后通知到你的业务服务器。
+$notifyUrl = 'http://375dec79.ngrok.com/notify.php';
+$config = new \Qiniu\Config();
+//$config->useHTTPS=true;
+
+$pfop = new PersistentFop($auth, $config);
+
+//要进行转码的转码操作。 http://developer.qiniu.com/docs/v6/api/reference/fop/av/avthumb.html
+$fops = "avthumb/mp4/s/640x360/vb/1.4m|saveas/" . \Qiniu\base64_urlSafeEncode($bucket . ":qiniu_640x360.mp4");
+
+list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force);
+echo "\n====> pfop avthumb result: \n";
+if ($err != null) {
+ var_dump($err);
+} else {
+ echo "PersistentFop Id: $id\n";
+}
+
+//查询转码的进度和状态
+list($ret, $err) = $pfop->status($id);
+echo "\n====> pfop avthumb status: \n";
+if ($err != null) {
+ var_dump($err);
+} else {
+ var_dump($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/pfop_watermark.php b/vendor/qiniu/php-sdk/examples/pfop_watermark.php
new file mode 100755
index 0000000..72aa6c4
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/pfop_watermark.php
@@ -0,0 +1,52 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+//对已经上传到七牛的视频发起异步转码操作
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$auth = new Auth($accessKey, $secretKey);
+
+//要转码的文件所在的空间和文件名。
+$key = 'qiniu.mp4';
+
+//转码是使用的队列名称。 https://portal.qiniu.com/mps/pipeline
+$pipeline = 'sdktest';
+
+//转码完成后通知到你的业务服务器。
+$notifyUrl = 'http://375dec79.ngrok.com/notify.php';
+$force = false;
+
+$config = new \Qiniu\Config();
+//$config->useHTTPS=true;
+$pfop = new PersistentFop($auth, $config);
+
+//需要添加水印的图片UrlSafeBase64
+//可以参考http://developer.qiniu.com/code/v6/api/dora-api/av/video-watermark.html
+$base64URL = Qiniu\base64_urlSafeEncode('http://devtools.qiniu.com/qiniu.png');
+
+//水印参数
+$fops = "avthumb/mp4/s/640x360/vb/1.4m/image/" . $base64URL . "|saveas/"
+ . \Qiniu\base64_urlSafeEncode($bucket . ":qiniu_wm.mp4");
+
+list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force);
+echo "\n====> pfop avthumb result: \n";
+if ($err != null) {
+ var_dump($err);
+} else {
+ echo "PersistentFop Id: $id\n";
+}
+
+//查询转码的进度和状态
+list($ret, $err) = $pfop->status($id);
+echo "\n====> pfop avthumb status: \n";
+if ($err != null) {
+ var_dump($err);
+} else {
+ var_dump($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/php-logo.png b/vendor/qiniu/php-sdk/examples/php-logo.png
new file mode 100755
index 0000000..77e051f
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/php-logo.png
Binary files differ
diff --git a/vendor/qiniu/php-sdk/examples/prefop.php b/vendor/qiniu/php-sdk/examples/prefop.php
new file mode 100755
index 0000000..ae61a5f
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/prefop.php
@@ -0,0 +1,29 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+$accessKey = 'Access_Key';
+$secretKey = 'Secret_Key';
+$auth = new Auth($accessKey, $secretKey);
+
+//要持久化处理的文件所在的空间和文件名。
+$bucket = 'Bucket_Name';
+
+//持久化处理使用的队列名称。 https://portal.qiniu.com/mps/pipeline
+$pipeline = 'pipeline_name';
+
+//持久化处理完成后通知到你的业务服务器。
+$notifyUrl = 'http://375dec79.ngrok.com/notify.php';
+$pfop = new PersistentFop($auth, $bucket, $pipeline, $notifyUrl);
+
+$id = "z2.5955c739e3d0041bf80c9baa";
+//查询持久化处理的进度和状态
+list($ret, $err) = $pfop->status($id);
+echo "\n====> pfop avthumb status: \n";
+if ($err != null) {
+ var_dump($err);
+} else {
+ var_dump($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/pulpvideo.php b/vendor/qiniu/php-sdk/examples/pulpvideo.php
new file mode 100755
index 0000000..bad8821
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/pulpvideo.php
@@ -0,0 +1,55 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Http\Client;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$argusManager = new \Qiniu\Storage\ArgusManager($auth, $config);
+
+$reqBody = array();
+$reqBody['uri'] = "xxxx";
+
+$ops = array();
+$ops = array(
+ array(
+ 'op' => 'pulp',
+ 'params' => array(
+ 'labels' => array(
+ array(
+ 'label' => "1",
+ 'select' => 1,
+ 'score' => 2,
+ ),
+ )
+ )
+ ),
+);
+
+$params = array();
+$params = array(
+ 'async' => false,
+ 'vframe' => array(
+ 'mode' => 1,
+ 'interval' => 8,
+ )
+);
+
+$req = array();
+$req['data'] = $reqBody;
+$req['ops'] = $ops;
+$req['params'] = $params;
+$body = json_encode($req);
+
+$vid = "xxxx";
+list($ret, $err) = $argusManager->pulpVideo($body, $vid);
+
+if ($err !== null) {
+ var_dump($err);
+} else {
+ var_dump($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/put_bucketAccessMode.php b/vendor/qiniu/php-sdk/examples/put_bucketAccessMode.php
new file mode 100755
index 0000000..b453926
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/put_bucketAccessMode.php
@@ -0,0 +1,21 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+$private = 1;
+
+list($Info, $err) = $bucketManager->putBucketAccessMode($bucket, $private);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/put_bucketAccessStyleMode.php b/vendor/qiniu/php-sdk/examples/put_bucketAccessStyleMode.php
new file mode 100755
index 0000000..2f7c27f
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/put_bucketAccessStyleMode.php
@@ -0,0 +1,21 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+$mode = 1;
+
+list($Info, $err) = $bucketManager->putBucketAccessStyleMode($bucket, $mode);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/put_bucketEvent.php b/vendor/qiniu/php-sdk/examples/put_bucketEvent.php
new file mode 100755
index 0000000..33dbb44
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/put_bucketEvent.php
@@ -0,0 +1,25 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+$name = 'testdemo';
+$prefix = 'test1';
+$suffix = 'mp3';
+$event = array("move","copy");
+$callbackURL = 'http://www.qiniu.com';
+
+list($Info, $err) = $bucketManager->putBucketEvent($bucket, $name, $prefix, $suffix, $event, $callbackURL);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/put_bucketMaxAge.php b/vendor/qiniu/php-sdk/examples/put_bucketMaxAge.php
new file mode 100755
index 0000000..77092e9
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/put_bucketMaxAge.php
@@ -0,0 +1,21 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+$maxAge = 15768000;
+
+list($Info, $err) = $bucketManager->putBucketMaxAge($bucket, $maxAge);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/put_bucketQuota.php b/vendor/qiniu/php-sdk/examples/put_bucketQuota.php
new file mode 100755
index 0000000..18082b6
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/put_bucketQuota.php
@@ -0,0 +1,22 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+$size = 99999;
+$count = 99;
+
+list($Info, $err) = $bucketManager->putBucketQuota($bucket, $size, $count);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/put_referAntiLeech.php b/vendor/qiniu/php-sdk/examples/put_referAntiLeech.php
new file mode 100755
index 0000000..6828bc9
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/put_referAntiLeech.php
@@ -0,0 +1,23 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+$mode = 1;
+$norefer = "1";
+$pattern = "*.qiniu.com";
+
+list($Info, $err) = $bucketManager->putReferAntiLeech($bucket, $mode, $norefer, $pattern);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/qetag.php b/vendor/qiniu/php-sdk/examples/qetag.php
new file mode 100755
index 0000000..f6aff8a
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/qetag.php
@@ -0,0 +1,11 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+use Qiniu\Etag;
+
+$localFile = "/Users/jemy/Documents/qiniu.mp4";
+list($etag, $err) = Etag::sum($localFile);
+if ($err == null) {
+ echo "Etag: $etag";
+} else {
+ var_dump($err);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_change_mime.php b/vendor/qiniu/php-sdk/examples/rs_batch_change_mime.php
new file mode 100755
index 0000000..d7d07cf
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_batch_change_mime.php
@@ -0,0 +1,28 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keyMimePairs = array(
+ 'qiniu.mp4' => 'video/x-mp4',
+ 'qiniu.png' => 'image/x-png',
+ 'qiniu.jpg' => 'image/x-jpg'
+);
+
+$ops = $bucketManager->buildBatchChangeMime($bucket, $keyMimePairs);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_change_type.php b/vendor/qiniu/php-sdk/examples/rs_batch_change_type.php
new file mode 100755
index 0000000..5f3f1cd
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_batch_change_type.php
@@ -0,0 +1,34 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keys = array(
+ 'qiniu.mp4',
+ 'qiniu.png',
+ 'qiniu.jpg'
+);
+
+$keyTypePairs = array();
+//type=0表示普通存储,type=1表示低频存储
+foreach ($keys as $key) {
+ $keyTypePairs[$key] = 1;
+}
+
+$ops = $bucketManager->buildBatchChangeType($bucket, $keyTypePairs);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_copy.php b/vendor/qiniu/php-sdk/examples/rs_batch_copy.php
new file mode 100755
index 0000000..988c642
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_batch_copy.php
@@ -0,0 +1,36 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keys = array(
+ 'qiniu.mp4',
+ 'qiniu.png',
+ 'qiniu.jpg'
+);
+
+$keyPairs = array();
+foreach ($keys as $key) {
+ $keyPairs[$key] = $key . "_copy";
+}
+
+$srcBucket = $bucket;
+$destBucket = $bucket;
+
+$ops = $bucketManager->buildBatchCopy($srcBucket, $keyPairs, $destBucket, true);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_delete.php b/vendor/qiniu/php-sdk/examples/rs_batch_delete.php
new file mode 100755
index 0000000..4f15586
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_batch_delete.php
@@ -0,0 +1,28 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keys = array(
+ 'qiniu.mp4',
+ 'qiniu.png',
+ 'qiniu.jpg'
+);
+
+$ops = $bucketManager->buildBatchDelete($bucket, $keys);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_delete_after_days.php b/vendor/qiniu/php-sdk/examples/rs_batch_delete_after_days.php
new file mode 100755
index 0000000..dabfe84
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_batch_delete_after_days.php
@@ -0,0 +1,34 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keys = array(
+ 'qiniu.mp4',
+ 'qiniu.png',
+ 'qiniu.jpg'
+);
+
+$keyDayPairs = array();
+//day=0表示永久存储
+foreach ($keys as $key) {
+ $keyDayPairs[$key] = 0;
+}
+
+$ops = $bucketManager->buildBatchDeleteAfterDays($bucket, $keyDayPairs);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_move.php b/vendor/qiniu/php-sdk/examples/rs_batch_move.php
new file mode 100755
index 0000000..8922522
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_batch_move.php
@@ -0,0 +1,36 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keys = array(
+ 'qiniu.mp4',
+ 'qiniu.png',
+ 'qiniu.jpg'
+);
+
+$keyPairs = array();
+foreach ($keys as $key) {
+ $keyPairs[$key . "_copy"] = $key . "_move";
+}
+
+$srcBucket = $bucket;
+$destBucket = $bucket;
+
+$ops = $bucketManager->buildBatchMove($srcBucket, $keyPairs, $destBucket, true);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_stat.php b/vendor/qiniu/php-sdk/examples/rs_batch_stat.php
new file mode 100755
index 0000000..a95fee7
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_batch_stat.php
@@ -0,0 +1,28 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keys = array(
+ 'qiniu.mp4',
+ 'qiniu.png',
+ 'qiniu.jpg'
+);
+
+$ops = $bucketManager->buildBatchStat($bucket, $keys);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_bucket_domains.php b/vendor/qiniu/php-sdk/examples/rs_bucket_domains.php
new file mode 100755
index 0000000..ea27cdc
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_bucket_domains.php
@@ -0,0 +1,19 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+list($domains, $err) = $bucketManager->domains($bucket);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($domains);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_buckets.php b/vendor/qiniu/php-sdk/examples/rs_buckets.php
new file mode 100755
index 0000000..5fe1304
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_buckets.php
@@ -0,0 +1,19 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+list($buckets, $err) = $bucketManager->buckets(true);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($buckets);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_change_mime.php b/vendor/qiniu/php-sdk/examples/rs_change_mime.php
new file mode 100755
index 0000000..0d3f3ad
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_change_mime.php
@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = 'qiniu.mp4';
+$newMime = 'video/x-mp4';
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$err = $bucketManager->changeMime($bucket, $key, $newMime);
+if ($err) {
+ print_r($err);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_change_status.php b/vendor/qiniu/php-sdk/examples/rs_change_status.php
new file mode 100755
index 0000000..cbcea5c
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_change_status.php
@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = "qiniu.jpg";
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$status = 1;//0表示启用;1表示禁用
+
+$err = $bucketManager->changeStatus($bucket, $key, $status);
+if ($err) {
+ print_r($err);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_change_type.php b/vendor/qiniu/php-sdk/examples/rs_change_type.php
new file mode 100755
index 0000000..acb8963
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_change_type.php
@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = "qiniu.mp4";
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$fileType = 1;//0 表示普通存储,1表示低频存储
+
+$err = $bucketManager->changeType($bucket, $key, $fileType);
+if ($err) {
+ print_r($err);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_copy.php b/vendor/qiniu/php-sdk/examples/rs_copy.php
new file mode 100755
index 0000000..10e7de8
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_copy.php
@@ -0,0 +1,22 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = "qiniu.mp4";
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$srcBucket = $bucket;
+$destBucket = $bucket;
+$srcKey = $key;
+$destKey = $key . "_copy";
+$err = $bucketManager->copy($srcBucket, $srcKey, $destBucket, $destKey, true);
+if ($err) {
+ print_r($err);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_delete.php b/vendor/qiniu/php-sdk/examples/rs_delete.php
new file mode 100755
index 0000000..365d3be
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_delete.php
@@ -0,0 +1,17 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = "qiniu.mp4_copy";
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+$err = $bucketManager->delete($bucket, $key);
+if ($err) {
+ print_r($err);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_delete_after_days.php b/vendor/qiniu/php-sdk/examples/rs_delete_after_days.php
new file mode 100755
index 0000000..ba0b586
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_delete_after_days.php
@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = 'qiniu.mp4';
+$days = 10;
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$err = $bucketManager->deleteAfterDays($bucket, $key, $days);
+if ($err) {
+ print_r($err);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_download_urls.php b/vendor/qiniu/php-sdk/examples/rs_download_urls.php
new file mode 100755
index 0000000..522b9f2
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_download_urls.php
@@ -0,0 +1,17 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+// 构建Auth对象
+$auth = new Auth($accessKey, $secretKey);
+
+// 私有空间中的外链 http://<domain>/<file_key>
+$baseUrl = 'http://if-pri.qiniudn.com/qiniu.png?imageView2/1/h/500';
+// 对链接进行签名
+$signedUrl = $auth->privateDownloadUrl($baseUrl);
+
+echo $signedUrl;
diff --git a/vendor/qiniu/php-sdk/examples/rs_fetch.php b/vendor/qiniu/php-sdk/examples/rs_fetch.php
new file mode 100755
index 0000000..6792410
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_fetch.php
@@ -0,0 +1,34 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\BucketManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$auth = new Auth($accessKey, $secretKey);
+$bucketManager = new BucketManager($auth);
+
+$url = 'http://devtools.qiniu.com/qiniu.png';
+$key = time() . '.png';
+
+// 指定抓取的文件保存名称
+list($ret, $err) = $bucketManager->fetch($url, $bucket, $key);
+echo "=====> fetch $url to bucket: $bucket key: $key\n";
+if ($err !== null) {
+ var_dump($err);
+} else {
+ print_r($ret);
+}
+
+// 不指定key时,以文件内容的hash作为文件名
+$key = null;
+list($ret, $err) = $bucketManager->fetch($url, $bucket, $key);
+echo "=====> fetch $url to bucket: $bucket key: $(etag)\n";
+if ($err !== null) {
+ var_dump($err);
+} else {
+ print_r($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_move.php b/vendor/qiniu/php-sdk/examples/rs_move.php
new file mode 100755
index 0000000..5610585
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_move.php
@@ -0,0 +1,22 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = "qiniu.mp4";
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$srcBucket = $bucket;
+$destBucket = $bucket;
+$srcKey = $key . "_copy";
+$destKey = $key . "_move";
+$err = $bucketManager->move($srcBucket, $srcKey, $destBucket, $destKey, true);
+if ($err) {
+ print_r($err);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_prefetch.php b/vendor/qiniu/php-sdk/examples/rs_prefetch.php
new file mode 100755
index 0000000..de947a7
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_prefetch.php
@@ -0,0 +1,17 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = "qiniu.mp4";
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+$err = $bucketManager->prefetch($bucket, $key);
+if ($err) {
+ print_r($err);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_pub_domain.php b/vendor/qiniu/php-sdk/examples/rs_pub_domain.php
new file mode 100755
index 0000000..2e81922
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_pub_domain.php
@@ -0,0 +1,19 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = 'gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5';
+$secretKey = '9G4isTkVuj5ITPqH1ajhljJMTc2k4m-hZh5r5ZsK';
+$bucket = 'file';
+
+$domain = 'lfxfile.qiniuts.com';
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$err = $bucketManager->publishDomain($bucket, $domain);
+if ($err) {
+ print_r($err);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rs_stat.php b/vendor/qiniu/php-sdk/examples/rs_stat.php
new file mode 100755
index 0000000..891e4e0
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rs_stat.php
@@ -0,0 +1,19 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = "qiniu.mp4";
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+list($fileInfo, $err) = $bucketManager->stat($bucket, $key);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($fileInfo);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rsf_list_bucket.php b/vendor/qiniu/php-sdk/examples/rsf_list_bucket.php
new file mode 100755
index 0000000..5ce9a62
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rsf_list_bucket.php
@@ -0,0 +1,46 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\BucketManager;
+
+// http://developer.qiniu.com/docs/v6/api/reference/rs/list.html#list-description
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$auth = new Auth($accessKey, $secretKey);
+$bucketManager = new BucketManager($auth);
+
+
+// 要列取文件的公共前缀
+$prefix = '';
+
+// 上次列举返回的位置标记,作为本次列举的起点信息。
+$marker = '';
+
+// 本次列举的条目数
+$limit = 200;
+
+$delimiter = '/';
+
+// 列举文件
+do {
+ list($ret, $err) = $bucketManager->listFiles($bucket, $prefix, $marker, $limit, $delimiter);
+ if ($err !== null) {
+ echo "\n====> list file err: \n";
+ var_dump($err);
+ } else {
+ $marker = null;
+ if (array_key_exists('marker', $ret)) {
+ $marker = $ret['marker'];
+ }
+ echo "Marker: $marker\n";
+ echo "\nList Items====>\n";
+ //var_dump($ret['items']);
+ print('items count:' . count($ret['items']) . "\n");
+ if (array_key_exists('commonPrefixes', $ret)) {
+ print_r($ret['commonPrefixes']);
+ }
+ }
+} while (!empty($marker));
diff --git a/vendor/qiniu/php-sdk/examples/rsf_list_files.php b/vendor/qiniu/php-sdk/examples/rsf_list_files.php
new file mode 100755
index 0000000..a3981c5
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rsf_list_files.php
@@ -0,0 +1,38 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\BucketManager;
+
+// http://developer.qiniu.com/docs/v6/api/reference/rs/list.html#list-description
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$auth = new Auth($accessKey, $secretKey);
+$bucketManager = new BucketManager($auth);
+
+
+// 要列取文件的公共前缀
+$prefix = '';
+
+// 上次列举返回的位置标记,作为本次列举的起点信息。
+$marker = '';
+
+// 本次列举的条目数
+$limit = 100;
+
+$delimiter = '/';
+
+// 列举文件
+list($ret, $err) = $bucketManager->listFiles($bucket, $prefix, $marker, $limit, $delimiter);
+if ($err !== null) {
+ echo "\n====> list file err: \n";
+ var_dump($err);
+} else {
+ if (array_key_exists('marker', $ret)) {
+ echo "Marker:" . $ret["marker"] . "\n";
+ }
+ echo "\nList Iterms====>\n";
+ //var_dump($ret['items']);
+}
diff --git a/vendor/qiniu/php-sdk/examples/rsf_v2list_bucket.php b/vendor/qiniu/php-sdk/examples/rsf_v2list_bucket.php
new file mode 100755
index 0000000..4dcf270
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/rsf_v2list_bucket.php
@@ -0,0 +1,33 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\BucketManager;
+
+// http://developer.qiniu.com/docs/v6/api/reference/rs/list.html#list-description
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = 'xxxx';
+$auth = new Auth($accessKey, $secretKey);
+$bucketManager = new BucketManager($auth);
+
+
+// 要列取文件的公共前缀
+$prefix = '';
+
+// 上次列举返回的位置标记,作为本次列举的起点信息。
+$marker = '';
+
+// 本次列举的条目数
+$limit = 1000;
+
+$delimiter = '';
+
+list($ret, $err) = $bucketManager->listFilesv2($bucket, $prefix, $marker, $limit, $delimiter, true);
+
+if ($err) {
+ print_r($err);
+} else {
+ print_r($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/saveas.php b/vendor/qiniu/php-sdk/examples/saveas.php
new file mode 100755
index 0000000..d896f3b
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/saveas.php
@@ -0,0 +1,28 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+// 后台来获取AK, SK
+$accessKey = 'Access_Key';
+$secretKey = 'Secret_Key';
+
+//生成EncodedEntryURI的值
+$entry = '<bucket>:<Key>';//<Key>为生成缩略图的文件名
+//生成的值
+$encodedEntryURI = \Qiniu\base64_urlSafeEncode($entry);
+
+//使用SecretKey对新的下载URL进行HMAC1-SHA1签名
+$newurl = "78re52.com1.z0.glb.clouddn.com/resource/Ship.jpg?imageView2/2/w/200/h/200|saveas/" . $encodedEntryURI;
+
+$sign = hash_hmac("sha1", $newurl, $secretKey, true);
+
+//对签名进行URL安全的Base64编码
+$encodedSign = \Qiniu\base64_urlSafeEncode($sign);
+//最终得到的完整下载URL
+$finalURL = "http://" . $newurl . "/sign/" . $accessKey . ":" . $encodedSign;
+
+$callbackBody = file_get_contents("$finalURL");
+
+echo $callbackBody;
diff --git a/vendor/qiniu/php-sdk/examples/update_bucketEvent.php b/vendor/qiniu/php-sdk/examples/update_bucketEvent.php
new file mode 100755
index 0000000..26347ac
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/update_bucketEvent.php
@@ -0,0 +1,25 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+$name = 'demo';
+$prefix = 'test';
+$suffix = 'mp4';
+$event = 'mkfile';
+$callbackURL = 'https://www.qiniu.com';
+
+list($Info, $err) = $bucketManager->updateBucketEvent($bucket, $name, $prefix, $suffix, $event, $callbackURL);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/update_bucketLifecycleRule.php b/vendor/qiniu/php-sdk/examples/update_bucketLifecycleRule.php
new file mode 100755
index 0000000..85eb07a
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/update_bucketLifecycleRule.php
@@ -0,0 +1,30 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$bucket = 'xxxx';
+$name = 'demo';
+$prefix = 'test';
+$delete_after_days = 90;
+$to_line_after_days =80;
+
+list($Info, $err) = $bucketManager->updateBucketLifecycleRule(
+ $bucket,
+ $name,
+ $prefix,
+ $delete_after_days,
+ $to_line_after_days
+);
+if ($err) {
+ print_r($err);
+} else {
+ print_r($Info);
+}
diff --git a/vendor/qiniu/php-sdk/examples/upload_and_callback.php b/vendor/qiniu/php-sdk/examples/upload_and_callback.php
new file mode 100755
index 0000000..1a70705
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/upload_and_callback.php
@@ -0,0 +1,34 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+use Qiniu\Auth;
+use Qiniu\Storage\UploadManager;
+
+// use Qiniu\Config;
+// use Qiniu\Zone;
+
+// 指定zone上传
+// $zone = Zone::qvmZonez0(); //华东QVM内网上传指定host
+// $config = new Config($zone);
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$auth = new Auth($accessKey, $secretKey);
+// 上传文件到七牛后, 七牛将文件名和文件大小回调给业务服务器.
+// 可参考文档: http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html
+$policy = array(
+ 'callbackUrl' => 'http://your.domain.com/upload_verify_callback.php',
+ 'callbackBody' => 'filename=$(fname)&filesize=$(fsize)'
+);
+$uptoken = $auth->uploadToken($bucket, null, 3600, $policy);
+//上传文件的本地路径
+$filePath = './php-logo.png';
+//指定 config
+// $uploadMgr = new UploadManager($config);
+$uploadMgr = new UploadManager();
+list($ret, $err) = $uploadMgr->putFile($uptoken, null, $filePath);
+echo "\n====> putFile result: \n";
+if ($err !== null) {
+ var_dump($err);
+} else {
+ var_dump($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/upload_and_pfop.php b/vendor/qiniu/php-sdk/examples/upload_and_pfop.php
new file mode 100755
index 0000000..898c09c
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/upload_and_pfop.php
@@ -0,0 +1,38 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\UploadManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$auth = new Auth($accessKey, $secretKey);
+
+
+// 在七牛保存的文件名
+$key = 'php-logo.png';
+$uploadMgr = new UploadManager();
+
+$pfop = "imageMogr2/rotate/90|saveas/" . \Qiniu\base64_urlSafeEncode($bucket . ":php-logo-rotate.png");
+
+//转码完成后通知到你的业务服务器。(公网可以访问,并相应200 OK)
+$notifyUrl = 'http://notify.fake.com';
+
+//独立的转码队列:https://portal.qiniu.com/mps/pipeline
+$pipeline = 'sdktest';
+
+$policy = array(
+ 'persistentOps' => $pfop,
+ 'persistentNotifyUrl' => $notifyUrl,
+ 'persistentPipeline' => $pipeline
+);
+$token = $auth->uploadToken($bucket, null, 3600, $policy);
+
+list($ret, $err) = $uploadMgr->putFile($token, null, $key);
+echo "\n====> putFile result: \n";
+if ($err !== null) {
+ var_dump($err);
+} else {
+ var_dump($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/upload_mgr_init.php b/vendor/qiniu/php-sdk/examples/upload_mgr_init.php
new file mode 100755
index 0000000..3459ef1
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/upload_mgr_init.php
@@ -0,0 +1,18 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\UploadManager;
+
+$accessKey = 'Access_Key';
+$secretKey = 'Secret_Key';
+$auth = new Auth($accessKey, $secretKey);
+
+// 空间名 http://developer.qiniu.com/docs/v6/api/overview/concepts.html#bucket
+$bucket = 'Bucket_Name';
+
+// 生成上传Token
+$token = $auth->uploadToken($bucket);
+
+// 构建 UploadManager 对象
+$uploadMgr = new UploadManager();
diff --git a/vendor/qiniu/php-sdk/examples/upload_multi_demos.php b/vendor/qiniu/php-sdk/examples/upload_multi_demos.php
new file mode 100755
index 0000000..3bbcd60
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/upload_multi_demos.php
@@ -0,0 +1,85 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\UploadManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$pipeline = 'sdktest';
+
+$auth = new Auth($accessKey, $secretKey);
+$token = $auth->uploadToken($bucket);
+$uploadMgr = new UploadManager();
+
+//----------------------------------------upload demo1 ----------------------------------------
+// 上传字符串到七牛
+list($ret, $err) = $uploadMgr->put($token, null, 'content string');
+echo "\n====> put result: \n";
+if ($err !== null) {
+ var_dump($err);
+} else {
+ var_dump($ret);
+}
+
+
+//----------------------------------------upload demo2 ----------------------------------------
+// 上传文件到七牛
+$filePath = './php-logo.png';
+$key = 'php-logo.png';
+list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath);
+echo "\n====> putFile result: \n";
+if ($err !== null) {
+ var_dump($err);
+} else {
+ var_dump($ret);
+}
+
+
+//----------------------------------------upload demo3 ----------------------------------------
+// 上传文件到七牛后, 七牛将文件名和文件大小回调给业务服务器.
+// 可参考文档: http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html
+$policy = array(
+ 'callbackUrl' => 'http://172.30.251.210/upload_verify_callback.php',
+ 'callbackBody' => 'filename=$(fname)&filesize=$(fsize)'
+// 'callbackBodyType' => 'application/json',
+// 'callbackBody' => '{"filename":$(fname), "filesize": $(fsize)}' //设置application/json格式回调
+);
+$token = $auth->uploadToken($bucket, null, 3600, $policy);
+
+
+list($ret, $err) = $uploadMgr->putFile($token, null, $key);
+echo "\n====> putFile result: \n";
+if ($err !== null) {
+ var_dump($err);
+} else {
+ var_dump($ret);
+}
+
+
+//----------------------------------------upload demo4 ----------------------------------------
+//上传视频,上传完成后进行m3u8的转码, 并给视频打水印
+$wmImg = Qiniu\base64_urlSafeEncode('http://devtools.qiniudn.com/qiniu.png');
+$pfop = "avthumb/m3u8/wmImage/$wmImg";
+
+//转码完成后回调到业务服务器。(公网可以访问,并相应200 OK)
+$notifyUrl = 'http://notify.fake.com';
+
+//独立的转码队列:https://portal.qiniu.com/mps/pipeline
+
+
+$policy = array(
+ 'persistentOps' => $pfop,
+ 'persistentNotifyUrl' => $notifyUrl,
+ 'persistentPipeline' => $pipeline
+);
+$token = $auth->uploadToken($bucket, null, 3600, $policy);
+print($token);
+list($ret, $err) = $uploadMgr->putFile($token, null, $key);
+echo "\n====> putFile result: \n";
+if ($err !== null) {
+ var_dump($err);
+} else {
+ var_dump($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/upload_simple_file.php b/vendor/qiniu/php-sdk/examples/upload_simple_file.php
new file mode 100755
index 0000000..9d003f0
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/upload_simple_file.php
@@ -0,0 +1,37 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+// 引入鉴权类
+use Qiniu\Auth;
+
+// 引入上传类
+use Qiniu\Storage\UploadManager;
+
+// 需要填写你的 Access Key 和 Secret Key
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+// 构建鉴权对象
+$auth = new Auth($accessKey, $secretKey);
+
+// 生成上传 Token
+$token = $auth->uploadToken($bucket);
+
+// 要上传文件的本地路径
+$filePath = './php-logo.png';
+
+// 上传到七牛后保存的文件名
+$key = 'my-php-logo.png';
+
+// 初始化 UploadManager 对象并进行文件的上传。
+$uploadMgr = new UploadManager();
+
+// 调用 UploadManager 的 putFile 方法进行文件的上传。
+list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath);
+echo "\n====> putFile result: \n";
+if ($err !== null) {
+ var_dump($err);
+} else {
+ var_dump($ret);
+}
diff --git a/vendor/qiniu/php-sdk/examples/upload_tokens.php b/vendor/qiniu/php-sdk/examples/upload_tokens.php
new file mode 100755
index 0000000..90f2423
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/upload_tokens.php
@@ -0,0 +1,68 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+// 初始化Auth状态
+$auth = new Auth($accessKey, $secretKey);
+
+// 简单上传凭证
+$expires = 3600;
+
+$policy = null;
+$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true);
+print($upToken . "\n");
+
+// 自定义凭证有效期(示例2小时)
+$expires = 7200;
+$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true);
+print($upToken . "\n");
+
+// 覆盖上传凭证
+$expires = 3600;
+$keyToOverwrite = 'qiniu.mp4';
+$upToken = $auth->uploadToken($bucket, $keyToOverwrite, $expires, $policy, true);
+print($upToken . "\n");
+
+//自定义上传回复(非callback模式)凭证
+$returnBody = '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}';
+$policy = array(
+ 'returnBody' => $returnBody
+);
+$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true);
+print($upToken . "\n");
+
+//带回调业务服务器的凭证(application/json)
+$policy = array(
+ 'callbackUrl' => 'http://api.example.com/qiniu/upload/callback',
+ 'callbackBody' => '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}',
+ 'callbackBodyType' => 'application/json'
+);
+$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true);
+print($upToken . "\n");
+
+
+//带回调业务服务器的凭证(application/x-www-form-urlencoded)
+$policy = array(
+ 'callbackUrl' => 'http://api.example.com/qiniu/upload/callback',
+ 'callbackBody' => 'key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)&name=$(x:name)'
+);
+$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true);
+print($upToken . "\n");
+
+//带数据处理的凭证
+$saveMp4Entry = \Qiniu\base64_urlSafeEncode($bucket . ":avthumb_test_target.mp4");
+$saveJpgEntry = \Qiniu\base64_urlSafeEncode($bucket . ":vframe_test_target.jpg");
+$avthumbMp4Fop = "avthumb/mp4|saveas/" . $saveMp4Entry;
+$vframeJpgFop = "vframe/jpg/offset/1|saveas/" . $saveJpgEntry;
+$policy = array(
+ 'persistentOps' => $avthumbMp4Fop . ";" . $vframeJpgFop,
+ 'persistentPipeline' => "video-pipe",
+ 'persistentNotifyUrl' => "http://api.example.com/qiniu/pfop/notify",
+);
+$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true);
+print($upToken . "\n");
diff --git a/vendor/qiniu/php-sdk/examples/upload_verify_callback.php b/vendor/qiniu/php-sdk/examples/upload_verify_callback.php
new file mode 100755
index 0000000..ffbbc80
--- /dev/null
+++ b/vendor/qiniu/php-sdk/examples/upload_verify_callback.php
@@ -0,0 +1,32 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$auth = new Auth($accessKey, $secretKey);
+
+//获取回调的body信息
+$callbackBody = file_get_contents('php://input');
+
+//回调的contentType
+$contentType = 'application/x-www-form-urlencoded';
+
+//回调的签名信息,可以验证该回调是否来自七牛
+$authorization = $_SERVER['HTTP_AUTHORIZATION'];
+
+//七牛回调的url,具体可以参考:http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html
+$url = 'http://172.30.251.210/upload_verify_callback.php';
+
+$isQiniuCallback = $auth->verifyCallback($contentType, $authorization, $url, $callbackBody);
+
+if ($isQiniuCallback) {
+ $resp = array('ret' => 'success');
+} else {
+ $resp = array('ret' => 'failed');
+}
+
+echo json_encode($resp);
diff --git a/vendor/qiniu/php-sdk/phpunit.xml.dist b/vendor/qiniu/php-sdk/phpunit.xml.dist
new file mode 100755
index 0000000..840f6e5
--- /dev/null
+++ b/vendor/qiniu/php-sdk/phpunit.xml.dist
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ bootstrap="./tests/bootstrap.php"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnFailure="false"
+>
+ <testsuites>
+ <testsuite name="Qiniu SDK Tests">
+ <directory>tests</directory>
+ </testsuite>
+ </testsuites>
+
+</phpunit>
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Auth.php b/vendor/qiniu/php-sdk/src/Qiniu/Auth.php
new file mode 100755
index 0000000..b5e4a6b
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Auth.php
@@ -0,0 +1,187 @@
+<?php
+namespace Qiniu;
+
+use Qiniu\Zone;
+
+final class Auth
+{
+ private $accessKey;
+ private $secretKey;
+
+ public function __construct($accessKey, $secretKey)
+ {
+ $this->accessKey = $accessKey;
+ $this->secretKey = $secretKey;
+ }
+
+ public function getAccessKey()
+ {
+ return $this->accessKey;
+ }
+
+ public function sign($data)
+ {
+ $hmac = hash_hmac('sha1', $data, $this->secretKey, true);
+ return $this->accessKey . ':' . \Qiniu\base64_urlSafeEncode($hmac);
+ }
+
+ public function signWithData($data)
+ {
+ $encodedData = \Qiniu\base64_urlSafeEncode($data);
+ return $this->sign($encodedData) . ':' . $encodedData;
+ }
+
+ public function signRequest($urlString, $body, $contentType = null)
+ {
+ $url = parse_url($urlString);
+ $data = '';
+ if (array_key_exists('path', $url)) {
+ $data = $url['path'];
+ }
+ if (array_key_exists('query', $url)) {
+ $data .= '?' . $url['query'];
+ }
+ $data .= "\n";
+
+ if ($body !== null && $contentType === 'application/x-www-form-urlencoded') {
+ $data .= $body;
+ }
+ return $this->sign($data);
+ }
+
+ public function verifyCallback($contentType, $originAuthorization, $url, $body)
+ {
+ $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType);
+ return $originAuthorization === $authorization;
+ }
+
+ public function privateDownloadUrl($baseUrl, $expires = 3600)
+ {
+ $deadline = time() + $expires;
+
+ $pos = strpos($baseUrl, '?');
+ if ($pos !== false) {
+ $baseUrl .= '&e=';
+ } else {
+ $baseUrl .= '?e=';
+ }
+ $baseUrl .= $deadline;
+
+ $token = $this->sign($baseUrl);
+ return "$baseUrl&token=$token";
+ }
+
+ public function uploadToken($bucket, $key = null, $expires = 3600, $policy = null, $strictPolicy = true)
+ {
+ $deadline = time() + $expires;
+ $scope = $bucket;
+ if ($key !== null) {
+ $scope .= ':' . $key;
+ }
+
+ $args = self::copyPolicy($args, $policy, $strictPolicy);
+ $args['scope'] = $scope;
+ $args['deadline'] = $deadline;
+
+ $b = json_encode($args);
+ return $this->signWithData($b);
+ }
+
+ /**
+ *上传策略,参数规格详见
+ *http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html
+ */
+ private static $policyFields = array(
+ 'callbackUrl',
+ 'callbackBody',
+ 'callbackHost',
+ 'callbackBodyType',
+ 'callbackFetchKey',
+
+ 'returnUrl',
+ 'returnBody',
+
+ 'endUser',
+ 'saveKey',
+ 'insertOnly',
+
+ 'detectMime',
+ 'mimeLimit',
+ 'fsizeMin',
+ 'fsizeLimit',
+
+ 'persistentOps',
+ 'persistentNotifyUrl',
+ 'persistentPipeline',
+
+ 'deleteAfterDays',
+ 'fileType',
+ 'isPrefixalScope',
+ );
+
+ private static function copyPolicy(&$policy, $originPolicy, $strictPolicy)
+ {
+ if ($originPolicy === null) {
+ return array();
+ }
+ foreach ($originPolicy as $key => $value) {
+ if (!$strictPolicy || in_array((string)$key, self::$policyFields, true)) {
+ $policy[$key] = $value;
+ }
+ }
+ return $policy;
+ }
+
+ public function authorization($url, $body = null, $contentType = null)
+ {
+ $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType);
+ return array('Authorization' => $authorization);
+ }
+
+ public function authorizationV2($url, $method, $body = null, $contentType = null)
+ {
+ $urlItems = parse_url($url);
+ $host = $urlItems['host'];
+
+ if (isset($urlItems['port'])) {
+ $port = $urlItems['port'];
+ } else {
+ $port = '';
+ }
+
+ $path = $urlItems['path'];
+ if (isset($urlItems['query'])) {
+ $query = $urlItems['query'];
+ } else {
+ $query = '';
+ }
+
+ //write request uri
+ $toSignStr = $method . ' ' . $path;
+ if (!empty($query)) {
+ $toSignStr .= '?' . $query;
+ }
+
+ //write host and port
+ $toSignStr .= "\nHost: " . $host;
+ if (!empty($port)) {
+ $toSignStr .= ":" . $port;
+ }
+
+ //write content type
+ if (!empty($contentType)) {
+ $toSignStr .= "\nContent-Type: " . $contentType;
+ }
+
+ $toSignStr .= "\n\n";
+
+ //write body
+ if (!empty($body)) {
+ $toSignStr .= $body;
+ }
+
+ $sign = $this->sign($toSignStr);
+ $auth = 'Qiniu ' . $sign;
+ return array('Authorization' => $auth);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Cdn/CdnManager.php b/vendor/qiniu/php-sdk/src/Qiniu/Cdn/CdnManager.php
new file mode 100755
index 0000000..c324757
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Cdn/CdnManager.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace Qiniu\Cdn;
+
+use Qiniu\Auth;
+use Qiniu\Http\Error;
+use Qiniu\Http\Client;
+
+final class CdnManager
+{
+
+ private $auth;
+ private $server;
+
+ public function __construct(Auth $auth)
+ {
+ $this->auth = $auth;
+ $this->server = 'http://fusion.qiniuapi.com';
+ }
+
+ /**
+ * @param array $urls 待刷新的文件链接数组
+ * @return array
+ */
+ public function refreshUrls(array $urls)
+ {
+ return $this->refreshUrlsAndDirs($urls, array());
+ }
+
+ /**
+ * @param array $dirs 待刷新的文件链接数组
+ * @return array
+ * 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh
+ * 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category
+ */
+ public function refreshDirs(array $dirs)
+ {
+ return $this->refreshUrlsAndDirs(array(), $dirs);
+ }
+
+ /**
+ * @param array $urls 待刷新的文件链接数组
+ * @param array $dirs 待刷新的目录链接数组
+ *
+ * @return array 刷新的请求回复和错误,参考 examples/cdn_manager.php 代码
+ * @link http://developer.qiniu.com/article/fusion/api/refresh.html
+ *
+ * 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh
+ * 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category
+ */
+ public function refreshUrlsAndDirs(array $urls, array $dirs)
+ {
+ $req = array();
+ if (!empty($urls)) {
+ $req['urls'] = $urls;
+ }
+ if (!empty($dirs)) {
+ $req['dirs'] = $dirs;
+ }
+
+ $url = $this->server . '/v2/tune/refresh';
+ $body = json_encode($req);
+ return $this->post($url, $body);
+ }
+
+ /**
+ * @param array $urls 待预取的文件链接数组
+ *
+ * @return array 预取的请求回复和错误,参考 examples/cdn_manager.php 代码
+ *
+ * @link http://developer.qiniu.com/article/fusion/api/refresh.html
+ */
+ public function prefetchUrls(array $urls)
+ {
+ $req = array(
+ 'urls' => $urls,
+ );
+
+ $url = $this->server . '/v2/tune/prefetch';
+ $body = json_encode($req);
+ return $this->post($url, $body);
+ }
+
+ /**
+ * @param array $domains 待获取带宽数据的域名数组
+ * @param string $startDate 开始的日期,格式类似 2017-01-01
+ * @param string $endDate 结束的日期,格式类似 2017-01-01
+ * @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day
+ *
+ * @return array 带宽数据和错误信息,参考 examples/cdn_manager.php 代码
+ *
+ * @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
+ */
+ public function getBandwidthData(array $domains, $startDate, $endDate, $granularity)
+ {
+ $req = array();
+ $req['domains'] = implode(';', $domains);
+ $req['startDate'] = $startDate;
+ $req['endDate'] = $endDate;
+ $req['granularity'] = $granularity;
+
+ $url = $this->server . '/v2/tune/bandwidth';
+ $body = json_encode($req);
+ return $this->post($url, $body);
+ }
+
+ /**
+ * @param array $domains 待获取流量数据的域名数组
+ * @param string $startDate 开始的日期,格式类似 2017-01-01
+ * @param string $endDate 结束的日期,格式类似 2017-01-01
+ * @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day
+ *
+ * @return array 流量数据和错误信息,参考 examples/cdn_manager.php 代码
+ *
+ * @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
+ */
+ public function getFluxData(array $domains, $startDate, $endDate, $granularity)
+ {
+ $req = array();
+ $req['domains'] = implode(';', $domains);
+ $req['startDate'] = $startDate;
+ $req['endDate'] = $endDate;
+ $req['granularity'] = $granularity;
+
+ $url = $this->server . '/v2/tune/flux';
+ $body = json_encode($req);
+ return $this->post($url, $body);
+ }
+
+ /**
+ * @param array $domains 待获取日志下载链接的域名数组
+ * @param string $logDate 获取指定日期的日志下载链接,格式类似 2017-01-01
+ *
+ * @return array 日志下载链接数据和错误信息,参考 examples/cdn_manager.php 代码
+ *
+ * @link http://developer.qiniu.com/article/fusion/api/log.html
+ */
+ public function getCdnLogList(array $domains, $logDate)
+ {
+ $req = array();
+ $req['domains'] = implode(';', $domains);
+ $req['day'] = $logDate;
+
+ $url = $this->server . '/v2/tune/log/list';
+ $body = json_encode($req);
+ return $this->post($url, $body);
+ }
+
+ private function post($url, $body)
+ {
+ $headers = $this->auth->authorization($url, $body, 'application/json');
+ $headers['Content-Type'] = 'application/json';
+ $ret = Client::post($url, $body, $headers);
+ if (!$ret->ok()) {
+ return array(null, new Error($url, $ret));
+ }
+ $r = ($ret->body === null) ? array() : $ret->json();
+ return array($r, null);
+ }
+
+ /**
+ * 构建时间戳防盗链鉴权的访问外链
+ *
+ * @param string $rawUrl 需要签名的资源url
+ * @param string $encryptKey 时间戳防盗链密钥
+ * @param string $durationInSeconds 链接的有效期(以秒为单位)
+ *
+ * @return string 带鉴权信息的资源外链,参考 examples/cdn_timestamp_antileech.php 代码
+ */
+ public static function createTimestampAntiLeechUrl($rawUrl, $encryptKey, $durationInSeconds)
+ {
+ $parsedUrl = parse_url($rawUrl);
+
+ $deadline = time() + $durationInSeconds;
+ $expireHex = dechex($deadline);
+
+ $path = isset($parsedUrl['path']) ? $parsedUrl['path'] : '';
+ $path = implode('/', array_map('rawurlencode', explode('/', $path)));
+ $strToSign = $encryptKey . $path . $expireHex;
+ $signStr = md5($strToSign);
+
+ $url = $parsedUrl['scheme'].'://'.$parsedUrl['host'].$path;
+
+ if (isset($parsedUrl['query'])) {
+ $signedUrl = $url . '&sign=' . $signStr . '&t=' . $expireHex;
+ } else {
+ $signedUrl = $url . '?sign=' . $signStr . '&t=' . $expireHex;
+ }
+
+ return $signedUrl;
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Config.php b/vendor/qiniu/php-sdk/src/Qiniu/Config.php
new file mode 100755
index 0000000..ec75df6
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Config.php
@@ -0,0 +1,140 @@
+<?php
+namespace Qiniu;
+
+final class Config
+{
+ const SDK_VER = '7.2.9';
+
+ const BLOCK_SIZE = 4194304; //4*1024*1024 分块上传块大小,该参数为接口规格,不能修改
+
+ const RSF_HOST = 'rsf.qiniu.com';
+ const API_HOST = 'api.qiniu.com';
+ const RS_HOST = 'rs.qiniu.com'; //RS Host
+ const UC_HOST = 'uc.qbox.me'; //UC Host
+ const RTCAPI_HOST = 'http://rtc.qiniuapi.com';
+ const ARGUS_HOST = 'argus.atlab.ai';
+ const CASTER_HOST = 'pili-caster.qiniuapi.com';
+ const SMS_HOST="https://sms.qiniuapi.com";
+ const RTCAPI_VERSION = 'v3';
+ const SMS_VERSION='v1';
+
+ // Zone 空间对应的存储区域
+ public $region;
+ //BOOL 是否使用https域名
+ public $useHTTPS;
+ //BOOL 是否使用CDN加速上传域名
+ public $useCdnDomains;
+ // Zone Cache
+ private $regionCache;
+
+ // 构造函数
+ public function __construct(Region $z = null)
+ {
+ $this->zone = $z;
+ $this->useHTTPS = false;
+ $this->useCdnDomains = false;
+ $this->regionCache = array();
+ }
+
+ public function getUpHost($accessKey, $bucket)
+ {
+ $region = $this->getRegion($accessKey, $bucket);
+ if ($this->useHTTPS === true) {
+ $scheme = "https://";
+ } else {
+ $scheme = "http://";
+ }
+
+ $host = $region->srcUpHosts[0];
+ if ($this->useCdnDomains === true) {
+ $host = $region->cdnUpHosts[0];
+ }
+
+ return $scheme . $host;
+ }
+
+ public function getUpBackupHost($accessKey, $bucket)
+ {
+ $region = $this->getRegion($accessKey, $bucket);
+ if ($this->useHTTPS === true) {
+ $scheme = "https://";
+ } else {
+ $scheme = "http://";
+ }
+
+ $host = $region->cdnUpHosts[0];
+ if ($this->useCdnDomains === true) {
+ $host = $region->srcUpHosts[0];
+ }
+
+ return $scheme . $host;
+ }
+
+ public function getRsHost($accessKey, $bucket)
+ {
+ $region = $this->getRegion($accessKey, $bucket);
+
+ if ($this->useHTTPS === true) {
+ $scheme = "https://";
+ } else {
+ $scheme = "http://";
+ }
+
+ return $scheme . $region->rsHost;
+ }
+
+ public function getRsfHost($accessKey, $bucket)
+ {
+ $region = $this->getRegion($accessKey, $bucket);
+
+ if ($this->useHTTPS === true) {
+ $scheme = "https://";
+ } else {
+ $scheme = "http://";
+ }
+
+ return $scheme . $region->rsfHost;
+ }
+
+ public function getIovipHost($accessKey, $bucket)
+ {
+ $region = $this->getRegion($accessKey, $bucket);
+
+ if ($this->useHTTPS === true) {
+ $scheme = "https://";
+ } else {
+ $scheme = "http://";
+ }
+
+ return $scheme . $region->iovipHost;
+ }
+
+ public function getApiHost($accessKey, $bucket)
+ {
+ $region = $this->getRegion($accessKey, $bucket);
+
+ if ($this->useHTTPS === true) {
+ $scheme = "https://";
+ } else {
+ $scheme = "http://";
+ }
+
+ return $scheme . $region->apiHost;
+ }
+
+ private function getRegion($accessKey, $bucket)
+ {
+ $cacheId = "$accessKey:$bucket";
+
+ if (isset($this->regionCache[$cacheId])) {
+ $region = $this->regionCache[$cacheId];
+ } elseif (isset($this->zone)) {
+ $region = $this->zone;
+ $this->regionCache[$cacheId] = $region;
+ } else {
+ $region = Zone::queryZone($accessKey, $bucket);
+ $this->regionCache[$cacheId] = $region;
+ }
+ return $region;
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Etag.php b/vendor/qiniu/php-sdk/src/Qiniu/Etag.php
new file mode 100755
index 0000000..d7be064
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Etag.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Qiniu;
+
+use Qiniu\Config;
+
+final class Etag
+{
+ private static function packArray($v, $a)
+ {
+ return call_user_func_array('pack', array_merge(array($v), (array)$a));
+ }
+
+ private static function blockCount($fsize)
+ {
+ return intval(($fsize + (Config::BLOCK_SIZE - 1)) / Config::BLOCK_SIZE);
+ }
+
+ private static function calcSha1($data)
+ {
+ $sha1Str = sha1($data, true);
+ $err = error_get_last();
+ if ($err !== null) {
+ return array(null, $err);
+ }
+ $byteArray = unpack('C*', $sha1Str);
+ return array($byteArray, null);
+ }
+
+
+ public static function sum($filename)
+ {
+ $fhandler = fopen($filename, 'r');
+ $err = error_get_last();
+ if ($err !== null) {
+ return array(null, $err);
+ }
+
+ $fstat = fstat($fhandler);
+ $fsize = $fstat['size'];
+ if ((int)$fsize === 0) {
+ fclose($fhandler);
+ return array('Fto5o-5ea0sNMlW_75VgGJCv2AcJ', null);
+ }
+ $blockCnt = self::blockCount($fsize);
+ $sha1Buf = array();
+
+ if ($blockCnt <= 1) {
+ array_push($sha1Buf, 0x16);
+ $fdata = fread($fhandler, Config::BLOCK_SIZE);
+ if ($err !== null) {
+ fclose($fhandler);
+ return array(null, $err);
+ }
+ list($sha1Code,) = self::calcSha1($fdata);
+ $sha1Buf = array_merge($sha1Buf, $sha1Code);
+ } else {
+ array_push($sha1Buf, 0x96);
+ $sha1BlockBuf = array();
+ for ($i = 0; $i < $blockCnt; $i++) {
+ $fdata = fread($fhandler, Config::BLOCK_SIZE);
+ list($sha1Code, $err) = self::calcSha1($fdata);
+ if ($err !== null) {
+ fclose($fhandler);
+ return array(null, $err);
+ }
+ $sha1BlockBuf = array_merge($sha1BlockBuf, $sha1Code);
+ }
+ $tmpData = self::packArray('C*', $sha1BlockBuf);
+ list($sha1Final,) = self::calcSha1($tmpData);
+ $sha1Buf = array_merge($sha1Buf, $sha1Final);
+ }
+ $etag = \Qiniu\base64_urlSafeEncode(self::packArray('C*', $sha1Buf));
+ return array($etag, null);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Http/Client.php b/vendor/qiniu/php-sdk/src/Qiniu/Http/Client.php
new file mode 100755
index 0000000..0fe03ce
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Http/Client.php
@@ -0,0 +1,160 @@
+<?php
+namespace Qiniu\Http;
+
+use Qiniu\Config;
+use Qiniu\Http\Request;
+use Qiniu\Http\Response;
+
+final class Client
+{
+ public static function get($url, array $headers = array())
+ {
+ $request = new Request('GET', $url, $headers);
+ return self::sendRequest($request);
+ }
+
+ public static function delete($url, array $headers = array())
+ {
+ $request = new Request('DELETE', $url, $headers);
+ return self::sendRequest($request);
+ }
+
+ public static function post($url, $body, array $headers = array())
+ {
+ $request = new Request('POST', $url, $headers, $body);
+ return self::sendRequest($request);
+ }
+
+ public static function PUT($url, $body, array $headers = array())
+ {
+ $request = new Request('PUT', $url, $headers, $body);
+ return self::sendRequest($request);
+ }
+
+ public static function multipartPost(
+ $url,
+ $fields,
+ $name,
+ $fileName,
+ $fileBody,
+ $mimeType = null,
+ array $headers = array()
+ ) {
+ $data = array();
+ $mimeBoundary = md5(microtime());
+
+ foreach ($fields as $key => $val) {
+ array_push($data, '--' . $mimeBoundary);
+ array_push($data, "Content-Disposition: form-data; name=\"$key\"");
+ array_push($data, '');
+ array_push($data, $val);
+ }
+
+ array_push($data, '--' . $mimeBoundary);
+ $finalMimeType = empty($mimeType) ? 'application/octet-stream' : $mimeType;
+ $finalFileName = self::escapeQuotes($fileName);
+ array_push($data, "Content-Disposition: form-data; name=\"$name\"; filename=\"$finalFileName\"");
+ array_push($data, "Content-Type: $finalMimeType");
+ array_push($data, '');
+ array_push($data, $fileBody);
+
+ array_push($data, '--' . $mimeBoundary . '--');
+ array_push($data, '');
+
+ $body = implode("\r\n", $data);
+ // var_dump($data);exit;
+ $contentType = 'multipart/form-data; boundary=' . $mimeBoundary;
+ $headers['Content-Type'] = $contentType;
+ $request = new Request('POST', $url, $headers, $body);
+ return self::sendRequest($request);
+ }
+
+ private static function userAgent()
+ {
+ $sdkInfo = "QiniuPHP/" . Config::SDK_VER;
+
+ $systemInfo = php_uname("s");
+ $machineInfo = php_uname("m");
+
+ $envInfo = "($systemInfo/$machineInfo)";
+
+ $phpVer = phpversion();
+
+ $ua = "$sdkInfo $envInfo PHP/$phpVer";
+ return $ua;
+ }
+
+ public static function sendRequest($request)
+ {
+ $t1 = microtime(true);
+ $ch = curl_init();
+ $options = array(
+ CURLOPT_USERAGENT => self::userAgent(),
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_SSL_VERIFYPEER => false,
+ CURLOPT_SSL_VERIFYHOST => false,
+ CURLOPT_HEADER => true,
+ CURLOPT_NOBODY => false,
+ CURLOPT_CUSTOMREQUEST => $request->method,
+ CURLOPT_URL => $request->url,
+ );
+ // Handle open_basedir & safe mode
+ if (!ini_get('safe_mode') && !ini_get('open_basedir')) {
+ $options[CURLOPT_FOLLOWLOCATION] = true;
+ }
+ if (!empty($request->headers)) {
+ $headers = array();
+ foreach ($request->headers as $key => $val) {
+ array_push($headers, "$key: $val");
+ }
+ $options[CURLOPT_HTTPHEADER] = $headers;
+ }
+ curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
+ if (!empty($request->body)) {
+ $options[CURLOPT_POSTFIELDS] = $request->body;
+ }
+ curl_setopt_array($ch, $options);
+ $result = curl_exec($ch);
+ $t2 = microtime(true);
+ $duration = round($t2 - $t1, 3);
+ $ret = curl_errno($ch);
+ if ($ret !== 0) {
+ $r = new Response(-1, $duration, array(), null, curl_error($ch));
+ curl_close($ch);
+ return $r;
+ }
+ $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
+ $headers = self::parseHeaders(substr($result, 0, $header_size));
+ $body = substr($result, $header_size);
+ curl_close($ch);
+ return new Response($code, $duration, $headers, $body, null);
+ }
+
+ private static function parseHeaders($raw)
+ {
+ $headers = array();
+ $headerLines = explode("\r\n", $raw);
+ foreach ($headerLines as $line) {
+ $headerLine = trim($line);
+ $kv = explode(':', $headerLine);
+ if (count($kv) > 1) {
+ $kv[0] =self::ucwordsHyphen($kv[0]);
+ $headers[$kv[0]] = trim($kv[1]);
+ }
+ }
+ return $headers;
+ }
+
+ private static function escapeQuotes($str)
+ {
+ $find = array("\\", "\"");
+ $replace = array("\\\\", "\\\"");
+ return str_replace($find, $replace, $str);
+ }
+
+ private static function ucwordsHyphen($str)
+ {
+ return str_replace('- ', '-', ucwords(str_replace('-', '- ', $str)));
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Http/Error.php b/vendor/qiniu/php-sdk/src/Qiniu/Http/Error.php
new file mode 100755
index 0000000..73477cf
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Http/Error.php
@@ -0,0 +1,35 @@
+<?php
+namespace Qiniu\Http;
+
+/**
+ * 七牛业务请求逻辑错误封装类,主要用来解析API请求返回如下的内容:
+ * <pre>
+ * {"error" : "detailed error message"}
+ * </pre>
+ */
+final class Error
+{
+ private $url;
+ private $response;
+
+ public function __construct($url, $response)
+ {
+ $this->url = $url;
+ $this->response = $response;
+ }
+
+ public function code()
+ {
+ return $this->response->statusCode;
+ }
+
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ public function message()
+ {
+ return $this->response->error;
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Http/Request.php b/vendor/qiniu/php-sdk/src/Qiniu/Http/Request.php
new file mode 100755
index 0000000..43b0bfd
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Http/Request.php
@@ -0,0 +1,18 @@
+<?php
+namespace Qiniu\Http;
+
+final class Request
+{
+ public $url;
+ public $headers;
+ public $body;
+ public $method;
+
+ public function __construct($method, $url, array $headers = array(), $body = null)
+ {
+ $this->method = strtoupper($method);
+ $this->url = $url;
+ $this->headers = $headers;
+ $this->body = $body;
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Http/Response.php b/vendor/qiniu/php-sdk/src/Qiniu/Http/Response.php
new file mode 100755
index 0000000..f22ab37
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Http/Response.php
@@ -0,0 +1,176 @@
+<?php
+
+namespace Qiniu\Http;
+
+/**
+ * HTTP response Object
+ */
+final class Response
+{
+ public $statusCode;
+ public $headers;
+ public $body;
+ public $error;
+ private $jsonData;
+ public $duration;
+
+ /** @var array Mapping of status codes to reason phrases */
+ private static $statusTexts = array(
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status',
+ 208 => 'Already Reported',
+ 226 => 'IM Used',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+ 308 => 'Permanent Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Reserved for WebDAV advanced collections expired proposal',
+ 426 => 'Upgrade required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 506 => 'Variant Also Negotiates (Experimental)',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 510 => 'Not Extended',
+ 511 => 'Network Authentication Required',
+ );
+
+ /**
+ * @param int $code 状态码
+ * @param double $duration 请求时长
+ * @param array $headers 响应头部
+ * @param string $body 响应内容
+ * @param string $error 错误描述
+ */
+ public function __construct($code, $duration, array $headers = array(), $body = null, $error = null)
+ {
+ $this->statusCode = $code;
+ $this->duration = $duration;
+ $this->headers = $headers;
+ $this->body = $body;
+ $this->error = $error;
+ $this->jsonData = null;
+ if ($error !== null) {
+ return;
+ }
+
+ if ($body === null) {
+ if ($code >= 400) {
+ $this->error = self::$statusTexts[$code];
+ }
+ return;
+ }
+ if (self::isJson($headers)) {
+ try {
+ $jsonData = self::bodyJson($body);
+ if ($code >= 400) {
+ $this->error = $body;
+ if ($jsonData['error'] !== null) {
+ $this->error = $jsonData['error'];
+ }
+ }
+ $this->jsonData = $jsonData;
+ } catch (\InvalidArgumentException $e) {
+ $this->error = $body;
+ if ($code >= 200 && $code < 300) {
+ $this->error = $e->getMessage();
+ }
+ }
+ } elseif ($code >= 400) {
+ $this->error = $body;
+ }
+ return;
+ }
+
+ public function json()
+ {
+ return $this->jsonData;
+ }
+
+ private static function bodyJson($body)
+ {
+ return \Qiniu\json_decode((string) $body, true, 512);
+ }
+
+ public function xVia()
+ {
+ $via = $this->headers['X-Via'];
+ if ($via === null) {
+ $via = $this->headers['X-Px'];
+ }
+ if ($via === null) {
+ $via = $this->headers['Fw-Via'];
+ }
+ return $via;
+ }
+
+ public function xLog()
+ {
+ return $this->headers['X-Log'];
+ }
+
+ public function xReqId()
+ {
+ return $this->headers['X-Reqid'];
+ }
+
+ public function ok()
+ {
+ return $this->statusCode >= 200 && $this->statusCode < 300 && $this->error === null;
+ }
+
+ public function needRetry()
+ {
+ $code = $this->statusCode;
+ if ($code < 0 || ($code / 100 === 5 and $code !== 579) || $code === 996) {
+ return true;
+ }
+ }
+
+ private static function isJson($headers)
+ {
+ return array_key_exists('Content-Type', $headers) &&
+ strpos($headers['Content-Type'], 'application/json') === 0;
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php b/vendor/qiniu/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php
new file mode 100755
index 0000000..1ac5bf7
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php
@@ -0,0 +1,282 @@
+<?php
+namespace Qiniu\Processing;
+
+use Qiniu;
+
+/**
+ * 主要涉及图片链接拼接
+ *
+ * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html
+ */
+final class ImageUrlBuilder
+{
+ /**
+ * mode合法范围值
+ *
+ * @var array
+ */
+ protected $modeArr = array(0, 1, 2, 3, 4, 5);
+
+ /**
+ * format合法值
+ *
+ * @var array
+ */
+ protected $formatArr = array('psd', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'bmp');
+
+ /**
+ * 水印图片位置合法值
+ *
+ * @var array
+ */
+ protected $gravityArr = array('NorthWest', 'North', 'NorthEast',
+ 'West', 'Center', 'East', 'SouthWest', 'South', 'SouthEast');
+
+ /**
+ * 缩略图链接拼接
+ *
+ * @param string $url 图片链接
+ * @param int $mode 缩略模式
+ * @param int $width 宽度
+ * @param int $height 长度
+ * @param string $format 输出类型
+ * @param int $quality 图片质量
+ * @param int $interlace 是否支持渐进显示
+ * @param int $ignoreError 忽略结果
+ * @return string
+ * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+ public function thumbnail(
+ $url,
+ $mode,
+ $width,
+ $height,
+ $format = null,
+ $interlace = null,
+ $quality = null,
+ $ignoreError = 1
+ ) {
+
+ // url合法效验
+ if (!$this->isUrl($url)) {
+ return $url;
+ }
+
+ // 参数合法性效验
+ if (!in_array(intval($mode), $this->modeArr, true)) {
+ return $url;
+ }
+
+ if (!$width || !$height) {
+ return $url;
+ }
+
+ $thumbStr = 'imageView2/' . $mode . '/w/' . $width . '/h/' . $height . '/';
+
+ // 拼接输出格式
+ if (!is_null($format)
+ && in_array($format, $this->formatArr)
+ ) {
+ $thumbStr .= 'format/' . $format . '/';
+ }
+
+ // 拼接渐进显示
+ if (!is_null($interlace)
+ && in_array(intval($interlace), array(0, 1), true)
+ ) {
+ $thumbStr .= 'interlace/' . $interlace . '/';
+ }
+
+ // 拼接图片质量
+ if (!is_null($quality)
+ && intval($quality) >= 0
+ && intval($quality) <= 100
+ ) {
+ $thumbStr .= 'q/' . $quality . '/';
+ }
+
+ $thumbStr .= 'ignore-error/' . $ignoreError . '/';
+
+ // 如果有query_string用|线分割实现多参数
+ return $url . ($this->hasQuery($url) ? '|' : '?') . $thumbStr;
+ }
+
+ /**
+ * 图片水印
+ *
+ * @param string $url 图片链接
+ * @param string $image 水印图片链接
+ * @param numeric $dissolve 透明度
+ * @param string $gravity 水印位置
+ * @param numeric $dx 横轴边距
+ * @param numeric $dy 纵轴边距
+ * @param numeric $watermarkScale 自适应原图的短边比例
+ * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html
+ * @return string
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+ public function waterImg(
+ $url,
+ $image,
+ $dissolve = 100,
+ $gravity = 'SouthEast',
+ $dx = null,
+ $dy = null,
+ $watermarkScale = null
+ ) {
+ // url合法效验
+ if (!$this->isUrl($url)) {
+ return $url;
+ }
+
+ $waterStr = 'watermark/1/image/' . \Qiniu\base64_urlSafeEncode($image) . '/';
+
+ // 拼接水印透明度
+ if (is_numeric($dissolve)
+ && $dissolve <= 100
+ ) {
+ $waterStr .= 'dissolve/' . $dissolve . '/';
+ }
+
+ // 拼接水印位置
+ if (in_array($gravity, $this->gravityArr, true)) {
+ $waterStr .= 'gravity/' . $gravity . '/';
+ }
+
+ // 拼接横轴边距
+ if (!is_null($dx)
+ && is_numeric($dx)
+ ) {
+ $waterStr .= 'dx/' . $dx . '/';
+ }
+
+ // 拼接纵轴边距
+ if (!is_null($dy)
+ && is_numeric($dy)
+ ) {
+ $waterStr .= 'dy/' . $dy . '/';
+ }
+
+ // 拼接自适应原图的短边比例
+ if (!is_null($watermarkScale)
+ && is_numeric($watermarkScale)
+ && $watermarkScale > 0
+ && $watermarkScale < 1
+ ) {
+ $waterStr .= 'ws/' . $watermarkScale . '/';
+ }
+
+ // 如果有query_string用|线分割实现多参数
+ return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr;
+ }
+
+ /**
+ * 文字水印
+ *
+ * @param string $url 图片链接
+ * @param string $text 文字
+ * @param string $font 文字字体
+ * @param string $fontSize 文字字号
+ * @param string $fontColor 文字颜色
+ * @param numeric $dissolve 透明度
+ * @param string $gravity 水印位置
+ * @param numeric $dx 横轴边距
+ * @param numeric $dy 纵轴边距
+ * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark
+ * @return string
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+ public function waterText(
+ $url,
+ $text,
+ $font = '黑体',
+ $fontSize = 0,
+ $fontColor = null,
+ $dissolve = 100,
+ $gravity = 'SouthEast',
+ $dx = null,
+ $dy = null
+ ) {
+ // url合法效验
+ if (!$this->isUrl($url)) {
+ return $url;
+ }
+
+ $waterStr = 'watermark/2/text/'
+ . \Qiniu\base64_urlSafeEncode($text) . '/font/'
+ . \Qiniu\base64_urlSafeEncode($font) . '/';
+
+ // 拼接文字大小
+ if (is_int($fontSize)) {
+ $waterStr .= 'fontsize/' . $fontSize . '/';
+ }
+
+ // 拼接文字颜色
+ if (!is_null($fontColor)
+ && $fontColor
+ ) {
+ $waterStr .= 'fill/' . \Qiniu\base64_urlSafeEncode($fontColor) . '/';
+ }
+
+ // 拼接水印透明度
+ if (is_numeric($dissolve)
+ && $dissolve <= 100
+ ) {
+ $waterStr .= 'dissolve/' . $dissolve . '/';
+ }
+
+ // 拼接水印位置
+ if (in_array($gravity, $this->gravityArr, true)) {
+ $waterStr .= 'gravity/' . $gravity . '/';
+ }
+
+ // 拼接横轴边距
+ if (!is_null($dx)
+ && is_numeric($dx)
+ ) {
+ $waterStr .= 'dx/' . $dx . '/';
+ }
+
+ // 拼接纵轴边距
+ if (!is_null($dy)
+ && is_numeric($dy)
+ ) {
+ $waterStr .= 'dy/' . $dy . '/';
+ }
+
+ // 如果有query_string用|线分割实现多参数
+ return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr;
+ }
+
+ /**
+ * 效验url合法性
+ *
+ * @param string $url url链接
+ * @return string
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+ protected function isUrl($url)
+ {
+ $urlArr = parse_url($url);
+
+ return $urlArr['scheme']
+ && in_array($urlArr['scheme'], array('http', 'https'))
+ && $urlArr['host']
+ && $urlArr['path'];
+ }
+
+ /**
+ * 检测是否有query
+ *
+ * @param string $url url链接
+ * @return string
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+ protected function hasQuery($url)
+ {
+ $urlArr = parse_url($url);
+
+ return !empty($urlArr['query']);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Processing/Operation.php b/vendor/qiniu/php-sdk/src/Qiniu/Processing/Operation.php
new file mode 100755
index 0000000..919136f
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Processing/Operation.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Qiniu\Processing;
+
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+
+final class Operation
+{
+
+ private $auth;
+ private $token_expire;
+ private $domain;
+
+ public function __construct($domain, $auth = null, $token_expire = 3600)
+ {
+ $this->auth = $auth;
+ $this->domain = $domain;
+ $this->token_expire = $token_expire;
+ }
+
+
+ /**
+ * 对资源文件进行处理
+ *
+ * @param $key 待处理的资源文件名
+ * @param $fops string|array fop操作,多次fop操作以array的形式传入。
+ * eg. imageView2/1/w/200/h/200, imageMogr2/thumbnail/!75px
+ *
+ * @return array 文件处理后的结果及错误。
+ *
+ * @link http://developer.qiniu.com/docs/v6/api/reference/fop/
+ */
+ public function execute($key, $fops)
+ {
+ $url = $this->buildUrl($key, $fops);
+ $resp = Client::get($url);
+ if (!$resp->ok()) {
+ return array(null, new Error($url, $resp));
+ }
+ if ($resp->json() !== null) {
+ return array($resp->json(), null);
+ }
+ return array($resp->body, null);
+ }
+
+ public function buildUrl($key, $fops, $protocol = 'http')
+ {
+ if (is_array($fops)) {
+ $fops = implode('|', $fops);
+ }
+
+ $url = $protocol . "://$this->domain/$key?$fops";
+ if ($this->auth !== null) {
+ $url = $this->auth->privateDownloadUrl($url, $this->token_expire);
+ }
+
+ return $url;
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Processing/PersistentFop.php b/vendor/qiniu/php-sdk/src/Qiniu/Processing/PersistentFop.php
new file mode 100755
index 0000000..24e7b73
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Processing/PersistentFop.php
@@ -0,0 +1,94 @@
+<?php
+namespace Qiniu\Processing;
+
+use Qiniu\Config;
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+use Qiniu\Processing\Operation;
+
+/**
+ * 持久化处理类,该类用于主动触发异步持久化操作.
+ *
+ * @link http://developer.qiniu.com/docs/v6/api/reference/fop/pfop/pfop.html
+ */
+final class PersistentFop
+{
+ /**
+ * @var 账号管理密钥对,Auth对象
+ */
+ private $auth;
+
+ /*
+ * @var 配置对象,Config 对象
+ * */
+ private $config;
+
+
+ public function __construct($auth, $config = null)
+ {
+ $this->auth = $auth;
+ if ($config == null) {
+ $this->config = new Config();
+ } else {
+ $this->config = $config;
+ }
+ }
+
+ /**
+ * 对资源文件进行异步持久化处理
+ * @param $bucket 资源所在空间
+ * @param $key 待处理的源文件
+ * @param $fops string|array 待处理的pfop操作,多个pfop操作以array的形式传入。
+ * eg. avthumb/mp3/ab/192k, vframe/jpg/offset/7/w/480/h/360
+ * @param $pipeline 资源处理队列
+ * @param $notify_url 处理结果通知地址
+ * @param $force 是否强制执行一次新的指令
+ *
+ *
+ * @return array 返回持久化处理的persistentId, 和返回的错误。
+ *
+ * @link http://developer.qiniu.com/docs/v6/api/reference/fop/
+ */
+ public function execute($bucket, $key, $fops, $pipeline = null, $notify_url = null, $force = false)
+ {
+ if (is_array($fops)) {
+ $fops = implode(';', $fops);
+ }
+ $params = array('bucket' => $bucket, 'key' => $key, 'fops' => $fops);
+ \Qiniu\setWithoutEmpty($params, 'pipeline', $pipeline);
+ \Qiniu\setWithoutEmpty($params, 'notifyURL', $notify_url);
+ if ($force) {
+ $params['force'] = 1;
+ }
+ $data = http_build_query($params);
+ $scheme = "http://";
+ if ($this->config->useHTTPS === true) {
+ $scheme = "https://";
+ }
+ $url = $scheme . Config::API_HOST . '/pfop/';
+ $headers = $this->auth->authorization($url, $data, 'application/x-www-form-urlencoded');
+ $headers['Content-Type'] = 'application/x-www-form-urlencoded';
+ $response = Client::post($url, $data, $headers);
+ if (!$response->ok()) {
+ return array(null, new Error($url, $response));
+ }
+ $r = $response->json();
+ $id = $r['persistentId'];
+ return array($id, null);
+ }
+
+ public function status($id)
+ {
+ $scheme = "http://";
+
+ if ($this->config->useHTTPS === true) {
+ $scheme = "https://";
+ }
+ $url = $scheme . Config::API_HOST . "/status/get/prefop?id=$id";
+ $response = Client::get($url);
+ if (!$response->ok()) {
+ return array(null, new Error($url, $response));
+ }
+ return array($response->json(), null);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Region.php b/vendor/qiniu/php-sdk/src/Qiniu/Region.php
new file mode 100755
index 0000000..eae21d1
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Region.php
@@ -0,0 +1,196 @@
+<?php
+namespace Qiniu;
+
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+
+class Region
+{
+
+ //源站上传域名
+ public $srcUpHosts;
+ //CDN加速上传域名
+ public $cdnUpHosts;
+ //资源管理域名
+ public $rsHost;
+ //资源列举域名
+ public $rsfHost;
+ //资源处理域名
+ public $apiHost;
+ //IOVIP域名
+ public $iovipHost;
+
+ //构造一个Region对象
+ public function __construct(
+ $srcUpHosts = array(),
+ $cdnUpHosts = array(),
+ $rsHost = "rs.qiniu.com",
+ $rsfHost = "rsf.qiniu.com",
+ $apiHost = "api.qiniu.com",
+ $iovipHost = null
+ ) {
+
+ $this->srcUpHosts = $srcUpHosts;
+ $this->cdnUpHosts = $cdnUpHosts;
+ $this->rsHost = $rsHost;
+ $this->rsfHost = $rsfHost;
+ $this->apiHost = $apiHost;
+ $this->iovipHost = $iovipHost;
+ }
+
+ //华东机房
+ public static function regionHuadong()
+ {
+ $regionHuadong = new Region(
+ array("up.qiniup.com", 'up-jjh.qiniup.com', 'up-xs.qiniup.com'),
+ array('upload.qiniup.com', 'upload-jjh.qiniup.com', 'upload-xs.qiniup.com'),
+ 'rs.qbox.me',
+ 'rsf.qbox.me',
+ 'api.qiniu.com',
+ 'iovip.qbox.me'
+ );
+ return $regionHuadong;
+ }
+
+ //华东机房内网上传
+ public static function qvmRegionHuadong()
+ {
+ $qvmRegionHuadong = new Region(
+ array("free-qvm-z0-xs.qiniup.com"),
+ 'rs.qbox.me',
+ 'rsf.qbox.me',
+ 'api.qiniu.com',
+ 'iovip.qbox.me'
+ );
+ return $qvmRegionHuadong;
+ }
+
+ //华北机房内网上传
+ public static function qvmRegionHuabei()
+ {
+ $qvmRegionHuabei = new Region(
+ array("free-qvm-z1-zz.qiniup.com"),
+ "rs-z1.qbox.me",
+ "rsf-z1.qbox.me",
+ "api-z1.qiniu.com",
+ "iovip-z1.qbox.me"
+ );
+ return $qvmRegionHuabei;
+ }
+
+ //华北机房
+ public static function regionHuabei()
+ {
+ $regionHuabei = new Region(
+ array('up-z1.qiniup.com'),
+ array('upload-z1.qiniup.com'),
+ "rs-z1.qbox.me",
+ "rsf-z1.qbox.me",
+ "api-z1.qiniu.com",
+ "iovip-z1.qbox.me"
+ );
+
+ return $regionHuabei;
+ }
+
+ //华南机房
+ public static function regionHuanan()
+ {
+ $regionHuanan = new Region(
+ array('up-z2.qiniup.com', 'up-dg.qiniup.com', 'up-fs.qiniup.com'),
+ array('upload-z2.qiniup.com', 'upload-dg.qiniup.com', 'upload-fs.qiniup.com'),
+ "rs-z2.qbox.me",
+ "rsf-z2.qbox.me",
+ "api-z2.qiniu.com",
+ "iovip-z2.qbox.me"
+ );
+ return $regionHuanan;
+ }
+
+ //北美机房
+ public static function regionNorthAmerica()
+ {
+ //北美机房
+ $regionNorthAmerica = new Region(
+ array('up-na0.qiniup.com'),
+ array('upload-na0.qiniup.com'),
+ "rs-na0.qbox.me",
+ "rsf-na0.qbox.me",
+ "api-na0.qiniu.com",
+ "iovip-na0.qbox.me"
+ );
+ return $regionNorthAmerica;
+ }
+
+ //新加坡机房
+ public static function regionSingapore()
+ {
+ //新加坡机房
+ $regionSingapore = new Region(
+ array('up-as0.qiniup.com'),
+ array('upload-as0.qiniup.com'),
+ "rs-as0.qbox.me",
+ "rsf-as0.qbox.me",
+ "api-as0.qiniu.com",
+ "iovip-as0.qbox.me"
+ );
+ return $regionSingapore;
+ }
+
+ /*
+ * GET /v2/query?ak=<ak>&&bucket=<bucket>
+ **/
+ public static function queryRegion($ak, $bucket)
+ {
+ $Region = new Region();
+ $url = Config::API_HOST . '/v2/query' . "?ak=$ak&bucket=$bucket";
+ $ret = Client::Get($url);
+ if (!$ret->ok()) {
+ return array(null, new Error($url, $ret));
+ }
+ $r = ($ret->body === null) ? array() : $ret->json();
+ //parse Region;
+
+ $iovipHost = $r['io']['src']['main'][0];
+ $Region->iovipHost = $iovipHost;
+ $accMain = $r['up']['acc']['main'][0];
+ array_push($Region->cdnUpHosts, $accMain);
+ if (isset($r['up']['acc']['backup'])) {
+ foreach ($r['up']['acc']['backup'] as $key => $value) {
+ array_push($Region->cdnUpHosts, $value);
+ }
+ }
+ $srcMain = $r['up']['src']['main'][0];
+ array_push($Region->srcUpHosts, $srcMain);
+ if (isset($r['up']['src']['backup'])) {
+ foreach ($r['up']['src']['backup'] as $key => $value) {
+ array_push($Region->srcUpHosts, $value);
+ }
+ }
+
+ //set specific hosts
+ if (strstr($Region->iovipHost, "z1") !== false) {
+ $Region->rsHost = "rs-z1.qbox.me";
+ $Region->rsfHost = "rsf-z1.qbox.me";
+ $Region->apiHost = "api-z1.qiniu.com";
+ } elseif (strstr($Region->iovipHost, "z2") !== false) {
+ $Region->rsHost = "rs-z2.qbox.me";
+ $Region->rsfHost = "rsf-z2.qbox.me";
+ $Region->apiHost = "api-z2.qiniu.com";
+ } elseif (strstr($Region->iovipHost, "na0") !== false) {
+ $Region->rsHost = "rs-na0.qbox.me";
+ $Region->rsfHost = "rsf-na0.qbox.me";
+ $Region->apiHost = "api-na0.qiniu.com";
+ } elseif (strstr($Region->iovipHost, "as0") !== false) {
+ $Region->rsHost = "rs-as0.qbox.me";
+ $Region->rsfHost = "rsf-as0.qbox.me";
+ $Region->apiHost = "api-as0.qiniu.com";
+ } else {
+ $Region->rsHost = "rs.qbox.me";
+ $Region->rsfHost = "rsf.qbox.me";
+ $Region->apiHost = "api.qiniu.com";
+ }
+
+ return $Region;
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Rtc/AppClient.php b/vendor/qiniu/php-sdk/src/Qiniu/Rtc/AppClient.php
new file mode 100755
index 0000000..0fd0019
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Rtc/AppClient.php
@@ -0,0 +1,209 @@
+<?php
+namespace Qiniu\Rtc;
+
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+use Qiniu\Config;
+use Qiniu\Auth;
+
+class AppClient
+{
+ private $auth;
+ private $baseURL;
+
+ public function __construct(Auth $auth)
+ {
+ $this->auth = $auth;
+
+ $this->baseURL = sprintf("%s/%s/apps", Config::RTCAPI_HOST, Config::RTCAPI_VERSION);
+ }
+
+ /*
+ * 创建应用
+ * hub: 直播空间名
+ * title: app 的名称 注意,Title 不是唯一标识,重复 create 动作将生成多个 app
+ * maxUsers:人数限制
+ * NoAutoKickUser: bool 类型,可选,禁止自动踢人(抢流)。默认为 false ,
+ 即同一个身份的 client (app/room/user) ,新的连麦请求可以成功,旧连接被关闭。
+ */
+ public function createApp($hub, $title, $maxUsers = null, $noAutoKickUser = null)
+ {
+ $params['hub'] = $hub;
+ $params['title'] = $title;
+ if (!empty($maxUsers)) {
+ $params['maxUsers'] = $maxUsers;
+ }
+ if (!empty($noAutoKickUser)) {
+ $params['noAutoKickUser'] = $noAutoKickUser;
+ }
+ $body = json_encode($params);
+ $ret = $this->post($this->baseURL, $body);
+ return $ret;
+ }
+
+ /*
+ * 更新应用
+ * appId: app 的唯一标识,创建的时候由系统生成。
+ * Title: app 的名称, 可选。
+ * Hub: 绑定的直播 hub,可选,用于合流后 rtmp 推流。
+ * MaxUsers: int 类型,可选,连麦房间支持的最大在线人数。
+ * NoAutoKickUser: bool 类型,可选,禁止自动踢人。
+ * MergePublishRtmp: 连麦合流转推 RTMP 的配置,可选择。其详细配置包括如下
+ Enable: 布尔类型,用于开启和关闭所有房间的合流功能。
+ AudioOnly: 布尔类型,可选,指定是否只合成音频。
+ Height, Width: int64,可选,指定合流输出的高和宽,默认为 640 x 480。
+ OutputFps: int64,可选,指定合流输出的帧率,默认为 25 fps 。
+ OutputKbps: int64,可选,指定合流输出的码率,默认为 1000 。
+ URL: 合流后转推旁路直播的地址,可选,支持魔法变量配置按照连麦房间号生成不同的推流地址。如果是转推到七牛直播云,不建议使用该配置。
+ StreamTitle: 转推七牛直播云的流名,可选,支持魔法变量配置按照连麦房间号生成不同的流名。例如,配置 Hub 为 qn-zhibo ,配置 StreamTitle 为 $(roomName) ,则房间 meeting-001 的合流将会被转推到 rtmp://pili-publish.qn-zhibo.***.com/qn-zhibo/meeting-001地址。详细配置细则,请咨询七牛技术支持。
+ */
+ public function updateApp($appId, $hub, $title, $maxUsers = null, $mergePublishRtmp = null, $noAutoKickUser = null)
+ {
+ $url = $this->baseURL . '/' . $appId;
+ $params['hub'] = $hub;
+ $params['title'] = $title;
+ if (!empty($maxUsers)) {
+ $params['maxUsers'] = $maxUsers;
+ }
+ if (!empty($noAutoKickUser)) {
+ $params['noAutoKickUser'] = $noAutoKickUser;
+ }
+ if (!empty($mergePublishRtmp)) {
+ $params['mergePublishRtmp'] = $mergePublishRtmp;
+ }
+ $body = json_encode($params);
+ $ret = $this->post($url, $body);
+ return $ret;
+ }
+
+ /*
+ * 获取应用信息
+ * appId: app 的唯一标识,创建的时候由系统生成。
+ */
+ public function getApp($appId)
+ {
+ $url = $this->baseURL . '/' . $appId;
+ $ret = $this->get($url);
+ return $ret;
+ }
+
+ /*
+ * 删除应用
+ * appId: app 的唯一标识,创建的时候由系统生成
+ */
+ public function deleteApp($appId)
+ {
+ $url = $this->baseURL . '/' . $appId;
+ list(, $err) = $this->delete($url);
+ return $err;
+ }
+
+ /*
+ * 获取房间内用户列表
+ * appId: app 的唯一标识,创建的时候由系统生成。
+ * roomName: 操作所查询的连麦房间。
+ */
+ public function listUser($appId, $roomName)
+ {
+ $url = sprintf("%s/%s/rooms/%s/users", $this->baseURL, $appId, $roomName);
+ $ret = $this->get($url);
+ return $ret;
+ }
+
+ /*
+ * 踢出用户
+ * appId: app 的唯一标识,创建的时候由系统生成。
+ * roomName: 连麦房间
+ * userId: 请求加入房间的用户ID
+ */
+ public function kickUser($appId, $roomName, $userId)
+ {
+ $url = sprintf("%s/%s/rooms/%s/users/%s", $this->baseURL, $appId, $roomName, $userId);
+ list(, $err) = $this->delete($url);
+ return $err;
+ }
+
+ /*
+ * 获取应用中活跃房间
+ * appId: app 的唯一标识,创建的时候由系统生成。
+ * prefix: 所查询房间名的前缀索引,可以为空。
+ * offset: int 类型,分页查询的位移标记。
+ * limit: int 类型,此次查询的最大长度。
+ * GET /v3/apps/<AppID>/rooms?prefix=<RoomNamePrefix>&offset=<Offset>&limit=<Limit>
+ */
+ public function listActiveRooms($appId, $prefix = null, $offset = null, $limit = null)
+ {
+ if (isset($prefix)) {
+ $query['prefix'] = $prefix;
+ }
+ if (isset($offset)) {
+ $query['offset'] = $offset;
+ }
+ if (isset($limit)) {
+ $query['limit'] = $limit;
+ }
+ if (isset($query) && !empty($query)) {
+ $query = '?' . http_build_query($query);
+ $url = sprintf("%s/%s/rooms%s", $this->baseURL, $appId, $query);
+ } else {
+ $url = sprintf("%s/%s/rooms", $this->baseURL, $appId);
+ }
+ $ret = $this->get($url);
+ return $ret;
+ }
+
+ /*
+ * 生成加入房间的令牌
+ * appId: app 的唯一标识,创建的时候由系统生成。
+ * roomName: 房间名称,需满足规格 ^[a-zA-Z0-9_-]{3,64}$
+ * userId: 请求加入房间的用户 ID,需满足规格 ^[a-zA-Z0-9_-]{3,50}$
+ * expireAt: int64 类型,鉴权的有效时间,传入以秒为单位的64位Unix
+ 绝对时间,token 将在该时间后失效。
+ * permission: 该用户的房间管理权限,"admin" 或 "user",默认为 "user" 。
+ 当权限角色为 "admin" 时,拥有将其他用户移除出房间等特权.
+ */
+ public function appToken($appId, $roomName, $userId, $expireAt, $permission)
+ {
+ $params['appId'] = $appId;
+ $params['userId'] = $userId;
+ $params['roomName'] = $roomName;
+ $params['permission'] = $permission;
+ $params['expireAt'] = $expireAt;
+ $appAccessString = json_encode($params);
+ return $this->auth->signWithData($appAccessString);
+ }
+
+ private function get($url, $cType = null)
+ {
+ $rtcToken = $this->auth->authorizationV2($url, "GET", null, $cType);
+ $rtcToken['Content-Type'] = $cType;
+ $ret = Client::get($url, $rtcToken);
+ if (!$ret->ok()) {
+ return array(null, new Error($url, $ret));
+ }
+ return array($ret->json(), null);
+ }
+
+ private function delete($url, $contentType = 'application/json')
+ {
+ $rtcToken = $this->auth->authorizationV2($url, "DELETE", null, $contentType);
+ $rtcToken['Content-Type'] = $contentType;
+ $ret = Client::delete($url, $rtcToken);
+ if (!$ret->ok()) {
+ return array(null, new Error($url, $ret));
+ }
+ return array($ret->json(), null);
+ }
+
+ private function post($url, $body, $contentType = 'application/json')
+ {
+ $rtcToken = $this->auth->authorizationV2($url, "POST", $body, $contentType);
+ $rtcToken['Content-Type'] = $contentType;
+ $ret = Client::post($url, $body, $rtcToken);
+ if (!$ret->ok()) {
+ return array(null, new Error($url, $ret));
+ }
+ $r = ($ret->body === null) ? array() : $ret->json();
+ return array($r, null);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Sms/Sms.php b/vendor/qiniu/php-sdk/src/Qiniu/Sms/Sms.php
new file mode 100755
index 0000000..592d6ba
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Sms/Sms.php
@@ -0,0 +1,337 @@
+<?php
+namespace Qiniu\Sms;
+
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+use Qiniu\Config;
+use Qiniu\Auth;
+
+class Sms
+{
+ private $auth;
+ private $baseURL;
+
+ public function __construct(Auth $auth)
+ {
+ $this->auth = $auth;
+
+ $this->baseURL = sprintf("%s/%s/", Config::SMS_HOST, Config::SMS_VERSION);
+ }
+
+ /*
+ * 创建签名
+ * signature: string 类型,必填,【长度限制8个字符内】超过长度会报错
+ * source: string 类型,必填,申请签名时必须指定签名来源。取值范围为:
+ nterprises_and_institutions 企事业单位的全称或简称
+ website 工信部备案网站的全称或简称
+ app APP应用的全称或简称
+ public_number_or_small_program 公众号或小程序的全称或简称
+ store_name 电商平台店铺名的全称或简称
+ trade_name 商标名的全称或简称,
+ * pics: 本地的图片路径 string 类型,可选
+ *@return: 类型array {
+ "signature_id": <signature_id>
+ }
+ */
+ public function createSignature(string $signature, string $source, string $pics = null)
+ {
+ $params['signature'] = $signature;
+ $params['source'] = $source;
+ if (!empty($pics)) {
+ $params['pics'] = $this->imgToBase64($pics);
+ }
+ $body = json_encode($params);
+ $url =$this->baseURL.'signature';
+ $ret = $this->post($url, $body);
+ return $ret;
+ }
+
+ /*
+ * 编辑签名
+ * id 签名id : string 类型,必填,
+ * signature: string 类型,必填,
+ * source: string 类型,必填,申请签名时必须指定签名来源。取值范围为:
+ enterprises_and_institutions 企事业单位的全称或简称
+ website 工信部备案网站的全称或简称
+ app APP应用的全称或简称
+ public_number_or_small_program 公众号或小程序的全称或简称
+ store_name 电商平台店铺名的全称或简称
+ trade_name 商标名的全称或简称,
+ * pics: 本地的图片路径 string 类型,可选,
+ * @return: 类型array {
+ "signature": string
+ }
+ */
+ public function updateSignature(string $id, string $signature, string $source, string $pics = null)
+ {
+ $params['signature'] = $signature;
+ $params['source'] = $source;
+ if (!empty($pics)) {
+ $params['pics'] = $this->imgToBase64($pics);
+ }
+ $body = json_encode($params);
+ $url =$this->baseURL.'signature/'.$id;
+ $ret = $this->PUT($url, $body);
+ return $ret;
+ }
+
+ /*
+ * 查询签名
+ * audit_status: 审核状态 string 类型,可选,
+ 取值范围为: "passed"(通过), "rejected"(未通过), "reviewing"(审核中)
+ * page:页码 int 类型,
+ * page_size: 分页大小 int 类型,可选, 默认为20
+ *@return: 类型array {
+ "items": [{
+ "id": string,
+ "signature": string,
+ "source": string,
+ "audit_status": string,
+ "reject_reason": string,
+ "created_at": int64,
+ "updated_at": int64
+ }...],
+ "total": int,
+ "page": int,
+ "page_size": int,
+ }
+ */
+ public function checkSignature(string $audit_status = null, int $page = 1, int $page_size = 20)
+ {
+
+ $url = sprintf(
+ "%s?audit_status=%s&page=%s&page_size=%s",
+ $this->baseURL.'signature',
+ $audit_status,
+ $page,
+ $page_size
+ );
+ $ret = $this->get($url);
+ return $ret;
+ }
+
+
+ /*
+ * 删除签名
+ * id 签名id string 类型,必填,
+ * @retrun : 请求成功 HTTP 状态码为 200
+ */
+ public function deleteSignature(string $id)
+ {
+ $url = $this->baseURL . 'signature/' . $id;
+ list(, $err) = $this->delete($url);
+ return $err;
+ }
+
+
+
+
+ /*
+ * 创建模板
+ * name : 模板名称 string 类型 ,必填
+ * template: 模板内容 string 类型,必填
+ * type: 模板类型 string 类型,必填,
+ 取值范围为: notification (通知类短信), verification (验证码短信), marketing (营销类短信)
+ * description: 申请理由简述 string 类型,必填
+ * signature_id: 已经审核通过的签名 string 类型,必填
+ * @return: 类型 array {
+ "template_id": string
+ }
+ */
+ public function createTemplate(
+ string $name,
+ string $template,
+ string $type,
+ string $description,
+ string $signture_id
+ ) {
+ $params['name'] = $name;
+ $params['template'] = $template;
+ $params['type'] = $type;
+ $params['description'] = $description;
+ $params['signature_id'] = $signture_id;
+
+ $body = json_encode($params);
+ $url =$this->baseURL.'template';
+ $ret = $this->post($url, $body);
+ return $ret;
+ }
+
+ /*
+ * 查询模板
+ * audit_status: 审核状态 string 类型 ,可选,
+ 取值范围为: passed (通过), rejected (未通过), reviewing (审核中)
+ * page: 页码 int 类型,可选,默认为 1
+ * page_size: 分页大小 int 类型,可选,默认为 20
+ * @return: 类型array{
+ "items": [{
+ "id": string,
+ "name": string,
+ "template": string,
+ "audit_status": string,
+ "reject_reason": string,
+ "type": string,
+ "signature_id": string, // 模版绑定的签名ID
+ "signature_text": string, // 模版绑定的签名内容
+ "created_at": int64,
+ "updated_at": int64
+ }...],
+ "total": int,
+ "page": int,
+ "page_size": int
+ }
+ */
+ public function queryTemplate(string $audit_status = null, int $page = 1, int $page_size = 20)
+ {
+
+ $url = sprintf(
+ "%s?audit_status=%s&page=%s&page_size=%s",
+ $this->baseURL.'template',
+ $audit_status,
+ $page,
+ $page_size
+ );
+ $ret = $this->get($url);
+ return $ret;
+ }
+
+ /*
+ * 编辑模板
+ * id :模板id
+ * name : 模板名称 string 类型 ,必填
+ * template: 模板内容 string 类型,必填
+ * description: 申请理由简述 string 类型,必填
+ * signature_id: 已经审核通过的签名 string 类型,必填
+ * @retrun : 请求成功 HTTP 状态码为 200
+ */
+ public function updateTemplate(
+ string $id,
+ string $name,
+ string $template,
+ string $description,
+ string $signature_id
+ ) {
+ $params['name'] = $name;
+ $params['template'] = $template;
+ $params['description'] = $description;
+ $params['signature_id'] = $signature_id;
+ $body = json_encode($params);
+ $url =$this->baseURL.'template/'.$id;
+ $ret = $this->PUT($url, $body);
+ return $ret;
+ }
+
+ /*
+ * 删除模板
+ * id :模板id string 类型,必填,
+ * @retrun : 请求成功 HTTP 状态码为 200
+ */
+ public function deleteTemplate(string $id)
+ {
+ $url = $this->baseURL . 'template/' . $id;
+ list(, $err) = $this->delete($url);
+ return $err;
+ }
+
+ /*
+ * 发送短信
+ * 编辑模板
+ * template_id :模板id string类型,必填
+ * mobiles : 手机号数组 []string 类型 ,必填
+ * parameters: 模板内容 map[string]string 类型,可选
+ * @return: 类型json {
+ "job_id": string
+ }
+ */
+ public function sendMessage(string $template_id, array $mobiles, array $parameters = null)
+ {
+ $params['template_id'] = $template_id;
+ $params['mobiles'] = $mobiles;
+ if (!empty($parameters)) {
+ $params['parameters'] = $parameters;
+ }
+ $body = json_encode($params);
+ $url =$this->baseURL.'message';
+ $ret = $this->post($url, $body);
+ return $ret;
+ }
+
+ public function imgToBase64(string $img_file)
+ {
+ $img_base64 = '';
+ if (file_exists($img_file)) {
+ $app_img_file = $img_file; // 图片路径
+ $img_info = getimagesize($app_img_file); // 取得图片的大小,类型等
+ $fp = fopen($app_img_file, "r"); // 图片是否可读权限
+ if ($fp) {
+ $filesize = filesize($app_img_file);
+ if ($filesize > 5*1024*1024) {
+ die("pic size < 5M !");
+ }
+ $content = fread($fp, $filesize);
+ $file_content = chunk_split(base64_encode($content)); // base64编码
+ switch ($img_info[2]) { //判读图片类型
+ case 1:
+ $img_type = 'gif';
+ break;
+ case 2:
+ $img_type = 'jpg';
+ break;
+ case 3:
+ $img_type = 'png';
+ break;
+ }
+ //合成图片的base64编码
+ $img_base64 = 'data:image/' . $img_type . ';base64,' . $file_content;
+ }
+ fclose($fp);
+ }
+
+ return $img_base64;
+ }
+
+ private function get($url, $cType = null)
+ {
+ $rtcToken = $this->auth->authorizationV2($url, "GET", null, $cType);
+ $rtcToken['Content-Type'] = $cType;
+ $ret = Client::get($url, $rtcToken);
+ if (!$ret->ok()) {
+ return array(null, new Error($url, $ret));
+ }
+ return array($ret->json(), null);
+ }
+
+ private function delete($url, $contentType = 'application/json')
+ {
+ $rtcToken = $this->auth->authorizationV2($url, "DELETE", null, $contentType);
+ $rtcToken['Content-Type'] = $contentType;
+ $ret = Client::delete($url, $rtcToken);
+ if (!$ret->ok()) {
+ return array(null, new Error($url, $ret));
+ }
+ return array($ret->json(), null);
+ }
+
+ private function post($url, $body, $contentType = 'application/json')
+ {
+ $rtcToken = $this->auth->authorizationV2($url, "POST", $body, $contentType);
+ $rtcToken['Content-Type'] = $contentType;
+ $ret = Client::post($url, $body, $rtcToken);
+ if (!$ret->ok()) {
+ return array(null, new Error($url, $ret));
+ }
+ $r = ($ret->body === null) ? array() : $ret->json();
+ return array($r, null);
+ }
+ private function PUT($url, $body, $contentType = 'application/json')
+ {
+ $rtcToken = $this->auth->authorizationV2($url, "PUT", $body, $contentType);
+ $rtcToken['Content-Type'] = $contentType;
+ $ret = Client::put($url, $body, $rtcToken);
+ if (!$ret->ok()) {
+ return array(null, new Error($url, $ret));
+ }
+ $r = ($ret->body === null) ? array() : $ret->json();
+ return array($r, null);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Storage/ArgusManager.php b/vendor/qiniu/php-sdk/src/Qiniu/Storage/ArgusManager.php
new file mode 100755
index 0000000..0889370
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Storage/ArgusManager.php
@@ -0,0 +1,73 @@
+<?php
+namespace Qiniu\Storage;
+
+use Qiniu\Auth;
+use Qiniu\Config;
+use Qiniu\Zone;
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+
+/**
+ * 主要涉及了鉴黄接口的实现,具体的接口规格可以参考
+ *
+ * @link https://developer.qiniu.com/dora/manual/3674/kodo-product-introduction
+ */
+final class ArgusManager
+{
+ private $auth;
+ private $config;
+
+ public function __construct(Auth $auth, Config $config = null)
+ {
+ $this->auth = $auth;
+ if ($config == null) {
+ $this->config = new Config();
+ } else {
+ $this->config = $config;
+ }
+ }
+
+ /**
+ * 视频鉴黄
+ *
+ * @param $body body信息
+ * @param $vid videoID
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ * @link https://developer.qiniu.com/dora/manual/4258/video-pulp
+ */
+ public function pulpVideo($body, $vid)
+ {
+ $path = '/v1/video/' . $vid;
+
+ return $this->arPost($path, $body);
+ }
+
+ private function getArHost()
+ {
+ $scheme = "http://";
+ if ($this->config->useHTTPS == true) {
+ $scheme = "https://";
+ }
+ return $scheme . Config::ARGUS_HOST;
+ }
+
+ private function arPost($path, $body = null)
+ {
+ $url = $this->getArHost() . $path;
+ return $this->post($url, $body);
+ }
+
+ private function post($url, $body)
+ {
+ $headers = $this->auth->authorizationV2($url, 'POST', $body, 'application/json');
+ $headers['Content-Type']='application/json';
+ $ret = Client::post($url, $body, $headers);
+ if (!$ret->ok()) {
+ print($ret->statusCode);
+ return array(null, new Error($url, $ret));
+ }
+ $r = ($ret->body === null) ? array() : $ret->json();
+ return array($r, null);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Storage/BucketManager.php b/vendor/qiniu/php-sdk/src/Qiniu/Storage/BucketManager.php
new file mode 100755
index 0000000..0a2413d
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Storage/BucketManager.php
@@ -0,0 +1,1107 @@
+<?php
+namespace Qiniu\Storage;
+
+use Qiniu\Auth;
+use Qiniu\Config;
+use Qiniu\Zone;
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+
+/**
+ * 主要涉及了空间资源管理及批量操作接口的实现,具体的接口规格可以参考
+ *
+ * @link https://developer.qiniu.com/kodo/api/1274/rs
+ */
+final class BucketManager
+{
+ private $auth;
+ private $config;
+
+ public function __construct(Auth $auth, Config $config = null)
+ {
+ $this->auth = $auth;
+ if ($config == null) {
+ $this->config = new Config();
+ } else {
+ $this->config = $config;
+ }
+ }
+
+ /**
+ * 获取指定账号下所有的空间名。
+ *
+ * @return string[] 包含所有空间名
+ */
+ public function buckets($shared = true)
+ {
+ $includeShared = "false";
+ if ($shared === true) {
+ $includeShared = "true";
+ }
+ return $this->rsGet('/buckets?shared=' . $includeShared);
+ }
+
+ /**
+ * 列举空间,返回bucket列表
+ * region 指定区域,global 指定全局空间。
+ * 在指定了 region 参数时,
+ * 如果指定 global 为 true,那么忽略 region 参数指定的区域,返回所有区域的全局空间。
+ * 如果没有指定 global 为 true,那么返回指定区域中非全局空间。
+ * 在没有指定 region 参数时(包括指定为空""),
+ * 如果指定 global 为 true,那么返回所有区域的全局空间。
+ * 如果没有指定 global 为 true,那么返回指定区域中所有的空间,包括全局空间。
+ * 在指定了line为 true 时,只返回 Line 空间;否则,只返回非 Line 空间。
+ * share 参数用于指定共享空间。
+ */
+
+ public function listbuckets(
+ $region = null,
+ $line = 'false',
+ $shared = 'false'
+ ) {
+ $path = '/v3/buckets?region=' . $region . '&line=' . $line . '&shared=' . $shared;
+ $info = $this->ucPost($path);
+ return $info;
+ }
+
+ /**
+ * 创建空间
+ *
+ * @param $name 创建的空间名
+ * @param $region 创建的区域,默认华东
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ */
+ public function createBucket($name, $region = 'z0')
+ {
+ $path = '/mkbucketv2/'.$name.'/region/' . $region;
+ return $this->rsPost($path, null);
+ }
+
+ /**
+ * 删除空间
+ *
+ * @param $name 删除的空间名
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ */
+ public function deleteBucket($name)
+ {
+ $path = '/drop/'.$name;
+ return $this->rsPost($path, null);
+ }
+
+ /**
+ * 获取指定空间绑定的所有的域名
+ *
+ * @return string[] 包含所有空间域名
+ */
+ public function domains($bucket)
+ {
+ return $this->apiGet('/v6/domain/list?tbl=' . $bucket);
+ }
+
+ /**
+ * 获取指定空间的相关信息
+ *
+ * @return string[] 包含空间信息
+ */
+ public function bucketInfo($bucket)
+ {
+ $path = '/v2/bucketInfo?bucket=' . $bucket;
+ $info = $this->ucPost($path);
+ return $info;
+ }
+
+ /**
+ * 获取指定zone的空间信息列表
+ * 在Region 未指定且Global 不为 true 时(包含未指定的情况,下同),返回用户的所有空间。
+ * 在指定了 region 参数且 global 不为 true 时,只列举非全局空间。
+ * shared 不指定shared参数或指定shared为rw或false时,返回包含具有读写权限空间,
+ * 指定shared为rd或true时,返回包含具有读权限空间。
+ * fs:如果为 true,会返回每个空间当前的文件数和存储量(实时数据)。
+ * @return string[] 包含空间信息
+ */
+ public function bucketInfos($region = null, $shared = 'false', $fs = 'false')
+ {
+ $path = '/v2/bucketInfos?region=' . $region . '&shared=' . $shared . '&fs=' . $fs;
+ $info = $this->ucPost($path);
+ return $info;
+ }
+
+ /**
+ * 获取空间绑定的域名列表
+ * @return string[] 包含空间绑定的所有域名
+ */
+
+ /**
+ * 列取空间的文件列表
+ *
+ * @param $bucket 空间名
+ * @param $prefix 列举前缀
+ * @param $marker 列举标识符
+ * @param $limit 单次列举个数限制
+ * @param $delimiter 指定目录分隔符
+ *
+ * @return array 包含文件信息的数组,类似:[
+* {
+* "hash" => "<Hash string>",
+* "key" => "<Key string>",
+* "fsize" => "<file size>",
+* "putTime" => "<file modify time>"
+* },
+* ...
+* ]
+ * @link http://developer.qiniu.com/docs/v6/api/reference/rs/list.html
+ */
+ public function listFiles(
+ $bucket,
+ $prefix = null,
+ $marker = null,
+ $limit = 1000,
+ $delimiter = null
+ ) {
+ $query = array('bucket' => $bucket);
+ \Qiniu\setWithoutEmpty($query, 'prefix', $prefix);
+ \Qiniu\setWithoutEmpty($query, 'marker', $marker);
+ \Qiniu\setWithoutEmpty($query, 'limit', $limit);
+ \Qiniu\setWithoutEmpty($query, 'delimiter', $delimiter);
+ $url = $this->getRsfHost() . '/list?' . http_build_query($query);
+ return $this->get($url);
+ }
+
+ /**
+ * 列取空间的文件列表
+ *
+ * @param $bucket 空间名
+ * @param $prefix 列举前缀
+ * @param $marker 列举标识符
+ * @param $limit 单次列举个数限制
+ * @param $delimiter 指定目录分隔符
+ * @param $skipconfirm 是否跳过已删除条目的确认机制
+ *
+ * @return array 包含文件信息的数组,类似:[
+* {
+* "hash" => "<Hash string>",
+* "key" => "<Key string>",
+* "fsize" => "<file size>",
+* "putTime" => "<file modify time>"
+* },
+* ...
+* ]
+ * @link http://developer.qiniu.com/docs/v6/api/reference/rs/list.html
+ */
+ public function listFilesv2(
+ $bucket,
+ $prefix = null,
+ $marker = null,
+ $limit = 1000,
+ $delimiter = null,
+ $skipconfirm = true
+ ) {
+ $query = array('bucket' => $bucket);
+ \Qiniu\setWithoutEmpty($query, 'prefix', $prefix);
+ \Qiniu\setWithoutEmpty($query, 'marker', $marker);
+ \Qiniu\setWithoutEmpty($query, 'limit', $limit);
+ \Qiniu\setWithoutEmpty($query, 'delimiter', $delimiter);
+ \Qiniu\setWithoutEmpty($query, 'skipconfirm', $skipconfirm);
+ $path = '/v2/list?' . http_build_query($query);
+ $url = $this->getRsfHost() . $path;
+ $headers = $this->auth->authorization($url, null, 'application/x-www-form-urlencoded');
+ $ret = Client::post($url, null, $headers);
+ if (!$ret->ok()) {
+ return array(null, new Error($url, $ret));
+ }
+ $r = explode("\n", $ret->body);
+ $pop = array_pop($r);
+ return array($r, null);
+ }
+
+ /**
+ * 设置Referer防盗链
+ *
+ * @param $bucket 空间名
+ * @param $mode 0: 表示关闭Referer(使用此选项将会忽略以下参数并将恢复默认值);
+ * 1: 表示设置Referer白名单; 2:表示设置Referer黑名单
+ * @param $norefer 0: 表示不允许空 Refer 访问; 1: 表示允许空 Refer 访问
+ * @param $pattern 规则字符串, 当前允许格式分为三种: 一种为空主机头域名,
+ * 比如 foo.com; 一种是泛域名,比如 *.bar.com; 一种是完全通配符,
+ * 即一个 *; 多个规则之间用;隔开, 比如: foo.com;*.bar.com;sub.foo.com;*.sub.bar.com
+ * @param $source_enabled 源站是否支持,默认为0只给CDN配置, 设置为1表示开启源站防盗链
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ */
+ // public function referAntiLeech(){
+
+ // }
+
+ /**
+ * 增加bucket生命规则
+ *
+ * @param $bucket 空间名
+ * @param $name 规则名称 bucket 内唯一,长度小于50,不能为空,只能为
+ * 字母、数字、下划线
+ * @param $prefix 同一个 bucket 里面前缀不能重复
+ * @param $delete_after_days 指定上传文件多少天后删除,指定为0表示不删除,
+ * 大于0表示多少天后删除,需大于 to_line_after_days
+ * @param $to_line_after_days 指定文件上传多少天后转低频存储。指定为0表示
+ * 不转低频存储,小于0表示上传的文件立即变低频存储
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ */
+ public function bucketLifecycleRule(
+ $bucket,
+ $name,
+ $prefix,
+ $delete_after_days,
+ $to_line_after_days
+ ) {
+ $path = '/rules/add';
+ if ($bucket) {
+ $params['bucket'] = $bucket;
+ }
+ if ($name) {
+ $params['name'] = $name;
+ }
+ if ($prefix) {
+ $params['prefix'] = $prefix;
+ }
+ if ($delete_after_days) {
+ $params['delete_after_days'] = $delete_after_days;
+ }
+ if ($to_line_after_days) {
+ $params['to_line_after_days'] = $to_line_after_days;
+ }
+ $data = http_build_query($params);
+ $info = $this->ucPost($path, $data);
+ return $info;
+ }
+
+ /**
+ * 更新bucket生命规则
+ *
+ * @param $bucket 空间名
+ * @param $name 规则名称 bucket 内唯一,长度小于50,不能为空,只能为字母、
+ * 数字、下划线
+ * @param $prefix 同一个 bucket 里面前缀不能重复
+ * @param $delete_after_days 指定上传文件多少天后删除,指定为0表示不删除,
+ * 大于0表示多少天后删除,需大于 to_line_after_days
+ * @param $to_line_after_days 指定文件上传多少天后转低频存储。指定为0表示不
+ * 转低频存储,小于0表示上传的文件立即变低频存储
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ */
+ public function updateBucketLifecycleRule(
+ $bucket,
+ $name,
+ $prefix,
+ $delete_after_days,
+ $to_line_after_days
+ ) {
+ $path = '/rules/update';
+ if ($bucket) {
+ $params['bucket'] = $bucket;
+ }
+ if ($name) {
+ $params['name'] = $name;
+ }
+ if ($prefix) {
+ $params['prefix'] = $prefix;
+ }
+ if ($delete_after_days) {
+ $params['delete_after_days'] = $delete_after_days;
+ }
+ if ($to_line_after_days) {
+ $params['to_line_after_days'] = $to_line_after_days;
+ }
+ $data = http_build_query($params);
+ $info = $this->ucPost($path, $data);
+ return $info;
+ }
+
+ /**
+ * 获取bucket生命规则
+ *
+ * @param $bucket 空间名
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ */
+ public function getBucketLifecycleRules($bucket)
+ {
+ $path = '/rules/get?bucket=' . $bucket;
+ $info = $this->ucGet($path);
+ return $info;
+ }
+
+ /**
+ * 删除bucket生命规则
+ *
+ * @param $bucket 空间名
+ * @param $name 规则名称 bucket 内唯一,长度小于50,不能为空,
+ * 只能为字母、数字、下划线()
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ */
+ public function deleteBucketLifecycleRule($bucket, $name)
+ {
+ $path = '/rules/delete';
+ if ($bucket) {
+ $params['bucket'] = $bucket;
+ }
+ if ($name) {
+ $params['name'] = $name;
+ }
+ $data = http_build_query($params);
+ $info = $this->ucPost($path, $data);
+ return $info;
+ }
+
+ /**
+ * 增加bucket事件通知规则
+ *
+ * @param $bucket 空间名
+ * @param $name 规则名称 bucket 内唯一,长度小于50,不能为空,
+ * 只能为字母、数字、下划线()
+ * @param $prefix 同一个 bucket 里面前缀不能重复
+ * @param $suffix 可选,文件配置的后缀
+ * @param $event 事件类型,可以指定多个,包括 put,mkfile,delete,copy,move,append,
+ * disable,enable,deleteMarkerCreate
+ * @param $callbackURL 通知URL,可以指定多个,失败依次重试
+ * @param $access_key 可选,设置的话会对通知请求用对应的ak、sk进行签名
+ * @param $host 可选,通知请求的host
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ */
+ public function putBucketEvent(
+ $bucket,
+ $name,
+ $prefix,
+ $suffix,
+ $event,
+ $callbackURL,
+ $access_key = null,
+ $host = null
+ ) {
+ $path = '/events/add';
+ if ($bucket) {
+ $params['bucket'] = $bucket;
+ }
+ if ($name) {
+ $params['name'] = $name;
+ }
+ if ($prefix) {
+ $params['prefix'] = $prefix;
+ }
+ if ($suffix) {
+ $params['suffix'] = $suffix;
+ }
+ if ($callbackURL) {
+ $params['callbackURL'] = $callbackURL;
+ }
+ if ($access_key) {
+ $params['access_key'] = $access_key;
+ }
+ if ($host) {
+ $params['host'] = $host;
+ }
+ $data = http_build_query($params);
+ if ($event) {
+ $eventpath = "";
+ foreach ($event as $key => $value) {
+ $eventpath .= "&event=$value";
+ }
+ $data .= $eventpath;
+ }
+ $info = $this->ucPost($path, $data);
+ return $info;
+ }
+
+ /**
+ * 更新bucket事件通知规则
+ *
+ * @param $bucket 空间名
+ * @param $name 规则名称 bucket 内唯一,长度小于50,不能为空,
+ * 只能为字母、数字、下划线()
+ * @param $prefix 同一个 bucket 里面前缀不能重复
+ * @param $suffix 可选,文件配置的后缀
+ * @param $event 事件类型,可以指定多个,包括 put,mkfile,delete,copy,move,append,disable,
+ * enable,deleteMarkerCreate
+ * @param $callbackURL 通知URL,可以指定多个,失败依次重试
+ * @param $access_key 可选,设置的话会对通知请求用对应的ak、sk进行签名
+ * @param $host 可选,通知请求的host
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ */
+ public function updateBucketEvent(
+ $bucket,
+ $name,
+ $prefix,
+ $suffix,
+ $event,
+ $callbackURL,
+ $access_key = null,
+ $host = null
+ ) {
+ $path = '/events/update';
+ if ($bucket) {
+ $params['bucket'] = $bucket;
+ }
+ if ($name) {
+ $params['name'] = $name;
+ }
+ if ($prefix) {
+ $params['prefix'] = $prefix;
+ }
+ if ($suffix) {
+ $params['suffix'] = $suffix;
+ }
+ if ($event) {
+ $params['event'] = $event;
+ }
+ if ($callbackURL) {
+ $params['callbackURL'] = $callbackURL;
+ }
+ if ($access_key) {
+ $params['access_key'] = $access_key;
+ }
+ if ($host) {
+ $params['host'] = $host;
+ }
+ $data = http_build_query($params);
+ $info = $this->ucPost($path, $data);
+ return $info;
+ }
+
+ /**
+ * 获取bucket事件通知规则
+ *
+ * @param $bucket 空间名
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ */
+ public function getBucketEvents($bucket)
+ {
+ $path = '/events/get?bucket=' . $bucket;
+ $info = $this->ucGet($path);
+ return $info;
+ }
+
+ /**
+ * 删除bucket事件通知规则
+ *
+ * @param $bucket 空间名
+ * @param $name 规则名称 bucket 内唯一,长度小于50,不能为空,
+ * 只能为字母、数字、下划线
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ */
+ public function deleteBucketEvent($bucket, $name)
+ {
+ $path = '/events/delete';
+ if ($bucket) {
+ $params['bucket'] = $bucket;
+ }
+ if ($name) {
+ $params['name'] = $name;
+ }
+ $data = http_build_query($params);
+ $info = $this->ucPost($path, $data);
+ return $info;
+ }
+
+ /**
+ * 设置bucket的跨域信息,最多允许设置10条跨域规则。
+ * 对于同一个域名如果设置了多条规则,那么按顺序使用第一条匹配的规则去生成返回值。
+ * 对于简单跨域请求,只匹配 Origin;
+ * allowed_orgin: 允许的域名。必填;支持通配符*;*表示全部匹配;只有第一个*生效;
+ * 需要设置"Scheme";大小写敏感。例如
+ * 规则:http://*.abc.*.com 请求:"http://test.abc.test.com" 结果:不通过
+ * 规则:"http://abc.com" 请求:"https://abc.com"/"abc.com" 结果:不通过
+ * 规则:"abc.com" 请求:"http://abc.com" 结果:不通过
+ * allowed_method: 允许的方法。必填;不支持通配符;大小写不敏感;
+ * allowed_header: 允许的header。选填;支持通配符*,
+ * 但只能是单独的*,表示允许全部header,其他*不生效;
+ * 空则不允许任何header;大小写不敏感;
+ * exposed_header: 暴露的header。选填;不支持通配符;
+ * X-Log, X-Reqid是默认会暴露的两个header;
+ * 其他的header如果没有设置,则不会暴露;大小写不敏感;
+ * max_age: 结果可以缓存的时间。选填;空则不缓存;
+ * allowed_credentials:该配置不支持设置,默认为true。
+ * 备注:如果没有设置任何corsRules,那么默认允许所有的跨域请求
+ */
+ // public function putCorsRules(string $bucket, array $params)
+ // {
+ // $path = '/corsRules/set/' . $bucket;
+ // $data = json_encode($params);
+ // $info = $this->ucPost($path, $data);
+ // return $info;
+ // }
+
+ /**
+ * 获取bucket的跨域信息
+ * $bucket 空间名
+ */
+ public function getCorsRules($bucket)
+ {
+ $path = '/corsRules/get/' . $bucket;
+ $info = $this->ucGet($path);
+ return $info;
+ }
+
+ /**
+ * 设置回源规则
+ * 使用该API设置源站优先级高于/image设置的源站,即IO优先读取source接口设置的源站配置,
+ * 如果存在会忽略/image设置的源站
+ * Bucket 空间名
+ * Host(可选)回源Host
+ * RetryCodes(可选),镜像回源时源站返回Code可以重试,最多指定3个,当前只支持4xx错误码重试
+ * SourceQiniuAK,SourceQiniuSK(可选)如果存在将在回源时对URL进行签名,客户源站可以验证
+ * 以保证请求来自Qiniu服务器
+ * Expires(可选) 签名过期时间,如果不设置默认为1小时
+ * Addr 回源地址,不可重复。
+ * Weight 权重,范围限制1-100,不填默认为1,回源时会根据所有源的权重值进行源站选择,
+ * 主备源会分开计算.
+ * Backup 是否备用回源,回源优先尝试主源
+ */
+ // public function putBucktSourceConfig(array $params)
+ // {
+ // $path = '/mirrorConfig/set';
+ // $data = json_encode($params);
+ // $info = $this->ucPostV2($path, $data);
+ // return $info;
+ // }
+
+ /**
+ * 获取空间回源配置
+ */
+ public function getBucktSourceConfig(array $params)
+ {
+ $path = '/mirrorConfig/get';
+ $data = json_encode($params);
+ $info = $this->ucPostV2($path, $data);
+ return $info;
+ }
+
+ /**
+ * 开关原图保护
+ * mode 为1表示开启原图保护,0表示关闭
+ */
+ public function putBucketAccessStyleMode($bucket, $mode)
+ {
+ $path = '/accessMode/' . $bucket . '/mode/' . $mode;
+ $info = $this->ucPost($path, null);
+ return $info;
+ }
+
+ /**
+ * 设置私有属性
+ * private为0表示公开,为1表示私有
+ */
+ public function putBucketAccessMode($bucket, $private)
+ {
+ $path = '/bucket/' . $bucket . '/private/' . $private;
+ $info = $this->ucPost($path, null);
+ return $info;
+ }
+
+ /**
+ * 设置referer防盗链
+ * bucket=<BucketName>: bucket 名
+ * mode=<AntiLeechMode>:
+ * 0: 表示关闭Referer(使用此选项将会忽略以下参数并将恢复默认值);
+ * 1: 表示设置Referer白名单; 2: 表示设置Referer黑名单
+ * norefer=<NoRefer>: 0: 表示不允许空 Refer 访问;
+ * 1: 表示允许空 Refer 访问
+ * pattern=<Pattern>: 规则字符串, 当前允许格式分为三种:
+ * 一种为空主机头域名, 比如 foo.com;
+ * 一种是泛域名, 比如 *.bar.com; 一种是完全通配符, 即一个 *;
+ * 多个规则之间用;隔开, 比如: foo.com;*.bar.com;sub.foo.com;*.sub.bar.com
+ * 空主机头域名可以是多级域名,比如 foo.bar.com。
+ * 多个域名之间不允许夹带空白字符。
+ * source_enabled=:1
+ */
+ public function putReferAntiLeech($bucket, $mode, $norefer, $pattern, $enabled = 1)
+ {
+ $path = "/referAntiLeech?bucket=$bucket&mode=$mode&norefer=$norefer&pattern=$pattern&source_enabled=$enabled";
+ $info = $this->ucPost($path, null);
+ return $info;
+ }
+
+ /**
+ * 设置Bucket的maxAge
+ * maxAge为0或者负数表示为默认值(31536000)
+ */
+ public function putBucketMaxAge($bucket, $maxAge)
+ {
+ $path = '/maxAge?bucket=' . $bucket . '&maxAge=' . $maxAge;
+ $info = $this->ucPost($path, null);
+ return $info;
+ }
+
+ /**
+ * 设置配额
+ * <bucket>: 空间名称,不支持授权空间
+ * <size>: 空间存储量配额,参数传入0或不传表示不更改当前配置,传入-1表示取消限额,
+ * 新创建的空间默认没有限额。
+ * <count>: 空间文件数配额,参数含义同<size>
+ */
+ public function putBucketQuota($bucket, $size, $count)
+ {
+ $path = '/setbucketquota/' . $bucket . '/size/' . $size . '/count/' . $count;
+ $info = $this->apiPost($path, null);
+ return $info;
+ }
+
+ /**
+ * 获取配额
+ * bucket 空间名称
+ */
+ public function getBucketQuota($bucket)
+ {
+ $path = '/getbucketquota/' . $bucket;
+ $info = $this->apiPost($path, null);
+ return $info;
+ }
+
+ /**
+ * 获取资源的元信息,但不返回文件内容
+ *
+ * @param $bucket 待获取信息资源所在的空间
+ * @param $key 待获取资源的文件名
+ *
+ * @return array 包含文件信息的数组,类似:
+* [
+* "hash" => "<Hash string>",
+* "key" => "<Key string>",
+* "fsize" => <file size>,
+* "putTime" => "<file modify time>"
+* "fileType" => <file type>
+* ]
+ *
+ * @link http://developer.qiniu.com/docs/v6/api/reference/rs/stat.html
+ */
+ public function stat($bucket, $key)
+ {
+ $path = '/stat/' . \Qiniu\entry($bucket, $key);
+ return $this->rsGet($path);
+ }
+
+ /**
+ * 删除指定资源
+ *
+ * @param $bucket 待删除资源所在的空间
+ * @param $key 待删除资源的文件名
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ * @link http://developer.qiniu.com/docs/v6/api/reference/rs/delete.html
+ */
+ public function delete($bucket, $key)
+ {
+ $path = '/delete/' . \Qiniu\entry($bucket, $key);
+ list(, $error) = $this->rsPost($path);
+ return $error;
+ }
+
+
+ /**
+ * 给资源进行重命名,本质为move操作。
+ *
+ * @param $bucket 待操作资源所在空间
+ * @param $oldname 待操作资源文件名
+ * @param $newname 目标资源文件名
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ */
+ public function rename($bucket, $oldname, $newname)
+ {
+ return $this->move($bucket, $oldname, $bucket, $newname);
+ }
+
+ /**
+ * 对资源进行复制。
+ *
+ * @param $from_bucket 待操作资源所在空间
+ * @param $from_key 待操作资源文件名
+ * @param $to_bucket 目标资源空间名
+ * @param $to_key 目标资源文件名
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ * @link http://developer.qiniu.com/docs/v6/api/reference/rs/copy.html
+ */
+ public function copy($from_bucket, $from_key, $to_bucket, $to_key, $force = false)
+ {
+ $from = \Qiniu\entry($from_bucket, $from_key);
+ $to = \Qiniu\entry($to_bucket, $to_key);
+ $path = '/copy/' . $from . '/' . $to;
+ if ($force === true) {
+ $path .= '/force/true';
+ }
+ list(, $error) = $this->rsPost($path);
+ return $error;
+ }
+
+ /**
+ * 将资源从一个空间到另一个空间
+ *
+ * @param $from_bucket 待操作资源所在空间
+ * @param $from_key 待操作资源文件名
+ * @param $to_bucket 目标资源空间名
+ * @param $to_key 目标资源文件名
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ * @link http://developer.qiniu.com/docs/v6/api/reference/rs/move.html
+ */
+ public function move($from_bucket, $from_key, $to_bucket, $to_key, $force = false)
+ {
+ $from = \Qiniu\entry($from_bucket, $from_key);
+ $to = \Qiniu\entry($to_bucket, $to_key);
+ $path = '/move/' . $from . '/' . $to;
+ if ($force) {
+ $path .= '/force/true';
+ }
+ list(, $error) = $this->rsPost($path);
+ return $error;
+ }
+
+ /**
+ * 主动修改指定资源的文件元信息
+ *
+ * @param $bucket 待操作资源所在空间
+ * @param $key 待操作资源文件名
+ * @param $mime 待操作文件目标mimeType
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ * @link http://developer.qiniu.com/docs/v6/api/reference/rs/chgm.html
+ */
+ public function changeMime($bucket, $key, $mime)
+ {
+ $resource = \Qiniu\entry($bucket, $key);
+ $encode_mime = \Qiniu\base64_urlSafeEncode($mime);
+ $path = '/chgm/' . $resource . '/mime/' . $encode_mime;
+ list(, $error) = $this->rsPost($path);
+ return $error;
+ }
+
+
+ /**
+ * 修改指定资源的存储类型
+ *
+ * @param $bucket 待操作资源所在空间
+ * @param $key 待操作资源文件名
+ * @param $fileType 待操作文件目标文件类型
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ * @link https://developer.qiniu.com/kodo/api/3710/modify-the-file-type
+ */
+ public function changeType($bucket, $key, $fileType)
+ {
+ $resource = \Qiniu\entry($bucket, $key);
+ $path = '/chtype/' . $resource . '/type/' . $fileType;
+ list(, $error) = $this->rsPost($path);
+ return $error;
+ }
+
+ /**
+ * 修改文件的存储状态,即禁用状态和启用状态间的的互相转换
+ *
+ * @param $bucket 待操作资源所在空间
+ * @param $key 待操作资源文件名
+ * @param $status 待操作文件目标文件类型
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ * @link https://developer.qiniu.com/kodo/api/4173/modify-the-file-status
+ */
+ public function changeStatus($bucket, $key, $status)
+ {
+ $resource = \Qiniu\entry($bucket, $key);
+ $path = '/chstatus/' . $resource . '/status/' . $status;
+ list(, $error) = $this->rsPost($path);
+ return $error;
+ }
+
+ /**
+ * 从指定URL抓取资源,并将该资源存储到指定空间中
+ *
+ * @param $url 指定的URL
+ * @param $bucket 目标资源空间
+ * @param $key 目标资源文件名
+ *
+ * @return array 包含已拉取的文件信息。
+ * 成功时: [
+ * [
+ * "hash" => "<Hash string>",
+ * "key" => "<Key string>"
+ * ],
+ * null
+ * ]
+ *
+ * 失败时: [
+ * null,
+ * Qiniu/Http/Error
+ * ]
+ * @link http://developer.qiniu.com/docs/v6/api/reference/rs/fetch.html
+ */
+ public function fetch($url, $bucket, $key = null)
+ {
+
+ $resource = \Qiniu\base64_urlSafeEncode($url);
+ $to = \Qiniu\entry($bucket, $key);
+ $path = '/fetch/' . $resource . '/to/' . $to;
+
+ $ak = $this->auth->getAccessKey();
+ $ioHost = $this->config->getIovipHost($ak, $bucket);
+
+ $url = $ioHost . $path;
+ return $this->post($url, null);
+ }
+
+ /**
+ * 从镜像源站抓取资源到空间中,如果空间中已经存在,则覆盖该资源
+ *
+ * @param $bucket 待获取资源所在的空间
+ * @param $key 代获取资源文件名
+ *
+ * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error
+ * @link http://developer.qiniu.com/docs/v6/api/reference/rs/prefetch.html
+ */
+ public function prefetch($bucket, $key)
+ {
+ $resource = \Qiniu\entry($bucket, $key);
+ $path = '/prefetch/' . $resource;
+
+ $ak = $this->auth->getAccessKey();
+ $ioHost = $this->config->getIovipHost($ak, $bucket);
+
+ $url = $ioHost . $path;
+ list(, $error) = $this->post($url, null);
+ return $error;
+ }
+
+ /**
+ * 在单次请求中进行多个资源管理操作
+ *
+ * @param $operations 资源管理操作数组
+ *
+ * @return array 每个资源的处理情况,结果类似:
+ * [
+ * { "code" => <HttpCode int>, "data" => <Data> },
+ * { "code" => <HttpCode int> },
+ * { "code" => <HttpCode int> },
+ * { "code" => <HttpCode int> },
+ * { "code" => <HttpCode int>, "data" => { "error": "<ErrorMessage string>" } },
+ * ...
+ * ]
+ * @link http://developer.qiniu.com/docs/v6/api/reference/rs/batch.html
+ */
+ public function batch($operations)
+ {
+ $params = 'op=' . implode('&op=', $operations);
+ return $this->rsPost('/batch', $params);
+ }
+
+ /**
+ * 设置文件的生命周期
+ *
+ * @param $bucket 设置文件生命周期文件所在的空间
+ * @param $key 设置文件生命周期文件的文件名
+ * @param $days 设置该文件多少天后删除,当$days设置为0时表示取消该文件的生命周期
+ *
+ * @return Mixed
+ * @link https://developer.qiniu.com/kodo/api/update-file-lifecycle
+ */
+ public function deleteAfterDays($bucket, $key, $days)
+ {
+ $entry = \Qiniu\entry($bucket, $key);
+ $path = "/deleteAfterDays/$entry/$days";
+ list(, $error) = $this->rsPost($path);
+ return $error;
+ }
+
+ private function getRsfHost()
+ {
+ $scheme = "http://";
+ if ($this->config->useHTTPS == true) {
+ $scheme = "https://";
+ }
+ return $scheme . Config::RSF_HOST;
+ }
+
+ private function getRsHost()
+ {
+ $scheme = "http://";
+ if ($this->config->useHTTPS == true) {
+ $scheme = "https://";
+ }
+ return $scheme . Config::RS_HOST;
+ }
+
+ private function getApiHost()
+ {
+ $scheme = "http://";
+ if ($this->config->useHTTPS == true) {
+ $scheme = "https://";
+ }
+ return $scheme . Config::API_HOST;
+ }
+
+ private function getUcHost()
+ {
+ $scheme = "http://";
+ if ($this->config->useHTTPS == true) {
+ $scheme = "https://";
+ }
+ return $scheme . Config::UC_HOST;
+ }
+
+ private function rsPost($path, $body = null)
+ {
+ $url = $this->getRsHost() . $path;
+ return $this->post($url, $body);
+ }
+
+ private function apiPost($path, $body = null)
+ {
+ $url = $this->getApiHost() . $path;
+ return $this->post($url, $body);
+ }
+
+ private function ucPost($path, $body = null)
+ {
+ $url = $this->getUcHost() . $path;
+ return $this->post($url, $body);
+ }
+
+ private function ucGet($path)
+ {
+ $url = $this->getUcHost() . $path;
+ return $this->get($url);
+ }
+
+ private function apiGet($path)
+ {
+ $url = $this->getApiHost() . $path;
+ return $this->get($url);
+ }
+
+ private function rsGet($path)
+ {
+ $url = $this->getRsHost() . $path;
+ return $this->get($url);
+ }
+
+ private function get($url)
+ {
+ $headers = $this->auth->authorization($url);
+ $ret = Client::get($url, $headers);
+ if (!$ret->ok()) {
+ return array(null, new Error($url, $ret));
+ }
+ return array($ret->json(), null);
+ }
+
+ private function post($url, $body)
+ {
+ $headers = $this->auth->authorization($url, $body, 'application/x-www-form-urlencoded');
+ $ret = Client::post($url, $body, $headers);
+ if (!$ret->ok()) {
+ return array(null, new Error($url, $ret));
+ }
+ $r = ($ret->body === null) ? array() : $ret->json();
+ return array($r, null);
+ }
+
+ private function ucPostV2($path, $body)
+ {
+ $url = $this->getUcHost() . $path;
+ return $this->postV2($url, $body);
+ }
+
+ private function postV2($url, $body)
+ {
+ $headers = $this->auth->authorizationV2($url, 'POST', $body, 'application/json');
+ $headers["Content-Type"] = 'application/json';
+ $ret = Client::post($url, $body, $headers);
+ if (!$ret->ok()) {
+ return array(null, new Error($url, $ret));
+ }
+ $r = ($ret->body === null) ? array() : $ret->json();
+ return array($r, null);
+ }
+
+ public static function buildBatchCopy($source_bucket, $key_pairs, $target_bucket, $force)
+ {
+ return self::twoKeyBatch('/copy', $source_bucket, $key_pairs, $target_bucket, $force);
+ }
+
+
+ public static function buildBatchRename($bucket, $key_pairs, $force)
+ {
+ return self::buildBatchMove($bucket, $key_pairs, $bucket, $force);
+ }
+
+
+ public static function buildBatchMove($source_bucket, $key_pairs, $target_bucket, $force)
+ {
+ return self::twoKeyBatch('/move', $source_bucket, $key_pairs, $target_bucket, $force);
+ }
+
+
+ public static function buildBatchDelete($bucket, $keys)
+ {
+ return self::oneKeyBatch('/delete', $bucket, $keys);
+ }
+
+
+ public static function buildBatchStat($bucket, $keys)
+ {
+ return self::oneKeyBatch('/stat', $bucket, $keys);
+ }
+
+ public static function buildBatchDeleteAfterDays($bucket, $key_day_pairs)
+ {
+ $data = array();
+ foreach ($key_day_pairs as $key => $day) {
+ array_push($data, '/deleteAfterDays/' . \Qiniu\entry($bucket, $key) . '/' . $day);
+ }
+ return $data;
+ }
+
+ public static function buildBatchChangeMime($bucket, $key_mime_pairs)
+ {
+ $data = array();
+ foreach ($key_mime_pairs as $key => $mime) {
+ array_push($data, '/chgm/' . \Qiniu\entry($bucket, $key) . '/mime/' . base64_encode($mime));
+ }
+ return $data;
+ }
+
+ public static function buildBatchChangeType($bucket, $key_type_pairs)
+ {
+ $data = array();
+ foreach ($key_type_pairs as $key => $type) {
+ array_push($data, '/chtype/' . \Qiniu\entry($bucket, $key) . '/type/' . $type);
+ }
+ return $data;
+ }
+
+ private static function oneKeyBatch($operation, $bucket, $keys)
+ {
+ $data = array();
+ foreach ($keys as $key) {
+ array_push($data, $operation . '/' . \Qiniu\entry($bucket, $key));
+ }
+ return $data;
+ }
+
+ private static function twoKeyBatch($operation, $source_bucket, $key_pairs, $target_bucket, $force)
+ {
+ if ($target_bucket === null) {
+ $target_bucket = $source_bucket;
+ }
+ $data = array();
+ $forceOp = "false";
+ if ($force) {
+ $forceOp = "true";
+ }
+ foreach ($key_pairs as $from_key => $to_key) {
+ $from = \Qiniu\entry($source_bucket, $from_key);
+ $to = \Qiniu\entry($target_bucket, $to_key);
+ array_push($data, $operation . '/' . $from . '/' . $to . "/force/" . $forceOp);
+ }
+ return $data;
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Storage/FormUploader.php b/vendor/qiniu/php-sdk/src/Qiniu/Storage/FormUploader.php
new file mode 100755
index 0000000..d87756a
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Storage/FormUploader.php
@@ -0,0 +1,137 @@
+<?php
+namespace Qiniu\Storage;
+
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+
+final class FormUploader
+{
+
+ /**
+ * 上传二进制流到七牛, 内部使用
+ *
+ * @param $upToken 上传凭证
+ * @param $key 上传文件名
+ * @param $data 上传二进制流
+ * @param $config 上传配置
+ * @param $params 自定义变量,规格参考
+ * http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
+ * @param $mime 上传数据的mimeType
+ *
+ * @return array 包含已上传文件的信息,类似:
+ * [
+ * "hash" => "<Hash string>",
+ * "key" => "<Key string>"
+ * ]
+ */
+ public static function put(
+ $upToken,
+ $key,
+ $data,
+ $config,
+ $params,
+ $mime,
+ $fname
+ ) {
+ $fields = array('token' => $upToken);
+ if ($key === null) {
+ } else {
+ $fields['key'] = $key;
+ }
+
+ //enable crc32 check by default
+ $fields['crc32'] = \Qiniu\crc32_data($data);
+
+ if ($params) {
+ foreach ($params as $k => $v) {
+ $fields[$k] = $v;
+ }
+ }
+
+ list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken);
+ if ($err != null) {
+ return array(null, $err);
+ }
+
+ $upHost = $config->getUpHost($accessKey, $bucket);
+
+ $response = Client::multipartPost($upHost, $fields, 'file', $fname, $data, $mime);
+ if (!$response->ok()) {
+ return array(null, new Error($upHost, $response));
+ }
+ return array($response->json(), null);
+ }
+
+ /**
+ * 上传文件到七牛,内部使用
+ *
+ * @param $upToken 上传凭证
+ * @param $key 上传文件名
+ * @param $filePath 上传文件的路径
+ * @param $config 上传配置
+ * @param $params 自定义变量,规格参考
+ * http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
+ * @param $mime 上传数据的mimeType
+ *
+ * @return array 包含已上传文件的信息,类似:
+ * [
+ * "hash" => "<Hash string>",
+ * "key" => "<Key string>"
+ * ]
+ */
+ public static function putFile(
+ $upToken,
+ $key,
+ $filePath,
+ $config,
+ $params,
+ $mime
+ ) {
+
+
+ $fields = array('token' => $upToken, 'file' => self::createFile($filePath, $mime));
+ if ($key !== null) {
+ $fields['key'] = $key;
+ }
+
+ $fields['crc32'] = \Qiniu\crc32_file($filePath);
+
+ if ($params) {
+ foreach ($params as $k => $v) {
+ $fields[$k] = $v;
+ }
+ }
+ $fields['key'] = $key;
+ $headers = array('Content-Type' => 'multipart/form-data');
+
+ list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken);
+ if ($err != null) {
+ return array(null, $err);
+ }
+
+ $upHost = $config->getUpHost($accessKey, $bucket);
+
+ $response = Client::post($upHost, $fields, $headers);
+ if (!$response->ok()) {
+ return array(null, new Error($upHost, $response));
+ }
+ return array($response->json(), null);
+ }
+
+ private static function createFile($filename, $mime)
+ {
+ // PHP 5.5 introduced a CurlFile object that deprecates the old @filename syntax
+ // See: https://wiki.php.net/rfc/curl-file-upload
+ if (function_exists('curl_file_create')) {
+ return curl_file_create($filename, $mime);
+ }
+
+ // Use the old style if using an older version of PHP
+ $value = "@{$filename}";
+ if (!empty($mime)) {
+ $value .= ';type=' . $mime;
+ }
+
+ return $value;
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Storage/ResumeUploader.php b/vendor/qiniu/php-sdk/src/Qiniu/Storage/ResumeUploader.php
new file mode 100755
index 0000000..5d8bf87
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Storage/ResumeUploader.php
@@ -0,0 +1,169 @@
+<?php
+namespace Qiniu\Storage;
+
+use Qiniu\Config;
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+
+/**
+ * 断点续上传类, 该类主要实现了断点续上传中的分块上传,
+ * 以及相应地创建块和创建文件过程.
+ *
+ * @link http://developer.qiniu.com/docs/v6/api/reference/up/mkblk.html
+ * @link http://developer.qiniu.com/docs/v6/api/reference/up/mkfile.html
+ */
+final class ResumeUploader
+{
+ private $upToken;
+ private $key;
+ private $inputStream;
+ private $size;
+ private $params;
+ private $mime;
+ private $contexts;
+ private $host;
+ private $currentUrl;
+ private $config;
+
+ /**
+ * 上传二进制流到七牛
+ *
+ * @param $upToken 上传凭证
+ * @param $key 上传文件名
+ * @param $inputStream 上传二进制流
+ * @param $size 上传流的大小
+ * @param $params 自定义变量
+ * @param $mime 上传数据的mimeType
+ *
+ * @link http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
+ */
+ public function __construct(
+ $upToken,
+ $key,
+ $inputStream,
+ $size,
+ $params,
+ $mime,
+ $config
+ ) {
+
+ $this->upToken = $upToken;
+ $this->key = $key;
+ $this->inputStream = $inputStream;
+ $this->size = $size;
+ $this->params = $params;
+ $this->mime = $mime;
+ $this->contexts = array();
+ $this->config = $config;
+
+ list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken);
+ if ($err != null) {
+ return array(null, $err);
+ }
+
+ $upHost = $config->getUpHost($accessKey, $bucket);
+ if ($err != null) {
+ throw new \Exception($err->message(), 1);
+ }
+ $this->host = $upHost;
+ }
+
+ /**
+ * 上传操作
+ */
+ public function upload($fname)
+ {
+ $uploaded = 0;
+ while ($uploaded < $this->size) {
+ $blockSize = $this->blockSize($uploaded);
+ $data = fread($this->inputStream, $blockSize);
+ if ($data === false) {
+ throw new \Exception("file read failed", 1);
+ }
+ $crc = \Qiniu\crc32_data($data);
+ $response = $this->makeBlock($data, $blockSize);
+ $ret = null;
+ if ($response->ok() && $response->json() != null) {
+ $ret = $response->json();
+ }
+ if ($response->statusCode < 0) {
+ list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($this->upToken);
+ if ($err != null) {
+ return array(null, $err);
+ }
+
+ $upHostBackup = $this->config->getUpBackupHost($accessKey, $bucket);
+ $this->host = $upHostBackup;
+ }
+ if ($response->needRetry() || !isset($ret['crc32']) || $crc != $ret['crc32']) {
+ $response = $this->makeBlock($data, $blockSize);
+ $ret = $response->json();
+ }
+
+ if (!$response->ok() || !isset($ret['crc32']) || $crc != $ret['crc32']) {
+ return array(null, new Error($this->currentUrl, $response));
+ }
+ array_push($this->contexts, $ret['ctx']);
+ $uploaded += $blockSize;
+ }
+ return $this->makeFile($fname);
+ }
+
+ /**
+ * 创建块
+ */
+ private function makeBlock($block, $blockSize)
+ {
+ $url = $this->host . '/mkblk/' . $blockSize;
+ return $this->post($url, $block);
+ }
+
+ private function fileUrl($fname)
+ {
+ $url = $this->host . '/mkfile/' . $this->size;
+ $url .= '/mimeType/' . \Qiniu\base64_urlSafeEncode($this->mime);
+ if ($this->key != null) {
+ $url .= '/key/' . \Qiniu\base64_urlSafeEncode($this->key);
+ }
+ $url .= '/fname/' . \Qiniu\base64_urlSafeEncode($fname);
+ if (!empty($this->params)) {
+ foreach ($this->params as $key => $value) {
+ $val = \Qiniu\base64_urlSafeEncode($value);
+ $url .= "/$key/$val";
+ }
+ }
+ return $url;
+ }
+
+ /**
+ * 创建文件
+ */
+ private function makeFile($fname)
+ {
+ $url = $this->fileUrl($fname);
+ $body = implode(',', $this->contexts);
+ $response = $this->post($url, $body);
+ if ($response->needRetry()) {
+ $response = $this->post($url, $body);
+ }
+ if (!$response->ok()) {
+ return array(null, new Error($this->currentUrl, $response));
+ }
+ return array($response->json(), null);
+ }
+
+ private function post($url, $data)
+ {
+ $this->currentUrl = $url;
+ $headers = array('Authorization' => 'UpToken ' . $this->upToken);
+ return Client::post($url, $data, $headers);
+ }
+
+ private function blockSize($uploaded)
+ {
+ if ($this->size < $uploaded + Config::BLOCK_SIZE) {
+ return $this->size - $uploaded;
+ }
+ return Config::BLOCK_SIZE;
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Storage/UploadManager.php b/vendor/qiniu/php-sdk/src/Qiniu/Storage/UploadManager.php
new file mode 100755
index 0000000..209df11
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Storage/UploadManager.php
@@ -0,0 +1,144 @@
+<?php
+namespace Qiniu\Storage;
+
+use Qiniu\Config;
+use Qiniu\Http\HttpClient;
+use Qiniu\Storage\ResumeUploader;
+use Qiniu\Storage\FormUploader;
+
+/**
+ * 主要涉及了资源上传接口的实现
+ *
+ * @link http://developer.qiniu.com/docs/v6/api/reference/up/
+ */
+final class UploadManager
+{
+ private $config;
+
+ public function __construct(Config $config = null)
+ {
+ if ($config === null) {
+ $config = new Config();
+ }
+ $this->config = $config;
+ }
+
+ /**
+ * 上传二进制流到七牛
+ *
+ * @param $upToken 上传凭证
+ * @param $key 上传文件名
+ * @param $data 上传二进制流
+ * @param $params 自定义变量,规格参考
+ * http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
+ * @param $mime 上传数据的mimeType
+ * @param $checkCrc 是否校验crc32
+ *
+ * @return array 包含已上传文件的信息,类似:
+ * [
+ * "hash" => "<Hash string>",
+ * "key" => "<Key string>"
+ * ]
+ */
+ public function put(
+ $upToken,
+ $key,
+ $data,
+ $params = null,
+ $mime = 'application/octet-stream',
+ $fname = null
+ ) {
+
+ $params = self::trimParams($params);
+ return FormUploader::put(
+ $upToken,
+ $key,
+ $data,
+ $this->config,
+ $params,
+ $mime,
+ $fname
+ );
+ }
+
+
+ /**
+ * 上传文件到七牛
+ *
+ * @param $upToken 上传凭证
+ * @param $key 上传文件名
+ * @param $filePath 上传文件的路径
+ * @param $params 自定义变量,规格参考
+ * http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
+ * @param $mime 上传数据的mimeType
+ * @param $checkCrc 是否校验crc32
+ *
+ * @return array 包含已上传文件的信息,类似:
+ * [
+ * "hash" => "<Hash string>",
+ * "key" => "<Key string>"
+ * ]
+ */
+ public function putFile(
+ $upToken,
+ $key,
+ $filePath,
+ $params = null,
+ $mime = 'application/octet-stream',
+ $checkCrc = false
+ ) {
+
+ $file = fopen($filePath, 'rb');
+ if ($file === false) {
+ throw new \Exception("file can not open", 1);
+ }
+ $params = self::trimParams($params);
+ $stat = fstat($file);
+ $size = $stat['size'];
+ if ($size <= Config::BLOCK_SIZE) {
+ $data = fread($file, $size);
+ fclose($file);
+ if ($data === false) {
+ throw new \Exception("file can not read", 1);
+ }
+ return FormUploader::put(
+ $upToken,
+ $key,
+ $data,
+ $this->config,
+ $params,
+ $mime,
+ basename($filePath)
+ );
+ }
+
+ $up = new ResumeUploader(
+ $upToken,
+ $key,
+ $file,
+ $size,
+ $params,
+ $mime,
+ $this->config
+ );
+ $ret = $up->upload(basename($filePath));
+ fclose($file);
+ return $ret;
+ }
+
+ public static function trimParams($params)
+ {
+ if ($params === null) {
+ return null;
+ }
+ $ret = array();
+ foreach ($params as $k => $v) {
+ $pos1 = strpos($k, 'x:');
+ $pos2 = strpos($k, 'x-qn-meta-');
+ if (($pos1 === 0 || $pos2 === 0) && !empty($v)) {
+ $ret[$k] = $v;
+ }
+ }
+ return $ret;
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Zone.php b/vendor/qiniu/php-sdk/src/Qiniu/Zone.php
new file mode 100755
index 0000000..7af0e3f
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/Zone.php
@@ -0,0 +1,47 @@
+<?php
+namespace Qiniu;
+
+use Qiniu\Region;
+
+class Zone extends Region
+{
+ public static function zonez0()
+ {
+ return parent::regionHuadong();
+ }
+
+ public static function zonez1()
+ {
+ return parent::regionHuabei();
+ }
+
+ public static function zonez2()
+ {
+ return parent::regionHuanan();
+ }
+
+ public static function zoneAs0()
+ {
+ return parent::regionSingapore();
+ }
+
+ public static function zoneNa0()
+ {
+ return parent::regionNorthAmerica();
+ }
+
+ public static function qvmZonez0()
+ {
+ return parent::qvmRegionHuadong();
+ }
+
+ public static function qvmZonez1()
+ {
+ return parent::qvmRegionHuabei();
+ }
+
+ public static function queryZone($ak, $bucket)
+ {
+ return parent::queryRegion($ak, $bucket);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/src/Qiniu/functions.php b/vendor/qiniu/php-sdk/src/Qiniu/functions.php
new file mode 100755
index 0000000..5831a51
--- /dev/null
+++ b/vendor/qiniu/php-sdk/src/Qiniu/functions.php
@@ -0,0 +1,264 @@
+<?php
+
+namespace Qiniu;
+
+use Qiniu\Config;
+
+if (!defined('QINIU_FUNCTIONS_VERSION')) {
+ define('QINIU_FUNCTIONS_VERSION', Config::SDK_VER);
+
+ /**
+ * 计算文件的crc32检验码:
+ *
+ * @param $file string 待计算校验码的文件路径
+ *
+ * @return string 文件内容的crc32校验码
+ */
+ function crc32_file($file)
+ {
+ $hash = hash_file('crc32b', $file);
+ $array = unpack('N', pack('H*', $hash));
+ return sprintf('%u', $array[1]);
+ }
+
+ /**
+ * 计算输入流的crc32检验码
+ *
+ * @param $data 待计算校验码的字符串
+ *
+ * @return string 输入字符串的crc32校验码
+ */
+ function crc32_data($data)
+ {
+ $hash = hash('crc32b', $data);
+ $array = unpack('N', pack('H*', $hash));
+ return sprintf('%u', $array[1]);
+ }
+
+ /**
+ * 对提供的数据进行urlsafe的base64编码。
+ *
+ * @param string $data 待编码的数据,一般为字符串
+ *
+ * @return string 编码后的字符串
+ * @link http://developer.qiniu.com/docs/v6/api/overview/appendix.html#urlsafe-base64
+ */
+ function base64_urlSafeEncode($data)
+ {
+ $find = array('+', '/');
+ $replace = array('-', '_');
+ return str_replace($find, $replace, base64_encode($data));
+ }
+
+ /**
+ * 对提供的urlsafe的base64编码的数据进行解码
+ *
+ * @param string $str 待解码的数据,一般为字符串
+ *
+ * @return string 解码后的字符串
+ */
+ function base64_urlSafeDecode($str)
+ {
+ $find = array('-', '_');
+ $replace = array('+', '/');
+ return base64_decode(str_replace($find, $replace, $str));
+ }
+
+ /**
+ * Wrapper for JSON decode that implements error detection with helpful
+ * error messages.
+ *
+ * @param string $json JSON data to parse
+ * @param bool $assoc When true, returned objects will be converted
+ * into associative arrays.
+ * @param int $depth User specified recursion depth.
+ *
+ * @return mixed
+ * @throws \InvalidArgumentException if the JSON cannot be parsed.
+ * @link http://www.php.net/manual/en/function.json-decode.php
+ */
+ function json_decode($json, $assoc = false, $depth = 512)
+ {
+ static $jsonErrors = array(
+ JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded',
+ JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch',
+ JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found',
+ JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON',
+ JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded'
+ );
+
+ if (empty($json)) {
+ return null;
+ }
+ $data = \json_decode($json, $assoc, $depth);
+
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ $last = json_last_error();
+ throw new \InvalidArgumentException(
+ 'Unable to parse JSON data: '
+ . (isset($jsonErrors[$last])
+ ? $jsonErrors[$last]
+ : 'Unknown error')
+ );
+ }
+
+ return $data;
+ }
+
+ /**
+ * 计算七牛API中的数据格式
+ *
+ * @param $bucket 待操作的空间名
+ * @param $key 待操作的文件名
+ *
+ * @return string 符合七牛API规格的数据格式
+ * @link http://developer.qiniu.com/docs/v6/api/reference/data-formats.html
+ */
+ function entry($bucket, $key)
+ {
+ $en = $bucket;
+ if (!empty($key)) {
+ $en = $bucket . ':' . $key;
+ }
+ return base64_urlSafeEncode($en);
+ }
+
+ /**
+ * array 辅助方法,无值时不set
+ *
+ * @param $array 待操作array
+ * @param $key key
+ * @param $value value 为null时 不设置
+ *
+ * @return array 原来的array,便于连续操作
+ */
+ function setWithoutEmpty(&$array, $key, $value)
+ {
+ if (!empty($value)) {
+ $array[$key] = $value;
+ }
+ return $array;
+ }
+
+ /**
+ * 缩略图链接拼接
+ *
+ * @param string $url 图片链接
+ * @param int $mode 缩略模式
+ * @param int $width 宽度
+ * @param int $height 长度
+ * @param string $format 输出类型
+ * @param int $quality 图片质量
+ * @param int $interlace 是否支持渐进显示
+ * @param int $ignoreError 忽略结果
+ * @return string
+ * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+ function thumbnail(
+ $url,
+ $mode,
+ $width,
+ $height,
+ $format = null,
+ $quality = null,
+ $interlace = null,
+ $ignoreError = 1
+ ) {
+
+ static $imageUrlBuilder = null;
+ if (is_null($imageUrlBuilder)) {
+ $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder;
+ }
+
+ return call_user_func_array(array($imageUrlBuilder, 'thumbnail'), func_get_args());
+ }
+
+ /**
+ * 图片水印
+ *
+ * @param string $url 图片链接
+ * @param string $image 水印图片链接
+ * @param numeric $dissolve 透明度
+ * @param string $gravity 水印位置
+ * @param numeric $dx 横轴边距
+ * @param numeric $dy 纵轴边距
+ * @param numeric $watermarkScale 自适应原图的短边比例
+ * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html
+ * @return string
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+ function waterImg(
+ $url,
+ $image,
+ $dissolve = 100,
+ $gravity = 'SouthEast',
+ $dx = null,
+ $dy = null,
+ $watermarkScale = null
+ ) {
+
+ static $imageUrlBuilder = null;
+ if (is_null($imageUrlBuilder)) {
+ $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder;
+ }
+
+ return call_user_func_array(array($imageUrlBuilder, 'waterImg'), func_get_args());
+ }
+
+ /**
+ * 文字水印
+ *
+ * @param string $url 图片链接
+ * @param string $text 文字
+ * @param string $font 文字字体
+ * @param string $fontSize 文字字号
+ * @param string $fontColor 文字颜色
+ * @param numeric $dissolve 透明度
+ * @param string $gravity 水印位置
+ * @param numeric $dx 横轴边距
+ * @param numeric $dy 纵轴边距
+ * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark
+ * @return string
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+ function waterText(
+ $url,
+ $text,
+ $font = '黑体',
+ $fontSize = 0,
+ $fontColor = null,
+ $dissolve = 100,
+ $gravity = 'SouthEast',
+ $dx = null,
+ $dy = null
+ ) {
+
+ static $imageUrlBuilder = null;
+ if (is_null($imageUrlBuilder)) {
+ $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder;
+ }
+
+ return call_user_func_array(array($imageUrlBuilder, 'waterText'), func_get_args());
+ }
+
+ /**
+ * 从uptoken解析accessKey和bucket
+ *
+ * @param $upToken
+ * @return array(ak,bucket,err=null)
+ */
+ function explodeUpToken($upToken)
+ {
+ $items = explode(':', $upToken);
+ if (count($items) != 3) {
+ return array(null, null, "invalid uptoken");
+ }
+ $accessKey = $items[0];
+ $putPolicy = json_decode(base64_urlSafeDecode($items[2]));
+ $scope = $putPolicy->scope;
+ $scopeItems = explode(':', $scope);
+ $bucket = $scopeItems[0];
+ return array($accessKey, $bucket, null);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/test-env.sh b/vendor/qiniu/php-sdk/test-env.sh
new file mode 100755
index 0000000..eedf6b5
--- /dev/null
+++ b/vendor/qiniu/php-sdk/test-env.sh
@@ -0,0 +1,4 @@
+export QINIU_ACCESS_KEY=xxx
+export QINIU_SECRET_KEY=xxx
+export QINIU_TEST_BUCKET=phpsdk
+export QINIU_TEST_DOMAIN=phpsdk.qiniudn.com
\ No newline at end of file
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/AuthTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/AuthTest.php
new file mode 100755
index 0000000..712cbd5
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/AuthTest.php
@@ -0,0 +1,71 @@
+<?php
+// Hack to override the time returned from the S3SignatureV4
+// @codingStandardsIgnoreStart
+namespace Qiniu {
+ function time()
+ {
+ return isset($_SERVER['override_qiniu_auth_time'])
+ ? 1234567890
+ : \time();
+ }
+}
+
+namespace Qiniu\Tests {
+ use Qiniu\Auth;
+
+ // @codingStandardsIgnoreEnd
+
+ class AuthTest extends \PHPUnit_Framework_TestCase
+ {
+
+ public function testSign()
+ {
+ global $dummyAuth;
+ $token = $dummyAuth->sign('test');
+ $this->assertEquals('abcdefghklmnopq:mSNBTR7uS2crJsyFr2Amwv1LaYg=', $token);
+ }
+
+ public function testSignWithData()
+ {
+ global $dummyAuth;
+ $token = $dummyAuth->signWithData('test');
+ $this->assertEquals('abcdefghklmnopq:-jP8eEV9v48MkYiBGs81aDxl60E=:dGVzdA==', $token);
+ }
+
+ public function testSignRequest()
+ {
+ global $dummyAuth;
+ $token = $dummyAuth->signRequest('http://www.qiniu.com?go=1', 'test', '');
+ $this->assertEquals('abcdefghklmnopq:cFyRVoWrE3IugPIMP5YJFTO-O-Y=', $token);
+ $ctype = 'application/x-www-form-urlencoded';
+ $token = $dummyAuth->signRequest('http://www.qiniu.com?go=1', 'test', $ctype);
+ $this->assertEquals($token, 'abcdefghklmnopq:svWRNcacOE-YMsc70nuIYdaa1e4=');
+ }
+
+ public function testPrivateDownloadUrl()
+ {
+ global $dummyAuth;
+ $_SERVER['override_qiniu_auth_time'] = true;
+ $url = $dummyAuth->privateDownloadUrl('http://www.qiniu.com?go=1');
+ $expect = 'http://www.qiniu.com?go=1&e=1234571490&token=abcdefghklmnopq:8vzBeLZ9W3E4kbBLFLW0Xe0u7v4=';
+ $this->assertEquals($expect, $url);
+ unset($_SERVER['override_qiniu_auth_time']);
+ }
+
+ public function testUploadToken()
+ {
+ global $dummyAuth;
+ $_SERVER['override_qiniu_auth_time'] = true;
+ $token = $dummyAuth->uploadToken('1', '2', 3600, array('endUser' => 'y'));
+ // @codingStandardsIgnoreStart
+ $exp = 'abcdefghklmnopq:yyeexeUkPOROoTGvwBjJ0F0VLEo=:eyJlbmRVc2VyIjoieSIsInNjb3BlIjoiMToyIiwiZGVhZGxpbmUiOjEyMzQ1NzE0OTB9';
+ // @codingStandardsIgnoreEnd
+ $this->assertEquals($exp, $token);
+ unset($_SERVER['override_qiniu_auth_time']);
+ }
+
+ public function testVerifyCallback()
+ {
+ }
+ }
+}
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Base64Test.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Base64Test.php
new file mode 100755
index 0000000..6d63353
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Base64Test.php
@@ -0,0 +1,14 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu;
+
+class Base64Test extends \PHPUnit_Framework_TestCase
+{
+ public function testUrlSafe()
+ {
+ $a = '你好';
+ $b = \Qiniu\base64_urlSafeEncode($a);
+ $this->assertEquals($a, \Qiniu\base64_urlSafeDecode($b));
+ }
+}
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/BucketTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/BucketTest.php
new file mode 100755
index 0000000..534d8c2
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/BucketTest.php
@@ -0,0 +1,227 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Storage\BucketManager;
+
+class BucketTest extends \PHPUnit_Framework_TestCase
+{
+ protected $bucketManager;
+ protected $dummyBucketManager;
+ protected $bucketName;
+ protected $key;
+ protected $key2;
+
+ protected function setUp()
+ {
+ global $bucketName;
+ global $key;
+ global $key2;
+ $this->bucketName = $bucketName;
+ $this->key = $key;
+ $this->key2 = $key2;
+
+ global $testAuth;
+ $this->bucketManager = new BucketManager($testAuth);
+
+ global $dummyAuth;
+ $this->dummyBucketManager = new BucketManager($dummyAuth);
+ }
+
+ public function testBuckets()
+ {
+
+ list($list, $error) = $this->bucketManager->buckets();
+ $this->assertTrue(in_array($this->bucketName, $list));
+ $this->assertNull($error);
+
+ list($list2, $error) = $this->dummyBucketManager->buckets();
+ $this->assertEquals(401, $error->code());
+ $this->assertNull($list2);
+ $this->assertNotNull($error->message());
+ }
+
+ public function testList()
+ {
+ list($ret, $error) = $this->bucketManager->listFiles($this->bucketName, null, null, 10);
+ $this->assertNotNull($ret['items'][0]);
+ $this->assertNotNull($ret['marker']);
+ }
+
+ public function testStat()
+ {
+ list($stat, $error) = $this->bucketManager->stat($this->bucketName, $this->key);
+ $this->assertNotNull($stat);
+ $this->assertNull($error);
+ $this->assertNotNull($stat['hash']);
+
+ list($stat, $error) = $this->bucketManager->stat($this->bucketName, 'nofile');
+ $this->assertNull($stat);
+ $this->assertEquals(612, $error->code());
+ $this->assertNotNull($error->message());
+
+ list($stat, $error) = $this->bucketManager->stat('nobucket', 'nofile');
+ $this->assertNull($stat);
+ $this->assertEquals(631, $error->code());
+ $this->assertNotNull($error->message());
+ }
+
+ public function testDelete()
+ {
+ $error = $this->bucketManager->delete($this->bucketName, 'del');
+ $this->assertEquals(612, $error->code());
+ }
+
+
+ public function testRename()
+ {
+ $key = 'renamefrom' . rand();
+ $this->bucketManager->copy($this->bucketName, $this->key, $this->bucketName, $key);
+ $key2 = 'renameto' . $key;
+ $error = $this->bucketManager->rename($this->bucketName, $key, $key2);
+ $this->assertNull($error);
+ $error = $this->bucketManager->delete($this->bucketName, $key2);
+ $this->assertNull($error);
+ }
+
+
+ public function testCopy()
+ {
+ $key = 'copyto' . rand();
+ $this->bucketManager->delete($this->bucketName, $key);
+
+ $error = $this->bucketManager->copy(
+ $this->bucketName,
+ $this->key,
+ $this->bucketName,
+ $key
+ );
+ $this->assertNull($error);
+
+ //test force copy
+ $error = $this->bucketManager->copy(
+ $this->bucketName,
+ $this->key2,
+ $this->bucketName,
+ $key,
+ true
+ );
+ $this->assertNull($error);
+
+ list($key2Stat,) = $this->bucketManager->stat($this->bucketName, $this->key2);
+ list($key2CopiedStat,) = $this->bucketManager->stat($this->bucketName, $key);
+
+ $this->assertEquals($key2Stat['hash'], $key2CopiedStat['hash']);
+
+ $error = $this->bucketManager->delete($this->bucketName, $key);
+ $this->assertNull($error);
+ }
+
+
+ public function testChangeMime()
+ {
+ $error = $this->bucketManager->changeMime(
+ $this->bucketName,
+ 'php-sdk.html',
+ 'text/html'
+ );
+ $this->assertNull($error);
+ }
+
+ public function testPrefetch()
+ {
+ $error = $this->bucketManager->prefetch(
+ $this->bucketName,
+ 'php-sdk.html'
+ );
+ $this->assertNull($error);
+ }
+
+ public function testFetch()
+ {
+ list($ret, $error) = $this->bucketManager->fetch(
+ 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html',
+ $this->bucketName,
+ 'fetch.html'
+ );
+ $this->assertArrayHasKey('hash', $ret);
+ $this->assertNull($error);
+
+ list($ret, $error) = $this->bucketManager->fetch(
+ 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html',
+ $this->bucketName,
+ ''
+ );
+ $this->assertArrayHasKey('key', $ret);
+ $this->assertNull($error);
+
+ list($ret, $error) = $this->bucketManager->fetch(
+ 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html',
+ $this->bucketName
+ );
+ $this->assertArrayHasKey('key', $ret);
+ $this->assertNull($error);
+ }
+
+ public function testBatchCopy()
+ {
+ $key = 'copyto' . rand();
+ $ops = BucketManager::buildBatchCopy(
+ $this->bucketName,
+ array($this->key => $key),
+ $this->bucketName,
+ true
+ );
+ list($ret, $error) = $this->bucketManager->batch($ops);
+ $this->assertEquals(200, $ret[0]['code']);
+ $ops = BucketManager::buildBatchDelete($this->bucketName, array($key));
+ list($ret, $error) = $this->bucketManager->batch($ops);
+ $this->assertEquals(200, $ret[0]['code']);
+ }
+
+ public function testBatchMove()
+ {
+ $key = 'movefrom' . rand();
+ $this->bucketManager->copy($this->bucketName, $this->key, $this->bucketName, $key);
+ $key2 = $key . 'to';
+ $ops = BucketManager::buildBatchMove(
+ $this->bucketName,
+ array($key => $key2),
+ $this->bucketName,
+ true
+ );
+ list($ret, $error) = $this->bucketManager->batch($ops);
+ $this->assertEquals(200, $ret[0]['code']);
+ $error = $this->bucketManager->delete($this->bucketName, $key2);
+ $this->assertNull($error);
+ }
+
+ public function testBatchRename()
+ {
+ $key = 'rename' . rand();
+ $this->bucketManager->copy($this->bucketName, $this->key, $this->bucketName, $key);
+ $key2 = $key . 'to';
+ $ops = BucketManager::buildBatchRename($this->bucketName, array($key => $key2), true);
+ list($ret, $error) = $this->bucketManager->batch($ops);
+ $this->assertEquals(200, $ret[0]['code']);
+ $error = $this->bucketManager->delete($this->bucketName, $key2);
+ $this->assertNull($error);
+ }
+
+ public function testBatchStat()
+ {
+ $ops = BucketManager::buildBatchStat($this->bucketName, array('php-sdk.html'));
+ list($ret, $error) = $this->bucketManager->batch($ops);
+ $this->assertEquals(200, $ret[0]['code']);
+ }
+
+ public function testDeleteAfterDays()
+ {
+ $key = rand();
+ $err = $this->bucketManager->deleteAfterDays($this->bucketName, $key, 1);
+ $this->assertEquals(612, $err->code());
+
+ $this->bucketManager->copy($this->bucketName, $this->key, $this->bucketName, $key);
+ $err = $this->bucketManager->deleteAfterDays($this->bucketName, $key, 1);
+ $this->assertEquals(null, $err);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php
new file mode 100755
index 0000000..3c45bbd
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Created by IntelliJ IDEA.
+ * User: wf
+ * Date: 2017/6/21
+ * Time: AM8:46
+ */
+
+namespace Qiniu\Tests;
+
+use Qiniu\Cdn\CdnManager;
+use Qiniu\Http\Client;
+
+class CdnManagerTest extends \PHPUnit_Framework_TestCase
+{
+ protected $cdnManager;
+ protected $encryptKey;
+ protected $imgUrl;
+
+ protected function setUp()
+ {
+ global $timestampAntiLeechEncryptKey;
+ global $customDomain;
+ global $testAuth;
+
+ $this->cdnManager = new CdnManager($testAuth);
+ $this->encryptKey = $timestampAntiLeechEncryptKey;
+ $this->imgUrl = $customDomain . '/24.jpg';
+ }
+
+ public function testCreateTimestampAntiLeechUrl()
+ {
+
+ $signUrl = $this->cdnManager->createTimestampAntiLeechUrl($this->imgUrl, $this->encryptKey, 3600);
+
+ $response = Client::get($signUrl);
+
+ $this->assertEquals($response->statusCode, 200);
+ $this->assertNull($response->error);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Crc32Test.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Crc32Test.php
new file mode 100755
index 0000000..bfb36da
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Crc32Test.php
@@ -0,0 +1,21 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu;
+
+class Crc32Test extends \PHPUnit_Framework_TestCase
+{
+ public function testData()
+ {
+ $a = '你好';
+ $b = \Qiniu\crc32_data($a);
+ $this->assertEquals('1352841281', $b);
+ }
+
+ public function testFile()
+ {
+ $b = \Qiniu\crc32_file(__file__);
+ $c = \Qiniu\crc32_file(__file__);
+ $this->assertEquals($c, $b);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/DownloadTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/DownloadTest.php
new file mode 100755
index 0000000..82990f2
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/DownloadTest.php
@@ -0,0 +1,25 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Http\Client;
+
+class DownloadTest extends \PHPUnit_Framework_TestCase
+{
+ public function test()
+ {
+ global $testAuth;
+ $base_url = 'http://private-res.qiniudn.com/gogopher.jpg';
+ $private_url = $testAuth->privateDownloadUrl($base_url);
+ $response = Client::get($private_url);
+ $this->assertEquals(200, $response->statusCode);
+ }
+
+ public function testFop()
+ {
+ global $testAuth;
+ $base_url = 'http://private-res.qiniudn.com/gogopher.jpg?exif';
+ $private_url = $testAuth->privateDownloadUrl($base_url);
+ $response = Client::get($private_url);
+ $this->assertEquals(200, $response->statusCode);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/EtagTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/EtagTest.php
new file mode 100755
index 0000000..42a1499
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/EtagTest.php
@@ -0,0 +1,52 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Etag;
+
+class EtagTest extends \PHPUnit_Framework_TestCase
+{
+ public function test0M()
+ {
+ $file = qiniuTempFile(0);
+ list($r, $error) = Etag::sum($file);
+ unlink($file);
+ $this->assertEquals('Fto5o-5ea0sNMlW_75VgGJCv2AcJ', $r);
+ $this->assertNull($error);
+ }
+
+ public function testLess4M()
+ {
+ $file = qiniuTempFile(3 * 1024 * 1024);
+ list($r, $error) = Etag::sum($file);
+ unlink($file);
+ $this->assertEquals('Fs5BpnAjRykYTg6o5E09cjuXrDkG', $r);
+ $this->assertNull($error);
+ }
+
+ public function test4M()
+ {
+ $file = qiniuTempFile(4 * 1024 * 1024);
+ list($r, $error) = Etag::sum($file);
+ unlink($file);
+ $this->assertEquals('FiuKULnybewpEnrfTmxjsxc-3dWp', $r);
+ $this->assertNull($error);
+ }
+
+ public function testMore4M()
+ {
+ $file = qiniuTempFile(5 * 1024 * 1024);
+ list($r, $error) = Etag::sum($file);
+ unlink($file);
+ $this->assertEquals('lhvyfIWMYFTq4s4alzlhXoAkqfVL', $r);
+ $this->assertNull($error);
+ }
+
+ public function test8M()
+ {
+ $file = qiniuTempFile(8 * 1024 * 1024);
+ list($r, $error) = Etag::sum($file);
+ unlink($file);
+ $this->assertEquals('lmRm9ZfGZ86bnMys4wRTWtJj9ClG', $r);
+ $this->assertNull($error);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FopTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FopTest.php
new file mode 100755
index 0000000..e1ea730
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FopTest.php
@@ -0,0 +1,37 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Processing\Operation;
+use Qiniu\Processing\PersistentFop;
+
+class FopTest extends \PHPUnit_Framework_TestCase
+{
+ public function testExifPub()
+ {
+ $fop = new Operation('testres.qiniudn.com');
+ list($exif, $error) = $fop->execute('gogopher.jpg', 'exif');
+ $this->assertNull($error);
+ $this->assertNotNull($exif);
+ }
+
+ public function testExifPrivate()
+ {
+ global $testAuth;
+ $fop = new Operation('private-res.qiniudn.com', $testAuth);
+ list($exif, $error) = $fop->execute('noexif.jpg', 'exif');
+ $this->assertNotNull($error);
+ $this->assertNull($exif);
+ }
+
+ public function testbuildUrl()
+ {
+ $fops = 'imageView2/2/h/200';
+ $fop = new Operation('testres.qiniudn.com');
+ $url = $fop->buildUrl('gogopher.jpg', $fops);
+ $this->assertEquals($url, 'http://testres.qiniudn.com/gogopher.jpg?imageView2/2/h/200');
+
+ $fops = array('imageView2/2/h/200', 'imageInfo');
+ $url = $fop->buildUrl('gogopher.jpg', $fops);
+ $this->assertEquals($url, 'http://testres.qiniudn.com/gogopher.jpg?imageView2/2/h/200|imageInfo');
+ }
+}
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FormUpTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FormUpTest.php
new file mode 100755
index 0000000..4813eed
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FormUpTest.php
@@ -0,0 +1,59 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Storage\FormUploader;
+use Qiniu\Storage\UploadManager;
+use Qiniu\Config;
+
+class FormUpTest extends \PHPUnit_Framework_TestCase
+{
+ protected $bucketName;
+ protected $auth;
+ protected $cfg;
+
+ protected function setUp()
+ {
+ global $bucketName;
+ $this->bucketName = $bucketName;
+
+ global $testAuth;
+ $this->auth = $testAuth;
+ $this->cfg = new Config();
+ }
+
+ public function testData()
+ {
+ $token = $this->auth->uploadToken($this->bucketName);
+ list($ret, $error) = FormUploader::put($token, 'formput', 'hello world', $this->cfg, null, 'text/plain', null);
+ $this->assertNull($error);
+ $this->assertNotNull($ret['hash']);
+ }
+
+ public function testData2()
+ {
+ $upManager = new UploadManager();
+ $token = $this->auth->uploadToken($this->bucketName);
+ list($ret, $error) = $upManager->put($token, 'formput', 'hello world', null, 'text/plain', null);
+ $this->assertNull($error);
+ $this->assertNotNull($ret['hash']);
+ }
+
+ public function testFile()
+ {
+ $key = 'formPutFile';
+ $token = $this->auth->uploadToken($this->bucketName, $key);
+ list($ret, $error) = FormUploader::putFile($token, $key, __file__, $this->cfg, null, 'text/plain', null);
+ $this->assertNull($error);
+ $this->assertNotNull($ret['hash']);
+ }
+
+ public function testFile2()
+ {
+ $key = 'formPutFile';
+ $token = $this->auth->uploadToken($this->bucketName, $key);
+ $upManager = new UploadManager();
+ list($ret, $error) = $upManager->putFile($token, $key, __file__, null, 'text/plain', null);
+ $this->assertNull($error);
+ $this->assertNotNull($ret['hash']);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/HttpTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/HttpTest.php
new file mode 100755
index 0000000..e2ab5fc
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/HttpTest.php
@@ -0,0 +1,43 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Http\Client;
+
+class HttpTest extends \PHPUnit_Framework_TestCase
+{
+ public function testGet()
+ {
+ $response = Client::get('baidu.com');
+ $this->assertEquals($response->statusCode, 200);
+ $this->assertNotNull($response->body);
+ $this->assertNull($response->error);
+ }
+
+ public function testGetQiniu()
+ {
+ $response = Client::get('up.qiniu.com');
+ $this->assertEquals(405, $response->statusCode);
+ $this->assertNotNull($response->body);
+ $this->assertNotNull($response->xReqId());
+ $this->assertNotNull($response->xLog());
+ $this->assertNotNull($response->error);
+ }
+
+ public function testPost()
+ {
+ $response = Client::post('baidu.com', null);
+ $this->assertEquals($response->statusCode, 200);
+ $this->assertNotNull($response->body);
+ $this->assertNull($response->error);
+ }
+
+ public function testPostQiniu()
+ {
+ $response = Client::post('up.qiniu.com', null);
+ $this->assertEquals($response->statusCode, 400);
+ $this->assertNotNull($response->body);
+ $this->assertNotNull($response->xReqId());
+ $this->assertNotNull($response->xLog());
+ $this->assertNotNull($response->error);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php
new file mode 100755
index 0000000..fca87b6
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php
@@ -0,0 +1,261 @@
+<?php
+
+namespace Qiniu\Tests;
+
+/**
+ * imageprocess test
+ *
+ * @package Qiniu
+ * @subpackage test
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+class ImageUrlBuilderTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * 缩略图测试
+ *
+ * @test
+ * @return void
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+ public function testThumbutl()
+ {
+ $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder();
+ $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg';
+ $url2 = $url . '?watermark/1/gravity/SouthEast/dx/0/dy/0/image/'
+ . 'aHR0cDovL2Fkcy1jZG4uY2h1Y2h1amllLmNvbS9Ga1R6bnpIY2RLdmRBUFc5cHZZZ3pTc21UY0tB';
+ // 异常测试
+ $this->assertEquals($url, $imageUrlBuilder->thumbnail($url, 1, 0, 0));
+ $this->assertEquals($url, \Qiniu\thumbnail($url, 1, 0, 0));
+
+ // 简单缩略测试
+ $this->assertEquals(
+ $url . '?imageView2/1/w/200/h/200/ignore-error/1/',
+ $imageUrlBuilder->thumbnail($url, 1, 200, 200)
+ );
+ $this->assertEquals(
+ $url . '?imageView2/1/w/200/h/200/ignore-error/1/',
+ \Qiniu\thumbnail($url, 1, 200, 200)
+ );
+
+ // 输出格式测试
+ $this->assertEquals(
+ $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/',
+ $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png')
+ );
+ $this->assertEquals(
+ $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/',
+ \Qiniu\thumbnail($url, 1, 200, 200, 'png')
+ );
+
+ // 渐进显示测试
+ $this->assertEquals(
+ $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/ignore-error/1/',
+ $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png', 1)
+ );
+ $this->assertEquals(
+ $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/',
+ \Qiniu\thumbnail($url, 1, 200, 200, 'png', 2)
+ );
+
+ // 图片质量测试
+ $this->assertEquals(
+ $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/q/80/ignore-error/1/',
+ $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png', 1, 80)
+ );
+ $this->assertEquals(
+ $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/ignore-error/1/',
+ \Qiniu\thumbnail($url, 1, 200, 200, 'png', 1, 101)
+ );
+
+ // 多参数测试
+ $this->assertEquals(
+ $url2 . '|imageView2/1/w/200/h/200/ignore-error/1/',
+ $imageUrlBuilder->thumbnail($url2, 1, 200, 200)
+ );
+ $this->assertEquals(
+ $url2 . '|imageView2/1/w/200/h/200/ignore-error/1/',
+ \Qiniu\thumbnail($url2, 1, 200, 200)
+ );
+ }
+
+ /**
+ * 图片水印测试
+ *
+ * @test
+ * @param void
+ * @return void
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+ public function waterImgTest()
+ {
+ $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder();
+ $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg';
+ $url2 = $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/';
+ $image = 'http://developer.qiniu.com/resource/logo-2.jpg';
+
+ // 水印简单测试
+ $this->assertEquals(
+ $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+ . '/dissolve/100/gravity/SouthEast/',
+ $imageUrlBuilder->waterImg($url, $image)
+ );
+ $this->assertEquals(
+ $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+ . '/gravity/SouthEast/',
+ $imageUrlBuilder->waterImg($url, $image, 101)
+ );
+ $this->assertEquals(
+ $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==/',
+ $imageUrlBuilder->waterImg($url, $image, 101, 'sdfsd')
+ );
+ $this->assertEquals(
+ $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+ . '/dissolve/100/gravity/SouthEast/',
+ \Qiniu\waterImg($url, $image)
+ );
+
+ // 横轴边距测试
+ $this->assertEquals(
+ $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+ . '/dissolve/100/gravity/SouthEast/dx/10/',
+ $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10)
+ );
+ $this->assertEquals(
+ $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+ . '/dissolve/100/gravity/SouthEast/',
+ \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad')
+ );
+
+ // 纵轴边距测试
+ $this->assertEquals(
+ $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+ . '/dissolve/100/gravity/SouthEast/dx/10/dy/10/',
+ $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10, 10)
+ );
+ $this->assertEquals(
+ $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+ . '/dissolve/100/gravity/SouthEast/',
+ \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad', 'asdf')
+ );
+
+ // 自适应原图的短边比例测试
+ $this->assertEquals(
+ $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+ . '/dissolve/100/gravity/SouthEast/dx/10/dy/10/ws/0.5/',
+ $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10, 10, 0.5)
+ );
+ $this->assertEquals(
+ $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+ . '/dissolve/100/gravity/SouthEast/',
+ \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad', 'asdf', 2)
+ );
+
+ // 多参数测试
+ $this->assertEquals(
+ $url2 . '|watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+ . '/dissolve/100/gravity/SouthEast/',
+ $imageUrlBuilder->waterImg($url2, $image)
+ );
+ $this->assertEquals(
+ $url2 . '|watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+ . '/dissolve/100/gravity/SouthEast/',
+ \Qiniu\waterImg($url2, $image)
+ );
+ }
+
+ /**
+ * 文字水印测试
+ *
+ * @test
+ * @param void
+ * @return void
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+ public function waterTextTest()
+ {
+ $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder();
+ $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg';
+ $url2 = $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/';
+ $text = '测试一下';
+ $font = '微软雅黑';
+ $fontColor = '#FF0000';
+
+ // 水印简单测试
+ $this->assertEquals($url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/'
+ . 'fontsize/500/dissolve/100/gravity/SouthEast/', $imageUrlBuilder->waterText($url, $text, $font, 500));
+ $this->assertEquals(
+ $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/'
+ . 'dissolve/100/gravity/SouthEast/',
+ \Qiniu\waterText($url, $text, $font, 'sdf')
+ );
+
+ // 字体颜色测试
+ $this->assertEquals(
+ $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/'
+ . 'I0ZGMDAwMA==/dissolve/100/gravity/SouthEast/',
+ $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor)
+ );
+ $this->assertEquals(
+ $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA=='
+ . '/dissolve/100/gravity/SouthEast/',
+ \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor)
+ );
+
+ // 透明度测试
+ $this->assertEquals(
+ $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA=='
+ . '/dissolve/80/gravity/SouthEast/',
+ $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80)
+ );
+ $this->assertEquals(
+ $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA=='
+ . '/gravity/SouthEast/',
+ \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101)
+ );
+
+ // 水印位置测试
+ $this->assertEquals(
+ $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA=='
+ . '/dissolve/80/gravity/East/',
+ $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East')
+ );
+ $this->assertEquals(
+ $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/',
+ \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf')
+ );
+
+ // 横轴距离测试
+ $this->assertEquals(
+ $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA=='
+ . '/dissolve/80/gravity/East/dx/10/',
+ $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East', 10)
+ );
+ $this->assertEquals(
+ $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/',
+ \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf', 'sdfs')
+ );
+
+ // 纵轴距离测试
+ $this->assertEquals(
+ $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA=='
+ . '/dissolve/80/gravity/East/dx/10/dy/10/',
+ $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East', 10, 10)
+ );
+ $this->assertEquals(
+ $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/',
+ \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf', 'sdfs', 'ssdf')
+ );
+ // 多参数测试
+ $this->assertEquals(
+ $url2 . '|watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/'
+ . 'fontsize/500/dissolve/100/gravity/SouthEast/',
+ $imageUrlBuilder->waterText($url2, $text, $font, 500)
+ );
+ $this->assertEquals(
+ $url2 . '|watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/'
+ . 'fontsize/500/dissolve/100/gravity/SouthEast/',
+ \Qiniu\waterText($url2, $text, $font, 500)
+ );
+ }
+}
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/PfopTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/PfopTest.php
new file mode 100755
index 0000000..d03b3f6
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/PfopTest.php
@@ -0,0 +1,66 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Processing\Operation;
+use Qiniu\Processing\PersistentFop;
+
+class PfopTest extends \PHPUnit_Framework_TestCase
+{
+ public function testPfop()
+ {
+ global $testAuth;
+ $bucket = 'testres';
+ $key = 'sintel_trailer.mp4';
+ $pfop = new PersistentFop($testAuth, null);
+
+ $fops = 'avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240';
+ list($id, $error) = $pfop->execute($bucket, $key, $fops);
+ $this->assertNull($error);
+ list($status, $error) = $pfop->status($id);
+ $this->assertNotNull($status);
+ $this->assertNull($error);
+ }
+
+
+ public function testPfops()
+ {
+ global $testAuth;
+ $bucket = 'testres';
+ $key = 'sintel_trailer.mp4';
+ $fops = array(
+ 'avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240',
+ 'vframe/jpg/offset/7/w/480/h/360',
+ );
+ $pfop = new PersistentFop($testAuth, null);
+
+ list($id, $error) = $pfop->execute($bucket, $key, $fops);
+ $this->assertNull($error);
+
+ list($status, $error) = $pfop->status($id);
+ $this->assertNotNull($status);
+ $this->assertNull($error);
+ }
+
+ public function testMkzip()
+ {
+ global $testAuth;
+ $bucket = 'phpsdk';
+ $key = 'php-logo.png';
+ $pfop = new PersistentFop($testAuth, null);
+
+ $url1 = 'http://phpsdk.qiniudn.com/php-logo.png';
+ $url2 = 'http://phpsdk.qiniudn.com/php-sdk.html';
+ $zipKey = 'test.zip';
+
+ $fops = 'mkzip/2/url/' . \Qiniu\base64_urlSafeEncode($url1);
+ $fops .= '/url/' . \Qiniu\base64_urlSafeEncode($url2);
+ $fops .= '|saveas/' . \Qiniu\base64_urlSafeEncode("$bucket:$zipKey");
+
+ list($id, $error) = $pfop->execute($bucket, $key, $fops);
+ $this->assertNull($error);
+
+ list($status, $error) = $pfop->status($id);
+ $this->assertNotNull($status);
+ $this->assertNull($error);
+ }
+}
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php
new file mode 100755
index 0000000..41e6ea8
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php
@@ -0,0 +1,60 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Storage\ResumeUploader;
+use Qiniu\Storage\UploadManager;
+use Qiniu\Config;
+use Qiniu\Zone;
+
+class ResumeUpTest extends \PHPUnit_Framework_TestCase
+{
+ protected $bucketName;
+ protected $auth;
+
+ protected function setUp()
+ {
+ global $bucketName;
+ $this->bucketName = $bucketName;
+
+ global $testAuth;
+ $this->auth = $testAuth;
+ }
+
+ public function test4ML()
+ {
+ $key = 'resumePutFile4ML';
+ $upManager = new UploadManager();
+ $token = $this->auth->uploadToken($this->bucketName, $key);
+ $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10);
+ list($ret, $error) = $upManager->putFile($token, $key, $tempFile);
+ $this->assertNull($error);
+ $this->assertNotNull($ret['hash']);
+ unlink($tempFile);
+ }
+
+ public function test4ML2()
+ {
+ $key = 'resumePutFile4ML';
+ $zone = new Zone(array('up.fake.qiniu.com'), array('up.qiniup.com'));
+ $cfg = new Config($zone);
+ $upManager = new UploadManager($cfg);
+ $token = $this->auth->uploadToken($this->bucketName, $key);
+ $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10);
+ list($ret, $error) = $upManager->putFile($token, $key, $tempFile);
+ $this->assertNull($error);
+ $this->assertNotNull($ret['hash']);
+ unlink($tempFile);
+ }
+
+ // public function test8M()
+ // {
+ // $key = 'resumePutFile8M';
+ // $upManager = new UploadManager();
+ // $token = $this->auth->uploadToken($this->bucketName, $key);
+ // $tempFile = qiniuTempFile(8*1024*1024+10);
+ // list($ret, $error) = $upManager->putFile($token, $key, $tempFile);
+ // $this->assertNull($error);
+ // $this->assertNotNull($ret['hash']);
+ // unlink($tempFile);
+ // }
+}
diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ZoneTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ZoneTest.php
new file mode 100755
index 0000000..d32875b
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ZoneTest.php
@@ -0,0 +1,59 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu;
+use Qiniu\Zone;
+
+class ZoneTest extends \PHPUnit_Framework_TestCase
+{
+ protected $zone;
+ protected $zoneHttps;
+ protected $ak;
+
+ protected $bucketName;
+ protected $bucketNameBC;
+ protected $bucketNameNA;
+
+
+ protected function setUp()
+ {
+ global $bucketName;
+ $this->bucketName = $bucketName;
+
+ global $bucketNameBC;
+ $this->bucketNameBC = $bucketNameBC;
+
+ global $bucketNameNA;
+ $this->bucketNameNA = $bucketNameNA;
+
+ global $accessKey;
+ $this->ak = $accessKey;
+
+ $this->zone = new Zone();
+ $this->zoneHttps = new Zone('https');
+ }
+
+ public function testUpHosts()
+ {
+ $zone = Zone::queryZone($this->ak, $this->bucketName);
+ $this->assertContains('upload.qiniup.com', $zone->cdnUpHosts);
+
+ $zone = Zone::queryZone($this->ak, $this->bucketNameBC);
+ $this->assertContains('upload-z1.qiniup.com', $zone->cdnUpHosts);
+
+ $zone = Zone::queryZone($this->ak, $this->bucketNameNA);
+ $this->assertContains('upload-na0.qiniup.com', $zone->cdnUpHosts);
+ }
+
+ public function testIoHosts()
+ {
+ $zone = Zone::queryZone($this->ak, $this->bucketName);
+ $this->assertEquals($zone->iovipHost, 'iovip.qbox.me');
+
+ $zone = Zone::queryZone($this->ak, $this->bucketNameBC);
+ $this->assertEquals($zone->iovipHost, 'iovip-z1.qbox.me');
+
+ $zone = Zone::queryZone($this->ak, $this->bucketNameNA);
+ $this->assertEquals($zone->iovipHost, 'iovip-na0.qbox.me');
+ }
+}
diff --git a/vendor/qiniu/php-sdk/tests/bootstrap.php b/vendor/qiniu/php-sdk/tests/bootstrap.php
new file mode 100755
index 0000000..c300057
--- /dev/null
+++ b/vendor/qiniu/php-sdk/tests/bootstrap.php
@@ -0,0 +1,43 @@
+<?php
+// @codingStandardsIgnoreFile
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$testAuth = new Auth($accessKey, $secretKey);
+
+$bucketName = 'phpsdk';
+$key = 'php-logo.png';
+$key2 = 'niu.jpg';
+
+$bucketNameBC = 'phpsdk-bc';
+$bucketNameNA = 'phpsdk-na';
+
+$dummyAccessKey = 'abcdefghklmnopq';
+$dummySecretKey = '1234567890';
+$dummyAuth = new Auth($dummyAccessKey, $dummySecretKey);
+
+//cdn
+$timestampAntiLeechEncryptKey = getenv('QINIU_TIMESTAMP_ENCRPTKEY');
+$customDomain = "http://phpsdk.peterpy.cn";
+
+$tid = getenv('TRAVIS_JOB_NUMBER');
+if (!empty($tid)) {
+ $pid = getmypid();
+ $tid = strstr($tid, '.');
+ $tid .= '.' . $pid;
+}
+
+function qiniuTempFile($size)
+{
+ $fileName = tempnam(sys_get_temp_dir(), 'qiniu_');
+ $file = fopen($fileName, 'wb');
+ if ($size > 0) {
+ fseek($file, $size - 1);
+ fwrite($file, ' ');
+ }
+ fclose($file);
+ return $fileName;
+}
diff --git a/vendor/ralouphie/getallheaders/.gitignore b/vendor/ralouphie/getallheaders/.gitignore
new file mode 100755
index 0000000..1324e7d
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/.gitignore
@@ -0,0 +1,5 @@
+.idea
+.DS_store
+/vendor/
+composer.phar
+composer.lock
diff --git a/vendor/ralouphie/getallheaders/.travis.yml b/vendor/ralouphie/getallheaders/.travis.yml
new file mode 100755
index 0000000..f45b55f
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/.travis.yml
@@ -0,0 +1,18 @@
+language: php
+
+php:
+ - 5.3
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+
+before_script:
+ - composer install
+
+script:
+ - mkdir -p build/logs
+ - php vendor/bin/phpunit -c phpunit.xml
+
+after_script:
+ - php vendor/bin/coveralls -v
\ No newline at end of file
diff --git a/vendor/ralouphie/getallheaders/LICENSE b/vendor/ralouphie/getallheaders/LICENSE
new file mode 100755
index 0000000..be5540c
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Ralph Khattar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/ralouphie/getallheaders/README.md b/vendor/ralouphie/getallheaders/README.md
new file mode 100755
index 0000000..f3329d6
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/README.md
@@ -0,0 +1,19 @@
+getallheaders
+=============
+
+PHP `getallheaders()` polyfill. Compatible with PHP >= 5.3.
+
+[](https://travis-ci.org/ralouphie/getallheaders)
+[](https://coveralls.io/r/ralouphie/getallheaders?branch=master)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+
+
+This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php).
+
+## Install
+
+```
+composer require ralouphie/getallheaders
+```
diff --git a/vendor/ralouphie/getallheaders/composer.json b/vendor/ralouphie/getallheaders/composer.json
new file mode 100755
index 0000000..5a0d595
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/composer.json
@@ -0,0 +1,21 @@
+{
+ "name": "ralouphie/getallheaders",
+ "description": "A polyfill for getallheaders.",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~3.7.0",
+ "satooshi/php-coveralls": ">=1.0"
+ },
+ "autoload": {
+ "files": ["src/getallheaders.php"]
+ }
+}
\ No newline at end of file
diff --git a/vendor/ralouphie/getallheaders/phpunit.xml b/vendor/ralouphie/getallheaders/phpunit.xml
new file mode 100755
index 0000000..7255b23
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/phpunit.xml
@@ -0,0 +1,22 @@
+<phpunit
+ bootstrap="vendor/autoload.php"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ strict="true">
+
+ <testsuite>
+ <directory>./tests</directory>
+ </testsuite>
+
+ <filter>
+ <whitelist>
+ <directory suffix=".php">src</directory>
+ </whitelist>
+ </filter>
+
+ <logging>
+ <log type="coverage-clover" target="build/logs/clover.xml"/>
+ </logging>
+
+</phpunit>
\ No newline at end of file
diff --git a/vendor/ralouphie/getallheaders/src/getallheaders.php b/vendor/ralouphie/getallheaders/src/getallheaders.php
new file mode 100755
index 0000000..c7285a5
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/src/getallheaders.php
@@ -0,0 +1,46 @@
+<?php
+
+if (!function_exists('getallheaders')) {
+
+ /**
+ * Get all HTTP header key/values as an associative array for the current request.
+ *
+ * @return string[string] The HTTP header key/value pairs.
+ */
+ function getallheaders()
+ {
+ $headers = array();
+
+ $copy_server = array(
+ 'CONTENT_TYPE' => 'Content-Type',
+ 'CONTENT_LENGTH' => 'Content-Length',
+ 'CONTENT_MD5' => 'Content-Md5',
+ );
+
+ foreach ($_SERVER as $key => $value) {
+ if (substr($key, 0, 5) === 'HTTP_') {
+ $key = substr($key, 5);
+ if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) {
+ $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))));
+ $headers[$key] = $value;
+ }
+ } elseif (isset($copy_server[$key])) {
+ $headers[$copy_server[$key]] = $value;
+ }
+ }
+
+ if (!isset($headers['Authorization'])) {
+ if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
+ $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
+ } elseif (isset($_SERVER['PHP_AUTH_USER'])) {
+ $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
+ $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass);
+ } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) {
+ $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST'];
+ }
+ }
+
+ return $headers;
+ }
+
+}
diff --git a/vendor/ralouphie/getallheaders/tests/GetAllHeadersTest.php b/vendor/ralouphie/getallheaders/tests/GetAllHeadersTest.php
new file mode 100755
index 0000000..8e3d179
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/tests/GetAllHeadersTest.php
@@ -0,0 +1,121 @@
+<?php
+
+class GetAllHeadersTest extends \PHPUnit_Framework_TestCase
+{
+
+ /**
+ * @dataProvider testWorksData
+ */
+ public function testWorks($test_type, $expected, $server)
+ {
+ foreach ($server as $key => $val) {
+ $_SERVER[$key] = $val;
+ }
+ $result = getallheaders();
+ $this->assertEquals($expected, $result, "Error testing $test_type works.");
+ }
+
+ public function testWorksData()
+ {
+ return array(
+ array(
+ 'normal case',
+ array(
+ 'Key-One' => 'foo',
+ 'Key-Two' => 'bar',
+ 'Another-Key-For-Testing' => 'baz'
+ ),
+ array(
+ 'HTTP_KEY_ONE' => 'foo',
+ 'HTTP_KEY_TWO' => 'bar',
+ 'HTTP_ANOTHER_KEY_FOR_TESTING' => 'baz'
+ )
+ ),
+ array(
+ 'Content-Type',
+ array(
+ 'Content-Type' => 'two'
+ ),
+ array(
+ 'HTTP_CONTENT_TYPE' => 'one',
+ 'CONTENT_TYPE' => 'two'
+ )
+ ),
+ array(
+ 'Content-Length',
+ array(
+ 'Content-Length' => '222'
+ ),
+ array(
+ 'CONTENT_LENGTH' => '222',
+ 'HTTP_CONTENT_LENGTH' => '111'
+ )
+ ),
+ array(
+ 'Content-Length (HTTP_CONTENT_LENGTH only)',
+ array(
+ 'Content-Length' => '111'
+ ),
+ array(
+ 'HTTP_CONTENT_LENGTH' => '111'
+ )
+ ),
+ array(
+ 'Content-MD5',
+ array(
+ 'Content-Md5' => 'aef123'
+ ),
+ array(
+ 'CONTENT_MD5' => 'aef123',
+ 'HTTP_CONTENT_MD5' => 'fea321'
+ )
+ ),
+ array(
+ 'Content-MD5 (HTTP_CONTENT_MD5 only)',
+ array(
+ 'Content-Md5' => 'f123'
+ ),
+ array(
+ 'HTTP_CONTENT_MD5' => 'f123'
+ )
+ ),
+ array(
+ 'Authorization (normal)',
+ array(
+ 'Authorization' => 'testing'
+ ),
+ array(
+ 'HTTP_AUTHORIZATION' => 'testing',
+ )
+ ),
+ array(
+ 'Authorization (redirect)',
+ array(
+ 'Authorization' => 'testing redirect'
+ ),
+ array(
+ 'REDIRECT_HTTP_AUTHORIZATION' => 'testing redirect',
+ )
+ ),
+ array(
+ 'Authorization (PHP_AUTH_USER + PHP_AUTH_PW)',
+ array(
+ 'Authorization' => 'Basic ' . base64_encode('foo:bar')
+ ),
+ array(
+ 'PHP_AUTH_USER' => 'foo',
+ 'PHP_AUTH_PW' => 'bar'
+ )
+ ),
+ array(
+ 'Authorization (PHP_AUTH_DIGEST)',
+ array(
+ 'Authorization' => 'example-digest'
+ ),
+ array(
+ 'PHP_AUTH_DIGEST' => 'example-digest'
+ )
+ )
+ );
+ }
+}
diff --git a/vendor/services.php b/vendor/services.php
new file mode 100755
index 0000000..23a761b
--- /dev/null
+++ b/vendor/services.php
@@ -0,0 +1,9 @@
+<?php
+// This file is automatically generated at:2023-04-12 09:28:46
+declare (strict_types = 1);
+return array (
+ 0 => 'think\\captcha\\CaptchaService',
+ 1 => 'think\\migration\\Service',
+ 2 => 'think\\app\\Service',
+ 3 => 'think\\queue\\Service',
+);
\ No newline at end of file
diff --git a/vendor/songshenzong/support/.gitignore b/vendor/songshenzong/support/.gitignore
new file mode 100755
index 0000000..e8144b2
--- /dev/null
+++ b/vendor/songshenzong/support/.gitignore
@@ -0,0 +1,7 @@
+.DS_Store
+.idea
+/vendor/
+/support.iml
+build
+composer.lock
+/.php_cs.cache
diff --git a/vendor/songshenzong/support/.travis.yml b/vendor/songshenzong/support/.travis.yml
new file mode 100755
index 0000000..5fc36f9
--- /dev/null
+++ b/vendor/songshenzong/support/.travis.yml
@@ -0,0 +1,22 @@
+language: php
+install: true
+php:
+ - 7.2
+before_script:
+ - composer install --no-interaction --prefer-source --dev
+ - curl -sSfL -o ~/.phpenv/versions/hhvm/bin/phpunit https://phar.phpunit.de/phpunit-5.7.phar
+script: phpunit
+before_deploy:
+ - git config --local user.email "i@songshenzong.com"
+ - git config --local user.name "Songshenzong"
+ - export GIT_TAG=$TRAVIS_BRANCH-0.1.$TRAVIS_BUILD_NUMBER
+ - git tag $GIT_TAG -a -m "Generated tag from TravisCI for build $TRAVIS_BUILD_NUMBER"
+deploy:
+ provider: releases
+ api_key:
+ secure: tMfMInav0NrKJUZw861pXhAfhioLm/f6dSP9fyNePOnUbbKAqubac6Lh6IbVLlM//tY+hF5p0Ln4q1XYn7JAd9PzO3tWkZC83HDIP9cbZnMKqXU5EaX2fuRwbxt82ISYXbKtwfRBtR2ZkTUSk1rh1TtjkXSJ5Wpv+An2GefF5jwPKeME6fqLsIlCBlReFFD+o36wJaS8vMIIsOsaVxmhZsjBpSBAX6YyV+bOGwgK6YTfPXjh3nz7JrJ1mx+XxFU60cMlpYHgw46FF9ZgJB03T+UtQswUTGDtHdSmVP4nentzuw90prO2w51fOK4cLj7c2Bx/9Wda4Cte8XO1g7sBoSuVaHDtn3qini8pSA1jvjx6GXojqk3NnV3PLA+BOJm7yrNWssrAQxPBm6PJsF7YSrX418MTzoMhvz8TBmc6R9S5r7kyeaB4pY0ZP6O/04C59U/79sVbQO184FkiIX5Ezf7wRsaKCuxa/FavyAdfXG4WmDQLJmvLUa1bzvRd+O94/I9eoh+1/YhN7sPlXctHjdWKIdy5StkB43gP7r+DorBWF3rHrtmGTjAk17SJxyTsiqDmLgNTTeu5+f9j/40Jz65fiN1QFLTQBVXKEWcaGX1kB85GRpa4mQebI7TzqGpGHPkP+3Fc7Fjw8Jc6a06LTDPJVSMSSChi8j/IKYyMb0k=
+ file:
+ - "README.md"
+ on:
+ repo: songshenzong/support
+ tags: true
diff --git a/vendor/songshenzong/support/CHANGELOG.md b/vendor/songshenzong/support/CHANGELOG.md
new file mode 100755
index 0000000..ab009bf
--- /dev/null
+++ b/vendor/songshenzong/support/CHANGELOG.md
@@ -0,0 +1,24 @@
+# ChangeLog
+
+---
+
+## 2.0.5 - 2019-08-30
+- Improved `Time`.
+
+## 2.0.4 - 2019-08-30
+- Added `Time`.
+
+## 2.0.3 - 2019-08-27
+- Added `Env` for `OS`.
+
+## 2.0.2 - 2019-08-14
+- Added `get` for `Arrays`.
+
+## 2.0.1 - 2019-06-12
+- Added `Arrays`.
+
+## 2.0.0 - 2019-06-12
+- Improved Design.
+
+## 1.0.0 - 2017-03-07
+- Released
diff --git a/vendor/songshenzong/support/LICENSE.txt b/vendor/songshenzong/support/LICENSE.txt
new file mode 100755
index 0000000..6bdfed4
--- /dev/null
+++ b/vendor/songshenzong/support/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (C) 2019 Songshenzong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/songshenzong/support/README.md b/vendor/songshenzong/support/README.md
new file mode 100755
index 0000000..ca217d4
--- /dev/null
+++ b/vendor/songshenzong/support/README.md
@@ -0,0 +1,32 @@
+[][travis]
+[][packagist]
+[][packagist]
+[][packagist]
+[][packagist]
+
+
+## Installation
+
+Require this package with composer:
+
+
+```shell
+composer require songshenzong/support
+```
+
+## Quick Examples
+
+Please refer to our extensive [Wiki documentation](https://github.com/songshenzong/support/wiki) for more information.
+
+
+## Support
+
+For answers you may not find in the Wiki, avoid posting issues. Feel free to ask for support on Songshenzong.com
+
+
+## License
+
+This package is licensed under the [BSD 3-Clause license](http://opensource.org/licenses/BSD-3-Clause).
+
+[packagist]: https://packagist.org/packages/songshenzong/support
+[travis]: https://travis-ci.org/songshenzong/support
diff --git a/vendor/songshenzong/support/composer.json b/vendor/songshenzong/support/composer.json
new file mode 100755
index 0000000..61da3c7
--- /dev/null
+++ b/vendor/songshenzong/support/composer.json
@@ -0,0 +1,60 @@
+{
+ "name": "songshenzong/support",
+ "description": "The Songshenzong Support package.",
+ "keywords": [
+ "support",
+ "laravel",
+ "web",
+ "Tools"
+ ],
+ "license": "MIT",
+ "homepage": "http://songshenzong.com",
+ "support": {
+ "issues": "https://github.com/songshenzong/support/issues",
+ "source": "https://github.com/songshenzong/support"
+ },
+ "authors": [
+ {
+ "name": "Songshenzong",
+ "email": "i@songshenzong.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.5",
+ "ext-xml": "*",
+ "ext-json": "*",
+ "ext-simplexml": "*",
+ "danielstjules/stringy": "^3.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35|^5.4.3",
+ "laravel/framework": "^5.8"
+ },
+ "autoload": {
+ "psr-4": {
+ "Songshenzong\\Support\\": "src/"
+ },
+ "files": [
+ "src/StringsHelpers.php",
+ "src/BashEchoHelpers.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Songshenzong\\Support\\Test\\": "tests/"
+ }
+ },
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Songshenzong\\Support\\StringsServiceProvider"
+ ],
+ "aliases": {
+ "Strings": "Songshenzong\\Support\\StringsFacade"
+ }
+ }
+ },
+ "scripts": {
+ "fixer": "php-cs-fixer fix ./"
+ }
+}
diff --git a/vendor/songshenzong/support/phpunit.xml b/vendor/songshenzong/support/phpunit.xml
new file mode 100755
index 0000000..ee3926d
--- /dev/null
+++ b/vendor/songshenzong/support/phpunit.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="./tests/bootstrap.php"
+ colors="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ testSuiteLoaderFile="phpunit/src/Runner/StandardTestSuiteLoader.php">
+
+ <testsuites>
+ <testsuite name="All">
+ <directory>tests</directory>
+ </testsuite>
+ </testsuites>
+
+ <groups>
+ <exclude>
+ <group>integration</group>
+ </exclude>
+ </groups>
+
+ <logging>
+ <log type="junit" target="build/junit.xml"/>
+ <log type="coverage-clover" target="build/coverage/clover.xml"/>
+ </logging>
+
+ <filter>
+ <whitelist>
+ <directory suffix=".php">./src</directory>
+ <exclude>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/vendor/songshenzong/support/src/Arrays.php b/vendor/songshenzong/support/src/Arrays.php
new file mode 100755
index 0000000..910e2c7
--- /dev/null
+++ b/vendor/songshenzong/support/src/Arrays.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Songshenzong\Support;
+
+use InvalidArgumentException;
+
+/**
+ * Class Arrays
+ *
+ * @package Songshenzong\Support
+ */
+class Arrays
+{
+ /**
+ * @param array $arrays
+ *
+ * @return array
+ */
+ public static function merge(array $arrays)
+ {
+ $result = [];
+ foreach ($arrays as $array) {
+ foreach ($array as $key => $value) {
+ if (is_int($key)) {
+ $result[] = $value;
+ continue;
+ }
+
+ if (isset($result[$key]) && is_array($result[$key])) {
+ $result[$key] = self::merge(
+ [$result[$key], $value]
+ );
+ continue;
+ }
+
+ $result[$key] = $value;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns item from array or $default if item is not set.
+ *
+ * @param array $array
+ * @param string|int|array $key one or more keys
+ * @param null $default
+ *
+ * @return mixed
+ */
+ public static function get(array $array, $key, $default = null)
+ {
+ foreach (is_array($key) ? $key : [$key] as $k) {
+ if (is_array($array) && array_key_exists($k, $array)) {
+ $array = $array[$k];
+ } else {
+ if (func_num_args() < 3) {
+ throw new InvalidArgumentException("Missing item '$k'.");
+ }
+
+ return $default;
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * @param array $system_version
+ */
+ public static function versionCompare(array &$system_version)
+ {
+ for ($i = 0; $i < count($system_version) - 1; $i++) {
+ for ($j = 0; $j < count($system_version) - 1 - $i; $j++) {
+ if (version_compare($system_version[$j], $system_version[$j + 1], '<')) {
+ $tmp = $system_version[$j];
+ $system_version[$j] = $system_version[$j + 1];
+ $system_version[$j + 1] = $tmp;
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/songshenzong/support/src/BashEcho.php b/vendor/songshenzong/support/src/BashEcho.php
new file mode 100755
index 0000000..35ca0d5
--- /dev/null
+++ b/vendor/songshenzong/support/src/BashEcho.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Songshenzong\Support;
+
+/**
+ * Class BashEcho
+ *
+ * @package Songshenzong\Support
+ */
+class BashEcho
+{
+
+ /**
+ * @param string $string
+ */
+ public static function echoRed($string)
+ {
+ $cmd = "echo -ne \"\033[31m" . $string . " \033[0m\n\"";
+ $a = exec($cmd);
+ print $a . PHP_EOL;
+ }
+
+ /**
+ * @param string $string
+ */
+ public static function echoCyan($string)
+ {
+ $cmd = "printf \"\033[35m" . $string . "\033[0m\n\"";
+ $a = exec($cmd);
+ print $a . PHP_EOL;
+ }
+
+ /**
+ * @param string $string
+ */
+ public static function echoGreen($string)
+ {
+ $cmd = "printf \"\033[32m" . $string . "\033[0m\n\"";
+ $a = exec($cmd);
+ print $a . PHP_EOL;
+ }
+
+ /**
+ * @param string $string
+ */
+ public static function echoBrown($string)
+ {
+ $cmd = "printf \"\033[33m" . $string . "\033[0m\n\"";
+ $a = exec($cmd);
+ print $a . PHP_EOL;
+ }
+
+ /**
+ * @param string $string
+ */
+ public static function echoBlue($string)
+ {
+ $cmd = "printf \"\033[34m" . $string . "\033[0m\n\"";
+ $a = exec($cmd);
+ print $a . PHP_EOL;
+ }
+
+ /**
+ * @param string $string
+ */
+ public static function echoPurple($string)
+ {
+ $cmd = "printf \"\033[35m" . $string . "\033[0m\n\"";
+ $a = exec($cmd);
+ print $a . PHP_EOL;
+ }
+}
diff --git a/vendor/songshenzong/support/src/BashEchoHelpers.php b/vendor/songshenzong/support/src/BashEchoHelpers.php
new file mode 100755
index 0000000..372d006
--- /dev/null
+++ b/vendor/songshenzong/support/src/BashEchoHelpers.php
@@ -0,0 +1,64 @@
+<?php
+
+use Songshenzong\Support\BashEcho;
+
+if (!function_exists('echoRed')) {
+ /**
+ * @param $string
+ */
+ function echoRed($string)
+ {
+ BashEcho::echoRed($string);
+ }
+}
+
+if (!function_exists('echoGreen')) {
+ /**
+ * @param $string
+ */
+ function echoGreen($string)
+ {
+ BashEcho::echoGreen($string);
+ }
+}
+
+if (!function_exists('echoBrown')) {
+ /**
+ * @param $string
+ */
+ function echoBrown($string)
+ {
+ BashEcho::echoBrown($string);
+ }
+}
+
+if (!function_exists('echoBlue')) {
+
+ /**
+ * @param $string
+ */
+ function echoBlue($string)
+ {
+ BashEcho::echoBlue($string);
+ }
+}
+
+if (!function_exists('echoPurple')) {
+ /**
+ * @param $string
+ */
+ function echoPurple($string)
+ {
+ BashEcho::echoPurple($string);
+ }
+}
+
+if (!function_exists('echoCyan')) {
+ /**
+ * @param $string
+ */
+ function echoCyan($string)
+ {
+ BashEcho::echoCyan($string);
+ }
+}
diff --git a/vendor/songshenzong/support/src/Env.php b/vendor/songshenzong/support/src/Env.php
new file mode 100755
index 0000000..de9002a
--- /dev/null
+++ b/vendor/songshenzong/support/src/Env.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace Songshenzong\Support;
+
+use Closure;
+
+/**
+ * Class Env
+ *
+ * @package Songshenzong\Support
+ */
+class Env
+{
+
+ /**
+ * Gets the value of an environment variable.
+ *
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public static function env($key, $default = null)
+ {
+ $value = getenv($key);
+
+ if ($value === false) {
+ return self::value($default);
+ }
+
+ if (self::envSubstr($value)) {
+ return substr($value, 1, -1);
+ }
+
+ return self::envConversion($value);
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return bool|mixed
+ */
+ public static function envNotEmpty($key)
+ {
+ $value = self::env($key, false);
+ if ($value) {
+ return $value;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return the default value of the given value.
+ *
+ * @param mixed $value
+ *
+ * @return mixed
+ */
+ public static function value($value)
+ {
+ return $value instanceof Closure ? $value() : $value;
+ }
+
+ /**
+ * @param $value
+ *
+ * @return bool
+ */
+ public static function envSubstr($value)
+ {
+ return ($valueLength = strlen($value)) > 1
+ && strpos($value, '"') === 0
+ && $value[$valueLength - 1] === '"';
+ }
+
+ /**
+ * @param $value
+ *
+ * @return bool|string|null
+ */
+ public static function envConversion($value)
+ {
+ $key = strtolower($value);
+
+ if ($key === 'null' || $key === '(null)') {
+ return null;
+ }
+
+ $list = [
+ 'true' => true,
+ '(true)' => true,
+ 'false' => false,
+ '(false)' => false,
+ 'empty' => '',
+ '(empty)' => '',
+ ];
+
+ return isset($list[$key]) ? $list[$key] : $value;
+ }
+}
diff --git a/vendor/songshenzong/support/src/OS.php b/vendor/songshenzong/support/src/OS.php
new file mode 100755
index 0000000..7af044e
--- /dev/null
+++ b/vendor/songshenzong/support/src/OS.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Songshenzong\Support;
+
+use Stringy\Stringy;
+
+/**
+ * Class OS
+ *
+ * @package Songshenzong\Support
+ */
+class OS
+{
+ /**
+ * @param $filename
+ *
+ * @return bool
+ */
+ public static function inOpenBasedir($filename)
+ {
+ $open_basedir = ini_get('open_basedir');
+ if (!$open_basedir) {
+ return true;
+ }
+
+ $dirs = explode(PATH_SEPARATOR, $open_basedir);
+
+ return empty($dirs) || self::inDir($filename, $dirs);
+ }
+
+ /**
+ * @param string $filename
+ * @param array $dirs
+ *
+ * @return bool
+ */
+ public static function inDir($filename, array $dirs)
+ {
+ foreach ($dirs as $dir) {
+ if (!Stringy::create($dir)->endsWith(DIRECTORY_SEPARATOR)) {
+ $dir .= DIRECTORY_SEPARATOR;
+ }
+
+ if (0 === strpos($filename, $dir)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return bool
+ */
+ public static function isWindows()
+ {
+ return PATH_SEPARATOR === ';';
+ }
+
+ /**
+ * Gets the environment's HOME directory.
+ *
+ * @return null|string
+ */
+ public static function getHomeDirectory()
+ {
+ if (getenv('HOME')) {
+ return getenv('HOME');
+ }
+
+ return (getenv('HOMEDRIVE') && getenv('HOMEPATH'))
+ ? getenv('HOMEDRIVE') . getenv('HOMEPATH')
+ : null;
+ }
+}
diff --git a/vendor/songshenzong/support/src/Strings.php b/vendor/songshenzong/support/src/Strings.php
new file mode 100755
index 0000000..a5cb566
--- /dev/null
+++ b/vendor/songshenzong/support/src/Strings.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Songshenzong\Support;
+
+use Songshenzong\Support\Traits\Str;
+use Songshenzong\Support\Traits\Uri;
+
+/**
+ * Class Strings
+ *
+ * @package Songshenzong\Support
+ */
+class Strings
+{
+ use Str, Uri;
+}
diff --git a/vendor/songshenzong/support/src/StringsFacade.php b/vendor/songshenzong/support/src/StringsFacade.php
new file mode 100755
index 0000000..3b8d1d5
--- /dev/null
+++ b/vendor/songshenzong/support/src/StringsFacade.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Songshenzong\Support;
+
+use Illuminate\Support\Facades\Facade;
+
+/**
+ * Class StringsFacade
+ *
+ * @package Songshenzong\Support
+ */
+class StringsFacade extends Facade
+{
+ /**
+ * @return string
+ */
+ protected static function getFacadeAccessor()
+ {
+ return 'Strings';
+ }
+}
diff --git a/vendor/songshenzong/support/src/StringsHelpers.php b/vendor/songshenzong/support/src/StringsHelpers.php
new file mode 100755
index 0000000..bc4b13d
--- /dev/null
+++ b/vendor/songshenzong/support/src/StringsHelpers.php
@@ -0,0 +1,13 @@
+<?php
+
+use Songshenzong\Support\Strings;
+
+if (!function_exists('strings')) {
+ /**
+ * @return Strings
+ */
+ function strings()
+ {
+ return new Strings;
+ }
+}
diff --git a/vendor/songshenzong/support/src/StringsServiceProvider.php b/vendor/songshenzong/support/src/StringsServiceProvider.php
new file mode 100755
index 0000000..1145f47
--- /dev/null
+++ b/vendor/songshenzong/support/src/StringsServiceProvider.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Songshenzong\Support;
+
+use Illuminate\Support\ServiceProvider;
+
+/**
+ * Class StringsServiceProvider
+ *
+ * @package Songshenzong\Support
+ */
+class StringsServiceProvider extends ServiceProvider
+{
+ /**
+ * Indicates if loading of the provider is deferred.
+ *
+ * @var bool
+ */
+ protected $defer = false;
+
+ /**
+ * Register the service provider.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ $this->app->singleton(
+ 'Strings',
+ static function () {
+ return new Strings();
+ }
+ );
+
+ $this->app->alias('Strings', StringsFacade::class);
+ }
+}
diff --git a/vendor/songshenzong/support/src/Time.php b/vendor/songshenzong/support/src/Time.php
new file mode 100755
index 0000000..82bd1a1
--- /dev/null
+++ b/vendor/songshenzong/support/src/Time.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Songshenzong\Support;
+
+/**
+ * Class Time
+ *
+ * @package Songshenzong\Support
+ */
+class Time
+{
+
+ /**
+ * @param int|string $begin_time
+ * @param int|string $end_time
+ *
+ * @return array
+ */
+ public static function dates($begin_time, $end_time = null)
+ {
+ if ($end_time === null) {
+ $end_time = time();
+ }
+
+ // Y-m-d to timestamp
+ if (!is_int($begin_time)) {
+ $begin_time = strtotime($begin_time);
+ }
+
+ // Y-m-d to timestamp
+ if (!is_int($end_time)) {
+ $end_time = strtotime($end_time);
+ }
+
+ $dates = [];
+
+ for ($start = $begin_time; $start <= $end_time; $start += 86400) {
+ $dates[] = date('Y-m-d', $start);
+ }
+
+ return $dates;
+ }
+
+ /**
+ * Format Time.
+ *
+ * @param string $time_string
+ *
+ * @return false|string
+ */
+ public static function formatTime($time_string)
+ {
+ $time = time() - strtotime($time_string);
+ $time1 = time() - strtotime('today');
+ if ($time < 60) {
+ $str = '刚刚';
+ } elseif ($time < 3600) {
+ $min = floor($time / 60);
+ $str = $min . '分钟前';
+ } elseif ($time < 24 * 3600) {
+ $min = floor($time / (60 * 60));
+ $str = $min . '小时前';
+ } elseif ($time > $time1 && $time < 7 * 24 * 3600) {
+ $min = floor($time / (3600 * 24));
+ $str = $min . '天前';
+ } else {
+ $str = date('n月j日 H:i', strtotime($time_string));
+ }
+
+ return $str;
+ }
+}
diff --git a/vendor/songshenzong/support/src/Traits/Str.php b/vendor/songshenzong/support/src/Traits/Str.php
new file mode 100755
index 0000000..29f44a3
--- /dev/null
+++ b/vendor/songshenzong/support/src/Traits/Str.php
@@ -0,0 +1,295 @@
+<?php
+
+namespace Songshenzong\Support\Traits;
+
+use stdClass;
+use SimpleXMLElement;
+
+/**
+ * Trait Str
+ *
+ * @package Songshenzong\Support\Traits
+ */
+trait Str
+{
+
+ /**
+ * @param string $string
+ *
+ * @return array
+ */
+ public static function toArray($string = '')
+ {
+ if ($string === '') {
+ return [];
+ }
+
+ if (self::isJson($string)) {
+ return json_decode($string, true);
+ }
+
+ if (self::isXml($string)) {
+ return self::xmlToArray($string);
+ }
+
+ $unserialize = self::unserialize($string);
+ if ($unserialize === false) {
+ return [];
+ }
+
+ if (\is_array($unserialize)) {
+ return $unserialize;
+ }
+
+ return [];
+ }
+
+ /**
+ * @param string $string
+ *
+ * @return bool
+ */
+ public static function isJson($string = '')
+ {
+ if ($string === '') {
+ return false;
+ }
+
+ \json_decode($string, true);
+ if (\json_last_error()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $string
+ *
+ * @return bool
+ */
+ public static function isXml($string = '')
+ {
+ if ($string === '') {
+ return false;
+ }
+
+ $xml_parser = xml_parser_create();
+ if (!xml_parse($xml_parser, $string, true)) {
+ xml_parser_free($xml_parser);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $string
+ *
+ * @return array
+ */
+ public static function xmlToArray($string)
+ {
+ return json_decode(json_encode(simplexml_load_string($string)), true);
+ }
+
+ /**
+ * @param string $serialized
+ *
+ * @return mixed
+ */
+ public static function unserialize($serialized)
+ {
+ // Set Handle
+ set_error_handler(
+ function () {
+ },
+ E_ALL
+ );
+ $result = unserialize((string)$serialized);
+ // Restores the previous error handler function
+ restore_error_handler();
+ if ($result === false) {
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param string $string
+ *
+ * @return stdClass|SimpleXMLElement
+ *
+ */
+ public static function toObject($string = '')
+ {
+ if ($string === '') {
+ return new stdClass();
+ }
+
+ if (self::isJson($string)) {
+ return json_decode($string);
+ }
+
+ if (self::isXml($string)) {
+ return self::xmlToObject($string);
+ }
+
+ $unserialize = self::unserialize($string);
+ if ($unserialize === false) {
+ return new stdClass();
+ }
+
+ if (\is_object($unserialize)) {
+ return $unserialize;
+ }
+
+ return new stdClass();
+ }
+
+ /**
+ * @param string $string
+ *
+ * @return SimpleXMLElement
+ */
+ public static function xmlToObject($string)
+ {
+ return simplexml_load_string($string);
+ }
+
+ /**
+ * @param string $string
+ *
+ * @return bool
+ */
+ public static function isSerialized($string)
+ {
+ // Set Handle
+ set_error_handler(
+ function () {
+ },
+ E_ALL
+ );
+ $result = unserialize($string);
+ // Restores the previous error handler function
+ restore_error_handler();
+
+ return !($result === false);
+ }
+
+ /**
+ * @param string $string
+ *
+ * @return string
+ */
+ public static function filter($string)
+ {
+ $filter = [
+ "\n",
+ '`',
+ '·',
+ '~',
+ '!',
+ '!',
+ '@',
+ '#',
+ '$',
+ '¥',
+ '%',
+ '^',
+ '……',
+ '&',
+ '*',
+ '(',
+ ')',
+ '(',
+ ')',
+ '-',
+ '_',
+ '——',
+ '+',
+ '=',
+ '|',
+ '\\',
+ '[',
+ ']',
+ '【',
+ '】',
+ '{',
+ '}',
+ ';',
+ ';',
+ ':',
+ ':',
+ '\'',
+ '"',
+ '“',
+ '”',
+ ',',
+ ',',
+ '<',
+ '>',
+ '《',
+ '》',
+ '.',
+ '。',
+ '/',
+ '、',
+ '?',
+ '?',
+ ';',
+ 'nbsp',
+ ];
+
+ $str = str_replace($filter, '', $string);
+
+ return trim($str);
+ }
+
+ /**
+ * @param string $string
+ *
+ * @return string
+ */
+ public static function trim($string)
+ {
+ $filter = [
+ "\0",
+ "\n",
+ "\t",
+ "\x0B",
+ "\r",
+ ' ',
+ ];
+
+ $str = str_replace($filter, '', $string);
+
+ return trim($str);
+ }
+
+ /**
+ * Is Set and Not Empty.
+ *
+ * @param $value
+ *
+ * @return bool
+ */
+ public static function isSetAndNotEmpty($value)
+ {
+ return isset($value) && !empty($value);
+ }
+
+ /**
+ * Is Set and Not Empty and Not Null.
+ *
+ * @param $value
+ *
+ * @return bool
+ */
+ public static function isSetAndNotEmptyAndNotNull($value)
+ {
+ return isset($value) && !empty($value) && $value !== 'null';
+ }
+}
diff --git a/vendor/songshenzong/support/src/Traits/Uri.php b/vendor/songshenzong/support/src/Traits/Uri.php
new file mode 100755
index 0000000..02e7566
--- /dev/null
+++ b/vendor/songshenzong/support/src/Traits/Uri.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Songshenzong\Support\Traits;
+
+/**
+ * Trait Uri
+ *
+ * @package Songshenzong\Support\Traits
+ */
+trait Uri
+{
+
+ /**
+ * @param string $uri
+ * @param array $parameters
+ * @param bool $appendsCurrentUri
+ *
+ * @return string
+ */
+ public static function uri($uri, array $parameters = [], $appendsCurrentUri = false)
+ {
+ $uriComponents = parse_url($uri);
+
+ if (isset($uriComponents['query'])) {
+ \parse_str($uriComponents['query'], $uriComponents['query']);
+ } else {
+ $uriComponents['query'] = [];
+ }
+
+ if ($appendsCurrentUri) {
+ $newQuery = $parameters + $_GET + $uriComponents['query'];
+ } else {
+ $newQuery = $parameters + $uriComponents['query'];
+ }
+
+ $newUriComponents = isset($uriComponents['scheme']) ? $uriComponents['scheme'] . '://' : '';
+ $newUriComponents .= isset($uriComponents['host']) ? $uriComponents['host'] : '';
+ $newUriComponents .= isset($uriComponents['port']) ? ':' . $uriComponents['port'] : '';
+ $newUriComponents .= isset($uriComponents['path']) ? $uriComponents['path'] : '';
+ $newUriComponents .= '?' . http_build_query($newQuery);
+
+ return $newUriComponents;
+ }
+
+ /**
+ * @param $host
+ *
+ * @return bool
+ */
+ public static function host($host)
+ {
+ if (!isset($_SERVER['HTTP_HOST'])) {
+ return false;
+ }
+ $lower = \strtolower($_SERVER['HTTP_HOST']);
+
+ if (\is_string($host)) {
+ return $host === $lower;
+ }
+
+ if (\is_array($host)) {
+ foreach ($host as $item) {
+ if ($item === $lower) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/songshenzong/support/tests/Core/EchoBashTest.php b/vendor/songshenzong/support/tests/Core/EchoBashTest.php
new file mode 100755
index 0000000..7cefe0a
--- /dev/null
+++ b/vendor/songshenzong/support/tests/Core/EchoBashTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Songshenzong\Support\Test\Core;
+
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Class EchoBashTest
+ *
+ * @package Songshenzong\Support\Test\Core
+ */
+class EchoBashTest extends TestCase
+{
+
+ /**
+ * @throws \PHPUnit\Framework\ExpectationFailedException
+ * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
+ */
+ public function testIsJson()
+ {
+ echoRed('echo Red');
+ echoPurple('echo Purple');
+ echoGreen('echo Green');
+ echoCyan('echo Cyan');
+ echoBrown('echo Brown');
+ echoBlue('echo Blue');
+ $this->assertEquals(true, true);
+ }
+}
diff --git a/vendor/songshenzong/support/tests/Core/StrTest.php b/vendor/songshenzong/support/tests/Core/StrTest.php
new file mode 100755
index 0000000..d85e31f
--- /dev/null
+++ b/vendor/songshenzong/support/tests/Core/StrTest.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Songshenzong\Support\Test\Core;
+
+use stdClass;
+use PHPUnit\Framework\TestCase;
+use Songshenzong\Support\Strings;
+
+/**
+ * Class StrTest
+ *
+ * @package Songshenzong\Support\Test\Core
+ */
+class StrTest extends TestCase
+{
+ /**
+ * @var string
+ */
+ protected $xmlString = <<<ETO
+<?xml version="1.0" encoding="UTF-8"?>
+<books>
+ <book>
+ <author>Jack Herrington</author>
+ <title>PHP Hacks</title>
+ <publisher>O'Reilly</publisher>
+ </book>
+ <book>
+ <author>Jack Herrington</author>
+ <title>Podcasting Hacks</title>
+ <publisher>O'Reilly</publisher>
+ </book>
+</books>
+ETO;
+
+ /**
+ * @var string
+ */
+ protected $jsonString = '{ "tools": [ { "name":"css format" , "site":"https://songshenzong.com" }, { "name":"pwd check" , "site":"https://songshenzong.com" } ] }';
+
+ /**
+ * @var array
+ */
+ protected $simpleJsonArray = ['json' => true];
+
+ /**
+ * @var string
+ */
+ protected $simpleJsonString = '{"json":true}';
+
+ /**
+ * @throws \PHPUnit\Framework\ExpectationFailedException
+ * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
+ */
+ public function testIsJson()
+ {
+ $this->assertEquals(false, Strings::isJson());
+ $this->assertEquals(false, Strings::isJson(''));
+ $this->assertEquals(false, Strings::isJson($this->xmlString));
+ $this->assertEquals(true, Strings::isJson($this->simpleJsonString));
+ $this->assertEquals(true, Strings::isJson($this->jsonString));
+ }
+
+ /**
+ * @throws \PHPUnit\Framework\ExpectationFailedException
+ * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
+ */
+ public function testIsXml()
+ {
+ $this->assertEquals(false, Strings::isXml(''));
+ $this->assertEquals(false, Strings::isXml());
+ $this->assertEquals(false, Strings::isXml($this->jsonString));
+ $this->assertEquals(true, Strings::isXml($this->xmlString));
+ $this->assertEquals(false, Strings::isXml($this->simpleJsonString));
+ }
+
+ /**
+ * @throws \PHPUnit\Framework\ExpectationFailedException
+ * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
+ */
+ public function testToArray()
+ {
+ $this->assertEquals($this->simpleJsonArray, Strings::toArray($this->simpleJsonString));
+ $this->assertEquals([], Strings::toArray());
+ $this->assertEquals([], Strings::toArray(''));
+ }
+
+ /**
+ * @throws \PHPUnit\Framework\ExpectationFailedException
+ * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
+ */
+ public function testToObject()
+ {
+ $this->assertEquals(true, Strings::toObject($this->simpleJsonString)->json);
+ $this->assertObjectHasAttribute('book', Strings::toObject($this->xmlString));
+ $this->assertEquals(new stdClass(), Strings::toObject());
+ $this->assertEquals(new stdClass(), Strings::toObject(''));
+ }
+
+ /**
+ * @throws \PHPUnit\Framework\ExpectationFailedException
+ * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
+ */
+ public function testXmlToArray()
+ {
+ $this->assertArrayHasKey('book', Strings::xmlToArray($this->xmlString));
+ $this->assertObjectHasAttribute('book', Strings::xmlToObject($this->xmlString));
+ }
+}
diff --git a/vendor/songshenzong/support/tests/OSTest.php b/vendor/songshenzong/support/tests/OSTest.php
new file mode 100755
index 0000000..301a45c
--- /dev/null
+++ b/vendor/songshenzong/support/tests/OSTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Songshenzong\Support\Test\Core;
+
+use ReflectionClass;
+use ReflectionException;
+use Songshenzong\Support\OS;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Class OSTest
+ *
+ * @package Songshenzong\Support\Test\Core
+ */
+class OSTest extends TestCase
+{
+
+ /**
+ * @throws ReflectionException
+ */
+ public function testGetsHomeDirectoryForWindowsUser()
+ {
+ putenv('HOME=');
+ putenv('HOMEDRIVE=C:');
+ putenv('HOMEPATH=\\Users\\Support');
+ $ref = new ReflectionClass(OS::class);
+ $method = $ref->getMethod('getHomeDirectory');
+ $method->setAccessible(true);
+ $this->assertEquals('C:\\Users\\Support', $method->invoke(null));
+ }
+
+ /**
+ * @depends testGetsHomeDirectoryForWindowsUser
+ * @throws ReflectionException
+ */
+ public function testGetsHomeDirectoryForLinuxUser()
+ {
+ putenv('HOME=/root');
+ putenv('HOMEDRIVE=');
+ putenv('HOMEPATH=');
+ $ref = new ReflectionClass(OS::class);
+ $method = $ref->getMethod('getHomeDirectory');
+ $method->setAccessible(true);
+ $this->assertEquals('/root', $method->invoke(null));
+ }
+}
diff --git a/vendor/songshenzong/support/tests/TimeTest.php b/vendor/songshenzong/support/tests/TimeTest.php
new file mode 100755
index 0000000..99610c3
--- /dev/null
+++ b/vendor/songshenzong/support/tests/TimeTest.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Songshenzong\Support\Test\Core;
+
+use Songshenzong\Support\Time;
+use PHPUnit\Framework\TestCase;
+
+class TimeTest extends TestCase
+{
+ public function testDates()
+ {
+ $expect = [
+ '2019-09-10',
+ '2019-09-11',
+ '2019-09-12',
+ ];
+
+ self::assertEquals($expect, Time::dates('2019-09-10', '2019-09-12'));
+
+ self::assertEquals($expect, Time::dates(strtotime('2019-09-10'), strtotime('2019-09-12')));
+ }
+}
diff --git a/vendor/songshenzong/support/tests/bootstrap.php b/vendor/songshenzong/support/tests/bootstrap.php
new file mode 100755
index 0000000..fefd973
--- /dev/null
+++ b/vendor/songshenzong/support/tests/bootstrap.php
@@ -0,0 +1,11 @@
+<?php
+
+error_reporting(E_ALL);
+ini_set('display_errors', '1');
+
+if (!ini_get('date.timezone')) {
+ date_default_timezone_set('GMT');
+}
+
+$loader = require dirname(__DIR__) . '/vendor/autoload.php';
+$loader->add('Songshenzong\\Support\\Test\\', __DIR__);
diff --git a/vendor/spatie/macroable/.styleci.yml b/vendor/spatie/macroable/.styleci.yml
new file mode 100755
index 0000000..f4d3cbc
--- /dev/null
+++ b/vendor/spatie/macroable/.styleci.yml
@@ -0,0 +1,4 @@
+preset: laravel
+
+disabled:
+ - single_class_element_per_statement
diff --git a/vendor/spatie/macroable/CHANGELOG.md b/vendor/spatie/macroable/CHANGELOG.md
new file mode 100755
index 0000000..2afae93
--- /dev/null
+++ b/vendor/spatie/macroable/CHANGELOG.md
@@ -0,0 +1,7 @@
+# Changelog
+
+All notable changes to `macroable` will be documented in this file
+
+## 1.0.0 - 2017-09-16
+
+- initial release
diff --git a/vendor/spatie/macroable/CONTRIBUTING.md b/vendor/spatie/macroable/CONTRIBUTING.md
new file mode 100755
index 0000000..4da74e3
--- /dev/null
+++ b/vendor/spatie/macroable/CONTRIBUTING.md
@@ -0,0 +1,55 @@
+# Contributing
+
+Contributions are **welcome** and will be fully **credited**.
+
+Please read and understand the contribution guide before creating an issue or pull request.
+
+## Etiquette
+
+This project is open source, and as such, the maintainers give their free time to build and maintain the source code
+held within. They make the code freely available in the hope that it will be of use to other developers. It would be
+extremely unfair for them to suffer abuse or anger for their hard work.
+
+Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
+world that developers are civilized and selfless people.
+
+It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
+quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.
+
+## Viability
+
+When requesting or submitting new features, first consider whether it might be useful to others. Open
+source projects are used by many developers, who may have entirely different needs to your own. Think about
+whether or not your feature is likely to be used by other users of the project.
+
+## Procedure
+
+Before filing an issue:
+
+- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
+- Check to make sure your feature suggestion isn't already present within the project.
+- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
+- Check the pull requests tab to ensure that the feature isn't already in progress.
+
+Before submitting a pull request:
+
+- Check the codebase to ensure that your feature doesn't already exist.
+- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.
+
+## Requirements
+
+If the project maintainer has any additional requirements, you will find them listed here.
+
+- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
+
+- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
+
+- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
+
+- **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
+
+- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
+
+- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
+
+**Happy coding**!
diff --git a/vendor/spatie/macroable/LICENSE.md b/vendor/spatie/macroable/LICENSE.md
new file mode 100755
index 0000000..0b32f5f
--- /dev/null
+++ b/vendor/spatie/macroable/LICENSE.md
@@ -0,0 +1,21 @@
+# The MIT License (MIT)
+
+Copyright (c) Spatie bvba <info@spatie.be>
+
+> Permission is hereby granted, free of charge, to any person obtaining a copy
+> of this software and associated documentation files (the "Software"), to deal
+> in the Software without restriction, including without limitation the rights
+> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+> copies of the Software, and to permit persons to whom the Software is
+> furnished to do so, subject to the following conditions:
+>
+> The above copyright notice and this permission notice shall be included in
+> all copies or substantial portions of the Software.
+>
+> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+> THE SOFTWARE.
diff --git a/vendor/spatie/macroable/README.md b/vendor/spatie/macroable/README.md
new file mode 100755
index 0000000..ed24e2b
--- /dev/null
+++ b/vendor/spatie/macroable/README.md
@@ -0,0 +1,130 @@
+# A trait to dynamically add methods to a class
+
+[](https://packagist.org/packages/spatie/macroable)
+[](https://travis-ci.org/spatie/macroable)
+[](https://insight.sensiolabs.com/projects/c3dbed0e-9794-4c54-b40c-ccaf5a1394de)
+[](https://scrutinizer-ci.com/g/spatie/macroable)
+[](https://packagist.org/packages/spatie/macroable)
+
+This package provides a trait that, when applied to a class, makes it possible to add methods to that class at runtime.
+
+Here's a quick example:
+
+```php
+$myClass = new class() {
+ use Spatie\Macroable\Macroable;
+};
+
+$myClass::macro('concatenate', function(... $strings) {
+ return implode('-', $strings);
+};
+
+$myClass->concatenate('one', 'two', 'three'); // returns 'one-two-three'
+```
+
+The idea of a macroable trait and the implementation is taken from [the `macroable` trait](https://github.com/laravel/framework/blob/master/src/Illuminate/Support/Traits/Macroable.php) of the [the Laravel framework](https://laravel.com).
+
+
+## Postcardware
+
+You're free to use this package (it's [MIT-licensed](LICENSE.md)), but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using.
+
+Our address is: Spatie, Samberstraat 69D, 2060 Antwerp, Belgium.
+
+We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards).
+
+## Installation
+
+You can install the package via composer:
+
+```bash
+composer require spatie/macroable
+```
+
+## Usage
+
+You can add a new method to a class using `macro`:
+
+```php
+$macroableClass = new class() {
+ use Spatie\Macroable\Macroable;
+};
+
+$macroableClass::macro('concatenate', function(... $strings) {
+ return implode('-', $strings);
+};
+
+$myClass->concatenate('one', 'two', 'three'); // returns 'one-two-three'
+```
+
+Callables passed to the `macro` function will be bound to the `class`
+
+```php
+$macroableClass = new class() {
+
+ protected $name = 'myName';
+
+ use Spatie\Macroable\Macroable;
+};
+
+$macroableClass::macro('getName', function() {
+ return $this->name;
+};
+
+$macroableClass->getName(); // returns 'myName'
+```
+
+You can also add multiple methods in one go my using a mixin class. A mixin class contains methods that return callables. Each method from the mixin will be registered on the macroable class.
+
+```php
+$mixin = new class() {
+ public function mixinMethod()
+ {
+ return 'mixinMethod';
+ }
+
+ public function anotherMixinMethod()
+ {
+ return 'anotherMixinMethod';
+ }
+};
+
+$macroableClass->mixin($mixin);
+
+$macroableClass->mixinMethod() // returns 'mixinMethod';
+
+$macroableClass->anotherMixinMethod() // returns 'anotherMixinMethod';
+```
+
+## Changelog
+
+Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
+
+## Testing
+
+``` bash
+composer test
+```
+
+## Contributing
+
+Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
+
+## Security
+
+If you discover any security related issues, please email freek@spatie.be instead of using the issue tracker.
+
+## Credits
+
+- [Freek Van der Herten](https://github.com/freekmurze)
+- [All Contributors](../../contributors)
+
+Idea and code is taken from [the `macroable` trait](https://github.com/laravel/framework/blob/master/src/Illuminate/Support/Traits/Macroable.php) of the [the Laravel framework](https://laravel.com).
+
+## About Spatie
+
+Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource).
+
+## License
+
+The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
diff --git a/vendor/spatie/macroable/composer.json b/vendor/spatie/macroable/composer.json
new file mode 100755
index 0000000..d455f83
--- /dev/null
+++ b/vendor/spatie/macroable/composer.json
@@ -0,0 +1,40 @@
+{
+ "name": "spatie/macroable",
+ "description": "A trait to dynamically add methods to a class",
+ "keywords": [
+ "spatie",
+ "macroable"
+ ],
+ "homepage": "https://github.com/spatie/macroable",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.3"
+ },
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Macroable\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Spatie\\Macroable\\Test\\": "tests"
+ }
+ },
+ "scripts": {
+ "test": "vendor/bin/phpunit"
+ },
+ "config": {
+ "sort-packages": true
+ }
+}
diff --git a/vendor/spatie/macroable/config/.gitkeep b/vendor/spatie/macroable/config/.gitkeep
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/vendor/spatie/macroable/config/.gitkeep
diff --git a/vendor/spatie/macroable/src/Macroable.php b/vendor/spatie/macroable/src/Macroable.php
new file mode 100755
index 0000000..e7e0208
--- /dev/null
+++ b/vendor/spatie/macroable/src/Macroable.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Spatie\Macroable;
+
+use Closure;
+use ReflectionClass;
+use ReflectionMethod;
+use BadMethodCallException;
+
+trait Macroable
+{
+ protected static $macros = [];
+
+ /**
+ * Register a custom macro.
+ *
+ * @param string $name
+ * @param object|callable $macro
+ */
+ public static function macro(string $name, $macro)
+ {
+ static::$macros[$name] = $macro;
+ }
+
+ /**
+ * Mix another object into the class.
+ *
+ * @param object $mixin
+ */
+ public static function mixin($mixin)
+ {
+ $methods = (new ReflectionClass($mixin))->getMethods(
+ ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
+ );
+
+ foreach ($methods as $method) {
+ $method->setAccessible(true);
+
+ static::macro($method->name, $method->invoke($mixin));
+ }
+ }
+
+ public static function hasMacro(string $name): bool
+ {
+ return isset(static::$macros[$name]);
+ }
+
+ public static function __callStatic($method, $parameters)
+ {
+ if (! static::hasMacro($method)) {
+ throw new BadMethodCallException("Method {$method} does not exist.");
+ }
+
+ if (static::$macros[$method] instanceof Closure) {
+ return call_user_func_array(Closure::bind(static::$macros[$method], null, static::class), $parameters);
+ }
+
+ return call_user_func_array(static::$macros[$method], $parameters);
+ }
+
+ public function __call($method, $parameters)
+ {
+ if (! static::hasMacro($method)) {
+ throw new BadMethodCallException("Method {$method} does not exist.");
+ }
+
+ $macro = static::$macros[$method];
+
+ if ($macro instanceof Closure) {
+ return call_user_func_array($macro->bindTo($this, static::class), $parameters);
+ }
+
+ return call_user_func_array($macro, $parameters);
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/.gitignore b/vendor/symfony/event-dispatcher/.gitignore
new file mode 100755
index 0000000..c49a5d8
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/vendor/symfony/event-dispatcher/CHANGELOG.md b/vendor/symfony/event-dispatcher/CHANGELOG.md
new file mode 100755
index 0000000..bb42ee1
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/CHANGELOG.md
@@ -0,0 +1,23 @@
+CHANGELOG
+=========
+
+2.5.0
+-----
+
+ * added Debug\TraceableEventDispatcher (originally in HttpKernel)
+ * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface
+ * added RegisterListenersPass (originally in HttpKernel)
+
+2.1.0
+-----
+
+ * added TraceableEventDispatcherInterface
+ * added ContainerAwareEventDispatcher
+ * added a reference to the EventDispatcher on the Event
+ * added a reference to the Event name on the event
+ * added fluid interface to the dispatch() method which now returns the Event
+ object
+ * added GenericEvent event class
+ * added the possibility for subscribers to subscribe several times for the
+ same event
+ * added ImmutableEventDispatcher
diff --git a/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php b/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php
new file mode 100755
index 0000000..4dcede7
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php
@@ -0,0 +1,183 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Lazily loads listeners and subscribers from the dependency injection
+ * container.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ * @author Jordan Alliot <jordan.alliot@gmail.com>
+ */
+class ContainerAwareEventDispatcher extends EventDispatcher
+{
+ private $container;
+
+ /**
+ * The service IDs of the event listeners and subscribers.
+ */
+ private $listenerIds = array();
+
+ /**
+ * The services registered as listeners.
+ */
+ private $listeners = array();
+
+ public function __construct(ContainerInterface $container)
+ {
+ $this->container = $container;
+ }
+
+ /**
+ * Adds a service as event listener.
+ *
+ * @param string $eventName Event for which the listener is added
+ * @param array $callback The service ID of the listener service & the method
+ * name that has to be called
+ * @param int $priority The higher this value, the earlier an event listener
+ * will be triggered in the chain.
+ * Defaults to 0.
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addListenerService($eventName, $callback, $priority = 0)
+ {
+ if (!\is_array($callback) || 2 !== \count($callback)) {
+ throw new \InvalidArgumentException('Expected an array("service", "method") argument');
+ }
+
+ $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority);
+ }
+
+ public function removeListener($eventName, $listener)
+ {
+ $this->lazyLoad($eventName);
+
+ if (isset($this->listenerIds[$eventName])) {
+ foreach ($this->listenerIds[$eventName] as $i => $args) {
+ list($serviceId, $method) = $args;
+ $key = $serviceId.'.'.$method;
+ if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) {
+ unset($this->listeners[$eventName][$key]);
+ if (empty($this->listeners[$eventName])) {
+ unset($this->listeners[$eventName]);
+ }
+ unset($this->listenerIds[$eventName][$i]);
+ if (empty($this->listenerIds[$eventName])) {
+ unset($this->listenerIds[$eventName]);
+ }
+ }
+ }
+ }
+
+ parent::removeListener($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners($eventName = null)
+ {
+ if (null === $eventName) {
+ return $this->listenerIds || $this->listeners || parent::hasListeners();
+ }
+
+ if (isset($this->listenerIds[$eventName])) {
+ return true;
+ }
+
+ return parent::hasListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners($eventName = null)
+ {
+ if (null === $eventName) {
+ foreach ($this->listenerIds as $serviceEventName => $args) {
+ $this->lazyLoad($serviceEventName);
+ }
+ } else {
+ $this->lazyLoad($eventName);
+ }
+
+ return parent::getListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListenerPriority($eventName, $listener)
+ {
+ $this->lazyLoad($eventName);
+
+ return parent::getListenerPriority($eventName, $listener);
+ }
+
+ /**
+ * Adds a service as event subscriber.
+ *
+ * @param string $serviceId The service ID of the subscriber service
+ * @param string $class The service's class name (which must implement EventSubscriberInterface)
+ */
+ public function addSubscriberService($serviceId, $class)
+ {
+ foreach ($class::getSubscribedEvents() as $eventName => $params) {
+ if (\is_string($params)) {
+ $this->listenerIds[$eventName][] = array($serviceId, $params, 0);
+ } elseif (\is_string($params[0])) {
+ $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0);
+ } else {
+ foreach ($params as $listener) {
+ $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0);
+ }
+ }
+ }
+ }
+
+ public function getContainer()
+ {
+ return $this->container;
+ }
+
+ /**
+ * Lazily loads listeners for this event from the dependency injection
+ * container.
+ *
+ * @param string $eventName The name of the event to dispatch. The name of
+ * the event is the name of the method that is
+ * invoked on listeners.
+ */
+ protected function lazyLoad($eventName)
+ {
+ if (isset($this->listenerIds[$eventName])) {
+ foreach ($this->listenerIds[$eventName] as $args) {
+ list($serviceId, $method, $priority) = $args;
+ $listener = $this->container->get($serviceId);
+
+ $key = $serviceId.'.'.$method;
+ if (!isset($this->listeners[$eventName][$key])) {
+ $this->addListener($eventName, array($listener, $method), $priority);
+ } elseif ($this->listeners[$eventName][$key] !== $listener) {
+ parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method));
+ $this->addListener($eventName, array($listener, $method), $priority);
+ }
+
+ $this->listeners[$eventName][$key] = $listener;
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
new file mode 100755
index 0000000..53d7c5d
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
@@ -0,0 +1,375 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Debug;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Stopwatch\Stopwatch;
+
+/**
+ * Collects some data about event listeners.
+ *
+ * This event dispatcher delegates the dispatching to another one.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class TraceableEventDispatcher implements TraceableEventDispatcherInterface
+{
+ protected $logger;
+ protected $stopwatch;
+
+ private $called;
+ private $dispatcher;
+ private $wrappedListeners;
+
+ public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
+ {
+ $this->dispatcher = $dispatcher;
+ $this->stopwatch = $stopwatch;
+ $this->logger = $logger;
+ $this->called = array();
+ $this->wrappedListeners = array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addListener($eventName, $listener, $priority = 0)
+ {
+ $this->dispatcher->addListener($eventName, $listener, $priority);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ $this->dispatcher->addSubscriber($subscriber);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeListener($eventName, $listener)
+ {
+ if (isset($this->wrappedListeners[$eventName])) {
+ foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
+ if ($wrappedListener->getWrappedListener() === $listener) {
+ $listener = $wrappedListener;
+ unset($this->wrappedListeners[$eventName][$index]);
+ break;
+ }
+ }
+ }
+
+ return $this->dispatcher->removeListener($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeSubscriber(EventSubscriberInterface $subscriber)
+ {
+ return $this->dispatcher->removeSubscriber($subscriber);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners($eventName = null)
+ {
+ return $this->dispatcher->getListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListenerPriority($eventName, $listener)
+ {
+ if (!method_exists($this->dispatcher, 'getListenerPriority')) {
+ return 0;
+ }
+
+ return $this->dispatcher->getListenerPriority($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners($eventName = null)
+ {
+ return $this->dispatcher->hasListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch($eventName, Event $event = null)
+ {
+ if (null === $event) {
+ $event = new Event();
+ }
+
+ if (null !== $this->logger && $event->isPropagationStopped()) {
+ $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
+ }
+
+ $this->preProcess($eventName);
+ $this->preDispatch($eventName, $event);
+
+ $e = $this->stopwatch->start($eventName, 'section');
+
+ $this->dispatcher->dispatch($eventName, $event);
+
+ if ($e->isStarted()) {
+ $e->stop();
+ }
+
+ $this->postDispatch($eventName, $event);
+ $this->postProcess($eventName);
+
+ return $event;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCalledListeners()
+ {
+ $called = array();
+ foreach ($this->called as $eventName => $listeners) {
+ foreach ($listeners as $listener) {
+ $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName);
+ $called[$eventName.'.'.$info['pretty']] = $info;
+ }
+ }
+
+ return $called;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getNotCalledListeners()
+ {
+ try {
+ $allListeners = $this->getListeners();
+ } catch (\Exception $e) {
+ if (null !== $this->logger) {
+ $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e));
+ }
+
+ // unable to retrieve the uncalled listeners
+ return array();
+ }
+
+ $notCalled = array();
+ foreach ($allListeners as $eventName => $listeners) {
+ foreach ($listeners as $listener) {
+ $called = false;
+ if (isset($this->called[$eventName])) {
+ foreach ($this->called[$eventName] as $l) {
+ if ($l->getWrappedListener() === $listener) {
+ $called = true;
+
+ break;
+ }
+ }
+ }
+
+ if (!$called) {
+ $info = $this->getListenerInfo($listener, $eventName);
+ $notCalled[$eventName.'.'.$info['pretty']] = $info;
+ }
+ }
+ }
+
+ uasort($notCalled, array($this, 'sortListenersByPriority'));
+
+ return $notCalled;
+ }
+
+ /**
+ * Proxies all method calls to the original event dispatcher.
+ *
+ * @param string $method The method name
+ * @param array $arguments The method arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, $arguments)
+ {
+ return \call_user_func_array(array($this->dispatcher, $method), $arguments);
+ }
+
+ /**
+ * Called before dispatching the event.
+ *
+ * @param string $eventName The event name
+ * @param Event $event The event
+ */
+ protected function preDispatch($eventName, Event $event)
+ {
+ }
+
+ /**
+ * Called after dispatching the event.
+ *
+ * @param string $eventName The event name
+ * @param Event $event The event
+ */
+ protected function postDispatch($eventName, Event $event)
+ {
+ }
+
+ private function preProcess($eventName)
+ {
+ foreach ($this->dispatcher->getListeners($eventName) as $listener) {
+ $info = $this->getListenerInfo($listener, $eventName);
+ $name = isset($info['class']) ? $info['class'] : $info['type'];
+ $wrappedListener = new WrappedListener($listener, $name, $this->stopwatch, $this);
+ $this->wrappedListeners[$eventName][] = $wrappedListener;
+ $this->dispatcher->removeListener($eventName, $listener);
+ $this->dispatcher->addListener($eventName, $wrappedListener, $info['priority']);
+ }
+ }
+
+ private function postProcess($eventName)
+ {
+ unset($this->wrappedListeners[$eventName]);
+ $skipped = false;
+ foreach ($this->dispatcher->getListeners($eventName) as $listener) {
+ if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
+ continue;
+ }
+ // Unwrap listener
+ $priority = $this->getListenerPriority($eventName, $listener);
+ $this->dispatcher->removeListener($eventName, $listener);
+ $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
+
+ $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName);
+ if ($listener->wasCalled()) {
+ if (null !== $this->logger) {
+ $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty']));
+ }
+
+ if (!isset($this->called[$eventName])) {
+ $this->called[$eventName] = new \SplObjectStorage();
+ }
+
+ $this->called[$eventName]->attach($listener);
+ }
+
+ if (null !== $this->logger && $skipped) {
+ $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName));
+ }
+
+ if ($listener->stoppedPropagation()) {
+ if (null !== $this->logger) {
+ $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName));
+ }
+
+ $skipped = true;
+ }
+ }
+ }
+
+ /**
+ * Returns information about the listener.
+ *
+ * @param object $listener The listener
+ * @param string $eventName The event name
+ *
+ * @return array Information about the listener
+ */
+ private function getListenerInfo($listener, $eventName)
+ {
+ $info = array(
+ 'event' => $eventName,
+ 'priority' => $this->getListenerPriority($eventName, $listener),
+ );
+
+ // unwrap for correct listener info
+ if ($listener instanceof WrappedListener) {
+ $listener = $listener->getWrappedListener();
+ }
+
+ if ($listener instanceof \Closure) {
+ $info += array(
+ 'type' => 'Closure',
+ 'pretty' => 'closure',
+ );
+ } elseif (\is_string($listener)) {
+ try {
+ $r = new \ReflectionFunction($listener);
+ $file = $r->getFileName();
+ $line = $r->getStartLine();
+ } catch (\ReflectionException $e) {
+ $file = null;
+ $line = null;
+ }
+ $info += array(
+ 'type' => 'Function',
+ 'function' => $listener,
+ 'file' => $file,
+ 'line' => $line,
+ 'pretty' => $listener,
+ );
+ } elseif (\is_array($listener) || (\is_object($listener) && \is_callable($listener))) {
+ if (!\is_array($listener)) {
+ $listener = array($listener, '__invoke');
+ }
+ $class = \is_object($listener[0]) ? \get_class($listener[0]) : $listener[0];
+ try {
+ $r = new \ReflectionMethod($class, $listener[1]);
+ $file = $r->getFileName();
+ $line = $r->getStartLine();
+ } catch (\ReflectionException $e) {
+ $file = null;
+ $line = null;
+ }
+ $info += array(
+ 'type' => 'Method',
+ 'class' => $class,
+ 'method' => $listener[1],
+ 'file' => $file,
+ 'line' => $line,
+ 'pretty' => $class.'::'.$listener[1],
+ );
+ }
+
+ return $info;
+ }
+
+ private function sortListenersByPriority($a, $b)
+ {
+ if (\is_int($a['priority']) && !\is_int($b['priority'])) {
+ return 1;
+ }
+
+ if (!\is_int($a['priority']) && \is_int($b['priority'])) {
+ return -1;
+ }
+
+ if ($a['priority'] === $b['priority']) {
+ return 0;
+ }
+
+ if ($a['priority'] > $b['priority']) {
+ return -1;
+ }
+
+ return 1;
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php
new file mode 100755
index 0000000..5483e81
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php
@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Debug;
+
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface TraceableEventDispatcherInterface extends EventDispatcherInterface
+{
+ /**
+ * Gets the called listeners.
+ *
+ * @return array An array of called listeners
+ */
+ public function getCalledListeners();
+
+ /**
+ * Gets the not called listeners.
+ *
+ * @return array An array of not called listeners
+ */
+ public function getNotCalledListeners();
+}
diff --git a/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php
new file mode 100755
index 0000000..1552af0
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php
@@ -0,0 +1,71 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Debug;
+
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\Stopwatch\Stopwatch;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class WrappedListener
+{
+ private $listener;
+ private $name;
+ private $called;
+ private $stoppedPropagation;
+ private $stopwatch;
+ private $dispatcher;
+
+ public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
+ {
+ $this->listener = $listener;
+ $this->name = $name;
+ $this->stopwatch = $stopwatch;
+ $this->dispatcher = $dispatcher;
+ $this->called = false;
+ $this->stoppedPropagation = false;
+ }
+
+ public function getWrappedListener()
+ {
+ return $this->listener;
+ }
+
+ public function wasCalled()
+ {
+ return $this->called;
+ }
+
+ public function stoppedPropagation()
+ {
+ return $this->stoppedPropagation;
+ }
+
+ public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher)
+ {
+ $this->called = true;
+
+ $e = $this->stopwatch->start($this->name, 'event_listener');
+
+ \call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher);
+
+ if ($e->isStarted()) {
+ $e->stop();
+ }
+
+ if ($event->isPropagationStopped()) {
+ $this->stoppedPropagation = true;
+ }
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php
new file mode 100755
index 0000000..5a94ae8
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * Compiler pass to register tagged services for an event dispatcher.
+ */
+class RegisterListenersPass implements CompilerPassInterface
+{
+ protected $dispatcherService;
+ protected $listenerTag;
+ protected $subscriberTag;
+
+ /**
+ * @param string $dispatcherService Service name of the event dispatcher in processed container
+ * @param string $listenerTag Tag name used for listener
+ * @param string $subscriberTag Tag name used for subscribers
+ */
+ public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber')
+ {
+ $this->dispatcherService = $dispatcherService;
+ $this->listenerTag = $listenerTag;
+ $this->subscriberTag = $subscriberTag;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
+ return;
+ }
+
+ $definition = $container->findDefinition($this->dispatcherService);
+
+ foreach ($container->findTaggedServiceIds($this->listenerTag) as $id => $events) {
+ $def = $container->getDefinition($id);
+ if (!$def->isPublic()) {
+ throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event listeners are lazy-loaded.', $id));
+ }
+
+ if ($def->isAbstract()) {
+ throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event listeners are lazy-loaded.', $id));
+ }
+
+ foreach ($events as $event) {
+ $priority = isset($event['priority']) ? $event['priority'] : 0;
+
+ if (!isset($event['event'])) {
+ throw new \InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
+ }
+
+ if (!isset($event['method'])) {
+ $event['method'] = 'on'.preg_replace_callback(array(
+ '/(?<=\b)[a-z]/i',
+ '/[^a-z0-9]/i',
+ ), function ($matches) { return strtoupper($matches[0]); }, $event['event']);
+ $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
+ }
+
+ $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority));
+ }
+ }
+
+ foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) {
+ $def = $container->getDefinition($id);
+ if (!$def->isPublic()) {
+ throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id));
+ }
+
+ if ($def->isAbstract()) {
+ throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id));
+ }
+
+ // We must assume that the class value has been correctly filled, even if the service is created by a factory
+ $class = $container->getParameterBag()->resolveValue($def->getClass());
+ $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';
+
+ if (!is_subclass_of($class, $interface)) {
+ if (!class_exists($class, false)) {
+ throw new \InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
+ }
+
+ throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
+ }
+
+ $definition->addMethodCall('addSubscriberService', array($id, $class));
+ }
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Event.php b/vendor/symfony/event-dispatcher/Event.php
new file mode 100755
index 0000000..320919a
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Event.php
@@ -0,0 +1,120 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * Event is the base class for classes containing event data.
+ *
+ * This class contains no event data. It is used by events that do not pass
+ * state information to an event handler when an event is raised.
+ *
+ * You can call the method stopPropagation() to abort the execution of
+ * further listeners in your event listener.
+ *
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class Event
+{
+ /**
+ * @var bool Whether no further event listeners should be triggered
+ */
+ private $propagationStopped = false;
+
+ /**
+ * @var EventDispatcherInterface Dispatcher that dispatched this event
+ */
+ private $dispatcher;
+
+ /**
+ * @var string This event's name
+ */
+ private $name;
+
+ /**
+ * Returns whether further event listeners should be triggered.
+ *
+ * @see Event::stopPropagation()
+ *
+ * @return bool Whether propagation was already stopped for this event
+ */
+ public function isPropagationStopped()
+ {
+ return $this->propagationStopped;
+ }
+
+ /**
+ * Stops the propagation of the event to further event listeners.
+ *
+ * If multiple event listeners are connected to the same event, no
+ * further event listener will be triggered once any trigger calls
+ * stopPropagation().
+ */
+ public function stopPropagation()
+ {
+ $this->propagationStopped = true;
+ }
+
+ /**
+ * Stores the EventDispatcher that dispatches this Event.
+ *
+ * @param EventDispatcherInterface $dispatcher
+ *
+ * @deprecated since version 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call.
+ */
+ public function setDispatcher(EventDispatcherInterface $dispatcher)
+ {
+ $this->dispatcher = $dispatcher;
+ }
+
+ /**
+ * Returns the EventDispatcher that dispatches this Event.
+ *
+ * @return EventDispatcherInterface
+ *
+ * @deprecated since version 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call.
+ */
+ public function getDispatcher()
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0. The event dispatcher instance can be received in the listener call instead.', E_USER_DEPRECATED);
+
+ return $this->dispatcher;
+ }
+
+ /**
+ * Gets the event's name.
+ *
+ * @return string
+ *
+ * @deprecated since version 2.4, to be removed in 3.0. The event name is passed to the listener call.
+ */
+ public function getName()
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0. The event name can be received in the listener call instead.', E_USER_DEPRECATED);
+
+ return $this->name;
+ }
+
+ /**
+ * Sets the event's name property.
+ *
+ * @param string $name The event name
+ *
+ * @deprecated since version 2.4, to be removed in 3.0. The event name is passed to the listener call.
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/EventDispatcher.php b/vendor/symfony/event-dispatcher/EventDispatcher.php
new file mode 100755
index 0000000..b41b98e
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/EventDispatcher.php
@@ -0,0 +1,198 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * The EventDispatcherInterface is the central point of Symfony's event listener system.
+ *
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
+ *
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @author Jordan Alliot <jordan.alliot@gmail.com>
+ */
+class EventDispatcher implements EventDispatcherInterface
+{
+ private $listeners = array();
+ private $sorted = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch($eventName, Event $event = null)
+ {
+ if (null === $event) {
+ $event = new Event();
+ }
+
+ $event->setDispatcher($this);
+ $event->setName($eventName);
+
+ if ($listeners = $this->getListeners($eventName)) {
+ $this->doDispatch($listeners, $eventName, $event);
+ }
+
+ return $event;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners($eventName = null)
+ {
+ if (null !== $eventName) {
+ if (!isset($this->listeners[$eventName])) {
+ return array();
+ }
+
+ if (!isset($this->sorted[$eventName])) {
+ $this->sortListeners($eventName);
+ }
+
+ return $this->sorted[$eventName];
+ }
+
+ foreach ($this->listeners as $eventName => $eventListeners) {
+ if (!isset($this->sorted[$eventName])) {
+ $this->sortListeners($eventName);
+ }
+ }
+
+ return array_filter($this->sorted);
+ }
+
+ /**
+ * Gets the listener priority for a specific event.
+ *
+ * Returns null if the event or the listener does not exist.
+ *
+ * @param string $eventName The name of the event
+ * @param callable $listener The listener
+ *
+ * @return int|null The event listener priority
+ */
+ public function getListenerPriority($eventName, $listener)
+ {
+ if (!isset($this->listeners[$eventName])) {
+ return;
+ }
+
+ foreach ($this->listeners[$eventName] as $priority => $listeners) {
+ if (false !== \in_array($listener, $listeners, true)) {
+ return $priority;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners($eventName = null)
+ {
+ return (bool) $this->getListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addListener($eventName, $listener, $priority = 0)
+ {
+ $this->listeners[$eventName][$priority][] = $listener;
+ unset($this->sorted[$eventName]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeListener($eventName, $listener)
+ {
+ if (!isset($this->listeners[$eventName])) {
+ return;
+ }
+
+ foreach ($this->listeners[$eventName] as $priority => $listeners) {
+ if (false !== ($key = array_search($listener, $listeners, true))) {
+ unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
+ if (\is_string($params)) {
+ $this->addListener($eventName, array($subscriber, $params));
+ } elseif (\is_string($params[0])) {
+ $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
+ } else {
+ foreach ($params as $listener) {
+ $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeSubscriber(EventSubscriberInterface $subscriber)
+ {
+ foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
+ if (\is_array($params) && \is_array($params[0])) {
+ foreach ($params as $listener) {
+ $this->removeListener($eventName, array($subscriber, $listener[0]));
+ }
+ } else {
+ $this->removeListener($eventName, array($subscriber, \is_string($params) ? $params : $params[0]));
+ }
+ }
+ }
+
+ /**
+ * Triggers the listeners of an event.
+ *
+ * This method can be overridden to add functionality that is executed
+ * for each listener.
+ *
+ * @param callable[] $listeners The event listeners
+ * @param string $eventName The name of the event to dispatch
+ * @param Event $event The event object to pass to the event handlers/listeners
+ */
+ protected function doDispatch($listeners, $eventName, Event $event)
+ {
+ foreach ($listeners as $listener) {
+ if ($event->isPropagationStopped()) {
+ break;
+ }
+ \call_user_func($listener, $event, $eventName, $this);
+ }
+ }
+
+ /**
+ * Sorts the internal list of listeners for the given event by priority.
+ *
+ * @param string $eventName The name of the event
+ */
+ private function sortListeners($eventName)
+ {
+ krsort($this->listeners[$eventName]);
+ $this->sorted[$eventName] = \call_user_func_array('array_merge', $this->listeners[$eventName]);
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php
new file mode 100755
index 0000000..60160a9
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php
@@ -0,0 +1,81 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * The EventDispatcherInterface is the central point of Symfony's event listener system.
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface EventDispatcherInterface
+{
+ /**
+ * Dispatches an event to all registered listeners.
+ *
+ * @param string $eventName The name of the event to dispatch. The name of
+ * the event is the name of the method that is
+ * invoked on listeners.
+ * @param Event $event The event to pass to the event handlers/listeners
+ * If not supplied, an empty Event instance is created
+ *
+ * @return Event
+ */
+ public function dispatch($eventName, Event $event = null);
+
+ /**
+ * Adds an event listener that listens on the specified events.
+ *
+ * @param string $eventName The event to listen on
+ * @param callable $listener The listener
+ * @param int $priority The higher this value, the earlier an event
+ * listener will be triggered in the chain (defaults to 0)
+ */
+ public function addListener($eventName, $listener, $priority = 0);
+
+ /**
+ * Adds an event subscriber.
+ *
+ * The subscriber is asked for all the events he is
+ * interested in and added as a listener for these events.
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber);
+
+ /**
+ * Removes an event listener from the specified events.
+ *
+ * @param string $eventName The event to remove a listener from
+ * @param callable $listener The listener to remove
+ */
+ public function removeListener($eventName, $listener);
+
+ public function removeSubscriber(EventSubscriberInterface $subscriber);
+
+ /**
+ * Gets the listeners of a specific event or all listeners sorted by descending priority.
+ *
+ * @param string $eventName The name of the event
+ *
+ * @return array The event listeners for the specified event, or all event listeners by event name
+ */
+ public function getListeners($eventName = null);
+
+ /**
+ * Checks whether an event has any registered listeners.
+ *
+ * @param string $eventName The name of the event
+ *
+ * @return bool true if the specified event has any listeners, false otherwise
+ */
+ public function hasListeners($eventName = null);
+}
diff --git a/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php
new file mode 100755
index 0000000..8af7789
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * An EventSubscriber knows himself what events he is interested in.
+ * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
+ * {@link getSubscribedEvents} and registers the subscriber as a listener for all
+ * returned events.
+ *
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Jonathan Wage <jonwage@gmail.com>
+ * @author Roman Borschel <roman@code-factory.org>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface EventSubscriberInterface
+{
+ /**
+ * Returns an array of event names this subscriber wants to listen to.
+ *
+ * The array keys are event names and the value can be:
+ *
+ * * The method name to call (priority defaults to 0)
+ * * An array composed of the method name to call and the priority
+ * * An array of arrays composed of the method names to call and respective
+ * priorities, or 0 if unset
+ *
+ * For instance:
+ *
+ * * array('eventName' => 'methodName')
+ * * array('eventName' => array('methodName', $priority))
+ * * array('eventName' => array(array('methodName1', $priority), array('methodName2')))
+ *
+ * @return array The event names to listen to
+ */
+ public static function getSubscribedEvents();
+}
diff --git a/vendor/symfony/event-dispatcher/GenericEvent.php b/vendor/symfony/event-dispatcher/GenericEvent.php
new file mode 100755
index 0000000..f0be7e1
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/GenericEvent.php
@@ -0,0 +1,175 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * Event encapsulation class.
+ *
+ * Encapsulates events thus decoupling the observer from the subject they encapsulate.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
+{
+ protected $subject;
+ protected $arguments;
+
+ /**
+ * Encapsulate an event with $subject and $args.
+ *
+ * @param mixed $subject The subject of the event, usually an object or a callable
+ * @param array $arguments Arguments to store in the event
+ */
+ public function __construct($subject = null, array $arguments = array())
+ {
+ $this->subject = $subject;
+ $this->arguments = $arguments;
+ }
+
+ /**
+ * Getter for subject property.
+ *
+ * @return mixed The observer subject
+ */
+ public function getSubject()
+ {
+ return $this->subject;
+ }
+
+ /**
+ * Get argument by key.
+ *
+ * @param string $key Key
+ *
+ * @return mixed Contents of array key
+ *
+ * @throws \InvalidArgumentException if key is not found
+ */
+ public function getArgument($key)
+ {
+ if ($this->hasArgument($key)) {
+ return $this->arguments[$key];
+ }
+
+ throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
+ }
+
+ /**
+ * Add argument to event.
+ *
+ * @param string $key Argument name
+ * @param mixed $value Value
+ *
+ * @return $this
+ */
+ public function setArgument($key, $value)
+ {
+ $this->arguments[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Getter for all arguments.
+ *
+ * @return array
+ */
+ public function getArguments()
+ {
+ return $this->arguments;
+ }
+
+ /**
+ * Set args property.
+ *
+ * @param array $args Arguments
+ *
+ * @return $this
+ */
+ public function setArguments(array $args = array())
+ {
+ $this->arguments = $args;
+
+ return $this;
+ }
+
+ /**
+ * Has argument.
+ *
+ * @param string $key Key of arguments array
+ *
+ * @return bool
+ */
+ public function hasArgument($key)
+ {
+ return array_key_exists($key, $this->arguments);
+ }
+
+ /**
+ * ArrayAccess for argument getter.
+ *
+ * @param string $key Array key
+ *
+ * @return mixed
+ *
+ * @throws \InvalidArgumentException if key does not exist in $this->args
+ */
+ public function offsetGet($key)
+ {
+ return $this->getArgument($key);
+ }
+
+ /**
+ * ArrayAccess for argument setter.
+ *
+ * @param string $key Array key to set
+ * @param mixed $value Value
+ */
+ public function offsetSet($key, $value)
+ {
+ $this->setArgument($key, $value);
+ }
+
+ /**
+ * ArrayAccess for unset argument.
+ *
+ * @param string $key Array key
+ */
+ public function offsetUnset($key)
+ {
+ if ($this->hasArgument($key)) {
+ unset($this->arguments[$key]);
+ }
+ }
+
+ /**
+ * ArrayAccess has argument.
+ *
+ * @param string $key Array key
+ *
+ * @return bool
+ */
+ public function offsetExists($key)
+ {
+ return $this->hasArgument($key);
+ }
+
+ /**
+ * IteratorAggregate for iterating over the object like an array.
+ *
+ * @return \ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->arguments);
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php
new file mode 100755
index 0000000..b3cf56c
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php
@@ -0,0 +1,91 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * A read-only proxy for an event dispatcher.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class ImmutableEventDispatcher implements EventDispatcherInterface
+{
+ private $dispatcher;
+
+ public function __construct(EventDispatcherInterface $dispatcher)
+ {
+ $this->dispatcher = $dispatcher;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch($eventName, Event $event = null)
+ {
+ return $this->dispatcher->dispatch($eventName, $event);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addListener($eventName, $listener, $priority = 0)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeListener($eventName, $listener)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeSubscriber(EventSubscriberInterface $subscriber)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners($eventName = null)
+ {
+ return $this->dispatcher->getListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListenerPriority($eventName, $listener)
+ {
+ return $this->dispatcher->getListenerPriority($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners($eventName = null)
+ {
+ return $this->dispatcher->hasListeners($eventName);
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/LICENSE b/vendor/symfony/event-dispatcher/LICENSE
new file mode 100755
index 0000000..21d7fb9
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2018 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/event-dispatcher/README.md b/vendor/symfony/event-dispatcher/README.md
new file mode 100755
index 0000000..185c3fe
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/README.md
@@ -0,0 +1,15 @@
+EventDispatcher Component
+=========================
+
+The EventDispatcher component provides tools that allow your application
+components to communicate with each other by dispatching events and listening to
+them.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/event_dispatcher/index.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php
new file mode 100755
index 0000000..48de632
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php
@@ -0,0 +1,398 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+abstract class AbstractEventDispatcherTest extends TestCase
+{
+ /* Some pseudo events */
+ const preFoo = 'pre.foo';
+ const postFoo = 'post.foo';
+ const preBar = 'pre.bar';
+ const postBar = 'post.bar';
+
+ /**
+ * @var EventDispatcher
+ */
+ private $dispatcher;
+
+ private $listener;
+
+ protected function setUp()
+ {
+ $this->dispatcher = $this->createEventDispatcher();
+ $this->listener = new TestEventListener();
+ }
+
+ protected function tearDown()
+ {
+ $this->dispatcher = null;
+ $this->listener = null;
+ }
+
+ abstract protected function createEventDispatcher();
+
+ public function testInitialState()
+ {
+ $this->assertEquals(array(), $this->dispatcher->getListeners());
+ $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
+ }
+
+ public function testAddListener()
+ {
+ $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
+ $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
+ $this->assertTrue($this->dispatcher->hasListeners());
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+ $this->assertCount(1, $this->dispatcher->getListeners(self::preFoo));
+ $this->assertCount(1, $this->dispatcher->getListeners(self::postFoo));
+ $this->assertCount(2, $this->dispatcher->getListeners());
+ }
+
+ public function testGetListenersSortsByPriority()
+ {
+ $listener1 = new TestEventListener();
+ $listener2 = new TestEventListener();
+ $listener3 = new TestEventListener();
+ $listener1->name = '1';
+ $listener2->name = '2';
+ $listener3->name = '3';
+
+ $this->dispatcher->addListener('pre.foo', array($listener1, 'preFoo'), -10);
+ $this->dispatcher->addListener('pre.foo', array($listener2, 'preFoo'), 10);
+ $this->dispatcher->addListener('pre.foo', array($listener3, 'preFoo'));
+
+ $expected = array(
+ array($listener2, 'preFoo'),
+ array($listener3, 'preFoo'),
+ array($listener1, 'preFoo'),
+ );
+
+ $this->assertSame($expected, $this->dispatcher->getListeners('pre.foo'));
+ }
+
+ public function testGetAllListenersSortsByPriority()
+ {
+ $listener1 = new TestEventListener();
+ $listener2 = new TestEventListener();
+ $listener3 = new TestEventListener();
+ $listener4 = new TestEventListener();
+ $listener5 = new TestEventListener();
+ $listener6 = new TestEventListener();
+
+ $this->dispatcher->addListener('pre.foo', $listener1, -10);
+ $this->dispatcher->addListener('pre.foo', $listener2);
+ $this->dispatcher->addListener('pre.foo', $listener3, 10);
+ $this->dispatcher->addListener('post.foo', $listener4, -10);
+ $this->dispatcher->addListener('post.foo', $listener5);
+ $this->dispatcher->addListener('post.foo', $listener6, 10);
+
+ $expected = array(
+ 'pre.foo' => array($listener3, $listener2, $listener1),
+ 'post.foo' => array($listener6, $listener5, $listener4),
+ );
+
+ $this->assertSame($expected, $this->dispatcher->getListeners());
+ }
+
+ public function testGetListenerPriority()
+ {
+ $listener1 = new TestEventListener();
+ $listener2 = new TestEventListener();
+
+ $this->dispatcher->addListener('pre.foo', $listener1, -10);
+ $this->dispatcher->addListener('pre.foo', $listener2);
+
+ $this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1));
+ $this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2));
+ $this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2));
+ $this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {}));
+ }
+
+ public function testDispatch()
+ {
+ $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
+ $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
+ $this->dispatcher->dispatch(self::preFoo);
+ $this->assertTrue($this->listener->preFooInvoked);
+ $this->assertFalse($this->listener->postFooInvoked);
+ $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent'));
+ $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo));
+ $event = new Event();
+ $return = $this->dispatcher->dispatch(self::preFoo, $event);
+ $this->assertSame($event, $return);
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacyDispatch()
+ {
+ $event = new Event();
+ $this->dispatcher->dispatch(self::preFoo, $event);
+ $this->assertEquals('pre.foo', $event->getName());
+ }
+
+ public function testDispatchForClosure()
+ {
+ $invoked = 0;
+ $listener = function () use (&$invoked) {
+ ++$invoked;
+ };
+ $this->dispatcher->addListener('pre.foo', $listener);
+ $this->dispatcher->addListener('post.foo', $listener);
+ $this->dispatcher->dispatch(self::preFoo);
+ $this->assertEquals(1, $invoked);
+ }
+
+ public function testStopEventPropagation()
+ {
+ $otherListener = new TestEventListener();
+
+ // postFoo() stops the propagation, so only one listener should
+ // be executed
+ // Manually set priority to enforce $this->listener to be called first
+ $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'), 10);
+ $this->dispatcher->addListener('post.foo', array($otherListener, 'postFoo'));
+ $this->dispatcher->dispatch(self::postFoo);
+ $this->assertTrue($this->listener->postFooInvoked);
+ $this->assertFalse($otherListener->postFooInvoked);
+ }
+
+ public function testDispatchByPriority()
+ {
+ $invoked = array();
+ $listener1 = function () use (&$invoked) {
+ $invoked[] = '1';
+ };
+ $listener2 = function () use (&$invoked) {
+ $invoked[] = '2';
+ };
+ $listener3 = function () use (&$invoked) {
+ $invoked[] = '3';
+ };
+ $this->dispatcher->addListener('pre.foo', $listener1, -10);
+ $this->dispatcher->addListener('pre.foo', $listener2);
+ $this->dispatcher->addListener('pre.foo', $listener3, 10);
+ $this->dispatcher->dispatch(self::preFoo);
+ $this->assertEquals(array('3', '2', '1'), $invoked);
+ }
+
+ public function testRemoveListener()
+ {
+ $this->dispatcher->addListener('pre.bar', $this->listener);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preBar));
+ $this->dispatcher->removeListener('pre.bar', $this->listener);
+ $this->assertFalse($this->dispatcher->hasListeners(self::preBar));
+ $this->dispatcher->removeListener('notExists', $this->listener);
+ }
+
+ public function testAddSubscriber()
+ {
+ $eventSubscriber = new TestEventSubscriber();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+ }
+
+ public function testAddSubscriberWithPriorities()
+ {
+ $eventSubscriber = new TestEventSubscriber();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+
+ $eventSubscriber = new TestEventSubscriberWithPriorities();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+
+ $listeners = $this->dispatcher->getListeners('pre.foo');
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertCount(2, $listeners);
+ $this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]);
+ }
+
+ public function testAddSubscriberWithMultipleListeners()
+ {
+ $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+
+ $listeners = $this->dispatcher->getListeners('pre.foo');
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertCount(2, $listeners);
+ $this->assertEquals('preFoo2', $listeners[0][1]);
+ }
+
+ public function testRemoveSubscriber()
+ {
+ $eventSubscriber = new TestEventSubscriber();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+ $this->dispatcher->removeSubscriber($eventSubscriber);
+ $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
+ }
+
+ public function testRemoveSubscriberWithPriorities()
+ {
+ $eventSubscriber = new TestEventSubscriberWithPriorities();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->dispatcher->removeSubscriber($eventSubscriber);
+ $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+ }
+
+ public function testRemoveSubscriberWithMultipleListeners()
+ {
+ $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertCount(2, $this->dispatcher->getListeners(self::preFoo));
+ $this->dispatcher->removeSubscriber($eventSubscriber);
+ $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacyEventReceivesTheDispatcherInstance()
+ {
+ $dispatcher = null;
+ $this->dispatcher->addListener('test', function ($event) use (&$dispatcher) {
+ $dispatcher = $event->getDispatcher();
+ });
+ $this->dispatcher->dispatch('test');
+ $this->assertSame($this->dispatcher, $dispatcher);
+ }
+
+ public function testEventReceivesTheDispatcherInstanceAsArgument()
+ {
+ $listener = new TestWithDispatcher();
+ $this->dispatcher->addListener('test', array($listener, 'foo'));
+ $this->assertNull($listener->name);
+ $this->assertNull($listener->dispatcher);
+ $this->dispatcher->dispatch('test');
+ $this->assertEquals('test', $listener->name);
+ $this->assertSame($this->dispatcher, $listener->dispatcher);
+ }
+
+ /**
+ * @see https://bugs.php.net/bug.php?id=62976
+ *
+ * This bug affects:
+ * - The PHP 5.3 branch for versions < 5.3.18
+ * - The PHP 5.4 branch for versions < 5.4.8
+ * - The PHP 5.5 branch is not affected
+ */
+ public function testWorkaroundForPhpBug62976()
+ {
+ $dispatcher = $this->createEventDispatcher();
+ $dispatcher->addListener('bug.62976', new CallableClass());
+ $dispatcher->removeListener('bug.62976', function () {});
+ $this->assertTrue($dispatcher->hasListeners('bug.62976'));
+ }
+
+ public function testHasListenersWhenAddedCallbackListenerIsRemoved()
+ {
+ $listener = function () {};
+ $this->dispatcher->addListener('foo', $listener);
+ $this->dispatcher->removeListener('foo', $listener);
+ $this->assertFalse($this->dispatcher->hasListeners());
+ }
+
+ public function testGetListenersWhenAddedCallbackListenerIsRemoved()
+ {
+ $listener = function () {};
+ $this->dispatcher->addListener('foo', $listener);
+ $this->dispatcher->removeListener('foo', $listener);
+ $this->assertSame(array(), $this->dispatcher->getListeners());
+ }
+
+ public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled()
+ {
+ $this->assertFalse($this->dispatcher->hasListeners('foo'));
+ $this->assertFalse($this->dispatcher->hasListeners());
+ }
+}
+
+class CallableClass
+{
+ public function __invoke()
+ {
+ }
+}
+
+class TestEventListener
+{
+ public $preFooInvoked = false;
+ public $postFooInvoked = false;
+
+ /* Listener methods */
+
+ public function preFoo(Event $e)
+ {
+ $this->preFooInvoked = true;
+ }
+
+ public function postFoo(Event $e)
+ {
+ $this->postFooInvoked = true;
+
+ $e->stopPropagation();
+ }
+}
+
+class TestWithDispatcher
+{
+ public $name;
+ public $dispatcher;
+
+ public function foo(Event $e, $name, $dispatcher)
+ {
+ $this->name = $name;
+ $this->dispatcher = $dispatcher;
+ }
+}
+
+class TestEventSubscriber implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array('pre.foo' => 'preFoo', 'post.foo' => 'postFoo');
+ }
+}
+
+class TestEventSubscriberWithPriorities implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'pre.foo' => array('preFoo', 10),
+ 'post.foo' => array('postFoo'),
+ );
+ }
+}
+
+class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array('pre.foo' => array(
+ array('preFoo1'),
+ array('preFoo2', 10),
+ ));
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php
new file mode 100755
index 0000000..224a292
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php
@@ -0,0 +1,277 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use Symfony\Component\DependencyInjection\Container;
+use Symfony\Component\DependencyInjection\Scope;
+use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest
+{
+ protected function createEventDispatcher()
+ {
+ $container = new Container();
+
+ return new ContainerAwareEventDispatcher($container);
+ }
+
+ public function testAddAListenerService()
+ {
+ $event = new Event();
+
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $service
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $dispatcher->dispatch('onEvent', $event);
+ }
+
+ public function testAddASubscriberService()
+ {
+ $event = new Event();
+
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\SubscriberService')->getMock();
+
+ $service
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $service
+ ->expects($this->once())
+ ->method('onEventWithPriority')
+ ->with($event)
+ ;
+
+ $service
+ ->expects($this->once())
+ ->method('onEventNested')
+ ->with($event)
+ ;
+
+ $container = new Container();
+ $container->set('service.subscriber', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addSubscriberService('service.subscriber', 'Symfony\Component\EventDispatcher\Tests\SubscriberService');
+
+ $dispatcher->dispatch('onEvent', $event);
+ $dispatcher->dispatch('onEventWithPriority', $event);
+ $dispatcher->dispatch('onEventNested', $event);
+ }
+
+ public function testPreventDuplicateListenerService()
+ {
+ $event = new Event();
+
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $service
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 5);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 10);
+
+ $dispatcher->dispatch('onEvent', $event);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @group legacy
+ */
+ public function testTriggerAListenerServiceOutOfScope()
+ {
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $scope = new Scope('scope');
+ $container = new Container();
+ $container->addScope($scope);
+ $container->enterScope('scope');
+
+ $container->set('service.listener', $service, 'scope');
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $container->leaveScope('scope');
+ $dispatcher->dispatch('onEvent');
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testReEnteringAScope()
+ {
+ $event = new Event();
+
+ $service1 = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $service1
+ ->expects($this->exactly(2))
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $scope = new Scope('scope');
+ $container = new Container();
+ $container->addScope($scope);
+ $container->enterScope('scope');
+
+ $container->set('service.listener', $service1, 'scope');
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+ $dispatcher->dispatch('onEvent', $event);
+
+ $service2 = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $service2
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $container->enterScope('scope');
+ $container->set('service.listener', $service2, 'scope');
+
+ $dispatcher->dispatch('onEvent', $event);
+
+ $container->leaveScope('scope');
+
+ $dispatcher->dispatch('onEvent');
+ }
+
+ public function testHasListenersOnLazyLoad()
+ {
+ $event = new Event();
+
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $event->setDispatcher($dispatcher);
+ $event->setName('onEvent');
+
+ $service
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $this->assertTrue($dispatcher->hasListeners());
+
+ if ($dispatcher->hasListeners('onEvent')) {
+ $dispatcher->dispatch('onEvent');
+ }
+ }
+
+ public function testGetListenersOnLazyLoad()
+ {
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $listeners = $dispatcher->getListeners();
+
+ $this->assertArrayHasKey('onEvent', $listeners);
+
+ $this->assertCount(1, $dispatcher->getListeners('onEvent'));
+ }
+
+ public function testRemoveAfterDispatch()
+ {
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $dispatcher->dispatch('onEvent', new Event());
+ $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
+ $this->assertFalse($dispatcher->hasListeners('onEvent'));
+ }
+
+ public function testRemoveBeforeDispatch()
+ {
+ $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock();
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
+ $this->assertFalse($dispatcher->hasListeners('onEvent'));
+ }
+}
+
+class Service
+{
+ public function onEvent(Event $e)
+ {
+ }
+}
+
+class SubscriberService implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'onEvent' => 'onEvent',
+ 'onEventWithPriority' => array('onEventWithPriority', 10),
+ 'onEventNested' => array(array('onEventNested')),
+ );
+ }
+
+ public function onEvent(Event $e)
+ {
+ }
+
+ public function onEventWithPriority(Event $e)
+ {
+ }
+
+ public function onEventNested(Event $e)
+ {
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
new file mode 100755
index 0000000..ffdda38
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
@@ -0,0 +1,254 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests\Debug;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
+use Symfony\Component\EventDispatcher\Debug\WrappedListener;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Stopwatch\Stopwatch;
+
+class TraceableEventDispatcherTest extends TestCase
+{
+ public function testAddRemoveListener()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $tdispatcher->addListener('foo', $listener = function () {});
+ $listeners = $dispatcher->getListeners('foo');
+ $this->assertCount(1, $listeners);
+ $this->assertSame($listener, $listeners[0]);
+
+ $tdispatcher->removeListener('foo', $listener);
+ $this->assertCount(0, $dispatcher->getListeners('foo'));
+ }
+
+ public function testGetListeners()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $tdispatcher->addListener('foo', $listener = function () {});
+ $this->assertSame($dispatcher->getListeners('foo'), $tdispatcher->getListeners('foo'));
+ }
+
+ public function testHasListeners()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $this->assertFalse($dispatcher->hasListeners('foo'));
+ $this->assertFalse($tdispatcher->hasListeners('foo'));
+
+ $tdispatcher->addListener('foo', $listener = function () {});
+ $this->assertTrue($dispatcher->hasListeners('foo'));
+ $this->assertTrue($tdispatcher->hasListeners('foo'));
+ }
+
+ public function testGetListenerPriority()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $tdispatcher->addListener('foo', function () {}, 123);
+
+ $listeners = $dispatcher->getListeners('foo');
+ $this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0]));
+
+ // Verify that priority is preserved when listener is removed and re-added
+ // in preProcess() and postProcess().
+ $tdispatcher->dispatch('foo', new Event());
+ $listeners = $dispatcher->getListeners('foo');
+ $this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0]));
+ }
+
+ public function testGetListenerPriorityReturnsZeroWhenWrappedMethodDoesNotExist()
+ {
+ $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
+ $traceableEventDispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+ $traceableEventDispatcher->addListener('foo', function () {}, 123);
+ $listeners = $traceableEventDispatcher->getListeners('foo');
+
+ $this->assertSame(0, $traceableEventDispatcher->getListenerPriority('foo', $listeners[0]));
+ }
+
+ public function testAddRemoveSubscriber()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $subscriber = new EventSubscriber();
+
+ $tdispatcher->addSubscriber($subscriber);
+ $listeners = $dispatcher->getListeners('foo');
+ $this->assertCount(1, $listeners);
+ $this->assertSame(array($subscriber, 'call'), $listeners[0]);
+
+ $tdispatcher->removeSubscriber($subscriber);
+ $this->assertCount(0, $dispatcher->getListeners('foo'));
+ }
+
+ /**
+ * @dataProvider isWrappedDataProvider
+ *
+ * @param bool $isWrapped
+ */
+ public function testGetCalledListeners($isWrapped)
+ {
+ $dispatcher = new EventDispatcher();
+ $stopWatch = new Stopwatch();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, $stopWatch);
+
+ $listener = function () {};
+ if ($isWrapped) {
+ $listener = new WrappedListener($listener, 'foo', $stopWatch, $dispatcher);
+ }
+
+ $tdispatcher->addListener('foo', $listener, 5);
+
+ $this->assertEquals(array(), $tdispatcher->getCalledListeners());
+ $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure', 'priority' => 5)), $tdispatcher->getNotCalledListeners());
+
+ $tdispatcher->dispatch('foo');
+
+ $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure', 'priority' => 5)), $tdispatcher->getCalledListeners());
+ $this->assertEquals(array(), $tdispatcher->getNotCalledListeners());
+ }
+
+ public function isWrappedDataProvider()
+ {
+ return array(
+ array(false),
+ array(true),
+ );
+ }
+
+ public function testGetCalledListenersNested()
+ {
+ $tdispatcher = null;
+ $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+ $dispatcher->addListener('foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) {
+ $tdispatcher = $dispatcher;
+ $dispatcher->dispatch('bar');
+ });
+ $dispatcher->addListener('bar', function (Event $event) {});
+ $dispatcher->dispatch('foo');
+ $this->assertSame($dispatcher, $tdispatcher);
+ $this->assertCount(2, $dispatcher->getCalledListeners());
+ }
+
+ public function testLogger()
+ {
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
+ $tdispatcher->addListener('foo', $listener1 = function () {});
+ $tdispatcher->addListener('foo', $listener2 = function () {});
+
+ $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".');
+ $logger->expects($this->at(1))->method('debug')->with('Notified event "foo" to listener "closure".');
+
+ $tdispatcher->dispatch('foo');
+ }
+
+ public function testLoggerWithStoppedEvent()
+ {
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
+ $tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); });
+ $tdispatcher->addListener('foo', $listener2 = function () {});
+
+ $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".');
+ $logger->expects($this->at(1))->method('debug')->with('Listener "closure" stopped propagation of the event "foo".');
+ $logger->expects($this->at(2))->method('debug')->with('Listener "closure" was not called for event "foo".');
+
+ $tdispatcher->dispatch('foo');
+ }
+
+ public function testDispatchCallListeners()
+ {
+ $called = array();
+
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+ $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo1'; }, 10);
+ $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo2'; }, 20);
+
+ $tdispatcher->dispatch('foo');
+
+ $this->assertSame(array('foo2', 'foo1'), $called);
+ }
+
+ public function testDispatchNested()
+ {
+ $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+ $loop = 1;
+ $dispatchedEvents = 0;
+ $dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) {
+ ++$loop;
+ if (2 == $loop) {
+ $dispatcher->dispatch('foo');
+ }
+ });
+ $dispatcher->addListener('foo', function () use (&$dispatchedEvents) {
+ ++$dispatchedEvents;
+ });
+
+ $dispatcher->dispatch('foo');
+
+ $this->assertSame(2, $dispatchedEvents);
+ }
+
+ public function testDispatchReusedEventNested()
+ {
+ $nestedCall = false;
+ $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+ $dispatcher->addListener('foo', function (Event $e) use ($dispatcher) {
+ $dispatcher->dispatch('bar', $e);
+ });
+ $dispatcher->addListener('bar', function (Event $e) use (&$nestedCall) {
+ $nestedCall = true;
+ });
+
+ $this->assertFalse($nestedCall);
+ $dispatcher->dispatch('foo');
+ $this->assertTrue($nestedCall);
+ }
+
+ public function testListenerCanRemoveItselfWhenExecuted()
+ {
+ $eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+ $listener1 = function ($event, $eventName, EventDispatcherInterface $dispatcher) use (&$listener1) {
+ $dispatcher->removeListener('foo', $listener1);
+ };
+ $eventDispatcher->addListener('foo', $listener1);
+ $eventDispatcher->addListener('foo', function () {});
+ $eventDispatcher->dispatch('foo');
+
+ $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed');
+ }
+}
+
+class EventSubscriber implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array('foo' => 'call');
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
new file mode 100755
index 0000000..7490d0d
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
@@ -0,0 +1,154 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
+
+class RegisterListenersPassTest extends TestCase
+{
+ /**
+ * Tests that event subscribers not implementing EventSubscriberInterface
+ * trigger an exception.
+ *
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEventSubscriberWithoutInterface()
+ {
+ $builder = new ContainerBuilder();
+ $builder->register('event_dispatcher');
+ $builder->register('my_event_subscriber', 'stdClass')
+ ->addTag('kernel.event_subscriber');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($builder);
+ }
+
+ public function testValidEventSubscriber()
+ {
+ $services = array(
+ 'my_event_subscriber' => array(0 => array()),
+ );
+
+ $builder = new ContainerBuilder();
+ $eventDispatcherDefinition = $builder->register('event_dispatcher');
+ $builder->register('my_event_subscriber', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService')
+ ->addTag('kernel.event_subscriber');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($builder);
+
+ $this->assertEquals(array(array('addSubscriberService', array('my_event_subscriber', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService'))), $eventDispatcherDefinition->getMethodCalls());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The service "foo" must be public as event listeners are lazy-loaded.
+ */
+ public function testPrivateEventListener()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_listener', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The service "foo" must be public as event subscribers are lazy-loaded.
+ */
+ public function testPrivateEventSubscriber()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_subscriber', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The service "foo" must not be abstract as event listeners are lazy-loaded.
+ */
+ public function testAbstractEventListener()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The service "foo" must not be abstract as event subscribers are lazy-loaded.
+ */
+ public function testAbstractEventSubscriber()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+
+ public function testEventSubscriberResolvableClassName()
+ {
+ $container = new ContainerBuilder();
+
+ $container->setParameter('subscriber.class', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService');
+ $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+
+ $definition = $container->getDefinition('event_dispatcher');
+ $expected_calls = array(
+ array(
+ 'addSubscriberService',
+ array(
+ 'foo',
+ 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService',
+ ),
+ ),
+ );
+ $this->assertSame($expected_calls, $definition->getMethodCalls());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class"
+ */
+ public function testEventSubscriberUnresolvableClassName()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+}
+
+class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php
new file mode 100755
index 0000000..5faa5c8
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php
@@ -0,0 +1,22 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+class EventDispatcherTest extends AbstractEventDispatcherTest
+{
+ protected function createEventDispatcher()
+ {
+ return new EventDispatcher();
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/EventTest.php b/vendor/symfony/event-dispatcher/Tests/EventTest.php
new file mode 100755
index 0000000..bdc14ab
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/EventTest.php
@@ -0,0 +1,97 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+/**
+ * Test class for Event.
+ */
+class EventTest extends TestCase
+{
+ /**
+ * @var \Symfony\Component\EventDispatcher\Event
+ */
+ protected $event;
+
+ /**
+ * @var \Symfony\Component\EventDispatcher\EventDispatcher
+ */
+ protected $dispatcher;
+
+ /**
+ * Sets up the fixture, for example, opens a network connection.
+ * This method is called before a test is executed.
+ */
+ protected function setUp()
+ {
+ $this->event = new Event();
+ $this->dispatcher = new EventDispatcher();
+ }
+
+ /**
+ * Tears down the fixture, for example, closes a network connection.
+ * This method is called after a test is executed.
+ */
+ protected function tearDown()
+ {
+ $this->event = null;
+ $this->dispatcher = null;
+ }
+
+ public function testIsPropagationStopped()
+ {
+ $this->assertFalse($this->event->isPropagationStopped());
+ }
+
+ public function testStopPropagationAndIsPropagationStopped()
+ {
+ $this->event->stopPropagation();
+ $this->assertTrue($this->event->isPropagationStopped());
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacySetDispatcher()
+ {
+ $this->event->setDispatcher($this->dispatcher);
+ $this->assertSame($this->dispatcher, $this->event->getDispatcher());
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacyGetDispatcher()
+ {
+ $this->assertNull($this->event->getDispatcher());
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacyGetName()
+ {
+ $this->assertNull($this->event->getName());
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacySetName()
+ {
+ $this->event->setName('foo');
+ $this->assertEquals('foo', $this->event->getName());
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php b/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php
new file mode 100755
index 0000000..b63f69d
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php
@@ -0,0 +1,136 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\GenericEvent;
+
+/**
+ * Test class for Event.
+ */
+class GenericEventTest extends TestCase
+{
+ /**
+ * @var GenericEvent
+ */
+ private $event;
+
+ private $subject;
+
+ /**
+ * Prepares the environment before running a test.
+ */
+ protected function setUp()
+ {
+ $this->subject = new \stdClass();
+ $this->event = new GenericEvent($this->subject, array('name' => 'Event'));
+ }
+
+ /**
+ * Cleans up the environment after running a test.
+ */
+ protected function tearDown()
+ {
+ $this->subject = null;
+ $this->event = null;
+ }
+
+ public function testConstruct()
+ {
+ $this->assertEquals($this->event, new GenericEvent($this->subject, array('name' => 'Event')));
+ }
+
+ /**
+ * Tests Event->getArgs().
+ */
+ public function testGetArguments()
+ {
+ // test getting all
+ $this->assertSame(array('name' => 'Event'), $this->event->getArguments());
+ }
+
+ public function testSetArguments()
+ {
+ $result = $this->event->setArguments(array('foo' => 'bar'));
+ $this->assertAttributeSame(array('foo' => 'bar'), 'arguments', $this->event);
+ $this->assertSame($this->event, $result);
+ }
+
+ public function testSetArgument()
+ {
+ $result = $this->event->setArgument('foo2', 'bar2');
+ $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
+ $this->assertEquals($this->event, $result);
+ }
+
+ public function testGetArgument()
+ {
+ // test getting key
+ $this->assertEquals('Event', $this->event->getArgument('name'));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testGetArgException()
+ {
+ $this->event->getArgument('nameNotExist');
+ }
+
+ public function testOffsetGet()
+ {
+ // test getting key
+ $this->assertEquals('Event', $this->event['name']);
+
+ // test getting invalid arg
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
+ $this->assertFalse($this->event['nameNotExist']);
+ }
+
+ public function testOffsetSet()
+ {
+ $this->event['foo2'] = 'bar2';
+ $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
+ }
+
+ public function testOffsetUnset()
+ {
+ unset($this->event['name']);
+ $this->assertAttributeSame(array(), 'arguments', $this->event);
+ }
+
+ public function testOffsetIsset()
+ {
+ $this->assertArrayHasKey('name', $this->event);
+ $this->assertArrayNotHasKey('nameNotExist', $this->event);
+ }
+
+ public function testHasArgument()
+ {
+ $this->assertTrue($this->event->hasArgument('name'));
+ $this->assertFalse($this->event->hasArgument('nameNotExist'));
+ }
+
+ public function testGetSubject()
+ {
+ $this->assertSame($this->subject, $this->event->getSubject());
+ }
+
+ public function testHasIterator()
+ {
+ $data = array();
+ foreach ($this->event as $key => $value) {
+ $data[$key] = $value;
+ }
+ $this->assertEquals(array('name' => 'Event'), $data);
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
new file mode 100755
index 0000000..04f2861
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
@@ -0,0 +1,106 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class ImmutableEventDispatcherTest extends TestCase
+{
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $innerDispatcher;
+
+ /**
+ * @var ImmutableEventDispatcher
+ */
+ private $dispatcher;
+
+ protected function setUp()
+ {
+ $this->innerDispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
+ $this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher);
+ }
+
+ public function testDispatchDelegates()
+ {
+ $event = new Event();
+
+ $this->innerDispatcher->expects($this->once())
+ ->method('dispatch')
+ ->with('event', $event)
+ ->will($this->returnValue('result'));
+
+ $this->assertSame('result', $this->dispatcher->dispatch('event', $event));
+ }
+
+ public function testGetListenersDelegates()
+ {
+ $this->innerDispatcher->expects($this->once())
+ ->method('getListeners')
+ ->with('event')
+ ->will($this->returnValue('result'));
+
+ $this->assertSame('result', $this->dispatcher->getListeners('event'));
+ }
+
+ public function testHasListenersDelegates()
+ {
+ $this->innerDispatcher->expects($this->once())
+ ->method('hasListeners')
+ ->with('event')
+ ->will($this->returnValue('result'));
+
+ $this->assertSame('result', $this->dispatcher->hasListeners('event'));
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testAddListenerDisallowed()
+ {
+ $this->dispatcher->addListener('event', function () { return 'foo'; });
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testAddSubscriberDisallowed()
+ {
+ $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock();
+
+ $this->dispatcher->addSubscriber($subscriber);
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testRemoveListenerDisallowed()
+ {
+ $this->dispatcher->removeListener('event', function () { return 'foo'; });
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testRemoveSubscriberDisallowed()
+ {
+ $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock();
+
+ $this->dispatcher->removeSubscriber($subscriber);
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/composer.json b/vendor/symfony/event-dispatcher/composer.json
new file mode 100755
index 0000000..14fc24b
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "symfony/event-dispatcher",
+ "type": "library",
+ "description": "Symfony EventDispatcher Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.9"
+ },
+ "require-dev": {
+ "symfony/dependency-injection": "~2.6|~3.0.0",
+ "symfony/expression-language": "~2.6|~3.0.0",
+ "symfony/config": "^2.0.5|~3.0.0",
+ "symfony/stopwatch": "~2.3|~3.0.0",
+ "psr/log": "~1.0"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/event-dispatcher/phpunit.xml.dist b/vendor/symfony/event-dispatcher/phpunit.xml.dist
new file mode 100755
index 0000000..f2eb169
--- /dev/null
+++ b/vendor/symfony/event-dispatcher/phpunit.xml.dist
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
+ backupGlobals="false"
+ colors="true"
+ bootstrap="vendor/autoload.php"
+ failOnRisky="true"
+ failOnWarning="true"
+>
+ <php>
+ <ini name="error_reporting" value="-1" />
+ </php>
+
+ <testsuites>
+ <testsuite name="Symfony EventDispatcher Component Test Suite">
+ <directory>./Tests/</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>./</directory>
+ <exclude>
+ <directory>./Resources</directory>
+ <directory>./Tests</directory>
+ <directory>./vendor</directory>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/vendor/symfony/http-foundation/.gitignore b/vendor/symfony/http-foundation/.gitignore
new file mode 100755
index 0000000..c49a5d8
--- /dev/null
+++ b/vendor/symfony/http-foundation/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/vendor/symfony/http-foundation/AcceptHeader.php b/vendor/symfony/http-foundation/AcceptHeader.php
new file mode 100755
index 0000000..968b71f
--- /dev/null
+++ b/vendor/symfony/http-foundation/AcceptHeader.php
@@ -0,0 +1,168 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Represents an Accept-* header.
+ *
+ * An accept header is compound with a list of items,
+ * sorted by descending quality.
+ *
+ * @author Jean-François Simon <contact@jfsimon.fr>
+ */
+class AcceptHeader
+{
+ /**
+ * @var AcceptHeaderItem[]
+ */
+ private $items = [];
+
+ /**
+ * @var bool
+ */
+ private $sorted = true;
+
+ /**
+ * @param AcceptHeaderItem[] $items
+ */
+ public function __construct(array $items)
+ {
+ foreach ($items as $item) {
+ $this->add($item);
+ }
+ }
+
+ /**
+ * Builds an AcceptHeader instance from a string.
+ *
+ * @param string $headerValue
+ *
+ * @return self
+ */
+ public static function fromString($headerValue)
+ {
+ $index = 0;
+
+ return new self(array_map(function ($itemValue) use (&$index) {
+ $item = AcceptHeaderItem::fromString($itemValue);
+ $item->setIndex($index++);
+
+ return $item;
+ }, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', $headerValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)));
+ }
+
+ /**
+ * Returns header value's string representation.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return implode(',', $this->items);
+ }
+
+ /**
+ * Tests if header has given value.
+ *
+ * @param string $value
+ *
+ * @return bool
+ */
+ public function has($value)
+ {
+ return isset($this->items[$value]);
+ }
+
+ /**
+ * Returns given value's item, if exists.
+ *
+ * @param string $value
+ *
+ * @return AcceptHeaderItem|null
+ */
+ public function get($value)
+ {
+ return isset($this->items[$value]) ? $this->items[$value] : null;
+ }
+
+ /**
+ * Adds an item.
+ *
+ * @return $this
+ */
+ public function add(AcceptHeaderItem $item)
+ {
+ $this->items[$item->getValue()] = $item;
+ $this->sorted = false;
+
+ return $this;
+ }
+
+ /**
+ * Returns all items.
+ *
+ * @return AcceptHeaderItem[]
+ */
+ public function all()
+ {
+ $this->sort();
+
+ return $this->items;
+ }
+
+ /**
+ * Filters items on their value using given regex.
+ *
+ * @param string $pattern
+ *
+ * @return self
+ */
+ public function filter($pattern)
+ {
+ return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) {
+ return preg_match($pattern, $item->getValue());
+ }));
+ }
+
+ /**
+ * Returns first item.
+ *
+ * @return AcceptHeaderItem|null
+ */
+ public function first()
+ {
+ $this->sort();
+
+ return !empty($this->items) ? reset($this->items) : null;
+ }
+
+ /**
+ * Sorts items by descending quality.
+ */
+ private function sort()
+ {
+ if (!$this->sorted) {
+ uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) {
+ $qA = $a->getQuality();
+ $qB = $b->getQuality();
+
+ if ($qA === $qB) {
+ return $a->getIndex() > $b->getIndex() ? 1 : -1;
+ }
+
+ return $qA > $qB ? -1 : 1;
+ });
+
+ $this->sorted = true;
+ }
+ }
+}
diff --git a/vendor/symfony/http-foundation/AcceptHeaderItem.php b/vendor/symfony/http-foundation/AcceptHeaderItem.php
new file mode 100755
index 0000000..f6e8968
--- /dev/null
+++ b/vendor/symfony/http-foundation/AcceptHeaderItem.php
@@ -0,0 +1,209 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Represents an Accept-* header item.
+ *
+ * @author Jean-François Simon <contact@jfsimon.fr>
+ */
+class AcceptHeaderItem
+{
+ private $value;
+ private $quality = 1.0;
+ private $index = 0;
+ private $attributes = [];
+
+ /**
+ * @param string $value
+ * @param array $attributes
+ */
+ public function __construct($value, array $attributes = [])
+ {
+ $this->value = $value;
+ foreach ($attributes as $name => $value) {
+ $this->setAttribute($name, $value);
+ }
+ }
+
+ /**
+ * Builds an AcceptHeaderInstance instance from a string.
+ *
+ * @param string $itemValue
+ *
+ * @return self
+ */
+ public static function fromString($itemValue)
+ {
+ $bits = preg_split('/\s*(?:;*("[^"]+");*|;*(\'[^\']+\');*|;+)\s*/', $itemValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+ $value = array_shift($bits);
+ $attributes = [];
+
+ $lastNullAttribute = null;
+ foreach ($bits as $bit) {
+ if (($start = substr($bit, 0, 1)) === ($end = substr($bit, -1)) && ('"' === $start || '\'' === $start)) {
+ $attributes[$lastNullAttribute] = substr($bit, 1, -1);
+ } elseif ('=' === $end) {
+ $lastNullAttribute = $bit = substr($bit, 0, -1);
+ $attributes[$bit] = null;
+ } else {
+ $parts = explode('=', $bit);
+ $attributes[$parts[0]] = isset($parts[1]) && \strlen($parts[1]) > 0 ? $parts[1] : '';
+ }
+ }
+
+ return new self(($start = substr($value, 0, 1)) === ($end = substr($value, -1)) && ('"' === $start || '\'' === $start) ? substr($value, 1, -1) : $value, $attributes);
+ }
+
+ /**
+ * Returns header value's string representation.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : '');
+ if (\count($this->attributes) > 0) {
+ $string .= ';'.implode(';', array_map(function ($name, $value) {
+ return sprintf(preg_match('/[,;=]/', $value) ? '%s="%s"' : '%s=%s', $name, $value);
+ }, array_keys($this->attributes), $this->attributes));
+ }
+
+ return $string;
+ }
+
+ /**
+ * Set the item value.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setValue($value)
+ {
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * Returns the item value.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Set the item quality.
+ *
+ * @param float $quality
+ *
+ * @return $this
+ */
+ public function setQuality($quality)
+ {
+ $this->quality = $quality;
+
+ return $this;
+ }
+
+ /**
+ * Returns the item quality.
+ *
+ * @return float
+ */
+ public function getQuality()
+ {
+ return $this->quality;
+ }
+
+ /**
+ * Set the item index.
+ *
+ * @param int $index
+ *
+ * @return $this
+ */
+ public function setIndex($index)
+ {
+ $this->index = $index;
+
+ return $this;
+ }
+
+ /**
+ * Returns the item index.
+ *
+ * @return int
+ */
+ public function getIndex()
+ {
+ return $this->index;
+ }
+
+ /**
+ * Tests if an attribute exists.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function hasAttribute($name)
+ {
+ return isset($this->attributes[$name]);
+ }
+
+ /**
+ * Returns an attribute by its name.
+ *
+ * @param string $name
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getAttribute($name, $default = null)
+ {
+ return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
+ }
+
+ /**
+ * Returns all attributes.
+ *
+ * @return array
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * Set an attribute.
+ *
+ * @param string $name
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setAttribute($name, $value)
+ {
+ if ('q' === $name) {
+ $this->quality = (float) $value;
+ } else {
+ $this->attributes[$name] = (string) $value;
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/symfony/http-foundation/ApacheRequest.php b/vendor/symfony/http-foundation/ApacheRequest.php
new file mode 100755
index 0000000..4e99186
--- /dev/null
+++ b/vendor/symfony/http-foundation/ApacheRequest.php
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Request represents an HTTP request from an Apache server.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class ApacheRequest extends Request
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function prepareRequestUri()
+ {
+ return $this->server->get('REQUEST_URI');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function prepareBaseUrl()
+ {
+ $baseUrl = $this->server->get('SCRIPT_NAME');
+
+ if (false === strpos($this->server->get('REQUEST_URI'), $baseUrl)) {
+ // assume mod_rewrite
+ return rtrim(\dirname($baseUrl), '/\\');
+ }
+
+ return $baseUrl;
+ }
+}
diff --git a/vendor/symfony/http-foundation/BinaryFileResponse.php b/vendor/symfony/http-foundation/BinaryFileResponse.php
new file mode 100755
index 0000000..cf648df
--- /dev/null
+++ b/vendor/symfony/http-foundation/BinaryFileResponse.php
@@ -0,0 +1,359 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
+use Symfony\Component\HttpFoundation\File\File;
+
+/**
+ * BinaryFileResponse represents an HTTP response delivering a file.
+ *
+ * @author Niklas Fiekas <niklas.fiekas@tu-clausthal.de>
+ * @author stealth35 <stealth35-php@live.fr>
+ * @author Igor Wiedler <igor@wiedler.ch>
+ * @author Jordan Alliot <jordan.alliot@gmail.com>
+ * @author Sergey Linnik <linniksa@gmail.com>
+ */
+class BinaryFileResponse extends Response
+{
+ protected static $trustXSendfileTypeHeader = false;
+
+ /**
+ * @var File
+ */
+ protected $file;
+ protected $offset = 0;
+ protected $maxlen = -1;
+ protected $deleteFileAfterSend = false;
+
+ /**
+ * @param \SplFileInfo|string $file The file to stream
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ * @param bool $public Files are public by default
+ * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename
+ * @param bool $autoEtag Whether the ETag header should be automatically set
+ * @param bool $autoLastModified Whether the Last-Modified header should be automatically set
+ */
+ public function __construct($file, $status = 200, $headers = [], $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
+ {
+ parent::__construct(null, $status, $headers);
+
+ $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified);
+
+ if ($public) {
+ $this->setPublic();
+ }
+ }
+
+ /**
+ * @param \SplFileInfo|string $file The file to stream
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ * @param bool $public Files are public by default
+ * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename
+ * @param bool $autoEtag Whether the ETag header should be automatically set
+ * @param bool $autoLastModified Whether the Last-Modified header should be automatically set
+ *
+ * @return static
+ */
+ public static function create($file = null, $status = 200, $headers = [], $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
+ {
+ return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified);
+ }
+
+ /**
+ * Sets the file to stream.
+ *
+ * @param \SplFileInfo|string $file The file to stream
+ * @param string $contentDisposition
+ * @param bool $autoEtag
+ * @param bool $autoLastModified
+ *
+ * @return $this
+ *
+ * @throws FileException
+ */
+ public function setFile($file, $contentDisposition = null, $autoEtag = false, $autoLastModified = true)
+ {
+ if (!$file instanceof File) {
+ if ($file instanceof \SplFileInfo) {
+ $file = new File($file->getPathname());
+ } else {
+ $file = new File((string) $file);
+ }
+ }
+
+ if (!$file->isReadable()) {
+ throw new FileException('File must be readable.');
+ }
+
+ $this->file = $file;
+
+ if ($autoEtag) {
+ $this->setAutoEtag();
+ }
+
+ if ($autoLastModified) {
+ $this->setAutoLastModified();
+ }
+
+ if ($contentDisposition) {
+ $this->setContentDisposition($contentDisposition);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Gets the file.
+ *
+ * @return File The file to stream
+ */
+ public function getFile()
+ {
+ return $this->file;
+ }
+
+ /**
+ * Automatically sets the Last-Modified header according the file modification date.
+ */
+ public function setAutoLastModified()
+ {
+ $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime()));
+
+ return $this;
+ }
+
+ /**
+ * Automatically sets the ETag header according to the checksum of the file.
+ */
+ public function setAutoEtag()
+ {
+ $this->setEtag(base64_encode(hash_file('sha256', $this->file->getPathname(), true)));
+
+ return $this;
+ }
+
+ /**
+ * Sets the Content-Disposition header with the given filename.
+ *
+ * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT
+ * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file
+ * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename
+ *
+ * @return $this
+ */
+ public function setContentDisposition($disposition, $filename = '', $filenameFallback = '')
+ {
+ if ('' === $filename) {
+ $filename = $this->file->getFilename();
+ }
+
+ if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) {
+ $encoding = mb_detect_encoding($filename, null, true) ?: '8bit';
+
+ for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) {
+ $char = mb_substr($filename, $i, 1, $encoding);
+
+ if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) {
+ $filenameFallback .= '_';
+ } else {
+ $filenameFallback .= $char;
+ }
+ }
+ }
+
+ $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback);
+ $this->headers->set('Content-Disposition', $dispositionHeader);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prepare(Request $request)
+ {
+ if (!$this->headers->has('Content-Type')) {
+ $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
+ }
+
+ if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) {
+ $this->setProtocolVersion('1.1');
+ }
+
+ $this->ensureIEOverSSLCompatibility($request);
+
+ $this->offset = 0;
+ $this->maxlen = -1;
+
+ if (false === $fileSize = $this->file->getSize()) {
+ return $this;
+ }
+ $this->headers->set('Content-Length', $fileSize);
+
+ if (!$this->headers->has('Accept-Ranges')) {
+ // Only accept ranges on safe HTTP methods
+ $this->headers->set('Accept-Ranges', $request->isMethodSafe(false) ? 'bytes' : 'none');
+ }
+
+ if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) {
+ // Use X-Sendfile, do not send any content.
+ $type = $request->headers->get('X-Sendfile-Type');
+ $path = $this->file->getRealPath();
+ // Fall back to scheme://path for stream wrapped locations.
+ if (false === $path) {
+ $path = $this->file->getPathname();
+ }
+ if ('x-accel-redirect' === strtolower($type)) {
+ // Do X-Accel-Mapping substitutions.
+ // @link http://wiki.nginx.org/X-accel#X-Accel-Redirect
+ foreach (explode(',', $request->headers->get('X-Accel-Mapping', '')) as $mapping) {
+ $mapping = explode('=', $mapping, 2);
+
+ if (2 === \count($mapping)) {
+ $pathPrefix = trim($mapping[0]);
+ $location = trim($mapping[1]);
+
+ if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) {
+ $path = $location.substr($path, \strlen($pathPrefix));
+ break;
+ }
+ }
+ }
+ }
+ $this->headers->set($type, $path);
+ $this->maxlen = 0;
+ } elseif ($request->headers->has('Range')) {
+ // Process the range headers.
+ if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) {
+ $range = $request->headers->get('Range');
+
+ list($start, $end) = explode('-', substr($range, 6), 2) + [0];
+
+ $end = ('' === $end) ? $fileSize - 1 : (int) $end;
+
+ if ('' === $start) {
+ $start = $fileSize - $end;
+ $end = $fileSize - 1;
+ } else {
+ $start = (int) $start;
+ }
+
+ if ($start <= $end) {
+ if ($start < 0 || $end > $fileSize - 1) {
+ $this->setStatusCode(416);
+ $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize));
+ } elseif (0 !== $start || $end !== $fileSize - 1) {
+ $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
+ $this->offset = $start;
+
+ $this->setStatusCode(206);
+ $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
+ $this->headers->set('Content-Length', $end - $start + 1);
+ }
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ private function hasValidIfRangeHeader($header)
+ {
+ if ($this->getEtag() === $header) {
+ return true;
+ }
+
+ if (null === $lastModified = $this->getLastModified()) {
+ return false;
+ }
+
+ return $lastModified->format('D, d M Y H:i:s').' GMT' === $header;
+ }
+
+ /**
+ * Sends the file.
+ *
+ * {@inheritdoc}
+ */
+ public function sendContent()
+ {
+ if (!$this->isSuccessful()) {
+ return parent::sendContent();
+ }
+
+ if (0 === $this->maxlen) {
+ return $this;
+ }
+
+ $out = fopen('php://output', 'wb');
+ $file = fopen($this->file->getPathname(), 'rb');
+
+ stream_copy_to_stream($file, $out, $this->maxlen, $this->offset);
+
+ fclose($out);
+ fclose($file);
+
+ if ($this->deleteFileAfterSend && file_exists($this->file->getPathname())) {
+ unlink($this->file->getPathname());
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \LogicException when the content is not null
+ */
+ public function setContent($content)
+ {
+ if (null !== $content) {
+ throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return false
+ */
+ public function getContent()
+ {
+ return false;
+ }
+
+ /**
+ * Trust X-Sendfile-Type header.
+ */
+ public static function trustXSendfileTypeHeader()
+ {
+ self::$trustXSendfileTypeHeader = true;
+ }
+
+ /**
+ * If this is set to true, the file will be unlinked after the request is send
+ * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used.
+ *
+ * @param bool $shouldDelete
+ *
+ * @return $this
+ */
+ public function deleteFileAfterSend($shouldDelete)
+ {
+ $this->deleteFileAfterSend = $shouldDelete;
+
+ return $this;
+ }
+}
diff --git a/vendor/symfony/http-foundation/CHANGELOG.md b/vendor/symfony/http-foundation/CHANGELOG.md
new file mode 100755
index 0000000..7bfde80
--- /dev/null
+++ b/vendor/symfony/http-foundation/CHANGELOG.md
@@ -0,0 +1,165 @@
+CHANGELOG
+=========
+
+3.4.14
+------
+
+ * [BC BREAK] Support for the IIS-only `X_ORIGINAL_URL` and `X_REWRITE_URL`
+ HTTP headers has been dropped for security reasons.
+
+3.4.0
+-----
+
+ * implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new
+ `AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper
+ * deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes
+ * deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()`
+ * deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead
+ * deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead
+
+3.3.0
+-----
+
+ * the `Request::setTrustedProxies()` method takes a new `$trustedHeaderSet` argument,
+ see http://symfony.com/doc/current/components/http_foundation/trusting_proxies.html for more info,
+ * deprecated the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` methods,
+ * added `File\Stream`, to be passed to `BinaryFileResponse` when the size of the served file is unknown,
+ disabling `Range` and `Content-Length` handling, switching to chunked encoding instead
+ * added the `Cookie::fromString()` method that allows to create a cookie from a
+ raw header string
+
+3.1.0
+-----
+
+ * Added support for creating `JsonResponse` with a string of JSON data
+
+3.0.0
+-----
+
+ * The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY"
+
+2.8.0
+-----
+
+ * Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and
+ will be removed in 3.0.
+
+2.6.0
+-----
+
+ * PdoSessionHandler changes
+ - implemented different session locking strategies to prevent loss of data by concurrent access to the same session
+ - [BC BREAK] save session data in a binary column without base64_encode
+ - [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session
+ - implemented lazy connections that are only opened when a session is used by either passing a dsn string
+ explicitly or falling back to session.save_path ini setting
+ - added a createTable method that initializes a correctly defined table depending on the database vendor
+
+2.5.0
+-----
+
+ * added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation
+ of the options used while encoding data to JSON format.
+
+2.4.0
+-----
+
+ * added RequestStack
+ * added Request::getEncodings()
+ * added accessors methods to session handlers
+
+2.3.0
+-----
+
+ * added support for ranges of IPs in trusted proxies
+ * `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode)
+ * Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler`
+ to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases
+ to verify that Exceptions are properly thrown when the PDO queries fail.
+
+2.2.0
+-----
+
+ * fixed the Request::create() precedence (URI information always take precedence now)
+ * added Request::getTrustedProxies()
+ * deprecated Request::isProxyTrusted()
+ * [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects
+ * added a IpUtils class to check if an IP belongs to a CIDR
+ * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method)
+ * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to
+ enable it, and Request::getHttpMethodParameterOverride() to check if it is supported)
+ * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3
+ * Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3
+
+2.1.0
+-----
+
+ * added Request::getSchemeAndHttpHost() and Request::getUserInfo()
+ * added a fluent interface to the Response class
+ * added Request::isProxyTrusted()
+ * added JsonResponse
+ * added a getTargetUrl method to RedirectResponse
+ * added support for streamed responses
+ * made Response::prepare() method the place to enforce HTTP specification
+ * [BC BREAK] moved management of the locale from the Session class to the Request class
+ * added a generic access to the PHP built-in filter mechanism: ParameterBag::filter()
+ * made FileBinaryMimeTypeGuesser command configurable
+ * added Request::getUser() and Request::getPassword()
+ * added support for the PATCH method in Request
+ * removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3
+ * added ResponseHeaderBag::makeDisposition() (implements RFC 6266)
+ * made mimetype to extension conversion configurable
+ * [BC BREAK] Moved all session related classes and interfaces into own namespace, as
+ `Symfony\Component\HttpFoundation\Session` and renamed classes accordingly.
+ Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`.
+ * SessionHandlers must implement `\SessionHandlerInterface` or extend from the
+ `Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class.
+ * Added internal storage driver proxy mechanism for forward compatibility with
+ PHP 5.4 `\SessionHandler` class.
+ * Added session handlers for custom Memcache, Memcached and Null session save handlers.
+ * [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`.
+ * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and
+ `remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class
+ is a mediator for the session storage internals including the session handlers
+ which do the real work of participating in the internal PHP session workflow.
+ * [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit
+ and functional testing without starting real PHP sessions. Removed
+ `ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit
+ tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage`
+ for functional tests. These do not interact with global session ini
+ configuration values, session functions or `$_SESSION` superglobal. This means
+ they can be configured directly allowing multiple instances to work without
+ conflicting in the same PHP process.
+ * [BC BREAK] Removed the `close()` method from the `Session` class, as this is
+ now redundant.
+ * Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()`
+ `getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead
+ which returns a `FlashBagInterface`.
+ * `Session->clear()` now only clears session attributes as before it cleared
+ flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now.
+ * Session data is now managed by `SessionBagInterface` to better encapsulate
+ session data.
+ * Refactored session attribute and flash messages system to their own
+ `SessionBagInterface` implementations.
+ * Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This
+ implementation is ESI compatible.
+ * Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire
+ behavior of messages auto expiring after one page page load. Messages must
+ be retrieved by `get()` or `all()`.
+ * Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate
+ attributes storage behavior from 2.0.x (default).
+ * Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for
+ namespace session attributes.
+ * Flash API can stores messages in an array so there may be multiple messages
+ per flash type. The old `Session` class API remains without BC break as it
+ will allow single messages as before.
+ * Added basic session meta-data to the session to record session create time,
+ last updated time, and the lifetime of the session cookie that was provided
+ to the client.
+ * Request::getClientIp() method doesn't take a parameter anymore but bases
+ itself on the trustProxy parameter.
+ * Added isMethod() to Request object.
+ * [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of
+ a `Request` now all return a raw value (vs a urldecoded value before). Any call
+ to one of these methods must be checked and wrapped in a `rawurldecode()` if
+ needed.
diff --git a/vendor/symfony/http-foundation/Cookie.php b/vendor/symfony/http-foundation/Cookie.php
new file mode 100755
index 0000000..83a9708
--- /dev/null
+++ b/vendor/symfony/http-foundation/Cookie.php
@@ -0,0 +1,292 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Represents a cookie.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class Cookie
+{
+ protected $name;
+ protected $value;
+ protected $domain;
+ protected $expire;
+ protected $path;
+ protected $secure;
+ protected $httpOnly;
+ private $raw;
+ private $sameSite;
+
+ const SAMESITE_NONE = 'none';
+ const SAMESITE_LAX = 'lax';
+ const SAMESITE_STRICT = 'strict';
+
+ /**
+ * Creates cookie from raw header string.
+ *
+ * @param string $cookie
+ * @param bool $decode
+ *
+ * @return static
+ */
+ public static function fromString($cookie, $decode = false)
+ {
+ $data = [
+ 'expires' => 0,
+ 'path' => '/',
+ 'domain' => null,
+ 'secure' => false,
+ 'httponly' => false,
+ 'raw' => !$decode,
+ 'samesite' => null,
+ ];
+ foreach (explode(';', $cookie) as $part) {
+ if (false === strpos($part, '=')) {
+ $key = trim($part);
+ $value = true;
+ } else {
+ list($key, $value) = explode('=', trim($part), 2);
+ $key = trim($key);
+ $value = trim($value);
+ }
+ if (!isset($data['name'])) {
+ $data['name'] = $decode ? urldecode($key) : $key;
+ $data['value'] = true === $value ? null : ($decode ? urldecode($value) : $value);
+ continue;
+ }
+ switch ($key = strtolower($key)) {
+ case 'name':
+ case 'value':
+ break;
+ case 'max-age':
+ $data['expires'] = time() + (int) $value;
+ break;
+ default:
+ $data[$key] = $value;
+ break;
+ }
+ }
+
+ return new static($data['name'], $data['value'], $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']);
+ }
+
+ /**
+ * @param string $name The name of the cookie
+ * @param string|null $value The value of the cookie
+ * @param int|string|\DateTimeInterface $expire The time the cookie expires
+ * @param string $path The path on the server in which the cookie will be available on
+ * @param string|null $domain The domain that the cookie is available to
+ * @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client
+ * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
+ * @param bool $raw Whether the cookie value should be sent with no url encoding
+ * @param string|null $sameSite Whether the cookie will be available for cross-site requests
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null)
+ {
+ // from PHP source code
+ if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
+ throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
+ }
+
+ if (empty($name)) {
+ throw new \InvalidArgumentException('The cookie name cannot be empty.');
+ }
+
+ // convert expiration time to a Unix timestamp
+ if ($expire instanceof \DateTimeInterface) {
+ $expire = $expire->format('U');
+ } elseif (!is_numeric($expire)) {
+ $expire = strtotime($expire);
+
+ if (false === $expire) {
+ throw new \InvalidArgumentException('The cookie expiration time is not valid.');
+ }
+ }
+
+ $this->name = $name;
+ $this->value = $value;
+ $this->domain = $domain;
+ $this->expire = 0 < $expire ? (int) $expire : 0;
+ $this->path = empty($path) ? '/' : $path;
+ $this->secure = (bool) $secure;
+ $this->httpOnly = (bool) $httpOnly;
+ $this->raw = (bool) $raw;
+
+ if (null !== $sameSite) {
+ $sameSite = strtolower($sameSite);
+ }
+
+ if (!\in_array($sameSite, [self::SAMESITE_LAX, self::SAMESITE_STRICT, self::SAMESITE_NONE, null], true)) {
+ throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.');
+ }
+
+ $this->sameSite = $sameSite;
+ }
+
+ /**
+ * Returns the cookie as a string.
+ *
+ * @return string The cookie
+ */
+ public function __toString()
+ {
+ $str = ($this->isRaw() ? $this->getName() : urlencode($this->getName())).'=';
+
+ if ('' === (string) $this->getValue()) {
+ $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0';
+ } else {
+ $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue());
+
+ if (0 !== $this->getExpiresTime()) {
+ $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge();
+ }
+ }
+
+ if ($this->getPath()) {
+ $str .= '; path='.$this->getPath();
+ }
+
+ if ($this->getDomain()) {
+ $str .= '; domain='.$this->getDomain();
+ }
+
+ if (true === $this->isSecure()) {
+ $str .= '; secure';
+ }
+
+ if (true === $this->isHttpOnly()) {
+ $str .= '; httponly';
+ }
+
+ if (null !== $this->getSameSite()) {
+ $str .= '; samesite='.$this->getSameSite();
+ }
+
+ return $str;
+ }
+
+ /**
+ * Gets the name of the cookie.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Gets the value of the cookie.
+ *
+ * @return string|null
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Gets the domain that the cookie is available to.
+ *
+ * @return string|null
+ */
+ public function getDomain()
+ {
+ return $this->domain;
+ }
+
+ /**
+ * Gets the time the cookie expires.
+ *
+ * @return int
+ */
+ public function getExpiresTime()
+ {
+ return $this->expire;
+ }
+
+ /**
+ * Gets the max-age attribute.
+ *
+ * @return int
+ */
+ public function getMaxAge()
+ {
+ $maxAge = $this->expire - time();
+
+ return 0 >= $maxAge ? 0 : $maxAge;
+ }
+
+ /**
+ * Gets the path on the server in which the cookie will be available on.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client.
+ *
+ * @return bool
+ */
+ public function isSecure()
+ {
+ return $this->secure;
+ }
+
+ /**
+ * Checks whether the cookie will be made accessible only through the HTTP protocol.
+ *
+ * @return bool
+ */
+ public function isHttpOnly()
+ {
+ return $this->httpOnly;
+ }
+
+ /**
+ * Whether this cookie is about to be cleared.
+ *
+ * @return bool
+ */
+ public function isCleared()
+ {
+ return 0 !== $this->expire && $this->expire < time();
+ }
+
+ /**
+ * Checks if the cookie value should be sent with no url encoding.
+ *
+ * @return bool
+ */
+ public function isRaw()
+ {
+ return $this->raw;
+ }
+
+ /**
+ * Gets the SameSite attribute.
+ *
+ * @return string|null
+ */
+ public function getSameSite()
+ {
+ return $this->sameSite;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php
new file mode 100755
index 0000000..5fcf5b4
--- /dev/null
+++ b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * The HTTP request contains headers with conflicting information.
+ *
+ * @author Magnus Nordlander <magnus@fervo.se>
+ */
+class ConflictingHeadersException extends \UnexpectedValueException implements RequestExceptionInterface
+{
+}
diff --git a/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php b/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php
new file mode 100755
index 0000000..478d0dc
--- /dev/null
+++ b/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * Interface for Request exceptions.
+ *
+ * Exceptions implementing this interface should trigger an HTTP 400 response in the application code.
+ */
+interface RequestExceptionInterface
+{
+}
diff --git a/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php b/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php
new file mode 100755
index 0000000..ae7a5f1
--- /dev/null
+++ b/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php
@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Exception;
+
+/**
+ * Raised when a user has performed an operation that should be considered
+ * suspicious from a security perspective.
+ */
+class SuspiciousOperationException extends \UnexpectedValueException implements RequestExceptionInterface
+{
+}
diff --git a/vendor/symfony/http-foundation/ExpressionRequestMatcher.php b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php
new file mode 100755
index 0000000..26bed7d
--- /dev/null
+++ b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php
@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+
+/**
+ * ExpressionRequestMatcher uses an expression to match a Request.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class ExpressionRequestMatcher extends RequestMatcher
+{
+ private $language;
+ private $expression;
+
+ public function setExpression(ExpressionLanguage $language, $expression)
+ {
+ $this->language = $language;
+ $this->expression = $expression;
+ }
+
+ public function matches(Request $request)
+ {
+ if (!$this->language) {
+ throw new \LogicException('Unable to match the request as the expression language is not available.');
+ }
+
+ return $this->language->evaluate($this->expression, [
+ 'request' => $request,
+ 'method' => $request->getMethod(),
+ 'path' => rawurldecode($request->getPathInfo()),
+ 'host' => $request->getHost(),
+ 'ip' => $request->getClientIp(),
+ 'attributes' => $request->attributes->all(),
+ ]) && parent::matches($request);
+ }
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php
new file mode 100755
index 0000000..3b8e41d
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when the access on a file was denied.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class AccessDeniedException extends FileException
+{
+ /**
+ * @param string $path The path to the accessed file
+ */
+ public function __construct($path)
+ {
+ parent::__construct(sprintf('The file %s could not be accessed', $path));
+ }
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/FileException.php b/vendor/symfony/http-foundation/File/Exception/FileException.php
new file mode 100755
index 0000000..fad5133
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/FileException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an error occurred in the component File.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class FileException extends \RuntimeException
+{
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php
new file mode 100755
index 0000000..bfcc37e
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when a file was not found.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class FileNotFoundException extends FileException
+{
+ /**
+ * @param string $path The path to the file that was not found
+ */
+ public function __construct($path)
+ {
+ parent::__construct(sprintf('The file "%s" does not exist', $path));
+ }
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php
new file mode 100755
index 0000000..62005d3
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php
@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+class UnexpectedTypeException extends FileException
+{
+ public function __construct($value, $expectedType)
+ {
+ parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, \is_object($value) ? \get_class($value) : \gettype($value)));
+ }
+}
diff --git a/vendor/symfony/http-foundation/File/Exception/UploadException.php b/vendor/symfony/http-foundation/File/Exception/UploadException.php
new file mode 100755
index 0000000..7074e76
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Exception/UploadException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\Exception;
+
+/**
+ * Thrown when an error occurred during file upload.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class UploadException extends FileException
+{
+}
diff --git a/vendor/symfony/http-foundation/File/File.php b/vendor/symfony/http-foundation/File/File.php
new file mode 100755
index 0000000..3422058
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/File.php
@@ -0,0 +1,138 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
+use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
+
+/**
+ * A file in the file system.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class File extends \SplFileInfo
+{
+ /**
+ * Constructs a new file from the given path.
+ *
+ * @param string $path The path to the file
+ * @param bool $checkPath Whether to check the path or not
+ *
+ * @throws FileNotFoundException If the given path is not a file
+ */
+ public function __construct($path, $checkPath = true)
+ {
+ if ($checkPath && !is_file($path)) {
+ throw new FileNotFoundException($path);
+ }
+
+ parent::__construct($path);
+ }
+
+ /**
+ * Returns the extension based on the mime type.
+ *
+ * If the mime type is unknown, returns null.
+ *
+ * This method uses the mime type as guessed by getMimeType()
+ * to guess the file extension.
+ *
+ * @return string|null The guessed extension or null if it cannot be guessed
+ *
+ * @see ExtensionGuesser
+ * @see getMimeType()
+ */
+ public function guessExtension()
+ {
+ $type = $this->getMimeType();
+ $guesser = ExtensionGuesser::getInstance();
+
+ return $guesser->guess($type);
+ }
+
+ /**
+ * Returns the mime type of the file.
+ *
+ * The mime type is guessed using a MimeTypeGuesser instance, which uses finfo(),
+ * mime_content_type() and the system binary "file" (in this order), depending on
+ * which of those are available.
+ *
+ * @return string|null The guessed mime type (e.g. "application/pdf")
+ *
+ * @see MimeTypeGuesser
+ */
+ public function getMimeType()
+ {
+ $guesser = MimeTypeGuesser::getInstance();
+
+ return $guesser->guess($this->getPathname());
+ }
+
+ /**
+ * Moves the file to a new location.
+ *
+ * @param string $directory The destination folder
+ * @param string $name The new file name
+ *
+ * @return self A File object representing the new file
+ *
+ * @throws FileException if the target file could not be created
+ */
+ public function move($directory, $name = null)
+ {
+ $target = $this->getTargetFile($directory, $name);
+
+ set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
+ $renamed = rename($this->getPathname(), $target);
+ restore_error_handler();
+ if (!$renamed) {
+ throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
+ }
+
+ @chmod($target, 0666 & ~umask());
+
+ return $target;
+ }
+
+ protected function getTargetFile($directory, $name = null)
+ {
+ if (!is_dir($directory)) {
+ if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
+ throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
+ }
+ } elseif (!is_writable($directory)) {
+ throw new FileException(sprintf('Unable to write in the "%s" directory', $directory));
+ }
+
+ $target = rtrim($directory, '/\\').\DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name));
+
+ return new self($target, false);
+ }
+
+ /**
+ * Returns locale independent base name of the given path.
+ *
+ * @param string $name The new file name
+ *
+ * @return string containing
+ */
+ protected function getName($name)
+ {
+ $originalName = str_replace('\\', '/', $name);
+ $pos = strrpos($originalName, '/');
+ $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1);
+
+ return $originalName;
+ }
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php
new file mode 100755
index 0000000..80f4d47
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php
@@ -0,0 +1,94 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+/**
+ * A singleton mime type to file extension guesser.
+ *
+ * A default guesser is provided.
+ * You can register custom guessers by calling the register()
+ * method on the singleton instance:
+ *
+ * $guesser = ExtensionGuesser::getInstance();
+ * $guesser->register(new MyCustomExtensionGuesser());
+ *
+ * The last registered guesser is preferred over previously registered ones.
+ */
+class ExtensionGuesser implements ExtensionGuesserInterface
+{
+ /**
+ * The singleton instance.
+ *
+ * @var ExtensionGuesser
+ */
+ private static $instance = null;
+
+ /**
+ * All registered ExtensionGuesserInterface instances.
+ *
+ * @var array
+ */
+ protected $guessers = [];
+
+ /**
+ * Returns the singleton instance.
+ *
+ * @return self
+ */
+ public static function getInstance()
+ {
+ if (null === self::$instance) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Registers all natively provided extension guessers.
+ */
+ private function __construct()
+ {
+ $this->register(new MimeTypeExtensionGuesser());
+ }
+
+ /**
+ * Registers a new extension guesser.
+ *
+ * When guessing, this guesser is preferred over previously registered ones.
+ */
+ public function register(ExtensionGuesserInterface $guesser)
+ {
+ array_unshift($this->guessers, $guesser);
+ }
+
+ /**
+ * Tries to guess the extension.
+ *
+ * The mime type is passed to each registered mime type guesser in reverse order
+ * of their registration (last registered is queried first). Once a guesser
+ * returns a value that is not NULL, this method terminates and returns the
+ * value.
+ *
+ * @param string $mimeType The mime type
+ *
+ * @return string The guessed extension or NULL, if none could be guessed
+ */
+ public function guess($mimeType)
+ {
+ foreach ($this->guessers as $guesser) {
+ if (null !== $extension = $guesser->guess($mimeType)) {
+ return $extension;
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php
new file mode 100755
index 0000000..d19a0e5
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+/**
+ * Guesses the file extension corresponding to a given mime type.
+ */
+interface ExtensionGuesserInterface
+{
+ /**
+ * Makes a best guess for a file extension, given a mime type.
+ *
+ * @param string $mimeType The mime type
+ *
+ * @return string The guessed extension or NULL, if none could be guessed
+ */
+ public function guess($mimeType);
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php
new file mode 100755
index 0000000..34e015e
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php
@@ -0,0 +1,99 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+
+/**
+ * Guesses the mime type with the binary "file" (only available on *nix).
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
+{
+ private $cmd;
+
+ /**
+ * The $cmd pattern must contain a "%s" string that will be replaced
+ * with the file name to guess.
+ *
+ * The command output must start with the mime type of the file.
+ *
+ * @param string $cmd The command to run to get the mime type of a file
+ */
+ public function __construct($cmd = 'file -b --mime %s 2>/dev/null')
+ {
+ $this->cmd = $cmd;
+ }
+
+ /**
+ * Returns whether this guesser is supported on the current OS.
+ *
+ * @return bool
+ */
+ public static function isSupported()
+ {
+ static $supported = null;
+
+ if (null !== $supported) {
+ return $supported;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('passthru') || !\function_exists('escapeshellarg')) {
+ return $supported = false;
+ }
+
+ ob_start();
+ passthru('command -v file', $exitStatus);
+ $binPath = trim(ob_get_clean());
+
+ return $supported = 0 === $exitStatus && '' !== $binPath;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function guess($path)
+ {
+ if (!is_file($path)) {
+ throw new FileNotFoundException($path);
+ }
+
+ if (!is_readable($path)) {
+ throw new AccessDeniedException($path);
+ }
+
+ if (!self::isSupported()) {
+ return;
+ }
+
+ ob_start();
+
+ // need to use --mime instead of -i. see #6641
+ passthru(sprintf($this->cmd, escapeshellarg($path)), $return);
+ if ($return > 0) {
+ ob_end_clean();
+
+ return;
+ }
+
+ $type = trim(ob_get_clean());
+
+ if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) {
+ // it's not a type, but an error message
+ return;
+ }
+
+ return $match[1];
+ }
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php
new file mode 100755
index 0000000..bf1ee9f
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+
+/**
+ * Guesses the mime type using the PECL extension FileInfo.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
+{
+ private $magicFile;
+
+ /**
+ * @param string $magicFile A magic file to use with the finfo instance
+ *
+ * @see http://www.php.net/manual/en/function.finfo-open.php
+ */
+ public function __construct($magicFile = null)
+ {
+ $this->magicFile = $magicFile;
+ }
+
+ /**
+ * Returns whether this guesser is supported on the current OS/PHP setup.
+ *
+ * @return bool
+ */
+ public static function isSupported()
+ {
+ return \function_exists('finfo_open');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function guess($path)
+ {
+ if (!is_file($path)) {
+ throw new FileNotFoundException($path);
+ }
+
+ if (!is_readable($path)) {
+ throw new AccessDeniedException($path);
+ }
+
+ if (!self::isSupported()) {
+ return;
+ }
+
+ if (!$finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) {
+ return;
+ }
+
+ return $finfo->file($path);
+ }
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php
new file mode 100755
index 0000000..c0f9140
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php
@@ -0,0 +1,819 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+/**
+ * Provides a best-guess mapping of mime type to file extension.
+ */
+class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
+{
+ /**
+ * A map of mime types and their default extensions.
+ *
+ * This list has been placed under the public domain by the Apache HTTPD project.
+ * This list has been updated from upstream on 2019-01-14.
+ *
+ * @see https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
+ */
+ protected $defaultExtensions = [
+ 'application/andrew-inset' => 'ez',
+ 'application/applixware' => 'aw',
+ 'application/atom+xml' => 'atom',
+ 'application/atomcat+xml' => 'atomcat',
+ 'application/atomsvc+xml' => 'atomsvc',
+ 'application/ccxml+xml' => 'ccxml',
+ 'application/cdmi-capability' => 'cdmia',
+ 'application/cdmi-container' => 'cdmic',
+ 'application/cdmi-domain' => 'cdmid',
+ 'application/cdmi-object' => 'cdmio',
+ 'application/cdmi-queue' => 'cdmiq',
+ 'application/cu-seeme' => 'cu',
+ 'application/davmount+xml' => 'davmount',
+ 'application/docbook+xml' => 'dbk',
+ 'application/dssc+der' => 'dssc',
+ 'application/dssc+xml' => 'xdssc',
+ 'application/ecmascript' => 'ecma',
+ 'application/emma+xml' => 'emma',
+ 'application/epub+zip' => 'epub',
+ 'application/exi' => 'exi',
+ 'application/font-tdpfr' => 'pfr',
+ 'application/gml+xml' => 'gml',
+ 'application/gpx+xml' => 'gpx',
+ 'application/gxf' => 'gxf',
+ 'application/hyperstudio' => 'stk',
+ 'application/inkml+xml' => 'ink',
+ 'application/ipfix' => 'ipfix',
+ 'application/java-archive' => 'jar',
+ 'application/java-serialized-object' => 'ser',
+ 'application/java-vm' => 'class',
+ 'application/javascript' => 'js',
+ 'application/json' => 'json',
+ 'application/jsonml+json' => 'jsonml',
+ 'application/lost+xml' => 'lostxml',
+ 'application/mac-binhex40' => 'hqx',
+ 'application/mac-compactpro' => 'cpt',
+ 'application/mads+xml' => 'mads',
+ 'application/marc' => 'mrc',
+ 'application/marcxml+xml' => 'mrcx',
+ 'application/mathematica' => 'ma',
+ 'application/mathml+xml' => 'mathml',
+ 'application/mbox' => 'mbox',
+ 'application/mediaservercontrol+xml' => 'mscml',
+ 'application/metalink+xml' => 'metalink',
+ 'application/metalink4+xml' => 'meta4',
+ 'application/mets+xml' => 'mets',
+ 'application/mods+xml' => 'mods',
+ 'application/mp21' => 'm21',
+ 'application/mp4' => 'mp4s',
+ 'application/msword' => 'doc',
+ 'application/mxf' => 'mxf',
+ 'application/octet-stream' => 'bin',
+ 'application/oda' => 'oda',
+ 'application/oebps-package+xml' => 'opf',
+ 'application/ogg' => 'ogx',
+ 'application/omdoc+xml' => 'omdoc',
+ 'application/onenote' => 'onetoc',
+ 'application/oxps' => 'oxps',
+ 'application/patch-ops-error+xml' => 'xer',
+ 'application/pdf' => 'pdf',
+ 'application/pgp-encrypted' => 'pgp',
+ 'application/pgp-signature' => 'asc',
+ 'application/pics-rules' => 'prf',
+ 'application/pkcs10' => 'p10',
+ 'application/pkcs7-mime' => 'p7m',
+ 'application/pkcs7-signature' => 'p7s',
+ 'application/pkcs8' => 'p8',
+ 'application/pkix-attr-cert' => 'ac',
+ 'application/pkix-cert' => 'cer',
+ 'application/pkix-crl' => 'crl',
+ 'application/pkix-pkipath' => 'pkipath',
+ 'application/pkixcmp' => 'pki',
+ 'application/pls+xml' => 'pls',
+ 'application/postscript' => 'ai',
+ 'application/prs.cww' => 'cww',
+ 'application/pskc+xml' => 'pskcxml',
+ 'application/rdf+xml' => 'rdf',
+ 'application/reginfo+xml' => 'rif',
+ 'application/relax-ng-compact-syntax' => 'rnc',
+ 'application/resource-lists+xml' => 'rl',
+ 'application/resource-lists-diff+xml' => 'rld',
+ 'application/rls-services+xml' => 'rs',
+ 'application/rpki-ghostbusters' => 'gbr',
+ 'application/rpki-manifest' => 'mft',
+ 'application/rpki-roa' => 'roa',
+ 'application/rsd+xml' => 'rsd',
+ 'application/rss+xml' => 'rss',
+ 'application/rtf' => 'rtf',
+ 'application/sbml+xml' => 'sbml',
+ 'application/scvp-cv-request' => 'scq',
+ 'application/scvp-cv-response' => 'scs',
+ 'application/scvp-vp-request' => 'spq',
+ 'application/scvp-vp-response' => 'spp',
+ 'application/sdp' => 'sdp',
+ 'application/set-payment-initiation' => 'setpay',
+ 'application/set-registration-initiation' => 'setreg',
+ 'application/shf+xml' => 'shf',
+ 'application/smil+xml' => 'smi',
+ 'application/sparql-query' => 'rq',
+ 'application/sparql-results+xml' => 'srx',
+ 'application/srgs' => 'gram',
+ 'application/srgs+xml' => 'grxml',
+ 'application/sru+xml' => 'sru',
+ 'application/ssdl+xml' => 'ssdl',
+ 'application/ssml+xml' => 'ssml',
+ 'application/tei+xml' => 'tei',
+ 'application/thraud+xml' => 'tfi',
+ 'application/timestamped-data' => 'tsd',
+ 'application/vnd.3gpp.pic-bw-large' => 'plb',
+ 'application/vnd.3gpp.pic-bw-small' => 'psb',
+ 'application/vnd.3gpp.pic-bw-var' => 'pvb',
+ 'application/vnd.3gpp2.tcap' => 'tcap',
+ 'application/vnd.3m.post-it-notes' => 'pwn',
+ 'application/vnd.accpac.simply.aso' => 'aso',
+ 'application/vnd.accpac.simply.imp' => 'imp',
+ 'application/vnd.acucobol' => 'acu',
+ 'application/vnd.acucorp' => 'atc',
+ 'application/vnd.adobe.air-application-installer-package+zip' => 'air',
+ 'application/vnd.adobe.formscentral.fcdt' => 'fcdt',
+ 'application/vnd.adobe.fxp' => 'fxp',
+ 'application/vnd.adobe.xdp+xml' => 'xdp',
+ 'application/vnd.adobe.xfdf' => 'xfdf',
+ 'application/vnd.ahead.space' => 'ahead',
+ 'application/vnd.airzip.filesecure.azf' => 'azf',
+ 'application/vnd.airzip.filesecure.azs' => 'azs',
+ 'application/vnd.amazon.ebook' => 'azw',
+ 'application/vnd.americandynamics.acc' => 'acc',
+ 'application/vnd.amiga.ami' => 'ami',
+ 'application/vnd.android.package-archive' => 'apk',
+ 'application/vnd.anser-web-certificate-issue-initiation' => 'cii',
+ 'application/vnd.anser-web-funds-transfer-initiation' => 'fti',
+ 'application/vnd.antix.game-component' => 'atx',
+ 'application/vnd.apple.installer+xml' => 'mpkg',
+ 'application/vnd.apple.mpegurl' => 'm3u8',
+ 'application/vnd.aristanetworks.swi' => 'swi',
+ 'application/vnd.astraea-software.iota' => 'iota',
+ 'application/vnd.audiograph' => 'aep',
+ 'application/vnd.blueice.multipass' => 'mpm',
+ 'application/vnd.bmi' => 'bmi',
+ 'application/vnd.businessobjects' => 'rep',
+ 'application/vnd.chemdraw+xml' => 'cdxml',
+ 'application/vnd.chipnuts.karaoke-mmd' => 'mmd',
+ 'application/vnd.cinderella' => 'cdy',
+ 'application/vnd.claymore' => 'cla',
+ 'application/vnd.cloanto.rp9' => 'rp9',
+ 'application/vnd.clonk.c4group' => 'c4g',
+ 'application/vnd.cluetrust.cartomobile-config' => 'c11amc',
+ 'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz',
+ 'application/vnd.commonspace' => 'csp',
+ 'application/vnd.contact.cmsg' => 'cdbcmsg',
+ 'application/vnd.cosmocaller' => 'cmc',
+ 'application/vnd.crick.clicker' => 'clkx',
+ 'application/vnd.crick.clicker.keyboard' => 'clkk',
+ 'application/vnd.crick.clicker.palette' => 'clkp',
+ 'application/vnd.crick.clicker.template' => 'clkt',
+ 'application/vnd.crick.clicker.wordbank' => 'clkw',
+ 'application/vnd.criticaltools.wbs+xml' => 'wbs',
+ 'application/vnd.ctc-posml' => 'pml',
+ 'application/vnd.cups-ppd' => 'ppd',
+ 'application/vnd.curl.car' => 'car',
+ 'application/vnd.curl.pcurl' => 'pcurl',
+ 'application/vnd.dart' => 'dart',
+ 'application/vnd.data-vision.rdz' => 'rdz',
+ 'application/vnd.dece.data' => 'uvf',
+ 'application/vnd.dece.ttml+xml' => 'uvt',
+ 'application/vnd.dece.unspecified' => 'uvx',
+ 'application/vnd.dece.zip' => 'uvz',
+ 'application/vnd.denovo.fcselayout-link' => 'fe_launch',
+ 'application/vnd.dna' => 'dna',
+ 'application/vnd.dolby.mlp' => 'mlp',
+ 'application/vnd.dpgraph' => 'dpg',
+ 'application/vnd.dreamfactory' => 'dfac',
+ 'application/vnd.ds-keypoint' => 'kpxx',
+ 'application/vnd.dvb.ait' => 'ait',
+ 'application/vnd.dvb.service' => 'svc',
+ 'application/vnd.dynageo' => 'geo',
+ 'application/vnd.ecowin.chart' => 'mag',
+ 'application/vnd.enliven' => 'nml',
+ 'application/vnd.epson.esf' => 'esf',
+ 'application/vnd.epson.msf' => 'msf',
+ 'application/vnd.epson.quickanime' => 'qam',
+ 'application/vnd.epson.salt' => 'slt',
+ 'application/vnd.epson.ssf' => 'ssf',
+ 'application/vnd.eszigno3+xml' => 'es3',
+ 'application/vnd.ezpix-album' => 'ez2',
+ 'application/vnd.ezpix-package' => 'ez3',
+ 'application/vnd.fdf' => 'fdf',
+ 'application/vnd.fdsn.mseed' => 'mseed',
+ 'application/vnd.fdsn.seed' => 'seed',
+ 'application/vnd.flographit' => 'gph',
+ 'application/vnd.fluxtime.clip' => 'ftc',
+ 'application/vnd.framemaker' => 'fm',
+ 'application/vnd.frogans.fnc' => 'fnc',
+ 'application/vnd.frogans.ltf' => 'ltf',
+ 'application/vnd.fsc.weblaunch' => 'fsc',
+ 'application/vnd.fujitsu.oasys' => 'oas',
+ 'application/vnd.fujitsu.oasys2' => 'oa2',
+ 'application/vnd.fujitsu.oasys3' => 'oa3',
+ 'application/vnd.fujitsu.oasysgp' => 'fg5',
+ 'application/vnd.fujitsu.oasysprs' => 'bh2',
+ 'application/vnd.fujixerox.ddd' => 'ddd',
+ 'application/vnd.fujixerox.docuworks' => 'xdw',
+ 'application/vnd.fujixerox.docuworks.binder' => 'xbd',
+ 'application/vnd.fuzzysheet' => 'fzs',
+ 'application/vnd.genomatix.tuxedo' => 'txd',
+ 'application/vnd.geogebra.file' => 'ggb',
+ 'application/vnd.geogebra.tool' => 'ggt',
+ 'application/vnd.geometry-explorer' => 'gex',
+ 'application/vnd.geonext' => 'gxt',
+ 'application/vnd.geoplan' => 'g2w',
+ 'application/vnd.geospace' => 'g3w',
+ 'application/vnd.gmx' => 'gmx',
+ 'application/vnd.google-earth.kml+xml' => 'kml',
+ 'application/vnd.google-earth.kmz' => 'kmz',
+ 'application/vnd.grafeq' => 'gqf',
+ 'application/vnd.groove-account' => 'gac',
+ 'application/vnd.groove-help' => 'ghf',
+ 'application/vnd.groove-identity-message' => 'gim',
+ 'application/vnd.groove-injector' => 'grv',
+ 'application/vnd.groove-tool-message' => 'gtm',
+ 'application/vnd.groove-tool-template' => 'tpl',
+ 'application/vnd.groove-vcard' => 'vcg',
+ 'application/vnd.hal+xml' => 'hal',
+ 'application/vnd.handheld-entertainment+xml' => 'zmm',
+ 'application/vnd.hbci' => 'hbci',
+ 'application/vnd.hhe.lesson-player' => 'les',
+ 'application/vnd.hp-hpgl' => 'hpgl',
+ 'application/vnd.hp-hpid' => 'hpid',
+ 'application/vnd.hp-hps' => 'hps',
+ 'application/vnd.hp-jlyt' => 'jlt',
+ 'application/vnd.hp-pcl' => 'pcl',
+ 'application/vnd.hp-pclxl' => 'pclxl',
+ 'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx',
+ 'application/vnd.ibm.minipay' => 'mpy',
+ 'application/vnd.ibm.modcap' => 'afp',
+ 'application/vnd.ibm.rights-management' => 'irm',
+ 'application/vnd.ibm.secure-container' => 'sc',
+ 'application/vnd.iccprofile' => 'icc',
+ 'application/vnd.igloader' => 'igl',
+ 'application/vnd.immervision-ivp' => 'ivp',
+ 'application/vnd.immervision-ivu' => 'ivu',
+ 'application/vnd.insors.igm' => 'igm',
+ 'application/vnd.intercon.formnet' => 'xpw',
+ 'application/vnd.intergeo' => 'i2g',
+ 'application/vnd.intu.qbo' => 'qbo',
+ 'application/vnd.intu.qfx' => 'qfx',
+ 'application/vnd.ipunplugged.rcprofile' => 'rcprofile',
+ 'application/vnd.irepository.package+xml' => 'irp',
+ 'application/vnd.is-xpr' => 'xpr',
+ 'application/vnd.isac.fcs' => 'fcs',
+ 'application/vnd.jam' => 'jam',
+ 'application/vnd.jcp.javame.midlet-rms' => 'rms',
+ 'application/vnd.jisp' => 'jisp',
+ 'application/vnd.joost.joda-archive' => 'joda',
+ 'application/vnd.kahootz' => 'ktz',
+ 'application/vnd.kde.karbon' => 'karbon',
+ 'application/vnd.kde.kchart' => 'chrt',
+ 'application/vnd.kde.kformula' => 'kfo',
+ 'application/vnd.kde.kivio' => 'flw',
+ 'application/vnd.kde.kontour' => 'kon',
+ 'application/vnd.kde.kpresenter' => 'kpr',
+ 'application/vnd.kde.kspread' => 'ksp',
+ 'application/vnd.kde.kword' => 'kwd',
+ 'application/vnd.kenameaapp' => 'htke',
+ 'application/vnd.kidspiration' => 'kia',
+ 'application/vnd.kinar' => 'kne',
+ 'application/vnd.koan' => 'skp',
+ 'application/vnd.kodak-descriptor' => 'sse',
+ 'application/vnd.las.las+xml' => 'lasxml',
+ 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd',
+ 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe',
+ 'application/vnd.lotus-1-2-3' => '123',
+ 'application/vnd.lotus-approach' => 'apr',
+ 'application/vnd.lotus-freelance' => 'pre',
+ 'application/vnd.lotus-notes' => 'nsf',
+ 'application/vnd.lotus-organizer' => 'org',
+ 'application/vnd.lotus-screencam' => 'scm',
+ 'application/vnd.lotus-wordpro' => 'lwp',
+ 'application/vnd.macports.portpkg' => 'portpkg',
+ 'application/vnd.mcd' => 'mcd',
+ 'application/vnd.medcalcdata' => 'mc1',
+ 'application/vnd.mediastation.cdkey' => 'cdkey',
+ 'application/vnd.mfer' => 'mwf',
+ 'application/vnd.mfmp' => 'mfm',
+ 'application/vnd.micrografx.flo' => 'flo',
+ 'application/vnd.micrografx.igx' => 'igx',
+ 'application/vnd.mif' => 'mif',
+ 'application/vnd.mobius.daf' => 'daf',
+ 'application/vnd.mobius.dis' => 'dis',
+ 'application/vnd.mobius.mbk' => 'mbk',
+ 'application/vnd.mobius.mqy' => 'mqy',
+ 'application/vnd.mobius.msl' => 'msl',
+ 'application/vnd.mobius.plc' => 'plc',
+ 'application/vnd.mobius.txf' => 'txf',
+ 'application/vnd.mophun.application' => 'mpn',
+ 'application/vnd.mophun.certificate' => 'mpc',
+ 'application/vnd.mozilla.xul+xml' => 'xul',
+ 'application/vnd.ms-artgalry' => 'cil',
+ 'application/vnd.ms-cab-compressed' => 'cab',
+ 'application/vnd.ms-excel' => 'xls',
+ 'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam',
+ 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb',
+ 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm',
+ 'application/vnd.ms-excel.template.macroenabled.12' => 'xltm',
+ 'application/vnd.ms-fontobject' => 'eot',
+ 'application/vnd.ms-htmlhelp' => 'chm',
+ 'application/vnd.ms-ims' => 'ims',
+ 'application/vnd.ms-lrm' => 'lrm',
+ 'application/vnd.ms-officetheme' => 'thmx',
+ 'application/vnd.ms-pki.seccat' => 'cat',
+ 'application/vnd.ms-pki.stl' => 'stl',
+ 'application/vnd.ms-powerpoint' => 'ppt',
+ 'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam',
+ 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm',
+ 'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm',
+ 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm',
+ 'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm',
+ 'application/vnd.ms-project' => 'mpp',
+ 'application/vnd.ms-word.document.macroenabled.12' => 'docm',
+ 'application/vnd.ms-word.template.macroenabled.12' => 'dotm',
+ 'application/vnd.ms-works' => 'wps',
+ 'application/vnd.ms-wpl' => 'wpl',
+ 'application/vnd.ms-xpsdocument' => 'xps',
+ 'application/vnd.mseq' => 'mseq',
+ 'application/vnd.musician' => 'mus',
+ 'application/vnd.muvee.style' => 'msty',
+ 'application/vnd.mynfc' => 'taglet',
+ 'application/vnd.neurolanguage.nlu' => 'nlu',
+ 'application/vnd.nitf' => 'ntf',
+ 'application/vnd.noblenet-directory' => 'nnd',
+ 'application/vnd.noblenet-sealer' => 'nns',
+ 'application/vnd.noblenet-web' => 'nnw',
+ 'application/vnd.nokia.n-gage.data' => 'ngdat',
+ 'application/vnd.nokia.n-gage.symbian.install' => 'n-gage',
+ 'application/vnd.nokia.radio-preset' => 'rpst',
+ 'application/vnd.nokia.radio-presets' => 'rpss',
+ 'application/vnd.novadigm.edm' => 'edm',
+ 'application/vnd.novadigm.edx' => 'edx',
+ 'application/vnd.novadigm.ext' => 'ext',
+ 'application/vnd.oasis.opendocument.chart' => 'odc',
+ 'application/vnd.oasis.opendocument.chart-template' => 'otc',
+ 'application/vnd.oasis.opendocument.database' => 'odb',
+ 'application/vnd.oasis.opendocument.formula' => 'odf',
+ 'application/vnd.oasis.opendocument.formula-template' => 'odft',
+ 'application/vnd.oasis.opendocument.graphics' => 'odg',
+ 'application/vnd.oasis.opendocument.graphics-template' => 'otg',
+ 'application/vnd.oasis.opendocument.image' => 'odi',
+ 'application/vnd.oasis.opendocument.image-template' => 'oti',
+ 'application/vnd.oasis.opendocument.presentation' => 'odp',
+ 'application/vnd.oasis.opendocument.presentation-template' => 'otp',
+ 'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
+ 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots',
+ 'application/vnd.oasis.opendocument.text' => 'odt',
+ 'application/vnd.oasis.opendocument.text-master' => 'odm',
+ 'application/vnd.oasis.opendocument.text-template' => 'ott',
+ 'application/vnd.oasis.opendocument.text-web' => 'oth',
+ 'application/vnd.olpc-sugar' => 'xo',
+ 'application/vnd.oma.dd2+xml' => 'dd2',
+ 'application/vnd.openofficeorg.extension' => 'oxt',
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
+ 'application/vnd.osgeo.mapguide.package' => 'mgp',
+ 'application/vnd.osgi.dp' => 'dp',
+ 'application/vnd.osgi.subsystem' => 'esa',
+ 'application/vnd.palm' => 'pdb',
+ 'application/vnd.pawaafile' => 'paw',
+ 'application/vnd.pg.format' => 'str',
+ 'application/vnd.pg.osasli' => 'ei6',
+ 'application/vnd.picsel' => 'efif',
+ 'application/vnd.pmi.widget' => 'wg',
+ 'application/vnd.pocketlearn' => 'plf',
+ 'application/vnd.powerbuilder6' => 'pbd',
+ 'application/vnd.previewsystems.box' => 'box',
+ 'application/vnd.proteus.magazine' => 'mgz',
+ 'application/vnd.publishare-delta-tree' => 'qps',
+ 'application/vnd.pvi.ptid1' => 'ptid',
+ 'application/vnd.quark.quarkxpress' => 'qxd',
+ 'application/vnd.realvnc.bed' => 'bed',
+ 'application/vnd.recordare.musicxml' => 'mxl',
+ 'application/vnd.recordare.musicxml+xml' => 'musicxml',
+ 'application/vnd.rig.cryptonote' => 'cryptonote',
+ 'application/vnd.rim.cod' => 'cod',
+ 'application/vnd.rn-realmedia' => 'rm',
+ 'application/vnd.rn-realmedia-vbr' => 'rmvb',
+ 'application/vnd.route66.link66+xml' => 'link66',
+ 'application/vnd.sailingtracker.track' => 'st',
+ 'application/vnd.seemail' => 'see',
+ 'application/vnd.sema' => 'sema',
+ 'application/vnd.semd' => 'semd',
+ 'application/vnd.semf' => 'semf',
+ 'application/vnd.shana.informed.formdata' => 'ifm',
+ 'application/vnd.shana.informed.formtemplate' => 'itp',
+ 'application/vnd.shana.informed.interchange' => 'iif',
+ 'application/vnd.shana.informed.package' => 'ipk',
+ 'application/vnd.simtech-mindmapper' => 'twd',
+ 'application/vnd.smaf' => 'mmf',
+ 'application/vnd.smart.teacher' => 'teacher',
+ 'application/vnd.solent.sdkm+xml' => 'sdkm',
+ 'application/vnd.spotfire.dxp' => 'dxp',
+ 'application/vnd.spotfire.sfs' => 'sfs',
+ 'application/vnd.stardivision.calc' => 'sdc',
+ 'application/vnd.stardivision.draw' => 'sda',
+ 'application/vnd.stardivision.impress' => 'sdd',
+ 'application/vnd.stardivision.math' => 'smf',
+ 'application/vnd.stardivision.writer' => 'sdw',
+ 'application/vnd.stardivision.writer-global' => 'sgl',
+ 'application/vnd.stepmania.package' => 'smzip',
+ 'application/vnd.stepmania.stepchart' => 'sm',
+ 'application/vnd.sun.xml.calc' => 'sxc',
+ 'application/vnd.sun.xml.calc.template' => 'stc',
+ 'application/vnd.sun.xml.draw' => 'sxd',
+ 'application/vnd.sun.xml.draw.template' => 'std',
+ 'application/vnd.sun.xml.impress' => 'sxi',
+ 'application/vnd.sun.xml.impress.template' => 'sti',
+ 'application/vnd.sun.xml.math' => 'sxm',
+ 'application/vnd.sun.xml.writer' => 'sxw',
+ 'application/vnd.sun.xml.writer.global' => 'sxg',
+ 'application/vnd.sun.xml.writer.template' => 'stw',
+ 'application/vnd.sus-calendar' => 'sus',
+ 'application/vnd.svd' => 'svd',
+ 'application/vnd.symbian.install' => 'sis',
+ 'application/vnd.syncml+xml' => 'xsm',
+ 'application/vnd.syncml.dm+wbxml' => 'bdm',
+ 'application/vnd.syncml.dm+xml' => 'xdm',
+ 'application/vnd.tao.intent-module-archive' => 'tao',
+ 'application/vnd.tcpdump.pcap' => 'pcap',
+ 'application/vnd.tmobile-livetv' => 'tmo',
+ 'application/vnd.trid.tpt' => 'tpt',
+ 'application/vnd.triscape.mxs' => 'mxs',
+ 'application/vnd.trueapp' => 'tra',
+ 'application/vnd.ufdl' => 'ufd',
+ 'application/vnd.uiq.theme' => 'utz',
+ 'application/vnd.umajin' => 'umj',
+ 'application/vnd.unity' => 'unityweb',
+ 'application/vnd.uoml+xml' => 'uoml',
+ 'application/vnd.vcx' => 'vcx',
+ 'application/vnd.visio' => 'vsd',
+ 'application/vnd.visionary' => 'vis',
+ 'application/vnd.vsf' => 'vsf',
+ 'application/vnd.wap.wbxml' => 'wbxml',
+ 'application/vnd.wap.wmlc' => 'wmlc',
+ 'application/vnd.wap.wmlscriptc' => 'wmlsc',
+ 'application/vnd.webturbo' => 'wtb',
+ 'application/vnd.wolfram.player' => 'nbp',
+ 'application/vnd.wordperfect' => 'wpd',
+ 'application/vnd.wqd' => 'wqd',
+ 'application/vnd.wt.stf' => 'stf',
+ 'application/vnd.xara' => 'xar',
+ 'application/vnd.xfdl' => 'xfdl',
+ 'application/vnd.yamaha.hv-dic' => 'hvd',
+ 'application/vnd.yamaha.hv-script' => 'hvs',
+ 'application/vnd.yamaha.hv-voice' => 'hvp',
+ 'application/vnd.yamaha.openscoreformat' => 'osf',
+ 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg',
+ 'application/vnd.yamaha.smaf-audio' => 'saf',
+ 'application/vnd.yamaha.smaf-phrase' => 'spf',
+ 'application/vnd.yellowriver-custom-menu' => 'cmp',
+ 'application/vnd.zul' => 'zir',
+ 'application/vnd.zzazz.deck+xml' => 'zaz',
+ 'application/voicexml+xml' => 'vxml',
+ 'application/widget' => 'wgt',
+ 'application/winhlp' => 'hlp',
+ 'application/wsdl+xml' => 'wsdl',
+ 'application/wspolicy+xml' => 'wspolicy',
+ 'application/x-7z-compressed' => '7z',
+ 'application/x-abiword' => 'abw',
+ 'application/x-ace-compressed' => 'ace',
+ 'application/x-apple-diskimage' => 'dmg',
+ 'application/x-authorware-bin' => 'aab',
+ 'application/x-authorware-map' => 'aam',
+ 'application/x-authorware-seg' => 'aas',
+ 'application/x-bcpio' => 'bcpio',
+ 'application/x-bittorrent' => 'torrent',
+ 'application/x-blorb' => 'blb',
+ 'application/x-bzip' => 'bz',
+ 'application/x-bzip2' => 'bz2',
+ 'application/x-cbr' => 'cbr',
+ 'application/x-cdlink' => 'vcd',
+ 'application/x-cfs-compressed' => 'cfs',
+ 'application/x-chat' => 'chat',
+ 'application/x-chess-pgn' => 'pgn',
+ 'application/x-conference' => 'nsc',
+ 'application/x-cpio' => 'cpio',
+ 'application/x-csh' => 'csh',
+ 'application/x-debian-package' => 'deb',
+ 'application/x-dgc-compressed' => 'dgc',
+ 'application/x-director' => 'dir',
+ 'application/x-doom' => 'wad',
+ 'application/x-dtbncx+xml' => 'ncx',
+ 'application/x-dtbook+xml' => 'dtb',
+ 'application/x-dtbresource+xml' => 'res',
+ 'application/x-dvi' => 'dvi',
+ 'application/x-envoy' => 'evy',
+ 'application/x-eva' => 'eva',
+ 'application/x-font-bdf' => 'bdf',
+ 'application/x-font-ghostscript' => 'gsf',
+ 'application/x-font-linux-psf' => 'psf',
+ 'application/x-font-otf' => 'otf',
+ 'application/x-font-pcf' => 'pcf',
+ 'application/x-font-snf' => 'snf',
+ 'application/x-font-ttf' => 'ttf',
+ 'application/x-font-type1' => 'pfa',
+ 'application/x-font-woff' => 'woff',
+ 'application/x-freearc' => 'arc',
+ 'application/x-futuresplash' => 'spl',
+ 'application/x-gca-compressed' => 'gca',
+ 'application/x-glulx' => 'ulx',
+ 'application/x-gnumeric' => 'gnumeric',
+ 'application/x-gramps-xml' => 'gramps',
+ 'application/x-gtar' => 'gtar',
+ 'application/x-hdf' => 'hdf',
+ 'application/x-install-instructions' => 'install',
+ 'application/x-iso9660-image' => 'iso',
+ 'application/x-java-jnlp-file' => 'jnlp',
+ 'application/x-latex' => 'latex',
+ 'application/x-lzh-compressed' => 'lzh',
+ 'application/x-mie' => 'mie',
+ 'application/x-mobipocket-ebook' => 'prc',
+ 'application/x-ms-application' => 'application',
+ 'application/x-ms-shortcut' => 'lnk',
+ 'application/x-ms-wmd' => 'wmd',
+ 'application/x-ms-wmz' => 'wmz',
+ 'application/x-ms-xbap' => 'xbap',
+ 'application/x-msaccess' => 'mdb',
+ 'application/x-msbinder' => 'obd',
+ 'application/x-mscardfile' => 'crd',
+ 'application/x-msclip' => 'clp',
+ 'application/x-msdownload' => 'exe',
+ 'application/x-msmediaview' => 'mvb',
+ 'application/x-msmetafile' => 'wmf',
+ 'application/x-msmoney' => 'mny',
+ 'application/x-mspublisher' => 'pub',
+ 'application/x-msschedule' => 'scd',
+ 'application/x-msterminal' => 'trm',
+ 'application/x-mswrite' => 'wri',
+ 'application/x-netcdf' => 'nc',
+ 'application/x-nzb' => 'nzb',
+ 'application/x-pkcs12' => 'p12',
+ 'application/x-pkcs7-certificates' => 'p7b',
+ 'application/x-pkcs7-certreqresp' => 'p7r',
+ 'application/x-rar-compressed' => 'rar',
+ 'application/x-rar' => 'rar',
+ 'application/x-research-info-systems' => 'ris',
+ 'application/x-sh' => 'sh',
+ 'application/x-shar' => 'shar',
+ 'application/x-shockwave-flash' => 'swf',
+ 'application/x-silverlight-app' => 'xap',
+ 'application/x-sql' => 'sql',
+ 'application/x-stuffit' => 'sit',
+ 'application/x-stuffitx' => 'sitx',
+ 'application/x-subrip' => 'srt',
+ 'application/x-sv4cpio' => 'sv4cpio',
+ 'application/x-sv4crc' => 'sv4crc',
+ 'application/x-t3vm-image' => 't3',
+ 'application/x-tads' => 'gam',
+ 'application/x-tar' => 'tar',
+ 'application/x-tcl' => 'tcl',
+ 'application/x-tex' => 'tex',
+ 'application/x-tex-tfm' => 'tfm',
+ 'application/x-texinfo' => 'texinfo',
+ 'application/x-tgif' => 'obj',
+ 'application/x-ustar' => 'ustar',
+ 'application/x-wais-source' => 'src',
+ 'application/x-x509-ca-cert' => 'der',
+ 'application/x-xfig' => 'fig',
+ 'application/x-xliff+xml' => 'xlf',
+ 'application/x-xpinstall' => 'xpi',
+ 'application/x-xz' => 'xz',
+ 'application/x-zip-compressed' => 'zip',
+ 'application/x-zmachine' => 'z1',
+ 'application/xaml+xml' => 'xaml',
+ 'application/xcap-diff+xml' => 'xdf',
+ 'application/xenc+xml' => 'xenc',
+ 'application/xhtml+xml' => 'xhtml',
+ 'application/xml' => 'xml',
+ 'application/xml-dtd' => 'dtd',
+ 'application/xop+xml' => 'xop',
+ 'application/xproc+xml' => 'xpl',
+ 'application/xslt+xml' => 'xslt',
+ 'application/xspf+xml' => 'xspf',
+ 'application/xv+xml' => 'mxml',
+ 'application/yang' => 'yang',
+ 'application/yin+xml' => 'yin',
+ 'application/zip' => 'zip',
+ 'audio/adpcm' => 'adp',
+ 'audio/basic' => 'au',
+ 'audio/midi' => 'mid',
+ 'audio/mp4' => 'm4a',
+ 'audio/mpeg' => 'mpga',
+ 'audio/ogg' => 'oga',
+ 'audio/s3m' => 's3m',
+ 'audio/silk' => 'sil',
+ 'audio/vnd.dece.audio' => 'uva',
+ 'audio/vnd.digital-winds' => 'eol',
+ 'audio/vnd.dra' => 'dra',
+ 'audio/vnd.dts' => 'dts',
+ 'audio/vnd.dts.hd' => 'dtshd',
+ 'audio/vnd.lucent.voice' => 'lvp',
+ 'audio/vnd.ms-playready.media.pya' => 'pya',
+ 'audio/vnd.nuera.ecelp4800' => 'ecelp4800',
+ 'audio/vnd.nuera.ecelp7470' => 'ecelp7470',
+ 'audio/vnd.nuera.ecelp9600' => 'ecelp9600',
+ 'audio/vnd.rip' => 'rip',
+ 'audio/webm' => 'weba',
+ 'audio/x-aac' => 'aac',
+ 'audio/x-aiff' => 'aif',
+ 'audio/x-caf' => 'caf',
+ 'audio/x-flac' => 'flac',
+ 'audio/x-matroska' => 'mka',
+ 'audio/x-mpegurl' => 'm3u',
+ 'audio/x-ms-wax' => 'wax',
+ 'audio/x-ms-wma' => 'wma',
+ 'audio/x-pn-realaudio' => 'ram',
+ 'audio/x-pn-realaudio-plugin' => 'rmp',
+ 'audio/x-wav' => 'wav',
+ 'audio/xm' => 'xm',
+ 'chemical/x-cdx' => 'cdx',
+ 'chemical/x-cif' => 'cif',
+ 'chemical/x-cmdf' => 'cmdf',
+ 'chemical/x-cml' => 'cml',
+ 'chemical/x-csml' => 'csml',
+ 'chemical/x-xyz' => 'xyz',
+ 'font/collection' => 'ttc',
+ 'font/otf' => 'otf',
+ 'font/ttf' => 'ttf',
+ 'font/woff' => 'woff',
+ 'font/woff2' => 'woff2',
+ 'image/bmp' => 'bmp',
+ 'image/x-ms-bmp' => 'bmp',
+ 'image/cgm' => 'cgm',
+ 'image/g3fax' => 'g3',
+ 'image/gif' => 'gif',
+ 'image/ief' => 'ief',
+ 'image/jpeg' => 'jpeg',
+ 'image/pjpeg' => 'jpeg',
+ 'image/ktx' => 'ktx',
+ 'image/png' => 'png',
+ 'image/prs.btif' => 'btif',
+ 'image/sgi' => 'sgi',
+ 'image/svg+xml' => 'svg',
+ 'image/tiff' => 'tiff',
+ 'image/vnd.adobe.photoshop' => 'psd',
+ 'image/vnd.dece.graphic' => 'uvi',
+ 'image/vnd.djvu' => 'djvu',
+ 'image/vnd.dvb.subtitle' => 'sub',
+ 'image/vnd.dwg' => 'dwg',
+ 'image/vnd.dxf' => 'dxf',
+ 'image/vnd.fastbidsheet' => 'fbs',
+ 'image/vnd.fpx' => 'fpx',
+ 'image/vnd.fst' => 'fst',
+ 'image/vnd.fujixerox.edmics-mmr' => 'mmr',
+ 'image/vnd.fujixerox.edmics-rlc' => 'rlc',
+ 'image/vnd.ms-modi' => 'mdi',
+ 'image/vnd.ms-photo' => 'wdp',
+ 'image/vnd.net-fpx' => 'npx',
+ 'image/vnd.wap.wbmp' => 'wbmp',
+ 'image/vnd.xiff' => 'xif',
+ 'image/webp' => 'webp',
+ 'image/x-3ds' => '3ds',
+ 'image/x-cmu-raster' => 'ras',
+ 'image/x-cmx' => 'cmx',
+ 'image/x-freehand' => 'fh',
+ 'image/x-icon' => 'ico',
+ 'image/x-mrsid-image' => 'sid',
+ 'image/x-pcx' => 'pcx',
+ 'image/x-pict' => 'pic',
+ 'image/x-portable-anymap' => 'pnm',
+ 'image/x-portable-bitmap' => 'pbm',
+ 'image/x-portable-graymap' => 'pgm',
+ 'image/x-portable-pixmap' => 'ppm',
+ 'image/x-rgb' => 'rgb',
+ 'image/x-tga' => 'tga',
+ 'image/x-xbitmap' => 'xbm',
+ 'image/x-xpixmap' => 'xpm',
+ 'image/x-xwindowdump' => 'xwd',
+ 'message/rfc822' => 'eml',
+ 'model/iges' => 'igs',
+ 'model/mesh' => 'msh',
+ 'model/vnd.collada+xml' => 'dae',
+ 'model/vnd.dwf' => 'dwf',
+ 'model/vnd.gdl' => 'gdl',
+ 'model/vnd.gtw' => 'gtw',
+ 'model/vnd.mts' => 'mts',
+ 'model/vnd.vtu' => 'vtu',
+ 'model/vrml' => 'wrl',
+ 'model/x3d+binary' => 'x3db',
+ 'model/x3d+vrml' => 'x3dv',
+ 'model/x3d+xml' => 'x3d',
+ 'text/cache-manifest' => 'appcache',
+ 'text/calendar' => 'ics',
+ 'text/css' => 'css',
+ 'text/csv' => 'csv',
+ 'text/html' => 'html',
+ 'text/n3' => 'n3',
+ 'text/plain' => 'txt',
+ 'text/prs.lines.tag' => 'dsc',
+ 'text/richtext' => 'rtx',
+ 'text/rtf' => 'rtf',
+ 'text/sgml' => 'sgml',
+ 'text/tab-separated-values' => 'tsv',
+ 'text/troff' => 't',
+ 'text/turtle' => 'ttl',
+ 'text/uri-list' => 'uri',
+ 'text/vcard' => 'vcard',
+ 'text/vnd.curl' => 'curl',
+ 'text/vnd.curl.dcurl' => 'dcurl',
+ 'text/vnd.curl.mcurl' => 'mcurl',
+ 'text/vnd.curl.scurl' => 'scurl',
+ 'text/vnd.dvb.subtitle' => 'sub',
+ 'text/vnd.fly' => 'fly',
+ 'text/vnd.fmi.flexstor' => 'flx',
+ 'text/vnd.graphviz' => 'gv',
+ 'text/vnd.in3d.3dml' => '3dml',
+ 'text/vnd.in3d.spot' => 'spot',
+ 'text/vnd.sun.j2me.app-descriptor' => 'jad',
+ 'text/vnd.wap.wml' => 'wml',
+ 'text/vnd.wap.wmlscript' => 'wmls',
+ 'text/vtt' => 'vtt',
+ 'text/x-asm' => 's',
+ 'text/x-c' => 'c',
+ 'text/x-fortran' => 'f',
+ 'text/x-java-source' => 'java',
+ 'text/x-nfo' => 'nfo',
+ 'text/x-opml' => 'opml',
+ 'text/x-pascal' => 'p',
+ 'text/x-setext' => 'etx',
+ 'text/x-sfv' => 'sfv',
+ 'text/x-uuencode' => 'uu',
+ 'text/x-vcalendar' => 'vcs',
+ 'text/x-vcard' => 'vcf',
+ 'video/3gpp' => '3gp',
+ 'video/3gpp2' => '3g2',
+ 'video/h261' => 'h261',
+ 'video/h263' => 'h263',
+ 'video/h264' => 'h264',
+ 'video/jpeg' => 'jpgv',
+ 'video/jpm' => 'jpm',
+ 'video/mj2' => 'mj2',
+ 'video/mp4' => 'mp4',
+ 'video/mpeg' => 'mpeg',
+ 'video/ogg' => 'ogv',
+ 'video/quicktime' => 'qt',
+ 'video/vnd.dece.hd' => 'uvh',
+ 'video/vnd.dece.mobile' => 'uvm',
+ 'video/vnd.dece.pd' => 'uvp',
+ 'video/vnd.dece.sd' => 'uvs',
+ 'video/vnd.dece.video' => 'uvv',
+ 'video/vnd.dvb.file' => 'dvb',
+ 'video/vnd.fvt' => 'fvt',
+ 'video/vnd.mpegurl' => 'mxu',
+ 'video/vnd.ms-playready.media.pyv' => 'pyv',
+ 'video/vnd.uvvu.mp4' => 'uvu',
+ 'video/vnd.vivo' => 'viv',
+ 'video/webm' => 'webm',
+ 'video/x-f4v' => 'f4v',
+ 'video/x-fli' => 'fli',
+ 'video/x-flv' => 'flv',
+ 'video/x-m4v' => 'm4v',
+ 'video/x-matroska' => 'mkv',
+ 'video/x-mng' => 'mng',
+ 'video/x-ms-asf' => 'asf',
+ 'video/x-ms-vob' => 'vob',
+ 'video/x-ms-wm' => 'wm',
+ 'video/x-ms-wmv' => 'wmv',
+ 'video/x-ms-wmx' => 'wmx',
+ 'video/x-ms-wvx' => 'wvx',
+ 'video/x-msvideo' => 'avi',
+ 'video/x-sgi-movie' => 'movie',
+ 'video/x-smv' => 'smv',
+ 'x-conference/x-cooltalk' => 'ice',
+ ];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function guess($mimeType)
+ {
+ if (isset($this->defaultExtensions[$mimeType])) {
+ return $this->defaultExtensions[$mimeType];
+ }
+
+ $lcMimeType = strtolower($mimeType);
+
+ return isset($this->defaultExtensions[$lcMimeType]) ? $this->defaultExtensions[$lcMimeType] : null;
+ }
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php
new file mode 100755
index 0000000..95d1ee2
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php
@@ -0,0 +1,133 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+
+/**
+ * A singleton mime type guesser.
+ *
+ * By default, all mime type guessers provided by the framework are installed
+ * (if available on the current OS/PHP setup).
+ *
+ * You can register custom guessers by calling the register() method on the
+ * singleton instance. Custom guessers are always called before any default ones.
+ *
+ * $guesser = MimeTypeGuesser::getInstance();
+ * $guesser->register(new MyCustomMimeTypeGuesser());
+ *
+ * If you want to change the order of the default guessers, just re-register your
+ * preferred one as a custom one. The last registered guesser is preferred over
+ * previously registered ones.
+ *
+ * Re-registering a built-in guesser also allows you to configure it:
+ *
+ * $guesser = MimeTypeGuesser::getInstance();
+ * $guesser->register(new FileinfoMimeTypeGuesser('/path/to/magic/file'));
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class MimeTypeGuesser implements MimeTypeGuesserInterface
+{
+ /**
+ * The singleton instance.
+ *
+ * @var MimeTypeGuesser
+ */
+ private static $instance = null;
+
+ /**
+ * All registered MimeTypeGuesserInterface instances.
+ *
+ * @var array
+ */
+ protected $guessers = [];
+
+ /**
+ * Returns the singleton instance.
+ *
+ * @return self
+ */
+ public static function getInstance()
+ {
+ if (null === self::$instance) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Resets the singleton instance.
+ */
+ public static function reset()
+ {
+ self::$instance = null;
+ }
+
+ /**
+ * Registers all natively provided mime type guessers.
+ */
+ private function __construct()
+ {
+ $this->register(new FileBinaryMimeTypeGuesser());
+ $this->register(new FileinfoMimeTypeGuesser());
+ }
+
+ /**
+ * Registers a new mime type guesser.
+ *
+ * When guessing, this guesser is preferred over previously registered ones.
+ */
+ public function register(MimeTypeGuesserInterface $guesser)
+ {
+ array_unshift($this->guessers, $guesser);
+ }
+
+ /**
+ * Tries to guess the mime type of the given file.
+ *
+ * The file is passed to each registered mime type guesser in reverse order
+ * of their registration (last registered is queried first). Once a guesser
+ * returns a value that is not NULL, this method terminates and returns the
+ * value.
+ *
+ * @param string $path The path to the file
+ *
+ * @return string The mime type or NULL, if none could be guessed
+ *
+ * @throws \LogicException
+ * @throws FileNotFoundException
+ * @throws AccessDeniedException
+ */
+ public function guess($path)
+ {
+ if (!is_file($path)) {
+ throw new FileNotFoundException($path);
+ }
+
+ if (!is_readable($path)) {
+ throw new AccessDeniedException($path);
+ }
+
+ foreach ($this->guessers as $guesser) {
+ if (null !== $mimeType = $guesser->guess($path)) {
+ return $mimeType;
+ }
+ }
+
+ if (2 === \count($this->guessers) && !FileBinaryMimeTypeGuesser::isSupported() && !FileinfoMimeTypeGuesser::isSupported()) {
+ throw new \LogicException('Unable to guess the mime type as no guessers are available (Did you enable the php_fileinfo extension?)');
+ }
+ }
+}
diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php
new file mode 100755
index 0000000..5ac1acb
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File\MimeType;
+
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+
+/**
+ * Guesses the mime type of a file.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface MimeTypeGuesserInterface
+{
+ /**
+ * Guesses the mime type of the file with the given path.
+ *
+ * @param string $path The path to the file
+ *
+ * @return string The mime type or NULL, if none could be guessed
+ *
+ * @throws FileNotFoundException If the file does not exist
+ * @throws AccessDeniedException If the file could not be read
+ */
+ public function guess($path);
+}
diff --git a/vendor/symfony/http-foundation/File/Stream.php b/vendor/symfony/http-foundation/File/Stream.php
new file mode 100755
index 0000000..69ae74c
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/Stream.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File;
+
+/**
+ * A PHP stream of unknown size.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class Stream extends File
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ return false;
+ }
+}
diff --git a/vendor/symfony/http-foundation/File/UploadedFile.php b/vendor/symfony/http-foundation/File/UploadedFile.php
new file mode 100755
index 0000000..a44c664
--- /dev/null
+++ b/vendor/symfony/http-foundation/File/UploadedFile.php
@@ -0,0 +1,268 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\File;
+
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
+use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
+use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
+
+/**
+ * A file uploaded through a form.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ * @author Florian Eckerstorfer <florian@eckerstorfer.org>
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class UploadedFile extends File
+{
+ private $test = false;
+ private $originalName;
+ private $mimeType;
+ private $size;
+ private $error;
+
+ /**
+ * Accepts the information of the uploaded file as provided by the PHP global $_FILES.
+ *
+ * The file object is only created when the uploaded file is valid (i.e. when the
+ * isValid() method returns true). Otherwise the only methods that could be called
+ * on an UploadedFile instance are:
+ *
+ * * getClientOriginalName,
+ * * getClientMimeType,
+ * * isValid,
+ * * getError.
+ *
+ * Calling any other method on an non-valid instance will cause an unpredictable result.
+ *
+ * @param string $path The full temporary path to the file
+ * @param string $originalName The original file name of the uploaded file
+ * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream
+ * @param int|null $size The file size provided by the uploader
+ * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK
+ * @param bool $test Whether the test mode is active
+ * Local files are used in test mode hence the code should not enforce HTTP uploads
+ *
+ * @throws FileException If file_uploads is disabled
+ * @throws FileNotFoundException If the file does not exist
+ */
+ public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false)
+ {
+ $this->originalName = $this->getName($originalName);
+ $this->mimeType = $mimeType ?: 'application/octet-stream';
+ $this->size = $size;
+ $this->error = $error ?: UPLOAD_ERR_OK;
+ $this->test = (bool) $test;
+
+ parent::__construct($path, UPLOAD_ERR_OK === $this->error);
+ }
+
+ /**
+ * Returns the original file name.
+ *
+ * It is extracted from the request from which the file has been uploaded.
+ * Then it should not be considered as a safe value.
+ *
+ * @return string|null The original name
+ */
+ public function getClientOriginalName()
+ {
+ return $this->originalName;
+ }
+
+ /**
+ * Returns the original file extension.
+ *
+ * It is extracted from the original file name that was uploaded.
+ * Then it should not be considered as a safe value.
+ *
+ * @return string The extension
+ */
+ public function getClientOriginalExtension()
+ {
+ return pathinfo($this->originalName, PATHINFO_EXTENSION);
+ }
+
+ /**
+ * Returns the file mime type.
+ *
+ * The client mime type is extracted from the request from which the file
+ * was uploaded, so it should not be considered as a safe value.
+ *
+ * For a trusted mime type, use getMimeType() instead (which guesses the mime
+ * type based on the file content).
+ *
+ * @return string|null The mime type
+ *
+ * @see getMimeType()
+ */
+ public function getClientMimeType()
+ {
+ return $this->mimeType;
+ }
+
+ /**
+ * Returns the extension based on the client mime type.
+ *
+ * If the mime type is unknown, returns null.
+ *
+ * This method uses the mime type as guessed by getClientMimeType()
+ * to guess the file extension. As such, the extension returned
+ * by this method cannot be trusted.
+ *
+ * For a trusted extension, use guessExtension() instead (which guesses
+ * the extension based on the guessed mime type for the file).
+ *
+ * @return string|null The guessed extension or null if it cannot be guessed
+ *
+ * @see guessExtension()
+ * @see getClientMimeType()
+ */
+ public function guessClientExtension()
+ {
+ $type = $this->getClientMimeType();
+ $guesser = ExtensionGuesser::getInstance();
+
+ return $guesser->guess($type);
+ }
+
+ /**
+ * Returns the file size.
+ *
+ * It is extracted from the request from which the file has been uploaded.
+ * Then it should not be considered as a safe value.
+ *
+ * @return int|null The file size
+ */
+ public function getClientSize()
+ {
+ return $this->size;
+ }
+
+ /**
+ * Returns the upload error.
+ *
+ * If the upload was successful, the constant UPLOAD_ERR_OK is returned.
+ * Otherwise one of the other UPLOAD_ERR_XXX constants is returned.
+ *
+ * @return int The upload error
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * Returns whether the file was uploaded successfully.
+ *
+ * @return bool True if the file has been uploaded with HTTP and no error occurred
+ */
+ public function isValid()
+ {
+ $isOk = UPLOAD_ERR_OK === $this->error;
+
+ return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname());
+ }
+
+ /**
+ * Moves the file to a new location.
+ *
+ * @param string $directory The destination folder
+ * @param string $name The new file name
+ *
+ * @return File A File object representing the new file
+ *
+ * @throws FileException if, for any reason, the file could not have been moved
+ */
+ public function move($directory, $name = null)
+ {
+ if ($this->isValid()) {
+ if ($this->test) {
+ return parent::move($directory, $name);
+ }
+
+ $target = $this->getTargetFile($directory, $name);
+
+ set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
+ $moved = move_uploaded_file($this->getPathname(), $target);
+ restore_error_handler();
+ if (!$moved) {
+ throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
+ }
+
+ @chmod($target, 0666 & ~umask());
+
+ return $target;
+ }
+
+ throw new FileException($this->getErrorMessage());
+ }
+
+ /**
+ * Returns the maximum size of an uploaded file as configured in php.ini.
+ *
+ * @return int The maximum size of an uploaded file in bytes
+ */
+ public static function getMaxFilesize()
+ {
+ $iniMax = strtolower(ini_get('upload_max_filesize'));
+
+ if ('' === $iniMax) {
+ return PHP_INT_MAX;
+ }
+
+ $max = ltrim($iniMax, '+');
+ if (0 === strpos($max, '0x')) {
+ $max = \intval($max, 16);
+ } elseif (0 === strpos($max, '0')) {
+ $max = \intval($max, 8);
+ } else {
+ $max = (int) $max;
+ }
+
+ switch (substr($iniMax, -1)) {
+ case 't': $max *= 1024;
+ // no break
+ case 'g': $max *= 1024;
+ // no break
+ case 'm': $max *= 1024;
+ // no break
+ case 'k': $max *= 1024;
+ }
+
+ return $max;
+ }
+
+ /**
+ * Returns an informative upload error message.
+ *
+ * @return string The error message regarding the specified error code
+ */
+ public function getErrorMessage()
+ {
+ static $errors = [
+ UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).',
+ UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.',
+ UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.',
+ UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
+ UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.',
+ UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.',
+ UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.',
+ ];
+
+ $errorCode = $this->error;
+ $maxFilesize = UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0;
+ $message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.';
+
+ return sprintf($message, $this->getClientOriginalName(), $maxFilesize);
+ }
+}
diff --git a/vendor/symfony/http-foundation/FileBag.php b/vendor/symfony/http-foundation/FileBag.php
new file mode 100755
index 0000000..ca849b3
--- /dev/null
+++ b/vendor/symfony/http-foundation/FileBag.php
@@ -0,0 +1,144 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+/**
+ * FileBag is a container for uploaded files.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
+ */
+class FileBag extends ParameterBag
+{
+ private static $fileKeys = ['error', 'name', 'size', 'tmp_name', 'type'];
+
+ /**
+ * @param array $parameters An array of HTTP files
+ */
+ public function __construct(array $parameters = [])
+ {
+ $this->replace($parameters);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function replace(array $files = [])
+ {
+ $this->parameters = [];
+ $this->add($files);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($key, $value)
+ {
+ if (!\is_array($value) && !$value instanceof UploadedFile) {
+ throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.');
+ }
+
+ parent::set($key, $this->convertFileInformation($value));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function add(array $files = [])
+ {
+ foreach ($files as $key => $file) {
+ $this->set($key, $file);
+ }
+ }
+
+ /**
+ * Converts uploaded files to UploadedFile instances.
+ *
+ * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information
+ *
+ * @return UploadedFile[]|UploadedFile|null A (multi-dimensional) array of UploadedFile instances
+ */
+ protected function convertFileInformation($file)
+ {
+ if ($file instanceof UploadedFile) {
+ return $file;
+ }
+
+ $file = $this->fixPhpFilesArray($file);
+ if (\is_array($file)) {
+ $keys = array_keys($file);
+ sort($keys);
+
+ if ($keys == self::$fileKeys) {
+ if (UPLOAD_ERR_NO_FILE == $file['error']) {
+ $file = null;
+ } else {
+ $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']);
+ }
+ } else {
+ $file = array_map([$this, 'convertFileInformation'], $file);
+ if (array_keys($keys) === $keys) {
+ $file = array_filter($file);
+ }
+ }
+ }
+
+ return $file;
+ }
+
+ /**
+ * Fixes a malformed PHP $_FILES array.
+ *
+ * PHP has a bug that the format of the $_FILES array differs, depending on
+ * whether the uploaded file fields had normal field names or array-like
+ * field names ("normal" vs. "parent[child]").
+ *
+ * This method fixes the array to look like the "normal" $_FILES array.
+ *
+ * It's safe to pass an already converted array, in which case this method
+ * just returns the original array unmodified.
+ *
+ * @return array
+ */
+ protected function fixPhpFilesArray($data)
+ {
+ if (!\is_array($data)) {
+ return $data;
+ }
+
+ $keys = array_keys($data);
+ sort($keys);
+
+ if (self::$fileKeys != $keys || !isset($data['name']) || !\is_array($data['name'])) {
+ return $data;
+ }
+
+ $files = $data;
+ foreach (self::$fileKeys as $k) {
+ unset($files[$k]);
+ }
+
+ foreach ($data['name'] as $key => $name) {
+ $files[$key] = $this->fixPhpFilesArray([
+ 'error' => $data['error'][$key],
+ 'name' => $name,
+ 'type' => $data['type'][$key],
+ 'tmp_name' => $data['tmp_name'][$key],
+ 'size' => $data['size'][$key],
+ ]);
+ }
+
+ return $files;
+ }
+}
diff --git a/vendor/symfony/http-foundation/HeaderBag.php b/vendor/symfony/http-foundation/HeaderBag.php
new file mode 100755
index 0000000..9798173
--- /dev/null
+++ b/vendor/symfony/http-foundation/HeaderBag.php
@@ -0,0 +1,331 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * HeaderBag is a container for HTTP headers.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class HeaderBag implements \IteratorAggregate, \Countable
+{
+ protected $headers = [];
+ protected $cacheControl = [];
+
+ /**
+ * @param array $headers An array of HTTP headers
+ */
+ public function __construct(array $headers = [])
+ {
+ foreach ($headers as $key => $values) {
+ $this->set($key, $values);
+ }
+ }
+
+ /**
+ * Returns the headers as a string.
+ *
+ * @return string The headers
+ */
+ public function __toString()
+ {
+ if (!$headers = $this->all()) {
+ return '';
+ }
+
+ ksort($headers);
+ $max = max(array_map('strlen', array_keys($headers))) + 1;
+ $content = '';
+ foreach ($headers as $name => $values) {
+ $name = implode('-', array_map('ucfirst', explode('-', $name)));
+ foreach ($values as $value) {
+ $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value);
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ * Returns the headers.
+ *
+ * @return array An array of headers
+ */
+ public function all()
+ {
+ return $this->headers;
+ }
+
+ /**
+ * Returns the parameter keys.
+ *
+ * @return array An array of parameter keys
+ */
+ public function keys()
+ {
+ return array_keys($this->all());
+ }
+
+ /**
+ * Replaces the current HTTP headers by a new set.
+ *
+ * @param array $headers An array of HTTP headers
+ */
+ public function replace(array $headers = [])
+ {
+ $this->headers = [];
+ $this->add($headers);
+ }
+
+ /**
+ * Adds new headers the current HTTP headers set.
+ *
+ * @param array $headers An array of HTTP headers
+ */
+ public function add(array $headers)
+ {
+ foreach ($headers as $key => $values) {
+ $this->set($key, $values);
+ }
+ }
+
+ /**
+ * Returns a header value by name.
+ *
+ * @param string $key The header name
+ * @param string|null $default The default value
+ * @param bool $first Whether to return the first value or all header values
+ *
+ * @return string|string[]|null The first header value or default value if $first is true, an array of values otherwise
+ */
+ public function get($key, $default = null, $first = true)
+ {
+ $key = str_replace('_', '-', strtolower($key));
+ $headers = $this->all();
+
+ if (!\array_key_exists($key, $headers)) {
+ if (null === $default) {
+ return $first ? null : [];
+ }
+
+ return $first ? $default : [$default];
+ }
+
+ if ($first) {
+ return \count($headers[$key]) ? $headers[$key][0] : $default;
+ }
+
+ return $headers[$key];
+ }
+
+ /**
+ * Sets a header by name.
+ *
+ * @param string $key The key
+ * @param string|string[] $values The value or an array of values
+ * @param bool $replace Whether to replace the actual value or not (true by default)
+ */
+ public function set($key, $values, $replace = true)
+ {
+ $key = str_replace('_', '-', strtolower($key));
+
+ if (\is_array($values)) {
+ $values = array_values($values);
+
+ if (true === $replace || !isset($this->headers[$key])) {
+ $this->headers[$key] = $values;
+ } else {
+ $this->headers[$key] = array_merge($this->headers[$key], $values);
+ }
+ } else {
+ if (true === $replace || !isset($this->headers[$key])) {
+ $this->headers[$key] = [$values];
+ } else {
+ $this->headers[$key][] = $values;
+ }
+ }
+
+ if ('cache-control' === $key) {
+ $this->cacheControl = $this->parseCacheControl(implode(', ', $this->headers[$key]));
+ }
+ }
+
+ /**
+ * Returns true if the HTTP header is defined.
+ *
+ * @param string $key The HTTP header
+ *
+ * @return bool true if the parameter exists, false otherwise
+ */
+ public function has($key)
+ {
+ return \array_key_exists(str_replace('_', '-', strtolower($key)), $this->all());
+ }
+
+ /**
+ * Returns true if the given HTTP header contains the given value.
+ *
+ * @param string $key The HTTP header name
+ * @param string $value The HTTP value
+ *
+ * @return bool true if the value is contained in the header, false otherwise
+ */
+ public function contains($key, $value)
+ {
+ return \in_array($value, $this->get($key, null, false));
+ }
+
+ /**
+ * Removes a header.
+ *
+ * @param string $key The HTTP header name
+ */
+ public function remove($key)
+ {
+ $key = str_replace('_', '-', strtolower($key));
+
+ unset($this->headers[$key]);
+
+ if ('cache-control' === $key) {
+ $this->cacheControl = [];
+ }
+ }
+
+ /**
+ * Returns the HTTP header value converted to a date.
+ *
+ * @param string $key The parameter key
+ * @param \DateTime $default The default value
+ *
+ * @return \DateTime|null The parsed DateTime or the default value if the header does not exist
+ *
+ * @throws \RuntimeException When the HTTP header is not parseable
+ */
+ public function getDate($key, \DateTime $default = null)
+ {
+ if (null === $value = $this->get($key)) {
+ return $default;
+ }
+
+ if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) {
+ throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value));
+ }
+
+ return $date;
+ }
+
+ /**
+ * Adds a custom Cache-Control directive.
+ *
+ * @param string $key The Cache-Control directive name
+ * @param mixed $value The Cache-Control directive value
+ */
+ public function addCacheControlDirective($key, $value = true)
+ {
+ $this->cacheControl[$key] = $value;
+
+ $this->set('Cache-Control', $this->getCacheControlHeader());
+ }
+
+ /**
+ * Returns true if the Cache-Control directive is defined.
+ *
+ * @param string $key The Cache-Control directive
+ *
+ * @return bool true if the directive exists, false otherwise
+ */
+ public function hasCacheControlDirective($key)
+ {
+ return \array_key_exists($key, $this->cacheControl);
+ }
+
+ /**
+ * Returns a Cache-Control directive value by name.
+ *
+ * @param string $key The directive name
+ *
+ * @return mixed|null The directive value if defined, null otherwise
+ */
+ public function getCacheControlDirective($key)
+ {
+ return \array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null;
+ }
+
+ /**
+ * Removes a Cache-Control directive.
+ *
+ * @param string $key The Cache-Control directive
+ */
+ public function removeCacheControlDirective($key)
+ {
+ unset($this->cacheControl[$key]);
+
+ $this->set('Cache-Control', $this->getCacheControlHeader());
+ }
+
+ /**
+ * Returns an iterator for headers.
+ *
+ * @return \ArrayIterator An \ArrayIterator instance
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->headers);
+ }
+
+ /**
+ * Returns the number of headers.
+ *
+ * @return int The number of headers
+ */
+ public function count()
+ {
+ return \count($this->headers);
+ }
+
+ protected function getCacheControlHeader()
+ {
+ $parts = [];
+ ksort($this->cacheControl);
+ foreach ($this->cacheControl as $key => $value) {
+ if (true === $value) {
+ $parts[] = $key;
+ } else {
+ if (preg_match('#[^a-zA-Z0-9._-]#', $value)) {
+ $value = '"'.$value.'"';
+ }
+
+ $parts[] = "$key=$value";
+ }
+ }
+
+ return implode(', ', $parts);
+ }
+
+ /**
+ * Parses a Cache-Control HTTP header.
+ *
+ * @param string $header The value of the Cache-Control HTTP header
+ *
+ * @return array An array representing the attribute values
+ */
+ protected function parseCacheControl($header)
+ {
+ $cacheControl = [];
+ preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER);
+ foreach ($matches as $match) {
+ $cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true);
+ }
+
+ return $cacheControl;
+ }
+}
diff --git a/vendor/symfony/http-foundation/IpUtils.php b/vendor/symfony/http-foundation/IpUtils.php
new file mode 100755
index 0000000..67d13e5
--- /dev/null
+++ b/vendor/symfony/http-foundation/IpUtils.php
@@ -0,0 +1,156 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Http utility functions.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class IpUtils
+{
+ private static $checkedIps = [];
+
+ /**
+ * This class should not be instantiated.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets.
+ *
+ * @param string $requestIp IP to check
+ * @param string|array $ips List of IPs or subnets (can be a string if only a single one)
+ *
+ * @return bool Whether the IP is valid
+ */
+ public static function checkIp($requestIp, $ips)
+ {
+ if (!\is_array($ips)) {
+ $ips = [$ips];
+ }
+
+ $method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4';
+
+ foreach ($ips as $ip) {
+ if (self::$method($requestIp, $ip)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Compares two IPv4 addresses.
+ * In case a subnet is given, it checks if it contains the request IP.
+ *
+ * @param string $requestIp IPv4 address to check
+ * @param string $ip IPv4 address or subnet in CIDR notation
+ *
+ * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet
+ */
+ public static function checkIp4($requestIp, $ip)
+ {
+ $cacheKey = $requestIp.'-'.$ip;
+ if (isset(self::$checkedIps[$cacheKey])) {
+ return self::$checkedIps[$cacheKey];
+ }
+
+ if (!filter_var($requestIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+
+ if (false !== strpos($ip, '/')) {
+ list($address, $netmask) = explode('/', $ip, 2);
+
+ if ('0' === $netmask) {
+ return self::$checkedIps[$cacheKey] = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
+ }
+
+ if ($netmask < 0 || $netmask > 32) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+ } else {
+ $address = $ip;
+ $netmask = 32;
+ }
+
+ if (false === ip2long($address)) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+
+ return self::$checkedIps[$cacheKey] = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask);
+ }
+
+ /**
+ * Compares two IPv6 addresses.
+ * In case a subnet is given, it checks if it contains the request IP.
+ *
+ * @author David Soria Parra <dsp at php dot net>
+ *
+ * @see https://github.com/dsp/v6tools
+ *
+ * @param string $requestIp IPv6 address to check
+ * @param string $ip IPv6 address or subnet in CIDR notation
+ *
+ * @return bool Whether the IP is valid
+ *
+ * @throws \RuntimeException When IPV6 support is not enabled
+ */
+ public static function checkIp6($requestIp, $ip)
+ {
+ $cacheKey = $requestIp.'-'.$ip;
+ if (isset(self::$checkedIps[$cacheKey])) {
+ return self::$checkedIps[$cacheKey];
+ }
+
+ if (!((\extension_loaded('sockets') && \defined('AF_INET6')) || @inet_pton('::1'))) {
+ throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".');
+ }
+
+ if (false !== strpos($ip, '/')) {
+ list($address, $netmask) = explode('/', $ip, 2);
+
+ if ('0' === $netmask) {
+ return (bool) unpack('n*', @inet_pton($address));
+ }
+
+ if ($netmask < 1 || $netmask > 128) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+ } else {
+ $address = $ip;
+ $netmask = 128;
+ }
+
+ $bytesAddr = unpack('n*', @inet_pton($address));
+ $bytesTest = unpack('n*', @inet_pton($requestIp));
+
+ if (!$bytesAddr || !$bytesTest) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+
+ for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) {
+ $left = $netmask - 16 * ($i - 1);
+ $left = ($left <= 16) ? $left : 16;
+ $mask = ~(0xffff >> $left) & 0xffff;
+ if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) {
+ return self::$checkedIps[$cacheKey] = false;
+ }
+ }
+
+ return self::$checkedIps[$cacheKey] = true;
+ }
+}
diff --git a/vendor/symfony/http-foundation/JsonResponse.php b/vendor/symfony/http-foundation/JsonResponse.php
new file mode 100755
index 0000000..6fb32ee
--- /dev/null
+++ b/vendor/symfony/http-foundation/JsonResponse.php
@@ -0,0 +1,220 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Response represents an HTTP response in JSON format.
+ *
+ * Note that this class does not force the returned JSON content to be an
+ * object. It is however recommended that you do return an object as it
+ * protects yourself against XSSI and JSON-JavaScript Hijacking.
+ *
+ * @see https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside
+ *
+ * @author Igor Wiedler <igor@wiedler.ch>
+ */
+class JsonResponse extends Response
+{
+ protected $data;
+ protected $callback;
+
+ // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML.
+ // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
+ const DEFAULT_ENCODING_OPTIONS = 15;
+
+ protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS;
+
+ /**
+ * @param mixed $data The response data
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ * @param bool $json If the data is already a JSON string
+ */
+ public function __construct($data = null, $status = 200, $headers = [], $json = false)
+ {
+ parent::__construct('', $status, $headers);
+
+ if (null === $data) {
+ $data = new \ArrayObject();
+ }
+
+ $json ? $this->setJson($data) : $this->setData($data);
+ }
+
+ /**
+ * Factory method for chainability.
+ *
+ * Example:
+ *
+ * return JsonResponse::create($data, 200)
+ * ->setSharedMaxAge(300);
+ *
+ * @param mixed $data The json response data
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @return static
+ */
+ public static function create($data = null, $status = 200, $headers = [])
+ {
+ return new static($data, $status, $headers);
+ }
+
+ /**
+ * Make easier the creation of JsonResponse from raw json.
+ */
+ public static function fromJsonString($data = null, $status = 200, $headers = [])
+ {
+ return new static($data, $status, $headers, true);
+ }
+
+ /**
+ * Sets the JSONP callback.
+ *
+ * @param string|null $callback The JSONP callback or null to use none
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException When the callback name is not valid
+ */
+ public function setCallback($callback = null)
+ {
+ if (null !== $callback) {
+ // partially taken from http://www.geekality.net/2011/08/03/valid-javascript-identifier/
+ // partially taken from https://github.com/willdurand/JsonpCallbackValidator
+ // JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details.
+ // (c) William Durand <william.durand1@gmail.com>
+ $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u';
+ $reserved = [
+ 'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while',
+ 'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export',
+ 'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false',
+ ];
+ $parts = explode('.', $callback);
+ foreach ($parts as $part) {
+ if (!preg_match($pattern, $part) || \in_array($part, $reserved, true)) {
+ throw new \InvalidArgumentException('The callback name is not valid.');
+ }
+ }
+ }
+
+ $this->callback = $callback;
+
+ return $this->update();
+ }
+
+ /**
+ * Sets a raw string containing a JSON document to be sent.
+ *
+ * @param string $json
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setJson($json)
+ {
+ $this->data = $json;
+
+ return $this->update();
+ }
+
+ /**
+ * Sets the data to be sent as JSON.
+ *
+ * @param mixed $data
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setData($data = [])
+ {
+ if (\defined('HHVM_VERSION')) {
+ // HHVM does not trigger any warnings and let exceptions
+ // thrown from a JsonSerializable object pass through.
+ // If only PHP did the same...
+ $data = json_encode($data, $this->encodingOptions);
+ } else {
+ if (!interface_exists('JsonSerializable', false)) {
+ set_error_handler(function () { return false; });
+ try {
+ $data = @json_encode($data, $this->encodingOptions);
+ } finally {
+ restore_error_handler();
+ }
+ } else {
+ try {
+ $data = json_encode($data, $this->encodingOptions);
+ } catch (\Exception $e) {
+ if ('Exception' === \get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
+ throw $e->getPrevious() ?: $e;
+ }
+ throw $e;
+ }
+ }
+ }
+
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new \InvalidArgumentException(json_last_error_msg());
+ }
+
+ return $this->setJson($data);
+ }
+
+ /**
+ * Returns options used while encoding data to JSON.
+ *
+ * @return int
+ */
+ public function getEncodingOptions()
+ {
+ return $this->encodingOptions;
+ }
+
+ /**
+ * Sets options used while encoding data to JSON.
+ *
+ * @param int $encodingOptions
+ *
+ * @return $this
+ */
+ public function setEncodingOptions($encodingOptions)
+ {
+ $this->encodingOptions = (int) $encodingOptions;
+
+ return $this->setData(json_decode($this->data));
+ }
+
+ /**
+ * Updates the content and headers according to the JSON data and callback.
+ *
+ * @return $this
+ */
+ protected function update()
+ {
+ if (null !== $this->callback) {
+ // Not using application/javascript for compatibility reasons with older browsers.
+ $this->headers->set('Content-Type', 'text/javascript');
+
+ return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data));
+ }
+
+ // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback)
+ // in order to not overwrite a custom definition.
+ if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) {
+ $this->headers->set('Content-Type', 'application/json');
+ }
+
+ return $this->setContent($this->data);
+ }
+}
diff --git a/vendor/symfony/http-foundation/LICENSE b/vendor/symfony/http-foundation/LICENSE
new file mode 100755
index 0000000..a677f43
--- /dev/null
+++ b/vendor/symfony/http-foundation/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/http-foundation/ParameterBag.php b/vendor/symfony/http-foundation/ParameterBag.php
new file mode 100755
index 0000000..f05e4a2
--- /dev/null
+++ b/vendor/symfony/http-foundation/ParameterBag.php
@@ -0,0 +1,234 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * ParameterBag is a container for key/value pairs.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class ParameterBag implements \IteratorAggregate, \Countable
+{
+ /**
+ * Parameter storage.
+ */
+ protected $parameters;
+
+ /**
+ * @param array $parameters An array of parameters
+ */
+ public function __construct(array $parameters = [])
+ {
+ $this->parameters = $parameters;
+ }
+
+ /**
+ * Returns the parameters.
+ *
+ * @return array An array of parameters
+ */
+ public function all()
+ {
+ return $this->parameters;
+ }
+
+ /**
+ * Returns the parameter keys.
+ *
+ * @return array An array of parameter keys
+ */
+ public function keys()
+ {
+ return array_keys($this->parameters);
+ }
+
+ /**
+ * Replaces the current parameters by a new set.
+ *
+ * @param array $parameters An array of parameters
+ */
+ public function replace(array $parameters = [])
+ {
+ $this->parameters = $parameters;
+ }
+
+ /**
+ * Adds parameters.
+ *
+ * @param array $parameters An array of parameters
+ */
+ public function add(array $parameters = [])
+ {
+ $this->parameters = array_replace($this->parameters, $parameters);
+ }
+
+ /**
+ * Returns a parameter by name.
+ *
+ * @param string $key The key
+ * @param mixed $default The default value if the parameter key does not exist
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default;
+ }
+
+ /**
+ * Sets a parameter by name.
+ *
+ * @param string $key The key
+ * @param mixed $value The value
+ */
+ public function set($key, $value)
+ {
+ $this->parameters[$key] = $value;
+ }
+
+ /**
+ * Returns true if the parameter is defined.
+ *
+ * @param string $key The key
+ *
+ * @return bool true if the parameter exists, false otherwise
+ */
+ public function has($key)
+ {
+ return \array_key_exists($key, $this->parameters);
+ }
+
+ /**
+ * Removes a parameter.
+ *
+ * @param string $key The key
+ */
+ public function remove($key)
+ {
+ unset($this->parameters[$key]);
+ }
+
+ /**
+ * Returns the alphabetic characters of the parameter value.
+ *
+ * @param string $key The parameter key
+ * @param string $default The default value if the parameter key does not exist
+ *
+ * @return string The filtered value
+ */
+ public function getAlpha($key, $default = '')
+ {
+ return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default));
+ }
+
+ /**
+ * Returns the alphabetic characters and digits of the parameter value.
+ *
+ * @param string $key The parameter key
+ * @param string $default The default value if the parameter key does not exist
+ *
+ * @return string The filtered value
+ */
+ public function getAlnum($key, $default = '')
+ {
+ return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default));
+ }
+
+ /**
+ * Returns the digits of the parameter value.
+ *
+ * @param string $key The parameter key
+ * @param string $default The default value if the parameter key does not exist
+ *
+ * @return string The filtered value
+ */
+ public function getDigits($key, $default = '')
+ {
+ // we need to remove - and + because they're allowed in the filter
+ return str_replace(['-', '+'], '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT));
+ }
+
+ /**
+ * Returns the parameter value converted to integer.
+ *
+ * @param string $key The parameter key
+ * @param int $default The default value if the parameter key does not exist
+ *
+ * @return int The filtered value
+ */
+ public function getInt($key, $default = 0)
+ {
+ return (int) $this->get($key, $default);
+ }
+
+ /**
+ * Returns the parameter value converted to boolean.
+ *
+ * @param string $key The parameter key
+ * @param bool $default The default value if the parameter key does not exist
+ *
+ * @return bool The filtered value
+ */
+ public function getBoolean($key, $default = false)
+ {
+ return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN);
+ }
+
+ /**
+ * Filter key.
+ *
+ * @param string $key Key
+ * @param mixed $default Default = null
+ * @param int $filter FILTER_* constant
+ * @param mixed $options Filter options
+ *
+ * @see http://php.net/manual/en/function.filter-var.php
+ *
+ * @return mixed
+ */
+ public function filter($key, $default = null, $filter = FILTER_DEFAULT, $options = [])
+ {
+ $value = $this->get($key, $default);
+
+ // Always turn $options into an array - this allows filter_var option shortcuts.
+ if (!\is_array($options) && $options) {
+ $options = ['flags' => $options];
+ }
+
+ // Add a convenience check for arrays.
+ if (\is_array($value) && !isset($options['flags'])) {
+ $options['flags'] = FILTER_REQUIRE_ARRAY;
+ }
+
+ return filter_var($value, $filter, $options);
+ }
+
+ /**
+ * Returns an iterator for parameters.
+ *
+ * @return \ArrayIterator An \ArrayIterator instance
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->parameters);
+ }
+
+ /**
+ * Returns the number of parameters.
+ *
+ * @return int The number of parameters
+ */
+ public function count()
+ {
+ return \count($this->parameters);
+ }
+}
diff --git a/vendor/symfony/http-foundation/README.md b/vendor/symfony/http-foundation/README.md
new file mode 100755
index 0000000..8907f0b
--- /dev/null
+++ b/vendor/symfony/http-foundation/README.md
@@ -0,0 +1,14 @@
+HttpFoundation Component
+========================
+
+The HttpFoundation component defines an object-oriented layer for the HTTP
+specification.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/http_foundation/index.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/vendor/symfony/http-foundation/RedirectResponse.php b/vendor/symfony/http-foundation/RedirectResponse.php
new file mode 100755
index 0000000..5e1dfe5
--- /dev/null
+++ b/vendor/symfony/http-foundation/RedirectResponse.php
@@ -0,0 +1,109 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * RedirectResponse represents an HTTP response doing a redirect.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class RedirectResponse extends Response
+{
+ protected $targetUrl;
+
+ /**
+ * Creates a redirect response so that it conforms to the rules defined for a redirect status code.
+ *
+ * @param string $url The URL to redirect to. The URL should be a full URL, with schema etc.,
+ * but practically every browser redirects on paths only as well
+ * @param int $status The status code (302 by default)
+ * @param array $headers The headers (Location is always set to the given URL)
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @see http://tools.ietf.org/html/rfc2616#section-10.3
+ */
+ public function __construct($url, $status = 302, $headers = [])
+ {
+ parent::__construct('', $status, $headers);
+
+ $this->setTargetUrl($url);
+
+ if (!$this->isRedirect()) {
+ throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
+ }
+
+ if (301 == $status && !\array_key_exists('cache-control', $headers)) {
+ $this->headers->remove('cache-control');
+ }
+ }
+
+ /**
+ * Factory method for chainability.
+ *
+ * @param string $url The url to redirect to
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @return static
+ */
+ public static function create($url = '', $status = 302, $headers = [])
+ {
+ return new static($url, $status, $headers);
+ }
+
+ /**
+ * Returns the target URL.
+ *
+ * @return string target URL
+ */
+ public function getTargetUrl()
+ {
+ return $this->targetUrl;
+ }
+
+ /**
+ * Sets the redirect target of this response.
+ *
+ * @param string $url The URL to redirect to
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setTargetUrl($url)
+ {
+ if (empty($url)) {
+ throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
+ }
+
+ $this->targetUrl = $url;
+
+ $this->setContent(
+ sprintf('<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8" />
+ <meta http-equiv="refresh" content="0;url=%1$s" />
+
+ <title>Redirecting to %1$s</title>
+ </head>
+ <body>
+ Redirecting to <a href="%1$s">%1$s</a>.
+ </body>
+</html>', htmlspecialchars($url, ENT_QUOTES, 'UTF-8')));
+
+ $this->headers->set('Location', $url);
+
+ return $this;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Request.php b/vendor/symfony/http-foundation/Request.php
new file mode 100755
index 0000000..ea3f460
--- /dev/null
+++ b/vendor/symfony/http-foundation/Request.php
@@ -0,0 +1,2191 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException;
+use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+
+/**
+ * Request represents an HTTP request.
+ *
+ * The methods dealing with URL accept / return a raw path (% encoded):
+ * * getBasePath
+ * * getBaseUrl
+ * * getPathInfo
+ * * getRequestUri
+ * * getUri
+ * * getUriForPath
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class Request
+{
+ const HEADER_FORWARDED = 0b00001; // When using RFC 7239
+ const HEADER_X_FORWARDED_FOR = 0b00010;
+ const HEADER_X_FORWARDED_HOST = 0b00100;
+ const HEADER_X_FORWARDED_PROTO = 0b01000;
+ const HEADER_X_FORWARDED_PORT = 0b10000;
+ const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers
+ const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host
+
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ const HEADER_CLIENT_IP = self::HEADER_X_FORWARDED_FOR;
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ const HEADER_CLIENT_HOST = self::HEADER_X_FORWARDED_HOST;
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ const HEADER_CLIENT_PROTO = self::HEADER_X_FORWARDED_PROTO;
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ const HEADER_CLIENT_PORT = self::HEADER_X_FORWARDED_PORT;
+
+ const METHOD_HEAD = 'HEAD';
+ const METHOD_GET = 'GET';
+ const METHOD_POST = 'POST';
+ const METHOD_PUT = 'PUT';
+ const METHOD_PATCH = 'PATCH';
+ const METHOD_DELETE = 'DELETE';
+ const METHOD_PURGE = 'PURGE';
+ const METHOD_OPTIONS = 'OPTIONS';
+ const METHOD_TRACE = 'TRACE';
+ const METHOD_CONNECT = 'CONNECT';
+
+ /**
+ * @var string[]
+ */
+ protected static $trustedProxies = [];
+
+ /**
+ * @var string[]
+ */
+ protected static $trustedHostPatterns = [];
+
+ /**
+ * @var string[]
+ */
+ protected static $trustedHosts = [];
+
+ /**
+ * Names for headers that can be trusted when
+ * using trusted proxies.
+ *
+ * The FORWARDED header is the standard as of rfc7239.
+ *
+ * The other headers are non-standard, but widely used
+ * by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
+ *
+ * @deprecated since version 3.3, to be removed in 4.0
+ */
+ protected static $trustedHeaders = [
+ self::HEADER_FORWARDED => 'FORWARDED',
+ self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
+ self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
+ self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
+ self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
+ ];
+
+ protected static $httpMethodParameterOverride = false;
+
+ /**
+ * Custom parameters.
+ *
+ * @var \Symfony\Component\HttpFoundation\ParameterBag
+ */
+ public $attributes;
+
+ /**
+ * Request body parameters ($_POST).
+ *
+ * @var \Symfony\Component\HttpFoundation\ParameterBag
+ */
+ public $request;
+
+ /**
+ * Query string parameters ($_GET).
+ *
+ * @var \Symfony\Component\HttpFoundation\ParameterBag
+ */
+ public $query;
+
+ /**
+ * Server and execution environment parameters ($_SERVER).
+ *
+ * @var \Symfony\Component\HttpFoundation\ServerBag
+ */
+ public $server;
+
+ /**
+ * Uploaded files ($_FILES).
+ *
+ * @var \Symfony\Component\HttpFoundation\FileBag
+ */
+ public $files;
+
+ /**
+ * Cookies ($_COOKIE).
+ *
+ * @var \Symfony\Component\HttpFoundation\ParameterBag
+ */
+ public $cookies;
+
+ /**
+ * Headers (taken from the $_SERVER).
+ *
+ * @var \Symfony\Component\HttpFoundation\HeaderBag
+ */
+ public $headers;
+
+ /**
+ * @var string|resource|false|null
+ */
+ protected $content;
+
+ /**
+ * @var array
+ */
+ protected $languages;
+
+ /**
+ * @var array
+ */
+ protected $charsets;
+
+ /**
+ * @var array
+ */
+ protected $encodings;
+
+ /**
+ * @var array
+ */
+ protected $acceptableContentTypes;
+
+ /**
+ * @var string
+ */
+ protected $pathInfo;
+
+ /**
+ * @var string
+ */
+ protected $requestUri;
+
+ /**
+ * @var string
+ */
+ protected $baseUrl;
+
+ /**
+ * @var string
+ */
+ protected $basePath;
+
+ /**
+ * @var string
+ */
+ protected $method;
+
+ /**
+ * @var string
+ */
+ protected $format;
+
+ /**
+ * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
+ */
+ protected $session;
+
+ /**
+ * @var string
+ */
+ protected $locale;
+
+ /**
+ * @var string
+ */
+ protected $defaultLocale = 'en';
+
+ /**
+ * @var array
+ */
+ protected static $formats;
+
+ protected static $requestFactory;
+
+ private $isHostValid = true;
+ private $isForwardedValid = true;
+
+ private static $trustedHeaderSet = -1;
+
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ private static $trustedHeaderNames = [
+ self::HEADER_FORWARDED => 'FORWARDED',
+ self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
+ self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
+ self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
+ self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
+ ];
+
+ private static $forwardedParams = [
+ self::HEADER_X_FORWARDED_FOR => 'for',
+ self::HEADER_X_FORWARDED_HOST => 'host',
+ self::HEADER_X_FORWARDED_PROTO => 'proto',
+ self::HEADER_X_FORWARDED_PORT => 'host',
+ ];
+
+ /**
+ * @param array $query The GET parameters
+ * @param array $request The POST parameters
+ * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array $cookies The COOKIE parameters
+ * @param array $files The FILES parameters
+ * @param array $server The SERVER parameters
+ * @param string|resource|null $content The raw body data
+ */
+ public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
+ {
+ $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
+ }
+
+ /**
+ * Sets the parameters for this request.
+ *
+ * This method also re-initializes all properties.
+ *
+ * @param array $query The GET parameters
+ * @param array $request The POST parameters
+ * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array $cookies The COOKIE parameters
+ * @param array $files The FILES parameters
+ * @param array $server The SERVER parameters
+ * @param string|resource|null $content The raw body data
+ */
+ public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
+ {
+ $this->request = new ParameterBag($request);
+ $this->query = new ParameterBag($query);
+ $this->attributes = new ParameterBag($attributes);
+ $this->cookies = new ParameterBag($cookies);
+ $this->files = new FileBag($files);
+ $this->server = new ServerBag($server);
+ $this->headers = new HeaderBag($this->server->getHeaders());
+
+ $this->content = $content;
+ $this->languages = null;
+ $this->charsets = null;
+ $this->encodings = null;
+ $this->acceptableContentTypes = null;
+ $this->pathInfo = null;
+ $this->requestUri = null;
+ $this->baseUrl = null;
+ $this->basePath = null;
+ $this->method = null;
+ $this->format = null;
+ }
+
+ /**
+ * Creates a new request with values from PHP's super globals.
+ *
+ * @return static
+ */
+ public static function createFromGlobals()
+ {
+ // With the php's bug #66606, the php's built-in web server
+ // stores the Content-Type and Content-Length header values in
+ // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields.
+ $server = $_SERVER;
+ if ('cli-server' === \PHP_SAPI) {
+ if (\array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) {
+ $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH'];
+ }
+ if (\array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
+ $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE'];
+ }
+ }
+
+ $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $server);
+
+ if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
+ && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
+ ) {
+ parse_str($request->getContent(), $data);
+ $request->request = new ParameterBag($data);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Creates a Request based on a given URI and configuration.
+ *
+ * The information contained in the URI always take precedence
+ * over the other information (server and parameters).
+ *
+ * @param string $uri The URI
+ * @param string $method The HTTP method
+ * @param array $parameters The query (GET) or request (POST) parameters
+ * @param array $cookies The request cookies ($_COOKIE)
+ * @param array $files The request files ($_FILES)
+ * @param array $server The server parameters ($_SERVER)
+ * @param string|resource|null $content The raw body data
+ *
+ * @return static
+ */
+ public static function create($uri, $method = 'GET', $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
+ {
+ $server = array_replace([
+ 'SERVER_NAME' => 'localhost',
+ 'SERVER_PORT' => 80,
+ 'HTTP_HOST' => 'localhost',
+ 'HTTP_USER_AGENT' => 'Symfony/3.X',
+ 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+ 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5',
+ 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'SCRIPT_NAME' => '',
+ 'SCRIPT_FILENAME' => '',
+ 'SERVER_PROTOCOL' => 'HTTP/1.1',
+ 'REQUEST_TIME' => time(),
+ ], $server);
+
+ $server['PATH_INFO'] = '';
+ $server['REQUEST_METHOD'] = strtoupper($method);
+
+ $components = parse_url($uri);
+ if (isset($components['host'])) {
+ $server['SERVER_NAME'] = $components['host'];
+ $server['HTTP_HOST'] = $components['host'];
+ }
+
+ if (isset($components['scheme'])) {
+ if ('https' === $components['scheme']) {
+ $server['HTTPS'] = 'on';
+ $server['SERVER_PORT'] = 443;
+ } else {
+ unset($server['HTTPS']);
+ $server['SERVER_PORT'] = 80;
+ }
+ }
+
+ if (isset($components['port'])) {
+ $server['SERVER_PORT'] = $components['port'];
+ $server['HTTP_HOST'] .= ':'.$components['port'];
+ }
+
+ if (isset($components['user'])) {
+ $server['PHP_AUTH_USER'] = $components['user'];
+ }
+
+ if (isset($components['pass'])) {
+ $server['PHP_AUTH_PW'] = $components['pass'];
+ }
+
+ if (!isset($components['path'])) {
+ $components['path'] = '/';
+ }
+
+ switch (strtoupper($method)) {
+ case 'POST':
+ case 'PUT':
+ case 'DELETE':
+ if (!isset($server['CONTENT_TYPE'])) {
+ $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
+ }
+ // no break
+ case 'PATCH':
+ $request = $parameters;
+ $query = [];
+ break;
+ default:
+ $request = [];
+ $query = $parameters;
+ break;
+ }
+
+ $queryString = '';
+ if (isset($components['query'])) {
+ parse_str(html_entity_decode($components['query']), $qs);
+
+ if ($query) {
+ $query = array_replace($qs, $query);
+ $queryString = http_build_query($query, '', '&');
+ } else {
+ $query = $qs;
+ $queryString = $components['query'];
+ }
+ } elseif ($query) {
+ $queryString = http_build_query($query, '', '&');
+ }
+
+ $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : '');
+ $server['QUERY_STRING'] = $queryString;
+
+ return self::createRequestFromFactory($query, $request, [], $cookies, $files, $server, $content);
+ }
+
+ /**
+ * Sets a callable able to create a Request instance.
+ *
+ * This is mainly useful when you need to override the Request class
+ * to keep BC with an existing system. It should not be used for any
+ * other purpose.
+ *
+ * @param callable|null $callable A PHP callable
+ */
+ public static function setFactory($callable)
+ {
+ self::$requestFactory = $callable;
+ }
+
+ /**
+ * Clones a request and overrides some of its parameters.
+ *
+ * @param array $query The GET parameters
+ * @param array $request The POST parameters
+ * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array $cookies The COOKIE parameters
+ * @param array $files The FILES parameters
+ * @param array $server The SERVER parameters
+ *
+ * @return static
+ */
+ public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
+ {
+ $dup = clone $this;
+ if (null !== $query) {
+ $dup->query = new ParameterBag($query);
+ }
+ if (null !== $request) {
+ $dup->request = new ParameterBag($request);
+ }
+ if (null !== $attributes) {
+ $dup->attributes = new ParameterBag($attributes);
+ }
+ if (null !== $cookies) {
+ $dup->cookies = new ParameterBag($cookies);
+ }
+ if (null !== $files) {
+ $dup->files = new FileBag($files);
+ }
+ if (null !== $server) {
+ $dup->server = new ServerBag($server);
+ $dup->headers = new HeaderBag($dup->server->getHeaders());
+ }
+ $dup->languages = null;
+ $dup->charsets = null;
+ $dup->encodings = null;
+ $dup->acceptableContentTypes = null;
+ $dup->pathInfo = null;
+ $dup->requestUri = null;
+ $dup->baseUrl = null;
+ $dup->basePath = null;
+ $dup->method = null;
+ $dup->format = null;
+
+ if (!$dup->get('_format') && $this->get('_format')) {
+ $dup->attributes->set('_format', $this->get('_format'));
+ }
+
+ if (!$dup->getRequestFormat(null)) {
+ $dup->setRequestFormat($this->getRequestFormat(null));
+ }
+
+ return $dup;
+ }
+
+ /**
+ * Clones the current request.
+ *
+ * Note that the session is not cloned as duplicated requests
+ * are most of the time sub-requests of the main one.
+ */
+ public function __clone()
+ {
+ $this->query = clone $this->query;
+ $this->request = clone $this->request;
+ $this->attributes = clone $this->attributes;
+ $this->cookies = clone $this->cookies;
+ $this->files = clone $this->files;
+ $this->server = clone $this->server;
+ $this->headers = clone $this->headers;
+ }
+
+ /**
+ * Returns the request as a string.
+ *
+ * @return string The request
+ */
+ public function __toString()
+ {
+ try {
+ $content = $this->getContent();
+ } catch (\LogicException $e) {
+ return trigger_error($e, E_USER_ERROR);
+ }
+
+ $cookieHeader = '';
+ $cookies = [];
+
+ foreach ($this->cookies as $k => $v) {
+ $cookies[] = $k.'='.$v;
+ }
+
+ if (!empty($cookies)) {
+ $cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n";
+ }
+
+ return
+ sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
+ $this->headers.
+ $cookieHeader."\r\n".
+ $content;
+ }
+
+ /**
+ * Overrides the PHP global variables according to this request instance.
+ *
+ * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE.
+ * $_FILES is never overridden, see rfc1867
+ */
+ public function overrideGlobals()
+ {
+ $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&')));
+
+ $_GET = $this->query->all();
+ $_POST = $this->request->all();
+ $_SERVER = $this->server->all();
+ $_COOKIE = $this->cookies->all();
+
+ foreach ($this->headers->all() as $key => $value) {
+ $key = strtoupper(str_replace('-', '_', $key));
+ if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'])) {
+ $_SERVER[$key] = implode(', ', $value);
+ } else {
+ $_SERVER['HTTP_'.$key] = implode(', ', $value);
+ }
+ }
+
+ $request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE];
+
+ $requestOrder = ini_get('request_order') ?: ini_get('variables_order');
+ $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp';
+
+ $_REQUEST = [];
+ foreach (str_split($requestOrder) as $order) {
+ $_REQUEST = array_merge($_REQUEST, $request[$order]);
+ }
+ }
+
+ /**
+ * Sets a list of trusted proxies.
+ *
+ * You should only list the reverse proxies that you manage directly.
+ *
+ * @param array $proxies A list of trusted proxies
+ * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies
+ *
+ * @throws \InvalidArgumentException When $trustedHeaderSet is invalid
+ */
+ public static function setTrustedProxies(array $proxies/*, int $trustedHeaderSet*/)
+ {
+ self::$trustedProxies = $proxies;
+
+ if (2 > \func_num_args()) {
+ @trigger_error(sprintf('The %s() method expects a bit field of Request::HEADER_* as second argument since Symfony 3.3. Defining it will be required in 4.0. ', __METHOD__), E_USER_DEPRECATED);
+
+ return;
+ }
+ $trustedHeaderSet = (int) func_get_arg(1);
+
+ foreach (self::$trustedHeaderNames as $header => $name) {
+ self::$trustedHeaders[$header] = $header & $trustedHeaderSet ? $name : null;
+ }
+ self::$trustedHeaderSet = $trustedHeaderSet;
+ }
+
+ /**
+ * Gets the list of trusted proxies.
+ *
+ * @return array An array of trusted proxies
+ */
+ public static function getTrustedProxies()
+ {
+ return self::$trustedProxies;
+ }
+
+ /**
+ * Gets the set of trusted headers from trusted proxies.
+ *
+ * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies
+ */
+ public static function getTrustedHeaderSet()
+ {
+ return self::$trustedHeaderSet;
+ }
+
+ /**
+ * Sets a list of trusted host patterns.
+ *
+ * You should only list the hosts you manage using regexs.
+ *
+ * @param array $hostPatterns A list of trusted host patterns
+ */
+ public static function setTrustedHosts(array $hostPatterns)
+ {
+ self::$trustedHostPatterns = array_map(function ($hostPattern) {
+ return sprintf('{%s}i', $hostPattern);
+ }, $hostPatterns);
+ // we need to reset trusted hosts on trusted host patterns change
+ self::$trustedHosts = [];
+ }
+
+ /**
+ * Gets the list of trusted host patterns.
+ *
+ * @return array An array of trusted host patterns
+ */
+ public static function getTrustedHosts()
+ {
+ return self::$trustedHostPatterns;
+ }
+
+ /**
+ * Sets the name for trusted headers.
+ *
+ * The following header keys are supported:
+ *
+ * * Request::HEADER_CLIENT_IP: defaults to X-Forwarded-For (see getClientIp())
+ * * Request::HEADER_CLIENT_HOST: defaults to X-Forwarded-Host (see getHost())
+ * * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getPort())
+ * * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure())
+ * * Request::HEADER_FORWARDED: defaults to Forwarded (see RFC 7239)
+ *
+ * Setting an empty value allows to disable the trusted header for the given key.
+ *
+ * @param string $key The header key
+ * @param string $value The header name
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @deprecated since version 3.3, to be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.
+ */
+ public static function setTrustedHeaderName($key, $value)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.', __METHOD__), E_USER_DEPRECATED);
+
+ if ('forwarded' === $key) {
+ $key = self::HEADER_FORWARDED;
+ } elseif ('client_ip' === $key) {
+ $key = self::HEADER_CLIENT_IP;
+ } elseif ('client_host' === $key) {
+ $key = self::HEADER_CLIENT_HOST;
+ } elseif ('client_proto' === $key) {
+ $key = self::HEADER_CLIENT_PROTO;
+ } elseif ('client_port' === $key) {
+ $key = self::HEADER_CLIENT_PORT;
+ } elseif (!\array_key_exists($key, self::$trustedHeaders)) {
+ throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key));
+ }
+
+ self::$trustedHeaders[$key] = $value;
+
+ if (null !== $value) {
+ self::$trustedHeaderNames[$key] = $value;
+ self::$trustedHeaderSet |= $key;
+ } else {
+ self::$trustedHeaderSet &= ~$key;
+ }
+ }
+
+ /**
+ * Gets the trusted proxy header name.
+ *
+ * @param string $key The header key
+ *
+ * @return string The header name
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @deprecated since version 3.3, to be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.
+ */
+ public static function getTrustedHeaderName($key)
+ {
+ if (2 > \func_num_args() || func_get_arg(1)) {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.', __METHOD__), E_USER_DEPRECATED);
+ }
+
+ if (!\array_key_exists($key, self::$trustedHeaders)) {
+ throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key));
+ }
+
+ return self::$trustedHeaders[$key];
+ }
+
+ /**
+ * Normalizes a query string.
+ *
+ * It builds a normalized query string, where keys/value pairs are alphabetized,
+ * have consistent escaping and unneeded delimiters are removed.
+ *
+ * @param string $qs Query string
+ *
+ * @return string A normalized query string for the Request
+ */
+ public static function normalizeQueryString($qs)
+ {
+ if ('' == $qs) {
+ return '';
+ }
+
+ $parts = [];
+ $order = [];
+
+ foreach (explode('&', $qs) as $param) {
+ if ('' === $param || '=' === $param[0]) {
+ // Ignore useless delimiters, e.g. "x=y&".
+ // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
+ // PHP also does not include them when building _GET.
+ continue;
+ }
+
+ $keyValuePair = explode('=', $param, 2);
+
+ // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
+ // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to
+ // RFC 3986 with rawurlencode.
+ $parts[] = isset($keyValuePair[1]) ?
+ rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) :
+ rawurlencode(urldecode($keyValuePair[0]));
+ $order[] = urldecode($keyValuePair[0]);
+ }
+
+ array_multisort($order, SORT_ASC, $parts);
+
+ return implode('&', $parts);
+ }
+
+ /**
+ * Enables support for the _method request parameter to determine the intended HTTP method.
+ *
+ * Be warned that enabling this feature might lead to CSRF issues in your code.
+ * Check that you are using CSRF tokens when required.
+ * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered
+ * and used to send a "PUT" or "DELETE" request via the _method request parameter.
+ * If these methods are not protected against CSRF, this presents a possible vulnerability.
+ *
+ * The HTTP method can only be overridden when the real HTTP method is POST.
+ */
+ public static function enableHttpMethodParameterOverride()
+ {
+ self::$httpMethodParameterOverride = true;
+ }
+
+ /**
+ * Checks whether support for the _method request parameter is enabled.
+ *
+ * @return bool True when the _method request parameter is enabled, false otherwise
+ */
+ public static function getHttpMethodParameterOverride()
+ {
+ return self::$httpMethodParameterOverride;
+ }
+
+ /**
+ * Gets a "parameter" value from any bag.
+ *
+ * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the
+ * flexibility in controllers, it is better to explicitly get request parameters from the appropriate
+ * public property instead (attributes, query, request).
+ *
+ * Order of precedence: PATH (routing placeholders or custom attributes), GET, BODY
+ *
+ * @param string $key The key
+ * @param mixed $default The default value if the parameter key does not exist
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ if ($this !== $result = $this->attributes->get($key, $this)) {
+ return $result;
+ }
+
+ if ($this !== $result = $this->query->get($key, $this)) {
+ return $result;
+ }
+
+ if ($this !== $result = $this->request->get($key, $this)) {
+ return $result;
+ }
+
+ return $default;
+ }
+
+ /**
+ * Gets the Session.
+ *
+ * @return SessionInterface|null The session
+ */
+ public function getSession()
+ {
+ return $this->session;
+ }
+
+ /**
+ * Whether the request contains a Session which was started in one of the
+ * previous requests.
+ *
+ * @return bool
+ */
+ public function hasPreviousSession()
+ {
+ // the check for $this->session avoids malicious users trying to fake a session cookie with proper name
+ return $this->hasSession() && $this->cookies->has($this->session->getName());
+ }
+
+ /**
+ * Whether the request contains a Session object.
+ *
+ * This method does not give any information about the state of the session object,
+ * like whether the session is started or not. It is just a way to check if this Request
+ * is associated with a Session instance.
+ *
+ * @return bool true when the Request contains a Session object, false otherwise
+ */
+ public function hasSession()
+ {
+ return null !== $this->session;
+ }
+
+ /**
+ * Sets the Session.
+ *
+ * @param SessionInterface $session The Session
+ */
+ public function setSession(SessionInterface $session)
+ {
+ $this->session = $session;
+ }
+
+ /**
+ * Returns the client IP addresses.
+ *
+ * In the returned array the most trusted IP address is first, and the
+ * least trusted one last. The "real" client IP address is the last one,
+ * but this is also the least trusted one. Trusted proxies are stripped.
+ *
+ * Use this method carefully; you should use getClientIp() instead.
+ *
+ * @return array The client IP addresses
+ *
+ * @see getClientIp()
+ */
+ public function getClientIps()
+ {
+ $ip = $this->server->get('REMOTE_ADDR');
+
+ if (!$this->isFromTrustedProxy()) {
+ return [$ip];
+ }
+
+ return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: [$ip];
+ }
+
+ /**
+ * Returns the client IP address.
+ *
+ * This method can read the client IP address from the "X-Forwarded-For" header
+ * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For"
+ * header value is a comma+space separated list of IP addresses, the left-most
+ * being the original client, and each successive proxy that passed the request
+ * adding the IP address where it received the request from.
+ *
+ * If your reverse proxy uses a different header name than "X-Forwarded-For",
+ * ("Client-Ip" for instance), configure it via the $trustedHeaderSet
+ * argument of the Request::setTrustedProxies() method instead.
+ *
+ * @return string|null The client IP address
+ *
+ * @see getClientIps()
+ * @see http://en.wikipedia.org/wiki/X-Forwarded-For
+ */
+ public function getClientIp()
+ {
+ $ipAddresses = $this->getClientIps();
+
+ return $ipAddresses[0];
+ }
+
+ /**
+ * Returns current script name.
+ *
+ * @return string
+ */
+ public function getScriptName()
+ {
+ return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', ''));
+ }
+
+ /**
+ * Returns the path being requested relative to the executed script.
+ *
+ * The path info always starts with a /.
+ *
+ * Suppose this request is instantiated from /mysite on localhost:
+ *
+ * * http://localhost/mysite returns an empty string
+ * * http://localhost/mysite/about returns '/about'
+ * * http://localhost/mysite/enco%20ded returns '/enco%20ded'
+ * * http://localhost/mysite/about?var=1 returns '/about'
+ *
+ * @return string The raw path (i.e. not urldecoded)
+ */
+ public function getPathInfo()
+ {
+ if (null === $this->pathInfo) {
+ $this->pathInfo = $this->preparePathInfo();
+ }
+
+ return $this->pathInfo;
+ }
+
+ /**
+ * Returns the root path from which this request is executed.
+ *
+ * Suppose that an index.php file instantiates this request object:
+ *
+ * * http://localhost/index.php returns an empty string
+ * * http://localhost/index.php/page returns an empty string
+ * * http://localhost/web/index.php returns '/web'
+ * * http://localhost/we%20b/index.php returns '/we%20b'
+ *
+ * @return string The raw path (i.e. not urldecoded)
+ */
+ public function getBasePath()
+ {
+ if (null === $this->basePath) {
+ $this->basePath = $this->prepareBasePath();
+ }
+
+ return $this->basePath;
+ }
+
+ /**
+ * Returns the root URL from which this request is executed.
+ *
+ * The base URL never ends with a /.
+ *
+ * This is similar to getBasePath(), except that it also includes the
+ * script filename (e.g. index.php) if one exists.
+ *
+ * @return string The raw URL (i.e. not urldecoded)
+ */
+ public function getBaseUrl()
+ {
+ if (null === $this->baseUrl) {
+ $this->baseUrl = $this->prepareBaseUrl();
+ }
+
+ return $this->baseUrl;
+ }
+
+ /**
+ * Gets the request's scheme.
+ *
+ * @return string
+ */
+ public function getScheme()
+ {
+ return $this->isSecure() ? 'https' : 'http';
+ }
+
+ /**
+ * Returns the port on which the request is made.
+ *
+ * This method can read the client port from the "X-Forwarded-Port" header
+ * when trusted proxies were set via "setTrustedProxies()".
+ *
+ * The "X-Forwarded-Port" header must contain the client port.
+ *
+ * If your reverse proxy uses a different header name than "X-Forwarded-Port",
+ * configure it via via the $trustedHeaderSet argument of the
+ * Request::setTrustedProxies() method instead.
+ *
+ * @return int|string can be a string if fetched from the server bag
+ */
+ public function getPort()
+ {
+ if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_PORT)) {
+ $host = $host[0];
+ } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
+ $host = $host[0];
+ } elseif (!$host = $this->headers->get('HOST')) {
+ return $this->server->get('SERVER_PORT');
+ }
+
+ if ('[' === $host[0]) {
+ $pos = strpos($host, ':', strrpos($host, ']'));
+ } else {
+ $pos = strrpos($host, ':');
+ }
+
+ if (false !== $pos) {
+ return (int) substr($host, $pos + 1);
+ }
+
+ return 'https' === $this->getScheme() ? 443 : 80;
+ }
+
+ /**
+ * Returns the user.
+ *
+ * @return string|null
+ */
+ public function getUser()
+ {
+ return $this->headers->get('PHP_AUTH_USER');
+ }
+
+ /**
+ * Returns the password.
+ *
+ * @return string|null
+ */
+ public function getPassword()
+ {
+ return $this->headers->get('PHP_AUTH_PW');
+ }
+
+ /**
+ * Gets the user info.
+ *
+ * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server
+ */
+ public function getUserInfo()
+ {
+ $userinfo = $this->getUser();
+
+ $pass = $this->getPassword();
+ if ('' != $pass) {
+ $userinfo .= ":$pass";
+ }
+
+ return $userinfo;
+ }
+
+ /**
+ * Returns the HTTP host being requested.
+ *
+ * The port name will be appended to the host if it's non-standard.
+ *
+ * @return string
+ */
+ public function getHttpHost()
+ {
+ $scheme = $this->getScheme();
+ $port = $this->getPort();
+
+ if (('http' == $scheme && 80 == $port) || ('https' == $scheme && 443 == $port)) {
+ return $this->getHost();
+ }
+
+ return $this->getHost().':'.$port;
+ }
+
+ /**
+ * Returns the requested URI (path and query string).
+ *
+ * @return string The raw URI (i.e. not URI decoded)
+ */
+ public function getRequestUri()
+ {
+ if (null === $this->requestUri) {
+ $this->requestUri = $this->prepareRequestUri();
+ }
+
+ return $this->requestUri;
+ }
+
+ /**
+ * Gets the scheme and HTTP host.
+ *
+ * If the URL was called with basic authentication, the user
+ * and the password are not added to the generated string.
+ *
+ * @return string The scheme and HTTP host
+ */
+ public function getSchemeAndHttpHost()
+ {
+ return $this->getScheme().'://'.$this->getHttpHost();
+ }
+
+ /**
+ * Generates a normalized URI (URL) for the Request.
+ *
+ * @return string A normalized URI (URL) for the Request
+ *
+ * @see getQueryString()
+ */
+ public function getUri()
+ {
+ if (null !== $qs = $this->getQueryString()) {
+ $qs = '?'.$qs;
+ }
+
+ return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs;
+ }
+
+ /**
+ * Generates a normalized URI for the given path.
+ *
+ * @param string $path A path to use instead of the current one
+ *
+ * @return string The normalized URI for the path
+ */
+ public function getUriForPath($path)
+ {
+ return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path;
+ }
+
+ /**
+ * Returns the path as relative reference from the current Request path.
+ *
+ * Only the URIs path component (no schema, host etc.) is relevant and must be given.
+ * Both paths must be absolute and not contain relative parts.
+ * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives.
+ * Furthermore, they can be used to reduce the link size in documents.
+ *
+ * Example target paths, given a base path of "/a/b/c/d":
+ * - "/a/b/c/d" -> ""
+ * - "/a/b/c/" -> "./"
+ * - "/a/b/" -> "../"
+ * - "/a/b/c/other" -> "other"
+ * - "/a/x/y" -> "../../x/y"
+ *
+ * @param string $path The target path
+ *
+ * @return string The relative target path
+ */
+ public function getRelativeUriForPath($path)
+ {
+ // be sure that we are dealing with an absolute path
+ if (!isset($path[0]) || '/' !== $path[0]) {
+ return $path;
+ }
+
+ if ($path === $basePath = $this->getPathInfo()) {
+ return '';
+ }
+
+ $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath);
+ $targetDirs = explode('/', substr($path, 1));
+ array_pop($sourceDirs);
+ $targetFile = array_pop($targetDirs);
+
+ foreach ($sourceDirs as $i => $dir) {
+ if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) {
+ unset($sourceDirs[$i], $targetDirs[$i]);
+ } else {
+ break;
+ }
+ }
+
+ $targetDirs[] = $targetFile;
+ $path = str_repeat('../', \count($sourceDirs)).implode('/', $targetDirs);
+
+ // A reference to the same base directory or an empty subdirectory must be prefixed with "./".
+ // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
+ // as the first segment of a relative-path reference, as it would be mistaken for a scheme name
+ // (see http://tools.ietf.org/html/rfc3986#section-4.2).
+ return !isset($path[0]) || '/' === $path[0]
+ || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
+ ? "./$path" : $path;
+ }
+
+ /**
+ * Generates the normalized query string for the Request.
+ *
+ * It builds a normalized query string, where keys/value pairs are alphabetized
+ * and have consistent escaping.
+ *
+ * @return string|null A normalized query string for the Request
+ */
+ public function getQueryString()
+ {
+ $qs = static::normalizeQueryString($this->server->get('QUERY_STRING'));
+
+ return '' === $qs ? null : $qs;
+ }
+
+ /**
+ * Checks whether the request is secure or not.
+ *
+ * This method can read the client protocol from the "X-Forwarded-Proto" header
+ * when trusted proxies were set via "setTrustedProxies()".
+ *
+ * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http".
+ *
+ * If your reverse proxy uses a different header name than "X-Forwarded-Proto"
+ * ("SSL_HTTPS" for instance), configure it via the $trustedHeaderSet
+ * argument of the Request::setTrustedProxies() method instead.
+ *
+ * @return bool
+ */
+ public function isSecure()
+ {
+ if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) {
+ return \in_array(strtolower($proto[0]), ['https', 'on', 'ssl', '1'], true);
+ }
+
+ $https = $this->server->get('HTTPS');
+
+ return !empty($https) && 'off' !== strtolower($https);
+ }
+
+ /**
+ * Returns the host name.
+ *
+ * This method can read the client host name from the "X-Forwarded-Host" header
+ * when trusted proxies were set via "setTrustedProxies()".
+ *
+ * The "X-Forwarded-Host" header must contain the client host name.
+ *
+ * If your reverse proxy uses a different header name than "X-Forwarded-Host",
+ * configure it via the $trustedHeaderSet argument of the
+ * Request::setTrustedProxies() method instead.
+ *
+ * @return string
+ *
+ * @throws SuspiciousOperationException when the host name is invalid or not trusted
+ */
+ public function getHost()
+ {
+ if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
+ $host = $host[0];
+ } elseif (!$host = $this->headers->get('HOST')) {
+ if (!$host = $this->server->get('SERVER_NAME')) {
+ $host = $this->server->get('SERVER_ADDR', '');
+ }
+ }
+
+ // trim and remove port number from host
+ // host is lowercase as per RFC 952/2181
+ $host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
+
+ // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
+ // check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
+ // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
+ if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
+ if (!$this->isHostValid) {
+ return '';
+ }
+ $this->isHostValid = false;
+
+ throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host));
+ }
+
+ if (\count(self::$trustedHostPatterns) > 0) {
+ // to avoid host header injection attacks, you should provide a list of trusted host patterns
+
+ if (\in_array($host, self::$trustedHosts)) {
+ return $host;
+ }
+
+ foreach (self::$trustedHostPatterns as $pattern) {
+ if (preg_match($pattern, $host)) {
+ self::$trustedHosts[] = $host;
+
+ return $host;
+ }
+ }
+
+ if (!$this->isHostValid) {
+ return '';
+ }
+ $this->isHostValid = false;
+
+ throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host));
+ }
+
+ return $host;
+ }
+
+ /**
+ * Sets the request method.
+ *
+ * @param string $method
+ */
+ public function setMethod($method)
+ {
+ $this->method = null;
+ $this->server->set('REQUEST_METHOD', $method);
+ }
+
+ /**
+ * Gets the request "intended" method.
+ *
+ * If the X-HTTP-Method-Override header is set, and if the method is a POST,
+ * then it is used to determine the "real" intended HTTP method.
+ *
+ * The _method request parameter can also be used to determine the HTTP method,
+ * but only if enableHttpMethodParameterOverride() has been called.
+ *
+ * The method is always an uppercased string.
+ *
+ * @return string The request method
+ *
+ * @see getRealMethod()
+ */
+ public function getMethod()
+ {
+ if (null !== $this->method) {
+ return $this->method;
+ }
+
+ $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
+
+ if ('POST' !== $this->method) {
+ return $this->method;
+ }
+
+ $method = $this->headers->get('X-HTTP-METHOD-OVERRIDE');
+
+ if (!$method && self::$httpMethodParameterOverride) {
+ $method = $this->request->get('_method', $this->query->get('_method', 'POST'));
+ }
+
+ if (!\is_string($method)) {
+ return $this->method;
+ }
+
+ $method = strtoupper($method);
+
+ if (\in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE'], true)) {
+ return $this->method = $method;
+ }
+
+ if (!preg_match('/^[A-Z]++$/D', $method)) {
+ throw new SuspiciousOperationException(sprintf('Invalid method override "%s".', $method));
+ }
+
+ return $this->method = $method;
+ }
+
+ /**
+ * Gets the "real" request method.
+ *
+ * @return string The request method
+ *
+ * @see getMethod()
+ */
+ public function getRealMethod()
+ {
+ return strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
+ }
+
+ /**
+ * Gets the mime type associated with the format.
+ *
+ * @param string $format The format
+ *
+ * @return string|null The associated mime type (null if not found)
+ */
+ public function getMimeType($format)
+ {
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ return isset(static::$formats[$format]) ? static::$formats[$format][0] : null;
+ }
+
+ /**
+ * Gets the mime types associated with the format.
+ *
+ * @param string $format The format
+ *
+ * @return array The associated mime types
+ */
+ public static function getMimeTypes($format)
+ {
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ return isset(static::$formats[$format]) ? static::$formats[$format] : [];
+ }
+
+ /**
+ * Gets the format associated with the mime type.
+ *
+ * @param string $mimeType The associated mime type
+ *
+ * @return string|null The format (null if not found)
+ */
+ public function getFormat($mimeType)
+ {
+ $canonicalMimeType = null;
+ if (false !== $pos = strpos($mimeType, ';')) {
+ $canonicalMimeType = trim(substr($mimeType, 0, $pos));
+ }
+
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ foreach (static::$formats as $format => $mimeTypes) {
+ if (\in_array($mimeType, (array) $mimeTypes)) {
+ return $format;
+ }
+ if (null !== $canonicalMimeType && \in_array($canonicalMimeType, (array) $mimeTypes)) {
+ return $format;
+ }
+ }
+ }
+
+ /**
+ * Associates a format with mime types.
+ *
+ * @param string $format The format
+ * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type)
+ */
+ public function setFormat($format, $mimeTypes)
+ {
+ if (null === static::$formats) {
+ static::initializeFormats();
+ }
+
+ static::$formats[$format] = \is_array($mimeTypes) ? $mimeTypes : [$mimeTypes];
+ }
+
+ /**
+ * Gets the request format.
+ *
+ * Here is the process to determine the format:
+ *
+ * * format defined by the user (with setRequestFormat())
+ * * _format request attribute
+ * * $default
+ *
+ * @param string|null $default The default format
+ *
+ * @return string|null The request format
+ */
+ public function getRequestFormat($default = 'html')
+ {
+ if (null === $this->format) {
+ $this->format = $this->attributes->get('_format');
+ }
+
+ return null === $this->format ? $default : $this->format;
+ }
+
+ /**
+ * Sets the request format.
+ *
+ * @param string $format The request format
+ */
+ public function setRequestFormat($format)
+ {
+ $this->format = $format;
+ }
+
+ /**
+ * Gets the format associated with the request.
+ *
+ * @return string|null The format (null if no content type is present)
+ */
+ public function getContentType()
+ {
+ return $this->getFormat($this->headers->get('CONTENT_TYPE'));
+ }
+
+ /**
+ * Sets the default locale.
+ *
+ * @param string $locale
+ */
+ public function setDefaultLocale($locale)
+ {
+ $this->defaultLocale = $locale;
+
+ if (null === $this->locale) {
+ $this->setPhpDefaultLocale($locale);
+ }
+ }
+
+ /**
+ * Get the default locale.
+ *
+ * @return string
+ */
+ public function getDefaultLocale()
+ {
+ return $this->defaultLocale;
+ }
+
+ /**
+ * Sets the locale.
+ *
+ * @param string $locale
+ */
+ public function setLocale($locale)
+ {
+ $this->setPhpDefaultLocale($this->locale = $locale);
+ }
+
+ /**
+ * Get the locale.
+ *
+ * @return string
+ */
+ public function getLocale()
+ {
+ return null === $this->locale ? $this->defaultLocale : $this->locale;
+ }
+
+ /**
+ * Checks if the request method is of specified type.
+ *
+ * @param string $method Uppercase request method (GET, POST etc)
+ *
+ * @return bool
+ */
+ public function isMethod($method)
+ {
+ return $this->getMethod() === strtoupper($method);
+ }
+
+ /**
+ * Checks whether or not the method is safe.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-4.2.1
+ *
+ * @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default.
+ *
+ * @return bool
+ */
+ public function isMethodSafe(/* $andCacheable = true */)
+ {
+ if (!\func_num_args() || func_get_arg(0)) {
+ // This deprecation should be turned into a BadMethodCallException in 4.0 (without adding the argument in the signature)
+ // then setting $andCacheable to false should be deprecated in 4.1
+ @trigger_error('Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since Symfony 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead.', E_USER_DEPRECATED);
+
+ return \in_array($this->getMethod(), ['GET', 'HEAD']);
+ }
+
+ return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']);
+ }
+
+ /**
+ * Checks whether or not the method is idempotent.
+ *
+ * @return bool
+ */
+ public function isMethodIdempotent()
+ {
+ return \in_array($this->getMethod(), ['HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE']);
+ }
+
+ /**
+ * Checks whether the method is cacheable or not.
+ *
+ * @see https://tools.ietf.org/html/rfc7231#section-4.2.3
+ *
+ * @return bool True for GET and HEAD, false otherwise
+ */
+ public function isMethodCacheable()
+ {
+ return \in_array($this->getMethod(), ['GET', 'HEAD']);
+ }
+
+ /**
+ * Returns the protocol version.
+ *
+ * If the application is behind a proxy, the protocol version used in the
+ * requests between the client and the proxy and between the proxy and the
+ * server might be different. This returns the former (from the "Via" header)
+ * if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns
+ * the latter (from the "SERVER_PROTOCOL" server parameter).
+ *
+ * @return string
+ */
+ public function getProtocolVersion()
+ {
+ if ($this->isFromTrustedProxy()) {
+ preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via'), $matches);
+
+ if ($matches) {
+ return 'HTTP/'.$matches[2];
+ }
+ }
+
+ return $this->server->get('SERVER_PROTOCOL');
+ }
+
+ /**
+ * Returns the request body content.
+ *
+ * @param bool $asResource If true, a resource will be returned
+ *
+ * @return string|resource The request body content or a resource to read the body stream
+ *
+ * @throws \LogicException
+ */
+ public function getContent($asResource = false)
+ {
+ $currentContentIsResource = \is_resource($this->content);
+ if (\PHP_VERSION_ID < 50600 && false === $this->content) {
+ throw new \LogicException('getContent() can only be called once when using the resource return type and PHP below 5.6.');
+ }
+
+ if (true === $asResource) {
+ if ($currentContentIsResource) {
+ rewind($this->content);
+
+ return $this->content;
+ }
+
+ // Content passed in parameter (test)
+ if (\is_string($this->content)) {
+ $resource = fopen('php://temp', 'r+');
+ fwrite($resource, $this->content);
+ rewind($resource);
+
+ return $resource;
+ }
+
+ $this->content = false;
+
+ return fopen('php://input', 'rb');
+ }
+
+ if ($currentContentIsResource) {
+ rewind($this->content);
+
+ return stream_get_contents($this->content);
+ }
+
+ if (null === $this->content || false === $this->content) {
+ $this->content = file_get_contents('php://input');
+ }
+
+ return $this->content;
+ }
+
+ /**
+ * Gets the Etags.
+ *
+ * @return array The entity tags
+ */
+ public function getETags()
+ {
+ return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isNoCache()
+ {
+ return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma');
+ }
+
+ /**
+ * Returns the preferred language.
+ *
+ * @param array $locales An array of ordered available locales
+ *
+ * @return string|null The preferred locale
+ */
+ public function getPreferredLanguage(array $locales = null)
+ {
+ $preferredLanguages = $this->getLanguages();
+
+ if (empty($locales)) {
+ return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null;
+ }
+
+ if (!$preferredLanguages) {
+ return $locales[0];
+ }
+
+ $extendedPreferredLanguages = [];
+ foreach ($preferredLanguages as $language) {
+ $extendedPreferredLanguages[] = $language;
+ if (false !== $position = strpos($language, '_')) {
+ $superLanguage = substr($language, 0, $position);
+ if (!\in_array($superLanguage, $preferredLanguages)) {
+ $extendedPreferredLanguages[] = $superLanguage;
+ }
+ }
+ }
+
+ $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales));
+
+ return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0];
+ }
+
+ /**
+ * Gets a list of languages acceptable by the client browser.
+ *
+ * @return array Languages ordered in the user browser preferences
+ */
+ public function getLanguages()
+ {
+ if (null !== $this->languages) {
+ return $this->languages;
+ }
+
+ $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all();
+ $this->languages = [];
+ foreach ($languages as $lang => $acceptHeaderItem) {
+ if (false !== strpos($lang, '-')) {
+ $codes = explode('-', $lang);
+ if ('i' === $codes[0]) {
+ // Language not listed in ISO 639 that are not variants
+ // of any listed language, which can be registered with the
+ // i-prefix, such as i-cherokee
+ if (\count($codes) > 1) {
+ $lang = $codes[1];
+ }
+ } else {
+ for ($i = 0, $max = \count($codes); $i < $max; ++$i) {
+ if (0 === $i) {
+ $lang = strtolower($codes[0]);
+ } else {
+ $lang .= '_'.strtoupper($codes[$i]);
+ }
+ }
+ }
+ }
+
+ $this->languages[] = $lang;
+ }
+
+ return $this->languages;
+ }
+
+ /**
+ * Gets a list of charsets acceptable by the client browser.
+ *
+ * @return array List of charsets in preferable order
+ */
+ public function getCharsets()
+ {
+ if (null !== $this->charsets) {
+ return $this->charsets;
+ }
+
+ return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all());
+ }
+
+ /**
+ * Gets a list of encodings acceptable by the client browser.
+ *
+ * @return array List of encodings in preferable order
+ */
+ public function getEncodings()
+ {
+ if (null !== $this->encodings) {
+ return $this->encodings;
+ }
+
+ return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all());
+ }
+
+ /**
+ * Gets a list of content types acceptable by the client browser.
+ *
+ * @return array List of content types in preferable order
+ */
+ public function getAcceptableContentTypes()
+ {
+ if (null !== $this->acceptableContentTypes) {
+ return $this->acceptableContentTypes;
+ }
+
+ return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all());
+ }
+
+ /**
+ * Returns true if the request is a XMLHttpRequest.
+ *
+ * It works if your JavaScript library sets an X-Requested-With HTTP header.
+ * It is known to work with common JavaScript frameworks:
+ *
+ * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript
+ *
+ * @return bool true if the request is an XMLHttpRequest, false otherwise
+ */
+ public function isXmlHttpRequest()
+ {
+ return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
+ }
+
+ /*
+ * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24)
+ *
+ * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd).
+ *
+ * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ */
+
+ protected function prepareRequestUri()
+ {
+ $requestUri = '';
+
+ if ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) {
+ // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem)
+ $requestUri = $this->server->get('UNENCODED_URL');
+ $this->server->remove('UNENCODED_URL');
+ $this->server->remove('IIS_WasUrlRewritten');
+ } elseif ($this->server->has('REQUEST_URI')) {
+ $requestUri = $this->server->get('REQUEST_URI');
+
+ if ('' !== $requestUri && '/' === $requestUri[0]) {
+ // To only use path and query remove the fragment.
+ if (false !== $pos = strpos($requestUri, '#')) {
+ $requestUri = substr($requestUri, 0, $pos);
+ }
+ } else {
+ // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path,
+ // only use URL path.
+ $uriComponents = parse_url($requestUri);
+
+ if (isset($uriComponents['path'])) {
+ $requestUri = $uriComponents['path'];
+ }
+
+ if (isset($uriComponents['query'])) {
+ $requestUri .= '?'.$uriComponents['query'];
+ }
+ }
+ } elseif ($this->server->has('ORIG_PATH_INFO')) {
+ // IIS 5.0, PHP as CGI
+ $requestUri = $this->server->get('ORIG_PATH_INFO');
+ if ('' != $this->server->get('QUERY_STRING')) {
+ $requestUri .= '?'.$this->server->get('QUERY_STRING');
+ }
+ $this->server->remove('ORIG_PATH_INFO');
+ }
+
+ // normalize the request URI to ease creating sub-requests from this request
+ $this->server->set('REQUEST_URI', $requestUri);
+
+ return $requestUri;
+ }
+
+ /**
+ * Prepares the base URL.
+ *
+ * @return string
+ */
+ protected function prepareBaseUrl()
+ {
+ $filename = basename($this->server->get('SCRIPT_FILENAME'));
+
+ if (basename($this->server->get('SCRIPT_NAME')) === $filename) {
+ $baseUrl = $this->server->get('SCRIPT_NAME');
+ } elseif (basename($this->server->get('PHP_SELF')) === $filename) {
+ $baseUrl = $this->server->get('PHP_SELF');
+ } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) {
+ $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility
+ } else {
+ // Backtrack up the script_filename to find the portion matching
+ // php_self
+ $path = $this->server->get('PHP_SELF', '');
+ $file = $this->server->get('SCRIPT_FILENAME', '');
+ $segs = explode('/', trim($file, '/'));
+ $segs = array_reverse($segs);
+ $index = 0;
+ $last = \count($segs);
+ $baseUrl = '';
+ do {
+ $seg = $segs[$index];
+ $baseUrl = '/'.$seg.$baseUrl;
+ ++$index;
+ } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos);
+ }
+
+ // Does the baseUrl have anything in common with the request_uri?
+ $requestUri = $this->getRequestUri();
+ if ('' !== $requestUri && '/' !== $requestUri[0]) {
+ $requestUri = '/'.$requestUri;
+ }
+
+ if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) {
+ // full $baseUrl matches
+ return $prefix;
+ }
+
+ if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(\dirname($baseUrl), '/'.\DIRECTORY_SEPARATOR).'/')) {
+ // directory portion of $baseUrl matches
+ return rtrim($prefix, '/'.\DIRECTORY_SEPARATOR);
+ }
+
+ $truncatedRequestUri = $requestUri;
+ if (false !== $pos = strpos($requestUri, '?')) {
+ $truncatedRequestUri = substr($requestUri, 0, $pos);
+ }
+
+ $basename = basename($baseUrl);
+ if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) {
+ // no match whatsoever; set it blank
+ return '';
+ }
+
+ // If using mod_rewrite or ISAPI_Rewrite strip the script filename
+ // out of baseUrl. $pos !== 0 makes sure it is not matching a value
+ // from PATH_INFO or QUERY_STRING
+ if (\strlen($requestUri) >= \strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && 0 !== $pos) {
+ $baseUrl = substr($requestUri, 0, $pos + \strlen($baseUrl));
+ }
+
+ return rtrim($baseUrl, '/'.\DIRECTORY_SEPARATOR);
+ }
+
+ /**
+ * Prepares the base path.
+ *
+ * @return string base path
+ */
+ protected function prepareBasePath()
+ {
+ $baseUrl = $this->getBaseUrl();
+ if (empty($baseUrl)) {
+ return '';
+ }
+
+ $filename = basename($this->server->get('SCRIPT_FILENAME'));
+ if (basename($baseUrl) === $filename) {
+ $basePath = \dirname($baseUrl);
+ } else {
+ $basePath = $baseUrl;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $basePath = str_replace('\\', '/', $basePath);
+ }
+
+ return rtrim($basePath, '/');
+ }
+
+ /**
+ * Prepares the path info.
+ *
+ * @return string path info
+ */
+ protected function preparePathInfo()
+ {
+ if (null === ($requestUri = $this->getRequestUri())) {
+ return '/';
+ }
+
+ // Remove the query string from REQUEST_URI
+ if (false !== $pos = strpos($requestUri, '?')) {
+ $requestUri = substr($requestUri, 0, $pos);
+ }
+ if ('' !== $requestUri && '/' !== $requestUri[0]) {
+ $requestUri = '/'.$requestUri;
+ }
+
+ if (null === ($baseUrl = $this->getBaseUrl())) {
+ return $requestUri;
+ }
+
+ $pathInfo = substr($requestUri, \strlen($baseUrl));
+ if (false === $pathInfo || '' === $pathInfo) {
+ // If substr() returns false then PATH_INFO is set to an empty string
+ return '/';
+ }
+
+ return (string) $pathInfo;
+ }
+
+ /**
+ * Initializes HTTP request formats.
+ */
+ protected static function initializeFormats()
+ {
+ static::$formats = [
+ 'html' => ['text/html', 'application/xhtml+xml'],
+ 'txt' => ['text/plain'],
+ 'js' => ['application/javascript', 'application/x-javascript', 'text/javascript'],
+ 'css' => ['text/css'],
+ 'json' => ['application/json', 'application/x-json'],
+ 'jsonld' => ['application/ld+json'],
+ 'xml' => ['text/xml', 'application/xml', 'application/x-xml'],
+ 'rdf' => ['application/rdf+xml'],
+ 'atom' => ['application/atom+xml'],
+ 'rss' => ['application/rss+xml'],
+ 'form' => ['application/x-www-form-urlencoded'],
+ ];
+ }
+
+ /**
+ * Sets the default PHP locale.
+ *
+ * @param string $locale
+ */
+ private function setPhpDefaultLocale($locale)
+ {
+ // if either the class Locale doesn't exist, or an exception is thrown when
+ // setting the default locale, the intl module is not installed, and
+ // the call can be ignored:
+ try {
+ if (class_exists('Locale', false)) {
+ \Locale::setDefault($locale);
+ }
+ } catch (\Exception $e) {
+ }
+ }
+
+ /**
+ * Returns the prefix as encoded in the string when the string starts with
+ * the given prefix, false otherwise.
+ *
+ * @param string $string The urlencoded string
+ * @param string $prefix The prefix not encoded
+ *
+ * @return string|false The prefix as it is encoded in $string, or false
+ */
+ private function getUrlencodedPrefix($string, $prefix)
+ {
+ if (0 !== strpos(rawurldecode($string), $prefix)) {
+ return false;
+ }
+
+ $len = \strlen($prefix);
+
+ if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) {
+ return $match[0];
+ }
+
+ return false;
+ }
+
+ private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
+ {
+ if (self::$requestFactory) {
+ $request = \call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content);
+
+ if (!$request instanceof self) {
+ throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.');
+ }
+
+ return $request;
+ }
+
+ return new static($query, $request, $attributes, $cookies, $files, $server, $content);
+ }
+
+ /**
+ * Indicates whether this request originated from a trusted proxy.
+ *
+ * This can be useful to determine whether or not to trust the
+ * contents of a proxy-specific header.
+ *
+ * @return bool true if the request came from a trusted proxy, false otherwise
+ */
+ public function isFromTrustedProxy()
+ {
+ return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies);
+ }
+
+ private function getTrustedValues($type, $ip = null)
+ {
+ $clientValues = [];
+ $forwardedValues = [];
+
+ if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) {
+ foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) {
+ $clientValues[] = (self::HEADER_CLIENT_PORT === $type ? '0.0.0.0:' : '').trim($v);
+ }
+ }
+
+ if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
+ $forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
+ $forwardedValues = preg_match_all(sprintf('{(?:%s)="?([a-zA-Z0-9\.:_\-/\[\]]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : [];
+ if (self::HEADER_CLIENT_PORT === $type) {
+ foreach ($forwardedValues as $k => $v) {
+ if (']' === substr($v, -1) || false === $v = strrchr($v, ':')) {
+ $v = $this->isSecure() ? ':443' : ':80';
+ }
+ $forwardedValues[$k] = '0.0.0.0'.$v;
+ }
+ }
+ }
+
+ if (null !== $ip) {
+ $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip);
+ $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip);
+ }
+
+ if ($forwardedValues === $clientValues || !$clientValues) {
+ return $forwardedValues;
+ }
+
+ if (!$forwardedValues) {
+ return $clientValues;
+ }
+
+ if (!$this->isForwardedValid) {
+ return null !== $ip ? ['0.0.0.0', $ip] : [];
+ }
+ $this->isForwardedValid = false;
+
+ throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type]));
+ }
+
+ private function normalizeAndFilterClientIps(array $clientIps, $ip)
+ {
+ if (!$clientIps) {
+ return [];
+ }
+ $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
+ $firstTrustedIp = null;
+
+ foreach ($clientIps as $key => $clientIp) {
+ if (strpos($clientIp, '.')) {
+ // Strip :port from IPv4 addresses. This is allowed in Forwarded
+ // and may occur in X-Forwarded-For.
+ $i = strpos($clientIp, ':');
+ if ($i) {
+ $clientIps[$key] = $clientIp = substr($clientIp, 0, $i);
+ }
+ } elseif (0 === strpos($clientIp, '[')) {
+ // Strip brackets and :port from IPv6 addresses.
+ $i = strpos($clientIp, ']', 1);
+ $clientIps[$key] = $clientIp = substr($clientIp, 1, $i - 1);
+ }
+
+ if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
+ unset($clientIps[$key]);
+
+ continue;
+ }
+
+ if (IpUtils::checkIp($clientIp, self::$trustedProxies)) {
+ unset($clientIps[$key]);
+
+ // Fallback to this when the client IP falls into the range of trusted proxies
+ if (null === $firstTrustedIp) {
+ $firstTrustedIp = $clientIp;
+ }
+ }
+ }
+
+ // Now the IP chain contains only untrusted proxies and the client IP
+ return $clientIps ? array_reverse($clientIps) : [$firstTrustedIp];
+ }
+}
diff --git a/vendor/symfony/http-foundation/RequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher.php
new file mode 100755
index 0000000..cadf70c
--- /dev/null
+++ b/vendor/symfony/http-foundation/RequestMatcher.php
@@ -0,0 +1,178 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * RequestMatcher compares a pre-defined set of checks against a Request instance.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class RequestMatcher implements RequestMatcherInterface
+{
+ /**
+ * @var string|null
+ */
+ private $path;
+
+ /**
+ * @var string|null
+ */
+ private $host;
+
+ /**
+ * @var string[]
+ */
+ private $methods = [];
+
+ /**
+ * @var string[]
+ */
+ private $ips = [];
+
+ /**
+ * @var array
+ */
+ private $attributes = [];
+
+ /**
+ * @var string[]
+ */
+ private $schemes = [];
+
+ /**
+ * @param string|null $path
+ * @param string|null $host
+ * @param string|string[]|null $methods
+ * @param string|string[]|null $ips
+ * @param array $attributes
+ * @param string|string[]|null $schemes
+ */
+ public function __construct($path = null, $host = null, $methods = null, $ips = null, array $attributes = [], $schemes = null)
+ {
+ $this->matchPath($path);
+ $this->matchHost($host);
+ $this->matchMethod($methods);
+ $this->matchIps($ips);
+ $this->matchScheme($schemes);
+
+ foreach ($attributes as $k => $v) {
+ $this->matchAttribute($k, $v);
+ }
+ }
+
+ /**
+ * Adds a check for the HTTP scheme.
+ *
+ * @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes
+ */
+ public function matchScheme($scheme)
+ {
+ $this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : [];
+ }
+
+ /**
+ * Adds a check for the URL host name.
+ *
+ * @param string|null $regexp A Regexp
+ */
+ public function matchHost($regexp)
+ {
+ $this->host = $regexp;
+ }
+
+ /**
+ * Adds a check for the URL path info.
+ *
+ * @param string|null $regexp A Regexp
+ */
+ public function matchPath($regexp)
+ {
+ $this->path = $regexp;
+ }
+
+ /**
+ * Adds a check for the client IP.
+ *
+ * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
+ */
+ public function matchIp($ip)
+ {
+ $this->matchIps($ip);
+ }
+
+ /**
+ * Adds a check for the client IP.
+ *
+ * @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
+ */
+ public function matchIps($ips)
+ {
+ $this->ips = null !== $ips ? (array) $ips : [];
+ }
+
+ /**
+ * Adds a check for the HTTP method.
+ *
+ * @param string|string[]|null $method An HTTP method or an array of HTTP methods
+ */
+ public function matchMethod($method)
+ {
+ $this->methods = null !== $method ? array_map('strtoupper', (array) $method) : [];
+ }
+
+ /**
+ * Adds a check for request attribute.
+ *
+ * @param string $key The request attribute name
+ * @param string $regexp A Regexp
+ */
+ public function matchAttribute($key, $regexp)
+ {
+ $this->attributes[$key] = $regexp;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function matches(Request $request)
+ {
+ if ($this->schemes && !\in_array($request->getScheme(), $this->schemes, true)) {
+ return false;
+ }
+
+ if ($this->methods && !\in_array($request->getMethod(), $this->methods, true)) {
+ return false;
+ }
+
+ foreach ($this->attributes as $key => $pattern) {
+ if (!preg_match('{'.$pattern.'}', $request->attributes->get($key))) {
+ return false;
+ }
+ }
+
+ if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) {
+ return false;
+ }
+
+ if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) {
+ return false;
+ }
+
+ if (IpUtils::checkIp($request->getClientIp(), $this->ips)) {
+ return true;
+ }
+
+ // Note to future implementors: add additional checks above the
+ // foreach above or else your check might not be run!
+ return 0 === \count($this->ips);
+ }
+}
diff --git a/vendor/symfony/http-foundation/RequestMatcherInterface.php b/vendor/symfony/http-foundation/RequestMatcherInterface.php
new file mode 100755
index 0000000..c26db3e
--- /dev/null
+++ b/vendor/symfony/http-foundation/RequestMatcherInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * RequestMatcherInterface is an interface for strategies to match a Request.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface RequestMatcherInterface
+{
+ /**
+ * Decides whether the rule(s) implemented by the strategy matches the supplied request.
+ *
+ * @return bool true if the request matches, false otherwise
+ */
+ public function matches(Request $request);
+}
diff --git a/vendor/symfony/http-foundation/RequestStack.php b/vendor/symfony/http-foundation/RequestStack.php
new file mode 100755
index 0000000..885d78a
--- /dev/null
+++ b/vendor/symfony/http-foundation/RequestStack.php
@@ -0,0 +1,103 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Request stack that controls the lifecycle of requests.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class RequestStack
+{
+ /**
+ * @var Request[]
+ */
+ private $requests = [];
+
+ /**
+ * Pushes a Request on the stack.
+ *
+ * This method should generally not be called directly as the stack
+ * management should be taken care of by the application itself.
+ */
+ public function push(Request $request)
+ {
+ $this->requests[] = $request;
+ }
+
+ /**
+ * Pops the current request from the stack.
+ *
+ * This operation lets the current request go out of scope.
+ *
+ * This method should generally not be called directly as the stack
+ * management should be taken care of by the application itself.
+ *
+ * @return Request|null
+ */
+ public function pop()
+ {
+ if (!$this->requests) {
+ return;
+ }
+
+ return array_pop($this->requests);
+ }
+
+ /**
+ * @return Request|null
+ */
+ public function getCurrentRequest()
+ {
+ return end($this->requests) ?: null;
+ }
+
+ /**
+ * Gets the master Request.
+ *
+ * Be warned that making your code aware of the master request
+ * might make it un-compatible with other features of your framework
+ * like ESI support.
+ *
+ * @return Request|null
+ */
+ public function getMasterRequest()
+ {
+ if (!$this->requests) {
+ return;
+ }
+
+ return $this->requests[0];
+ }
+
+ /**
+ * Returns the parent request of the current.
+ *
+ * Be warned that making your code aware of the parent request
+ * might make it un-compatible with other features of your framework
+ * like ESI support.
+ *
+ * If current Request is the master request, it returns null.
+ *
+ * @return Request|null
+ */
+ public function getParentRequest()
+ {
+ $pos = \count($this->requests) - 2;
+
+ if (!isset($this->requests[$pos])) {
+ return;
+ }
+
+ return $this->requests[$pos];
+ }
+}
diff --git a/vendor/symfony/http-foundation/Response.php b/vendor/symfony/http-foundation/Response.php
new file mode 100755
index 0000000..b7d1162
--- /dev/null
+++ b/vendor/symfony/http-foundation/Response.php
@@ -0,0 +1,1308 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Response represents an HTTP response.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class Response
+{
+ const HTTP_CONTINUE = 100;
+ const HTTP_SWITCHING_PROTOCOLS = 101;
+ const HTTP_PROCESSING = 102; // RFC2518
+ const HTTP_EARLY_HINTS = 103; // RFC8297
+ const HTTP_OK = 200;
+ const HTTP_CREATED = 201;
+ const HTTP_ACCEPTED = 202;
+ const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
+ const HTTP_NO_CONTENT = 204;
+ const HTTP_RESET_CONTENT = 205;
+ const HTTP_PARTIAL_CONTENT = 206;
+ const HTTP_MULTI_STATUS = 207; // RFC4918
+ const HTTP_ALREADY_REPORTED = 208; // RFC5842
+ const HTTP_IM_USED = 226; // RFC3229
+ const HTTP_MULTIPLE_CHOICES = 300;
+ const HTTP_MOVED_PERMANENTLY = 301;
+ const HTTP_FOUND = 302;
+ const HTTP_SEE_OTHER = 303;
+ const HTTP_NOT_MODIFIED = 304;
+ const HTTP_USE_PROXY = 305;
+ const HTTP_RESERVED = 306;
+ const HTTP_TEMPORARY_REDIRECT = 307;
+ const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238
+ const HTTP_BAD_REQUEST = 400;
+ const HTTP_UNAUTHORIZED = 401;
+ const HTTP_PAYMENT_REQUIRED = 402;
+ const HTTP_FORBIDDEN = 403;
+ const HTTP_NOT_FOUND = 404;
+ const HTTP_METHOD_NOT_ALLOWED = 405;
+ const HTTP_NOT_ACCEPTABLE = 406;
+ const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
+ const HTTP_REQUEST_TIMEOUT = 408;
+ const HTTP_CONFLICT = 409;
+ const HTTP_GONE = 410;
+ const HTTP_LENGTH_REQUIRED = 411;
+ const HTTP_PRECONDITION_FAILED = 412;
+ const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
+ const HTTP_REQUEST_URI_TOO_LONG = 414;
+ const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
+ const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+ const HTTP_EXPECTATION_FAILED = 417;
+ const HTTP_I_AM_A_TEAPOT = 418; // RFC2324
+ const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540
+ const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918
+ const HTTP_LOCKED = 423; // RFC4918
+ const HTTP_FAILED_DEPENDENCY = 424; // RFC4918
+
+ /**
+ * @deprecated
+ */
+ const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817
+ const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04
+ const HTTP_UPGRADE_REQUIRED = 426; // RFC2817
+ const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
+ const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
+ const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
+ const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
+ const HTTP_INTERNAL_SERVER_ERROR = 500;
+ const HTTP_NOT_IMPLEMENTED = 501;
+ const HTTP_BAD_GATEWAY = 502;
+ const HTTP_SERVICE_UNAVAILABLE = 503;
+ const HTTP_GATEWAY_TIMEOUT = 504;
+ const HTTP_VERSION_NOT_SUPPORTED = 505;
+ const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295
+ const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918
+ const HTTP_LOOP_DETECTED = 508; // RFC5842
+ const HTTP_NOT_EXTENDED = 510; // RFC2774
+ const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585
+
+ /**
+ * @var \Symfony\Component\HttpFoundation\ResponseHeaderBag
+ */
+ public $headers;
+
+ /**
+ * @var string
+ */
+ protected $content;
+
+ /**
+ * @var string
+ */
+ protected $version;
+
+ /**
+ * @var int
+ */
+ protected $statusCode;
+
+ /**
+ * @var string
+ */
+ protected $statusText;
+
+ /**
+ * @var string
+ */
+ protected $charset;
+
+ /**
+ * Status codes translation table.
+ *
+ * The list of codes is complete according to the
+ * {@link http://www.iana.org/assignments/http-status-codes/ Hypertext Transfer Protocol (HTTP) Status Code Registry}
+ * (last updated 2016-03-01).
+ *
+ * Unless otherwise noted, the status code is defined in RFC2616.
+ *
+ * @var array
+ */
+ public static $statusTexts = [
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing', // RFC2518
+ 103 => 'Early Hints',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status', // RFC4918
+ 208 => 'Already Reported', // RFC5842
+ 226 => 'IM Used', // RFC3229
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+ 308 => 'Permanent Redirect', // RFC7238
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Payload Too Large',
+ 414 => 'URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot', // RFC2324
+ 421 => 'Misdirected Request', // RFC7540
+ 422 => 'Unprocessable Entity', // RFC4918
+ 423 => 'Locked', // RFC4918
+ 424 => 'Failed Dependency', // RFC4918
+ 425 => 'Too Early', // RFC-ietf-httpbis-replay-04
+ 426 => 'Upgrade Required', // RFC2817
+ 428 => 'Precondition Required', // RFC6585
+ 429 => 'Too Many Requests', // RFC6585
+ 431 => 'Request Header Fields Too Large', // RFC6585
+ 451 => 'Unavailable For Legal Reasons', // RFC7725
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 506 => 'Variant Also Negotiates', // RFC2295
+ 507 => 'Insufficient Storage', // RFC4918
+ 508 => 'Loop Detected', // RFC5842
+ 510 => 'Not Extended', // RFC2774
+ 511 => 'Network Authentication Required', // RFC6585
+ ];
+
+ /**
+ * @param mixed $content The response content, see setContent()
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @throws \InvalidArgumentException When the HTTP status code is not valid
+ */
+ public function __construct($content = '', $status = 200, $headers = [])
+ {
+ $this->headers = new ResponseHeaderBag($headers);
+ $this->setContent($content);
+ $this->setStatusCode($status);
+ $this->setProtocolVersion('1.0');
+ }
+
+ /**
+ * Factory method for chainability.
+ *
+ * Example:
+ *
+ * return Response::create($body, 200)
+ * ->setSharedMaxAge(300);
+ *
+ * @param mixed $content The response content, see setContent()
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @return static
+ */
+ public static function create($content = '', $status = 200, $headers = [])
+ {
+ return new static($content, $status, $headers);
+ }
+
+ /**
+ * Returns the Response as an HTTP string.
+ *
+ * The string representation of the Response is the same as the
+ * one that will be sent to the client only if the prepare() method
+ * has been called before.
+ *
+ * @return string The Response as an HTTP string
+ *
+ * @see prepare()
+ */
+ public function __toString()
+ {
+ return
+ sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
+ $this->headers."\r\n".
+ $this->getContent();
+ }
+
+ /**
+ * Clones the current Response instance.
+ */
+ public function __clone()
+ {
+ $this->headers = clone $this->headers;
+ }
+
+ /**
+ * Prepares the Response before it is sent to the client.
+ *
+ * This method tweaks the Response to ensure that it is
+ * compliant with RFC 2616. Most of the changes are based on
+ * the Request that is "associated" with this Response.
+ *
+ * @return $this
+ */
+ public function prepare(Request $request)
+ {
+ $headers = $this->headers;
+
+ if ($this->isInformational() || $this->isEmpty()) {
+ $this->setContent(null);
+ $headers->remove('Content-Type');
+ $headers->remove('Content-Length');
+ } else {
+ // Content-type based on the Request
+ if (!$headers->has('Content-Type')) {
+ $format = $request->getRequestFormat();
+ if (null !== $format && $mimeType = $request->getMimeType($format)) {
+ $headers->set('Content-Type', $mimeType);
+ }
+ }
+
+ // Fix Content-Type
+ $charset = $this->charset ?: 'UTF-8';
+ if (!$headers->has('Content-Type')) {
+ $headers->set('Content-Type', 'text/html; charset='.$charset);
+ } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
+ // add the charset
+ $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
+ }
+
+ // Fix Content-Length
+ if ($headers->has('Transfer-Encoding')) {
+ $headers->remove('Content-Length');
+ }
+
+ if ($request->isMethod('HEAD')) {
+ // cf. RFC2616 14.13
+ $length = $headers->get('Content-Length');
+ $this->setContent(null);
+ if ($length) {
+ $headers->set('Content-Length', $length);
+ }
+ }
+ }
+
+ // Fix protocol
+ if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
+ $this->setProtocolVersion('1.1');
+ }
+
+ // Check if we need to send extra expire info headers
+ if ('1.0' == $this->getProtocolVersion() && false !== strpos($headers->get('Cache-Control'), 'no-cache')) {
+ $headers->set('pragma', 'no-cache');
+ $headers->set('expires', -1);
+ }
+
+ $this->ensureIEOverSSLCompatibility($request);
+
+ return $this;
+ }
+
+ /**
+ * Sends HTTP headers.
+ *
+ * @return $this
+ */
+ public function sendHeaders()
+ {
+ // headers have already been sent by the developer
+ if (headers_sent()) {
+ return $this;
+ }
+
+ // headers
+ foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
+ $replace = 0 === strcasecmp($name, 'Content-Type');
+ foreach ($values as $value) {
+ header($name.': '.$value, $replace, $this->statusCode);
+ }
+ }
+
+ // cookies
+ foreach ($this->headers->getCookies() as $cookie) {
+ header('Set-Cookie: '.$cookie->getName().strstr($cookie, '='), false, $this->statusCode);
+ }
+
+ // status
+ header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
+
+ return $this;
+ }
+
+ /**
+ * Sends content for the current web response.
+ *
+ * @return $this
+ */
+ public function sendContent()
+ {
+ echo $this->content;
+
+ return $this;
+ }
+
+ /**
+ * Sends HTTP headers and content.
+ *
+ * @return $this
+ */
+ public function send()
+ {
+ $this->sendHeaders();
+ $this->sendContent();
+
+ if (\function_exists('fastcgi_finish_request')) {
+ fastcgi_finish_request();
+ } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
+ static::closeOutputBuffers(0, true);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the response content.
+ *
+ * Valid types are strings, numbers, null, and objects that implement a __toString() method.
+ *
+ * @param mixed $content Content that can be cast to string
+ *
+ * @return $this
+ *
+ * @throws \UnexpectedValueException
+ */
+ public function setContent($content)
+ {
+ if (null !== $content && !\is_string($content) && !is_numeric($content) && !\is_callable([$content, '__toString'])) {
+ throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', \gettype($content)));
+ }
+
+ $this->content = (string) $content;
+
+ return $this;
+ }
+
+ /**
+ * Gets the current response content.
+ *
+ * @return string Content
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * Sets the HTTP protocol version (1.0 or 1.1).
+ *
+ * @param string $version The HTTP protocol version
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setProtocolVersion($version)
+ {
+ $this->version = $version;
+
+ return $this;
+ }
+
+ /**
+ * Gets the HTTP protocol version.
+ *
+ * @return string The HTTP protocol version
+ *
+ * @final since version 3.2
+ */
+ public function getProtocolVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * Sets the response status code.
+ *
+ * If the status text is null it will be automatically populated for the known
+ * status codes and left empty otherwise.
+ *
+ * @param int $code HTTP status code
+ * @param mixed $text HTTP status text
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException When the HTTP status code is not valid
+ *
+ * @final since version 3.2
+ */
+ public function setStatusCode($code, $text = null)
+ {
+ $this->statusCode = $code = (int) $code;
+ if ($this->isInvalid()) {
+ throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code));
+ }
+
+ if (null === $text) {
+ $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status';
+
+ return $this;
+ }
+
+ if (false === $text) {
+ $this->statusText = '';
+
+ return $this;
+ }
+
+ $this->statusText = $text;
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the status code for the current web response.
+ *
+ * @return int Status code
+ *
+ * @final since version 3.2
+ */
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ /**
+ * Sets the response charset.
+ *
+ * @param string $charset Character set
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setCharset($charset)
+ {
+ $this->charset = $charset;
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the response charset.
+ *
+ * @return string Character set
+ *
+ * @final since version 3.2
+ */
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ /**
+ * Returns true if the response may safely be kept in a shared (surrogate) cache.
+ *
+ * Responses marked "private" with an explicit Cache-Control directive are
+ * considered uncacheable.
+ *
+ * Responses with neither a freshness lifetime (Expires, max-age) nor cache
+ * validator (Last-Modified, ETag) are considered uncacheable because there is
+ * no way to tell when or how to remove them from the cache.
+ *
+ * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation,
+ * for example "status codes that are defined as cacheable by default [...]
+ * can be reused by a cache with heuristic expiration unless otherwise indicated"
+ * (https://tools.ietf.org/html/rfc7231#section-6.1)
+ *
+ * @return bool true if the response is worth caching, false otherwise
+ *
+ * @final since version 3.3
+ */
+ public function isCacheable()
+ {
+ if (!\in_array($this->statusCode, [200, 203, 300, 301, 302, 404, 410])) {
+ return false;
+ }
+
+ if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) {
+ return false;
+ }
+
+ return $this->isValidateable() || $this->isFresh();
+ }
+
+ /**
+ * Returns true if the response is "fresh".
+ *
+ * Fresh responses may be served from cache without any interaction with the
+ * origin. A response is considered fresh when it includes a Cache-Control/max-age
+ * indicator or Expires header and the calculated age is less than the freshness lifetime.
+ *
+ * @return bool true if the response is fresh, false otherwise
+ *
+ * @final since version 3.3
+ */
+ public function isFresh()
+ {
+ return $this->getTtl() > 0;
+ }
+
+ /**
+ * Returns true if the response includes headers that can be used to validate
+ * the response with the origin server using a conditional GET request.
+ *
+ * @return bool true if the response is validateable, false otherwise
+ *
+ * @final since version 3.3
+ */
+ public function isValidateable()
+ {
+ return $this->headers->has('Last-Modified') || $this->headers->has('ETag');
+ }
+
+ /**
+ * Marks the response as "private".
+ *
+ * It makes the response ineligible for serving other clients.
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setPrivate()
+ {
+ $this->headers->removeCacheControlDirective('public');
+ $this->headers->addCacheControlDirective('private');
+
+ return $this;
+ }
+
+ /**
+ * Marks the response as "public".
+ *
+ * It makes the response eligible for serving other clients.
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setPublic()
+ {
+ $this->headers->addCacheControlDirective('public');
+ $this->headers->removeCacheControlDirective('private');
+
+ return $this;
+ }
+
+ /**
+ * Marks the response as "immutable".
+ *
+ * @param bool $immutable enables or disables the immutable directive
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setImmutable($immutable = true)
+ {
+ if ($immutable) {
+ $this->headers->addCacheControlDirective('immutable');
+ } else {
+ $this->headers->removeCacheControlDirective('immutable');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns true if the response is marked as "immutable".
+ *
+ * @return bool returns true if the response is marked as "immutable"; otherwise false
+ *
+ * @final
+ */
+ public function isImmutable()
+ {
+ return $this->headers->hasCacheControlDirective('immutable');
+ }
+
+ /**
+ * Returns true if the response must be revalidated by caches.
+ *
+ * This method indicates that the response must not be served stale by a
+ * cache in any circumstance without first revalidating with the origin.
+ * When present, the TTL of the response should not be overridden to be
+ * greater than the value provided by the origin.
+ *
+ * @return bool true if the response must be revalidated by a cache, false otherwise
+ *
+ * @final since version 3.3
+ */
+ public function mustRevalidate()
+ {
+ return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate');
+ }
+
+ /**
+ * Returns the Date header as a DateTime instance.
+ *
+ * @return \DateTime A \DateTime instance
+ *
+ * @throws \RuntimeException When the header is not parseable
+ *
+ * @final since version 3.2
+ */
+ public function getDate()
+ {
+ return $this->headers->getDate('Date');
+ }
+
+ /**
+ * Sets the Date header.
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setDate(\DateTime $date)
+ {
+ $date->setTimezone(new \DateTimeZone('UTC'));
+ $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT');
+
+ return $this;
+ }
+
+ /**
+ * Returns the age of the response.
+ *
+ * @return int The age of the response in seconds
+ *
+ * @final since version 3.2
+ */
+ public function getAge()
+ {
+ if (null !== $age = $this->headers->get('Age')) {
+ return (int) $age;
+ }
+
+ return max(time() - $this->getDate()->format('U'), 0);
+ }
+
+ /**
+ * Marks the response stale by setting the Age header to be equal to the maximum age of the response.
+ *
+ * @return $this
+ */
+ public function expire()
+ {
+ if ($this->isFresh()) {
+ $this->headers->set('Age', $this->getMaxAge());
+ $this->headers->remove('Expires');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the value of the Expires header as a DateTime instance.
+ *
+ * @return \DateTime|null A DateTime instance or null if the header does not exist
+ *
+ * @final since version 3.2
+ */
+ public function getExpires()
+ {
+ try {
+ return $this->headers->getDate('Expires');
+ } catch (\RuntimeException $e) {
+ // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past
+ return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000');
+ }
+ }
+
+ /**
+ * Sets the Expires HTTP header with a DateTime instance.
+ *
+ * Passing null as value will remove the header.
+ *
+ * @param \DateTime|null $date A \DateTime instance or null to remove the header
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setExpires(\DateTime $date = null)
+ {
+ if (null === $date) {
+ $this->headers->remove('Expires');
+ } else {
+ $date = clone $date;
+ $date->setTimezone(new \DateTimeZone('UTC'));
+ $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the number of seconds after the time specified in the response's Date
+ * header when the response should no longer be considered fresh.
+ *
+ * First, it checks for a s-maxage directive, then a max-age directive, and then it falls
+ * back on an expires header. It returns null when no maximum age can be established.
+ *
+ * @return int|null Number of seconds
+ *
+ * @final since version 3.2
+ */
+ public function getMaxAge()
+ {
+ if ($this->headers->hasCacheControlDirective('s-maxage')) {
+ return (int) $this->headers->getCacheControlDirective('s-maxage');
+ }
+
+ if ($this->headers->hasCacheControlDirective('max-age')) {
+ return (int) $this->headers->getCacheControlDirective('max-age');
+ }
+
+ if (null !== $this->getExpires()) {
+ return $this->getExpires()->format('U') - $this->getDate()->format('U');
+ }
+ }
+
+ /**
+ * Sets the number of seconds after which the response should no longer be considered fresh.
+ *
+ * This methods sets the Cache-Control max-age directive.
+ *
+ * @param int $value Number of seconds
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setMaxAge($value)
+ {
+ $this->headers->addCacheControlDirective('max-age', $value);
+
+ return $this;
+ }
+
+ /**
+ * Sets the number of seconds after which the response should no longer be considered fresh by shared caches.
+ *
+ * This methods sets the Cache-Control s-maxage directive.
+ *
+ * @param int $value Number of seconds
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setSharedMaxAge($value)
+ {
+ $this->setPublic();
+ $this->headers->addCacheControlDirective('s-maxage', $value);
+
+ return $this;
+ }
+
+ /**
+ * Returns the response's time-to-live in seconds.
+ *
+ * It returns null when no freshness information is present in the response.
+ *
+ * When the responses TTL is <= 0, the response may not be served from cache without first
+ * revalidating with the origin.
+ *
+ * @return int|null The TTL in seconds
+ *
+ * @final since version 3.2
+ */
+ public function getTtl()
+ {
+ if (null !== $maxAge = $this->getMaxAge()) {
+ return $maxAge - $this->getAge();
+ }
+ }
+
+ /**
+ * Sets the response's time-to-live for shared caches.
+ *
+ * This method adjusts the Cache-Control/s-maxage directive.
+ *
+ * @param int $seconds Number of seconds
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setTtl($seconds)
+ {
+ $this->setSharedMaxAge($this->getAge() + $seconds);
+
+ return $this;
+ }
+
+ /**
+ * Sets the response's time-to-live for private/client caches.
+ *
+ * This method adjusts the Cache-Control/max-age directive.
+ *
+ * @param int $seconds Number of seconds
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setClientTtl($seconds)
+ {
+ $this->setMaxAge($this->getAge() + $seconds);
+
+ return $this;
+ }
+
+ /**
+ * Returns the Last-Modified HTTP header as a DateTime instance.
+ *
+ * @return \DateTime|null A DateTime instance or null if the header does not exist
+ *
+ * @throws \RuntimeException When the HTTP header is not parseable
+ *
+ * @final since version 3.2
+ */
+ public function getLastModified()
+ {
+ return $this->headers->getDate('Last-Modified');
+ }
+
+ /**
+ * Sets the Last-Modified HTTP header with a DateTime instance.
+ *
+ * Passing null as value will remove the header.
+ *
+ * @param \DateTime|null $date A \DateTime instance or null to remove the header
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setLastModified(\DateTime $date = null)
+ {
+ if (null === $date) {
+ $this->headers->remove('Last-Modified');
+ } else {
+ $date = clone $date;
+ $date->setTimezone(new \DateTimeZone('UTC'));
+ $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the literal value of the ETag HTTP header.
+ *
+ * @return string|null The ETag HTTP header or null if it does not exist
+ *
+ * @final since version 3.2
+ */
+ public function getEtag()
+ {
+ return $this->headers->get('ETag');
+ }
+
+ /**
+ * Sets the ETag value.
+ *
+ * @param string|null $etag The ETag unique identifier or null to remove the header
+ * @param bool $weak Whether you want a weak ETag or not
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setEtag($etag = null, $weak = false)
+ {
+ if (null === $etag) {
+ $this->headers->remove('Etag');
+ } else {
+ if (0 !== strpos($etag, '"')) {
+ $etag = '"'.$etag.'"';
+ }
+
+ $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the response's cache headers (validation and/or expiration).
+ *
+ * Available options are: etag, last_modified, max_age, s_maxage, private, public and immutable.
+ *
+ * @param array $options An array of cache options
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @final since version 3.3
+ */
+ public function setCache(array $options)
+ {
+ if ($diff = array_diff(array_keys($options), ['etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public', 'immutable'])) {
+ throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff)));
+ }
+
+ if (isset($options['etag'])) {
+ $this->setEtag($options['etag']);
+ }
+
+ if (isset($options['last_modified'])) {
+ $this->setLastModified($options['last_modified']);
+ }
+
+ if (isset($options['max_age'])) {
+ $this->setMaxAge($options['max_age']);
+ }
+
+ if (isset($options['s_maxage'])) {
+ $this->setSharedMaxAge($options['s_maxage']);
+ }
+
+ if (isset($options['public'])) {
+ if ($options['public']) {
+ $this->setPublic();
+ } else {
+ $this->setPrivate();
+ }
+ }
+
+ if (isset($options['private'])) {
+ if ($options['private']) {
+ $this->setPrivate();
+ } else {
+ $this->setPublic();
+ }
+ }
+
+ if (isset($options['immutable'])) {
+ $this->setImmutable((bool) $options['immutable']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Modifies the response so that it conforms to the rules defined for a 304 status code.
+ *
+ * This sets the status, removes the body, and discards any headers
+ * that MUST NOT be included in 304 responses.
+ *
+ * @return $this
+ *
+ * @see http://tools.ietf.org/html/rfc2616#section-10.3.5
+ *
+ * @final since version 3.3
+ */
+ public function setNotModified()
+ {
+ $this->setStatusCode(304);
+ $this->setContent(null);
+
+ // remove headers that MUST NOT be included with 304 Not Modified responses
+ foreach (['Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified'] as $header) {
+ $this->headers->remove($header);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns true if the response includes a Vary header.
+ *
+ * @return bool true if the response includes a Vary header, false otherwise
+ *
+ * @final since version 3.2
+ */
+ public function hasVary()
+ {
+ return null !== $this->headers->get('Vary');
+ }
+
+ /**
+ * Returns an array of header names given in the Vary header.
+ *
+ * @return array An array of Vary names
+ *
+ * @final since version 3.2
+ */
+ public function getVary()
+ {
+ if (!$vary = $this->headers->get('Vary', null, false)) {
+ return [];
+ }
+
+ $ret = [];
+ foreach ($vary as $item) {
+ $ret = array_merge($ret, preg_split('/[\s,]+/', $item));
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Sets the Vary header.
+ *
+ * @param string|array $headers
+ * @param bool $replace Whether to replace the actual value or not (true by default)
+ *
+ * @return $this
+ *
+ * @final since version 3.2
+ */
+ public function setVary($headers, $replace = true)
+ {
+ $this->headers->set('Vary', $headers, $replace);
+
+ return $this;
+ }
+
+ /**
+ * Determines if the Response validators (ETag, Last-Modified) match
+ * a conditional value specified in the Request.
+ *
+ * If the Response is not modified, it sets the status code to 304 and
+ * removes the actual content by calling the setNotModified() method.
+ *
+ * @return bool true if the Response validators match the Request, false otherwise
+ *
+ * @final since version 3.3
+ */
+ public function isNotModified(Request $request)
+ {
+ if (!$request->isMethodCacheable()) {
+ return false;
+ }
+
+ $notModified = false;
+ $lastModified = $this->headers->get('Last-Modified');
+ $modifiedSince = $request->headers->get('If-Modified-Since');
+
+ if ($etags = $request->getETags()) {
+ $notModified = \in_array($this->getEtag(), $etags) || \in_array('*', $etags);
+ }
+
+ if ($modifiedSince && $lastModified) {
+ $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified);
+ }
+
+ if ($notModified) {
+ $this->setNotModified();
+ }
+
+ return $notModified;
+ }
+
+ /**
+ * Is response invalid?
+ *
+ * @return bool
+ *
+ * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+ *
+ * @final since version 3.2
+ */
+ public function isInvalid()
+ {
+ return $this->statusCode < 100 || $this->statusCode >= 600;
+ }
+
+ /**
+ * Is response informative?
+ *
+ * @return bool
+ *
+ * @final since version 3.3
+ */
+ public function isInformational()
+ {
+ return $this->statusCode >= 100 && $this->statusCode < 200;
+ }
+
+ /**
+ * Is response successful?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isSuccessful()
+ {
+ return $this->statusCode >= 200 && $this->statusCode < 300;
+ }
+
+ /**
+ * Is the response a redirect?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isRedirection()
+ {
+ return $this->statusCode >= 300 && $this->statusCode < 400;
+ }
+
+ /**
+ * Is there a client error?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isClientError()
+ {
+ return $this->statusCode >= 400 && $this->statusCode < 500;
+ }
+
+ /**
+ * Was there a server side error?
+ *
+ * @return bool
+ *
+ * @final since version 3.3
+ */
+ public function isServerError()
+ {
+ return $this->statusCode >= 500 && $this->statusCode < 600;
+ }
+
+ /**
+ * Is the response OK?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isOk()
+ {
+ return 200 === $this->statusCode;
+ }
+
+ /**
+ * Is the response forbidden?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isForbidden()
+ {
+ return 403 === $this->statusCode;
+ }
+
+ /**
+ * Is the response a not found error?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isNotFound()
+ {
+ return 404 === $this->statusCode;
+ }
+
+ /**
+ * Is the response a redirect of some form?
+ *
+ * @param string $location
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isRedirect($location = null)
+ {
+ return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308]) && (null === $location ?: $location == $this->headers->get('Location'));
+ }
+
+ /**
+ * Is the response empty?
+ *
+ * @return bool
+ *
+ * @final since version 3.2
+ */
+ public function isEmpty()
+ {
+ return \in_array($this->statusCode, [204, 304]);
+ }
+
+ /**
+ * Cleans or flushes output buffers up to target level.
+ *
+ * Resulting level can be greater than target level if a non-removable buffer has been encountered.
+ *
+ * @param int $targetLevel The target output buffering level
+ * @param bool $flush Whether to flush or clean the buffers
+ *
+ * @final since version 3.3
+ */
+ public static function closeOutputBuffers($targetLevel, $flush)
+ {
+ $status = ob_get_status(true);
+ $level = \count($status);
+ // PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3
+ $flags = \defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1;
+
+ while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) {
+ if ($flush) {
+ ob_end_flush();
+ } else {
+ ob_end_clean();
+ }
+ }
+ }
+
+ /**
+ * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9.
+ *
+ * @see http://support.microsoft.com/kb/323308
+ *
+ * @final since version 3.3
+ */
+ protected function ensureIEOverSSLCompatibility(Request $request)
+ {
+ if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) && true === $request->isSecure()) {
+ if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) {
+ $this->headers->remove('Cache-Control');
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/http-foundation/ResponseHeaderBag.php b/vendor/symfony/http-foundation/ResponseHeaderBag.php
new file mode 100755
index 0000000..1dc8dc2
--- /dev/null
+++ b/vendor/symfony/http-foundation/ResponseHeaderBag.php
@@ -0,0 +1,340 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * ResponseHeaderBag is a container for Response HTTP headers.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class ResponseHeaderBag extends HeaderBag
+{
+ const COOKIES_FLAT = 'flat';
+ const COOKIES_ARRAY = 'array';
+
+ const DISPOSITION_ATTACHMENT = 'attachment';
+ const DISPOSITION_INLINE = 'inline';
+
+ protected $computedCacheControl = [];
+ protected $cookies = [];
+ protected $headerNames = [];
+
+ public function __construct(array $headers = [])
+ {
+ parent::__construct($headers);
+
+ if (!isset($this->headers['cache-control'])) {
+ $this->set('Cache-Control', '');
+ }
+
+ /* RFC2616 - 14.18 says all Responses need to have a Date */
+ if (!isset($this->headers['date'])) {
+ $this->initDate();
+ }
+ }
+
+ /**
+ * Returns the headers, with original capitalizations.
+ *
+ * @return array An array of headers
+ */
+ public function allPreserveCase()
+ {
+ $headers = [];
+ foreach ($this->all() as $name => $value) {
+ $headers[isset($this->headerNames[$name]) ? $this->headerNames[$name] : $name] = $value;
+ }
+
+ return $headers;
+ }
+
+ public function allPreserveCaseWithoutCookies()
+ {
+ $headers = $this->allPreserveCase();
+ if (isset($this->headerNames['set-cookie'])) {
+ unset($headers[$this->headerNames['set-cookie']]);
+ }
+
+ return $headers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function replace(array $headers = [])
+ {
+ $this->headerNames = [];
+
+ parent::replace($headers);
+
+ if (!isset($this->headers['cache-control'])) {
+ $this->set('Cache-Control', '');
+ }
+
+ if (!isset($this->headers['date'])) {
+ $this->initDate();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all()
+ {
+ $headers = parent::all();
+ foreach ($this->getCookies() as $cookie) {
+ $headers['set-cookie'][] = (string) $cookie;
+ }
+
+ return $headers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($key, $values, $replace = true)
+ {
+ $uniqueKey = str_replace('_', '-', strtolower($key));
+
+ if ('set-cookie' === $uniqueKey) {
+ if ($replace) {
+ $this->cookies = [];
+ }
+ foreach ((array) $values as $cookie) {
+ $this->setCookie(Cookie::fromString($cookie));
+ }
+ $this->headerNames[$uniqueKey] = $key;
+
+ return;
+ }
+
+ $this->headerNames[$uniqueKey] = $key;
+
+ parent::set($key, $values, $replace);
+
+ // ensure the cache-control header has sensible defaults
+ if (\in_array($uniqueKey, ['cache-control', 'etag', 'last-modified', 'expires'], true)) {
+ $computed = $this->computeCacheControlValue();
+ $this->headers['cache-control'] = [$computed];
+ $this->headerNames['cache-control'] = 'Cache-Control';
+ $this->computedCacheControl = $this->parseCacheControl($computed);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove($key)
+ {
+ $uniqueKey = str_replace('_', '-', strtolower($key));
+ unset($this->headerNames[$uniqueKey]);
+
+ if ('set-cookie' === $uniqueKey) {
+ $this->cookies = [];
+
+ return;
+ }
+
+ parent::remove($key);
+
+ if ('cache-control' === $uniqueKey) {
+ $this->computedCacheControl = [];
+ }
+
+ if ('date' === $uniqueKey) {
+ $this->initDate();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasCacheControlDirective($key)
+ {
+ return \array_key_exists($key, $this->computedCacheControl);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCacheControlDirective($key)
+ {
+ return \array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
+ }
+
+ public function setCookie(Cookie $cookie)
+ {
+ $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
+ $this->headerNames['set-cookie'] = 'Set-Cookie';
+ }
+
+ /**
+ * Removes a cookie from the array, but does not unset it in the browser.
+ *
+ * @param string $name
+ * @param string $path
+ * @param string $domain
+ */
+ public function removeCookie($name, $path = '/', $domain = null)
+ {
+ if (null === $path) {
+ $path = '/';
+ }
+
+ unset($this->cookies[$domain][$path][$name]);
+
+ if (empty($this->cookies[$domain][$path])) {
+ unset($this->cookies[$domain][$path]);
+
+ if (empty($this->cookies[$domain])) {
+ unset($this->cookies[$domain]);
+ }
+ }
+
+ if (empty($this->cookies)) {
+ unset($this->headerNames['set-cookie']);
+ }
+ }
+
+ /**
+ * Returns an array with all cookies.
+ *
+ * @param string $format
+ *
+ * @return Cookie[]
+ *
+ * @throws \InvalidArgumentException When the $format is invalid
+ */
+ public function getCookies($format = self::COOKIES_FLAT)
+ {
+ if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) {
+ throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY])));
+ }
+
+ if (self::COOKIES_ARRAY === $format) {
+ return $this->cookies;
+ }
+
+ $flattenedCookies = [];
+ foreach ($this->cookies as $path) {
+ foreach ($path as $cookies) {
+ foreach ($cookies as $cookie) {
+ $flattenedCookies[] = $cookie;
+ }
+ }
+ }
+
+ return $flattenedCookies;
+ }
+
+ /**
+ * Clears a cookie in the browser.
+ *
+ * @param string $name
+ * @param string $path
+ * @param string $domain
+ * @param bool $secure
+ * @param bool $httpOnly
+ */
+ public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true)
+ {
+ $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly));
+ }
+
+ /**
+ * Generates a HTTP Content-Disposition field-value.
+ *
+ * @param string $disposition One of "inline" or "attachment"
+ * @param string $filename A unicode string
+ * @param string $filenameFallback A string containing only ASCII characters that
+ * is semantically equivalent to $filename. If the filename is already ASCII,
+ * it can be omitted, or just copied from $filename
+ *
+ * @return string A string suitable for use as a Content-Disposition field-value
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @see RFC 6266
+ */
+ public function makeDisposition($disposition, $filename, $filenameFallback = '')
+ {
+ if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) {
+ throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
+ }
+
+ if ('' == $filenameFallback) {
+ $filenameFallback = $filename;
+ }
+
+ // filenameFallback is not ASCII.
+ if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
+ throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
+ }
+
+ // percent characters aren't safe in fallback.
+ if (false !== strpos($filenameFallback, '%')) {
+ throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
+ }
+
+ // path separators aren't allowed in either.
+ if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) {
+ throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
+ }
+
+ $output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback));
+
+ if ($filename !== $filenameFallback) {
+ $output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename));
+ }
+
+ return $output;
+ }
+
+ /**
+ * Returns the calculated value of the cache-control header.
+ *
+ * This considers several other headers and calculates or modifies the
+ * cache-control header to a sensible, conservative value.
+ *
+ * @return string
+ */
+ protected function computeCacheControlValue()
+ {
+ if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) {
+ return 'no-cache, private';
+ }
+
+ if (!$this->cacheControl) {
+ // conservative by default
+ return 'private, must-revalidate';
+ }
+
+ $header = $this->getCacheControlHeader();
+ if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) {
+ return $header;
+ }
+
+ // public if s-maxage is defined, private otherwise
+ if (!isset($this->cacheControl['s-maxage'])) {
+ return $header.', private';
+ }
+
+ return $header;
+ }
+
+ private function initDate()
+ {
+ $now = \DateTime::createFromFormat('U', time());
+ $now->setTimezone(new \DateTimeZone('UTC'));
+ $this->set('Date', $now->format('D, d M Y H:i:s').' GMT');
+ }
+}
diff --git a/vendor/symfony/http-foundation/ServerBag.php b/vendor/symfony/http-foundation/ServerBag.php
new file mode 100755
index 0000000..90da49f
--- /dev/null
+++ b/vendor/symfony/http-foundation/ServerBag.php
@@ -0,0 +1,102 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * ServerBag is a container for HTTP headers from the $_SERVER variable.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
+ * @author Robert Kiss <kepten@gmail.com>
+ */
+class ServerBag extends ParameterBag
+{
+ /**
+ * Gets the HTTP headers.
+ *
+ * @return array
+ */
+ public function getHeaders()
+ {
+ $headers = [];
+ $contentHeaders = ['CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true];
+ foreach ($this->parameters as $key => $value) {
+ if (0 === strpos($key, 'HTTP_')) {
+ $headers[substr($key, 5)] = $value;
+ }
+ // CONTENT_* are not prefixed with HTTP_
+ elseif (isset($contentHeaders[$key])) {
+ $headers[$key] = $value;
+ }
+ }
+
+ if (isset($this->parameters['PHP_AUTH_USER'])) {
+ $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER'];
+ $headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : '';
+ } else {
+ /*
+ * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default
+ * For this workaround to work, add these lines to your .htaccess file:
+ * RewriteCond %{HTTP:Authorization} ^(.+)$
+ * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+ *
+ * A sample .htaccess file:
+ * RewriteEngine On
+ * RewriteCond %{HTTP:Authorization} ^(.+)$
+ * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+ * RewriteCond %{REQUEST_FILENAME} !-f
+ * RewriteRule ^(.*)$ app.php [QSA,L]
+ */
+
+ $authorizationHeader = null;
+ if (isset($this->parameters['HTTP_AUTHORIZATION'])) {
+ $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION'];
+ } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) {
+ $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION'];
+ }
+
+ if (null !== $authorizationHeader) {
+ if (0 === stripos($authorizationHeader, 'basic ')) {
+ // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic
+ $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2);
+ if (2 == \count($exploded)) {
+ list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded;
+ }
+ } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) {
+ // In some circumstances PHP_AUTH_DIGEST needs to be set
+ $headers['PHP_AUTH_DIGEST'] = $authorizationHeader;
+ $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader;
+ } elseif (0 === stripos($authorizationHeader, 'bearer ')) {
+ /*
+ * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables,
+ * I'll just set $headers['AUTHORIZATION'] here.
+ * http://php.net/manual/en/reserved.variables.server.php
+ */
+ $headers['AUTHORIZATION'] = $authorizationHeader;
+ }
+ }
+ }
+
+ if (isset($headers['AUTHORIZATION'])) {
+ return $headers;
+ }
+
+ // PHP_AUTH_USER/PHP_AUTH_PW
+ if (isset($headers['PHP_AUTH_USER'])) {
+ $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']);
+ } elseif (isset($headers['PHP_AUTH_DIGEST'])) {
+ $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST'];
+ }
+
+ return $headers;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php
new file mode 100755
index 0000000..07118e8
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php
@@ -0,0 +1,148 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Attribute;
+
+/**
+ * This class relates to session attribute storage.
+ */
+class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable
+{
+ private $name = 'attributes';
+ private $storageKey;
+
+ protected $attributes = [];
+
+ /**
+ * @param string $storageKey The key used to store attributes in the session
+ */
+ public function __construct($storageKey = '_sf2_attributes')
+ {
+ $this->storageKey = $storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$attributes)
+ {
+ $this->attributes = &$attributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($name)
+ {
+ return \array_key_exists($name, $this->attributes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($name, $default = null)
+ {
+ return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function replace(array $attributes)
+ {
+ $this->attributes = [];
+ foreach ($attributes as $key => $value) {
+ $this->set($key, $value);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove($name)
+ {
+ $retval = null;
+ if (\array_key_exists($name, $this->attributes)) {
+ $retval = $this->attributes[$name];
+ unset($this->attributes[$name]);
+ }
+
+ return $retval;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ $return = $this->attributes;
+ $this->attributes = [];
+
+ return $return;
+ }
+
+ /**
+ * Returns an iterator for attributes.
+ *
+ * @return \ArrayIterator An \ArrayIterator instance
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->attributes);
+ }
+
+ /**
+ * Returns the number of attributes.
+ *
+ * @return int The number of attributes
+ */
+ public function count()
+ {
+ return \count($this->attributes);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php
new file mode 100755
index 0000000..0d8d179
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php
@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Attribute;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * Attributes store.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+interface AttributeBagInterface extends SessionBagInterface
+{
+ /**
+ * Checks if an attribute is defined.
+ *
+ * @param string $name The attribute name
+ *
+ * @return bool true if the attribute is defined, false otherwise
+ */
+ public function has($name);
+
+ /**
+ * Returns an attribute.
+ *
+ * @param string $name The attribute name
+ * @param mixed $default The default value if not found
+ *
+ * @return mixed
+ */
+ public function get($name, $default = null);
+
+ /**
+ * Sets an attribute.
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function set($name, $value);
+
+ /**
+ * Returns attributes.
+ *
+ * @return array Attributes
+ */
+ public function all();
+
+ /**
+ * Sets attributes.
+ *
+ * @param array $attributes Attributes
+ */
+ public function replace(array $attributes);
+
+ /**
+ * Removes an attribute.
+ *
+ * @param string $name
+ *
+ * @return mixed The removed value or null when it does not exist
+ */
+ public function remove($name);
+}
diff --git a/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php b/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php
new file mode 100755
index 0000000..bbf2e39
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php
@@ -0,0 +1,159 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Attribute;
+
+/**
+ * This class provides structured storage of session attributes using
+ * a name spacing character in the key.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NamespacedAttributeBag extends AttributeBag
+{
+ private $namespaceCharacter;
+
+ /**
+ * @param string $storageKey Session storage key
+ * @param string $namespaceCharacter Namespace character to use in keys
+ */
+ public function __construct($storageKey = '_sf2_attributes', $namespaceCharacter = '/')
+ {
+ $this->namespaceCharacter = $namespaceCharacter;
+ parent::__construct($storageKey);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($name)
+ {
+ // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
+ $attributes = $this->resolveAttributePath($name);
+ $name = $this->resolveKey($name);
+
+ if (null === $attributes) {
+ return false;
+ }
+
+ return \array_key_exists($name, $attributes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($name, $default = null)
+ {
+ // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is
+ $attributes = $this->resolveAttributePath($name);
+ $name = $this->resolveKey($name);
+
+ if (null === $attributes) {
+ return $default;
+ }
+
+ return \array_key_exists($name, $attributes) ? $attributes[$name] : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($name, $value)
+ {
+ $attributes = &$this->resolveAttributePath($name, true);
+ $name = $this->resolveKey($name);
+ $attributes[$name] = $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove($name)
+ {
+ $retval = null;
+ $attributes = &$this->resolveAttributePath($name);
+ $name = $this->resolveKey($name);
+ if (null !== $attributes && \array_key_exists($name, $attributes)) {
+ $retval = $attributes[$name];
+ unset($attributes[$name]);
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Resolves a path in attributes property and returns it as a reference.
+ *
+ * This method allows structured namespacing of session attributes.
+ *
+ * @param string $name Key name
+ * @param bool $writeContext Write context, default false
+ *
+ * @return array
+ */
+ protected function &resolveAttributePath($name, $writeContext = false)
+ {
+ $array = &$this->attributes;
+ $name = (0 === strpos($name, $this->namespaceCharacter)) ? substr($name, 1) : $name;
+
+ // Check if there is anything to do, else return
+ if (!$name) {
+ return $array;
+ }
+
+ $parts = explode($this->namespaceCharacter, $name);
+ if (\count($parts) < 2) {
+ if (!$writeContext) {
+ return $array;
+ }
+
+ $array[$parts[0]] = [];
+
+ return $array;
+ }
+
+ unset($parts[\count($parts) - 1]);
+
+ foreach ($parts as $part) {
+ if (null !== $array && !\array_key_exists($part, $array)) {
+ if (!$writeContext) {
+ $null = null;
+
+ return $null;
+ }
+
+ $array[$part] = [];
+ }
+
+ $array = &$array[$part];
+ }
+
+ return $array;
+ }
+
+ /**
+ * Resolves the key from the name.
+ *
+ * This is the last part in a dot separated string.
+ *
+ * @param string $name
+ *
+ * @return string
+ */
+ protected function resolveKey($name)
+ {
+ if (false !== $pos = strrpos($name, $this->namespaceCharacter)) {
+ $name = substr($name, $pos + 1);
+ }
+
+ return $name;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php
new file mode 100755
index 0000000..451c4a5
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php
@@ -0,0 +1,161 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Flash;
+
+/**
+ * AutoExpireFlashBag flash message container.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class AutoExpireFlashBag implements FlashBagInterface
+{
+ private $name = 'flashes';
+ private $flashes = ['display' => [], 'new' => []];
+ private $storageKey;
+
+ /**
+ * @param string $storageKey The key used to store flashes in the session
+ */
+ public function __construct($storageKey = '_symfony_flashes')
+ {
+ $this->storageKey = $storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$flashes)
+ {
+ $this->flashes = &$flashes;
+
+ // The logic: messages from the last request will be stored in new, so we move them to previous
+ // This request we will show what is in 'display'. What is placed into 'new' this time round will
+ // be moved to display next time round.
+ $this->flashes['display'] = \array_key_exists('new', $this->flashes) ? $this->flashes['new'] : [];
+ $this->flashes['new'] = [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function add($type, $message)
+ {
+ $this->flashes['new'][$type][] = $message;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function peek($type, array $default = [])
+ {
+ return $this->has($type) ? $this->flashes['display'][$type] : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function peekAll()
+ {
+ return \array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($type, array $default = [])
+ {
+ $return = $default;
+
+ if (!$this->has($type)) {
+ return $return;
+ }
+
+ if (isset($this->flashes['display'][$type])) {
+ $return = $this->flashes['display'][$type];
+ unset($this->flashes['display'][$type]);
+ }
+
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all()
+ {
+ $return = $this->flashes['display'];
+ $this->flashes['display'] = [];
+
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setAll(array $messages)
+ {
+ $this->flashes['new'] = $messages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($type, $messages)
+ {
+ $this->flashes['new'][$type] = (array) $messages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($type)
+ {
+ return \array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function keys()
+ {
+ return array_keys($this->flashes['display']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ return $this->all();
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBag.php b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php
new file mode 100755
index 0000000..f5d984a
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php
@@ -0,0 +1,152 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Flash;
+
+/**
+ * FlashBag flash message container.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class FlashBag implements FlashBagInterface
+{
+ private $name = 'flashes';
+ private $flashes = [];
+ private $storageKey;
+
+ /**
+ * @param string $storageKey The key used to store flashes in the session
+ */
+ public function __construct($storageKey = '_symfony_flashes')
+ {
+ $this->storageKey = $storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$flashes)
+ {
+ $this->flashes = &$flashes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function add($type, $message)
+ {
+ $this->flashes[$type][] = $message;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function peek($type, array $default = [])
+ {
+ return $this->has($type) ? $this->flashes[$type] : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function peekAll()
+ {
+ return $this->flashes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($type, array $default = [])
+ {
+ if (!$this->has($type)) {
+ return $default;
+ }
+
+ $return = $this->flashes[$type];
+
+ unset($this->flashes[$type]);
+
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all()
+ {
+ $return = $this->peekAll();
+ $this->flashes = [];
+
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($type, $messages)
+ {
+ $this->flashes[$type] = (array) $messages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setAll(array $messages)
+ {
+ $this->flashes = $messages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($type)
+ {
+ return \array_key_exists($type, $this->flashes) && $this->flashes[$type];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function keys()
+ {
+ return array_keys($this->flashes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->storageKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ return $this->all();
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php
new file mode 100755
index 0000000..99e8074
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php
@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Flash;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * FlashBagInterface.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+interface FlashBagInterface extends SessionBagInterface
+{
+ /**
+ * Adds a flash message for the given type.
+ *
+ * @param string $type
+ * @param mixed $message
+ */
+ public function add($type, $message);
+
+ /**
+ * Registers one or more messages for a given type.
+ *
+ * @param string $type
+ * @param string|array $messages
+ */
+ public function set($type, $messages);
+
+ /**
+ * Gets flash messages for a given type.
+ *
+ * @param string $type Message category type
+ * @param array $default Default value if $type does not exist
+ *
+ * @return array
+ */
+ public function peek($type, array $default = []);
+
+ /**
+ * Gets all flash messages.
+ *
+ * @return array
+ */
+ public function peekAll();
+
+ /**
+ * Gets and clears flash from the stack.
+ *
+ * @param string $type
+ * @param array $default Default value if $type does not exist
+ *
+ * @return array
+ */
+ public function get($type, array $default = []);
+
+ /**
+ * Gets and clears flashes from the stack.
+ *
+ * @return array
+ */
+ public function all();
+
+ /**
+ * Sets all flash messages.
+ */
+ public function setAll(array $messages);
+
+ /**
+ * Has flash messages for a given type?
+ *
+ * @param string $type
+ *
+ * @return bool
+ */
+ public function has($type);
+
+ /**
+ * Returns a list of all defined types.
+ *
+ * @return array
+ */
+ public function keys();
+}
diff --git a/vendor/symfony/http-foundation/Session/Session.php b/vendor/symfony/http-foundation/Session/Session.php
new file mode 100755
index 0000000..867ceba
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Session.php
@@ -0,0 +1,280 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Drak <drak@zikula.org>
+ */
+class Session implements SessionInterface, \IteratorAggregate, \Countable
+{
+ protected $storage;
+
+ private $flashName;
+ private $attributeName;
+ private $data = [];
+ private $usageIndex = 0;
+
+ /**
+ * @param SessionStorageInterface $storage A SessionStorageInterface instance
+ * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag)
+ * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag)
+ */
+ public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null)
+ {
+ $this->storage = $storage ?: new NativeSessionStorage();
+
+ $attributes = $attributes ?: new AttributeBag();
+ $this->attributeName = $attributes->getName();
+ $this->registerBag($attributes);
+
+ $flashes = $flashes ?: new FlashBag();
+ $this->flashName = $flashes->getName();
+ $this->registerBag($flashes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start()
+ {
+ return $this->storage->start();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($name)
+ {
+ return $this->getAttributeBag()->has($name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($name, $default = null)
+ {
+ return $this->getAttributeBag()->get($name, $default);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($name, $value)
+ {
+ $this->getAttributeBag()->set($name, $value);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all()
+ {
+ return $this->getAttributeBag()->all();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function replace(array $attributes)
+ {
+ $this->getAttributeBag()->replace($attributes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function remove($name)
+ {
+ return $this->getAttributeBag()->remove($name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ $this->getAttributeBag()->clear();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isStarted()
+ {
+ return $this->storage->isStarted();
+ }
+
+ /**
+ * Returns an iterator for attributes.
+ *
+ * @return \ArrayIterator An \ArrayIterator instance
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->getAttributeBag()->all());
+ }
+
+ /**
+ * Returns the number of attributes.
+ *
+ * @return int The number of attributes
+ */
+ public function count()
+ {
+ return \count($this->getAttributeBag()->all());
+ }
+
+ /**
+ * @return int
+ *
+ * @internal
+ */
+ public function getUsageIndex()
+ {
+ return $this->usageIndex;
+ }
+
+ /**
+ * @return bool
+ *
+ * @internal
+ */
+ public function isEmpty()
+ {
+ if ($this->isStarted()) {
+ ++$this->usageIndex;
+ }
+ foreach ($this->data as &$data) {
+ if (!empty($data)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidate($lifetime = null)
+ {
+ $this->storage->clear();
+
+ return $this->migrate(true, $lifetime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function migrate($destroy = false, $lifetime = null)
+ {
+ return $this->storage->regenerate($destroy, $lifetime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save()
+ {
+ $this->storage->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getId()
+ {
+ return $this->storage->getId();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setId($id)
+ {
+ if ($this->storage->getId() !== $id) {
+ $this->storage->setId($id);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->storage->getName();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setName($name)
+ {
+ $this->storage->setName($name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadataBag()
+ {
+ ++$this->usageIndex;
+
+ return $this->storage->getMetadataBag();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function registerBag(SessionBagInterface $bag)
+ {
+ $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBag($name)
+ {
+ return $this->storage->getBag($name)->getBag();
+ }
+
+ /**
+ * Gets the flashbag interface.
+ *
+ * @return FlashBagInterface
+ */
+ public function getFlashBag()
+ {
+ return $this->getBag($this->flashName);
+ }
+
+ /**
+ * Gets the attributebag interface.
+ *
+ * Note that this method was added to help with IDE autocompletion.
+ *
+ * @return AttributeBagInterface
+ */
+ private function getAttributeBag()
+ {
+ return $this->getBag($this->attributeName);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/SessionBagInterface.php b/vendor/symfony/http-foundation/Session/SessionBagInterface.php
new file mode 100755
index 0000000..8e37d06
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/SessionBagInterface.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+/**
+ * Session Bag store.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+interface SessionBagInterface
+{
+ /**
+ * Gets this bag's name.
+ *
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Initializes the Bag.
+ */
+ public function initialize(array &$array);
+
+ /**
+ * Gets the storage key for this bag.
+ *
+ * @return string
+ */
+ public function getStorageKey();
+
+ /**
+ * Clears out data from bag.
+ *
+ * @return mixed Whatever data was contained
+ */
+ public function clear();
+}
diff --git a/vendor/symfony/http-foundation/Session/SessionBagProxy.php b/vendor/symfony/http-foundation/Session/SessionBagProxy.php
new file mode 100755
index 0000000..3504bdf
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/SessionBagProxy.php
@@ -0,0 +1,89 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+final class SessionBagProxy implements SessionBagInterface
+{
+ private $bag;
+ private $data;
+ private $usageIndex;
+
+ public function __construct(SessionBagInterface $bag, array &$data, &$usageIndex)
+ {
+ $this->bag = $bag;
+ $this->data = &$data;
+ $this->usageIndex = &$usageIndex;
+ }
+
+ /**
+ * @return SessionBagInterface
+ */
+ public function getBag()
+ {
+ ++$this->usageIndex;
+
+ return $this->bag;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ if (!isset($this->data[$this->bag->getStorageKey()])) {
+ return true;
+ }
+ ++$this->usageIndex;
+
+ return empty($this->data[$this->bag->getStorageKey()]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->bag->getName();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$array)
+ {
+ ++$this->usageIndex;
+ $this->data[$this->bag->getStorageKey()] = &$array;
+
+ $this->bag->initialize($array);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->bag->getStorageKey();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ return $this->bag->clear();
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/SessionInterface.php b/vendor/symfony/http-foundation/Session/SessionInterface.php
new file mode 100755
index 0000000..95fca85
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/SessionInterface.php
@@ -0,0 +1,180 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session;
+
+use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag;
+
+/**
+ * Interface for the session.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+interface SessionInterface
+{
+ /**
+ * Starts the session storage.
+ *
+ * @return bool True if session started
+ *
+ * @throws \RuntimeException if session fails to start
+ */
+ public function start();
+
+ /**
+ * Returns the session ID.
+ *
+ * @return string The session ID
+ */
+ public function getId();
+
+ /**
+ * Sets the session ID.
+ *
+ * @param string $id
+ */
+ public function setId($id);
+
+ /**
+ * Returns the session name.
+ *
+ * @return mixed The session name
+ */
+ public function getName();
+
+ /**
+ * Sets the session name.
+ *
+ * @param string $name
+ */
+ public function setName($name);
+
+ /**
+ * Invalidates the current session.
+ *
+ * Clears all session attributes and flashes and regenerates the
+ * session and deletes the old session from persistence.
+ *
+ * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
+ * will leave the system settings unchanged, 0 sets the cookie
+ * to expire with browser session. Time is in seconds, and is
+ * not a Unix timestamp.
+ *
+ * @return bool True if session invalidated, false if error
+ */
+ public function invalidate($lifetime = null);
+
+ /**
+ * Migrates the current session to a new session id while maintaining all
+ * session attributes.
+ *
+ * @param bool $destroy Whether to delete the old session or leave it to garbage collection
+ * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
+ * will leave the system settings unchanged, 0 sets the cookie
+ * to expire with browser session. Time is in seconds, and is
+ * not a Unix timestamp.
+ *
+ * @return bool True if session migrated, false if error
+ */
+ public function migrate($destroy = false, $lifetime = null);
+
+ /**
+ * Force the session to be saved and closed.
+ *
+ * This method is generally not required for real sessions as
+ * the session will be automatically saved at the end of
+ * code execution.
+ */
+ public function save();
+
+ /**
+ * Checks if an attribute is defined.
+ *
+ * @param string $name The attribute name
+ *
+ * @return bool true if the attribute is defined, false otherwise
+ */
+ public function has($name);
+
+ /**
+ * Returns an attribute.
+ *
+ * @param string $name The attribute name
+ * @param mixed $default The default value if not found
+ *
+ * @return mixed
+ */
+ public function get($name, $default = null);
+
+ /**
+ * Sets an attribute.
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function set($name, $value);
+
+ /**
+ * Returns attributes.
+ *
+ * @return array Attributes
+ */
+ public function all();
+
+ /**
+ * Sets attributes.
+ *
+ * @param array $attributes Attributes
+ */
+ public function replace(array $attributes);
+
+ /**
+ * Removes an attribute.
+ *
+ * @param string $name
+ *
+ * @return mixed The removed value or null when it does not exist
+ */
+ public function remove($name);
+
+ /**
+ * Clears all attributes.
+ */
+ public function clear();
+
+ /**
+ * Checks if the session was started.
+ *
+ * @return bool
+ */
+ public function isStarted();
+
+ /**
+ * Registers a SessionBagInterface with the session.
+ */
+ public function registerBag(SessionBagInterface $bag);
+
+ /**
+ * Gets a bag instance by name.
+ *
+ * @param string $name
+ *
+ * @return SessionBagInterface
+ */
+ public function getBag($name);
+
+ /**
+ * Gets session meta.
+ *
+ * @return MetadataBag
+ */
+ public function getMetadataBag();
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php
new file mode 100755
index 0000000..eb09c0b
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php
@@ -0,0 +1,168 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * This abstract session handler provides a generic implementation
+ * of the PHP 7.0 SessionUpdateTimestampHandlerInterface,
+ * enabling strict and lazy session handling.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
+{
+ private $sessionName;
+ private $prefetchId;
+ private $prefetchData;
+ private $newSessionId;
+ private $igbinaryEmptyData;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ $this->sessionName = $sessionName;
+ if (!headers_sent() && !ini_get('session.cache_limiter') && '0' !== ini_get('session.cache_limiter')) {
+ header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) ini_get('session.cache_expire')));
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $sessionId
+ *
+ * @return string
+ */
+ abstract protected function doRead($sessionId);
+
+ /**
+ * @param string $sessionId
+ * @param string $data
+ *
+ * @return bool
+ */
+ abstract protected function doWrite($sessionId, $data);
+
+ /**
+ * @param string $sessionId
+ *
+ * @return bool
+ */
+ abstract protected function doDestroy($sessionId);
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateId($sessionId)
+ {
+ $this->prefetchData = $this->read($sessionId);
+ $this->prefetchId = $sessionId;
+
+ return '' !== $this->prefetchData;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ if (null !== $this->prefetchId) {
+ $prefetchId = $this->prefetchId;
+ $prefetchData = $this->prefetchData;
+ $this->prefetchId = $this->prefetchData = null;
+
+ if ($prefetchId === $sessionId || '' === $prefetchData) {
+ $this->newSessionId = '' === $prefetchData ? $sessionId : null;
+
+ return $prefetchData;
+ }
+ }
+
+ $data = $this->doRead($sessionId);
+ $this->newSessionId = '' === $data ? $sessionId : null;
+ if (\PHP_VERSION_ID < 70000) {
+ $this->prefetchData = $data;
+ }
+
+ return $data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($sessionId, $data)
+ {
+ if (\PHP_VERSION_ID < 70000 && $this->prefetchData) {
+ $readData = $this->prefetchData;
+ $this->prefetchData = null;
+
+ if ($readData === $data) {
+ return $this->updateTimestamp($sessionId, $data);
+ }
+ }
+ if (null === $this->igbinaryEmptyData) {
+ // see https://github.com/igbinary/igbinary/issues/146
+ $this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize([]) : '';
+ }
+ if ('' === $data || $this->igbinaryEmptyData === $data) {
+ return $this->destroy($sessionId);
+ }
+ $this->newSessionId = null;
+
+ return $this->doWrite($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ if (\PHP_VERSION_ID < 70000) {
+ $this->prefetchData = null;
+ }
+ if (!headers_sent() && filter_var(ini_get('session.use_cookies'), FILTER_VALIDATE_BOOLEAN)) {
+ if (!$this->sessionName) {
+ throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', \get_class($this)));
+ }
+ $sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
+ $sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
+ $sessionCookieFound = false;
+ $otherCookies = [];
+ foreach (headers_list() as $h) {
+ if (0 !== stripos($h, 'Set-Cookie:')) {
+ continue;
+ }
+ if (11 === strpos($h, $sessionCookie, 11)) {
+ $sessionCookieFound = true;
+
+ if (11 !== strpos($h, $sessionCookieWithId, 11)) {
+ $otherCookies[] = $h;
+ }
+ } else {
+ $otherCookies[] = $h;
+ }
+ }
+ if ($sessionCookieFound) {
+ header_remove('Set-Cookie');
+ foreach ($otherCookies as $h) {
+ header($h, false);
+ }
+ } else {
+ setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), filter_var(ini_get('session.cookie_secure'), FILTER_VALIDATE_BOOLEAN), filter_var(ini_get('session.cookie_httponly'), FILTER_VALIDATE_BOOLEAN));
+ }
+ }
+
+ return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php
new file mode 100755
index 0000000..3abc33c
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php
@@ -0,0 +1,118 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+@trigger_error(sprintf('The class %s is deprecated since Symfony 3.4 and will be removed in 4.0. Use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler instead.', MemcacheSessionHandler::class), E_USER_DEPRECATED);
+
+/**
+ * @author Drak <drak@zikula.org>
+ *
+ * @deprecated since version 3.4, to be removed in 4.0. Use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler instead.
+ */
+class MemcacheSessionHandler implements \SessionHandlerInterface
+{
+ private $memcache;
+
+ /**
+ * @var int Time to live in seconds
+ */
+ private $ttl;
+
+ /**
+ * @var string Key prefix for shared environments
+ */
+ private $prefix;
+
+ /**
+ * Constructor.
+ *
+ * List of available options:
+ * * prefix: The prefix to use for the memcache keys in order to avoid collision
+ * * expiretime: The time to live in seconds
+ *
+ * @param \Memcache $memcache A \Memcache instance
+ * @param array $options An associative array of Memcache options
+ *
+ * @throws \InvalidArgumentException When unsupported options are passed
+ */
+ public function __construct(\Memcache $memcache, array $options = [])
+ {
+ if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime'])) {
+ throw new \InvalidArgumentException(sprintf('The following options are not supported "%s"', implode(', ', $diff)));
+ }
+
+ $this->memcache = $memcache;
+ $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400;
+ $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ return $this->memcache->get($this->prefix.$sessionId) ?: '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($sessionId, $data)
+ {
+ return $this->memcache->set($this->prefix.$sessionId, $data, 0, time() + $this->ttl);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ $this->memcache->delete($this->prefix.$sessionId);
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ // not required here because memcache will auto expire the records anyhow.
+ return true;
+ }
+
+ /**
+ * Return a Memcache instance.
+ *
+ * @return \Memcache
+ */
+ protected function getMemcache()
+ {
+ return $this->memcache;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php
new file mode 100755
index 0000000..1db590b
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php
@@ -0,0 +1,122 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Memcached based session storage handler based on the Memcached class
+ * provided by the PHP memcached extension.
+ *
+ * @see http://php.net/memcached
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class MemcachedSessionHandler extends AbstractSessionHandler
+{
+ private $memcached;
+
+ /**
+ * @var int Time to live in seconds
+ */
+ private $ttl;
+
+ /**
+ * @var string Key prefix for shared environments
+ */
+ private $prefix;
+
+ /**
+ * Constructor.
+ *
+ * List of available options:
+ * * prefix: The prefix to use for the memcached keys in order to avoid collision
+ * * expiretime: The time to live in seconds.
+ *
+ * @param \Memcached $memcached A \Memcached instance
+ * @param array $options An associative array of Memcached options
+ *
+ * @throws \InvalidArgumentException When unsupported options are passed
+ */
+ public function __construct(\Memcached $memcached, array $options = [])
+ {
+ $this->memcached = $memcached;
+
+ if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime'])) {
+ throw new \InvalidArgumentException(sprintf('The following options are not supported "%s"', implode(', ', $diff)));
+ }
+
+ $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400;
+ $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return $this->memcached->quit();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doRead($sessionId)
+ {
+ return $this->memcached->get($this->prefix.$sessionId) ?: '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ $this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl);
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doWrite($sessionId, $data)
+ {
+ return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDestroy($sessionId)
+ {
+ $result = $this->memcached->delete($this->prefix.$sessionId);
+
+ return $result || \Memcached::RES_NOTFOUND == $this->memcached->getResultCode();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ // not required here because memcached will auto expire the records anyhow.
+ return true;
+ }
+
+ /**
+ * Return a Memcached instance.
+ *
+ * @return \Memcached
+ */
+ protected function getMemcached()
+ {
+ return $this->memcached;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php
new file mode 100755
index 0000000..3b5ccaa
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php
@@ -0,0 +1,255 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Session handler using the mongodb/mongodb package and MongoDB driver extension.
+ *
+ * @author Markus Bachmann <markus.bachmann@bachi.biz>
+ *
+ * @see https://packagist.org/packages/mongodb/mongodb
+ * @see http://php.net/manual/en/set.mongodb.php
+ */
+class MongoDbSessionHandler extends AbstractSessionHandler
+{
+ private $mongo;
+
+ /**
+ * @var \MongoCollection
+ */
+ private $collection;
+
+ /**
+ * @var array
+ */
+ private $options;
+
+ /**
+ * Constructor.
+ *
+ * List of available options:
+ * * database: The name of the database [required]
+ * * collection: The name of the collection [required]
+ * * id_field: The field name for storing the session id [default: _id]
+ * * data_field: The field name for storing the session data [default: data]
+ * * time_field: The field name for storing the timestamp [default: time]
+ * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at].
+ *
+ * It is strongly recommended to put an index on the `expiry_field` for
+ * garbage-collection. Alternatively it's possible to automatically expire
+ * the sessions in the database as described below:
+ *
+ * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions
+ * automatically. Such an index can for example look like this:
+ *
+ * db.<session-collection>.ensureIndex(
+ * { "<expiry-field>": 1 },
+ * { "expireAfterSeconds": 0 }
+ * )
+ *
+ * More details on: http://docs.mongodb.org/manual/tutorial/expire-data/
+ *
+ * If you use such an index, you can drop `gc_probability` to 0 since
+ * no garbage-collection is required.
+ *
+ * @param \MongoDB\Client $mongo A MongoDB\Client instance
+ * @param array $options An associative array of field options
+ *
+ * @throws \InvalidArgumentException When MongoClient or Mongo instance not provided
+ * @throws \InvalidArgumentException When "database" or "collection" not provided
+ */
+ public function __construct($mongo, array $options)
+ {
+ if ($mongo instanceof \MongoClient || $mongo instanceof \Mongo) {
+ @trigger_error(sprintf('Using %s with the legacy mongo extension is deprecated as of 3.4 and will be removed in 4.0. Use it with the mongodb/mongodb package and ext-mongodb instead.', __CLASS__), E_USER_DEPRECATED);
+ }
+
+ if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
+ throw new \InvalidArgumentException('MongoClient or Mongo instance required');
+ }
+
+ if (!isset($options['database']) || !isset($options['collection'])) {
+ throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler');
+ }
+
+ $this->mongo = $mongo;
+
+ $this->options = array_merge([
+ 'id_field' => '_id',
+ 'data_field' => 'data',
+ 'time_field' => 'time',
+ 'expiry_field' => 'expires_at',
+ ], $options);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDestroy($sessionId)
+ {
+ $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove';
+
+ $this->getCollection()->$methodName([
+ $this->options['id_field'] => $sessionId,
+ ]);
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteMany' : 'remove';
+
+ $this->getCollection()->$methodName([
+ $this->options['expiry_field'] => ['$lt' => $this->createDateTime()],
+ ]);
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doWrite($sessionId, $data)
+ {
+ $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
+
+ $fields = [
+ $this->options['time_field'] => $this->createDateTime(),
+ $this->options['expiry_field'] => $expiry,
+ ];
+
+ $options = ['upsert' => true];
+
+ if ($this->mongo instanceof \MongoDB\Client) {
+ $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
+ } else {
+ $fields[$this->options['data_field']] = new \MongoBinData($data, \MongoBinData::BYTE_ARRAY);
+ $options['multiple'] = false;
+ }
+
+ $methodName = $this->mongo instanceof \MongoDB\Client ? 'updateOne' : 'update';
+
+ $this->getCollection()->$methodName(
+ [$this->options['id_field'] => $sessionId],
+ ['$set' => $fields],
+ $options
+ );
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
+
+ if ($this->mongo instanceof \MongoDB\Client) {
+ $methodName = 'updateOne';
+ $options = [];
+ } else {
+ $methodName = 'update';
+ $options = ['multiple' => false];
+ }
+
+ $this->getCollection()->$methodName(
+ [$this->options['id_field'] => $sessionId],
+ ['$set' => [
+ $this->options['time_field'] => $this->createDateTime(),
+ $this->options['expiry_field'] => $expiry,
+ ]],
+ $options
+ );
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doRead($sessionId)
+ {
+ $dbData = $this->getCollection()->findOne([
+ $this->options['id_field'] => $sessionId,
+ $this->options['expiry_field'] => ['$gte' => $this->createDateTime()],
+ ]);
+
+ if (null === $dbData) {
+ return '';
+ }
+
+ if ($dbData[$this->options['data_field']] instanceof \MongoDB\BSON\Binary) {
+ return $dbData[$this->options['data_field']]->getData();
+ }
+
+ return $dbData[$this->options['data_field']]->bin;
+ }
+
+ /**
+ * Return a "MongoCollection" instance.
+ *
+ * @return \MongoCollection
+ */
+ private function getCollection()
+ {
+ if (null === $this->collection) {
+ $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']);
+ }
+
+ return $this->collection;
+ }
+
+ /**
+ * Return a Mongo instance.
+ *
+ * @return \Mongo|\MongoClient|\MongoDB\Client
+ */
+ protected function getMongo()
+ {
+ return $this->mongo;
+ }
+
+ /**
+ * Create a date object using the class appropriate for the current mongo connection.
+ *
+ * Return an instance of a MongoDate or \MongoDB\BSON\UTCDateTime
+ *
+ * @param int $seconds An integer representing UTC seconds since Jan 1 1970. Defaults to now.
+ *
+ * @return \MongoDate|\MongoDB\BSON\UTCDateTime
+ */
+ private function createDateTime($seconds = null)
+ {
+ if (null === $seconds) {
+ $seconds = time();
+ }
+
+ if ($this->mongo instanceof \MongoDB\Client) {
+ return new \MongoDB\BSON\UTCDateTime($seconds * 1000);
+ }
+
+ return new \MongoDate($seconds);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php
new file mode 100755
index 0000000..4e9704b
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php
@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Native session handler using PHP's built in file storage.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NativeFileSessionHandler extends NativeSessionHandler
+{
+ /**
+ * @param string $savePath Path of directory to save session files
+ * Default null will leave setting as defined by PHP.
+ * '/path', 'N;/path', or 'N;octal-mode;/path
+ *
+ * @see http://php.net/session.configuration.php#ini.session.save-path for further details.
+ *
+ * @throws \InvalidArgumentException On invalid $savePath
+ * @throws \RuntimeException When failing to create the save directory
+ */
+ public function __construct($savePath = null)
+ {
+ if (null === $savePath) {
+ $savePath = ini_get('session.save_path');
+ }
+
+ $baseDir = $savePath;
+
+ if ($count = substr_count($savePath, ';')) {
+ if ($count > 2) {
+ throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'', $savePath));
+ }
+
+ // characters after last ';' are the path
+ $baseDir = ltrim(strrchr($savePath, ';'), ';');
+ }
+
+ if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) {
+ throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $baseDir));
+ }
+
+ ini_set('session.save_path', $savePath);
+ ini_set('session.save_handler', 'files');
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php
new file mode 100755
index 0000000..9be4528
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * @deprecated since version 3.4, to be removed in 4.0. Use \SessionHandler instead.
+ * @see http://php.net/sessionhandler
+ */
+class NativeSessionHandler extends \SessionHandler
+{
+ public function __construct()
+ {
+ @trigger_error('The '.__NAMESPACE__.'\NativeSessionHandler class is deprecated since Symfony 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.', E_USER_DEPRECATED);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php
new file mode 100755
index 0000000..8d19315
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php
@@ -0,0 +1,76 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Can be used in unit testing or in a situations where persisted sessions are not desired.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NullSessionHandler extends AbstractSessionHandler
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateId($sessionId)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doRead($sessionId)
+ {
+ return '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doWrite($sessionId, $data)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDestroy($sessionId)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ return true;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
new file mode 100755
index 0000000..bc088e7
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
@@ -0,0 +1,918 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Session handler using a PDO connection to read and write data.
+ *
+ * It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements
+ * different locking strategies to handle concurrent access to the same session.
+ * Locking is necessary to prevent loss of data due to race conditions and to keep
+ * the session data consistent between read() and write(). With locking, requests
+ * for the same session will wait until the other one finished writing. For this
+ * reason it's best practice to close a session as early as possible to improve
+ * concurrency. PHPs internal files session handler also implements locking.
+ *
+ * Attention: Since SQLite does not support row level locks but locks the whole database,
+ * it means only one session can be accessed at a time. Even different sessions would wait
+ * for another to finish. So saving session in SQLite should only be considered for
+ * development or prototypes.
+ *
+ * Session data is a binary string that can contain non-printable characters like the null byte.
+ * For this reason it must be saved in a binary column in the database like BLOB in MySQL.
+ * Saving it in a character column could corrupt the data. You can use createTable()
+ * to initialize a correctly defined table.
+ *
+ * @see http://php.net/sessionhandlerinterface
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Michael Williams <michael.williams@funsational.com>
+ * @author Tobias Schultze <http://tobion.de>
+ */
+class PdoSessionHandler extends AbstractSessionHandler
+{
+ /**
+ * No locking is done. This means sessions are prone to loss of data due to
+ * race conditions of concurrent requests to the same session. The last session
+ * write will win in this case. It might be useful when you implement your own
+ * logic to deal with this like an optimistic approach.
+ */
+ const LOCK_NONE = 0;
+
+ /**
+ * Creates an application-level lock on a session. The disadvantage is that the
+ * lock is not enforced by the database and thus other, unaware parts of the
+ * application could still concurrently modify the session. The advantage is it
+ * does not require a transaction.
+ * This mode is not available for SQLite and not yet implemented for oci and sqlsrv.
+ */
+ const LOCK_ADVISORY = 1;
+
+ /**
+ * Issues a real row lock. Since it uses a transaction between opening and
+ * closing a session, you have to be careful when you use same database connection
+ * that you also use for your application logic. This mode is the default because
+ * it's the only reliable solution across DBMSs.
+ */
+ const LOCK_TRANSACTIONAL = 2;
+
+ /**
+ * @var \PDO|null PDO instance or null when not connected yet
+ */
+ private $pdo;
+
+ /**
+ * @var string|false|null DSN string or null for session.save_path or false when lazy connection disabled
+ */
+ private $dsn = false;
+
+ /**
+ * @var string Database driver
+ */
+ private $driver;
+
+ /**
+ * @var string Table name
+ */
+ private $table = 'sessions';
+
+ /**
+ * @var string Column for session id
+ */
+ private $idCol = 'sess_id';
+
+ /**
+ * @var string Column for session data
+ */
+ private $dataCol = 'sess_data';
+
+ /**
+ * @var string Column for lifetime
+ */
+ private $lifetimeCol = 'sess_lifetime';
+
+ /**
+ * @var string Column for timestamp
+ */
+ private $timeCol = 'sess_time';
+
+ /**
+ * @var string Username when lazy-connect
+ */
+ private $username = '';
+
+ /**
+ * @var string Password when lazy-connect
+ */
+ private $password = '';
+
+ /**
+ * @var array Connection options when lazy-connect
+ */
+ private $connectionOptions = [];
+
+ /**
+ * @var int The strategy for locking, see constants
+ */
+ private $lockMode = self::LOCK_TRANSACTIONAL;
+
+ /**
+ * It's an array to support multiple reads before closing which is manual, non-standard usage.
+ *
+ * @var \PDOStatement[] An array of statements to release advisory locks
+ */
+ private $unlockStatements = [];
+
+ /**
+ * @var bool True when the current session exists but expired according to session.gc_maxlifetime
+ */
+ private $sessionExpired = false;
+
+ /**
+ * @var bool Whether a transaction is active
+ */
+ private $inTransaction = false;
+
+ /**
+ * @var bool Whether gc() has been called
+ */
+ private $gcCalled = false;
+
+ /**
+ * You can either pass an existing database connection as PDO instance or
+ * pass a DSN string that will be used to lazy-connect to the database
+ * when the session is actually used. Furthermore it's possible to pass null
+ * which will then use the session.save_path ini setting as PDO DSN parameter.
+ *
+ * List of available options:
+ * * db_table: The name of the table [default: sessions]
+ * * db_id_col: The column where to store the session id [default: sess_id]
+ * * db_data_col: The column where to store the session data [default: sess_data]
+ * * db_lifetime_col: The column where to store the lifetime [default: sess_lifetime]
+ * * db_time_col: The column where to store the timestamp [default: sess_time]
+ * * db_username: The username when lazy-connect [default: '']
+ * * db_password: The password when lazy-connect [default: '']
+ * * db_connection_options: An array of driver-specific connection options [default: []]
+ * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL]
+ *
+ * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null
+ * @param array $options An associative array of options
+ *
+ * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
+ */
+ public function __construct($pdoOrDsn = null, array $options = [])
+ {
+ if ($pdoOrDsn instanceof \PDO) {
+ if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
+ throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
+ }
+
+ $this->pdo = $pdoOrDsn;
+ $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
+ } elseif (\is_string($pdoOrDsn) && false !== strpos($pdoOrDsn, '://')) {
+ $this->dsn = $this->buildDsnFromUrl($pdoOrDsn);
+ } else {
+ $this->dsn = $pdoOrDsn;
+ }
+
+ $this->table = isset($options['db_table']) ? $options['db_table'] : $this->table;
+ $this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol;
+ $this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol;
+ $this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol;
+ $this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol;
+ $this->username = isset($options['db_username']) ? $options['db_username'] : $this->username;
+ $this->password = isset($options['db_password']) ? $options['db_password'] : $this->password;
+ $this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions;
+ $this->lockMode = isset($options['lock_mode']) ? $options['lock_mode'] : $this->lockMode;
+ }
+
+ /**
+ * Creates the table to store sessions which can be called once for setup.
+ *
+ * Session ID is saved in a column of maximum length 128 because that is enough even
+ * for a 512 bit configured session.hash_function like Whirlpool. Session data is
+ * saved in a BLOB. One could also use a shorter inlined varbinary column
+ * if one was sure the data fits into it.
+ *
+ * @throws \PDOException When the table already exists
+ * @throws \DomainException When an unsupported PDO driver is used
+ */
+ public function createTable()
+ {
+ // connect if we are not yet
+ $this->getConnection();
+
+ switch ($this->driver) {
+ case 'mysql':
+ // We use varbinary for the ID column because it prevents unwanted conversions:
+ // - character set conversions between server and client
+ // - trailing space removal
+ // - case-insensitivity
+ // - language processing like é == e
+ $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol MEDIUMINT NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB";
+ break;
+ case 'sqlite':
+ $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
+ break;
+ case 'pgsql':
+ $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
+ break;
+ case 'oci':
+ $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
+ break;
+ case 'sqlsrv':
+ $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
+ break;
+ default:
+ throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver));
+ }
+
+ try {
+ $this->pdo->exec($sql);
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+ }
+
+ /**
+ * Returns true when the current session exists but expired according to session.gc_maxlifetime.
+ *
+ * Can be used to distinguish between a new session and one that expired due to inactivity.
+ *
+ * @return bool Whether current session expired
+ */
+ public function isSessionExpired()
+ {
+ return $this->sessionExpired;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ $this->sessionExpired = false;
+
+ if (null === $this->pdo) {
+ $this->connect($this->dsn ?: $savePath);
+ }
+
+ return parent::open($savePath, $sessionName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ try {
+ return parent::read($sessionId);
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process.
+ // This way, pruning expired sessions does not block them from being started while the current session is used.
+ $this->gcCalled = true;
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDestroy($sessionId)
+ {
+ // delete the record associated with this id
+ $sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
+
+ try {
+ $stmt = $this->pdo->prepare($sql);
+ $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+ $stmt->execute();
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doWrite($sessionId, $data)
+ {
+ $maxlifetime = (int) ini_get('session.gc_maxlifetime');
+
+ try {
+ // We use a single MERGE SQL query when supported by the database.
+ $mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime);
+ if (null !== $mergeStmt) {
+ $mergeStmt->execute();
+
+ return true;
+ }
+
+ $updateStmt = $this->getUpdateStatement($sessionId, $data, $maxlifetime);
+ $updateStmt->execute();
+
+ // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in
+ // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior).
+ // We can just catch such an error and re-execute the update. This is similar to a serializable
+ // transaction with retry logic on serialization failures but without the overhead and without possible
+ // false positives due to longer gap locking.
+ if (!$updateStmt->rowCount()) {
+ try {
+ $insertStmt = $this->getInsertStatement($sessionId, $data, $maxlifetime);
+ $insertStmt->execute();
+ } catch (\PDOException $e) {
+ // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys
+ if (0 === strpos($e->getCode(), '23')) {
+ $updateStmt->execute();
+ } else {
+ throw $e;
+ }
+ }
+ }
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ $maxlifetime = (int) ini_get('session.gc_maxlifetime');
+
+ try {
+ $updateStmt = $this->pdo->prepare(
+ "UPDATE $this->table SET $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"
+ );
+ $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+ $updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
+ $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
+ $updateStmt->execute();
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ $this->commit();
+
+ while ($unlockStmt = array_shift($this->unlockStatements)) {
+ $unlockStmt->execute();
+ }
+
+ if ($this->gcCalled) {
+ $this->gcCalled = false;
+
+ // delete the session records that have expired
+ if ('mysql' === $this->driver) {
+ $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol < :time";
+ } else {
+ $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time - $this->timeCol";
+ }
+
+ $stmt = $this->pdo->prepare($sql);
+ $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+ $stmt->execute();
+ }
+
+ if (false !== $this->dsn) {
+ $this->pdo = null; // only close lazy-connection
+ }
+
+ return true;
+ }
+
+ /**
+ * Lazy-connects to the database.
+ *
+ * @param string $dsn DSN string
+ */
+ private function connect($dsn)
+ {
+ $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions);
+ $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+ $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
+ }
+
+ /**
+ * Builds a PDO DSN from a URL-like connection string.
+ *
+ * @param string $dsnOrUrl
+ *
+ * @return string
+ *
+ * @todo implement missing support for oci DSN (which look totally different from other PDO ones)
+ */
+ private function buildDsnFromUrl($dsnOrUrl)
+ {
+ // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid
+ $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl);
+
+ $params = parse_url($url);
+
+ if (false === $params) {
+ return $dsnOrUrl; // If the URL is not valid, let's assume it might be a DSN already.
+ }
+
+ $params = array_map('rawurldecode', $params);
+
+ // Override the default username and password. Values passed through options will still win over these in the constructor.
+ if (isset($params['user'])) {
+ $this->username = $params['user'];
+ }
+
+ if (isset($params['pass'])) {
+ $this->password = $params['pass'];
+ }
+
+ if (!isset($params['scheme'])) {
+ throw new \InvalidArgumentException('URLs without scheme are not supported to configure the PdoSessionHandler');
+ }
+
+ $driverAliasMap = [
+ 'mssql' => 'sqlsrv',
+ 'mysql2' => 'mysql', // Amazon RDS, for some weird reason
+ 'postgres' => 'pgsql',
+ 'postgresql' => 'pgsql',
+ 'sqlite3' => 'sqlite',
+ ];
+
+ $driver = isset($driverAliasMap[$params['scheme']]) ? $driverAliasMap[$params['scheme']] : $params['scheme'];
+
+ // Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here.
+ if (0 === strpos($driver, 'pdo_') || 0 === strpos($driver, 'pdo-')) {
+ $driver = substr($driver, 4);
+ }
+
+ switch ($driver) {
+ case 'mysql':
+ case 'pgsql':
+ $dsn = $driver.':';
+
+ if (isset($params['host']) && '' !== $params['host']) {
+ $dsn .= 'host='.$params['host'].';';
+ }
+
+ if (isset($params['port']) && '' !== $params['port']) {
+ $dsn .= 'port='.$params['port'].';';
+ }
+
+ if (isset($params['path'])) {
+ $dbName = substr($params['path'], 1); // Remove the leading slash
+ $dsn .= 'dbname='.$dbName.';';
+ }
+
+ return $dsn;
+
+ case 'sqlite':
+ return 'sqlite:'.substr($params['path'], 1);
+
+ case 'sqlsrv':
+ $dsn = 'sqlsrv:server=';
+
+ if (isset($params['host'])) {
+ $dsn .= $params['host'];
+ }
+
+ if (isset($params['port']) && '' !== $params['port']) {
+ $dsn .= ','.$params['port'];
+ }
+
+ if (isset($params['path'])) {
+ $dbName = substr($params['path'], 1); // Remove the leading slash
+ $dsn .= ';Database='.$dbName;
+ }
+
+ return $dsn;
+
+ default:
+ throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme']));
+ }
+ }
+
+ /**
+ * Helper method to begin a transaction.
+ *
+ * Since SQLite does not support row level locks, we have to acquire a reserved lock
+ * on the database immediately. Because of https://bugs.php.net/42766 we have to create
+ * such a transaction manually which also means we cannot use PDO::commit or
+ * PDO::rollback or PDO::inTransaction for SQLite.
+ *
+ * Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions
+ * due to http://www.mysqlperformanceblog.com/2013/12/12/one-more-innodb-gap-lock-to-avoid/ .
+ * So we change it to READ COMMITTED.
+ */
+ private function beginTransaction()
+ {
+ if (!$this->inTransaction) {
+ if ('sqlite' === $this->driver) {
+ $this->pdo->exec('BEGIN IMMEDIATE TRANSACTION');
+ } else {
+ if ('mysql' === $this->driver) {
+ $this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
+ }
+ $this->pdo->beginTransaction();
+ }
+ $this->inTransaction = true;
+ }
+ }
+
+ /**
+ * Helper method to commit a transaction.
+ */
+ private function commit()
+ {
+ if ($this->inTransaction) {
+ try {
+ // commit read-write transaction which also releases the lock
+ if ('sqlite' === $this->driver) {
+ $this->pdo->exec('COMMIT');
+ } else {
+ $this->pdo->commit();
+ }
+ $this->inTransaction = false;
+ } catch (\PDOException $e) {
+ $this->rollback();
+
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * Helper method to rollback a transaction.
+ */
+ private function rollback()
+ {
+ // We only need to rollback if we are in a transaction. Otherwise the resulting
+ // error would hide the real problem why rollback was called. We might not be
+ // in a transaction when not using the transactional locking behavior or when
+ // two callbacks (e.g. destroy and write) are invoked that both fail.
+ if ($this->inTransaction) {
+ if ('sqlite' === $this->driver) {
+ $this->pdo->exec('ROLLBACK');
+ } else {
+ $this->pdo->rollBack();
+ }
+ $this->inTransaction = false;
+ }
+ }
+
+ /**
+ * Reads the session data in respect to the different locking strategies.
+ *
+ * We need to make sure we do not return session data that is already considered garbage according
+ * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes.
+ *
+ * @param string $sessionId Session ID
+ *
+ * @return string The session data
+ */
+ protected function doRead($sessionId)
+ {
+ if (self::LOCK_ADVISORY === $this->lockMode) {
+ $this->unlockStatements[] = $this->doAdvisoryLock($sessionId);
+ }
+
+ $selectSql = $this->getSelectSql();
+ $selectStmt = $this->pdo->prepare($selectSql);
+ $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+ $insertStmt = null;
+
+ do {
+ $selectStmt->execute();
+ $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);
+
+ if ($sessionRows) {
+ if ($sessionRows[0][1] + $sessionRows[0][2] < time()) {
+ $this->sessionExpired = true;
+
+ return '';
+ }
+
+ return \is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
+ }
+
+ if (null !== $insertStmt) {
+ $this->rollback();
+ throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.');
+ }
+
+ if (!filter_var(ini_get('session.use_strict_mode'), FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
+ // In strict mode, session fixation is not possible: new sessions always start with a unique
+ // random id, so that concurrency is not possible and this code path can be skipped.
+ // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
+ // until other connections to the session are committed.
+ try {
+ $insertStmt = $this->getInsertStatement($sessionId, '', 0);
+ $insertStmt->execute();
+ } catch (\PDOException $e) {
+ // Catch duplicate key error because other connection created the session already.
+ // It would only not be the case when the other connection destroyed the session.
+ if (0 === strpos($e->getCode(), '23')) {
+ // Retrieve finished session data written by concurrent connection by restarting the loop.
+ // We have to start a new transaction as a failed query will mark the current transaction as
+ // aborted in PostgreSQL and disallow further queries within it.
+ $this->rollback();
+ $this->beginTransaction();
+ continue;
+ }
+
+ throw $e;
+ }
+ }
+
+ return '';
+ } while (true);
+ }
+
+ /**
+ * Executes an application-level lock on the database.
+ *
+ * @param string $sessionId Session ID
+ *
+ * @return \PDOStatement The statement that needs to be executed later to release the lock
+ *
+ * @throws \DomainException When an unsupported PDO driver is used
+ *
+ * @todo implement missing advisory locks
+ * - for oci using DBMS_LOCK.REQUEST
+ * - for sqlsrv using sp_getapplock with LockOwner = Session
+ */
+ private function doAdvisoryLock($sessionId)
+ {
+ switch ($this->driver) {
+ case 'mysql':
+ // MySQL 5.7.5 and later enforces a maximum length on lock names of 64 characters. Previously, no limit was enforced.
+ $lockId = \substr($sessionId, 0, 64);
+ // should we handle the return value? 0 on timeout, null on error
+ // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout
+ $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)');
+ $stmt->bindValue(':key', $lockId, \PDO::PARAM_STR);
+ $stmt->execute();
+
+ $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)');
+ $releaseStmt->bindValue(':key', $lockId, \PDO::PARAM_STR);
+
+ return $releaseStmt;
+ case 'pgsql':
+ // Obtaining an exclusive session level advisory lock requires an integer key.
+ // When session.sid_bits_per_character > 4, the session id can contain non-hex-characters.
+ // So we cannot just use hexdec().
+ if (4 === \PHP_INT_SIZE) {
+ $sessionInt1 = $this->convertStringToInt($sessionId);
+ $sessionInt2 = $this->convertStringToInt(substr($sessionId, 4, 4));
+
+ $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)');
+ $stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT);
+ $stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT);
+ $stmt->execute();
+
+ $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)');
+ $releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT);
+ $releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT);
+ } else {
+ $sessionBigInt = $this->convertStringToInt($sessionId);
+
+ $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)');
+ $stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT);
+ $stmt->execute();
+
+ $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)');
+ $releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT);
+ }
+
+ return $releaseStmt;
+ case 'sqlite':
+ throw new \DomainException('SQLite does not support advisory locks.');
+ default:
+ throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver));
+ }
+ }
+
+ /**
+ * Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer.
+ *
+ * Keep in mind, PHP integers are signed.
+ *
+ * @param string $string
+ *
+ * @return int
+ */
+ private function convertStringToInt($string)
+ {
+ if (4 === \PHP_INT_SIZE) {
+ return (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]);
+ }
+
+ $int1 = (\ord($string[7]) << 24) + (\ord($string[6]) << 16) + (\ord($string[5]) << 8) + \ord($string[4]);
+ $int2 = (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]);
+
+ return $int2 + ($int1 << 32);
+ }
+
+ /**
+ * Return a locking or nonlocking SQL query to read session information.
+ *
+ * @return string The SQL string
+ *
+ * @throws \DomainException When an unsupported PDO driver is used
+ */
+ private function getSelectSql()
+ {
+ if (self::LOCK_TRANSACTIONAL === $this->lockMode) {
+ $this->beginTransaction();
+
+ switch ($this->driver) {
+ case 'mysql':
+ case 'oci':
+ case 'pgsql':
+ return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE";
+ case 'sqlsrv':
+ return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id";
+ case 'sqlite':
+ // we already locked when starting transaction
+ break;
+ default:
+ throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver));
+ }
+ }
+
+ return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id";
+ }
+
+ /**
+ * Returns an insert statement supported by the database for writing session data.
+ *
+ * @param string $sessionId Session ID
+ * @param string $sessionData Encoded session data
+ * @param int $maxlifetime session.gc_maxlifetime
+ *
+ * @return \PDOStatement The insert statement
+ */
+ private function getInsertStatement($sessionId, $sessionData, $maxlifetime)
+ {
+ switch ($this->driver) {
+ case 'oci':
+ $data = fopen('php://memory', 'r+');
+ fwrite($data, $sessionData);
+ rewind($data);
+ $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :lifetime, :time) RETURNING $this->dataCol into :data";
+ break;
+ default:
+ $data = $sessionData;
+ $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
+ break;
+ }
+
+ $stmt = $this->pdo->prepare($sql);
+ $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+ $stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
+ $stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
+ $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+
+ return $stmt;
+ }
+
+ /**
+ * Returns an update statement supported by the database for writing session data.
+ *
+ * @param string $sessionId Session ID
+ * @param string $sessionData Encoded session data
+ * @param int $maxlifetime session.gc_maxlifetime
+ *
+ * @return \PDOStatement The update statement
+ */
+ private function getUpdateStatement($sessionId, $sessionData, $maxlifetime)
+ {
+ switch ($this->driver) {
+ case 'oci':
+ $data = fopen('php://memory', 'r+');
+ fwrite($data, $sessionData);
+ rewind($data);
+ $sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data";
+ break;
+ default:
+ $data = $sessionData;
+ $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id";
+ break;
+ }
+
+ $stmt = $this->pdo->prepare($sql);
+ $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+ $stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
+ $stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
+ $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+
+ return $stmt;
+ }
+
+ /**
+ * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data.
+ *
+ * @param string $sessionId Session ID
+ * @param string $data Encoded session data
+ * @param int $maxlifetime session.gc_maxlifetime
+ *
+ * @return \PDOStatement|null The merge statement or null when not supported
+ */
+ private function getMergeStatement($sessionId, $data, $maxlifetime)
+ {
+ switch (true) {
+ case 'mysql' === $this->driver:
+ $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
+ "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
+ break;
+ case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='):
+ // MERGE is only available since SQL Server 2008 and must be terminated by semicolon
+ // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
+ $mergeSql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
+ "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
+ "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
+ break;
+ case 'sqlite' === $this->driver:
+ $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
+ break;
+ case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='):
+ $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ".
+ "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
+ break;
+ default:
+ // MERGE is not supported with LOBs: http://www.oracle.com/technetwork/articles/fuecks-lobs-095315.html
+ return null;
+ }
+
+ $mergeStmt = $this->pdo->prepare($mergeSql);
+
+ if ('sqlsrv' === $this->driver) {
+ $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR);
+ $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR);
+ $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB);
+ $mergeStmt->bindParam(4, $maxlifetime, \PDO::PARAM_INT);
+ $mergeStmt->bindValue(5, time(), \PDO::PARAM_INT);
+ $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB);
+ $mergeStmt->bindParam(7, $maxlifetime, \PDO::PARAM_INT);
+ $mergeStmt->bindValue(8, time(), \PDO::PARAM_INT);
+ } else {
+ $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
+ $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
+ $mergeStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
+ $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT);
+ }
+
+ return $mergeStmt;
+ }
+
+ /**
+ * Return a PDO instance.
+ *
+ * @return \PDO
+ */
+ protected function getConnection()
+ {
+ if (null === $this->pdo) {
+ $this->connect($this->dsn ?: ini_get('session.save_path'));
+ }
+
+ return $this->pdo;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php
new file mode 100755
index 0000000..83a1f2c
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php
@@ -0,0 +1,103 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class StrictSessionHandler extends AbstractSessionHandler
+{
+ private $handler;
+ private $doDestroy;
+
+ public function __construct(\SessionHandlerInterface $handler)
+ {
+ if ($handler instanceof \SessionUpdateTimestampHandlerInterface) {
+ throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', \get_class($handler), self::class));
+ }
+
+ $this->handler = $handler;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ parent::open($savePath, $sessionName);
+
+ return $this->handler->open($savePath, $sessionName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doRead($sessionId)
+ {
+ return $this->handler->read($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ return $this->write($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doWrite($sessionId, $data)
+ {
+ return $this->handler->write($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ $this->doDestroy = true;
+ $destroyed = parent::destroy($sessionId);
+
+ return $this->doDestroy ? $this->doDestroy($sessionId) : $destroyed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDestroy($sessionId)
+ {
+ $this->doDestroy = false;
+
+ return $this->handler->destroy($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return $this->handler->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ return $this->handler->gc($maxlifetime);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php
new file mode 100755
index 0000000..127e47f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php
@@ -0,0 +1,92 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+
+/**
+ * Wraps another SessionHandlerInterface to only write the session when it has been modified.
+ *
+ * @author Adrien Brault <adrien.brault@gmail.com>
+ *
+ * @deprecated since version 3.4, to be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.
+ */
+class WriteCheckSessionHandler implements \SessionHandlerInterface
+{
+ private $wrappedSessionHandler;
+
+ /**
+ * @var array sessionId => session
+ */
+ private $readSessions;
+
+ public function __construct(\SessionHandlerInterface $wrappedSessionHandler)
+ {
+ @trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.', self::class), E_USER_DEPRECATED);
+
+ $this->wrappedSessionHandler = $wrappedSessionHandler;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return $this->wrappedSessionHandler->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ return $this->wrappedSessionHandler->destroy($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ return $this->wrappedSessionHandler->gc($maxlifetime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ return $this->wrappedSessionHandler->open($savePath, $sessionName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ $session = $this->wrappedSessionHandler->read($sessionId);
+
+ $this->readSessions[$sessionId] = $session;
+
+ return $session;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($sessionId, $data)
+ {
+ if (isset($this->readSessions[$sessionId]) && $data === $this->readSessions[$sessionId]) {
+ return true;
+ }
+
+ return $this->wrappedSessionHandler->write($sessionId, $data);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php
new file mode 100755
index 0000000..a62f108
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php
@@ -0,0 +1,168 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * Metadata container.
+ *
+ * Adds metadata to the session.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class MetadataBag implements SessionBagInterface
+{
+ const CREATED = 'c';
+ const UPDATED = 'u';
+ const LIFETIME = 'l';
+
+ /**
+ * @var string
+ */
+ private $name = '__metadata';
+
+ /**
+ * @var string
+ */
+ private $storageKey;
+
+ /**
+ * @var array
+ */
+ protected $meta = [self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0];
+
+ /**
+ * Unix timestamp.
+ *
+ * @var int
+ */
+ private $lastUsed;
+
+ /**
+ * @var int
+ */
+ private $updateThreshold;
+
+ /**
+ * @param string $storageKey The key used to store bag in the session
+ * @param int $updateThreshold The time to wait between two UPDATED updates
+ */
+ public function __construct($storageKey = '_sf2_meta', $updateThreshold = 0)
+ {
+ $this->storageKey = $storageKey;
+ $this->updateThreshold = $updateThreshold;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$array)
+ {
+ $this->meta = &$array;
+
+ if (isset($array[self::CREATED])) {
+ $this->lastUsed = $this->meta[self::UPDATED];
+
+ $timeStamp = time();
+ if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) {
+ $this->meta[self::UPDATED] = $timeStamp;
+ }
+ } else {
+ $this->stampCreated();
+ }
+ }
+
+ /**
+ * Gets the lifetime that the session cookie was set with.
+ *
+ * @return int
+ */
+ public function getLifetime()
+ {
+ return $this->meta[self::LIFETIME];
+ }
+
+ /**
+ * Stamps a new session's metadata.
+ *
+ * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
+ * will leave the system settings unchanged, 0 sets the cookie
+ * to expire with browser session. Time is in seconds, and is
+ * not a Unix timestamp.
+ */
+ public function stampNew($lifetime = null)
+ {
+ $this->stampCreated($lifetime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->storageKey;
+ }
+
+ /**
+ * Gets the created timestamp metadata.
+ *
+ * @return int Unix timestamp
+ */
+ public function getCreated()
+ {
+ return $this->meta[self::CREATED];
+ }
+
+ /**
+ * Gets the last used metadata.
+ *
+ * @return int Unix timestamp
+ */
+ public function getLastUsed()
+ {
+ return $this->lastUsed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ // nothing to do
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Sets name.
+ *
+ * @param string $name
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ private function stampCreated($lifetime = null)
+ {
+ $timeStamp = time();
+ $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp;
+ $this->meta[self::LIFETIME] = (null === $lifetime) ? ini_get('session.cookie_lifetime') : $lifetime;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php
new file mode 100755
index 0000000..c1e7523
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php
@@ -0,0 +1,256 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * MockArraySessionStorage mocks the session for unit tests.
+ *
+ * No PHP session is actually started since a session can be initialized
+ * and shutdown only once per PHP execution cycle.
+ *
+ * When doing functional testing, you should use MockFileSessionStorage instead.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
+ * @author Drak <drak@zikula.org>
+ */
+class MockArraySessionStorage implements SessionStorageInterface
+{
+ /**
+ * @var string
+ */
+ protected $id = '';
+
+ /**
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * @var bool
+ */
+ protected $started = false;
+
+ /**
+ * @var bool
+ */
+ protected $closed = false;
+
+ /**
+ * @var array
+ */
+ protected $data = [];
+
+ /**
+ * @var MetadataBag
+ */
+ protected $metadataBag;
+
+ /**
+ * @var array|SessionBagInterface[]
+ */
+ protected $bags = [];
+
+ /**
+ * @param string $name Session name
+ * @param MetadataBag $metaBag MetadataBag instance
+ */
+ public function __construct($name = 'MOCKSESSID', MetadataBag $metaBag = null)
+ {
+ $this->name = $name;
+ $this->setMetadataBag($metaBag);
+ }
+
+ public function setSessionData(array $array)
+ {
+ $this->data = $array;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start()
+ {
+ if ($this->started) {
+ return true;
+ }
+
+ if (empty($this->id)) {
+ $this->id = $this->generateId();
+ }
+
+ $this->loadSession();
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function regenerate($destroy = false, $lifetime = null)
+ {
+ if (!$this->started) {
+ $this->start();
+ }
+
+ $this->metadataBag->stampNew($lifetime);
+ $this->id = $this->generateId();
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setId($id)
+ {
+ if ($this->started) {
+ throw new \LogicException('Cannot set session ID after the session has started.');
+ }
+
+ $this->id = $id;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save()
+ {
+ if (!$this->started || $this->closed) {
+ throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
+ }
+ // nothing to do since we don't persist the session data
+ $this->closed = false;
+ $this->started = false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ // clear out the bags
+ foreach ($this->bags as $bag) {
+ $bag->clear();
+ }
+
+ // clear out the session
+ $this->data = [];
+
+ // reconnect the bags to the session
+ $this->loadSession();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function registerBag(SessionBagInterface $bag)
+ {
+ $this->bags[$bag->getName()] = $bag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBag($name)
+ {
+ if (!isset($this->bags[$name])) {
+ throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
+ }
+
+ if (!$this->started) {
+ $this->start();
+ }
+
+ return $this->bags[$name];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isStarted()
+ {
+ return $this->started;
+ }
+
+ public function setMetadataBag(MetadataBag $bag = null)
+ {
+ if (null === $bag) {
+ $bag = new MetadataBag();
+ }
+
+ $this->metadataBag = $bag;
+ }
+
+ /**
+ * Gets the MetadataBag.
+ *
+ * @return MetadataBag
+ */
+ public function getMetadataBag()
+ {
+ return $this->metadataBag;
+ }
+
+ /**
+ * Generates a session ID.
+ *
+ * This doesn't need to be particularly cryptographically secure since this is just
+ * a mock.
+ *
+ * @return string
+ */
+ protected function generateId()
+ {
+ return hash('sha256', uniqid('ss_mock_', true));
+ }
+
+ protected function loadSession()
+ {
+ $bags = array_merge($this->bags, [$this->metadataBag]);
+
+ foreach ($bags as $bag) {
+ $key = $bag->getStorageKey();
+ $this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : [];
+ $bag->initialize($this->data[$key]);
+ }
+
+ $this->started = true;
+ $this->closed = false;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
new file mode 100755
index 0000000..9bbd1ba
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
@@ -0,0 +1,152 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+/**
+ * MockFileSessionStorage is used to mock sessions for
+ * functional testing when done in a single PHP process.
+ *
+ * No PHP session is actually started since a session can be initialized
+ * and shutdown only once per PHP execution cycle and this class does
+ * not pollute any session related globals, including session_*() functions
+ * or session.* PHP ini directives.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class MockFileSessionStorage extends MockArraySessionStorage
+{
+ private $savePath;
+
+ /**
+ * @param string $savePath Path of directory to save session files
+ * @param string $name Session name
+ * @param MetadataBag $metaBag MetadataBag instance
+ */
+ public function __construct($savePath = null, $name = 'MOCKSESSID', MetadataBag $metaBag = null)
+ {
+ if (null === $savePath) {
+ $savePath = sys_get_temp_dir();
+ }
+
+ if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) {
+ throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $savePath));
+ }
+
+ $this->savePath = $savePath;
+
+ parent::__construct($name, $metaBag);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start()
+ {
+ if ($this->started) {
+ return true;
+ }
+
+ if (!$this->id) {
+ $this->id = $this->generateId();
+ }
+
+ $this->read();
+
+ $this->started = true;
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function regenerate($destroy = false, $lifetime = null)
+ {
+ if (!$this->started) {
+ $this->start();
+ }
+
+ if ($destroy) {
+ $this->destroy();
+ }
+
+ return parent::regenerate($destroy, $lifetime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save()
+ {
+ if (!$this->started) {
+ throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
+ }
+
+ $data = $this->data;
+
+ foreach ($this->bags as $bag) {
+ if (empty($data[$key = $bag->getStorageKey()])) {
+ unset($data[$key]);
+ }
+ }
+ if ([$key = $this->metadataBag->getStorageKey()] === array_keys($data)) {
+ unset($data[$key]);
+ }
+
+ try {
+ if ($data) {
+ file_put_contents($this->getFilePath(), serialize($data));
+ } else {
+ $this->destroy();
+ }
+ } finally {
+ $this->data = $data;
+ }
+
+ // this is needed for Silex, where the session object is re-used across requests
+ // in functional tests. In Symfony, the container is rebooted, so we don't have
+ // this issue
+ $this->started = false;
+ }
+
+ /**
+ * Deletes a session from persistent storage.
+ * Deliberately leaves session data in memory intact.
+ */
+ private function destroy()
+ {
+ if (is_file($this->getFilePath())) {
+ unlink($this->getFilePath());
+ }
+ }
+
+ /**
+ * Calculate path to file.
+ *
+ * @return string File path
+ */
+ private function getFilePath()
+ {
+ return $this->savePath.'/'.$this->id.'.mocksess';
+ }
+
+ /**
+ * Reads session from storage and loads session.
+ */
+ private function read()
+ {
+ $filePath = $this->getFilePath();
+ $this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : [];
+
+ $this->loadSession();
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php
new file mode 100755
index 0000000..c4dbe75
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php
@@ -0,0 +1,436 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+
+/**
+ * This provides a base class for session attribute storage.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NativeSessionStorage implements SessionStorageInterface
+{
+ /**
+ * @var SessionBagInterface[]
+ */
+ protected $bags = [];
+
+ /**
+ * @var bool
+ */
+ protected $started = false;
+
+ /**
+ * @var bool
+ */
+ protected $closed = false;
+
+ /**
+ * @var AbstractProxy|\SessionHandlerInterface
+ */
+ protected $saveHandler;
+
+ /**
+ * @var MetadataBag
+ */
+ protected $metadataBag;
+
+ /**
+ * Depending on how you want the storage driver to behave you probably
+ * want to override this constructor entirely.
+ *
+ * List of options for $options array with their defaults.
+ *
+ * @see http://php.net/session.configuration for options
+ * but we omit 'session.' from the beginning of the keys for convenience.
+ *
+ * ("auto_start", is not supported as it tells PHP to start a session before
+ * PHP starts to execute user-land code. Setting during runtime has no effect).
+ *
+ * cache_limiter, "" (use "0" to prevent headers from being sent entirely).
+ * cache_expire, "0"
+ * cookie_domain, ""
+ * cookie_httponly, ""
+ * cookie_lifetime, "0"
+ * cookie_path, "/"
+ * cookie_secure, ""
+ * entropy_file, ""
+ * entropy_length, "0"
+ * gc_divisor, "100"
+ * gc_maxlifetime, "1440"
+ * gc_probability, "1"
+ * hash_bits_per_character, "4"
+ * hash_function, "0"
+ * lazy_write, "1"
+ * name, "PHPSESSID"
+ * referer_check, ""
+ * serialize_handler, "php"
+ * use_strict_mode, "0"
+ * use_cookies, "1"
+ * use_only_cookies, "1"
+ * use_trans_sid, "0"
+ * upload_progress.enabled, "1"
+ * upload_progress.cleanup, "1"
+ * upload_progress.prefix, "upload_progress_"
+ * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS"
+ * upload_progress.freq, "1%"
+ * upload_progress.min-freq, "1"
+ * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
+ * sid_length, "32"
+ * sid_bits_per_character, "5"
+ * trans_sid_hosts, $_SERVER['HTTP_HOST']
+ * trans_sid_tags, "a=href,area=href,frame=src,form="
+ *
+ * @param array $options Session configuration options
+ * @param \SessionHandlerInterface|null $handler
+ * @param MetadataBag $metaBag MetadataBag
+ */
+ public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null)
+ {
+ $options += [
+ 'cache_limiter' => '',
+ 'cache_expire' => 0,
+ 'use_cookies' => 1,
+ 'lazy_write' => 1,
+ ];
+
+ session_register_shutdown();
+
+ $this->setMetadataBag($metaBag);
+ $this->setOptions($options);
+ $this->setSaveHandler($handler);
+ }
+
+ /**
+ * Gets the save handler instance.
+ *
+ * @return AbstractProxy|\SessionHandlerInterface
+ */
+ public function getSaveHandler()
+ {
+ return $this->saveHandler;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start()
+ {
+ if ($this->started) {
+ return true;
+ }
+
+ if (\PHP_SESSION_ACTIVE === session_status()) {
+ throw new \RuntimeException('Failed to start the session: already started by PHP.');
+ }
+
+ if (filter_var(ini_get('session.use_cookies'), FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) {
+ throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
+ }
+
+ // ok to try and start the session
+ if (!session_start()) {
+ throw new \RuntimeException('Failed to start the session');
+ }
+
+ $this->loadSession();
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getId()
+ {
+ return $this->saveHandler->getId();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setId($id)
+ {
+ $this->saveHandler->setId($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->saveHandler->getName();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setName($name)
+ {
+ $this->saveHandler->setName($name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function regenerate($destroy = false, $lifetime = null)
+ {
+ // Cannot regenerate the session ID for non-active sessions.
+ if (\PHP_SESSION_ACTIVE !== session_status()) {
+ return false;
+ }
+
+ if (headers_sent()) {
+ return false;
+ }
+
+ if (null !== $lifetime) {
+ ini_set('session.cookie_lifetime', $lifetime);
+ }
+
+ if ($destroy) {
+ $this->metadataBag->stampNew();
+ }
+
+ $isRegenerated = session_regenerate_id($destroy);
+
+ // The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it.
+ // @see https://bugs.php.net/bug.php?id=70013
+ $this->loadSession();
+
+ return $isRegenerated;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save()
+ {
+ $session = $_SESSION;
+
+ foreach ($this->bags as $bag) {
+ if (empty($_SESSION[$key = $bag->getStorageKey()])) {
+ unset($_SESSION[$key]);
+ }
+ }
+ if ([$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) {
+ unset($_SESSION[$key]);
+ }
+
+ // Register error handler to add information about the current save handler
+ $previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) {
+ if (E_WARNING === $type && 0 === strpos($msg, 'session_write_close():')) {
+ $handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler;
+ $msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', \get_class($handler));
+ }
+
+ return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false;
+ });
+
+ try {
+ session_write_close();
+ } finally {
+ restore_error_handler();
+ $_SESSION = $session;
+ }
+
+ $this->closed = true;
+ $this->started = false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ // clear out the bags
+ foreach ($this->bags as $bag) {
+ $bag->clear();
+ }
+
+ // clear out the session
+ $_SESSION = [];
+
+ // reconnect the bags to the session
+ $this->loadSession();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function registerBag(SessionBagInterface $bag)
+ {
+ if ($this->started) {
+ throw new \LogicException('Cannot register a bag when the session is already started.');
+ }
+
+ $this->bags[$bag->getName()] = $bag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBag($name)
+ {
+ if (!isset($this->bags[$name])) {
+ throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
+ }
+
+ if (!$this->started && $this->saveHandler->isActive()) {
+ $this->loadSession();
+ } elseif (!$this->started) {
+ $this->start();
+ }
+
+ return $this->bags[$name];
+ }
+
+ public function setMetadataBag(MetadataBag $metaBag = null)
+ {
+ if (null === $metaBag) {
+ $metaBag = new MetadataBag();
+ }
+
+ $this->metadataBag = $metaBag;
+ }
+
+ /**
+ * Gets the MetadataBag.
+ *
+ * @return MetadataBag
+ */
+ public function getMetadataBag()
+ {
+ return $this->metadataBag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isStarted()
+ {
+ return $this->started;
+ }
+
+ /**
+ * Sets session.* ini variables.
+ *
+ * For convenience we omit 'session.' from the beginning of the keys.
+ * Explicitly ignores other ini keys.
+ *
+ * @param array $options Session ini directives [key => value]
+ *
+ * @see http://php.net/session.configuration
+ */
+ public function setOptions(array $options)
+ {
+ if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
+ return;
+ }
+
+ $validOptions = array_flip([
+ 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly',
+ 'cookie_lifetime', 'cookie_path', 'cookie_secure',
+ 'entropy_file', 'entropy_length', 'gc_divisor',
+ 'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
+ 'hash_function', 'lazy_write', 'name', 'referer_check',
+ 'serialize_handler', 'use_strict_mode', 'use_cookies',
+ 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
+ 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
+ 'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags',
+ 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags',
+ ]);
+
+ foreach ($options as $key => $value) {
+ if (isset($validOptions[$key])) {
+ ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value);
+ }
+ }
+ }
+
+ /**
+ * Registers session save handler as a PHP session handler.
+ *
+ * To use internal PHP session save handlers, override this method using ini_set with
+ * session.save_handler and session.save_path e.g.
+ *
+ * ini_set('session.save_handler', 'files');
+ * ini_set('session.save_path', '/tmp');
+ *
+ * or pass in a \SessionHandler instance which configures session.save_handler in the
+ * constructor, for a template see NativeFileSessionHandler or use handlers in
+ * composer package drak/native-session
+ *
+ * @see http://php.net/session-set-save-handler
+ * @see http://php.net/sessionhandlerinterface
+ * @see http://php.net/sessionhandler
+ * @see http://github.com/drak/NativeSession
+ *
+ * @param \SessionHandlerInterface|null $saveHandler
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setSaveHandler($saveHandler = null)
+ {
+ if (!$saveHandler instanceof AbstractProxy &&
+ !$saveHandler instanceof \SessionHandlerInterface &&
+ null !== $saveHandler) {
+ throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
+ }
+
+ // Wrap $saveHandler in proxy and prevent double wrapping of proxy
+ if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
+ $saveHandler = new SessionHandlerProxy($saveHandler);
+ } elseif (!$saveHandler instanceof AbstractProxy) {
+ $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
+ }
+ $this->saveHandler = $saveHandler;
+
+ if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
+ return;
+ }
+
+ if ($this->saveHandler instanceof SessionHandlerProxy) {
+ session_set_save_handler($this->saveHandler, false);
+ }
+ }
+
+ /**
+ * Load the session with attributes.
+ *
+ * After starting the session, PHP retrieves the session from whatever handlers
+ * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()).
+ * PHP takes the return value from the read() handler, unserializes it
+ * and populates $_SESSION with the result automatically.
+ */
+ protected function loadSession(array &$session = null)
+ {
+ if (null === $session) {
+ $session = &$_SESSION;
+ }
+
+ $bags = array_merge($this->bags, [$this->metadataBag]);
+
+ foreach ($bags as $bag) {
+ $key = $bag->getStorageKey();
+ $session[$key] = isset($session[$key]) ? $session[$key] : [];
+ $bag->initialize($session[$key]);
+ }
+
+ $this->started = true;
+ $this->closed = false;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php
new file mode 100755
index 0000000..662ed50
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php
@@ -0,0 +1,59 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+/**
+ * Allows session to be started by PHP and managed by Symfony.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class PhpBridgeSessionStorage extends NativeSessionStorage
+{
+ /**
+ * @param \SessionHandlerInterface|null $handler
+ * @param MetadataBag $metaBag MetadataBag
+ */
+ public function __construct($handler = null, MetadataBag $metaBag = null)
+ {
+ $this->setMetadataBag($metaBag);
+ $this->setSaveHandler($handler);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start()
+ {
+ if ($this->started) {
+ return true;
+ }
+
+ $this->loadSession();
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ // clear out the bags and nothing else that may be set
+ // since the purpose of this driver is to share a handler
+ foreach ($this->bags as $bag) {
+ $bag->clear();
+ }
+
+ // reconnect the bags to the session
+ $this->loadSession();
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php
new file mode 100755
index 0000000..09c9248
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php
@@ -0,0 +1,122 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
+
+/**
+ * @author Drak <drak@zikula.org>
+ */
+abstract class AbstractProxy
+{
+ /**
+ * Flag if handler wraps an internal PHP session handler (using \SessionHandler).
+ *
+ * @var bool
+ */
+ protected $wrapper = false;
+
+ /**
+ * @var string
+ */
+ protected $saveHandlerName;
+
+ /**
+ * Gets the session.save_handler name.
+ *
+ * @return string
+ */
+ public function getSaveHandlerName()
+ {
+ return $this->saveHandlerName;
+ }
+
+ /**
+ * Is this proxy handler and instance of \SessionHandlerInterface.
+ *
+ * @return bool
+ */
+ public function isSessionHandlerInterface()
+ {
+ return $this instanceof \SessionHandlerInterface;
+ }
+
+ /**
+ * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
+ *
+ * @return bool
+ */
+ public function isWrapper()
+ {
+ return $this->wrapper;
+ }
+
+ /**
+ * Has a session started?
+ *
+ * @return bool
+ */
+ public function isActive()
+ {
+ return \PHP_SESSION_ACTIVE === session_status();
+ }
+
+ /**
+ * Gets the session ID.
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ return session_id();
+ }
+
+ /**
+ * Sets the session ID.
+ *
+ * @param string $id
+ *
+ * @throws \LogicException
+ */
+ public function setId($id)
+ {
+ if ($this->isActive()) {
+ throw new \LogicException('Cannot change the ID of an active session');
+ }
+
+ session_id($id);
+ }
+
+ /**
+ * Gets the session name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return session_name();
+ }
+
+ /**
+ * Sets the session name.
+ *
+ * @param string $name
+ *
+ * @throws \LogicException
+ */
+ public function setName($name)
+ {
+ if ($this->isActive()) {
+ throw new \LogicException('Cannot change the name of an active session');
+ }
+
+ session_name($name);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php
new file mode 100755
index 0000000..082eed1
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php
@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
+
+@trigger_error('The '.__NAMESPACE__.'\NativeProxy class is deprecated since Symfony 3.4 and will be removed in 4.0. Use your session handler implementation directly.', E_USER_DEPRECATED);
+
+/**
+ * This proxy is built-in session handlers in PHP 5.3.x.
+ *
+ * @deprecated since version 3.4, to be removed in 4.0. Use your session handler implementation directly.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NativeProxy extends AbstractProxy
+{
+ public function __construct()
+ {
+ // this makes an educated guess as to what the handler is since it should already be set.
+ $this->saveHandlerName = ini_get('session.save_handler');
+ }
+
+ /**
+ * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
+ *
+ * @return bool False
+ */
+ public function isWrapper()
+ {
+ return false;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php
new file mode 100755
index 0000000..b11cc39
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php
@@ -0,0 +1,101 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
+
+/**
+ * @author Drak <drak@zikula.org>
+ */
+class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
+{
+ protected $handler;
+
+ public function __construct(\SessionHandlerInterface $handler)
+ {
+ $this->handler = $handler;
+ $this->wrapper = ($handler instanceof \SessionHandler);
+ $this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user';
+ }
+
+ /**
+ * @return \SessionHandlerInterface
+ */
+ public function getHandler()
+ {
+ return $this->handler;
+ }
+
+ // \SessionHandlerInterface
+
+ /**
+ * {@inheritdoc}
+ */
+ public function open($savePath, $sessionName)
+ {
+ return (bool) $this->handler->open($savePath, $sessionName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ return (bool) $this->handler->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ return (string) $this->handler->read($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($sessionId, $data)
+ {
+ return (bool) $this->handler->write($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ return (bool) $this->handler->destroy($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function gc($maxlifetime)
+ {
+ return (bool) $this->handler->gc($maxlifetime);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateId($sessionId)
+ {
+ return !$this->handler instanceof \SessionUpdateTimestampHandlerInterface || $this->handler->validateId($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ return $this->handler instanceof \SessionUpdateTimestampHandlerInterface ? $this->handler->updateTimestamp($sessionId, $data) : $this->write($sessionId, $data);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php
new file mode 100755
index 0000000..66e8b33
--- /dev/null
+++ b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php
@@ -0,0 +1,137 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Session\Storage;
+
+use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
+
+/**
+ * StorageInterface.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Drak <drak@zikula.org>
+ */
+interface SessionStorageInterface
+{
+ /**
+ * Starts the session.
+ *
+ * @return bool True if started
+ *
+ * @throws \RuntimeException if something goes wrong starting the session
+ */
+ public function start();
+
+ /**
+ * Checks if the session is started.
+ *
+ * @return bool True if started, false otherwise
+ */
+ public function isStarted();
+
+ /**
+ * Returns the session ID.
+ *
+ * @return string The session ID or empty
+ */
+ public function getId();
+
+ /**
+ * Sets the session ID.
+ *
+ * @param string $id
+ */
+ public function setId($id);
+
+ /**
+ * Returns the session name.
+ *
+ * @return mixed The session name
+ */
+ public function getName();
+
+ /**
+ * Sets the session name.
+ *
+ * @param string $name
+ */
+ public function setName($name);
+
+ /**
+ * Regenerates id that represents this storage.
+ *
+ * This method must invoke session_regenerate_id($destroy) unless
+ * this interface is used for a storage object designed for unit
+ * or functional testing where a real PHP session would interfere
+ * with testing.
+ *
+ * Note regenerate+destroy should not clear the session data in memory
+ * only delete the session data from persistent storage.
+ *
+ * Care: When regenerating the session ID no locking is involved in PHP's
+ * session design. See https://bugs.php.net/bug.php?id=61470 for a discussion.
+ * So you must make sure the regenerated session is saved BEFORE sending the
+ * headers with the new ID. Symfony's HttpKernel offers a listener for this.
+ * See Symfony\Component\HttpKernel\EventListener\SaveSessionListener.
+ * Otherwise session data could get lost again for concurrent requests with the
+ * new ID. One result could be that you get logged out after just logging in.
+ *
+ * @param bool $destroy Destroy session when regenerating?
+ * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
+ * will leave the system settings unchanged, 0 sets the cookie
+ * to expire with browser session. Time is in seconds, and is
+ * not a Unix timestamp.
+ *
+ * @return bool True if session regenerated, false if error
+ *
+ * @throws \RuntimeException If an error occurs while regenerating this storage
+ */
+ public function regenerate($destroy = false, $lifetime = null);
+
+ /**
+ * Force the session to be saved and closed.
+ *
+ * This method must invoke session_write_close() unless this interface is
+ * used for a storage object design for unit or functional testing where
+ * a real PHP session would interfere with testing, in which case
+ * it should actually persist the session data if required.
+ *
+ * @throws \RuntimeException if the session is saved without being started, or if the session
+ * is already closed
+ */
+ public function save();
+
+ /**
+ * Clear all session data in memory.
+ */
+ public function clear();
+
+ /**
+ * Gets a SessionBagInterface by name.
+ *
+ * @param string $name
+ *
+ * @return SessionBagInterface
+ *
+ * @throws \InvalidArgumentException If the bag does not exist
+ */
+ public function getBag($name);
+
+ /**
+ * Registers a SessionBagInterface for use.
+ */
+ public function registerBag(SessionBagInterface $bag);
+
+ /**
+ * @return MetadataBag
+ */
+ public function getMetadataBag();
+}
diff --git a/vendor/symfony/http-foundation/StreamedResponse.php b/vendor/symfony/http-foundation/StreamedResponse.php
new file mode 100755
index 0000000..8bc5fc9
--- /dev/null
+++ b/vendor/symfony/http-foundation/StreamedResponse.php
@@ -0,0 +1,146 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * StreamedResponse represents a streamed HTTP response.
+ *
+ * A StreamedResponse uses a callback for its content.
+ *
+ * The callback should use the standard PHP functions like echo
+ * to stream the response back to the client. The flush() function
+ * can also be used if needed.
+ *
+ * @see flush()
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class StreamedResponse extends Response
+{
+ protected $callback;
+ protected $streamed;
+ private $headersSent;
+
+ /**
+ * @param callable|null $callback A valid PHP callback or null to set it later
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ */
+ public function __construct(callable $callback = null, $status = 200, $headers = [])
+ {
+ parent::__construct(null, $status, $headers);
+
+ if (null !== $callback) {
+ $this->setCallback($callback);
+ }
+ $this->streamed = false;
+ $this->headersSent = false;
+ }
+
+ /**
+ * Factory method for chainability.
+ *
+ * @param callable|null $callback A valid PHP callback or null to set it later
+ * @param int $status The response status code
+ * @param array $headers An array of response headers
+ *
+ * @return static
+ */
+ public static function create($callback = null, $status = 200, $headers = [])
+ {
+ return new static($callback, $status, $headers);
+ }
+
+ /**
+ * Sets the PHP callback associated with this Response.
+ *
+ * @param callable $callback A valid PHP callback
+ *
+ * @return $this
+ */
+ public function setCallback(callable $callback)
+ {
+ $this->callback = $callback;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * This method only sends the headers once.
+ *
+ * @return $this
+ */
+ public function sendHeaders()
+ {
+ if ($this->headersSent) {
+ return $this;
+ }
+
+ $this->headersSent = true;
+
+ return parent::sendHeaders();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * This method only sends the content once.
+ *
+ * @return $this
+ */
+ public function sendContent()
+ {
+ if ($this->streamed) {
+ return $this;
+ }
+
+ $this->streamed = true;
+
+ if (null === $this->callback) {
+ throw new \LogicException('The Response callback must not be null.');
+ }
+
+ \call_user_func($this->callback);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \LogicException when the content is not null
+ *
+ * @return $this
+ */
+ public function setContent($content)
+ {
+ if (null !== $content) {
+ throw new \LogicException('The content cannot be set on a StreamedResponse instance.');
+ }
+
+ $this->streamed = true;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return false
+ */
+ public function getContent()
+ {
+ return false;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php b/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php
new file mode 100755
index 0000000..a40a762
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php
@@ -0,0 +1,113 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\AcceptHeaderItem;
+
+class AcceptHeaderItemTest extends TestCase
+{
+ /**
+ * @dataProvider provideFromStringData
+ */
+ public function testFromString($string, $value, array $attributes)
+ {
+ $item = AcceptHeaderItem::fromString($string);
+ $this->assertEquals($value, $item->getValue());
+ $this->assertEquals($attributes, $item->getAttributes());
+ }
+
+ public function provideFromStringData()
+ {
+ return [
+ [
+ 'text/html',
+ 'text/html', [],
+ ],
+ [
+ '"this;should,not=matter"',
+ 'this;should,not=matter', [],
+ ],
+ [
+ "text/plain; charset=utf-8;param=\"this;should,not=matter\";\tfootnotes=true",
+ 'text/plain', ['charset' => 'utf-8', 'param' => 'this;should,not=matter', 'footnotes' => 'true'],
+ ],
+ [
+ '"this;should,not=matter";charset=utf-8',
+ 'this;should,not=matter', ['charset' => 'utf-8'],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideToStringData
+ */
+ public function testToString($value, array $attributes, $string)
+ {
+ $item = new AcceptHeaderItem($value, $attributes);
+ $this->assertEquals($string, (string) $item);
+ }
+
+ public function provideToStringData()
+ {
+ return [
+ [
+ 'text/html', [],
+ 'text/html',
+ ],
+ [
+ 'text/plain', ['charset' => 'utf-8', 'param' => 'this;should,not=matter', 'footnotes' => 'true'],
+ 'text/plain;charset=utf-8;param="this;should,not=matter";footnotes=true',
+ ],
+ ];
+ }
+
+ public function testValue()
+ {
+ $item = new AcceptHeaderItem('value', []);
+ $this->assertEquals('value', $item->getValue());
+
+ $item->setValue('new value');
+ $this->assertEquals('new value', $item->getValue());
+
+ $item->setValue(1);
+ $this->assertEquals('1', $item->getValue());
+ }
+
+ public function testQuality()
+ {
+ $item = new AcceptHeaderItem('value', []);
+ $this->assertEquals(1.0, $item->getQuality());
+
+ $item->setQuality(0.5);
+ $this->assertEquals(0.5, $item->getQuality());
+
+ $item->setAttribute('q', 0.75);
+ $this->assertEquals(0.75, $item->getQuality());
+ $this->assertFalse($item->hasAttribute('q'));
+ }
+
+ public function testAttribute()
+ {
+ $item = new AcceptHeaderItem('value', []);
+ $this->assertEquals([], $item->getAttributes());
+ $this->assertFalse($item->hasAttribute('test'));
+ $this->assertNull($item->getAttribute('test'));
+ $this->assertEquals('default', $item->getAttribute('test', 'default'));
+
+ $item->setAttribute('test', 'value');
+ $this->assertEquals(['test' => 'value'], $item->getAttributes());
+ $this->assertTrue($item->hasAttribute('test'));
+ $this->assertEquals('value', $item->getAttribute('test'));
+ $this->assertEquals('value', $item->getAttribute('test', 'default'));
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php b/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php
new file mode 100755
index 0000000..3199869
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php
@@ -0,0 +1,103 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\AcceptHeader;
+use Symfony\Component\HttpFoundation\AcceptHeaderItem;
+
+class AcceptHeaderTest extends TestCase
+{
+ public function testFirst()
+ {
+ $header = AcceptHeader::fromString('text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c');
+ $this->assertSame('text/html', $header->first()->getValue());
+ }
+
+ /**
+ * @dataProvider provideFromStringData
+ */
+ public function testFromString($string, array $items)
+ {
+ $header = AcceptHeader::fromString($string);
+ $parsed = array_values($header->all());
+ // reset index since the fixtures don't have them set
+ foreach ($parsed as $item) {
+ $item->setIndex(0);
+ }
+ $this->assertEquals($items, $parsed);
+ }
+
+ public function provideFromStringData()
+ {
+ return [
+ ['', []],
+ ['gzip', [new AcceptHeaderItem('gzip')]],
+ ['gzip,deflate,sdch', [new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch')]],
+ ["gzip, deflate\t,sdch", [new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch')]],
+ ['"this;should,not=matter"', [new AcceptHeaderItem('this;should,not=matter')]],
+ ];
+ }
+
+ /**
+ * @dataProvider provideToStringData
+ */
+ public function testToString(array $items, $string)
+ {
+ $header = new AcceptHeader($items);
+ $this->assertEquals($string, (string) $header);
+ }
+
+ public function provideToStringData()
+ {
+ return [
+ [[], ''],
+ [[new AcceptHeaderItem('gzip')], 'gzip'],
+ [[new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch')], 'gzip,deflate,sdch'],
+ [[new AcceptHeaderItem('this;should,not=matter')], 'this;should,not=matter'],
+ ];
+ }
+
+ /**
+ * @dataProvider provideFilterData
+ */
+ public function testFilter($string, $filter, array $values)
+ {
+ $header = AcceptHeader::fromString($string)->filter($filter);
+ $this->assertEquals($values, array_keys($header->all()));
+ }
+
+ public function provideFilterData()
+ {
+ return [
+ ['fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4', '/fr.*/', ['fr-FR', 'fr']],
+ ];
+ }
+
+ /**
+ * @dataProvider provideSortingData
+ */
+ public function testSorting($string, array $values)
+ {
+ $header = AcceptHeader::fromString($string);
+ $this->assertEquals($values, array_keys($header->all()));
+ }
+
+ public function provideSortingData()
+ {
+ return [
+ 'quality has priority' => ['*;q=0.3,ISO-8859-1,utf-8;q=0.7', ['ISO-8859-1', 'utf-8', '*']],
+ 'order matters when q is equal' => ['*;q=0.3,ISO-8859-1;q=0.7,utf-8;q=0.7', ['ISO-8859-1', 'utf-8', '*']],
+ 'order matters when q is equal2' => ['*;q=0.3,utf-8;q=0.7,ISO-8859-1;q=0.7', ['utf-8', 'ISO-8859-1', '*']],
+ ];
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php b/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php
new file mode 100755
index 0000000..6fa3b88
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php
@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\ApacheRequest;
+
+class ApacheRequestTest extends TestCase
+{
+ /**
+ * @dataProvider provideServerVars
+ */
+ public function testUriMethods($server, $expectedRequestUri, $expectedBaseUrl, $expectedPathInfo)
+ {
+ $request = new ApacheRequest();
+ $request->server->replace($server);
+
+ $this->assertEquals($expectedRequestUri, $request->getRequestUri(), '->getRequestUri() is correct');
+ $this->assertEquals($expectedBaseUrl, $request->getBaseUrl(), '->getBaseUrl() is correct');
+ $this->assertEquals($expectedPathInfo, $request->getPathInfo(), '->getPathInfo() is correct');
+ }
+
+ public function provideServerVars()
+ {
+ return [
+ [
+ [
+ 'REQUEST_URI' => '/foo/app_dev.php/bar',
+ 'SCRIPT_NAME' => '/foo/app_dev.php',
+ 'PATH_INFO' => '/bar',
+ ],
+ '/foo/app_dev.php/bar',
+ '/foo/app_dev.php',
+ '/bar',
+ ],
+ [
+ [
+ 'REQUEST_URI' => '/foo/bar',
+ 'SCRIPT_NAME' => '/foo/app_dev.php',
+ ],
+ '/foo/bar',
+ '/foo',
+ '/bar',
+ ],
+ [
+ [
+ 'REQUEST_URI' => '/app_dev.php/foo/bar',
+ 'SCRIPT_NAME' => '/app_dev.php',
+ 'PATH_INFO' => '/foo/bar',
+ ],
+ '/app_dev.php/foo/bar',
+ '/app_dev.php',
+ '/foo/bar',
+ ],
+ [
+ [
+ 'REQUEST_URI' => '/foo/bar',
+ 'SCRIPT_NAME' => '/app_dev.php',
+ ],
+ '/foo/bar',
+ '',
+ '/foo/bar',
+ ],
+ [
+ [
+ 'REQUEST_URI' => '/app_dev.php',
+ 'SCRIPT_NAME' => '/app_dev.php',
+ ],
+ '/app_dev.php',
+ '/app_dev.php',
+ '/',
+ ],
+ [
+ [
+ 'REQUEST_URI' => '/',
+ 'SCRIPT_NAME' => '/app_dev.php',
+ ],
+ '/',
+ '',
+ '/',
+ ],
+ ];
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php b/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php
new file mode 100755
index 0000000..c89f20d
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php
@@ -0,0 +1,365 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Symfony\Component\HttpFoundation\File\Stream;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\ResponseHeaderBag;
+use Symfony\Component\HttpFoundation\Tests\File\FakeFile;
+
+class BinaryFileResponseTest extends ResponseTestCase
+{
+ public function testConstruction()
+ {
+ $file = __DIR__.'/../README.md';
+ $response = new BinaryFileResponse($file, 404, ['X-Header' => 'Foo'], true, null, true, true);
+ $this->assertEquals(404, $response->getStatusCode());
+ $this->assertEquals('Foo', $response->headers->get('X-Header'));
+ $this->assertTrue($response->headers->has('ETag'));
+ $this->assertTrue($response->headers->has('Last-Modified'));
+ $this->assertFalse($response->headers->has('Content-Disposition'));
+
+ $response = BinaryFileResponse::create($file, 404, [], true, ResponseHeaderBag::DISPOSITION_INLINE);
+ $this->assertEquals(404, $response->getStatusCode());
+ $this->assertFalse($response->headers->has('ETag'));
+ $this->assertEquals('inline; filename="README.md"', $response->headers->get('Content-Disposition'));
+ }
+
+ public function testConstructWithNonAsciiFilename()
+ {
+ touch(sys_get_temp_dir().'/fööö.html');
+
+ $response = new BinaryFileResponse(sys_get_temp_dir().'/fööö.html', 200, [], true, 'attachment');
+
+ @unlink(sys_get_temp_dir().'/fööö.html');
+
+ $this->assertSame('fööö.html', $response->getFile()->getFilename());
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testSetContent()
+ {
+ $response = new BinaryFileResponse(__FILE__);
+ $response->setContent('foo');
+ }
+
+ public function testGetContent()
+ {
+ $response = new BinaryFileResponse(__FILE__);
+ $this->assertFalse($response->getContent());
+ }
+
+ public function testSetContentDispositionGeneratesSafeFallbackFilename()
+ {
+ $response = new BinaryFileResponse(__FILE__);
+ $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'föö.html');
+
+ $this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html', $response->headers->get('Content-Disposition'));
+ }
+
+ public function testSetContentDispositionGeneratesSafeFallbackFilenameForWronglyEncodedFilename()
+ {
+ $response = new BinaryFileResponse(__FILE__);
+
+ $iso88591EncodedFilename = utf8_decode('föö.html');
+ $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $iso88591EncodedFilename);
+
+ // the parameter filename* is invalid in this case (rawurldecode('f%F6%F6') does not provide a UTF-8 string but an ISO-8859-1 encoded one)
+ $this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%F6%F6.html', $response->headers->get('Content-Disposition'));
+ }
+
+ /**
+ * @dataProvider provideRanges
+ */
+ public function testRequests($requestRange, $offset, $length, $responseRange)
+ {
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream'])->setAutoEtag();
+
+ // do a request to get the ETag
+ $request = Request::create('/');
+ $response->prepare($request);
+ $etag = $response->headers->get('ETag');
+
+ // prepare a request for a range of the testing file
+ $request = Request::create('/');
+ $request->headers->set('If-Range', $etag);
+ $request->headers->set('Range', $requestRange);
+
+ $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r');
+ fseek($file, $offset);
+ $data = fread($file, $length);
+ fclose($file);
+
+ $this->expectOutputString($data);
+ $response = clone $response;
+ $response->prepare($request);
+ $response->sendContent();
+
+ $this->assertEquals(206, $response->getStatusCode());
+ $this->assertEquals($responseRange, $response->headers->get('Content-Range'));
+ $this->assertSame($length, $response->headers->get('Content-Length'));
+ }
+
+ /**
+ * @dataProvider provideRanges
+ */
+ public function testRequestsWithoutEtag($requestRange, $offset, $length, $responseRange)
+ {
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']);
+
+ // do a request to get the LastModified
+ $request = Request::create('/');
+ $response->prepare($request);
+ $lastModified = $response->headers->get('Last-Modified');
+
+ // prepare a request for a range of the testing file
+ $request = Request::create('/');
+ $request->headers->set('If-Range', $lastModified);
+ $request->headers->set('Range', $requestRange);
+
+ $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r');
+ fseek($file, $offset);
+ $data = fread($file, $length);
+ fclose($file);
+
+ $this->expectOutputString($data);
+ $response = clone $response;
+ $response->prepare($request);
+ $response->sendContent();
+
+ $this->assertEquals(206, $response->getStatusCode());
+ $this->assertEquals($responseRange, $response->headers->get('Content-Range'));
+ }
+
+ public function provideRanges()
+ {
+ return [
+ ['bytes=1-4', 1, 4, 'bytes 1-4/35'],
+ ['bytes=-5', 30, 5, 'bytes 30-34/35'],
+ ['bytes=30-', 30, 5, 'bytes 30-34/35'],
+ ['bytes=30-30', 30, 1, 'bytes 30-30/35'],
+ ['bytes=30-34', 30, 5, 'bytes 30-34/35'],
+ ];
+ }
+
+ public function testRangeRequestsWithoutLastModifiedDate()
+ {
+ // prevent auto last modified
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream'], true, null, false, false);
+
+ // prepare a request for a range of the testing file
+ $request = Request::create('/');
+ $request->headers->set('If-Range', date('D, d M Y H:i:s').' GMT');
+ $request->headers->set('Range', 'bytes=1-4');
+
+ $this->expectOutputString(file_get_contents(__DIR__.'/File/Fixtures/test.gif'));
+ $response = clone $response;
+ $response->prepare($request);
+ $response->sendContent();
+
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertNull($response->headers->get('Content-Range'));
+ }
+
+ /**
+ * @dataProvider provideFullFileRanges
+ */
+ public function testFullFileRequests($requestRange)
+ {
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream'])->setAutoEtag();
+
+ // prepare a request for a range of the testing file
+ $request = Request::create('/');
+ $request->headers->set('Range', $requestRange);
+
+ $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r');
+ $data = fread($file, 35);
+ fclose($file);
+
+ $this->expectOutputString($data);
+ $response = clone $response;
+ $response->prepare($request);
+ $response->sendContent();
+
+ $this->assertEquals(200, $response->getStatusCode());
+ }
+
+ public function provideFullFileRanges()
+ {
+ return [
+ ['bytes=0-'],
+ ['bytes=0-34'],
+ ['bytes=-35'],
+ // Syntactical invalid range-request should also return the full resource
+ ['bytes=20-10'],
+ ['bytes=50-40'],
+ ];
+ }
+
+ public function testUnpreparedResponseSendsFullFile()
+ {
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200);
+
+ $data = file_get_contents(__DIR__.'/File/Fixtures/test.gif');
+
+ $this->expectOutputString($data);
+ $response = clone $response;
+ $response->sendContent();
+
+ $this->assertEquals(200, $response->getStatusCode());
+ }
+
+ /**
+ * @dataProvider provideInvalidRanges
+ */
+ public function testInvalidRequests($requestRange)
+ {
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream'])->setAutoEtag();
+
+ // prepare a request for a range of the testing file
+ $request = Request::create('/');
+ $request->headers->set('Range', $requestRange);
+
+ $response = clone $response;
+ $response->prepare($request);
+ $response->sendContent();
+
+ $this->assertEquals(416, $response->getStatusCode());
+ $this->assertEquals('bytes */35', $response->headers->get('Content-Range'));
+ }
+
+ public function provideInvalidRanges()
+ {
+ return [
+ ['bytes=-40'],
+ ['bytes=30-40'],
+ ];
+ }
+
+ /**
+ * @dataProvider provideXSendfileFiles
+ */
+ public function testXSendfile($file)
+ {
+ $request = Request::create('/');
+ $request->headers->set('X-Sendfile-Type', 'X-Sendfile');
+
+ BinaryFileResponse::trustXSendfileTypeHeader();
+ $response = BinaryFileResponse::create($file, 200, ['Content-Type' => 'application/octet-stream']);
+ $response->prepare($request);
+
+ $this->expectOutputString('');
+ $response->sendContent();
+
+ $this->assertContains('README.md', $response->headers->get('X-Sendfile'));
+ }
+
+ public function provideXSendfileFiles()
+ {
+ return [
+ [__DIR__.'/../README.md'],
+ ['file://'.__DIR__.'/../README.md'],
+ ];
+ }
+
+ /**
+ * @dataProvider getSampleXAccelMappings
+ */
+ public function testXAccelMapping($realpath, $mapping, $virtual)
+ {
+ $request = Request::create('/');
+ $request->headers->set('X-Sendfile-Type', 'X-Accel-Redirect');
+ $request->headers->set('X-Accel-Mapping', $mapping);
+
+ $file = new FakeFile($realpath, __DIR__.'/File/Fixtures/test');
+
+ BinaryFileResponse::trustXSendfileTypeHeader();
+ $response = new BinaryFileResponse($file, 200, ['Content-Type' => 'application/octet-stream']);
+ $reflection = new \ReflectionObject($response);
+ $property = $reflection->getProperty('file');
+ $property->setAccessible(true);
+ $property->setValue($response, $file);
+
+ $response->prepare($request);
+ $this->assertEquals($virtual, $response->headers->get('X-Accel-Redirect'));
+ }
+
+ public function testDeleteFileAfterSend()
+ {
+ $request = Request::create('/');
+
+ $path = __DIR__.'/File/Fixtures/to_delete';
+ touch($path);
+ $realPath = realpath($path);
+ $this->assertFileExists($realPath);
+
+ $response = new BinaryFileResponse($realPath, 200, ['Content-Type' => 'application/octet-stream']);
+ $response->deleteFileAfterSend(true);
+
+ $response->prepare($request);
+ $response->sendContent();
+
+ $this->assertFileNotExists($path);
+ }
+
+ public function testAcceptRangeOnUnsafeMethods()
+ {
+ $request = Request::create('/', 'POST');
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']);
+ $response->prepare($request);
+
+ $this->assertEquals('none', $response->headers->get('Accept-Ranges'));
+ }
+
+ public function testAcceptRangeNotOverriden()
+ {
+ $request = Request::create('/', 'POST');
+ $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']);
+ $response->headers->set('Accept-Ranges', 'foo');
+ $response->prepare($request);
+
+ $this->assertEquals('foo', $response->headers->get('Accept-Ranges'));
+ }
+
+ public function getSampleXAccelMappings()
+ {
+ return [
+ ['/var/www/var/www/files/foo.txt', '/var/www/=/files/', '/files/var/www/files/foo.txt'],
+ ['/home/foo/bar.txt', '/var/www/=/files/,/home/foo/=/baz/', '/baz/bar.txt'],
+ ];
+ }
+
+ public function testStream()
+ {
+ $request = Request::create('/');
+ $response = new BinaryFileResponse(new Stream(__DIR__.'/../README.md'), 200, ['Content-Type' => 'text/plain']);
+ $response->prepare($request);
+
+ $this->assertNull($response->headers->get('Content-Length'));
+ }
+
+ protected function provideResponse()
+ {
+ return new BinaryFileResponse(__DIR__.'/../README.md', 200, ['Content-Type' => 'application/octet-stream']);
+ }
+
+ public static function tearDownAfterClass()
+ {
+ $path = __DIR__.'/../Fixtures/to_delete';
+ if (file_exists($path)) {
+ @unlink($path);
+ }
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/CookieTest.php b/vendor/symfony/http-foundation/Tests/CookieTest.php
new file mode 100755
index 0000000..aaf2edb
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/CookieTest.php
@@ -0,0 +1,235 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Cookie;
+
+/**
+ * CookieTest.
+ *
+ * @author John Kary <john@johnkary.net>
+ * @author Hugo Hamon <hugo.hamon@sensio.com>
+ *
+ * @group time-sensitive
+ */
+class CookieTest extends TestCase
+{
+ public function invalidNames()
+ {
+ return [
+ [''],
+ [',MyName'],
+ [';MyName'],
+ [' MyName'],
+ ["\tMyName"],
+ ["\rMyName"],
+ ["\nMyName"],
+ ["\013MyName"],
+ ["\014MyName"],
+ ];
+ }
+
+ /**
+ * @dataProvider invalidNames
+ * @expectedException \InvalidArgumentException
+ */
+ public function testInstantiationThrowsExceptionIfCookieNameContainsInvalidCharacters($name)
+ {
+ new Cookie($name);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testInvalidExpiration()
+ {
+ new Cookie('MyCookie', 'foo', 'bar');
+ }
+
+ public function testNegativeExpirationIsNotPossible()
+ {
+ $cookie = new Cookie('foo', 'bar', -100);
+
+ $this->assertSame(0, $cookie->getExpiresTime());
+ }
+
+ public function testGetValue()
+ {
+ $value = 'MyValue';
+ $cookie = new Cookie('MyCookie', $value);
+
+ $this->assertSame($value, $cookie->getValue(), '->getValue() returns the proper value');
+ }
+
+ public function testGetPath()
+ {
+ $cookie = new Cookie('foo', 'bar');
+
+ $this->assertSame('/', $cookie->getPath(), '->getPath() returns / as the default path');
+ }
+
+ public function testGetExpiresTime()
+ {
+ $cookie = new Cookie('foo', 'bar');
+
+ $this->assertEquals(0, $cookie->getExpiresTime(), '->getExpiresTime() returns the default expire date');
+
+ $cookie = new Cookie('foo', 'bar', $expire = time() + 3600);
+
+ $this->assertEquals($expire, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date');
+ }
+
+ public function testGetExpiresTimeIsCastToInt()
+ {
+ $cookie = new Cookie('foo', 'bar', 3600.9);
+
+ $this->assertSame(3600, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date as an integer');
+ }
+
+ public function testConstructorWithDateTime()
+ {
+ $expire = new \DateTime();
+ $cookie = new Cookie('foo', 'bar', $expire);
+
+ $this->assertEquals($expire->format('U'), $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date');
+ }
+
+ /**
+ * @requires PHP 5.5
+ */
+ public function testConstructorWithDateTimeImmutable()
+ {
+ $expire = new \DateTimeImmutable();
+ $cookie = new Cookie('foo', 'bar', $expire);
+
+ $this->assertEquals($expire->format('U'), $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date');
+ }
+
+ public function testGetExpiresTimeWithStringValue()
+ {
+ $value = '+1 day';
+ $cookie = new Cookie('foo', 'bar', $value);
+ $expire = strtotime($value);
+
+ $this->assertEquals($expire, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date', 1);
+ }
+
+ public function testGetDomain()
+ {
+ $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com');
+
+ $this->assertEquals('.myfoodomain.com', $cookie->getDomain(), '->getDomain() returns the domain name on which the cookie is valid');
+ }
+
+ public function testIsSecure()
+ {
+ $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com', true);
+
+ $this->assertTrue($cookie->isSecure(), '->isSecure() returns whether the cookie is transmitted over HTTPS');
+ }
+
+ public function testIsHttpOnly()
+ {
+ $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com', false, true);
+
+ $this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns whether the cookie is only transmitted over HTTP');
+ }
+
+ public function testCookieIsNotCleared()
+ {
+ $cookie = new Cookie('foo', 'bar', time() + 3600 * 24);
+
+ $this->assertFalse($cookie->isCleared(), '->isCleared() returns false if the cookie did not expire yet');
+ }
+
+ public function testCookieIsCleared()
+ {
+ $cookie = new Cookie('foo', 'bar', time() - 20);
+
+ $this->assertTrue($cookie->isCleared(), '->isCleared() returns true if the cookie has expired');
+
+ $cookie = new Cookie('foo', 'bar');
+
+ $this->assertFalse($cookie->isCleared());
+
+ $cookie = new Cookie('foo', 'bar', 0);
+
+ $this->assertFalse($cookie->isCleared());
+
+ $cookie = new Cookie('foo', 'bar', -1);
+
+ $this->assertFalse($cookie->isCleared());
+ }
+
+ public function testToString()
+ {
+ $cookie = new Cookie('foo', 'bar', $expire = strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true);
+ $this->assertEquals('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; Max-Age=0; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() returns string representation of the cookie');
+
+ $cookie = new Cookie('foo', 'bar with white spaces', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true);
+ $this->assertEquals('foo=bar%20with%20white%20spaces; expires=Fri, 20-May-2011 15:25:52 GMT; Max-Age=0; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() encodes the value of the cookie according to RFC 3986 (white space = %20)');
+
+ $cookie = new Cookie('foo', null, 1, '/admin/', '.myfoodomain.com');
+ $this->assertEquals('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', $expire = time() - 31536001).'; Max-Age=0; path=/admin/; domain=.myfoodomain.com; httponly', (string) $cookie, '->__toString() returns string representation of a cleared cookie if value is NULL');
+
+ $cookie = new Cookie('foo', 'bar', 0, '/', '');
+ $this->assertEquals('foo=bar; path=/; httponly', (string) $cookie);
+ }
+
+ public function testRawCookie()
+ {
+ $cookie = new Cookie('foo', 'b a r', 0, '/', null, false, false);
+ $this->assertFalse($cookie->isRaw());
+ $this->assertEquals('foo=b%20a%20r; path=/', (string) $cookie);
+
+ $cookie = new Cookie('foo', 'b+a+r', 0, '/', null, false, false, true);
+ $this->assertTrue($cookie->isRaw());
+ $this->assertEquals('foo=b+a+r; path=/', (string) $cookie);
+ }
+
+ public function testGetMaxAge()
+ {
+ $cookie = new Cookie('foo', 'bar');
+ $this->assertEquals(0, $cookie->getMaxAge());
+
+ $cookie = new Cookie('foo', 'bar', $expire = time() + 100);
+ $this->assertEquals($expire - time(), $cookie->getMaxAge());
+
+ $cookie = new Cookie('foo', 'bar', $expire = time() - 100);
+ $this->assertEquals(0, $cookie->getMaxAge());
+ }
+
+ public function testFromString()
+ {
+ $cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure; httponly');
+ $this->assertEquals(new Cookie('foo', 'bar', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true, true, true), $cookie);
+
+ $cookie = Cookie::fromString('foo=bar', true);
+ $this->assertEquals(new Cookie('foo', 'bar', 0, '/', null, false, false), $cookie);
+ }
+
+ public function testFromStringWithHttpOnly()
+ {
+ $cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure; httponly');
+ $this->assertTrue($cookie->isHttpOnly());
+
+ $cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure');
+ $this->assertFalse($cookie->isHttpOnly());
+ }
+
+ public function testSameSiteAttributeIsCaseInsensitive()
+ {
+ $cookie = new Cookie('foo', 'bar', 0, '/', null, false, true, false, 'Lax');
+ $this->assertEquals('lax', $cookie->getSameSite());
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php b/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php
new file mode 100755
index 0000000..2afdade
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\HttpFoundation\ExpressionRequestMatcher;
+use Symfony\Component\HttpFoundation\Request;
+
+class ExpressionRequestMatcherTest extends TestCase
+{
+ /**
+ * @expectedException \LogicException
+ */
+ public function testWhenNoExpressionIsSet()
+ {
+ $expressionRequestMatcher = new ExpressionRequestMatcher();
+ $expressionRequestMatcher->matches(new Request());
+ }
+
+ /**
+ * @dataProvider provideExpressions
+ */
+ public function testMatchesWhenParentMatchesIsTrue($expression, $expected)
+ {
+ $request = Request::create('/foo');
+ $expressionRequestMatcher = new ExpressionRequestMatcher();
+
+ $expressionRequestMatcher->setExpression(new ExpressionLanguage(), $expression);
+ $this->assertSame($expected, $expressionRequestMatcher->matches($request));
+ }
+
+ /**
+ * @dataProvider provideExpressions
+ */
+ public function testMatchesWhenParentMatchesIsFalse($expression)
+ {
+ $request = Request::create('/foo');
+ $request->attributes->set('foo', 'foo');
+ $expressionRequestMatcher = new ExpressionRequestMatcher();
+ $expressionRequestMatcher->matchAttribute('foo', 'bar');
+
+ $expressionRequestMatcher->setExpression(new ExpressionLanguage(), $expression);
+ $this->assertFalse($expressionRequestMatcher->matches($request));
+ }
+
+ public function provideExpressions()
+ {
+ return [
+ ['request.getMethod() == method', true],
+ ['request.getPathInfo() == path', true],
+ ['request.getHost() == host', true],
+ ['request.getClientIp() == ip', true],
+ ['request.attributes.all() == attributes', true],
+ ['request.getMethod() == method && request.getPathInfo() == path && request.getHost() == host && request.getClientIp() == ip && request.attributes.all() == attributes', true],
+ ['request.getMethod() != method', false],
+ ['request.getMethod() != method && request.getPathInfo() == path && request.getHost() == host && request.getClientIp() == ip && request.attributes.all() == attributes', false],
+ ];
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/File/FakeFile.php b/vendor/symfony/http-foundation/Tests/File/FakeFile.php
new file mode 100755
index 0000000..c415989
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/FakeFile.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\File;
+
+use Symfony\Component\HttpFoundation\File\File as OrigFile;
+
+class FakeFile extends OrigFile
+{
+ private $realpath;
+
+ public function __construct($realpath, $path)
+ {
+ $this->realpath = $realpath;
+ parent::__construct($path, false);
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function getRealpath()
+ {
+ return $this->realpath;
+ }
+
+ public function getSize()
+ {
+ return 42;
+ }
+
+ public function getMTime()
+ {
+ return time();
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/File/FileTest.php b/vendor/symfony/http-foundation/Tests/File/FileTest.php
new file mode 100755
index 0000000..fb82dae
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/FileTest.php
@@ -0,0 +1,180 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\File;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\File\File;
+use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
+
+class FileTest extends TestCase
+{
+ protected $file;
+
+ public function testGetMimeTypeUsesMimeTypeGuessers()
+ {
+ $file = new File(__DIR__.'/Fixtures/test.gif');
+ $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif');
+
+ MimeTypeGuesser::getInstance()->register($guesser);
+
+ $this->assertEquals('image/gif', $file->getMimeType());
+ }
+
+ public function testGuessExtensionWithoutGuesser()
+ {
+ $file = new File(__DIR__.'/Fixtures/directory/.empty');
+
+ $this->assertNull($file->guessExtension());
+ }
+
+ public function testGuessExtensionIsBasedOnMimeType()
+ {
+ $file = new File(__DIR__.'/Fixtures/test');
+ $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif');
+
+ MimeTypeGuesser::getInstance()->register($guesser);
+
+ $this->assertEquals('gif', $file->guessExtension());
+ }
+
+ /**
+ * @requires extension fileinfo
+ */
+ public function testGuessExtensionWithReset()
+ {
+ $file = new File(__DIR__.'/Fixtures/other-file.example');
+ $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif');
+ MimeTypeGuesser::getInstance()->register($guesser);
+
+ $this->assertEquals('gif', $file->guessExtension());
+
+ MimeTypeGuesser::reset();
+
+ $this->assertNull($file->guessExtension());
+ }
+
+ public function testConstructWhenFileNotExists()
+ {
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException');
+
+ new File(__DIR__.'/Fixtures/not_here');
+ }
+
+ public function testMove()
+ {
+ $path = __DIR__.'/Fixtures/test.copy.gif';
+ $targetDir = __DIR__.'/Fixtures/directory';
+ $targetPath = $targetDir.'/test.copy.gif';
+ @unlink($path);
+ @unlink($targetPath);
+ copy(__DIR__.'/Fixtures/test.gif', $path);
+
+ $file = new File($path);
+ $movedFile = $file->move($targetDir);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\File\File', $movedFile);
+
+ $this->assertFileExists($targetPath);
+ $this->assertFileNotExists($path);
+ $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+ @unlink($targetPath);
+ }
+
+ public function testMoveWithNewName()
+ {
+ $path = __DIR__.'/Fixtures/test.copy.gif';
+ $targetDir = __DIR__.'/Fixtures/directory';
+ $targetPath = $targetDir.'/test.newname.gif';
+ @unlink($path);
+ @unlink($targetPath);
+ copy(__DIR__.'/Fixtures/test.gif', $path);
+
+ $file = new File($path);
+ $movedFile = $file->move($targetDir, 'test.newname.gif');
+
+ $this->assertFileExists($targetPath);
+ $this->assertFileNotExists($path);
+ $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+ @unlink($targetPath);
+ }
+
+ public function getFilenameFixtures()
+ {
+ return [
+ ['original.gif', 'original.gif'],
+ ['..\\..\\original.gif', 'original.gif'],
+ ['../../original.gif', 'original.gif'],
+ ['файлfile.gif', 'файлfile.gif'],
+ ['..\\..\\файлfile.gif', 'файлfile.gif'],
+ ['../../файлfile.gif', 'файлfile.gif'],
+ ];
+ }
+
+ /**
+ * @dataProvider getFilenameFixtures
+ */
+ public function testMoveWithNonLatinName($filename, $sanitizedFilename)
+ {
+ $path = __DIR__.'/Fixtures/'.$sanitizedFilename;
+ $targetDir = __DIR__.'/Fixtures/directory/';
+ $targetPath = $targetDir.$sanitizedFilename;
+ @unlink($path);
+ @unlink($targetPath);
+ copy(__DIR__.'/Fixtures/test.gif', $path);
+
+ $file = new File($path);
+ $movedFile = $file->move($targetDir, $filename);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\File\File', $movedFile);
+
+ $this->assertFileExists($targetPath);
+ $this->assertFileNotExists($path);
+ $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+ @unlink($targetPath);
+ }
+
+ public function testMoveToAnUnexistentDirectory()
+ {
+ $sourcePath = __DIR__.'/Fixtures/test.copy.gif';
+ $targetDir = __DIR__.'/Fixtures/directory/sub';
+ $targetPath = $targetDir.'/test.copy.gif';
+ @unlink($sourcePath);
+ @unlink($targetPath);
+ @rmdir($targetDir);
+ copy(__DIR__.'/Fixtures/test.gif', $sourcePath);
+
+ $file = new File($sourcePath);
+ $movedFile = $file->move($targetDir);
+
+ $this->assertFileExists($targetPath);
+ $this->assertFileNotExists($sourcePath);
+ $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+ @unlink($sourcePath);
+ @unlink($targetPath);
+ @rmdir($targetDir);
+ }
+
+ protected function createMockGuesser($path, $mimeType)
+ {
+ $guesser = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface')->getMock();
+ $guesser
+ ->expects($this->once())
+ ->method('guess')
+ ->with($this->equalTo($path))
+ ->will($this->returnValue($mimeType))
+ ;
+
+ return $guesser;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension b/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension
new file mode 100755
index 0000000..4d1ae35
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension
@@ -0,0 +1 @@
+f
\ No newline at end of file
diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/case-sensitive-mime-type.xlsm b/vendor/symfony/http-foundation/Tests/File/Fixtures/case-sensitive-mime-type.xlsm
new file mode 100755
index 0000000..94d85e6
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/Fixtures/case-sensitive-mime-type.xlsm
Binary files differ
diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty b/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty
diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example b/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example
diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/test b/vendor/symfony/http-foundation/Tests/File/Fixtures/test
new file mode 100755
index 0000000..b636f4b
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/Fixtures/test
Binary files differ
diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif b/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif
new file mode 100755
index 0000000..b636f4b
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif
Binary files differ
diff --git a/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php b/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php
new file mode 100755
index 0000000..bb88807
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php
@@ -0,0 +1,90 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\File\MimeType;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\File\MimeType\FileBinaryMimeTypeGuesser;
+use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
+
+/**
+ * @requires extension fileinfo
+ */
+class MimeTypeTest extends TestCase
+{
+ protected $path;
+
+ public function testGuessImageWithoutExtension()
+ {
+ $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test'));
+ }
+
+ public function testGuessImageWithDirectory()
+ {
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException');
+
+ MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/directory');
+ }
+
+ public function testGuessImageWithFileBinaryMimeTypeGuesser()
+ {
+ $guesser = MimeTypeGuesser::getInstance();
+ $guesser->register(new FileBinaryMimeTypeGuesser());
+ $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test'));
+ }
+
+ public function testGuessImageWithKnownExtension()
+ {
+ $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test.gif'));
+ }
+
+ public function testGuessFileWithUnknownExtension()
+ {
+ $this->assertEquals('application/octet-stream', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/.unknownextension'));
+ }
+
+ public function testGuessWithIncorrectPath()
+ {
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException');
+ MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/not_here');
+ }
+
+ public function testGuessWithNonReadablePath()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Can not verify chmod operations on Windows');
+ }
+
+ if (!getenv('USER') || 'root' === getenv('USER')) {
+ $this->markTestSkipped('This test will fail if run under superuser');
+ }
+
+ $path = __DIR__.'/../Fixtures/to_delete';
+ touch($path);
+ @chmod($path, 0333);
+
+ if ('0333' == substr(sprintf('%o', fileperms($path)), -4)) {
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException');
+ MimeTypeGuesser::getInstance()->guess($path);
+ } else {
+ $this->markTestSkipped('Can not verify chmod operations, change of file permissions failed');
+ }
+ }
+
+ public static function tearDownAfterClass()
+ {
+ $path = __DIR__.'/../Fixtures/to_delete';
+ if (file_exists($path)) {
+ @chmod($path, 0666);
+ @unlink($path);
+ }
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php b/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php
new file mode 100755
index 0000000..5a37cda
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php
@@ -0,0 +1,286 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\File;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+class UploadedFileTest extends TestCase
+{
+ protected function setUp()
+ {
+ if (!ini_get('file_uploads')) {
+ $this->markTestSkipped('file_uploads is disabled in php.ini');
+ }
+ }
+
+ public function testConstructWhenFileNotExists()
+ {
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException');
+
+ new UploadedFile(
+ __DIR__.'/Fixtures/not_here',
+ 'original.gif',
+ null
+ );
+ }
+
+ public function testFileUploadsWithNoMimeType()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ null,
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ UPLOAD_ERR_OK
+ );
+
+ $this->assertEquals('application/octet-stream', $file->getClientMimeType());
+
+ if (\extension_loaded('fileinfo')) {
+ $this->assertEquals('image/gif', $file->getMimeType());
+ }
+ }
+
+ public function testFileUploadsWithUnknownMimeType()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/.unknownextension',
+ 'original.gif',
+ null,
+ filesize(__DIR__.'/Fixtures/.unknownextension'),
+ UPLOAD_ERR_OK
+ );
+
+ $this->assertEquals('application/octet-stream', $file->getClientMimeType());
+ }
+
+ public function testGuessClientExtension()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals('gif', $file->guessClientExtension());
+ }
+
+ public function testGuessClientExtensionWithIncorrectMimeType()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/jpeg',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals('jpeg', $file->guessClientExtension());
+ }
+
+ public function testCaseSensitiveMimeType()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/case-sensitive-mime-type.xlsm',
+ 'test.xlsm',
+ 'application/vnd.ms-excel.sheet.macroEnabled.12',
+ filesize(__DIR__.'/Fixtures/case-sensitive-mime-type.xlsm'),
+ null
+ );
+
+ $this->assertEquals('xlsm', $file->guessClientExtension());
+ }
+
+ public function testErrorIsOkByDefault()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals(UPLOAD_ERR_OK, $file->getError());
+ }
+
+ public function testGetClientOriginalName()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals('original.gif', $file->getClientOriginalName());
+ }
+
+ public function testGetClientOriginalExtension()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals('gif', $file->getClientOriginalExtension());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\HttpFoundation\File\Exception\FileException
+ */
+ public function testMoveLocalFileIsNotAllowed()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ UPLOAD_ERR_OK
+ );
+
+ $movedFile = $file->move(__DIR__.'/Fixtures/directory');
+ }
+
+ public function testMoveLocalFileIsAllowedInTestMode()
+ {
+ $path = __DIR__.'/Fixtures/test.copy.gif';
+ $targetDir = __DIR__.'/Fixtures/directory';
+ $targetPath = $targetDir.'/test.copy.gif';
+ @unlink($path);
+ @unlink($targetPath);
+ copy(__DIR__.'/Fixtures/test.gif', $path);
+
+ $file = new UploadedFile(
+ $path,
+ 'original.gif',
+ 'image/gif',
+ filesize($path),
+ UPLOAD_ERR_OK,
+ true
+ );
+
+ $movedFile = $file->move(__DIR__.'/Fixtures/directory');
+
+ $this->assertFileExists($targetPath);
+ $this->assertFileNotExists($path);
+ $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
+
+ @unlink($targetPath);
+ }
+
+ public function testGetClientOriginalNameSanitizeFilename()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ '../../original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals('original.gif', $file->getClientOriginalName());
+ }
+
+ public function testGetSize()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ 'image/gif',
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ null
+ );
+
+ $this->assertEquals(filesize(__DIR__.'/Fixtures/test.gif'), $file->getSize());
+
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test',
+ 'original.gif',
+ 'image/gif'
+ );
+
+ $this->assertEquals(filesize(__DIR__.'/Fixtures/test'), $file->getSize());
+ }
+
+ public function testGetExtension()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ null
+ );
+
+ $this->assertEquals('gif', $file->getExtension());
+ }
+
+ public function testIsValid()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ null,
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ UPLOAD_ERR_OK,
+ true
+ );
+
+ $this->assertTrue($file->isValid());
+ }
+
+ /**
+ * @dataProvider uploadedFileErrorProvider
+ */
+ public function testIsInvalidOnUploadError($error)
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ null,
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ $error
+ );
+
+ $this->assertFalse($file->isValid());
+ }
+
+ public function uploadedFileErrorProvider()
+ {
+ return [
+ [UPLOAD_ERR_INI_SIZE],
+ [UPLOAD_ERR_FORM_SIZE],
+ [UPLOAD_ERR_PARTIAL],
+ [UPLOAD_ERR_NO_TMP_DIR],
+ [UPLOAD_ERR_EXTENSION],
+ ];
+ }
+
+ public function testIsInvalidIfNotHttpUpload()
+ {
+ $file = new UploadedFile(
+ __DIR__.'/Fixtures/test.gif',
+ 'original.gif',
+ null,
+ filesize(__DIR__.'/Fixtures/test.gif'),
+ UPLOAD_ERR_OK
+ );
+
+ $this->assertFalse($file->isValid());
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/FileBagTest.php b/vendor/symfony/http-foundation/Tests/FileBagTest.php
new file mode 100755
index 0000000..0b6d660
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/FileBagTest.php
@@ -0,0 +1,175 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+use Symfony\Component\HttpFoundation\FileBag;
+
+/**
+ * FileBagTest.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
+ */
+class FileBagTest extends TestCase
+{
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testFileMustBeAnArrayOrUploadedFile()
+ {
+ new FileBag(['file' => 'foo']);
+ }
+
+ public function testShouldConvertsUploadedFiles()
+ {
+ $tmpFile = $this->createTempFile();
+ $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0);
+
+ $bag = new FileBag(['file' => [
+ 'name' => basename($tmpFile),
+ 'type' => 'text/plain',
+ 'tmp_name' => $tmpFile,
+ 'error' => 0,
+ 'size' => 100,
+ ]]);
+
+ $this->assertEquals($file, $bag->get('file'));
+ }
+
+ public function testShouldSetEmptyUploadedFilesToNull()
+ {
+ $bag = new FileBag(['file' => [
+ 'name' => '',
+ 'type' => '',
+ 'tmp_name' => '',
+ 'error' => UPLOAD_ERR_NO_FILE,
+ 'size' => 0,
+ ]]);
+
+ $this->assertNull($bag->get('file'));
+ }
+
+ public function testShouldRemoveEmptyUploadedFilesForMultiUpload()
+ {
+ $bag = new FileBag(['files' => [
+ 'name' => [''],
+ 'type' => [''],
+ 'tmp_name' => [''],
+ 'error' => [UPLOAD_ERR_NO_FILE],
+ 'size' => [0],
+ ]]);
+
+ $this->assertSame([], $bag->get('files'));
+ }
+
+ public function testShouldNotRemoveEmptyUploadedFilesForAssociativeArray()
+ {
+ $bag = new FileBag(['files' => [
+ 'name' => ['file1' => ''],
+ 'type' => ['file1' => ''],
+ 'tmp_name' => ['file1' => ''],
+ 'error' => ['file1' => UPLOAD_ERR_NO_FILE],
+ 'size' => ['file1' => 0],
+ ]]);
+
+ $this->assertSame(['file1' => null], $bag->get('files'));
+ }
+
+ public function testShouldConvertUploadedFilesWithPhpBug()
+ {
+ $tmpFile = $this->createTempFile();
+ $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0);
+
+ $bag = new FileBag([
+ 'child' => [
+ 'name' => [
+ 'file' => basename($tmpFile),
+ ],
+ 'type' => [
+ 'file' => 'text/plain',
+ ],
+ 'tmp_name' => [
+ 'file' => $tmpFile,
+ ],
+ 'error' => [
+ 'file' => 0,
+ ],
+ 'size' => [
+ 'file' => 100,
+ ],
+ ],
+ ]);
+
+ $files = $bag->all();
+ $this->assertEquals($file, $files['child']['file']);
+ }
+
+ public function testShouldConvertNestedUploadedFilesWithPhpBug()
+ {
+ $tmpFile = $this->createTempFile();
+ $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0);
+
+ $bag = new FileBag([
+ 'child' => [
+ 'name' => [
+ 'sub' => ['file' => basename($tmpFile)],
+ ],
+ 'type' => [
+ 'sub' => ['file' => 'text/plain'],
+ ],
+ 'tmp_name' => [
+ 'sub' => ['file' => $tmpFile],
+ ],
+ 'error' => [
+ 'sub' => ['file' => 0],
+ ],
+ 'size' => [
+ 'sub' => ['file' => 100],
+ ],
+ ],
+ ]);
+
+ $files = $bag->all();
+ $this->assertEquals($file, $files['child']['sub']['file']);
+ }
+
+ public function testShouldNotConvertNestedUploadedFiles()
+ {
+ $tmpFile = $this->createTempFile();
+ $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0);
+ $bag = new FileBag(['image' => ['file' => $file]]);
+
+ $files = $bag->all();
+ $this->assertEquals($file, $files['image']['file']);
+ }
+
+ protected function createTempFile()
+ {
+ return tempnam(sys_get_temp_dir().'/form_test', 'FormTest');
+ }
+
+ protected function setUp()
+ {
+ mkdir(sys_get_temp_dir().'/form_test', 0777, true);
+ }
+
+ protected function tearDown()
+ {
+ foreach (glob(sys_get_temp_dir().'/form_test/*') as $file) {
+ unlink($file);
+ }
+
+ rmdir(sys_get_temp_dir().'/form_test');
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/common.inc b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/common.inc
new file mode 100755
index 0000000..0bdf9e4
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/common.inc
@@ -0,0 +1,43 @@
+<?php
+
+use Symfony\Component\HttpFoundation\Response;
+
+$parent = __DIR__;
+while (!@file_exists($parent.'/vendor/autoload.php')) {
+ if (!@file_exists($parent)) {
+ // open_basedir restriction in effect
+ break;
+ }
+ if ($parent === dirname($parent)) {
+ echo "vendor/autoload.php not found\n";
+ exit(1);
+ }
+
+ $parent = dirname($parent);
+}
+
+require $parent.'/vendor/autoload.php';
+
+error_reporting(-1);
+ini_set('html_errors', 0);
+ini_set('display_errors', 1);
+
+if (filter_var(ini_get('xdebug.default_enable'), FILTER_VALIDATE_BOOLEAN)) {
+ xdebug_disable();
+}
+
+header_remove('X-Powered-By');
+header('Content-Type: text/plain; charset=utf-8');
+
+register_shutdown_function(function () {
+ echo "\n";
+ session_write_close();
+ print_r(headers_list());
+ echo "shutdown\n";
+});
+ob_start();
+
+$r = new Response();
+$r->headers->set('Date', 'Sat, 12 Nov 1955 20:04:00 GMT');
+
+return $r;
diff --git a/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.expected b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.expected
new file mode 100755
index 0000000..bdb9d02
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.expected
@@ -0,0 +1,11 @@
+
+Warning: Expiry date cannot have a year greater than 9999 in %scookie_max_age.php on line 10
+
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: no-cache, private
+ [2] => Date: Sat, 12 Nov 1955 20:04:00 GMT
+ [3] => Set-Cookie: foo=bar; expires=Sat, 01-Jan-10000 02:46:40 GMT; Max-Age=%d; path=/
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.php b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.php
new file mode 100755
index 0000000..8775a5c
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.php
@@ -0,0 +1,10 @@
+<?php
+
+use Symfony\Component\HttpFoundation\Cookie;
+
+$r = require __DIR__.'/common.inc';
+
+$r->headers->setCookie(new Cookie('foo', 'bar', 253402310800, '', null, false, false));
+$r->sendHeaders();
+
+setcookie('foo2', 'bar', 253402310800, '/');
diff --git a/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.expected b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.expected
new file mode 100755
index 0000000..0c09797
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.expected
@@ -0,0 +1,10 @@
+
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: no-cache, private
+ [2] => Date: Sat, 12 Nov 1955 20:04:00 GMT
+ [3] => Set-Cookie: ?*():@&+$/%#[]=?*():@&+$/%#[]; path=/
+ [4] => Set-Cookie: ?*():@&+$/%#[]=?*():@&+$/%#[]; path=/
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.php b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.php
new file mode 100755
index 0000000..2ca5b59
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.php
@@ -0,0 +1,12 @@
+<?php
+
+use Symfony\Component\HttpFoundation\Cookie;
+
+$r = require __DIR__.'/common.inc';
+
+$str = '?*():@&+$/%#[]';
+
+$r->headers->setCookie(new Cookie($str, $str, 0, '/', null, false, false, true));
+$r->sendHeaders();
+
+setrawcookie($str, $str, 0, '/', null, false, false);
diff --git a/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.expected b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.expected
new file mode 100755
index 0000000..cbde2cb
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.expected
@@ -0,0 +1,9 @@
+
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: no-cache, private
+ [2] => Date: Sat, 12 Nov 1955 20:04:00 GMT
+ [3] => Set-Cookie: CookieSamesiteLaxTest=LaxValue; path=/; httponly; samesite=lax
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.php b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.php
new file mode 100755
index 0000000..9a476f1
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.php
@@ -0,0 +1,8 @@
+<?php
+
+use Symfony\Component\HttpFoundation\Cookie;
+
+$r = require __DIR__.'/common.inc';
+
+$r->headers->setCookie(new Cookie('CookieSamesiteLaxTest', 'LaxValue', 0, '/', null, false, true, false, Cookie::SAMESITE_LAX));
+$r->sendHeaders();
diff --git a/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.expected b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.expected
new file mode 100755
index 0000000..adc491f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.expected
@@ -0,0 +1,9 @@
+
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: no-cache, private
+ [2] => Date: Sat, 12 Nov 1955 20:04:00 GMT
+ [3] => Set-Cookie: CookieSamesiteStrictTest=StrictValue; path=/; httponly; samesite=strict
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.php b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.php
new file mode 100755
index 0000000..3bcb41f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.php
@@ -0,0 +1,8 @@
+<?php
+
+use Symfony\Component\HttpFoundation\Cookie;
+
+$r = require __DIR__.'/common.inc';
+
+$r->headers->setCookie(new Cookie('CookieSamesiteStrictTest', 'StrictValue', 0, '/', null, false, true, false, Cookie::SAMESITE_STRICT));
+$r->sendHeaders();
diff --git a/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.expected b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.expected
new file mode 100755
index 0000000..14e44a3
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.expected
@@ -0,0 +1,10 @@
+
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: no-cache, private
+ [2] => Date: Sat, 12 Nov 1955 20:04:00 GMT
+ [3] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/
+ [4] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.php b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.php
new file mode 100755
index 0000000..05b9af3
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.php
@@ -0,0 +1,12 @@
+<?php
+
+use Symfony\Component\HttpFoundation\Cookie;
+
+$r = require __DIR__.'/common.inc';
+
+$str = '?*():@&+$/%#[]';
+
+$r->headers->setCookie(new Cookie($str, $str, 0, '', null, false, false));
+$r->sendHeaders();
+
+setcookie($str, $str, 0, '/');
diff --git a/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.expected b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.expected
new file mode 100755
index 0000000..2b560f0
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.expected
@@ -0,0 +1,6 @@
+The cookie name "Hello + world" contains invalid characters.
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.php b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.php
new file mode 100755
index 0000000..3fe1571
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.php
@@ -0,0 +1,11 @@
+<?php
+
+use Symfony\Component\HttpFoundation\Cookie;
+
+$r = require __DIR__.'/common.inc';
+
+try {
+ $r->headers->setCookie(new Cookie('Hello + world', 'hodor'));
+} catch (\InvalidArgumentException $e) {
+ echo $e->getMessage();
+}
diff --git a/vendor/symfony/http-foundation/Tests/HeaderBagTest.php b/vendor/symfony/http-foundation/Tests/HeaderBagTest.php
new file mode 100755
index 0000000..6c4915f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/HeaderBagTest.php
@@ -0,0 +1,205 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\HeaderBag;
+
+class HeaderBagTest extends TestCase
+{
+ public function testConstructor()
+ {
+ $bag = new HeaderBag(['foo' => 'bar']);
+ $this->assertTrue($bag->has('foo'));
+ }
+
+ public function testToStringNull()
+ {
+ $bag = new HeaderBag();
+ $this->assertEquals('', $bag->__toString());
+ }
+
+ public function testToStringNotNull()
+ {
+ $bag = new HeaderBag(['foo' => 'bar']);
+ $this->assertEquals("Foo: bar\r\n", $bag->__toString());
+ }
+
+ public function testKeys()
+ {
+ $bag = new HeaderBag(['foo' => 'bar']);
+ $keys = $bag->keys();
+ $this->assertEquals('foo', $keys[0]);
+ }
+
+ public function testGetDate()
+ {
+ $bag = new HeaderBag(['foo' => 'Tue, 4 Sep 2012 20:00:00 +0200']);
+ $headerDate = $bag->getDate('foo');
+ $this->assertInstanceOf('DateTime', $headerDate);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testGetDateException()
+ {
+ $bag = new HeaderBag(['foo' => 'Tue']);
+ $headerDate = $bag->getDate('foo');
+ }
+
+ public function testGetCacheControlHeader()
+ {
+ $bag = new HeaderBag();
+ $bag->addCacheControlDirective('public', '#a');
+ $this->assertTrue($bag->hasCacheControlDirective('public'));
+ $this->assertEquals('#a', $bag->getCacheControlDirective('public'));
+ }
+
+ public function testAll()
+ {
+ $bag = new HeaderBag(['foo' => 'bar']);
+ $this->assertEquals(['foo' => ['bar']], $bag->all(), '->all() gets all the input');
+
+ $bag = new HeaderBag(['FOO' => 'BAR']);
+ $this->assertEquals(['foo' => ['BAR']], $bag->all(), '->all() gets all the input key are lower case');
+ }
+
+ public function testReplace()
+ {
+ $bag = new HeaderBag(['foo' => 'bar']);
+
+ $bag->replace(['NOPE' => 'BAR']);
+ $this->assertEquals(['nope' => ['BAR']], $bag->all(), '->replace() replaces the input with the argument');
+ $this->assertFalse($bag->has('foo'), '->replace() overrides previously set the input');
+ }
+
+ public function testGet()
+ {
+ $bag = new HeaderBag(['foo' => 'bar', 'fuzz' => 'bizz']);
+ $this->assertEquals('bar', $bag->get('foo'), '->get return current value');
+ $this->assertEquals('bar', $bag->get('FoO'), '->get key in case insensitive');
+ $this->assertEquals(['bar'], $bag->get('foo', 'nope', false), '->get return the value as array');
+
+ // defaults
+ $this->assertNull($bag->get('none'), '->get unknown values returns null');
+ $this->assertEquals('default', $bag->get('none', 'default'), '->get unknown values returns default');
+ $this->assertEquals(['default'], $bag->get('none', 'default', false), '->get unknown values returns default as array');
+
+ $bag->set('foo', 'bor', false);
+ $this->assertEquals('bar', $bag->get('foo'), '->get return first value');
+ $this->assertEquals(['bar', 'bor'], $bag->get('foo', 'nope', false), '->get return all values as array');
+ }
+
+ public function testSetAssociativeArray()
+ {
+ $bag = new HeaderBag();
+ $bag->set('foo', ['bad-assoc-index' => 'value']);
+ $this->assertSame('value', $bag->get('foo'));
+ $this->assertEquals(['value'], $bag->get('foo', 'nope', false), 'assoc indices of multi-valued headers are ignored');
+ }
+
+ public function testContains()
+ {
+ $bag = new HeaderBag(['foo' => 'bar', 'fuzz' => 'bizz']);
+ $this->assertTrue($bag->contains('foo', 'bar'), '->contains first value');
+ $this->assertTrue($bag->contains('fuzz', 'bizz'), '->contains second value');
+ $this->assertFalse($bag->contains('nope', 'nope'), '->contains unknown value');
+ $this->assertFalse($bag->contains('foo', 'nope'), '->contains unknown value');
+
+ // Multiple values
+ $bag->set('foo', 'bor', false);
+ $this->assertTrue($bag->contains('foo', 'bar'), '->contains first value');
+ $this->assertTrue($bag->contains('foo', 'bor'), '->contains second value');
+ $this->assertFalse($bag->contains('foo', 'nope'), '->contains unknown value');
+ }
+
+ public function testCacheControlDirectiveAccessors()
+ {
+ $bag = new HeaderBag();
+ $bag->addCacheControlDirective('public');
+
+ $this->assertTrue($bag->hasCacheControlDirective('public'));
+ $this->assertTrue($bag->getCacheControlDirective('public'));
+ $this->assertEquals('public', $bag->get('cache-control'));
+
+ $bag->addCacheControlDirective('max-age', 10);
+ $this->assertTrue($bag->hasCacheControlDirective('max-age'));
+ $this->assertEquals(10, $bag->getCacheControlDirective('max-age'));
+ $this->assertEquals('max-age=10, public', $bag->get('cache-control'));
+
+ $bag->removeCacheControlDirective('max-age');
+ $this->assertFalse($bag->hasCacheControlDirective('max-age'));
+ }
+
+ public function testCacheControlDirectiveParsing()
+ {
+ $bag = new HeaderBag(['cache-control' => 'public, max-age=10']);
+ $this->assertTrue($bag->hasCacheControlDirective('public'));
+ $this->assertTrue($bag->getCacheControlDirective('public'));
+
+ $this->assertTrue($bag->hasCacheControlDirective('max-age'));
+ $this->assertEquals(10, $bag->getCacheControlDirective('max-age'));
+
+ $bag->addCacheControlDirective('s-maxage', 100);
+ $this->assertEquals('max-age=10, public, s-maxage=100', $bag->get('cache-control'));
+ }
+
+ public function testCacheControlDirectiveParsingQuotedZero()
+ {
+ $bag = new HeaderBag(['cache-control' => 'max-age="0"']);
+ $this->assertTrue($bag->hasCacheControlDirective('max-age'));
+ $this->assertEquals(0, $bag->getCacheControlDirective('max-age'));
+ }
+
+ public function testCacheControlDirectiveOverrideWithReplace()
+ {
+ $bag = new HeaderBag(['cache-control' => 'private, max-age=100']);
+ $bag->replace(['cache-control' => 'public, max-age=10']);
+ $this->assertTrue($bag->hasCacheControlDirective('public'));
+ $this->assertTrue($bag->getCacheControlDirective('public'));
+
+ $this->assertTrue($bag->hasCacheControlDirective('max-age'));
+ $this->assertEquals(10, $bag->getCacheControlDirective('max-age'));
+ }
+
+ public function testCacheControlClone()
+ {
+ $headers = ['foo' => 'bar'];
+ $bag1 = new HeaderBag($headers);
+ $bag2 = new HeaderBag($bag1->all());
+
+ $this->assertEquals($bag1->all(), $bag2->all());
+ }
+
+ public function testGetIterator()
+ {
+ $headers = ['foo' => 'bar', 'hello' => 'world', 'third' => 'charm'];
+ $headerBag = new HeaderBag($headers);
+
+ $i = 0;
+ foreach ($headerBag as $key => $val) {
+ ++$i;
+ $this->assertEquals([$headers[$key]], $val);
+ }
+
+ $this->assertEquals(\count($headers), $i);
+ }
+
+ public function testCount()
+ {
+ $headers = ['foo' => 'bar', 'HELLO' => 'WORLD'];
+ $headerBag = new HeaderBag($headers);
+
+ $this->assertCount(\count($headers), $headerBag);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/IpUtilsTest.php b/vendor/symfony/http-foundation/Tests/IpUtilsTest.php
new file mode 100755
index 0000000..c7f76b5
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/IpUtilsTest.php
@@ -0,0 +1,104 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\IpUtils;
+
+class IpUtilsTest extends TestCase
+{
+ /**
+ * @dataProvider getIpv4Data
+ */
+ public function testIpv4($matches, $remoteAddr, $cidr)
+ {
+ $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr));
+ }
+
+ public function getIpv4Data()
+ {
+ return [
+ [true, '192.168.1.1', '192.168.1.1'],
+ [true, '192.168.1.1', '192.168.1.1/1'],
+ [true, '192.168.1.1', '192.168.1.0/24'],
+ [false, '192.168.1.1', '1.2.3.4/1'],
+ [false, '192.168.1.1', '192.168.1.1/33'], // invalid subnet
+ [true, '192.168.1.1', ['1.2.3.4/1', '192.168.1.0/24']],
+ [true, '192.168.1.1', ['192.168.1.0/24', '1.2.3.4/1']],
+ [false, '192.168.1.1', ['1.2.3.4/1', '4.3.2.1/1']],
+ [true, '1.2.3.4', '0.0.0.0/0'],
+ [true, '1.2.3.4', '192.168.1.0/0'],
+ [false, '1.2.3.4', '256.256.256/0'], // invalid CIDR notation
+ [false, 'an_invalid_ip', '192.168.1.0/24'],
+ ];
+ }
+
+ /**
+ * @dataProvider getIpv6Data
+ */
+ public function testIpv6($matches, $remoteAddr, $cidr)
+ {
+ if (!\defined('AF_INET6')) {
+ $this->markTestSkipped('Only works when PHP is compiled without the option "disable-ipv6".');
+ }
+
+ $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr));
+ }
+
+ public function getIpv6Data()
+ {
+ return [
+ [true, '2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'],
+ [false, '2a00:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'],
+ [false, '2a01:198:603:0:396e:4789:8e99:890f', '::1'],
+ [true, '0:0:0:0:0:0:0:1', '::1'],
+ [false, '0:0:603:0:396e:4789:8e99:0001', '::1'],
+ [true, '0:0:603:0:396e:4789:8e99:0001', '::/0'],
+ [true, '0:0:603:0:396e:4789:8e99:0001', '2a01:198:603:0::/0'],
+ [true, '2a01:198:603:0:396e:4789:8e99:890f', ['::1', '2a01:198:603:0::/65']],
+ [true, '2a01:198:603:0:396e:4789:8e99:890f', ['2a01:198:603:0::/65', '::1']],
+ [false, '2a01:198:603:0:396e:4789:8e99:890f', ['::1', '1a01:198:603:0::/65']],
+ [false, '}__test|O:21:"JDatabaseDriverMysqli":3:{s:2', '::1'],
+ [false, '2a01:198:603:0:396e:4789:8e99:890f', 'unknown'],
+ ];
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @requires extension sockets
+ */
+ public function testAnIpv6WithOptionDisabledIpv6()
+ {
+ if (\defined('AF_INET6')) {
+ $this->markTestSkipped('Only works when PHP is compiled with the option "disable-ipv6".');
+ }
+
+ IpUtils::checkIp('2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65');
+ }
+
+ /**
+ * @dataProvider invalidIpAddressData
+ */
+ public function testInvalidIpAddressesDoNotMatch($requestIp, $proxyIp)
+ {
+ $this->assertFalse(IpUtils::checkIp4($requestIp, $proxyIp));
+ }
+
+ public function invalidIpAddressData()
+ {
+ return [
+ 'invalid proxy wildcard' => ['192.168.20.13', '*'],
+ 'invalid proxy missing netmask' => ['192.168.20.13', '0.0.0.0'],
+ 'invalid request IP with invalid proxy wildcard' => ['0.0.0.0', '*'],
+ ];
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/JsonResponseTest.php b/vendor/symfony/http-foundation/Tests/JsonResponseTest.php
new file mode 100755
index 0000000..5425896
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/JsonResponseTest.php
@@ -0,0 +1,266 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\JsonResponse;
+
+class JsonResponseTest extends TestCase
+{
+ protected function setUp()
+ {
+ parent::setUp();
+
+ if (!\defined('HHVM_VERSION')) {
+ $this->iniSet('serialize_precision', 14);
+ }
+ }
+
+ public function testConstructorEmptyCreatesJsonObject()
+ {
+ $response = new JsonResponse();
+ $this->assertSame('{}', $response->getContent());
+ }
+
+ public function testConstructorWithArrayCreatesJsonArray()
+ {
+ $response = new JsonResponse([0, 1, 2, 3]);
+ $this->assertSame('[0,1,2,3]', $response->getContent());
+ }
+
+ public function testConstructorWithAssocArrayCreatesJsonObject()
+ {
+ $response = new JsonResponse(['foo' => 'bar']);
+ $this->assertSame('{"foo":"bar"}', $response->getContent());
+ }
+
+ public function testConstructorWithSimpleTypes()
+ {
+ $response = new JsonResponse('foo');
+ $this->assertSame('"foo"', $response->getContent());
+
+ $response = new JsonResponse(0);
+ $this->assertSame('0', $response->getContent());
+
+ $response = new JsonResponse(0.1);
+ $this->assertSame('0.1', $response->getContent());
+
+ $response = new JsonResponse(true);
+ $this->assertSame('true', $response->getContent());
+ }
+
+ public function testConstructorWithCustomStatus()
+ {
+ $response = new JsonResponse([], 202);
+ $this->assertSame(202, $response->getStatusCode());
+ }
+
+ public function testConstructorAddsContentTypeHeader()
+ {
+ $response = new JsonResponse();
+ $this->assertSame('application/json', $response->headers->get('Content-Type'));
+ }
+
+ public function testConstructorWithCustomHeaders()
+ {
+ $response = new JsonResponse([], 200, ['ETag' => 'foo']);
+ $this->assertSame('application/json', $response->headers->get('Content-Type'));
+ $this->assertSame('foo', $response->headers->get('ETag'));
+ }
+
+ public function testConstructorWithCustomContentType()
+ {
+ $headers = ['Content-Type' => 'application/vnd.acme.blog-v1+json'];
+
+ $response = new JsonResponse([], 200, $headers);
+ $this->assertSame('application/vnd.acme.blog-v1+json', $response->headers->get('Content-Type'));
+ }
+
+ public function testSetJson()
+ {
+ $response = new JsonResponse('1', 200, [], true);
+ $this->assertEquals('1', $response->getContent());
+
+ $response = new JsonResponse('[1]', 200, [], true);
+ $this->assertEquals('[1]', $response->getContent());
+
+ $response = new JsonResponse(null, 200, []);
+ $response->setJson('true');
+ $this->assertEquals('true', $response->getContent());
+ }
+
+ public function testCreate()
+ {
+ $response = JsonResponse::create(['foo' => 'bar'], 204);
+
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertEquals('{"foo":"bar"}', $response->getContent());
+ $this->assertEquals(204, $response->getStatusCode());
+ }
+
+ public function testStaticCreateEmptyJsonObject()
+ {
+ $response = JsonResponse::create();
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('{}', $response->getContent());
+ }
+
+ public function testStaticCreateJsonArray()
+ {
+ $response = JsonResponse::create([0, 1, 2, 3]);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('[0,1,2,3]', $response->getContent());
+ }
+
+ public function testStaticCreateJsonObject()
+ {
+ $response = JsonResponse::create(['foo' => 'bar']);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('{"foo":"bar"}', $response->getContent());
+ }
+
+ public function testStaticCreateWithSimpleTypes()
+ {
+ $response = JsonResponse::create('foo');
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('"foo"', $response->getContent());
+
+ $response = JsonResponse::create(0);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('0', $response->getContent());
+
+ $response = JsonResponse::create(0.1);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('0.1', $response->getContent());
+
+ $response = JsonResponse::create(true);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
+ $this->assertSame('true', $response->getContent());
+ }
+
+ public function testStaticCreateWithCustomStatus()
+ {
+ $response = JsonResponse::create([], 202);
+ $this->assertSame(202, $response->getStatusCode());
+ }
+
+ public function testStaticCreateAddsContentTypeHeader()
+ {
+ $response = JsonResponse::create();
+ $this->assertSame('application/json', $response->headers->get('Content-Type'));
+ }
+
+ public function testStaticCreateWithCustomHeaders()
+ {
+ $response = JsonResponse::create([], 200, ['ETag' => 'foo']);
+ $this->assertSame('application/json', $response->headers->get('Content-Type'));
+ $this->assertSame('foo', $response->headers->get('ETag'));
+ }
+
+ public function testStaticCreateWithCustomContentType()
+ {
+ $headers = ['Content-Type' => 'application/vnd.acme.blog-v1+json'];
+
+ $response = JsonResponse::create([], 200, $headers);
+ $this->assertSame('application/vnd.acme.blog-v1+json', $response->headers->get('Content-Type'));
+ }
+
+ public function testSetCallback()
+ {
+ $response = JsonResponse::create(['foo' => 'bar'])->setCallback('callback');
+
+ $this->assertEquals('/**/callback({"foo":"bar"});', $response->getContent());
+ $this->assertEquals('text/javascript', $response->headers->get('Content-Type'));
+ }
+
+ public function testJsonEncodeFlags()
+ {
+ $response = new JsonResponse('<>\'&"');
+
+ $this->assertEquals('"\u003C\u003E\u0027\u0026\u0022"', $response->getContent());
+ }
+
+ public function testGetEncodingOptions()
+ {
+ $response = new JsonResponse();
+
+ $this->assertEquals(JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT, $response->getEncodingOptions());
+ }
+
+ public function testSetEncodingOptions()
+ {
+ $response = new JsonResponse();
+ $response->setData([[1, 2, 3]]);
+
+ $this->assertEquals('[[1,2,3]]', $response->getContent());
+
+ $response->setEncodingOptions(JSON_FORCE_OBJECT);
+
+ $this->assertEquals('{"0":{"0":1,"1":2,"2":3}}', $response->getContent());
+ }
+
+ public function testItAcceptsJsonAsString()
+ {
+ $response = JsonResponse::fromJsonString('{"foo":"bar"}');
+ $this->assertSame('{"foo":"bar"}', $response->getContent());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetCallbackInvalidIdentifier()
+ {
+ $response = new JsonResponse('foo');
+ $response->setCallback('+invalid');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetContent()
+ {
+ JsonResponse::create("\xB1\x31");
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage This error is expected
+ */
+ public function testSetContentJsonSerializeError()
+ {
+ if (!interface_exists('JsonSerializable', false)) {
+ $this->markTestSkipped('JsonSerializable is required.');
+ }
+
+ $serializable = new JsonSerializableObject();
+
+ JsonResponse::create($serializable);
+ }
+
+ public function testSetComplexCallback()
+ {
+ $response = JsonResponse::create(['foo' => 'bar']);
+ $response->setCallback('ಠ_ಠ["foo"].bar[0]');
+
+ $this->assertEquals('/**/ಠ_ಠ["foo"].bar[0]({"foo":"bar"});', $response->getContent());
+ }
+}
+
+if (interface_exists('JsonSerializable', false)) {
+ class JsonSerializableObject implements \JsonSerializable
+ {
+ public function jsonSerialize()
+ {
+ throw new \Exception('This error is expected');
+ }
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/ParameterBagTest.php b/vendor/symfony/http-foundation/Tests/ParameterBagTest.php
new file mode 100755
index 0000000..d2a5c99
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ParameterBagTest.php
@@ -0,0 +1,194 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\ParameterBag;
+
+class ParameterBagTest extends TestCase
+{
+ public function testConstructor()
+ {
+ $this->testAll();
+ }
+
+ public function testAll()
+ {
+ $bag = new ParameterBag(['foo' => 'bar']);
+ $this->assertEquals(['foo' => 'bar'], $bag->all(), '->all() gets all the input');
+ }
+
+ public function testKeys()
+ {
+ $bag = new ParameterBag(['foo' => 'bar']);
+ $this->assertEquals(['foo'], $bag->keys());
+ }
+
+ public function testAdd()
+ {
+ $bag = new ParameterBag(['foo' => 'bar']);
+ $bag->add(['bar' => 'bas']);
+ $this->assertEquals(['foo' => 'bar', 'bar' => 'bas'], $bag->all());
+ }
+
+ public function testRemove()
+ {
+ $bag = new ParameterBag(['foo' => 'bar']);
+ $bag->add(['bar' => 'bas']);
+ $this->assertEquals(['foo' => 'bar', 'bar' => 'bas'], $bag->all());
+ $bag->remove('bar');
+ $this->assertEquals(['foo' => 'bar'], $bag->all());
+ }
+
+ public function testReplace()
+ {
+ $bag = new ParameterBag(['foo' => 'bar']);
+
+ $bag->replace(['FOO' => 'BAR']);
+ $this->assertEquals(['FOO' => 'BAR'], $bag->all(), '->replace() replaces the input with the argument');
+ $this->assertFalse($bag->has('foo'), '->replace() overrides previously set the input');
+ }
+
+ public function testGet()
+ {
+ $bag = new ParameterBag(['foo' => 'bar', 'null' => null]);
+
+ $this->assertEquals('bar', $bag->get('foo'), '->get() gets the value of a parameter');
+ $this->assertEquals('default', $bag->get('unknown', 'default'), '->get() returns second argument as default if a parameter is not defined');
+ $this->assertNull($bag->get('null', 'default'), '->get() returns null if null is set');
+ }
+
+ public function testGetDoesNotUseDeepByDefault()
+ {
+ $bag = new ParameterBag(['foo' => ['bar' => 'moo']]);
+
+ $this->assertNull($bag->get('foo[bar]'));
+ }
+
+ public function testSet()
+ {
+ $bag = new ParameterBag([]);
+
+ $bag->set('foo', 'bar');
+ $this->assertEquals('bar', $bag->get('foo'), '->set() sets the value of parameter');
+
+ $bag->set('foo', 'baz');
+ $this->assertEquals('baz', $bag->get('foo'), '->set() overrides previously set parameter');
+ }
+
+ public function testHas()
+ {
+ $bag = new ParameterBag(['foo' => 'bar']);
+
+ $this->assertTrue($bag->has('foo'), '->has() returns true if a parameter is defined');
+ $this->assertFalse($bag->has('unknown'), '->has() return false if a parameter is not defined');
+ }
+
+ public function testGetAlpha()
+ {
+ $bag = new ParameterBag(['word' => 'foo_BAR_012']);
+
+ $this->assertEquals('fooBAR', $bag->getAlpha('word'), '->getAlpha() gets only alphabetic characters');
+ $this->assertEquals('', $bag->getAlpha('unknown'), '->getAlpha() returns empty string if a parameter is not defined');
+ }
+
+ public function testGetAlnum()
+ {
+ $bag = new ParameterBag(['word' => 'foo_BAR_012']);
+
+ $this->assertEquals('fooBAR012', $bag->getAlnum('word'), '->getAlnum() gets only alphanumeric characters');
+ $this->assertEquals('', $bag->getAlnum('unknown'), '->getAlnum() returns empty string if a parameter is not defined');
+ }
+
+ public function testGetDigits()
+ {
+ $bag = new ParameterBag(['word' => 'foo_BAR_012']);
+
+ $this->assertEquals('012', $bag->getDigits('word'), '->getDigits() gets only digits as string');
+ $this->assertEquals('', $bag->getDigits('unknown'), '->getDigits() returns empty string if a parameter is not defined');
+ }
+
+ public function testGetInt()
+ {
+ $bag = new ParameterBag(['digits' => '0123']);
+
+ $this->assertEquals(123, $bag->getInt('digits'), '->getInt() gets a value of parameter as integer');
+ $this->assertEquals(0, $bag->getInt('unknown'), '->getInt() returns zero if a parameter is not defined');
+ }
+
+ public function testFilter()
+ {
+ $bag = new ParameterBag([
+ 'digits' => '0123ab',
+ 'email' => 'example@example.com',
+ 'url' => 'http://example.com/foo',
+ 'dec' => '256',
+ 'hex' => '0x100',
+ 'array' => ['bang'],
+ ]);
+
+ $this->assertEmpty($bag->filter('nokey'), '->filter() should return empty by default if no key is found');
+
+ $this->assertEquals('0123', $bag->filter('digits', '', FILTER_SANITIZE_NUMBER_INT), '->filter() gets a value of parameter as integer filtering out invalid characters');
+
+ $this->assertEquals('example@example.com', $bag->filter('email', '', FILTER_VALIDATE_EMAIL), '->filter() gets a value of parameter as email');
+
+ $this->assertEquals('http://example.com/foo', $bag->filter('url', '', FILTER_VALIDATE_URL, ['flags' => FILTER_FLAG_PATH_REQUIRED]), '->filter() gets a value of parameter as URL with a path');
+
+ // This test is repeated for code-coverage
+ $this->assertEquals('http://example.com/foo', $bag->filter('url', '', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED), '->filter() gets a value of parameter as URL with a path');
+
+ $this->assertFalse($bag->filter('dec', '', FILTER_VALIDATE_INT, [
+ 'flags' => FILTER_FLAG_ALLOW_HEX,
+ 'options' => ['min_range' => 1, 'max_range' => 0xff],
+ ]), '->filter() gets a value of parameter as integer between boundaries');
+
+ $this->assertFalse($bag->filter('hex', '', FILTER_VALIDATE_INT, [
+ 'flags' => FILTER_FLAG_ALLOW_HEX,
+ 'options' => ['min_range' => 1, 'max_range' => 0xff],
+ ]), '->filter() gets a value of parameter as integer between boundaries');
+
+ $this->assertEquals(['bang'], $bag->filter('array', ''), '->filter() gets a value of parameter as an array');
+ }
+
+ public function testGetIterator()
+ {
+ $parameters = ['foo' => 'bar', 'hello' => 'world'];
+ $bag = new ParameterBag($parameters);
+
+ $i = 0;
+ foreach ($bag as $key => $val) {
+ ++$i;
+ $this->assertEquals($parameters[$key], $val);
+ }
+
+ $this->assertEquals(\count($parameters), $i);
+ }
+
+ public function testCount()
+ {
+ $parameters = ['foo' => 'bar', 'hello' => 'world'];
+ $bag = new ParameterBag($parameters);
+
+ $this->assertCount(\count($parameters), $bag);
+ }
+
+ public function testGetBoolean()
+ {
+ $parameters = ['string_true' => 'true', 'string_false' => 'false'];
+ $bag = new ParameterBag($parameters);
+
+ $this->assertTrue($bag->getBoolean('string_true'), '->getBoolean() gets the string true as boolean true');
+ $this->assertFalse($bag->getBoolean('string_false'), '->getBoolean() gets the string false as boolean false');
+ $this->assertFalse($bag->getBoolean('unknown'), '->getBoolean() returns false if a parameter is not defined');
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php b/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php
new file mode 100755
index 0000000..64c3e73
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php
@@ -0,0 +1,97 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
+class RedirectResponseTest extends TestCase
+{
+ public function testGenerateMetaRedirect()
+ {
+ $response = new RedirectResponse('foo.bar');
+
+ $this->assertEquals(1, preg_match(
+ '#<meta http-equiv="refresh" content="\d+;url=foo\.bar" />#',
+ preg_replace(['/\s+/', '/\'/'], [' ', '"'], $response->getContent())
+ ));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testRedirectResponseConstructorNullUrl()
+ {
+ $response = new RedirectResponse(null);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testRedirectResponseConstructorWrongStatusCode()
+ {
+ $response = new RedirectResponse('foo.bar', 404);
+ }
+
+ public function testGenerateLocationHeader()
+ {
+ $response = new RedirectResponse('foo.bar');
+
+ $this->assertTrue($response->headers->has('Location'));
+ $this->assertEquals('foo.bar', $response->headers->get('Location'));
+ }
+
+ public function testGetTargetUrl()
+ {
+ $response = new RedirectResponse('foo.bar');
+
+ $this->assertEquals('foo.bar', $response->getTargetUrl());
+ }
+
+ public function testSetTargetUrl()
+ {
+ $response = new RedirectResponse('foo.bar');
+ $response->setTargetUrl('baz.beep');
+
+ $this->assertEquals('baz.beep', $response->getTargetUrl());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetTargetUrlNull()
+ {
+ $response = new RedirectResponse('foo.bar');
+ $response->setTargetUrl(null);
+ }
+
+ public function testCreate()
+ {
+ $response = RedirectResponse::create('foo', 301);
+
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
+ $this->assertEquals(301, $response->getStatusCode());
+ }
+
+ public function testCacheHeaders()
+ {
+ $response = new RedirectResponse('foo.bar', 301);
+ $this->assertFalse($response->headers->hasCacheControlDirective('no-cache'));
+
+ $response = new RedirectResponse('foo.bar', 301, ['cache-control' => 'max-age=86400']);
+ $this->assertFalse($response->headers->hasCacheControlDirective('no-cache'));
+ $this->assertTrue($response->headers->hasCacheControlDirective('max-age'));
+
+ $response = new RedirectResponse('foo.bar', 302);
+ $this->assertTrue($response->headers->hasCacheControlDirective('no-cache'));
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php b/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php
new file mode 100755
index 0000000..7fb6925
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php
@@ -0,0 +1,151 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestMatcher;
+
+class RequestMatcherTest extends TestCase
+{
+ /**
+ * @dataProvider getMethodData
+ */
+ public function testMethod($requestMethod, $matcherMethod, $isMatch)
+ {
+ $matcher = new RequestMatcher();
+ $matcher->matchMethod($matcherMethod);
+ $request = Request::create('', $requestMethod);
+ $this->assertSame($isMatch, $matcher->matches($request));
+
+ $matcher = new RequestMatcher(null, null, $matcherMethod);
+ $request = Request::create('', $requestMethod);
+ $this->assertSame($isMatch, $matcher->matches($request));
+ }
+
+ public function getMethodData()
+ {
+ return [
+ ['get', 'get', true],
+ ['get', ['get', 'post'], true],
+ ['get', 'post', false],
+ ['get', 'GET', true],
+ ['get', ['GET', 'POST'], true],
+ ['get', 'POST', false],
+ ];
+ }
+
+ public function testScheme()
+ {
+ $httpRequest = $request = $request = Request::create('');
+ $httpsRequest = $request = $request = Request::create('', 'get', [], [], [], ['HTTPS' => 'on']);
+
+ $matcher = new RequestMatcher();
+ $matcher->matchScheme('https');
+ $this->assertFalse($matcher->matches($httpRequest));
+ $this->assertTrue($matcher->matches($httpsRequest));
+
+ $matcher->matchScheme('http');
+ $this->assertFalse($matcher->matches($httpsRequest));
+ $this->assertTrue($matcher->matches($httpRequest));
+
+ $matcher = new RequestMatcher();
+ $this->assertTrue($matcher->matches($httpsRequest));
+ $this->assertTrue($matcher->matches($httpRequest));
+ }
+
+ /**
+ * @dataProvider getHostData
+ */
+ public function testHost($pattern, $isMatch)
+ {
+ $matcher = new RequestMatcher();
+ $request = Request::create('', 'get', [], [], [], ['HTTP_HOST' => 'foo.example.com']);
+
+ $matcher->matchHost($pattern);
+ $this->assertSame($isMatch, $matcher->matches($request));
+
+ $matcher = new RequestMatcher(null, $pattern);
+ $this->assertSame($isMatch, $matcher->matches($request));
+ }
+
+ public function getHostData()
+ {
+ return [
+ ['.*\.example\.com', true],
+ ['\.example\.com$', true],
+ ['^.*\.example\.com$', true],
+ ['.*\.sensio\.com', false],
+ ['.*\.example\.COM', true],
+ ['\.example\.COM$', true],
+ ['^.*\.example\.COM$', true],
+ ['.*\.sensio\.COM', false],
+ ];
+ }
+
+ public function testPath()
+ {
+ $matcher = new RequestMatcher();
+
+ $request = Request::create('/admin/foo');
+
+ $matcher->matchPath('/admin/.*');
+ $this->assertTrue($matcher->matches($request));
+
+ $matcher->matchPath('/admin');
+ $this->assertTrue($matcher->matches($request));
+
+ $matcher->matchPath('^/admin/.*$');
+ $this->assertTrue($matcher->matches($request));
+
+ $matcher->matchMethod('/blog/.*');
+ $this->assertFalse($matcher->matches($request));
+ }
+
+ public function testPathWithLocaleIsNotSupported()
+ {
+ $matcher = new RequestMatcher();
+ $request = Request::create('/en/login');
+ $request->setLocale('en');
+
+ $matcher->matchPath('^/{_locale}/login$');
+ $this->assertFalse($matcher->matches($request));
+ }
+
+ public function testPathWithEncodedCharacters()
+ {
+ $matcher = new RequestMatcher();
+ $request = Request::create('/admin/fo%20o');
+ $matcher->matchPath('^/admin/fo o*$');
+ $this->assertTrue($matcher->matches($request));
+ }
+
+ public function testAttributes()
+ {
+ $matcher = new RequestMatcher();
+
+ $request = Request::create('/admin/foo');
+ $request->attributes->set('foo', 'foo_bar');
+
+ $matcher->matchAttribute('foo', 'foo_.*');
+ $this->assertTrue($matcher->matches($request));
+
+ $matcher->matchAttribute('foo', 'foo');
+ $this->assertTrue($matcher->matches($request));
+
+ $matcher->matchAttribute('foo', '^foo_bar$');
+ $this->assertTrue($matcher->matches($request));
+
+ $matcher->matchAttribute('foo', 'babar');
+ $this->assertFalse($matcher->matches($request));
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/RequestStackTest.php b/vendor/symfony/http-foundation/Tests/RequestStackTest.php
new file mode 100755
index 0000000..a84fb26
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/RequestStackTest.php
@@ -0,0 +1,70 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+class RequestStackTest extends TestCase
+{
+ public function testGetCurrentRequest()
+ {
+ $requestStack = new RequestStack();
+ $this->assertNull($requestStack->getCurrentRequest());
+
+ $request = Request::create('/foo');
+
+ $requestStack->push($request);
+ $this->assertSame($request, $requestStack->getCurrentRequest());
+
+ $this->assertSame($request, $requestStack->pop());
+ $this->assertNull($requestStack->getCurrentRequest());
+
+ $this->assertNull($requestStack->pop());
+ }
+
+ public function testGetMasterRequest()
+ {
+ $requestStack = new RequestStack();
+ $this->assertNull($requestStack->getMasterRequest());
+
+ $masterRequest = Request::create('/foo');
+ $subRequest = Request::create('/bar');
+
+ $requestStack->push($masterRequest);
+ $requestStack->push($subRequest);
+
+ $this->assertSame($masterRequest, $requestStack->getMasterRequest());
+ }
+
+ public function testGetParentRequest()
+ {
+ $requestStack = new RequestStack();
+ $this->assertNull($requestStack->getParentRequest());
+
+ $masterRequest = Request::create('/foo');
+
+ $requestStack->push($masterRequest);
+ $this->assertNull($requestStack->getParentRequest());
+
+ $firstSubRequest = Request::create('/bar');
+
+ $requestStack->push($firstSubRequest);
+ $this->assertSame($masterRequest, $requestStack->getParentRequest());
+
+ $secondSubRequest = Request::create('/baz');
+
+ $requestStack->push($secondSubRequest);
+ $this->assertSame($firstSubRequest, $requestStack->getParentRequest());
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/RequestTest.php b/vendor/symfony/http-foundation/Tests/RequestTest.php
new file mode 100755
index 0000000..d266e1f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/RequestTest.php
@@ -0,0 +1,2446 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Session\Session;
+use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
+
+class RequestTest extends TestCase
+{
+ protected function tearDown()
+ {
+ Request::setTrustedProxies([], -1);
+ Request::setTrustedHosts([]);
+ }
+
+ public function testInitialize()
+ {
+ $request = new Request();
+
+ $request->initialize(['foo' => 'bar']);
+ $this->assertEquals('bar', $request->query->get('foo'), '->initialize() takes an array of query parameters as its first argument');
+
+ $request->initialize([], ['foo' => 'bar']);
+ $this->assertEquals('bar', $request->request->get('foo'), '->initialize() takes an array of request parameters as its second argument');
+
+ $request->initialize([], [], ['foo' => 'bar']);
+ $this->assertEquals('bar', $request->attributes->get('foo'), '->initialize() takes an array of attributes as its third argument');
+
+ $request->initialize([], [], [], [], [], ['HTTP_FOO' => 'bar']);
+ $this->assertEquals('bar', $request->headers->get('FOO'), '->initialize() takes an array of HTTP headers as its sixth argument');
+ }
+
+ public function testGetLocale()
+ {
+ $request = new Request();
+ $request->setLocale('pl');
+ $locale = $request->getLocale();
+ $this->assertEquals('pl', $locale);
+ }
+
+ public function testGetUser()
+ {
+ $request = Request::create('http://user:password@test.com');
+ $user = $request->getUser();
+
+ $this->assertEquals('user', $user);
+ }
+
+ public function testGetPassword()
+ {
+ $request = Request::create('http://user:password@test.com');
+ $password = $request->getPassword();
+
+ $this->assertEquals('password', $password);
+ }
+
+ public function testIsNoCache()
+ {
+ $request = new Request();
+ $isNoCache = $request->isNoCache();
+
+ $this->assertFalse($isNoCache);
+ }
+
+ public function testGetContentType()
+ {
+ $request = new Request();
+ $contentType = $request->getContentType();
+
+ $this->assertNull($contentType);
+ }
+
+ public function testSetDefaultLocale()
+ {
+ $request = new Request();
+ $request->setDefaultLocale('pl');
+ $locale = $request->getLocale();
+
+ $this->assertEquals('pl', $locale);
+ }
+
+ public function testCreate()
+ {
+ $request = Request::create('http://test.com/foo?bar=baz');
+ $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('bar=baz', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com/foo', 'GET', ['bar' => 'baz']);
+ $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('bar=baz', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com/foo?bar=foo', 'GET', ['bar' => 'baz']);
+ $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('bar=baz', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('https://test.com/foo?bar=baz');
+ $this->assertEquals('https://test.com/foo?bar=baz', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('bar=baz', $request->getQueryString());
+ $this->assertEquals(443, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertTrue($request->isSecure());
+
+ $request = Request::create('test.com:90/foo');
+ $this->assertEquals('http://test.com:90/foo', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('test.com', $request->getHost());
+ $this->assertEquals('test.com:90', $request->getHttpHost());
+ $this->assertEquals(90, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('https://test.com:90/foo');
+ $this->assertEquals('https://test.com:90/foo', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('test.com', $request->getHost());
+ $this->assertEquals('test.com:90', $request->getHttpHost());
+ $this->assertEquals(90, $request->getPort());
+ $this->assertTrue($request->isSecure());
+
+ $request = Request::create('https://127.0.0.1:90/foo');
+ $this->assertEquals('https://127.0.0.1:90/foo', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('127.0.0.1', $request->getHost());
+ $this->assertEquals('127.0.0.1:90', $request->getHttpHost());
+ $this->assertEquals(90, $request->getPort());
+ $this->assertTrue($request->isSecure());
+
+ $request = Request::create('https://[::1]:90/foo');
+ $this->assertEquals('https://[::1]:90/foo', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('[::1]', $request->getHost());
+ $this->assertEquals('[::1]:90', $request->getHttpHost());
+ $this->assertEquals(90, $request->getPort());
+ $this->assertTrue($request->isSecure());
+
+ $request = Request::create('https://[::1]/foo');
+ $this->assertEquals('https://[::1]/foo', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('[::1]', $request->getHost());
+ $this->assertEquals('[::1]', $request->getHttpHost());
+ $this->assertEquals(443, $request->getPort());
+ $this->assertTrue($request->isSecure());
+
+ $json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}';
+ $request = Request::create('http://example.com/jsonrpc', 'POST', [], [], [], [], $json);
+ $this->assertEquals($json, $request->getContent());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com');
+ $this->assertEquals('http://test.com/', $request->getUri());
+ $this->assertEquals('/', $request->getPathInfo());
+ $this->assertEquals('', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com?test=1');
+ $this->assertEquals('http://test.com/?test=1', $request->getUri());
+ $this->assertEquals('/', $request->getPathInfo());
+ $this->assertEquals('test=1', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com:90/?test=1');
+ $this->assertEquals('http://test.com:90/?test=1', $request->getUri());
+ $this->assertEquals('/', $request->getPathInfo());
+ $this->assertEquals('test=1', $request->getQueryString());
+ $this->assertEquals(90, $request->getPort());
+ $this->assertEquals('test.com:90', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://username:password@test.com');
+ $this->assertEquals('http://test.com/', $request->getUri());
+ $this->assertEquals('/', $request->getPathInfo());
+ $this->assertEquals('', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertEquals('username', $request->getUser());
+ $this->assertEquals('password', $request->getPassword());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://username@test.com');
+ $this->assertEquals('http://test.com/', $request->getUri());
+ $this->assertEquals('/', $request->getPathInfo());
+ $this->assertEquals('', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertEquals('username', $request->getUser());
+ $this->assertSame('', $request->getPassword());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com/?foo');
+ $this->assertEquals('/?foo', $request->getRequestUri());
+ $this->assertEquals(['foo' => ''], $request->query->all());
+
+ // assume rewrite rule: (.*) --> app/app.php; app/ is a symlink to a symfony web/ directory
+ $request = Request::create('http://test.com/apparthotel-1234', 'GET', [], [], [],
+ [
+ 'DOCUMENT_ROOT' => '/var/www/www.test.com',
+ 'SCRIPT_FILENAME' => '/var/www/www.test.com/app/app.php',
+ 'SCRIPT_NAME' => '/app/app.php',
+ 'PHP_SELF' => '/app/app.php/apparthotel-1234',
+ ]);
+ $this->assertEquals('http://test.com/apparthotel-1234', $request->getUri());
+ $this->assertEquals('/apparthotel-1234', $request->getPathInfo());
+ $this->assertEquals('', $request->getQueryString());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertFalse($request->isSecure());
+
+ // Fragment should not be included in the URI
+ $request = Request::create('http://test.com/foo#bar');
+ $this->assertEquals('http://test.com/foo', $request->getUri());
+ }
+
+ public function testCreateWithRequestUri()
+ {
+ $request = Request::create('http://test.com:80/foo');
+ $request->server->set('REQUEST_URI', 'http://test.com:80/foo');
+ $this->assertEquals('http://test.com/foo', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('test.com', $request->getHost());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com:8080/foo');
+ $request->server->set('REQUEST_URI', 'http://test.com:8080/foo');
+ $this->assertEquals('http://test.com:8080/foo', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('test.com', $request->getHost());
+ $this->assertEquals('test.com:8080', $request->getHttpHost());
+ $this->assertEquals(8080, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('http://test.com/foo?bar=foo', 'GET', ['bar' => 'baz']);
+ $request->server->set('REQUEST_URI', 'http://test.com/foo?bar=foo');
+ $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('bar=baz', $request->getQueryString());
+ $this->assertEquals('test.com', $request->getHost());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ $request = Request::create('https://test.com:443/foo');
+ $request->server->set('REQUEST_URI', 'https://test.com:443/foo');
+ $this->assertEquals('https://test.com/foo', $request->getUri());
+ $this->assertEquals('/foo', $request->getPathInfo());
+ $this->assertEquals('test.com', $request->getHost());
+ $this->assertEquals('test.com', $request->getHttpHost());
+ $this->assertEquals(443, $request->getPort());
+ $this->assertTrue($request->isSecure());
+
+ // Fragment should not be included in the URI
+ $request = Request::create('http://test.com/foo#bar');
+ $request->server->set('REQUEST_URI', 'http://test.com/foo#bar');
+ $this->assertEquals('http://test.com/foo', $request->getUri());
+ }
+
+ /**
+ * @dataProvider getRequestUriData
+ */
+ public function testGetRequestUri($serverRequestUri, $expected, $message)
+ {
+ $request = new Request();
+ $request->server->add([
+ 'REQUEST_URI' => $serverRequestUri,
+
+ // For having http://test.com
+ 'SERVER_NAME' => 'test.com',
+ 'SERVER_PORT' => 80,
+ ]);
+
+ $this->assertSame($expected, $request->getRequestUri(), $message);
+ $this->assertSame($expected, $request->server->get('REQUEST_URI'), 'Normalize the request URI.');
+ }
+
+ public function getRequestUriData()
+ {
+ $message = 'Do not modify the path.';
+ yield ['/foo', '/foo', $message];
+ yield ['//bar/foo', '//bar/foo', $message];
+ yield ['///bar/foo', '///bar/foo', $message];
+
+ $message = 'Handle when the scheme, host are on REQUEST_URI.';
+ yield ['http://test.com/foo?bar=baz', '/foo?bar=baz', $message];
+
+ $message = 'Handle when the scheme, host and port are on REQUEST_URI.';
+ yield ['http://test.com:80/foo', '/foo', $message];
+ yield ['https://test.com:8080/foo', '/foo', $message];
+ yield ['https://test.com:443/foo', '/foo', $message];
+
+ $message = 'Fragment should not be included in the URI';
+ yield ['http://test.com/foo#bar', '/foo', $message];
+ yield ['/foo#bar', '/foo', $message];
+ }
+
+ public function testGetRequestUriWithoutRequiredHeader()
+ {
+ $expected = '';
+
+ $request = new Request();
+
+ $message = 'Fallback to empty URI when headers are missing.';
+ $this->assertSame($expected, $request->getRequestUri(), $message);
+ $this->assertSame($expected, $request->server->get('REQUEST_URI'), 'Normalize the request URI.');
+ }
+
+ public function testCreateCheckPrecedence()
+ {
+ // server is used by default
+ $request = Request::create('/', 'DELETE', [], [], [], [
+ 'HTTP_HOST' => 'example.com',
+ 'HTTPS' => 'on',
+ 'SERVER_PORT' => 443,
+ 'PHP_AUTH_USER' => 'fabien',
+ 'PHP_AUTH_PW' => 'pa$$',
+ 'QUERY_STRING' => 'foo=bar',
+ 'CONTENT_TYPE' => 'application/json',
+ ]);
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(443, $request->getPort());
+ $this->assertTrue($request->isSecure());
+ $this->assertEquals('fabien', $request->getUser());
+ $this->assertEquals('pa$$', $request->getPassword());
+ $this->assertEquals('', $request->getQueryString());
+ $this->assertEquals('application/json', $request->headers->get('CONTENT_TYPE'));
+
+ // URI has precedence over server
+ $request = Request::create('http://thomas:pokemon@example.net:8080/?foo=bar', 'GET', [], [], [], [
+ 'HTTP_HOST' => 'example.com',
+ 'HTTPS' => 'on',
+ 'SERVER_PORT' => 443,
+ ]);
+ $this->assertEquals('example.net', $request->getHost());
+ $this->assertEquals(8080, $request->getPort());
+ $this->assertFalse($request->isSecure());
+ $this->assertEquals('thomas', $request->getUser());
+ $this->assertEquals('pokemon', $request->getPassword());
+ $this->assertEquals('foo=bar', $request->getQueryString());
+ }
+
+ public function testDuplicate()
+ {
+ $request = new Request(['foo' => 'bar'], ['foo' => 'bar'], ['foo' => 'bar'], [], [], ['HTTP_FOO' => 'bar']);
+ $dup = $request->duplicate();
+
+ $this->assertEquals($request->query->all(), $dup->query->all(), '->duplicate() duplicates a request an copy the current query parameters');
+ $this->assertEquals($request->request->all(), $dup->request->all(), '->duplicate() duplicates a request an copy the current request parameters');
+ $this->assertEquals($request->attributes->all(), $dup->attributes->all(), '->duplicate() duplicates a request an copy the current attributes');
+ $this->assertEquals($request->headers->all(), $dup->headers->all(), '->duplicate() duplicates a request an copy the current HTTP headers');
+
+ $dup = $request->duplicate(['foo' => 'foobar'], ['foo' => 'foobar'], ['foo' => 'foobar'], [], [], ['HTTP_FOO' => 'foobar']);
+
+ $this->assertEquals(['foo' => 'foobar'], $dup->query->all(), '->duplicate() overrides the query parameters if provided');
+ $this->assertEquals(['foo' => 'foobar'], $dup->request->all(), '->duplicate() overrides the request parameters if provided');
+ $this->assertEquals(['foo' => 'foobar'], $dup->attributes->all(), '->duplicate() overrides the attributes if provided');
+ $this->assertEquals(['foo' => ['foobar']], $dup->headers->all(), '->duplicate() overrides the HTTP header if provided');
+ }
+
+ public function testDuplicateWithFormat()
+ {
+ $request = new Request([], [], ['_format' => 'json']);
+ $dup = $request->duplicate();
+
+ $this->assertEquals('json', $dup->getRequestFormat());
+ $this->assertEquals('json', $dup->attributes->get('_format'));
+
+ $request = new Request();
+ $request->setRequestFormat('xml');
+ $dup = $request->duplicate();
+
+ $this->assertEquals('xml', $dup->getRequestFormat());
+ }
+
+ /**
+ * @dataProvider getFormatToMimeTypeMapProviderWithAdditionalNullFormat
+ */
+ public function testGetFormatFromMimeType($format, $mimeTypes)
+ {
+ $request = new Request();
+ foreach ($mimeTypes as $mime) {
+ $this->assertEquals($format, $request->getFormat($mime));
+ }
+ $request->setFormat($format, $mimeTypes);
+ foreach ($mimeTypes as $mime) {
+ $this->assertEquals($format, $request->getFormat($mime));
+
+ if (null !== $format) {
+ $this->assertEquals($mimeTypes[0], $request->getMimeType($format));
+ }
+ }
+ }
+
+ public function getFormatToMimeTypeMapProviderWithAdditionalNullFormat()
+ {
+ return array_merge(
+ [[null, [null, 'unexistent-mime-type']]],
+ $this->getFormatToMimeTypeMapProvider()
+ );
+ }
+
+ public function testGetFormatFromMimeTypeWithParameters()
+ {
+ $request = new Request();
+ $this->assertEquals('json', $request->getFormat('application/json; charset=utf-8'));
+ $this->assertEquals('json', $request->getFormat('application/json;charset=utf-8'));
+ $this->assertEquals('json', $request->getFormat('application/json ; charset=utf-8'));
+ $this->assertEquals('json', $request->getFormat('application/json ;charset=utf-8'));
+ }
+
+ /**
+ * @dataProvider getFormatToMimeTypeMapProvider
+ */
+ public function testGetMimeTypeFromFormat($format, $mimeTypes)
+ {
+ $request = new Request();
+ $this->assertEquals($mimeTypes[0], $request->getMimeType($format));
+ }
+
+ /**
+ * @dataProvider getFormatToMimeTypeMapProvider
+ */
+ public function testGetMimeTypesFromFormat($format, $mimeTypes)
+ {
+ $this->assertEquals($mimeTypes, Request::getMimeTypes($format));
+ }
+
+ public function testGetMimeTypesFromInexistentFormat()
+ {
+ $request = new Request();
+ $this->assertNull($request->getMimeType('foo'));
+ $this->assertEquals([], Request::getMimeTypes('foo'));
+ }
+
+ public function testGetFormatWithCustomMimeType()
+ {
+ $request = new Request();
+ $request->setFormat('custom', 'application/vnd.foo.api;myversion=2.3');
+ $this->assertEquals('custom', $request->getFormat('application/vnd.foo.api;myversion=2.3'));
+ }
+
+ public function getFormatToMimeTypeMapProvider()
+ {
+ return [
+ ['txt', ['text/plain']],
+ ['js', ['application/javascript', 'application/x-javascript', 'text/javascript']],
+ ['css', ['text/css']],
+ ['json', ['application/json', 'application/x-json']],
+ ['jsonld', ['application/ld+json']],
+ ['xml', ['text/xml', 'application/xml', 'application/x-xml']],
+ ['rdf', ['application/rdf+xml']],
+ ['atom', ['application/atom+xml']],
+ ];
+ }
+
+ public function testGetUri()
+ {
+ $server = [];
+
+ // Standard Request on non default PORT
+ // http://host:8080/index.php/path/info?query=string
+
+ $server['HTTP_HOST'] = 'host:8080';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '8080';
+
+ $server['QUERY_STRING'] = 'query=string';
+ $server['REQUEST_URI'] = '/index.php/path/info?query=string';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['PATH_INFO'] = '/path/info';
+ $server['PATH_TRANSLATED'] = 'redirect:/index.php/path/info';
+ $server['PHP_SELF'] = '/index_dev.php/path/info';
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+
+ $request = new Request();
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('http://host:8080/index.php/path/info?query=string', $request->getUri(), '->getUri() with non default port');
+
+ // Use std port number
+ $server['HTTP_HOST'] = 'host';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('http://host/index.php/path/info?query=string', $request->getUri(), '->getUri() with default port');
+
+ // Without HOST HEADER
+ unset($server['HTTP_HOST']);
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('http://servername/index.php/path/info?query=string', $request->getUri(), '->getUri() with default port without HOST_HEADER');
+
+ // Request with URL REWRITING (hide index.php)
+ // RewriteCond %{REQUEST_FILENAME} !-f
+ // RewriteRule ^(.*)$ index.php [QSA,L]
+ // http://host:8080/path/info?query=string
+ $server = [];
+ $server['HTTP_HOST'] = 'host:8080';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '8080';
+
+ $server['REDIRECT_QUERY_STRING'] = 'query=string';
+ $server['REDIRECT_URL'] = '/path/info';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['QUERY_STRING'] = 'query=string';
+ $server['REQUEST_URI'] = '/path/info?toto=test&1=1';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['PHP_SELF'] = '/index.php';
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('http://host:8080/path/info?query=string', $request->getUri(), '->getUri() with rewrite');
+
+ // Use std port number
+ // http://host/path/info?query=string
+ $server['HTTP_HOST'] = 'host';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('http://host/path/info?query=string', $request->getUri(), '->getUri() with rewrite and default port');
+
+ // Without HOST HEADER
+ unset($server['HTTP_HOST']);
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('http://servername/path/info?query=string', $request->getUri(), '->getUri() with rewrite, default port without HOST_HEADER');
+
+ // With encoded characters
+
+ $server = [
+ 'HTTP_HOST' => 'host:8080',
+ 'SERVER_NAME' => 'servername',
+ 'SERVER_PORT' => '8080',
+ 'QUERY_STRING' => 'query=string',
+ 'REQUEST_URI' => '/ba%20se/index_dev.php/foo%20bar/in+fo?query=string',
+ 'SCRIPT_NAME' => '/ba se/index_dev.php',
+ 'PATH_TRANSLATED' => 'redirect:/index.php/foo bar/in+fo',
+ 'PHP_SELF' => '/ba se/index_dev.php/path/info',
+ 'SCRIPT_FILENAME' => '/some/where/ba se/index_dev.php',
+ ];
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals(
+ 'http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string',
+ $request->getUri()
+ );
+
+ // with user info
+
+ $server['PHP_AUTH_USER'] = 'fabien';
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', $request->getUri());
+
+ $server['PHP_AUTH_PW'] = 'symfony';
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', $request->getUri());
+ }
+
+ public function testGetUriForPath()
+ {
+ $request = Request::create('http://test.com/foo?bar=baz');
+ $this->assertEquals('http://test.com/some/path', $request->getUriForPath('/some/path'));
+
+ $request = Request::create('http://test.com:90/foo?bar=baz');
+ $this->assertEquals('http://test.com:90/some/path', $request->getUriForPath('/some/path'));
+
+ $request = Request::create('https://test.com/foo?bar=baz');
+ $this->assertEquals('https://test.com/some/path', $request->getUriForPath('/some/path'));
+
+ $request = Request::create('https://test.com:90/foo?bar=baz');
+ $this->assertEquals('https://test.com:90/some/path', $request->getUriForPath('/some/path'));
+
+ $server = [];
+
+ // Standard Request on non default PORT
+ // http://host:8080/index.php/path/info?query=string
+
+ $server['HTTP_HOST'] = 'host:8080';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '8080';
+
+ $server['QUERY_STRING'] = 'query=string';
+ $server['REQUEST_URI'] = '/index.php/path/info?query=string';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['PATH_INFO'] = '/path/info';
+ $server['PATH_TRANSLATED'] = 'redirect:/index.php/path/info';
+ $server['PHP_SELF'] = '/index_dev.php/path/info';
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+
+ $request = new Request();
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('http://host:8080/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with non default port');
+
+ // Use std port number
+ $server['HTTP_HOST'] = 'host';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('http://host/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with default port');
+
+ // Without HOST HEADER
+ unset($server['HTTP_HOST']);
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('http://servername/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with default port without HOST_HEADER');
+
+ // Request with URL REWRITING (hide index.php)
+ // RewriteCond %{REQUEST_FILENAME} !-f
+ // RewriteRule ^(.*)$ index.php [QSA,L]
+ // http://host:8080/path/info?query=string
+ $server = [];
+ $server['HTTP_HOST'] = 'host:8080';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '8080';
+
+ $server['REDIRECT_QUERY_STRING'] = 'query=string';
+ $server['REDIRECT_URL'] = '/path/info';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['QUERY_STRING'] = 'query=string';
+ $server['REQUEST_URI'] = '/path/info?toto=test&1=1';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['PHP_SELF'] = '/index.php';
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('http://host:8080/some/path', $request->getUriForPath('/some/path'), '->getUri() with rewrite');
+
+ // Use std port number
+ // http://host/path/info?query=string
+ $server['HTTP_HOST'] = 'host';
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('http://host/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite and default port');
+
+ // Without HOST HEADER
+ unset($server['HTTP_HOST']);
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '80';
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite, default port without HOST_HEADER');
+ $this->assertEquals('servername', $request->getHttpHost());
+
+ // with user info
+
+ $server['PHP_AUTH_USER'] = 'fabien';
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path'));
+
+ $server['PHP_AUTH_PW'] = 'symfony';
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path'));
+ }
+
+ /**
+ * @dataProvider getRelativeUriForPathData()
+ */
+ public function testGetRelativeUriForPath($expected, $pathinfo, $path)
+ {
+ $this->assertEquals($expected, Request::create($pathinfo)->getRelativeUriForPath($path));
+ }
+
+ public function getRelativeUriForPathData()
+ {
+ return [
+ ['me.png', '/foo', '/me.png'],
+ ['../me.png', '/foo/bar', '/me.png'],
+ ['me.png', '/foo/bar', '/foo/me.png'],
+ ['../baz/me.png', '/foo/bar/b', '/foo/baz/me.png'],
+ ['../../fooz/baz/me.png', '/foo/bar/b', '/fooz/baz/me.png'],
+ ['baz/me.png', '/foo/bar/b', 'baz/me.png'],
+ ];
+ }
+
+ public function testGetUserInfo()
+ {
+ $request = new Request();
+
+ $server = ['PHP_AUTH_USER' => 'fabien'];
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('fabien', $request->getUserInfo());
+
+ $server['PHP_AUTH_USER'] = '0';
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('0', $request->getUserInfo());
+
+ $server['PHP_AUTH_PW'] = '0';
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('0:0', $request->getUserInfo());
+ }
+
+ public function testGetSchemeAndHttpHost()
+ {
+ $request = new Request();
+
+ $server = [];
+ $server['SERVER_NAME'] = 'servername';
+ $server['SERVER_PORT'] = '90';
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost());
+
+ $server['PHP_AUTH_USER'] = 'fabien';
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost());
+
+ $server['PHP_AUTH_USER'] = '0';
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost());
+
+ $server['PHP_AUTH_PW'] = '0';
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost());
+ }
+
+ /**
+ * @dataProvider getQueryStringNormalizationData
+ */
+ public function testGetQueryString($query, $expectedQuery, $msg)
+ {
+ $request = new Request();
+
+ $request->server->set('QUERY_STRING', $query);
+ $this->assertSame($expectedQuery, $request->getQueryString(), $msg);
+ }
+
+ public function getQueryStringNormalizationData()
+ {
+ return [
+ ['foo', 'foo', 'works with valueless parameters'],
+ ['foo=', 'foo=', 'includes a dangling equal sign'],
+ ['bar=&foo=bar', 'bar=&foo=bar', '->works with empty parameters'],
+ ['foo=bar&bar=', 'bar=&foo=bar', 'sorts keys alphabetically'],
+
+ // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
+ // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str.
+ ['baz=Foo%20Baz&bar=Foo+Bar', 'bar=Foo%20Bar&baz=Foo%20Baz', 'normalizes spaces in both encodings "%20" and "+"'],
+
+ ['foo[]=1&foo[]=2', 'foo%5B%5D=1&foo%5B%5D=2', 'allows array notation'],
+ ['foo=1&foo=2', 'foo=1&foo=2', 'allows repeated parameters'],
+ ['pa%3Dram=foo%26bar%3Dbaz&test=test', 'pa%3Dram=foo%26bar%3Dbaz&test=test', 'works with encoded delimiters'],
+ ['0', '0', 'allows "0"'],
+ ['Foo Bar&Foo%20Baz', 'Foo%20Bar&Foo%20Baz', 'normalizes encoding in keys'],
+ ['bar=Foo Bar&baz=Foo%20Baz', 'bar=Foo%20Bar&baz=Foo%20Baz', 'normalizes encoding in values'],
+ ['foo=bar&&&test&&', 'foo=bar&test', 'removes unneeded delimiters'],
+ ['formula=e=m*c^2', 'formula=e%3Dm%2Ac%5E2', 'correctly treats only the first "=" as delimiter and the next as value'],
+
+ // Ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
+ // PHP also does not include them when building _GET.
+ ['foo=bar&=a=b&=x=y', 'foo=bar', 'removes params with empty key'],
+ ];
+ }
+
+ public function testGetQueryStringReturnsNull()
+ {
+ $request = new Request();
+
+ $this->assertNull($request->getQueryString(), '->getQueryString() returns null for non-existent query string');
+
+ $request->server->set('QUERY_STRING', '');
+ $this->assertNull($request->getQueryString(), '->getQueryString() returns null for empty query string');
+ }
+
+ public function testGetHost()
+ {
+ $request = new Request();
+
+ $request->initialize(['foo' => 'bar']);
+ $this->assertEquals('', $request->getHost(), '->getHost() return empty string if not initialized');
+
+ $request->initialize([], [], [], [], [], ['HTTP_HOST' => 'www.example.com']);
+ $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from Host Header');
+
+ // Host header with port number
+ $request->initialize([], [], [], [], [], ['HTTP_HOST' => 'www.example.com:8080']);
+ $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from Host Header with port number');
+
+ // Server values
+ $request->initialize([], [], [], [], [], ['SERVER_NAME' => 'www.example.com']);
+ $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from server name');
+
+ $request->initialize([], [], [], [], [], ['SERVER_NAME' => 'www.example.com', 'HTTP_HOST' => 'www.host.com']);
+ $this->assertEquals('www.host.com', $request->getHost(), '->getHost() value from Host header has priority over SERVER_NAME ');
+ }
+
+ public function testGetPort()
+ {
+ $request = Request::create('http://example.com', 'GET', [], [], [], [
+ 'HTTP_X_FORWARDED_PROTO' => 'https',
+ 'HTTP_X_FORWARDED_PORT' => '443',
+ ]);
+ $port = $request->getPort();
+
+ $this->assertEquals(80, $port, 'Without trusted proxies FORWARDED_PROTO and FORWARDED_PORT are ignored.');
+
+ Request::setTrustedProxies(['1.1.1.1'], Request::HEADER_X_FORWARDED_ALL);
+ $request = Request::create('http://example.com', 'GET', [], [], [], [
+ 'HTTP_X_FORWARDED_PROTO' => 'https',
+ 'HTTP_X_FORWARDED_PORT' => '8443',
+ ]);
+ $this->assertEquals(80, $request->getPort(), 'With PROTO and PORT on untrusted connection server value takes precedence.');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $this->assertEquals(8443, $request->getPort(), 'With PROTO and PORT set PORT takes precedence.');
+
+ $request = Request::create('http://example.com', 'GET', [], [], [], [
+ 'HTTP_X_FORWARDED_PROTO' => 'https',
+ ]);
+ $this->assertEquals(80, $request->getPort(), 'With only PROTO set getPort() ignores trusted headers on untrusted connection.');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $this->assertEquals(443, $request->getPort(), 'With only PROTO set getPort() defaults to 443.');
+
+ $request = Request::create('http://example.com', 'GET', [], [], [], [
+ 'HTTP_X_FORWARDED_PROTO' => 'http',
+ ]);
+ $this->assertEquals(80, $request->getPort(), 'If X_FORWARDED_PROTO is set to HTTP getPort() ignores trusted headers on untrusted connection.');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $this->assertEquals(80, $request->getPort(), 'If X_FORWARDED_PROTO is set to HTTP getPort() returns port of the original request.');
+
+ $request = Request::create('http://example.com', 'GET', [], [], [], [
+ 'HTTP_X_FORWARDED_PROTO' => 'On',
+ ]);
+ $this->assertEquals(80, $request->getPort(), 'With only PROTO set and value is On, getPort() ignores trusted headers on untrusted connection.');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $this->assertEquals(443, $request->getPort(), 'With only PROTO set and value is On, getPort() defaults to 443.');
+
+ $request = Request::create('http://example.com', 'GET', [], [], [], [
+ 'HTTP_X_FORWARDED_PROTO' => '1',
+ ]);
+ $this->assertEquals(80, $request->getPort(), 'With only PROTO set and value is 1, getPort() ignores trusted headers on untrusted connection.');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $this->assertEquals(443, $request->getPort(), 'With only PROTO set and value is 1, getPort() defaults to 443.');
+
+ $request = Request::create('http://example.com', 'GET', [], [], [], [
+ 'HTTP_X_FORWARDED_PROTO' => 'something-else',
+ ]);
+ $port = $request->getPort();
+ $this->assertEquals(80, $port, 'With only PROTO set and value is not recognized, getPort() defaults to 80.');
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testGetHostWithFakeHttpHostValue()
+ {
+ $request = new Request();
+ $request->initialize([], [], [], [], [], ['HTTP_HOST' => 'www.host.com?query=string']);
+ $request->getHost();
+ }
+
+ public function testGetSetMethod()
+ {
+ $request = new Request();
+
+ $this->assertEquals('GET', $request->getMethod(), '->getMethod() returns GET if no method is defined');
+
+ $request->setMethod('get');
+ $this->assertEquals('GET', $request->getMethod(), '->getMethod() returns an uppercased string');
+
+ $request->setMethod('PURGE');
+ $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method even if it is not a standard one');
+
+ $request->setMethod('POST');
+ $this->assertEquals('POST', $request->getMethod(), '->getMethod() returns the method POST if no _method is defined');
+
+ $request->setMethod('POST');
+ $request->request->set('_method', 'purge');
+ $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled');
+
+ $request = new Request();
+ $request->setMethod('POST');
+ $request->request->set('_method', 'purge');
+
+ $this->assertFalse(Request::getHttpMethodParameterOverride(), 'httpMethodParameterOverride should be disabled by default');
+
+ Request::enableHttpMethodParameterOverride();
+
+ $this->assertTrue(Request::getHttpMethodParameterOverride(), 'httpMethodParameterOverride should be enabled now but it is not');
+
+ $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST');
+ $this->disableHttpMethodParameterOverride();
+
+ $request = new Request();
+ $request->setMethod('POST');
+ $request->query->set('_method', 'purge');
+ $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled');
+
+ $request = new Request();
+ $request->setMethod('POST');
+ $request->query->set('_method', 'purge');
+ Request::enableHttpMethodParameterOverride();
+ $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST');
+ $this->disableHttpMethodParameterOverride();
+
+ $request = new Request();
+ $request->setMethod('POST');
+ $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete');
+ $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override even though _method is set if defined and POST');
+
+ $request = new Request();
+ $request->setMethod('POST');
+ $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete');
+ $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override if defined and POST');
+
+ $request = new Request();
+ $request->setMethod('POST');
+ $request->query->set('_method', ['delete', 'patch']);
+ $this->assertSame('POST', $request->getMethod(), '->getMethod() returns the request method if invalid type is defined in query');
+ }
+
+ /**
+ * @dataProvider getClientIpsProvider
+ */
+ public function testGetClientIp($expected, $remoteAddr, $httpForwardedFor, $trustedProxies)
+ {
+ $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies);
+
+ $this->assertEquals($expected[0], $request->getClientIp());
+ }
+
+ /**
+ * @dataProvider getClientIpsProvider
+ */
+ public function testGetClientIps($expected, $remoteAddr, $httpForwardedFor, $trustedProxies)
+ {
+ $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies);
+
+ $this->assertEquals($expected, $request->getClientIps());
+ }
+
+ /**
+ * @dataProvider getClientIpsForwardedProvider
+ */
+ public function testGetClientIpsForwarded($expected, $remoteAddr, $httpForwarded, $trustedProxies)
+ {
+ $request = $this->getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies);
+
+ $this->assertEquals($expected, $request->getClientIps());
+ }
+
+ public function getClientIpsForwardedProvider()
+ {
+ // $expected $remoteAddr $httpForwarded $trustedProxies
+ return [
+ [['127.0.0.1'], '127.0.0.1', 'for="_gazonk"', null],
+ [['127.0.0.1'], '127.0.0.1', 'for="_gazonk"', ['127.0.0.1']],
+ [['88.88.88.88'], '127.0.0.1', 'for="88.88.88.88:80"', ['127.0.0.1']],
+ [['192.0.2.60'], '::1', 'for=192.0.2.60;proto=http;by=203.0.113.43', ['::1']],
+ [['2620:0:1cfe:face:b00c::3', '192.0.2.43'], '::1', 'for=192.0.2.43, for=2620:0:1cfe:face:b00c::3', ['::1']],
+ [['2001:db8:cafe::17'], '::1', 'for="[2001:db8:cafe::17]:4711', ['::1']],
+ ];
+ }
+
+ public function getClientIpsProvider()
+ {
+ // $expected $remoteAddr $httpForwardedFor $trustedProxies
+ return [
+ // simple IPv4
+ [['88.88.88.88'], '88.88.88.88', null, null],
+ // trust the IPv4 remote addr
+ [['88.88.88.88'], '88.88.88.88', null, ['88.88.88.88']],
+
+ // simple IPv6
+ [['::1'], '::1', null, null],
+ // trust the IPv6 remote addr
+ [['::1'], '::1', null, ['::1']],
+
+ // forwarded for with remote IPv4 addr not trusted
+ [['127.0.0.1'], '127.0.0.1', '88.88.88.88', null],
+ // forwarded for with remote IPv4 addr trusted + comma
+ [['88.88.88.88'], '127.0.0.1', '88.88.88.88,', ['127.0.0.1']],
+ // forwarded for with remote IPv4 and all FF addrs trusted
+ [['88.88.88.88'], '127.0.0.1', '88.88.88.88', ['127.0.0.1', '88.88.88.88']],
+ // forwarded for with remote IPv4 range trusted
+ [['88.88.88.88'], '123.45.67.89', '88.88.88.88', ['123.45.67.0/24']],
+
+ // forwarded for with remote IPv6 addr not trusted
+ [['1620:0:1cfe:face:b00c::3'], '1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3', null],
+ // forwarded for with remote IPv6 addr trusted
+ [['2620:0:1cfe:face:b00c::3'], '1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3', ['1620:0:1cfe:face:b00c::3']],
+ // forwarded for with remote IPv6 range trusted
+ [['88.88.88.88'], '2a01:198:603:0:396e:4789:8e99:890f', '88.88.88.88', ['2a01:198:603:0::/65']],
+
+ // multiple forwarded for with remote IPv4 addr trusted
+ [['88.88.88.88', '87.65.43.21', '127.0.0.1'], '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', ['123.45.67.89']],
+ // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted
+ [['87.65.43.21', '127.0.0.1'], '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', ['123.45.67.89', '88.88.88.88']],
+ // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted but in the middle
+ [['88.88.88.88', '127.0.0.1'], '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', ['123.45.67.89', '87.65.43.21']],
+ // multiple forwarded for with remote IPv4 addr and all reverse proxies trusted
+ [['127.0.0.1'], '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', ['123.45.67.89', '87.65.43.21', '88.88.88.88', '127.0.0.1']],
+
+ // multiple forwarded for with remote IPv6 addr trusted
+ [['2620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3'], '1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', ['1620:0:1cfe:face:b00c::3']],
+ // multiple forwarded for with remote IPv6 addr and some reverse proxies trusted
+ [['3620:0:1cfe:face:b00c::3'], '1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', ['1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3']],
+ // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted but in the middle
+ [['2620:0:1cfe:face:b00c::3', '4620:0:1cfe:face:b00c::3'], '1620:0:1cfe:face:b00c::3', '4620:0:1cfe:face:b00c::3,3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', ['1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3']],
+
+ // client IP with port
+ [['88.88.88.88'], '127.0.0.1', '88.88.88.88:12345, 127.0.0.1', ['127.0.0.1']],
+
+ // invalid forwarded IP is ignored
+ [['88.88.88.88'], '127.0.0.1', 'unknown,88.88.88.88', ['127.0.0.1']],
+ [['88.88.88.88'], '127.0.0.1', '}__test|O:21:"JDatabaseDriverMysqli":3:{s:2,88.88.88.88', ['127.0.0.1']],
+ ];
+ }
+
+ /**
+ * @expectedException \Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException
+ * @dataProvider getClientIpsWithConflictingHeadersProvider
+ */
+ public function testGetClientIpsWithConflictingHeaders($httpForwarded, $httpXForwardedFor)
+ {
+ $request = new Request();
+
+ $server = [
+ 'REMOTE_ADDR' => '88.88.88.88',
+ 'HTTP_FORWARDED' => $httpForwarded,
+ 'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor,
+ ];
+
+ Request::setTrustedProxies(['88.88.88.88'], Request::HEADER_X_FORWARDED_ALL | Request::HEADER_FORWARDED);
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $request->getClientIps();
+ }
+
+ /**
+ * @dataProvider getClientIpsWithConflictingHeadersProvider
+ */
+ public function testGetClientIpsOnlyXHttpForwardedForTrusted($httpForwarded, $httpXForwardedFor)
+ {
+ $request = new Request();
+
+ $server = [
+ 'REMOTE_ADDR' => '88.88.88.88',
+ 'HTTP_FORWARDED' => $httpForwarded,
+ 'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor,
+ ];
+
+ Request::setTrustedProxies(['88.88.88.88'], Request::HEADER_X_FORWARDED_FOR);
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertSame(array_reverse(explode(',', $httpXForwardedFor)), $request->getClientIps());
+ }
+
+ public function getClientIpsWithConflictingHeadersProvider()
+ {
+ // $httpForwarded $httpXForwardedFor
+ return [
+ ['for=87.65.43.21', '192.0.2.60'],
+ ['for=87.65.43.21, for=192.0.2.60', '192.0.2.60'],
+ ['for=192.0.2.60', '192.0.2.60,87.65.43.21'],
+ ['for="::face", for=192.0.2.60', '192.0.2.60,192.0.2.43'],
+ ['for=87.65.43.21, for=192.0.2.60', '192.0.2.60,87.65.43.21'],
+ ];
+ }
+
+ /**
+ * @dataProvider getClientIpsWithAgreeingHeadersProvider
+ */
+ public function testGetClientIpsWithAgreeingHeaders($httpForwarded, $httpXForwardedFor, $expectedIps)
+ {
+ $request = new Request();
+
+ $server = [
+ 'REMOTE_ADDR' => '88.88.88.88',
+ 'HTTP_FORWARDED' => $httpForwarded,
+ 'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor,
+ ];
+
+ Request::setTrustedProxies(['88.88.88.88'], -1);
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $clientIps = $request->getClientIps();
+
+ $this->assertSame($expectedIps, $clientIps);
+ }
+
+ public function getClientIpsWithAgreeingHeadersProvider()
+ {
+ // $httpForwarded $httpXForwardedFor
+ return [
+ ['for="192.0.2.60"', '192.0.2.60', ['192.0.2.60']],
+ ['for=192.0.2.60, for=87.65.43.21', '192.0.2.60,87.65.43.21', ['87.65.43.21', '192.0.2.60']],
+ ['for="[::face]", for=192.0.2.60', '::face,192.0.2.60', ['192.0.2.60', '::face']],
+ ['for="192.0.2.60:80"', '192.0.2.60', ['192.0.2.60']],
+ ['for=192.0.2.60;proto=http;by=203.0.113.43', '192.0.2.60', ['192.0.2.60']],
+ ['for="[2001:db8:cafe::17]:4711"', '2001:db8:cafe::17', ['2001:db8:cafe::17']],
+ ];
+ }
+
+ public function testGetContentWorksTwiceInDefaultMode()
+ {
+ $req = new Request();
+ $this->assertEquals('', $req->getContent());
+ $this->assertEquals('', $req->getContent());
+ }
+
+ public function testGetContentReturnsResource()
+ {
+ $req = new Request();
+ $retval = $req->getContent(true);
+ $this->assertInternalType('resource', $retval);
+ $this->assertEquals('', fread($retval, 1));
+ $this->assertTrue(feof($retval));
+ }
+
+ public function testGetContentReturnsResourceWhenContentSetInConstructor()
+ {
+ $req = new Request([], [], [], [], [], [], 'MyContent');
+ $resource = $req->getContent(true);
+
+ $this->assertInternalType('resource', $resource);
+ $this->assertEquals('MyContent', stream_get_contents($resource));
+ }
+
+ public function testContentAsResource()
+ {
+ $resource = fopen('php://memory', 'r+');
+ fwrite($resource, 'My other content');
+ rewind($resource);
+
+ $req = new Request([], [], [], [], [], [], $resource);
+ $this->assertEquals('My other content', stream_get_contents($req->getContent(true)));
+ $this->assertEquals('My other content', $req->getContent());
+ }
+
+ /**
+ * @expectedException \LogicException
+ * @dataProvider getContentCantBeCalledTwiceWithResourcesProvider
+ */
+ public function testGetContentCantBeCalledTwiceWithResources($first, $second)
+ {
+ if (\PHP_VERSION_ID >= 50600) {
+ $this->markTestSkipped('PHP >= 5.6 allows to open php://input several times.');
+ }
+
+ $req = new Request();
+ $req->getContent($first);
+ $req->getContent($second);
+ }
+
+ public function getContentCantBeCalledTwiceWithResourcesProvider()
+ {
+ return [
+ 'Resource then fetch' => [true, false],
+ 'Resource then resource' => [true, true],
+ ];
+ }
+
+ /**
+ * @dataProvider getContentCanBeCalledTwiceWithResourcesProvider
+ * @requires PHP 5.6
+ */
+ public function testGetContentCanBeCalledTwiceWithResources($first, $second)
+ {
+ $req = new Request();
+ $a = $req->getContent($first);
+ $b = $req->getContent($second);
+
+ if ($first) {
+ $a = stream_get_contents($a);
+ }
+
+ if ($second) {
+ $b = stream_get_contents($b);
+ }
+
+ $this->assertSame($a, $b);
+ }
+
+ public function getContentCanBeCalledTwiceWithResourcesProvider()
+ {
+ return [
+ 'Fetch then fetch' => [false, false],
+ 'Fetch then resource' => [false, true],
+ 'Resource then fetch' => [true, false],
+ 'Resource then resource' => [true, true],
+ ];
+ }
+
+ public function provideOverloadedMethods()
+ {
+ return [
+ ['PUT'],
+ ['DELETE'],
+ ['PATCH'],
+ ['put'],
+ ['delete'],
+ ['patch'],
+ ];
+ }
+
+ /**
+ * @dataProvider provideOverloadedMethods
+ */
+ public function testCreateFromGlobals($method)
+ {
+ $normalizedMethod = strtoupper($method);
+
+ $_GET['foo1'] = 'bar1';
+ $_POST['foo2'] = 'bar2';
+ $_COOKIE['foo3'] = 'bar3';
+ $_FILES['foo4'] = ['bar4'];
+ $_SERVER['foo5'] = 'bar5';
+
+ $request = Request::createFromGlobals();
+ $this->assertEquals('bar1', $request->query->get('foo1'), '::fromGlobals() uses values from $_GET');
+ $this->assertEquals('bar2', $request->request->get('foo2'), '::fromGlobals() uses values from $_POST');
+ $this->assertEquals('bar3', $request->cookies->get('foo3'), '::fromGlobals() uses values from $_COOKIE');
+ $this->assertEquals(['bar4'], $request->files->get('foo4'), '::fromGlobals() uses values from $_FILES');
+ $this->assertEquals('bar5', $request->server->get('foo5'), '::fromGlobals() uses values from $_SERVER');
+
+ unset($_GET['foo1'], $_POST['foo2'], $_COOKIE['foo3'], $_FILES['foo4'], $_SERVER['foo5']);
+
+ $_SERVER['REQUEST_METHOD'] = $method;
+ $_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
+ $request = RequestContentProxy::createFromGlobals();
+ $this->assertEquals($normalizedMethod, $request->getMethod());
+ $this->assertEquals('mycontent', $request->request->get('content'));
+
+ unset($_SERVER['REQUEST_METHOD'], $_SERVER['CONTENT_TYPE']);
+
+ Request::createFromGlobals();
+ Request::enableHttpMethodParameterOverride();
+ $_POST['_method'] = $method;
+ $_POST['foo6'] = 'bar6';
+ $_SERVER['REQUEST_METHOD'] = 'PoSt';
+ $request = Request::createFromGlobals();
+ $this->assertEquals($normalizedMethod, $request->getMethod());
+ $this->assertEquals('POST', $request->getRealMethod());
+ $this->assertEquals('bar6', $request->request->get('foo6'));
+
+ unset($_POST['_method'], $_POST['foo6'], $_SERVER['REQUEST_METHOD']);
+ $this->disableHttpMethodParameterOverride();
+ }
+
+ public function testOverrideGlobals()
+ {
+ $request = new Request();
+ $request->initialize(['foo' => 'bar']);
+
+ // as the Request::overrideGlobals really work, it erase $_SERVER, so we must backup it
+ $server = $_SERVER;
+
+ $request->overrideGlobals();
+
+ $this->assertEquals(['foo' => 'bar'], $_GET);
+
+ $request->initialize([], ['foo' => 'bar']);
+ $request->overrideGlobals();
+
+ $this->assertEquals(['foo' => 'bar'], $_POST);
+
+ $this->assertArrayNotHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER);
+
+ $request->headers->set('X_FORWARDED_PROTO', 'https');
+
+ Request::setTrustedProxies(['1.1.1.1'], Request::HEADER_X_FORWARDED_ALL);
+ $this->assertFalse($request->isSecure());
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $this->assertTrue($request->isSecure());
+
+ $request->overrideGlobals();
+
+ $this->assertArrayHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER);
+
+ $request->headers->set('CONTENT_TYPE', 'multipart/form-data');
+ $request->headers->set('CONTENT_LENGTH', 12345);
+
+ $request->overrideGlobals();
+
+ $this->assertArrayHasKey('CONTENT_TYPE', $_SERVER);
+ $this->assertArrayHasKey('CONTENT_LENGTH', $_SERVER);
+
+ $request->initialize(['foo' => 'bar', 'baz' => 'foo']);
+ $request->query->remove('baz');
+
+ $request->overrideGlobals();
+
+ $this->assertEquals(['foo' => 'bar'], $_GET);
+ $this->assertEquals('foo=bar', $_SERVER['QUERY_STRING']);
+ $this->assertEquals('foo=bar', $request->server->get('QUERY_STRING'));
+
+ // restore initial $_SERVER array
+ $_SERVER = $server;
+ }
+
+ public function testGetScriptName()
+ {
+ $request = new Request();
+ $this->assertEquals('', $request->getScriptName());
+
+ $server = [];
+ $server['SCRIPT_NAME'] = '/index.php';
+
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('/index.php', $request->getScriptName());
+
+ $server = [];
+ $server['ORIG_SCRIPT_NAME'] = '/frontend.php';
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('/frontend.php', $request->getScriptName());
+
+ $server = [];
+ $server['SCRIPT_NAME'] = '/index.php';
+ $server['ORIG_SCRIPT_NAME'] = '/frontend.php';
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('/index.php', $request->getScriptName());
+ }
+
+ public function testGetBasePath()
+ {
+ $request = new Request();
+ $this->assertEquals('', $request->getBasePath());
+
+ $server = [];
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+ $request->initialize([], [], [], [], [], $server);
+ $this->assertEquals('', $request->getBasePath());
+
+ $server = [];
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+ $server['SCRIPT_NAME'] = '/index.php';
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('', $request->getBasePath());
+
+ $server = [];
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+ $server['PHP_SELF'] = '/index.php';
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('', $request->getBasePath());
+
+ $server = [];
+ $server['SCRIPT_FILENAME'] = '/some/where/index.php';
+ $server['ORIG_SCRIPT_NAME'] = '/index.php';
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('', $request->getBasePath());
+ }
+
+ public function testGetPathInfo()
+ {
+ $request = new Request();
+ $this->assertEquals('/', $request->getPathInfo());
+
+ $server = [];
+ $server['REQUEST_URI'] = '/path/info';
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('/path/info', $request->getPathInfo());
+
+ $server = [];
+ $server['REQUEST_URI'] = '/path%20test/info';
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('/path%20test/info', $request->getPathInfo());
+
+ $server = [];
+ $server['REQUEST_URI'] = '?a=b';
+ $request->initialize([], [], [], [], [], $server);
+
+ $this->assertEquals('/', $request->getPathInfo());
+ }
+
+ public function testGetParameterPrecedence()
+ {
+ $request = new Request();
+ $request->attributes->set('foo', 'attr');
+ $request->query->set('foo', 'query');
+ $request->request->set('foo', 'body');
+
+ $this->assertSame('attr', $request->get('foo'));
+
+ $request->attributes->remove('foo');
+ $this->assertSame('query', $request->get('foo'));
+
+ $request->query->remove('foo');
+ $this->assertSame('body', $request->get('foo'));
+
+ $request->request->remove('foo');
+ $this->assertNull($request->get('foo'));
+ }
+
+ public function testGetPreferredLanguage()
+ {
+ $request = new Request();
+ $this->assertNull($request->getPreferredLanguage());
+ $this->assertNull($request->getPreferredLanguage([]));
+ $this->assertEquals('fr', $request->getPreferredLanguage(['fr']));
+ $this->assertEquals('fr', $request->getPreferredLanguage(['fr', 'en']));
+ $this->assertEquals('en', $request->getPreferredLanguage(['en', 'fr']));
+ $this->assertEquals('fr-ch', $request->getPreferredLanguage(['fr-ch', 'fr-fr']));
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6');
+ $this->assertEquals('en', $request->getPreferredLanguage(['en', 'en-us']));
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6');
+ $this->assertEquals('en', $request->getPreferredLanguage(['fr', 'en']));
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.8');
+ $this->assertEquals('en', $request->getPreferredLanguage(['fr', 'en']));
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.8, fr-fr; q=0.6, fr; q=0.5');
+ $this->assertEquals('en', $request->getPreferredLanguage(['fr', 'en']));
+ }
+
+ public function testIsXmlHttpRequest()
+ {
+ $request = new Request();
+ $this->assertFalse($request->isXmlHttpRequest());
+
+ $request->headers->set('X-Requested-With', 'XMLHttpRequest');
+ $this->assertTrue($request->isXmlHttpRequest());
+
+ $request->headers->remove('X-Requested-With');
+ $this->assertFalse($request->isXmlHttpRequest());
+ }
+
+ /**
+ * @requires extension intl
+ */
+ public function testIntlLocale()
+ {
+ $request = new Request();
+
+ $request->setDefaultLocale('fr');
+ $this->assertEquals('fr', $request->getLocale());
+ $this->assertEquals('fr', \Locale::getDefault());
+
+ $request->setLocale('en');
+ $this->assertEquals('en', $request->getLocale());
+ $this->assertEquals('en', \Locale::getDefault());
+
+ $request->setDefaultLocale('de');
+ $this->assertEquals('en', $request->getLocale());
+ $this->assertEquals('en', \Locale::getDefault());
+ }
+
+ public function testGetCharsets()
+ {
+ $request = new Request();
+ $this->assertEquals([], $request->getCharsets());
+ $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6');
+ $this->assertEquals([], $request->getCharsets()); // testing caching
+
+ $request = new Request();
+ $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6');
+ $this->assertEquals(['ISO-8859-1', 'US-ASCII', 'UTF-8', 'ISO-10646-UCS-2'], $request->getCharsets());
+
+ $request = new Request();
+ $request->headers->set('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7');
+ $this->assertEquals(['ISO-8859-1', 'utf-8', '*'], $request->getCharsets());
+ }
+
+ public function testGetEncodings()
+ {
+ $request = new Request();
+ $this->assertEquals([], $request->getEncodings());
+ $request->headers->set('Accept-Encoding', 'gzip,deflate,sdch');
+ $this->assertEquals([], $request->getEncodings()); // testing caching
+
+ $request = new Request();
+ $request->headers->set('Accept-Encoding', 'gzip,deflate,sdch');
+ $this->assertEquals(['gzip', 'deflate', 'sdch'], $request->getEncodings());
+
+ $request = new Request();
+ $request->headers->set('Accept-Encoding', 'gzip;q=0.4,deflate;q=0.9,compress;q=0.7');
+ $this->assertEquals(['deflate', 'compress', 'gzip'], $request->getEncodings());
+ }
+
+ public function testGetAcceptableContentTypes()
+ {
+ $request = new Request();
+ $this->assertEquals([], $request->getAcceptableContentTypes());
+ $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*');
+ $this->assertEquals([], $request->getAcceptableContentTypes()); // testing caching
+
+ $request = new Request();
+ $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*');
+ $this->assertEquals(['application/vnd.wap.wmlscriptc', 'text/vnd.wap.wml', 'application/vnd.wap.xhtml+xml', 'application/xhtml+xml', 'text/html', 'multipart/mixed', '*/*'], $request->getAcceptableContentTypes());
+ }
+
+ public function testGetLanguages()
+ {
+ $request = new Request();
+ $this->assertEquals([], $request->getLanguages());
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6');
+ $this->assertEquals(['zh', 'en_US', 'en'], $request->getLanguages());
+ $this->assertEquals(['zh', 'en_US', 'en'], $request->getLanguages());
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.6, en; q=0.8');
+ $this->assertEquals(['zh', 'en', 'en_US'], $request->getLanguages()); // Test out of order qvalues
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, en, en-us');
+ $this->assertEquals(['zh', 'en', 'en_US'], $request->getLanguages()); // Test equal weighting without qvalues
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh; q=0.6, en, en-us; q=0.6');
+ $this->assertEquals(['en', 'zh', 'en_US'], $request->getLanguages()); // Test equal weighting with qvalues
+
+ $request = new Request();
+ $request->headers->set('Accept-language', 'zh, i-cherokee; q=0.6');
+ $this->assertEquals(['zh', 'cherokee'], $request->getLanguages());
+ }
+
+ public function testGetRequestFormat()
+ {
+ $request = new Request();
+ $this->assertEquals('html', $request->getRequestFormat());
+
+ // Ensure that setting different default values over time is possible,
+ // aka. setRequestFormat determines the state.
+ $this->assertEquals('json', $request->getRequestFormat('json'));
+ $this->assertEquals('html', $request->getRequestFormat('html'));
+
+ $request = new Request();
+ $this->assertNull($request->getRequestFormat(null));
+
+ $request = new Request();
+ $this->assertNull($request->setRequestFormat('foo'));
+ $this->assertEquals('foo', $request->getRequestFormat(null));
+
+ $request = new Request(['_format' => 'foo']);
+ $this->assertEquals('html', $request->getRequestFormat());
+ }
+
+ public function testHasSession()
+ {
+ $request = new Request();
+
+ $this->assertFalse($request->hasSession());
+ $request->setSession(new Session(new MockArraySessionStorage()));
+ $this->assertTrue($request->hasSession());
+ }
+
+ public function testGetSession()
+ {
+ $request = new Request();
+
+ $request->setSession(new Session(new MockArraySessionStorage()));
+ $this->assertTrue($request->hasSession());
+
+ $session = $request->getSession();
+ $this->assertObjectHasAttribute('storage', $session);
+ $this->assertObjectHasAttribute('flashName', $session);
+ $this->assertObjectHasAttribute('attributeName', $session);
+ }
+
+ public function testHasPreviousSession()
+ {
+ $request = new Request();
+
+ $this->assertFalse($request->hasPreviousSession());
+ $request->cookies->set('MOCKSESSID', 'foo');
+ $this->assertFalse($request->hasPreviousSession());
+ $request->setSession(new Session(new MockArraySessionStorage()));
+ $this->assertTrue($request->hasPreviousSession());
+ }
+
+ public function testToString()
+ {
+ $request = new Request();
+
+ $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6');
+ $request->cookies->set('Foo', 'Bar');
+
+ $asString = (string) $request;
+
+ $this->assertContains('Accept-Language: zh, en-us; q=0.8, en; q=0.6', $asString);
+ $this->assertContains('Cookie: Foo=Bar', $asString);
+
+ $request->cookies->set('Another', 'Cookie');
+
+ $asString = (string) $request;
+
+ $this->assertContains('Cookie: Foo=Bar; Another=Cookie', $asString);
+ }
+
+ public function testIsMethod()
+ {
+ $request = new Request();
+ $request->setMethod('POST');
+ $this->assertTrue($request->isMethod('POST'));
+ $this->assertTrue($request->isMethod('post'));
+ $this->assertFalse($request->isMethod('GET'));
+ $this->assertFalse($request->isMethod('get'));
+
+ $request->setMethod('GET');
+ $this->assertTrue($request->isMethod('GET'));
+ $this->assertTrue($request->isMethod('get'));
+ $this->assertFalse($request->isMethod('POST'));
+ $this->assertFalse($request->isMethod('post'));
+ }
+
+ /**
+ * @dataProvider getBaseUrlData
+ */
+ public function testGetBaseUrl($uri, $server, $expectedBaseUrl, $expectedPathInfo)
+ {
+ $request = Request::create($uri, 'GET', [], [], [], $server);
+
+ $this->assertSame($expectedBaseUrl, $request->getBaseUrl(), 'baseUrl');
+ $this->assertSame($expectedPathInfo, $request->getPathInfo(), 'pathInfo');
+ }
+
+ public function getBaseUrlData()
+ {
+ return [
+ [
+ '/fruit/strawberry/1234index.php/blah',
+ [
+ 'SCRIPT_FILENAME' => 'E:/Sites/cc-new/public_html/fruit/index.php',
+ 'SCRIPT_NAME' => '/fruit/index.php',
+ 'PHP_SELF' => '/fruit/index.php',
+ ],
+ '/fruit',
+ '/strawberry/1234index.php/blah',
+ ],
+ [
+ '/fruit/strawberry/1234index.php/blah',
+ [
+ 'SCRIPT_FILENAME' => 'E:/Sites/cc-new/public_html/index.php',
+ 'SCRIPT_NAME' => '/index.php',
+ 'PHP_SELF' => '/index.php',
+ ],
+ '',
+ '/fruit/strawberry/1234index.php/blah',
+ ],
+ [
+ '/foo%20bar/',
+ [
+ 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php',
+ 'SCRIPT_NAME' => '/foo bar/app.php',
+ 'PHP_SELF' => '/foo bar/app.php',
+ ],
+ '/foo%20bar',
+ '/',
+ ],
+ [
+ '/foo%20bar/home',
+ [
+ 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php',
+ 'SCRIPT_NAME' => '/foo bar/app.php',
+ 'PHP_SELF' => '/foo bar/app.php',
+ ],
+ '/foo%20bar',
+ '/home',
+ ],
+ [
+ '/foo%20bar/app.php/home',
+ [
+ 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php',
+ 'SCRIPT_NAME' => '/foo bar/app.php',
+ 'PHP_SELF' => '/foo bar/app.php',
+ ],
+ '/foo%20bar/app.php',
+ '/home',
+ ],
+ [
+ '/foo%20bar/app.php/home%3Dbaz',
+ [
+ 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php',
+ 'SCRIPT_NAME' => '/foo bar/app.php',
+ 'PHP_SELF' => '/foo bar/app.php',
+ ],
+ '/foo%20bar/app.php',
+ '/home%3Dbaz',
+ ],
+ [
+ '/foo/bar+baz',
+ [
+ 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo/app.php',
+ 'SCRIPT_NAME' => '/foo/app.php',
+ 'PHP_SELF' => '/foo/app.php',
+ ],
+ '/foo',
+ '/bar+baz',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider urlencodedStringPrefixData
+ */
+ public function testUrlencodedStringPrefix($string, $prefix, $expect)
+ {
+ $request = new Request();
+
+ $me = new \ReflectionMethod($request, 'getUrlencodedPrefix');
+ $me->setAccessible(true);
+
+ $this->assertSame($expect, $me->invoke($request, $string, $prefix));
+ }
+
+ public function urlencodedStringPrefixData()
+ {
+ return [
+ ['foo', 'foo', 'foo'],
+ ['fo%6f', 'foo', 'fo%6f'],
+ ['foo/bar', 'foo', 'foo'],
+ ['fo%6f/bar', 'foo', 'fo%6f'],
+ ['f%6f%6f/bar', 'foo', 'f%6f%6f'],
+ ['%66%6F%6F/bar', 'foo', '%66%6F%6F'],
+ ['fo+o/bar', 'fo+o', 'fo+o'],
+ ['fo%2Bo/bar', 'fo+o', 'fo%2Bo'],
+ ];
+ }
+
+ private function disableHttpMethodParameterOverride()
+ {
+ $class = new \ReflectionClass('Symfony\\Component\\HttpFoundation\\Request');
+ $property = $class->getProperty('httpMethodParameterOverride');
+ $property->setAccessible(true);
+ $property->setValue(false);
+ }
+
+ private function getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies)
+ {
+ $request = new Request();
+
+ $server = ['REMOTE_ADDR' => $remoteAddr];
+ if (null !== $httpForwardedFor) {
+ $server['HTTP_X_FORWARDED_FOR'] = $httpForwardedFor;
+ }
+
+ if ($trustedProxies) {
+ Request::setTrustedProxies($trustedProxies, Request::HEADER_X_FORWARDED_ALL);
+ }
+
+ $request->initialize([], [], [], [], [], $server);
+
+ return $request;
+ }
+
+ private function getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies)
+ {
+ $request = new Request();
+
+ $server = ['REMOTE_ADDR' => $remoteAddr];
+
+ if (null !== $httpForwarded) {
+ $server['HTTP_FORWARDED'] = $httpForwarded;
+ }
+
+ if ($trustedProxies) {
+ Request::setTrustedProxies($trustedProxies, Request::HEADER_FORWARDED);
+ }
+
+ $request->initialize([], [], [], [], [], $server);
+
+ return $request;
+ }
+
+ public function testTrustedProxiesXForwardedFor()
+ {
+ $request = Request::create('http://example.com/');
+ $request->server->set('REMOTE_ADDR', '3.3.3.3');
+ $request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2');
+ $request->headers->set('X_FORWARDED_HOST', 'foo.example.com:1234, real.example.com:8080');
+ $request->headers->set('X_FORWARDED_PROTO', 'https');
+ $request->headers->set('X_FORWARDED_PORT', 443);
+
+ // no trusted proxies
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // disabling proxy trusting
+ Request::setTrustedProxies([], Request::HEADER_X_FORWARDED_ALL);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // request is forwarded by a non-trusted proxy
+ Request::setTrustedProxies(['2.2.2.2'], Request::HEADER_X_FORWARDED_ALL);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // trusted proxy via setTrustedProxies()
+ Request::setTrustedProxies(['3.3.3.3', '2.2.2.2'], Request::HEADER_X_FORWARDED_ALL);
+ $this->assertEquals('1.1.1.1', $request->getClientIp());
+ $this->assertEquals('foo.example.com', $request->getHost());
+ $this->assertEquals(443, $request->getPort());
+ $this->assertTrue($request->isSecure());
+
+ // trusted proxy via setTrustedProxies()
+ Request::setTrustedProxies(['3.3.3.4', '2.2.2.2'], Request::HEADER_X_FORWARDED_ALL);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // check various X_FORWARDED_PROTO header values
+ Request::setTrustedProxies(['3.3.3.3', '2.2.2.2'], Request::HEADER_X_FORWARDED_ALL);
+ $request->headers->set('X_FORWARDED_PROTO', 'ssl');
+ $this->assertTrue($request->isSecure());
+
+ $request->headers->set('X_FORWARDED_PROTO', 'https, http');
+ $this->assertTrue($request->isSecure());
+ }
+
+ /**
+ * @group legacy
+ * @expectedDeprecation The "Symfony\Component\HttpFoundation\Request::setTrustedHeaderName()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.
+ */
+ public function testLegacyTrustedProxies()
+ {
+ $request = Request::create('http://example.com/');
+ $request->server->set('REMOTE_ADDR', '3.3.3.3');
+ $request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2');
+ $request->headers->set('X_FORWARDED_HOST', 'foo.example.com, real.example.com:8080');
+ $request->headers->set('X_FORWARDED_PROTO', 'https');
+ $request->headers->set('X_FORWARDED_PORT', 443);
+ $request->headers->set('X_MY_FOR', '3.3.3.3, 4.4.4.4');
+ $request->headers->set('X_MY_HOST', 'my.example.com');
+ $request->headers->set('X_MY_PROTO', 'http');
+ $request->headers->set('X_MY_PORT', 81);
+
+ Request::setTrustedProxies(['3.3.3.3', '2.2.2.2'], Request::HEADER_X_FORWARDED_ALL);
+
+ // custom header names
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_MY_FOR');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_MY_HOST');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_MY_PORT');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_MY_PROTO');
+ $this->assertEquals('4.4.4.4', $request->getClientIp());
+ $this->assertEquals('my.example.com', $request->getHost());
+ $this->assertEquals(81, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // disabling via empty header names
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, null);
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, null);
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, null);
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, null);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ //reset
+ Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'FORWARDED');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_FORWARDED_HOST');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_FORWARDED_PORT');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_FORWARDED_PROTO');
+ }
+
+ public function testTrustedProxiesForwarded()
+ {
+ $request = Request::create('http://example.com/');
+ $request->server->set('REMOTE_ADDR', '3.3.3.3');
+ $request->headers->set('FORWARDED', 'for=1.1.1.1, host=foo.example.com:8080, proto=https, for=2.2.2.2, host=real.example.com:8080');
+
+ // no trusted proxies
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // disabling proxy trusting
+ Request::setTrustedProxies([], Request::HEADER_FORWARDED);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // request is forwarded by a non-trusted proxy
+ Request::setTrustedProxies(['2.2.2.2'], Request::HEADER_FORWARDED);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // trusted proxy via setTrustedProxies()
+ Request::setTrustedProxies(['3.3.3.3', '2.2.2.2'], Request::HEADER_FORWARDED);
+ $this->assertEquals('1.1.1.1', $request->getClientIp());
+ $this->assertEquals('foo.example.com', $request->getHost());
+ $this->assertEquals(8080, $request->getPort());
+ $this->assertTrue($request->isSecure());
+
+ // trusted proxy via setTrustedProxies()
+ Request::setTrustedProxies(['3.3.3.4', '2.2.2.2'], Request::HEADER_FORWARDED);
+ $this->assertEquals('3.3.3.3', $request->getClientIp());
+ $this->assertEquals('example.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+ $this->assertFalse($request->isSecure());
+
+ // check various X_FORWARDED_PROTO header values
+ Request::setTrustedProxies(['3.3.3.3', '2.2.2.2'], Request::HEADER_FORWARDED);
+ $request->headers->set('FORWARDED', 'proto=ssl');
+ $this->assertTrue($request->isSecure());
+
+ $request->headers->set('FORWARDED', 'proto=https, proto=http');
+ $this->assertTrue($request->isSecure());
+ }
+
+ /**
+ * @group legacy
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetTrustedProxiesInvalidHeaderName()
+ {
+ Request::create('http://example.com/');
+ Request::setTrustedHeaderName('bogus name', 'X_MY_FOR');
+ }
+
+ /**
+ * @group legacy
+ * @expectedException \InvalidArgumentException
+ */
+ public function testGetTrustedProxiesInvalidHeaderName()
+ {
+ Request::create('http://example.com/');
+ Request::getTrustedHeaderName('bogus name');
+ }
+
+ /**
+ * @dataProvider iisRequestUriProvider
+ */
+ public function testIISRequestUri($headers, $server, $expectedRequestUri)
+ {
+ $request = new Request();
+ $request->headers->replace($headers);
+ $request->server->replace($server);
+
+ $this->assertEquals($expectedRequestUri, $request->getRequestUri(), '->getRequestUri() is correct');
+
+ $subRequestUri = '/bar/foo';
+ $subRequest = Request::create($subRequestUri, 'get', [], [], [], $request->server->all());
+ $this->assertEquals($subRequestUri, $subRequest->getRequestUri(), '->getRequestUri() is correct in sub request');
+ }
+
+ public function iisRequestUriProvider()
+ {
+ return [
+ [
+ [],
+ [
+ 'IIS_WasUrlRewritten' => '1',
+ 'UNENCODED_URL' => '/foo/bar',
+ ],
+ '/foo/bar',
+ ],
+ [
+ [],
+ [
+ 'ORIG_PATH_INFO' => '/foo/bar',
+ ],
+ '/foo/bar',
+ ],
+ [
+ [],
+ [
+ 'ORIG_PATH_INFO' => '/foo/bar',
+ 'QUERY_STRING' => 'foo=bar',
+ ],
+ '/foo/bar?foo=bar',
+ ],
+ ];
+ }
+
+ public function testTrustedHosts()
+ {
+ // create a request
+ $request = Request::create('/');
+
+ // no trusted host set -> no host check
+ $request->headers->set('host', 'evil.com');
+ $this->assertEquals('evil.com', $request->getHost());
+
+ // add a trusted domain and all its subdomains
+ Request::setTrustedHosts(['^([a-z]{9}\.)?trusted\.com$']);
+
+ // untrusted host
+ $request->headers->set('host', 'evil.com');
+ try {
+ $request->getHost();
+ $this->fail('Request::getHost() should throw an exception when host is not trusted.');
+ } catch (SuspiciousOperationException $e) {
+ $this->assertEquals('Untrusted Host "evil.com".', $e->getMessage());
+ }
+
+ // trusted hosts
+ $request->headers->set('host', 'trusted.com');
+ $this->assertEquals('trusted.com', $request->getHost());
+ $this->assertEquals(80, $request->getPort());
+
+ $request->server->set('HTTPS', true);
+ $request->headers->set('host', 'trusted.com');
+ $this->assertEquals('trusted.com', $request->getHost());
+ $this->assertEquals(443, $request->getPort());
+ $request->server->set('HTTPS', false);
+
+ $request->headers->set('host', 'trusted.com:8000');
+ $this->assertEquals('trusted.com', $request->getHost());
+ $this->assertEquals(8000, $request->getPort());
+
+ $request->headers->set('host', 'subdomain.trusted.com');
+ $this->assertEquals('subdomain.trusted.com', $request->getHost());
+ }
+
+ public function testSetTrustedHostsDoesNotBreakOnSpecialCharacters()
+ {
+ Request::setTrustedHosts(['localhost(\.local){0,1}#,example.com', 'localhost']);
+
+ $request = Request::create('/');
+ $request->headers->set('host', 'localhost');
+ $this->assertSame('localhost', $request->getHost());
+ }
+
+ public function testFactory()
+ {
+ Request::setFactory(function (array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) {
+ return new NewRequest();
+ });
+
+ $this->assertEquals('foo', Request::create('/')->getFoo());
+
+ Request::setFactory(null);
+ }
+
+ /**
+ * @dataProvider getLongHostNames
+ */
+ public function testVeryLongHosts($host)
+ {
+ $start = microtime(true);
+
+ $request = Request::create('/');
+ $request->headers->set('host', $host);
+ $this->assertEquals($host, $request->getHost());
+ $this->assertLessThan(5, microtime(true) - $start);
+ }
+
+ /**
+ * @dataProvider getHostValidities
+ */
+ public function testHostValidity($host, $isValid, $expectedHost = null, $expectedPort = null)
+ {
+ $request = Request::create('/');
+ $request->headers->set('host', $host);
+
+ if ($isValid) {
+ $this->assertSame($expectedHost ?: $host, $request->getHost());
+ if ($expectedPort) {
+ $this->assertSame($expectedPort, $request->getPort());
+ }
+ } else {
+ if (method_exists($this, 'expectException')) {
+ $this->expectException(SuspiciousOperationException::class);
+ $this->expectExceptionMessage('Invalid Host');
+ } else {
+ $this->setExpectedException(SuspiciousOperationException::class, 'Invalid Host');
+ }
+
+ $request->getHost();
+ }
+ }
+
+ public function getHostValidities()
+ {
+ return [
+ ['.a', false],
+ ['a..', false],
+ ['a.', true],
+ ["\xE9", false],
+ ['[::1]', true],
+ ['[::1]:80', true, '[::1]', 80],
+ [str_repeat('.', 101), false],
+ ];
+ }
+
+ public function getLongHostNames()
+ {
+ return [
+ ['a'.str_repeat('.a', 40000)],
+ [str_repeat(':', 101)],
+ ];
+ }
+
+ /**
+ * @dataProvider methodIdempotentProvider
+ */
+ public function testMethodIdempotent($method, $idempotent)
+ {
+ $request = new Request();
+ $request->setMethod($method);
+ $this->assertEquals($idempotent, $request->isMethodIdempotent());
+ }
+
+ public function methodIdempotentProvider()
+ {
+ return [
+ ['HEAD', true],
+ ['GET', true],
+ ['POST', false],
+ ['PUT', true],
+ ['PATCH', false],
+ ['DELETE', true],
+ ['PURGE', true],
+ ['OPTIONS', true],
+ ['TRACE', true],
+ ['CONNECT', false],
+ ];
+ }
+
+ /**
+ * @dataProvider methodSafeProvider
+ */
+ public function testMethodSafe($method, $safe)
+ {
+ $request = new Request();
+ $request->setMethod($method);
+ $this->assertEquals($safe, $request->isMethodSafe(false));
+ }
+
+ public function methodSafeProvider()
+ {
+ return [
+ ['HEAD', true],
+ ['GET', true],
+ ['POST', false],
+ ['PUT', false],
+ ['PATCH', false],
+ ['DELETE', false],
+ ['PURGE', false],
+ ['OPTIONS', true],
+ ['TRACE', true],
+ ['CONNECT', false],
+ ];
+ }
+
+ /**
+ * @group legacy
+ * @expectedDeprecation Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since Symfony 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead.
+ */
+ public function testMethodSafeChecksCacheable()
+ {
+ $request = new Request();
+ $request->setMethod('OPTIONS');
+ $this->assertFalse($request->isMethodSafe());
+ }
+
+ /**
+ * @dataProvider methodCacheableProvider
+ */
+ public function testMethodCacheable($method, $cacheable)
+ {
+ $request = new Request();
+ $request->setMethod($method);
+ $this->assertEquals($cacheable, $request->isMethodCacheable());
+ }
+
+ public function methodCacheableProvider()
+ {
+ return [
+ ['HEAD', true],
+ ['GET', true],
+ ['POST', false],
+ ['PUT', false],
+ ['PATCH', false],
+ ['DELETE', false],
+ ['PURGE', false],
+ ['OPTIONS', false],
+ ['TRACE', false],
+ ['CONNECT', false],
+ ];
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testGetTrustedHeaderName()
+ {
+ Request::setTrustedProxies(['8.8.8.8'], Request::HEADER_X_FORWARDED_ALL);
+
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+ $this->assertSame('X_FORWARDED_FOR', Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP));
+ $this->assertSame('X_FORWARDED_HOST', Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST));
+ $this->assertSame('X_FORWARDED_PORT', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT));
+ $this->assertSame('X_FORWARDED_PROTO', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO));
+
+ Request::setTrustedProxies(['8.8.8.8'], Request::HEADER_FORWARDED);
+
+ $this->assertSame('FORWARDED', Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO));
+
+ Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'A');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'B');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'C');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'D');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'E');
+
+ Request::setTrustedProxies(['8.8.8.8'], Request::HEADER_FORWARDED);
+
+ $this->assertSame('A', Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT));
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO));
+
+ Request::setTrustedProxies(['8.8.8.8'], Request::HEADER_X_FORWARDED_ALL);
+
+ $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+ $this->assertSame('B', Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP));
+ $this->assertSame('C', Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST));
+ $this->assertSame('D', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT));
+ $this->assertSame('E', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO));
+
+ Request::setTrustedProxies(['8.8.8.8'], Request::HEADER_FORWARDED);
+
+ $this->assertSame('A', Request::getTrustedHeaderName(Request::HEADER_FORWARDED));
+
+ //reset
+ Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'FORWARDED');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_FORWARDED_HOST');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_FORWARDED_PORT');
+ Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_FORWARDED_PROTO');
+ }
+
+ /**
+ * @dataProvider protocolVersionProvider
+ */
+ public function testProtocolVersion($serverProtocol, $trustedProxy, $via, $expected)
+ {
+ if ($trustedProxy) {
+ Request::setTrustedProxies(['1.1.1.1'], -1);
+ }
+
+ $request = new Request();
+ $request->server->set('SERVER_PROTOCOL', $serverProtocol);
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $request->headers->set('Via', $via);
+
+ $this->assertSame($expected, $request->getProtocolVersion());
+ }
+
+ public function protocolVersionProvider()
+ {
+ return [
+ 'untrusted without via' => ['HTTP/2.0', false, '', 'HTTP/2.0'],
+ 'untrusted with via' => ['HTTP/2.0', false, '1.0 fred, 1.1 nowhere.com (Apache/1.1)', 'HTTP/2.0'],
+ 'trusted without via' => ['HTTP/2.0', true, '', 'HTTP/2.0'],
+ 'trusted with via' => ['HTTP/2.0', true, '1.0 fred, 1.1 nowhere.com (Apache/1.1)', 'HTTP/1.0'],
+ 'trusted with via and protocol name' => ['HTTP/2.0', true, 'HTTP/1.0 fred, HTTP/1.1 nowhere.com (Apache/1.1)', 'HTTP/1.0'],
+ 'trusted with broken via' => ['HTTP/2.0', true, 'HTTP/1^0 foo', 'HTTP/2.0'],
+ 'trusted with partially-broken via' => ['HTTP/2.0', true, '1.0 fred, foo', 'HTTP/1.0'],
+ ];
+ }
+
+ public function nonstandardRequestsData()
+ {
+ return [
+ ['', '', '/', 'http://host:8080/', ''],
+ ['/', '', '/', 'http://host:8080/', ''],
+
+ ['hello/app.php/x', '', '/x', 'http://host:8080/hello/app.php/x', '/hello', '/hello/app.php'],
+ ['/hello/app.php/x', '', '/x', 'http://host:8080/hello/app.php/x', '/hello', '/hello/app.php'],
+
+ ['', 'a=b', '/', 'http://host:8080/?a=b'],
+ ['?a=b', 'a=b', '/', 'http://host:8080/?a=b'],
+ ['/?a=b', 'a=b', '/', 'http://host:8080/?a=b'],
+
+ ['x', 'a=b', '/x', 'http://host:8080/x?a=b'],
+ ['x?a=b', 'a=b', '/x', 'http://host:8080/x?a=b'],
+ ['/x?a=b', 'a=b', '/x', 'http://host:8080/x?a=b'],
+
+ ['hello/x', '', '/x', 'http://host:8080/hello/x', '/hello'],
+ ['/hello/x', '', '/x', 'http://host:8080/hello/x', '/hello'],
+
+ ['hello/app.php/x', 'a=b', '/x', 'http://host:8080/hello/app.php/x?a=b', '/hello', '/hello/app.php'],
+ ['hello/app.php/x?a=b', 'a=b', '/x', 'http://host:8080/hello/app.php/x?a=b', '/hello', '/hello/app.php'],
+ ['/hello/app.php/x?a=b', 'a=b', '/x', 'http://host:8080/hello/app.php/x?a=b', '/hello', '/hello/app.php'],
+ ];
+ }
+
+ /**
+ * @dataProvider nonstandardRequestsData
+ */
+ public function testNonstandardRequests($requestUri, $queryString, $expectedPathInfo, $expectedUri, $expectedBasePath = '', $expectedBaseUrl = null)
+ {
+ if (null === $expectedBaseUrl) {
+ $expectedBaseUrl = $expectedBasePath;
+ }
+
+ $server = [
+ 'HTTP_HOST' => 'host:8080',
+ 'SERVER_PORT' => '8080',
+ 'QUERY_STRING' => $queryString,
+ 'PHP_SELF' => '/hello/app.php',
+ 'SCRIPT_FILENAME' => '/some/path/app.php',
+ 'REQUEST_URI' => $requestUri,
+ ];
+
+ $request = new Request([], [], [], [], [], $server);
+
+ $this->assertEquals($expectedPathInfo, $request->getPathInfo());
+ $this->assertEquals($expectedUri, $request->getUri());
+ $this->assertEquals($queryString, $request->getQueryString());
+ $this->assertEquals(8080, $request->getPort());
+ $this->assertEquals('host:8080', $request->getHttpHost());
+ $this->assertEquals($expectedBaseUrl, $request->getBaseUrl());
+ $this->assertEquals($expectedBasePath, $request->getBasePath());
+ }
+
+ public function testTrustedHost()
+ {
+ Request::setTrustedProxies(['1.1.1.1'], -1);
+
+ $request = Request::create('/');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $request->headers->set('Forwarded', 'host=localhost:8080');
+ $request->headers->set('X-Forwarded-Host', 'localhost:8080');
+
+ $this->assertSame('localhost:8080', $request->getHttpHost());
+ $this->assertSame(8080, $request->getPort());
+
+ $request = Request::create('/');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $request->headers->set('Forwarded', 'host="[::1]:443"');
+ $request->headers->set('X-Forwarded-Host', '[::1]:443');
+ $request->headers->set('X-Forwarded-Port', 443);
+
+ $this->assertSame('[::1]:443', $request->getHttpHost());
+ $this->assertSame(443, $request->getPort());
+ }
+
+ public function testTrustedPort()
+ {
+ Request::setTrustedProxies(['1.1.1.1'], -1);
+
+ $request = Request::create('/');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $request->headers->set('Forwarded', 'host=localhost:8080');
+ $request->headers->set('X-Forwarded-Port', 8080);
+
+ $this->assertSame(8080, $request->getPort());
+
+ $request = Request::create('/');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $request->headers->set('Forwarded', 'host=localhost');
+ $request->headers->set('X-Forwarded-Port', 80);
+
+ $this->assertSame(80, $request->getPort());
+
+ $request = Request::create('/');
+ $request->server->set('REMOTE_ADDR', '1.1.1.1');
+ $request->headers->set('Forwarded', 'host="[::1]"');
+ $request->headers->set('X-Forwarded-Proto', 'https');
+ $request->headers->set('X-Forwarded-Port', 443);
+
+ $this->assertSame(443, $request->getPort());
+ }
+}
+
+class RequestContentProxy extends Request
+{
+ public function getContent($asResource = false)
+ {
+ return http_build_query(['_method' => 'PUT', 'content' => 'mycontent'], '', '&');
+ }
+}
+
+class NewRequest extends Request
+{
+ public function getFoo()
+ {
+ return 'foo';
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/ResponseFunctionalTest.php b/vendor/symfony/http-foundation/Tests/ResponseFunctionalTest.php
new file mode 100755
index 0000000..3d3e696
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ResponseFunctionalTest.php
@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @requires PHP 7.0
+ */
+class ResponseFunctionalTest extends TestCase
+{
+ private static $server;
+
+ public static function setUpBeforeClass()
+ {
+ $spec = [
+ 1 => ['file', '/dev/null', 'w'],
+ 2 => ['file', '/dev/null', 'w'],
+ ];
+ if (!self::$server = @proc_open('exec php -S localhost:8054', $spec, $pipes, __DIR__.'/Fixtures/response-functional')) {
+ self::markTestSkipped('PHP server unable to start.');
+ }
+ sleep(1);
+ }
+
+ public static function tearDownAfterClass()
+ {
+ if (self::$server) {
+ proc_terminate(self::$server);
+ proc_close(self::$server);
+ }
+ }
+
+ /**
+ * @dataProvider provideCookie
+ */
+ public function testCookie($fixture)
+ {
+ $result = file_get_contents(sprintf('http://localhost:8054/%s.php', $fixture));
+ $this->assertStringMatchesFormatFile(__DIR__.sprintf('/Fixtures/response-functional/%s.expected', $fixture), $result);
+ }
+
+ public function provideCookie()
+ {
+ foreach (glob(__DIR__.'/Fixtures/response-functional/*.php') as $file) {
+ yield [pathinfo($file, PATHINFO_FILENAME)];
+ }
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php b/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php
new file mode 100755
index 0000000..93aacf2
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php
@@ -0,0 +1,363 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\ResponseHeaderBag;
+
+/**
+ * @group time-sensitive
+ */
+class ResponseHeaderBagTest extends TestCase
+{
+ public function testAllPreserveCase()
+ {
+ $headers = [
+ 'fOo' => 'BAR',
+ 'ETag' => 'xyzzy',
+ 'Content-MD5' => 'Q2hlY2sgSW50ZWdyaXR5IQ==',
+ 'P3P' => 'CP="CAO PSA OUR"',
+ 'WWW-Authenticate' => 'Basic realm="WallyWorld"',
+ 'X-UA-Compatible' => 'IE=edge,chrome=1',
+ 'X-XSS-Protection' => '1; mode=block',
+ ];
+
+ $bag = new ResponseHeaderBag($headers);
+ $allPreservedCase = $bag->allPreserveCase();
+
+ foreach (array_keys($headers) as $headerName) {
+ $this->assertArrayHasKey($headerName, $allPreservedCase, '->allPreserveCase() gets all input keys in original case');
+ }
+ }
+
+ public function testCacheControlHeader()
+ {
+ $bag = new ResponseHeaderBag([]);
+ $this->assertEquals('no-cache, private', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
+
+ $bag = new ResponseHeaderBag(['Cache-Control' => 'public']);
+ $this->assertEquals('public', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('public'));
+
+ $bag = new ResponseHeaderBag(['ETag' => 'abcde']);
+ $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('private'));
+ $this->assertTrue($bag->hasCacheControlDirective('must-revalidate'));
+ $this->assertFalse($bag->hasCacheControlDirective('max-age'));
+
+ $bag = new ResponseHeaderBag(['Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT']);
+ $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag([
+ 'Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT',
+ 'Cache-Control' => 'max-age=3600',
+ ]);
+ $this->assertEquals('max-age=3600, private', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag(['Last-Modified' => 'abcde']);
+ $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag(['Etag' => 'abcde', 'Last-Modified' => 'abcde']);
+ $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag(['cache-control' => 'max-age=100']);
+ $this->assertEquals('max-age=100, private', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag(['cache-control' => 's-maxage=100']);
+ $this->assertEquals('s-maxage=100', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag(['cache-control' => 'private, max-age=100']);
+ $this->assertEquals('max-age=100, private', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag(['cache-control' => 'public, max-age=100']);
+ $this->assertEquals('max-age=100, public', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag();
+ $bag->set('Last-Modified', 'abcde');
+ $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag();
+ $bag->set('Cache-Control', ['public', 'must-revalidate']);
+ $this->assertCount(1, $bag->get('Cache-Control', null, false));
+ $this->assertEquals('must-revalidate, public', $bag->get('Cache-Control'));
+
+ $bag = new ResponseHeaderBag();
+ $bag->set('Cache-Control', 'public');
+ $bag->set('Cache-Control', 'must-revalidate', false);
+ $this->assertCount(1, $bag->get('Cache-Control', null, false));
+ $this->assertEquals('must-revalidate, public', $bag->get('Cache-Control'));
+ }
+
+ public function testCacheControlClone()
+ {
+ $headers = ['foo' => 'bar'];
+ $bag1 = new ResponseHeaderBag($headers);
+ $bag2 = new ResponseHeaderBag($bag1->allPreserveCase());
+ $this->assertEquals($bag1->allPreserveCase(), $bag2->allPreserveCase());
+ }
+
+ public function testToStringIncludesCookieHeaders()
+ {
+ $bag = new ResponseHeaderBag([]);
+ $bag->setCookie(new Cookie('foo', 'bar'));
+
+ $this->assertSetCookieHeader('foo=bar; path=/; httponly', $bag);
+
+ $bag->clearCookie('foo');
+
+ $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0; path=/; httponly', $bag);
+ }
+
+ public function testClearCookieSecureNotHttpOnly()
+ {
+ $bag = new ResponseHeaderBag([]);
+
+ $bag->clearCookie('foo', '/', null, true, false);
+
+ $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0; path=/; secure', $bag);
+ }
+
+ public function testReplace()
+ {
+ $bag = new ResponseHeaderBag([]);
+ $this->assertEquals('no-cache, private', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
+
+ $bag->replace(['Cache-Control' => 'public']);
+ $this->assertEquals('public', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('public'));
+ }
+
+ public function testReplaceWithRemove()
+ {
+ $bag = new ResponseHeaderBag([]);
+ $this->assertEquals('no-cache, private', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
+
+ $bag->remove('Cache-Control');
+ $bag->replace([]);
+ $this->assertEquals('no-cache, private', $bag->get('Cache-Control'));
+ $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
+ }
+
+ public function testCookiesWithSameNames()
+ {
+ $bag = new ResponseHeaderBag();
+ $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar'));
+ $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'foo.bar'));
+ $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'bar.foo'));
+ $bag->setCookie(new Cookie('foo', 'bar'));
+
+ $this->assertCount(4, $bag->getCookies());
+ $this->assertEquals('foo=bar; path=/path/foo; domain=foo.bar; httponly', $bag->get('set-cookie'));
+ $this->assertEquals([
+ 'foo=bar; path=/path/foo; domain=foo.bar; httponly',
+ 'foo=bar; path=/path/bar; domain=foo.bar; httponly',
+ 'foo=bar; path=/path/bar; domain=bar.foo; httponly',
+ 'foo=bar; path=/; httponly',
+ ], $bag->get('set-cookie', null, false));
+
+ $this->assertSetCookieHeader('foo=bar; path=/path/foo; domain=foo.bar; httponly', $bag);
+ $this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=foo.bar; httponly', $bag);
+ $this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=bar.foo; httponly', $bag);
+ $this->assertSetCookieHeader('foo=bar; path=/; httponly', $bag);
+
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+
+ $this->assertArrayHasKey('foo', $cookies['foo.bar']['/path/foo']);
+ $this->assertArrayHasKey('foo', $cookies['foo.bar']['/path/bar']);
+ $this->assertArrayHasKey('foo', $cookies['bar.foo']['/path/bar']);
+ $this->assertArrayHasKey('foo', $cookies['']['/']);
+ }
+
+ public function testRemoveCookie()
+ {
+ $bag = new ResponseHeaderBag();
+ $this->assertFalse($bag->has('set-cookie'));
+
+ $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar'));
+ $bag->setCookie(new Cookie('bar', 'foo', 0, '/path/bar', 'foo.bar'));
+ $this->assertTrue($bag->has('set-cookie'));
+
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+ $this->assertArrayHasKey('/path/foo', $cookies['foo.bar']);
+
+ $bag->removeCookie('foo', '/path/foo', 'foo.bar');
+ $this->assertTrue($bag->has('set-cookie'));
+
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+ $this->assertArrayNotHasKey('/path/foo', $cookies['foo.bar']);
+
+ $bag->removeCookie('bar', '/path/bar', 'foo.bar');
+ $this->assertFalse($bag->has('set-cookie'));
+
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+ $this->assertArrayNotHasKey('foo.bar', $cookies);
+ }
+
+ public function testRemoveCookieWithNullRemove()
+ {
+ $bag = new ResponseHeaderBag();
+ $bag->setCookie(new Cookie('foo', 'bar', 0));
+ $bag->setCookie(new Cookie('bar', 'foo', 0));
+
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+ $this->assertArrayHasKey('/', $cookies['']);
+
+ $bag->removeCookie('foo', null);
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+ $this->assertArrayNotHasKey('foo', $cookies['']['/']);
+
+ $bag->removeCookie('bar', null);
+ $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
+ $this->assertFalse(isset($cookies['']['/']['bar']));
+ }
+
+ public function testSetCookieHeader()
+ {
+ $bag = new ResponseHeaderBag();
+ $bag->set('set-cookie', 'foo=bar');
+ $this->assertEquals([new Cookie('foo', 'bar', 0, '/', null, false, false, true)], $bag->getCookies());
+
+ $bag->set('set-cookie', 'foo2=bar2', false);
+ $this->assertEquals([
+ new Cookie('foo', 'bar', 0, '/', null, false, false, true),
+ new Cookie('foo2', 'bar2', 0, '/', null, false, false, true),
+ ], $bag->getCookies());
+
+ $bag->remove('set-cookie');
+ $this->assertEquals([], $bag->getCookies());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testGetCookiesWithInvalidArgument()
+ {
+ $bag = new ResponseHeaderBag();
+
+ $bag->getCookies('invalid_argument');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testMakeDispositionInvalidDisposition()
+ {
+ $headers = new ResponseHeaderBag();
+
+ $headers->makeDisposition('invalid', 'foo.html');
+ }
+
+ /**
+ * @dataProvider provideMakeDisposition
+ */
+ public function testMakeDisposition($disposition, $filename, $filenameFallback, $expected)
+ {
+ $headers = new ResponseHeaderBag();
+
+ $this->assertEquals($expected, $headers->makeDisposition($disposition, $filename, $filenameFallback));
+ }
+
+ public function testToStringDoesntMessUpHeaders()
+ {
+ $headers = new ResponseHeaderBag();
+
+ $headers->set('Location', 'http://www.symfony.com');
+ $headers->set('Content-type', 'text/html');
+
+ (string) $headers;
+
+ $allHeaders = $headers->allPreserveCase();
+ $this->assertEquals(['http://www.symfony.com'], $allHeaders['Location']);
+ $this->assertEquals(['text/html'], $allHeaders['Content-type']);
+ }
+
+ public function provideMakeDisposition()
+ {
+ return [
+ ['attachment', 'foo.html', 'foo.html', 'attachment; filename="foo.html"'],
+ ['attachment', 'foo.html', '', 'attachment; filename="foo.html"'],
+ ['attachment', 'foo bar.html', '', 'attachment; filename="foo bar.html"'],
+ ['attachment', 'foo "bar".html', '', 'attachment; filename="foo \\"bar\\".html"'],
+ ['attachment', 'foo%20bar.html', 'foo bar.html', 'attachment; filename="foo bar.html"; filename*=utf-8\'\'foo%2520bar.html'],
+ ['attachment', 'föö.html', 'foo.html', 'attachment; filename="foo.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html'],
+ ];
+ }
+
+ /**
+ * @dataProvider provideMakeDispositionFail
+ * @expectedException \InvalidArgumentException
+ */
+ public function testMakeDispositionFail($disposition, $filename)
+ {
+ $headers = new ResponseHeaderBag();
+
+ $headers->makeDisposition($disposition, $filename);
+ }
+
+ public function provideMakeDispositionFail()
+ {
+ return [
+ ['attachment', 'foo%20bar.html'],
+ ['attachment', 'foo/bar.html'],
+ ['attachment', '/foo.html'],
+ ['attachment', 'foo\bar.html'],
+ ['attachment', '\foo.html'],
+ ['attachment', 'föö.html'],
+ ];
+ }
+
+ public function testDateHeaderAddedOnCreation()
+ {
+ $now = time();
+
+ $bag = new ResponseHeaderBag();
+ $this->assertTrue($bag->has('Date'));
+
+ $this->assertEquals($now, $bag->getDate('Date')->getTimestamp());
+ }
+
+ public function testDateHeaderCanBeSetOnCreation()
+ {
+ $someDate = 'Thu, 23 Mar 2017 09:15:12 GMT';
+ $bag = new ResponseHeaderBag(['Date' => $someDate]);
+
+ $this->assertEquals($someDate, $bag->get('Date'));
+ }
+
+ public function testDateHeaderWillBeRecreatedWhenRemoved()
+ {
+ $someDate = 'Thu, 23 Mar 2017 09:15:12 GMT';
+ $bag = new ResponseHeaderBag(['Date' => $someDate]);
+ $bag->remove('Date');
+
+ // a (new) Date header is still present
+ $this->assertTrue($bag->has('Date'));
+ $this->assertNotEquals($someDate, $bag->get('Date'));
+ }
+
+ public function testDateHeaderWillBeRecreatedWhenHeadersAreReplaced()
+ {
+ $bag = new ResponseHeaderBag();
+ $bag->replace([]);
+
+ $this->assertTrue($bag->has('Date'));
+ }
+
+ private function assertSetCookieHeader($expected, ResponseHeaderBag $actual)
+ {
+ $this->assertRegExp('#^Set-Cookie:\s+'.preg_quote($expected, '#').'$#m', str_replace("\r\n", "\n", (string) $actual));
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/ResponseTest.php b/vendor/symfony/http-foundation/Tests/ResponseTest.php
new file mode 100755
index 0000000..fe6bc02
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ResponseTest.php
@@ -0,0 +1,1014 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * @group time-sensitive
+ */
+class ResponseTest extends ResponseTestCase
+{
+ public function testCreate()
+ {
+ $response = Response::create('foo', 301, ['Foo' => 'bar']);
+
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response);
+ $this->assertEquals(301, $response->getStatusCode());
+ $this->assertEquals('bar', $response->headers->get('foo'));
+ }
+
+ public function testToString()
+ {
+ $response = new Response();
+ $response = explode("\r\n", $response);
+ $this->assertEquals('HTTP/1.0 200 OK', $response[0]);
+ $this->assertEquals('Cache-Control: no-cache, private', $response[1]);
+ }
+
+ public function testClone()
+ {
+ $response = new Response();
+ $responseClone = clone $response;
+ $this->assertEquals($response, $responseClone);
+ }
+
+ public function testSendHeaders()
+ {
+ $response = new Response();
+ $headers = $response->sendHeaders();
+ $this->assertObjectHasAttribute('headers', $headers);
+ $this->assertObjectHasAttribute('content', $headers);
+ $this->assertObjectHasAttribute('version', $headers);
+ $this->assertObjectHasAttribute('statusCode', $headers);
+ $this->assertObjectHasAttribute('statusText', $headers);
+ $this->assertObjectHasAttribute('charset', $headers);
+ }
+
+ public function testSend()
+ {
+ $response = new Response();
+ $responseSend = $response->send();
+ $this->assertObjectHasAttribute('headers', $responseSend);
+ $this->assertObjectHasAttribute('content', $responseSend);
+ $this->assertObjectHasAttribute('version', $responseSend);
+ $this->assertObjectHasAttribute('statusCode', $responseSend);
+ $this->assertObjectHasAttribute('statusText', $responseSend);
+ $this->assertObjectHasAttribute('charset', $responseSend);
+ }
+
+ public function testGetCharset()
+ {
+ $response = new Response();
+ $charsetOrigin = 'UTF-8';
+ $response->setCharset($charsetOrigin);
+ $charset = $response->getCharset();
+ $this->assertEquals($charsetOrigin, $charset);
+ }
+
+ public function testIsCacheable()
+ {
+ $response = new Response();
+ $this->assertFalse($response->isCacheable());
+ }
+
+ public function testIsCacheableWithErrorCode()
+ {
+ $response = new Response('', 500);
+ $this->assertFalse($response->isCacheable());
+ }
+
+ public function testIsCacheableWithNoStoreDirective()
+ {
+ $response = new Response();
+ $response->headers->set('cache-control', 'private');
+ $this->assertFalse($response->isCacheable());
+ }
+
+ public function testIsCacheableWithSetTtl()
+ {
+ $response = new Response();
+ $response->setTtl(10);
+ $this->assertTrue($response->isCacheable());
+ }
+
+ public function testMustRevalidate()
+ {
+ $response = new Response();
+ $this->assertFalse($response->mustRevalidate());
+ }
+
+ public function testMustRevalidateWithMustRevalidateCacheControlHeader()
+ {
+ $response = new Response();
+ $response->headers->set('cache-control', 'must-revalidate');
+
+ $this->assertTrue($response->mustRevalidate());
+ }
+
+ public function testMustRevalidateWithProxyRevalidateCacheControlHeader()
+ {
+ $response = new Response();
+ $response->headers->set('cache-control', 'proxy-revalidate');
+
+ $this->assertTrue($response->mustRevalidate());
+ }
+
+ public function testSetNotModified()
+ {
+ $response = new Response('foo');
+ $modified = $response->setNotModified();
+ $this->assertObjectHasAttribute('headers', $modified);
+ $this->assertObjectHasAttribute('content', $modified);
+ $this->assertObjectHasAttribute('version', $modified);
+ $this->assertObjectHasAttribute('statusCode', $modified);
+ $this->assertObjectHasAttribute('statusText', $modified);
+ $this->assertObjectHasAttribute('charset', $modified);
+ $this->assertEquals(304, $modified->getStatusCode());
+
+ ob_start();
+ $modified->sendContent();
+ $string = ob_get_clean();
+ $this->assertEmpty($string);
+ }
+
+ public function testIsSuccessful()
+ {
+ $response = new Response();
+ $this->assertTrue($response->isSuccessful());
+ }
+
+ public function testIsNotModified()
+ {
+ $response = new Response();
+ $modified = $response->isNotModified(new Request());
+ $this->assertFalse($modified);
+ }
+
+ public function testIsNotModifiedNotSafe()
+ {
+ $request = Request::create('/homepage', 'POST');
+
+ $response = new Response();
+ $this->assertFalse($response->isNotModified($request));
+ }
+
+ public function testIsNotModifiedLastModified()
+ {
+ $before = 'Sun, 25 Aug 2013 18:32:31 GMT';
+ $modified = 'Sun, 25 Aug 2013 18:33:31 GMT';
+ $after = 'Sun, 25 Aug 2013 19:33:31 GMT';
+
+ $request = new Request();
+ $request->headers->set('If-Modified-Since', $modified);
+
+ $response = new Response();
+
+ $response->headers->set('Last-Modified', $modified);
+ $this->assertTrue($response->isNotModified($request));
+
+ $response->headers->set('Last-Modified', $before);
+ $this->assertTrue($response->isNotModified($request));
+
+ $response->headers->set('Last-Modified', $after);
+ $this->assertFalse($response->isNotModified($request));
+
+ $response->headers->set('Last-Modified', '');
+ $this->assertFalse($response->isNotModified($request));
+ }
+
+ public function testIsNotModifiedEtag()
+ {
+ $etagOne = 'randomly_generated_etag';
+ $etagTwo = 'randomly_generated_etag_2';
+
+ $request = new Request();
+ $request->headers->set('if_none_match', sprintf('%s, %s, %s', $etagOne, $etagTwo, 'etagThree'));
+
+ $response = new Response();
+
+ $response->headers->set('ETag', $etagOne);
+ $this->assertTrue($response->isNotModified($request));
+
+ $response->headers->set('ETag', $etagTwo);
+ $this->assertTrue($response->isNotModified($request));
+
+ $response->headers->set('ETag', '');
+ $this->assertFalse($response->isNotModified($request));
+ }
+
+ public function testIsNotModifiedLastModifiedAndEtag()
+ {
+ $before = 'Sun, 25 Aug 2013 18:32:31 GMT';
+ $modified = 'Sun, 25 Aug 2013 18:33:31 GMT';
+ $after = 'Sun, 25 Aug 2013 19:33:31 GMT';
+ $etag = 'randomly_generated_etag';
+
+ $request = new Request();
+ $request->headers->set('if_none_match', sprintf('%s, %s', $etag, 'etagThree'));
+ $request->headers->set('If-Modified-Since', $modified);
+
+ $response = new Response();
+
+ $response->headers->set('ETag', $etag);
+ $response->headers->set('Last-Modified', $after);
+ $this->assertFalse($response->isNotModified($request));
+
+ $response->headers->set('ETag', 'non-existent-etag');
+ $response->headers->set('Last-Modified', $before);
+ $this->assertFalse($response->isNotModified($request));
+
+ $response->headers->set('ETag', $etag);
+ $response->headers->set('Last-Modified', $modified);
+ $this->assertTrue($response->isNotModified($request));
+ }
+
+ public function testIsNotModifiedIfModifiedSinceAndEtagWithoutLastModified()
+ {
+ $modified = 'Sun, 25 Aug 2013 18:33:31 GMT';
+ $etag = 'randomly_generated_etag';
+
+ $request = new Request();
+ $request->headers->set('if_none_match', sprintf('%s, %s', $etag, 'etagThree'));
+ $request->headers->set('If-Modified-Since', $modified);
+
+ $response = new Response();
+
+ $response->headers->set('ETag', $etag);
+ $this->assertTrue($response->isNotModified($request));
+
+ $response->headers->set('ETag', 'non-existent-etag');
+ $this->assertFalse($response->isNotModified($request));
+ }
+
+ public function testIsValidateable()
+ {
+ $response = new Response('', 200, ['Last-Modified' => $this->createDateTimeOneHourAgo()->format(DATE_RFC2822)]);
+ $this->assertTrue($response->isValidateable(), '->isValidateable() returns true if Last-Modified is present');
+
+ $response = new Response('', 200, ['ETag' => '"12345"']);
+ $this->assertTrue($response->isValidateable(), '->isValidateable() returns true if ETag is present');
+
+ $response = new Response();
+ $this->assertFalse($response->isValidateable(), '->isValidateable() returns false when no validator is present');
+ }
+
+ public function testGetDate()
+ {
+ $oneHourAgo = $this->createDateTimeOneHourAgo();
+ $response = new Response('', 200, ['Date' => $oneHourAgo->format(DATE_RFC2822)]);
+ $date = $response->getDate();
+ $this->assertEquals($oneHourAgo->getTimestamp(), $date->getTimestamp(), '->getDate() returns the Date header if present');
+
+ $response = new Response();
+ $date = $response->getDate();
+ $this->assertEquals(time(), $date->getTimestamp(), '->getDate() returns the current Date if no Date header present');
+
+ $response = new Response('', 200, ['Date' => $this->createDateTimeOneHourAgo()->format(DATE_RFC2822)]);
+ $now = $this->createDateTimeNow();
+ $response->headers->set('Date', $now->format(DATE_RFC2822));
+ $date = $response->getDate();
+ $this->assertEquals($now->getTimestamp(), $date->getTimestamp(), '->getDate() returns the date when the header has been modified');
+
+ $response = new Response('', 200);
+ $now = $this->createDateTimeNow();
+ $response->headers->remove('Date');
+ $date = $response->getDate();
+ $this->assertEquals($now->getTimestamp(), $date->getTimestamp(), '->getDate() returns the current Date when the header has previously been removed');
+ }
+
+ public function testGetMaxAge()
+ {
+ $response = new Response();
+ $response->headers->set('Cache-Control', 's-maxage=600, max-age=0');
+ $this->assertEquals(600, $response->getMaxAge(), '->getMaxAge() uses s-maxage cache control directive when present');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'max-age=600');
+ $this->assertEquals(600, $response->getMaxAge(), '->getMaxAge() falls back to max-age when no s-maxage directive present');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'must-revalidate');
+ $response->headers->set('Expires', $this->createDateTimeOneHourLater()->format(DATE_RFC2822));
+ $this->assertEquals(3600, $response->getMaxAge(), '->getMaxAge() falls back to Expires when no max-age or s-maxage directive present');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'must-revalidate');
+ $response->headers->set('Expires', -1);
+ $this->assertEquals('Sat, 01 Jan 00 00:00:00 +0000', $response->getExpires()->format(DATE_RFC822));
+
+ $response = new Response();
+ $this->assertNull($response->getMaxAge(), '->getMaxAge() returns null if no freshness information available');
+ }
+
+ public function testSetSharedMaxAge()
+ {
+ $response = new Response();
+ $response->setSharedMaxAge(20);
+
+ $cacheControl = $response->headers->get('Cache-Control');
+ $this->assertEquals('public, s-maxage=20', $cacheControl);
+ }
+
+ public function testIsPrivate()
+ {
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'max-age=100');
+ $response->setPrivate();
+ $this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true');
+ $this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'public, max-age=100');
+ $response->setPrivate();
+ $this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true');
+ $this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true');
+ $this->assertFalse($response->headers->hasCacheControlDirective('public'), '->isPrivate() removes the public Cache-Control directive');
+ }
+
+ public function testExpire()
+ {
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'max-age=100');
+ $response->expire();
+ $this->assertEquals(100, $response->headers->get('Age'), '->expire() sets the Age to max-age when present');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'max-age=100, s-maxage=500');
+ $response->expire();
+ $this->assertEquals(500, $response->headers->get('Age'), '->expire() sets the Age to s-maxage when both max-age and s-maxage are present');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'max-age=5, s-maxage=500');
+ $response->headers->set('Age', '1000');
+ $response->expire();
+ $this->assertEquals(1000, $response->headers->get('Age'), '->expire() does nothing when the response is already stale/expired');
+
+ $response = new Response();
+ $response->expire();
+ $this->assertFalse($response->headers->has('Age'), '->expire() does nothing when the response does not include freshness information');
+
+ $response = new Response();
+ $response->headers->set('Expires', -1);
+ $response->expire();
+ $this->assertNull($response->headers->get('Age'), '->expire() does not set the Age when the response is expired');
+
+ $response = new Response();
+ $response->headers->set('Expires', date(DATE_RFC2822, time() + 600));
+ $response->expire();
+ $this->assertNull($response->headers->get('Expires'), '->expire() removes the Expires header when the response is fresh');
+ }
+
+ public function testGetTtl()
+ {
+ $response = new Response();
+ $this->assertNull($response->getTtl(), '->getTtl() returns null when no Expires or Cache-Control headers are present');
+
+ $response = new Response();
+ $response->headers->set('Expires', $this->createDateTimeOneHourLater()->format(DATE_RFC2822));
+ $this->assertEquals(3600, $response->getTtl(), '->getTtl() uses the Expires header when no max-age is present');
+
+ $response = new Response();
+ $response->headers->set('Expires', $this->createDateTimeOneHourAgo()->format(DATE_RFC2822));
+ $this->assertLessThan(0, $response->getTtl(), '->getTtl() returns negative values when Expires is in past');
+
+ $response = new Response();
+ $response->headers->set('Expires', $response->getDate()->format(DATE_RFC2822));
+ $response->headers->set('Age', 0);
+ $this->assertSame(0, $response->getTtl(), '->getTtl() correctly handles zero');
+
+ $response = new Response();
+ $response->headers->set('Cache-Control', 'max-age=60');
+ $this->assertEquals(60, $response->getTtl(), '->getTtl() uses Cache-Control max-age when present');
+ }
+
+ public function testSetClientTtl()
+ {
+ $response = new Response();
+ $response->setClientTtl(10);
+
+ $this->assertEquals($response->getMaxAge(), $response->getAge() + 10);
+ }
+
+ public function testGetSetProtocolVersion()
+ {
+ $response = new Response();
+
+ $this->assertEquals('1.0', $response->getProtocolVersion());
+
+ $response->setProtocolVersion('1.1');
+
+ $this->assertEquals('1.1', $response->getProtocolVersion());
+ }
+
+ public function testGetVary()
+ {
+ $response = new Response();
+ $this->assertEquals([], $response->getVary(), '->getVary() returns an empty array if no Vary header is present');
+
+ $response = new Response();
+ $response->headers->set('Vary', 'Accept-Language');
+ $this->assertEquals(['Accept-Language'], $response->getVary(), '->getVary() parses a single header name value');
+
+ $response = new Response();
+ $response->headers->set('Vary', 'Accept-Language User-Agent X-Foo');
+ $this->assertEquals(['Accept-Language', 'User-Agent', 'X-Foo'], $response->getVary(), '->getVary() parses multiple header name values separated by spaces');
+
+ $response = new Response();
+ $response->headers->set('Vary', 'Accept-Language,User-Agent, X-Foo');
+ $this->assertEquals(['Accept-Language', 'User-Agent', 'X-Foo'], $response->getVary(), '->getVary() parses multiple header name values separated by commas');
+
+ $vary = ['Accept-Language', 'User-Agent', 'X-foo'];
+
+ $response = new Response();
+ $response->headers->set('Vary', $vary);
+ $this->assertEquals($vary, $response->getVary(), '->getVary() parses multiple header name values in arrays');
+
+ $response = new Response();
+ $response->headers->set('Vary', 'Accept-Language, User-Agent, X-foo');
+ $this->assertEquals($vary, $response->getVary(), '->getVary() parses multiple header name values in arrays');
+ }
+
+ public function testSetVary()
+ {
+ $response = new Response();
+ $response->setVary('Accept-Language');
+ $this->assertEquals(['Accept-Language'], $response->getVary());
+
+ $response->setVary('Accept-Language, User-Agent');
+ $this->assertEquals(['Accept-Language', 'User-Agent'], $response->getVary(), '->setVary() replace the vary header by default');
+
+ $response->setVary('X-Foo', false);
+ $this->assertEquals(['Accept-Language', 'User-Agent', 'X-Foo'], $response->getVary(), '->setVary() doesn\'t wipe out earlier Vary headers if replace is set to false');
+ }
+
+ public function testDefaultContentType()
+ {
+ $headerMock = $this->getMockBuilder('Symfony\Component\HttpFoundation\ResponseHeaderBag')->setMethods(['set'])->getMock();
+ $headerMock->expects($this->at(0))
+ ->method('set')
+ ->with('Content-Type', 'text/html');
+ $headerMock->expects($this->at(1))
+ ->method('set')
+ ->with('Content-Type', 'text/html; charset=UTF-8');
+
+ $response = new Response('foo');
+ $response->headers = $headerMock;
+
+ $response->prepare(new Request());
+ }
+
+ public function testContentTypeCharset()
+ {
+ $response = new Response();
+ $response->headers->set('Content-Type', 'text/css');
+
+ // force fixContentType() to be called
+ $response->prepare(new Request());
+
+ $this->assertEquals('text/css; charset=UTF-8', $response->headers->get('Content-Type'));
+ }
+
+ public function testPrepareDoesNothingIfContentTypeIsSet()
+ {
+ $response = new Response('foo');
+ $response->headers->set('Content-Type', 'text/plain');
+
+ $response->prepare(new Request());
+
+ $this->assertEquals('text/plain; charset=UTF-8', $response->headers->get('content-type'));
+ }
+
+ public function testPrepareDoesNothingIfRequestFormatIsNotDefined()
+ {
+ $response = new Response('foo');
+
+ $response->prepare(new Request());
+
+ $this->assertEquals('text/html; charset=UTF-8', $response->headers->get('content-type'));
+ }
+
+ public function testPrepareSetContentType()
+ {
+ $response = new Response('foo');
+ $request = Request::create('/');
+ $request->setRequestFormat('json');
+
+ $response->prepare($request);
+
+ $this->assertEquals('application/json', $response->headers->get('content-type'));
+ }
+
+ public function testPrepareRemovesContentForHeadRequests()
+ {
+ $response = new Response('foo');
+ $request = Request::create('/', 'HEAD');
+
+ $length = 12345;
+ $response->headers->set('Content-Length', $length);
+ $response->prepare($request);
+
+ $this->assertEquals('', $response->getContent());
+ $this->assertEquals($length, $response->headers->get('Content-Length'), 'Content-Length should be as if it was GET; see RFC2616 14.13');
+ }
+
+ public function testPrepareRemovesContentForInformationalResponse()
+ {
+ $response = new Response('foo');
+ $request = Request::create('/');
+
+ $response->setContent('content');
+ $response->setStatusCode(101);
+ $response->prepare($request);
+ $this->assertEquals('', $response->getContent());
+ $this->assertFalse($response->headers->has('Content-Type'));
+ $this->assertFalse($response->headers->has('Content-Type'));
+
+ $response->setContent('content');
+ $response->setStatusCode(304);
+ $response->prepare($request);
+ $this->assertEquals('', $response->getContent());
+ $this->assertFalse($response->headers->has('Content-Type'));
+ $this->assertFalse($response->headers->has('Content-Length'));
+ }
+
+ public function testPrepareRemovesContentLength()
+ {
+ $response = new Response('foo');
+ $request = Request::create('/');
+
+ $response->headers->set('Content-Length', 12345);
+ $response->prepare($request);
+ $this->assertEquals(12345, $response->headers->get('Content-Length'));
+
+ $response->headers->set('Transfer-Encoding', 'chunked');
+ $response->prepare($request);
+ $this->assertFalse($response->headers->has('Content-Length'));
+ }
+
+ public function testPrepareSetsPragmaOnHttp10Only()
+ {
+ $request = Request::create('/', 'GET');
+ $request->server->set('SERVER_PROTOCOL', 'HTTP/1.0');
+
+ $response = new Response('foo');
+ $response->prepare($request);
+ $this->assertEquals('no-cache', $response->headers->get('pragma'));
+ $this->assertEquals('-1', $response->headers->get('expires'));
+
+ $request->server->set('SERVER_PROTOCOL', 'HTTP/1.1');
+ $response = new Response('foo');
+ $response->prepare($request);
+ $this->assertFalse($response->headers->has('pragma'));
+ $this->assertFalse($response->headers->has('expires'));
+ }
+
+ public function testSetCache()
+ {
+ $response = new Response();
+ // ['etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public']
+ try {
+ $response->setCache(['wrong option' => 'value']);
+ $this->fail('->setCache() throws an InvalidArgumentException if an option is not supported');
+ } catch (\Exception $e) {
+ $this->assertInstanceOf('InvalidArgumentException', $e, '->setCache() throws an InvalidArgumentException if an option is not supported');
+ $this->assertContains('"wrong option"', $e->getMessage());
+ }
+
+ $options = ['etag' => '"whatever"'];
+ $response->setCache($options);
+ $this->assertEquals($response->getEtag(), '"whatever"');
+
+ $now = $this->createDateTimeNow();
+ $options = ['last_modified' => $now];
+ $response->setCache($options);
+ $this->assertEquals($response->getLastModified()->getTimestamp(), $now->getTimestamp());
+
+ $options = ['max_age' => 100];
+ $response->setCache($options);
+ $this->assertEquals($response->getMaxAge(), 100);
+
+ $options = ['s_maxage' => 200];
+ $response->setCache($options);
+ $this->assertEquals($response->getMaxAge(), 200);
+
+ $this->assertTrue($response->headers->hasCacheControlDirective('public'));
+ $this->assertFalse($response->headers->hasCacheControlDirective('private'));
+
+ $response->setCache(['public' => true]);
+ $this->assertTrue($response->headers->hasCacheControlDirective('public'));
+ $this->assertFalse($response->headers->hasCacheControlDirective('private'));
+
+ $response->setCache(['public' => false]);
+ $this->assertFalse($response->headers->hasCacheControlDirective('public'));
+ $this->assertTrue($response->headers->hasCacheControlDirective('private'));
+
+ $response->setCache(['private' => true]);
+ $this->assertFalse($response->headers->hasCacheControlDirective('public'));
+ $this->assertTrue($response->headers->hasCacheControlDirective('private'));
+
+ $response->setCache(['private' => false]);
+ $this->assertTrue($response->headers->hasCacheControlDirective('public'));
+ $this->assertFalse($response->headers->hasCacheControlDirective('private'));
+
+ $response->setCache(['immutable' => true]);
+ $this->assertTrue($response->headers->hasCacheControlDirective('immutable'));
+
+ $response->setCache(['immutable' => false]);
+ $this->assertFalse($response->headers->hasCacheControlDirective('immutable'));
+ }
+
+ public function testSendContent()
+ {
+ $response = new Response('test response rendering', 200);
+
+ ob_start();
+ $response->sendContent();
+ $string = ob_get_clean();
+ $this->assertContains('test response rendering', $string);
+ }
+
+ public function testSetPublic()
+ {
+ $response = new Response();
+ $response->setPublic();
+
+ $this->assertTrue($response->headers->hasCacheControlDirective('public'));
+ $this->assertFalse($response->headers->hasCacheControlDirective('private'));
+ }
+
+ public function testSetImmutable()
+ {
+ $response = new Response();
+ $response->setImmutable();
+
+ $this->assertTrue($response->headers->hasCacheControlDirective('immutable'));
+ }
+
+ public function testIsImmutable()
+ {
+ $response = new Response();
+ $response->setImmutable();
+
+ $this->assertTrue($response->isImmutable());
+ }
+
+ public function testSetExpires()
+ {
+ $response = new Response();
+ $response->setExpires(null);
+
+ $this->assertNull($response->getExpires(), '->setExpires() remove the header when passed null');
+
+ $now = $this->createDateTimeNow();
+ $response->setExpires($now);
+
+ $this->assertEquals($response->getExpires()->getTimestamp(), $now->getTimestamp());
+ }
+
+ public function testSetLastModified()
+ {
+ $response = new Response();
+ $response->setLastModified($this->createDateTimeNow());
+ $this->assertNotNull($response->getLastModified());
+
+ $response->setLastModified(null);
+ $this->assertNull($response->getLastModified());
+ }
+
+ public function testIsInvalid()
+ {
+ $response = new Response();
+
+ try {
+ $response->setStatusCode(99);
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertTrue($response->isInvalid());
+ }
+
+ try {
+ $response->setStatusCode(650);
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertTrue($response->isInvalid());
+ }
+
+ $response = new Response('', 200);
+ $this->assertFalse($response->isInvalid());
+ }
+
+ /**
+ * @dataProvider getStatusCodeFixtures
+ */
+ public function testSetStatusCode($code, $text, $expectedText)
+ {
+ $response = new Response();
+
+ $response->setStatusCode($code, $text);
+
+ $statusText = new \ReflectionProperty($response, 'statusText');
+ $statusText->setAccessible(true);
+
+ $this->assertEquals($expectedText, $statusText->getValue($response));
+ }
+
+ public function getStatusCodeFixtures()
+ {
+ return [
+ ['200', null, 'OK'],
+ ['200', false, ''],
+ ['200', 'foo', 'foo'],
+ ['199', null, 'unknown status'],
+ ['199', false, ''],
+ ['199', 'foo', 'foo'],
+ ];
+ }
+
+ public function testIsInformational()
+ {
+ $response = new Response('', 100);
+ $this->assertTrue($response->isInformational());
+
+ $response = new Response('', 200);
+ $this->assertFalse($response->isInformational());
+ }
+
+ public function testIsRedirectRedirection()
+ {
+ foreach ([301, 302, 303, 307] as $code) {
+ $response = new Response('', $code);
+ $this->assertTrue($response->isRedirection());
+ $this->assertTrue($response->isRedirect());
+ }
+
+ $response = new Response('', 304);
+ $this->assertTrue($response->isRedirection());
+ $this->assertFalse($response->isRedirect());
+
+ $response = new Response('', 200);
+ $this->assertFalse($response->isRedirection());
+ $this->assertFalse($response->isRedirect());
+
+ $response = new Response('', 404);
+ $this->assertFalse($response->isRedirection());
+ $this->assertFalse($response->isRedirect());
+
+ $response = new Response('', 301, ['Location' => '/good-uri']);
+ $this->assertFalse($response->isRedirect('/bad-uri'));
+ $this->assertTrue($response->isRedirect('/good-uri'));
+ }
+
+ public function testIsNotFound()
+ {
+ $response = new Response('', 404);
+ $this->assertTrue($response->isNotFound());
+
+ $response = new Response('', 200);
+ $this->assertFalse($response->isNotFound());
+ }
+
+ public function testIsEmpty()
+ {
+ foreach ([204, 304] as $code) {
+ $response = new Response('', $code);
+ $this->assertTrue($response->isEmpty());
+ }
+
+ $response = new Response('', 200);
+ $this->assertFalse($response->isEmpty());
+ }
+
+ public function testIsForbidden()
+ {
+ $response = new Response('', 403);
+ $this->assertTrue($response->isForbidden());
+
+ $response = new Response('', 200);
+ $this->assertFalse($response->isForbidden());
+ }
+
+ public function testIsOk()
+ {
+ $response = new Response('', 200);
+ $this->assertTrue($response->isOk());
+
+ $response = new Response('', 404);
+ $this->assertFalse($response->isOk());
+ }
+
+ public function testIsServerOrClientError()
+ {
+ $response = new Response('', 404);
+ $this->assertTrue($response->isClientError());
+ $this->assertFalse($response->isServerError());
+
+ $response = new Response('', 500);
+ $this->assertFalse($response->isClientError());
+ $this->assertTrue($response->isServerError());
+ }
+
+ public function testHasVary()
+ {
+ $response = new Response();
+ $this->assertFalse($response->hasVary());
+
+ $response->setVary('User-Agent');
+ $this->assertTrue($response->hasVary());
+ }
+
+ public function testSetEtag()
+ {
+ $response = new Response('', 200, ['ETag' => '"12345"']);
+ $response->setEtag();
+
+ $this->assertNull($response->headers->get('Etag'), '->setEtag() removes Etags when call with null');
+ }
+
+ /**
+ * @dataProvider validContentProvider
+ */
+ public function testSetContent($content)
+ {
+ $response = new Response();
+ $response->setContent($content);
+ $this->assertEquals((string) $content, $response->getContent());
+ }
+
+ /**
+ * @expectedException \UnexpectedValueException
+ * @dataProvider invalidContentProvider
+ */
+ public function testSetContentInvalid($content)
+ {
+ $response = new Response();
+ $response->setContent($content);
+ }
+
+ public function testSettersAreChainable()
+ {
+ $response = new Response();
+
+ $setters = [
+ 'setProtocolVersion' => '1.0',
+ 'setCharset' => 'UTF-8',
+ 'setPublic' => null,
+ 'setPrivate' => null,
+ 'setDate' => $this->createDateTimeNow(),
+ 'expire' => null,
+ 'setMaxAge' => 1,
+ 'setSharedMaxAge' => 1,
+ 'setTtl' => 1,
+ 'setClientTtl' => 1,
+ ];
+
+ foreach ($setters as $setter => $arg) {
+ $this->assertEquals($response, $response->{$setter}($arg));
+ }
+ }
+
+ public function testNoDeprecationsAreTriggered()
+ {
+ new DefaultResponse();
+ $this->getMockBuilder(Response::class)->getMock();
+
+ // we just need to ensure that subclasses of Response can be created without any deprecations
+ // being triggered if the subclass does not override any final methods
+ $this->addToAssertionCount(1);
+ }
+
+ public function validContentProvider()
+ {
+ return [
+ 'obj' => [new StringableObject()],
+ 'string' => ['Foo'],
+ 'int' => [2],
+ ];
+ }
+
+ public function invalidContentProvider()
+ {
+ return [
+ 'obj' => [new \stdClass()],
+ 'array' => [[]],
+ 'bool' => [true, '1'],
+ ];
+ }
+
+ protected function createDateTimeOneHourAgo()
+ {
+ return $this->createDateTimeNow()->sub(new \DateInterval('PT1H'));
+ }
+
+ protected function createDateTimeOneHourLater()
+ {
+ return $this->createDateTimeNow()->add(new \DateInterval('PT1H'));
+ }
+
+ protected function createDateTimeNow()
+ {
+ $date = new \DateTime();
+
+ return $date->setTimestamp(time());
+ }
+
+ protected function provideResponse()
+ {
+ return new Response();
+ }
+
+ /**
+ * @see http://github.com/zendframework/zend-diactoros for the canonical source repository
+ *
+ * @author Fábio Pacheco
+ * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
+ */
+ public function ianaCodesReasonPhrasesProvider()
+ {
+ if (!\in_array('https', stream_get_wrappers(), true)) {
+ $this->markTestSkipped('The "https" wrapper is not available');
+ }
+
+ $ianaHttpStatusCodes = new \DOMDocument();
+
+ $context = stream_context_create([
+ 'http' => [
+ 'method' => 'GET',
+ 'timeout' => 30,
+ 'user_agent' => __METHOD__,
+ ],
+ ]);
+
+ $ianaHttpStatusCodes->loadXML(file_get_contents('https://www.iana.org/assignments/http-status-codes/http-status-codes.xml', false, $context));
+ if (!$ianaHttpStatusCodes->relaxNGValidate(__DIR__.'/schema/http-status-codes.rng')) {
+ self::fail('Invalid IANA\'s HTTP status code list.');
+ }
+
+ $ianaCodesReasonPhrases = [];
+
+ $xpath = new \DOMXPath($ianaHttpStatusCodes);
+ $xpath->registerNamespace('ns', 'http://www.iana.org/assignments');
+
+ $records = $xpath->query('//ns:record');
+ foreach ($records as $record) {
+ $value = $xpath->query('.//ns:value', $record)->item(0)->nodeValue;
+ $description = $xpath->query('.//ns:description', $record)->item(0)->nodeValue;
+
+ if (\in_array($description, ['Unassigned', '(Unused)'], true)) {
+ continue;
+ }
+
+ if (preg_match('/^([0-9]+)\s*\-\s*([0-9]+)$/', $value, $matches)) {
+ for ($value = $matches[1]; $value <= $matches[2]; ++$value) {
+ $ianaCodesReasonPhrases[] = [$value, $description];
+ }
+ } else {
+ $ianaCodesReasonPhrases[] = [$value, $description];
+ }
+ }
+
+ return $ianaCodesReasonPhrases;
+ }
+
+ /**
+ * @dataProvider ianaCodesReasonPhrasesProvider
+ */
+ public function testReasonPhraseDefaultsAgainstIana($code, $reasonPhrase)
+ {
+ $this->assertEquals($reasonPhrase, Response::$statusTexts[$code]);
+ }
+}
+
+class StringableObject
+{
+ public function __toString()
+ {
+ return 'Foo';
+ }
+}
+
+class DefaultResponse extends Response
+{
+}
+
+class ExtendedResponse extends Response
+{
+ public function setLastModified(\DateTime $date = null)
+ {
+ }
+
+ public function getDate()
+ {
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/ResponseTestCase.php b/vendor/symfony/http-foundation/Tests/ResponseTestCase.php
new file mode 100755
index 0000000..4ead34c
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ResponseTestCase.php
@@ -0,0 +1,89 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+
+abstract class ResponseTestCase extends TestCase
+{
+ public function testNoCacheControlHeaderOnAttachmentUsingHTTPSAndMSIE()
+ {
+ // Check for HTTPS and IE 8
+ $request = new Request();
+ $request->server->set('HTTPS', true);
+ $request->server->set('HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)');
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertFalse($response->headers->has('Cache-Control'));
+
+ // Check for IE 10 and HTTPS
+ $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)');
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertTrue($response->headers->has('Cache-Control'));
+
+ // Check for IE 9 and HTTPS
+ $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)');
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertTrue($response->headers->has('Cache-Control'));
+
+ // Check for IE 9 and HTTP
+ $request->server->set('HTTPS', false);
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertTrue($response->headers->has('Cache-Control'));
+
+ // Check for IE 8 and HTTP
+ $request->server->set('HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)');
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertTrue($response->headers->has('Cache-Control'));
+
+ // Check for non-IE and HTTPS
+ $request->server->set('HTTPS', true);
+ $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17');
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertTrue($response->headers->has('Cache-Control'));
+
+ // Check for non-IE and HTTP
+ $request->server->set('HTTPS', false);
+
+ $response = $this->provideResponse();
+ $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"');
+ $response->prepare($request);
+
+ $this->assertTrue($response->headers->has('Cache-Control'));
+ }
+
+ abstract protected function provideResponse();
+}
diff --git a/vendor/symfony/http-foundation/Tests/ServerBagTest.php b/vendor/symfony/http-foundation/Tests/ServerBagTest.php
new file mode 100755
index 0000000..0663b11
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/ServerBagTest.php
@@ -0,0 +1,170 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\ServerBag;
+
+/**
+ * ServerBagTest.
+ *
+ * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
+ */
+class ServerBagTest extends TestCase
+{
+ public function testShouldExtractHeadersFromServerArray()
+ {
+ $server = [
+ 'SOME_SERVER_VARIABLE' => 'value',
+ 'SOME_SERVER_VARIABLE2' => 'value',
+ 'ROOT' => 'value',
+ 'HTTP_CONTENT_TYPE' => 'text/html',
+ 'HTTP_CONTENT_LENGTH' => '0',
+ 'HTTP_ETAG' => 'asdf',
+ 'PHP_AUTH_USER' => 'foo',
+ 'PHP_AUTH_PW' => 'bar',
+ ];
+
+ $bag = new ServerBag($server);
+
+ $this->assertEquals([
+ 'CONTENT_TYPE' => 'text/html',
+ 'CONTENT_LENGTH' => '0',
+ 'ETAG' => 'asdf',
+ 'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'),
+ 'PHP_AUTH_USER' => 'foo',
+ 'PHP_AUTH_PW' => 'bar',
+ ], $bag->getHeaders());
+ }
+
+ public function testHttpPasswordIsOptional()
+ {
+ $bag = new ServerBag(['PHP_AUTH_USER' => 'foo']);
+
+ $this->assertEquals([
+ 'AUTHORIZATION' => 'Basic '.base64_encode('foo:'),
+ 'PHP_AUTH_USER' => 'foo',
+ 'PHP_AUTH_PW' => '',
+ ], $bag->getHeaders());
+ }
+
+ public function testHttpBasicAuthWithPhpCgi()
+ {
+ $bag = new ServerBag(['HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:bar')]);
+
+ $this->assertEquals([
+ 'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'),
+ 'PHP_AUTH_USER' => 'foo',
+ 'PHP_AUTH_PW' => 'bar',
+ ], $bag->getHeaders());
+ }
+
+ public function testHttpBasicAuthWithPhpCgiBogus()
+ {
+ $bag = new ServerBag(['HTTP_AUTHORIZATION' => 'Basic_'.base64_encode('foo:bar')]);
+
+ // Username and passwords should not be set as the header is bogus
+ $headers = $bag->getHeaders();
+ $this->assertArrayNotHasKey('PHP_AUTH_USER', $headers);
+ $this->assertArrayNotHasKey('PHP_AUTH_PW', $headers);
+ }
+
+ public function testHttpBasicAuthWithPhpCgiRedirect()
+ {
+ $bag = new ServerBag(['REDIRECT_HTTP_AUTHORIZATION' => 'Basic '.base64_encode('username:pass:word')]);
+
+ $this->assertEquals([
+ 'AUTHORIZATION' => 'Basic '.base64_encode('username:pass:word'),
+ 'PHP_AUTH_USER' => 'username',
+ 'PHP_AUTH_PW' => 'pass:word',
+ ], $bag->getHeaders());
+ }
+
+ public function testHttpBasicAuthWithPhpCgiEmptyPassword()
+ {
+ $bag = new ServerBag(['HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:')]);
+
+ $this->assertEquals([
+ 'AUTHORIZATION' => 'Basic '.base64_encode('foo:'),
+ 'PHP_AUTH_USER' => 'foo',
+ 'PHP_AUTH_PW' => '',
+ ], $bag->getHeaders());
+ }
+
+ public function testHttpDigestAuthWithPhpCgi()
+ {
+ $digest = 'Digest username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"';
+ $bag = new ServerBag(['HTTP_AUTHORIZATION' => $digest]);
+
+ $this->assertEquals([
+ 'AUTHORIZATION' => $digest,
+ 'PHP_AUTH_DIGEST' => $digest,
+ ], $bag->getHeaders());
+ }
+
+ public function testHttpDigestAuthWithPhpCgiBogus()
+ {
+ $digest = 'Digest_username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"';
+ $bag = new ServerBag(['HTTP_AUTHORIZATION' => $digest]);
+
+ // Username and passwords should not be set as the header is bogus
+ $headers = $bag->getHeaders();
+ $this->assertArrayNotHasKey('PHP_AUTH_USER', $headers);
+ $this->assertArrayNotHasKey('PHP_AUTH_PW', $headers);
+ }
+
+ public function testHttpDigestAuthWithPhpCgiRedirect()
+ {
+ $digest = 'Digest username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"';
+ $bag = new ServerBag(['REDIRECT_HTTP_AUTHORIZATION' => $digest]);
+
+ $this->assertEquals([
+ 'AUTHORIZATION' => $digest,
+ 'PHP_AUTH_DIGEST' => $digest,
+ ], $bag->getHeaders());
+ }
+
+ public function testOAuthBearerAuth()
+ {
+ $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo';
+ $bag = new ServerBag(['HTTP_AUTHORIZATION' => $headerContent]);
+
+ $this->assertEquals([
+ 'AUTHORIZATION' => $headerContent,
+ ], $bag->getHeaders());
+ }
+
+ public function testOAuthBearerAuthWithRedirect()
+ {
+ $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo';
+ $bag = new ServerBag(['REDIRECT_HTTP_AUTHORIZATION' => $headerContent]);
+
+ $this->assertEquals([
+ 'AUTHORIZATION' => $headerContent,
+ ], $bag->getHeaders());
+ }
+
+ /**
+ * @see https://github.com/symfony/symfony/issues/17345
+ */
+ public function testItDoesNotOverwriteTheAuthorizationHeaderIfItIsAlreadySet()
+ {
+ $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo';
+ $bag = new ServerBag(['PHP_AUTH_USER' => 'foo', 'HTTP_AUTHORIZATION' => $headerContent]);
+
+ $this->assertEquals([
+ 'AUTHORIZATION' => $headerContent,
+ 'PHP_AUTH_USER' => 'foo',
+ 'PHP_AUTH_PW' => '',
+ ], $bag->getHeaders());
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php
new file mode 100755
index 0000000..3f2f7b3
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php
@@ -0,0 +1,186 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Attribute;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+
+/**
+ * Tests AttributeBag.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class AttributeBagTest extends TestCase
+{
+ private $array = [];
+
+ /**
+ * @var AttributeBag
+ */
+ private $bag;
+
+ protected function setUp()
+ {
+ $this->array = [
+ 'hello' => 'world',
+ 'always' => 'be happy',
+ 'user.login' => 'drak',
+ 'csrf.token' => [
+ 'a' => '1234',
+ 'b' => '4321',
+ ],
+ 'category' => [
+ 'fishing' => [
+ 'first' => 'cod',
+ 'second' => 'sole',
+ ],
+ ],
+ ];
+ $this->bag = new AttributeBag('_sf2');
+ $this->bag->initialize($this->array);
+ }
+
+ protected function tearDown()
+ {
+ $this->bag = null;
+ $this->array = [];
+ }
+
+ public function testInitialize()
+ {
+ $bag = new AttributeBag();
+ $bag->initialize($this->array);
+ $this->assertEquals($this->array, $bag->all());
+ $array = ['should' => 'change'];
+ $bag->initialize($array);
+ $this->assertEquals($array, $bag->all());
+ }
+
+ public function testGetStorageKey()
+ {
+ $this->assertEquals('_sf2', $this->bag->getStorageKey());
+ $attributeBag = new AttributeBag('test');
+ $this->assertEquals('test', $attributeBag->getStorageKey());
+ }
+
+ public function testGetSetName()
+ {
+ $this->assertEquals('attributes', $this->bag->getName());
+ $this->bag->setName('foo');
+ $this->assertEquals('foo', $this->bag->getName());
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testHas($key, $value, $exists)
+ {
+ $this->assertEquals($exists, $this->bag->has($key));
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testGet($key, $value, $expected)
+ {
+ $this->assertEquals($value, $this->bag->get($key));
+ }
+
+ public function testGetDefaults()
+ {
+ $this->assertNull($this->bag->get('user2.login'));
+ $this->assertEquals('default', $this->bag->get('user2.login', 'default'));
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testSet($key, $value, $expected)
+ {
+ $this->bag->set($key, $value);
+ $this->assertEquals($value, $this->bag->get($key));
+ }
+
+ public function testAll()
+ {
+ $this->assertEquals($this->array, $this->bag->all());
+
+ $this->bag->set('hello', 'fabien');
+ $array = $this->array;
+ $array['hello'] = 'fabien';
+ $this->assertEquals($array, $this->bag->all());
+ }
+
+ public function testReplace()
+ {
+ $array = [];
+ $array['name'] = 'jack';
+ $array['foo.bar'] = 'beep';
+ $this->bag->replace($array);
+ $this->assertEquals($array, $this->bag->all());
+ $this->assertNull($this->bag->get('hello'));
+ $this->assertNull($this->bag->get('always'));
+ $this->assertNull($this->bag->get('user.login'));
+ }
+
+ public function testRemove()
+ {
+ $this->assertEquals('world', $this->bag->get('hello'));
+ $this->bag->remove('hello');
+ $this->assertNull($this->bag->get('hello'));
+
+ $this->assertEquals('be happy', $this->bag->get('always'));
+ $this->bag->remove('always');
+ $this->assertNull($this->bag->get('always'));
+
+ $this->assertEquals('drak', $this->bag->get('user.login'));
+ $this->bag->remove('user.login');
+ $this->assertNull($this->bag->get('user.login'));
+ }
+
+ public function testClear()
+ {
+ $this->bag->clear();
+ $this->assertEquals([], $this->bag->all());
+ }
+
+ public function attributesProvider()
+ {
+ return [
+ ['hello', 'world', true],
+ ['always', 'be happy', true],
+ ['user.login', 'drak', true],
+ ['csrf.token', ['a' => '1234', 'b' => '4321'], true],
+ ['category', ['fishing' => ['first' => 'cod', 'second' => 'sole']], true],
+ ['user2.login', null, false],
+ ['never', null, false],
+ ['bye', null, false],
+ ['bye/for/now', null, false],
+ ];
+ }
+
+ public function testGetIterator()
+ {
+ $i = 0;
+ foreach ($this->bag as $key => $val) {
+ $this->assertEquals($this->array[$key], $val);
+ ++$i;
+ }
+
+ $this->assertEquals(\count($this->array), $i);
+ }
+
+ public function testCount()
+ {
+ $this->assertCount(\count($this->array), $this->bag);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php
new file mode 100755
index 0000000..6b4bb17
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php
@@ -0,0 +1,204 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Attribute;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag;
+
+/**
+ * Tests NamespacedAttributeBag.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NamespacedAttributeBagTest extends TestCase
+{
+ private $array = [];
+
+ /**
+ * @var NamespacedAttributeBag
+ */
+ private $bag;
+
+ protected function setUp()
+ {
+ $this->array = [
+ 'hello' => 'world',
+ 'always' => 'be happy',
+ 'user.login' => 'drak',
+ 'csrf.token' => [
+ 'a' => '1234',
+ 'b' => '4321',
+ ],
+ 'category' => [
+ 'fishing' => [
+ 'first' => 'cod',
+ 'second' => 'sole',
+ ],
+ ],
+ ];
+ $this->bag = new NamespacedAttributeBag('_sf2', '/');
+ $this->bag->initialize($this->array);
+ }
+
+ protected function tearDown()
+ {
+ $this->bag = null;
+ $this->array = [];
+ }
+
+ public function testInitialize()
+ {
+ $bag = new NamespacedAttributeBag();
+ $bag->initialize($this->array);
+ $this->assertEquals($this->array, $this->bag->all());
+ $array = ['should' => 'not stick'];
+ $bag->initialize($array);
+
+ // should have remained the same
+ $this->assertEquals($this->array, $this->bag->all());
+ }
+
+ public function testGetStorageKey()
+ {
+ $this->assertEquals('_sf2', $this->bag->getStorageKey());
+ $attributeBag = new NamespacedAttributeBag('test');
+ $this->assertEquals('test', $attributeBag->getStorageKey());
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testHas($key, $value, $exists)
+ {
+ $this->assertEquals($exists, $this->bag->has($key));
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testHasNoSideEffect($key, $value, $expected)
+ {
+ $expected = json_encode($this->bag->all());
+ $this->bag->has($key);
+
+ $this->assertEquals($expected, json_encode($this->bag->all()));
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testGet($key, $value, $expected)
+ {
+ $this->assertEquals($value, $this->bag->get($key));
+ }
+
+ public function testGetDefaults()
+ {
+ $this->assertNull($this->bag->get('user2.login'));
+ $this->assertEquals('default', $this->bag->get('user2.login', 'default'));
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testGetNoSideEffect($key, $value, $expected)
+ {
+ $expected = json_encode($this->bag->all());
+ $this->bag->get($key);
+
+ $this->assertEquals($expected, json_encode($this->bag->all()));
+ }
+
+ /**
+ * @dataProvider attributesProvider
+ */
+ public function testSet($key, $value, $expected)
+ {
+ $this->bag->set($key, $value);
+ $this->assertEquals($value, $this->bag->get($key));
+ }
+
+ public function testAll()
+ {
+ $this->assertEquals($this->array, $this->bag->all());
+
+ $this->bag->set('hello', 'fabien');
+ $array = $this->array;
+ $array['hello'] = 'fabien';
+ $this->assertEquals($array, $this->bag->all());
+ }
+
+ public function testReplace()
+ {
+ $array = [];
+ $array['name'] = 'jack';
+ $array['foo.bar'] = 'beep';
+ $this->bag->replace($array);
+ $this->assertEquals($array, $this->bag->all());
+ $this->assertNull($this->bag->get('hello'));
+ $this->assertNull($this->bag->get('always'));
+ $this->assertNull($this->bag->get('user.login'));
+ }
+
+ public function testRemove()
+ {
+ $this->assertEquals('world', $this->bag->get('hello'));
+ $this->bag->remove('hello');
+ $this->assertNull($this->bag->get('hello'));
+
+ $this->assertEquals('be happy', $this->bag->get('always'));
+ $this->bag->remove('always');
+ $this->assertNull($this->bag->get('always'));
+
+ $this->assertEquals('drak', $this->bag->get('user.login'));
+ $this->bag->remove('user.login');
+ $this->assertNull($this->bag->get('user.login'));
+ }
+
+ public function testRemoveExistingNamespacedAttribute()
+ {
+ $this->assertSame('cod', $this->bag->remove('category/fishing/first'));
+ }
+
+ public function testRemoveNonexistingNamespacedAttribute()
+ {
+ $this->assertNull($this->bag->remove('foo/bar/baz'));
+ }
+
+ public function testClear()
+ {
+ $this->bag->clear();
+ $this->assertEquals([], $this->bag->all());
+ }
+
+ public function attributesProvider()
+ {
+ return [
+ ['hello', 'world', true],
+ ['always', 'be happy', true],
+ ['user.login', 'drak', true],
+ ['csrf.token', ['a' => '1234', 'b' => '4321'], true],
+ ['csrf.token/a', '1234', true],
+ ['csrf.token/b', '4321', true],
+ ['category', ['fishing' => ['first' => 'cod', 'second' => 'sole']], true],
+ ['category/fishing', ['first' => 'cod', 'second' => 'sole'], true],
+ ['category/fishing/missing/first', null, false],
+ ['category/fishing/first', 'cod', true],
+ ['category/fishing/second', 'sole', true],
+ ['category/fishing/missing/second', null, false],
+ ['user2.login', null, false],
+ ['never', null, false],
+ ['bye', null, false],
+ ['bye/for/now', null, false],
+ ];
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php
new file mode 100755
index 0000000..b4e2c3a
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php
@@ -0,0 +1,161 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Flash;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag as FlashBag;
+
+/**
+ * AutoExpireFlashBagTest.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class AutoExpireFlashBagTest extends TestCase
+{
+ /**
+ * @var \Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag
+ */
+ private $bag;
+
+ protected $array = [];
+
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->bag = new FlashBag();
+ $this->array = ['new' => ['notice' => ['A previous flash message']]];
+ $this->bag->initialize($this->array);
+ }
+
+ protected function tearDown()
+ {
+ $this->bag = null;
+ parent::tearDown();
+ }
+
+ public function testInitialize()
+ {
+ $bag = new FlashBag();
+ $array = ['new' => ['notice' => ['A previous flash message']]];
+ $bag->initialize($array);
+ $this->assertEquals(['A previous flash message'], $bag->peek('notice'));
+ $array = ['new' => [
+ 'notice' => ['Something else'],
+ 'error' => ['a'],
+ ]];
+ $bag->initialize($array);
+ $this->assertEquals(['Something else'], $bag->peek('notice'));
+ $this->assertEquals(['a'], $bag->peek('error'));
+ }
+
+ public function testGetStorageKey()
+ {
+ $this->assertEquals('_symfony_flashes', $this->bag->getStorageKey());
+ $attributeBag = new FlashBag('test');
+ $this->assertEquals('test', $attributeBag->getStorageKey());
+ }
+
+ public function testGetSetName()
+ {
+ $this->assertEquals('flashes', $this->bag->getName());
+ $this->bag->setName('foo');
+ $this->assertEquals('foo', $this->bag->getName());
+ }
+
+ public function testPeek()
+ {
+ $this->assertEquals([], $this->bag->peek('non_existing'));
+ $this->assertEquals(['default'], $this->bag->peek('non_existing', ['default']));
+ $this->assertEquals(['A previous flash message'], $this->bag->peek('notice'));
+ $this->assertEquals(['A previous flash message'], $this->bag->peek('notice'));
+ }
+
+ public function testSet()
+ {
+ $this->bag->set('notice', 'Foo');
+ $this->assertEquals(['A previous flash message'], $this->bag->peek('notice'));
+ }
+
+ public function testHas()
+ {
+ $this->assertFalse($this->bag->has('nothing'));
+ $this->assertTrue($this->bag->has('notice'));
+ }
+
+ public function testKeys()
+ {
+ $this->assertEquals(['notice'], $this->bag->keys());
+ }
+
+ public function testPeekAll()
+ {
+ $array = [
+ 'new' => [
+ 'notice' => 'Foo',
+ 'error' => 'Bar',
+ ],
+ ];
+
+ $this->bag->initialize($array);
+ $this->assertEquals([
+ 'notice' => 'Foo',
+ 'error' => 'Bar',
+ ], $this->bag->peekAll()
+ );
+
+ $this->assertEquals([
+ 'notice' => 'Foo',
+ 'error' => 'Bar',
+ ], $this->bag->peekAll()
+ );
+ }
+
+ public function testGet()
+ {
+ $this->assertEquals([], $this->bag->get('non_existing'));
+ $this->assertEquals(['default'], $this->bag->get('non_existing', ['default']));
+ $this->assertEquals(['A previous flash message'], $this->bag->get('notice'));
+ $this->assertEquals([], $this->bag->get('notice'));
+ }
+
+ public function testSetAll()
+ {
+ $this->bag->setAll(['a' => 'first', 'b' => 'second']);
+ $this->assertFalse($this->bag->has('a'));
+ $this->assertFalse($this->bag->has('b'));
+ }
+
+ public function testAll()
+ {
+ $this->bag->set('notice', 'Foo');
+ $this->bag->set('error', 'Bar');
+ $this->assertEquals([
+ 'notice' => ['A previous flash message'],
+ ], $this->bag->all()
+ );
+
+ $this->assertEquals([], $this->bag->all());
+ }
+
+ public function testClear()
+ {
+ $this->assertEquals(['notice' => ['A previous flash message']], $this->bag->clear());
+ }
+
+ public function testDoNotRemoveTheNewFlashesWhenDisplayingTheExistingOnes()
+ {
+ $this->bag->add('success', 'Something');
+ $this->bag->all();
+
+ $this->assertEquals(['new' => ['success' => ['Something']], 'display' => []], $this->array);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php
new file mode 100755
index 0000000..6d8619e
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php
@@ -0,0 +1,157 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Flash;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+
+/**
+ * FlashBagTest.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class FlashBagTest extends TestCase
+{
+ /**
+ * @var \Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface
+ */
+ private $bag;
+
+ protected $array = [];
+
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->bag = new FlashBag();
+ $this->array = ['notice' => ['A previous flash message']];
+ $this->bag->initialize($this->array);
+ }
+
+ protected function tearDown()
+ {
+ $this->bag = null;
+ parent::tearDown();
+ }
+
+ public function testInitialize()
+ {
+ $bag = new FlashBag();
+ $bag->initialize($this->array);
+ $this->assertEquals($this->array, $bag->peekAll());
+ $array = ['should' => ['change']];
+ $bag->initialize($array);
+ $this->assertEquals($array, $bag->peekAll());
+ }
+
+ public function testGetStorageKey()
+ {
+ $this->assertEquals('_symfony_flashes', $this->bag->getStorageKey());
+ $attributeBag = new FlashBag('test');
+ $this->assertEquals('test', $attributeBag->getStorageKey());
+ }
+
+ public function testGetSetName()
+ {
+ $this->assertEquals('flashes', $this->bag->getName());
+ $this->bag->setName('foo');
+ $this->assertEquals('foo', $this->bag->getName());
+ }
+
+ public function testPeek()
+ {
+ $this->assertEquals([], $this->bag->peek('non_existing'));
+ $this->assertEquals(['default'], $this->bag->peek('not_existing', ['default']));
+ $this->assertEquals(['A previous flash message'], $this->bag->peek('notice'));
+ $this->assertEquals(['A previous flash message'], $this->bag->peek('notice'));
+ }
+
+ public function testAdd()
+ {
+ $tab = ['bar' => 'baz'];
+ $this->bag->add('string_message', 'lorem');
+ $this->bag->add('object_message', new \stdClass());
+ $this->bag->add('array_message', $tab);
+
+ $this->assertEquals(['lorem'], $this->bag->get('string_message'));
+ $this->assertEquals([new \stdClass()], $this->bag->get('object_message'));
+ $this->assertEquals([$tab], $this->bag->get('array_message'));
+ }
+
+ public function testGet()
+ {
+ $this->assertEquals([], $this->bag->get('non_existing'));
+ $this->assertEquals(['default'], $this->bag->get('not_existing', ['default']));
+ $this->assertEquals(['A previous flash message'], $this->bag->get('notice'));
+ $this->assertEquals([], $this->bag->get('notice'));
+ }
+
+ public function testAll()
+ {
+ $this->bag->set('notice', 'Foo');
+ $this->bag->set('error', 'Bar');
+ $this->assertEquals([
+ 'notice' => ['Foo'],
+ 'error' => ['Bar'], ], $this->bag->all()
+ );
+
+ $this->assertEquals([], $this->bag->all());
+ }
+
+ public function testSet()
+ {
+ $this->bag->set('notice', 'Foo');
+ $this->bag->set('notice', 'Bar');
+ $this->assertEquals(['Bar'], $this->bag->peek('notice'));
+ }
+
+ public function testHas()
+ {
+ $this->assertFalse($this->bag->has('nothing'));
+ $this->assertTrue($this->bag->has('notice'));
+ }
+
+ public function testKeys()
+ {
+ $this->assertEquals(['notice'], $this->bag->keys());
+ }
+
+ public function testSetAll()
+ {
+ $this->bag->add('one_flash', 'Foo');
+ $this->bag->add('another_flash', 'Bar');
+ $this->assertTrue($this->bag->has('one_flash'));
+ $this->assertTrue($this->bag->has('another_flash'));
+ $this->bag->setAll(['unique_flash' => 'FooBar']);
+ $this->assertFalse($this->bag->has('one_flash'));
+ $this->assertFalse($this->bag->has('another_flash'));
+ $this->assertSame(['unique_flash' => 'FooBar'], $this->bag->all());
+ $this->assertSame([], $this->bag->all());
+ }
+
+ public function testPeekAll()
+ {
+ $this->bag->set('notice', 'Foo');
+ $this->bag->set('error', 'Bar');
+ $this->assertEquals([
+ 'notice' => ['Foo'],
+ 'error' => ['Bar'],
+ ], $this->bag->peekAll()
+ );
+ $this->assertTrue($this->bag->has('notice'));
+ $this->assertTrue($this->bag->has('error'));
+ $this->assertEquals([
+ 'notice' => ['Foo'],
+ 'error' => ['Bar'],
+ ], $this->bag->peekAll()
+ );
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/SessionTest.php b/vendor/symfony/http-foundation/Tests/Session/SessionTest.php
new file mode 100755
index 0000000..afa00fc
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/SessionTest.php
@@ -0,0 +1,263 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Session;
+use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
+
+/**
+ * SessionTest.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Robert Schönthal <seroscho@googlemail.com>
+ * @author Drak <drak@zikula.org>
+ */
+class SessionTest extends TestCase
+{
+ /**
+ * @var \Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface
+ */
+ protected $storage;
+
+ /**
+ * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
+ */
+ protected $session;
+
+ protected function setUp()
+ {
+ $this->storage = new MockArraySessionStorage();
+ $this->session = new Session($this->storage, new AttributeBag(), new FlashBag());
+ }
+
+ protected function tearDown()
+ {
+ $this->storage = null;
+ $this->session = null;
+ }
+
+ public function testStart()
+ {
+ $this->assertEquals('', $this->session->getId());
+ $this->assertTrue($this->session->start());
+ $this->assertNotEquals('', $this->session->getId());
+ }
+
+ public function testIsStarted()
+ {
+ $this->assertFalse($this->session->isStarted());
+ $this->session->start();
+ $this->assertTrue($this->session->isStarted());
+ }
+
+ public function testSetId()
+ {
+ $this->assertEquals('', $this->session->getId());
+ $this->session->setId('0123456789abcdef');
+ $this->session->start();
+ $this->assertEquals('0123456789abcdef', $this->session->getId());
+ }
+
+ public function testSetIdAfterStart()
+ {
+ $this->session->start();
+ $id = $this->session->getId();
+
+ $e = null;
+ try {
+ $this->session->setId($id);
+ } catch (\Exception $e) {
+ }
+
+ $this->assertNull($e);
+
+ try {
+ $this->session->setId('different');
+ } catch (\Exception $e) {
+ }
+
+ $this->assertInstanceOf('\LogicException', $e);
+ }
+
+ public function testSetName()
+ {
+ $this->assertEquals('MOCKSESSID', $this->session->getName());
+ $this->session->setName('session.test.com');
+ $this->session->start();
+ $this->assertEquals('session.test.com', $this->session->getName());
+ }
+
+ public function testGet()
+ {
+ // tests defaults
+ $this->assertNull($this->session->get('foo'));
+ $this->assertEquals(1, $this->session->get('foo', 1));
+ }
+
+ /**
+ * @dataProvider setProvider
+ */
+ public function testSet($key, $value)
+ {
+ $this->session->set($key, $value);
+ $this->assertEquals($value, $this->session->get($key));
+ }
+
+ /**
+ * @dataProvider setProvider
+ */
+ public function testHas($key, $value)
+ {
+ $this->session->set($key, $value);
+ $this->assertTrue($this->session->has($key));
+ $this->assertFalse($this->session->has($key.'non_value'));
+ }
+
+ public function testReplace()
+ {
+ $this->session->replace(['happiness' => 'be good', 'symfony' => 'awesome']);
+ $this->assertEquals(['happiness' => 'be good', 'symfony' => 'awesome'], $this->session->all());
+ $this->session->replace([]);
+ $this->assertEquals([], $this->session->all());
+ }
+
+ /**
+ * @dataProvider setProvider
+ */
+ public function testAll($key, $value, $result)
+ {
+ $this->session->set($key, $value);
+ $this->assertEquals($result, $this->session->all());
+ }
+
+ /**
+ * @dataProvider setProvider
+ */
+ public function testClear($key, $value)
+ {
+ $this->session->set('hi', 'fabien');
+ $this->session->set($key, $value);
+ $this->session->clear();
+ $this->assertEquals([], $this->session->all());
+ }
+
+ public function setProvider()
+ {
+ return [
+ ['foo', 'bar', ['foo' => 'bar']],
+ ['foo.bar', 'too much beer', ['foo.bar' => 'too much beer']],
+ ['great', 'symfony is great', ['great' => 'symfony is great']],
+ ];
+ }
+
+ /**
+ * @dataProvider setProvider
+ */
+ public function testRemove($key, $value)
+ {
+ $this->session->set('hi.world', 'have a nice day');
+ $this->session->set($key, $value);
+ $this->session->remove($key);
+ $this->assertEquals(['hi.world' => 'have a nice day'], $this->session->all());
+ }
+
+ public function testInvalidate()
+ {
+ $this->session->set('invalidate', 123);
+ $this->session->invalidate();
+ $this->assertEquals([], $this->session->all());
+ }
+
+ public function testMigrate()
+ {
+ $this->session->set('migrate', 321);
+ $this->session->migrate();
+ $this->assertEquals(321, $this->session->get('migrate'));
+ }
+
+ public function testMigrateDestroy()
+ {
+ $this->session->set('migrate', 333);
+ $this->session->migrate(true);
+ $this->assertEquals(333, $this->session->get('migrate'));
+ }
+
+ public function testSave()
+ {
+ $this->session->start();
+ $this->session->save();
+
+ $this->assertFalse($this->session->isStarted());
+ }
+
+ public function testGetId()
+ {
+ $this->assertEquals('', $this->session->getId());
+ $this->session->start();
+ $this->assertNotEquals('', $this->session->getId());
+ }
+
+ public function testGetFlashBag()
+ {
+ $this->assertInstanceOf('Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface', $this->session->getFlashBag());
+ }
+
+ public function testGetIterator()
+ {
+ $attributes = ['hello' => 'world', 'symfony' => 'rocks'];
+ foreach ($attributes as $key => $val) {
+ $this->session->set($key, $val);
+ }
+
+ $i = 0;
+ foreach ($this->session as $key => $val) {
+ $this->assertEquals($attributes[$key], $val);
+ ++$i;
+ }
+
+ $this->assertEquals(\count($attributes), $i);
+ }
+
+ public function testGetCount()
+ {
+ $this->session->set('hello', 'world');
+ $this->session->set('symfony', 'rocks');
+
+ $this->assertCount(2, $this->session);
+ }
+
+ public function testGetMeta()
+ {
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\MetadataBag', $this->session->getMetadataBag());
+ }
+
+ public function testIsEmpty()
+ {
+ $this->assertTrue($this->session->isEmpty());
+
+ $this->session->set('hello', 'world');
+ $this->assertFalse($this->session->isEmpty());
+
+ $this->session->remove('hello');
+ $this->assertTrue($this->session->isEmpty());
+
+ $flash = $this->session->getFlashBag();
+ $flash->set('hello', 'world');
+ $this->assertFalse($this->session->isEmpty());
+
+ $flash->get('hello');
+ $this->assertTrue($this->session->isEmpty());
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
new file mode 100755
index 0000000..98bc67b
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @requires PHP 7.0
+ */
+class AbstractSessionHandlerTest extends TestCase
+{
+ private static $server;
+
+ public static function setUpBeforeClass()
+ {
+ $spec = [
+ 1 => ['file', '/dev/null', 'w'],
+ 2 => ['file', '/dev/null', 'w'],
+ ];
+ if (!self::$server = @proc_open('exec php -S localhost:8053', $spec, $pipes, __DIR__.'/Fixtures')) {
+ self::markTestSkipped('PHP server unable to start.');
+ }
+ sleep(1);
+ }
+
+ public static function tearDownAfterClass()
+ {
+ if (self::$server) {
+ proc_terminate(self::$server);
+ proc_close(self::$server);
+ }
+ }
+
+ /**
+ * @dataProvider provideSession
+ */
+ public function testSession($fixture)
+ {
+ $context = ['http' => ['header' => "Cookie: sid=123abc\r\n"]];
+ $context = stream_context_create($context);
+ $result = file_get_contents(sprintf('http://localhost:8053/%s.php', $fixture), false, $context);
+
+ $this->assertStringEqualsFile(__DIR__.sprintf('/Fixtures/%s.expected', $fixture), $result);
+ }
+
+ public function provideSession()
+ {
+ foreach (glob(__DIR__.'/Fixtures/*.php') as $file) {
+ yield [pathinfo($file, PATHINFO_FILENAME)];
+ }
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc
new file mode 100755
index 0000000..7a064c7
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc
@@ -0,0 +1,151 @@
+<?php
+
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler;
+
+$parent = __DIR__;
+while (!@file_exists($parent.'/vendor/autoload.php')) {
+ if (!@file_exists($parent)) {
+ // open_basedir restriction in effect
+ break;
+ }
+ if ($parent === dirname($parent)) {
+ echo "vendor/autoload.php not found\n";
+ exit(1);
+ }
+
+ $parent = dirname($parent);
+}
+
+require $parent.'/vendor/autoload.php';
+
+error_reporting(-1);
+ini_set('html_errors', 0);
+ini_set('display_errors', 1);
+ini_set('session.gc_probability', 0);
+ini_set('session.serialize_handler', 'php');
+ini_set('session.cookie_lifetime', 0);
+ini_set('session.cookie_domain', '');
+ini_set('session.cookie_secure', '');
+ini_set('session.cookie_httponly', '');
+ini_set('session.use_cookies', 1);
+ini_set('session.use_only_cookies', 1);
+ini_set('session.cache_expire', 180);
+ini_set('session.cookie_path', '/');
+ini_set('session.cookie_domain', '');
+ini_set('session.cookie_secure', 1);
+ini_set('session.cookie_httponly', 1);
+ini_set('session.use_strict_mode', 1);
+ini_set('session.lazy_write', 1);
+ini_set('session.name', 'sid');
+ini_set('session.save_path', __DIR__);
+ini_set('session.cache_limiter', '');
+
+header_remove('X-Powered-By');
+header('Content-Type: text/plain; charset=utf-8');
+
+register_shutdown_function(function () {
+ echo "\n";
+ session_write_close();
+ print_r(headers_list());
+ echo "shutdown\n";
+});
+ob_start();
+
+class TestSessionHandler extends AbstractSessionHandler
+{
+ private $data;
+
+ public function __construct($data = '')
+ {
+ $this->data = $data;
+ }
+
+ public function open($path, $name)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::open($path, $name);
+ }
+
+ public function validateId($sessionId)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::validateId($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($sessionId)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::read($sessionId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateTimestamp($sessionId, $data)
+ {
+ echo __FUNCTION__, "\n";
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($sessionId, $data)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::write($sessionId, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function destroy($sessionId)
+ {
+ echo __FUNCTION__, "\n";
+
+ return parent::destroy($sessionId);
+ }
+
+ public function close()
+ {
+ echo __FUNCTION__, "\n";
+
+ return true;
+ }
+
+ public function gc($maxLifetime)
+ {
+ echo __FUNCTION__, "\n";
+
+ return true;
+ }
+
+ protected function doRead($sessionId)
+ {
+ echo __FUNCTION__.': ', $this->data, "\n";
+
+ return $this->data;
+ }
+
+ protected function doWrite($sessionId, $data)
+ {
+ echo __FUNCTION__.': ', $data, "\n";
+
+ return true;
+ }
+
+ protected function doDestroy($sessionId)
+ {
+ echo __FUNCTION__, "\n";
+
+ return true;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected
new file mode 100755
index 0000000..8203714
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected
@@ -0,0 +1,17 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+
+write
+destroy
+doDestroy
+close
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: max-age=10800, private, must-revalidate
+ [2] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php
new file mode 100755
index 0000000..3cfc125
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php
@@ -0,0 +1,8 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
+session_start();
+
+unset($_SESSION['abc']);
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.expected
new file mode 100755
index 0000000..587adaf
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.expected
@@ -0,0 +1,14 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+123
+updateTimestamp
+close
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: max-age=10800, private, must-revalidate
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php
new file mode 100755
index 0000000..3e62fb9
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php
@@ -0,0 +1,8 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
+session_start();
+
+echo $_SESSION['abc'];
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected
new file mode 100755
index 0000000..baa5f2f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected
@@ -0,0 +1,24 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+destroy
+doDestroy
+close
+open
+validateId
+read
+doRead: abc|i:123;
+read
+
+write
+doWrite: abc|i:123;
+close
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: max-age=10800, private, must-revalidate
+ [2] => Set-Cookie: sid=random_session_id; path=/; secure; HttpOnly
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php
new file mode 100755
index 0000000..a0f635c
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php
@@ -0,0 +1,10 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
+session_start();
+
+session_regenerate_id(true);
+
+ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); });
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.expected
new file mode 100755
index 0000000..4533a10
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.expected
@@ -0,0 +1,20 @@
+open
+validateId
+read
+doRead:
+read
+Array
+(
+ [0] => bar
+)
+$_SESSION is not empty
+write
+destroy
+close
+$_SESSION is not empty
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: max-age=0, private, must-revalidate
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php
new file mode 100755
index 0000000..96dca3c
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php
@@ -0,0 +1,24 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+
+$storage = new NativeSessionStorage();
+$storage->setSaveHandler(new TestSessionHandler());
+$flash = new FlashBag();
+$storage->registerBag($flash);
+$storage->start();
+
+$flash->add('foo', 'bar');
+
+print_r($flash->get('foo'));
+echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty';
+echo "\n";
+
+$storage->save();
+
+echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty';
+
+ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); });
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected
new file mode 100755
index 0000000..33da0a5
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected
@@ -0,0 +1,15 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+
+updateTimestamp
+close
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: max-age=10800, private, must-revalidate
+ [2] => Set-Cookie: abc=def
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php
new file mode 100755
index 0000000..ffb5b20
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php
@@ -0,0 +1,8 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
+session_start();
+
+setcookie('abc', 'def');
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected
new file mode 100755
index 0000000..5de2d9e
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected
@@ -0,0 +1,24 @@
+open
+validateId
+read
+doRead: abc|i:123;
+read
+updateTimestamp
+close
+open
+validateId
+read
+doRead: abc|i:123;
+read
+
+write
+destroy
+doDestroy
+close
+Array
+(
+ [0] => Content-Type: text/plain; charset=utf-8
+ [1] => Cache-Control: max-age=10800, private, must-revalidate
+ [2] => Set-Cookie: abc=def
+)
+shutdown
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php
new file mode 100755
index 0000000..ec51193
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php
@@ -0,0 +1,13 @@
+<?php
+
+require __DIR__.'/common.inc';
+
+setcookie('abc', 'def');
+
+session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
+session_start();
+session_write_close();
+session_start();
+
+$_SESSION['abc'] = 234;
+unset($_SESSION['abc']);
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php
new file mode 100755
index 0000000..68daa4e
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php
@@ -0,0 +1,135 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcacheSessionHandler;
+
+/**
+ * @requires extension memcache
+ * @group time-sensitive
+ * @group legacy
+ */
+class MemcacheSessionHandlerTest extends TestCase
+{
+ const PREFIX = 'prefix_';
+ const TTL = 1000;
+
+ /**
+ * @var MemcacheSessionHandler
+ */
+ protected $storage;
+
+ protected $memcache;
+
+ protected function setUp()
+ {
+ if (\defined('HHVM_VERSION')) {
+ $this->markTestSkipped('PHPUnit_MockObject cannot mock the Memcache class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
+ }
+
+ parent::setUp();
+ $this->memcache = $this->getMockBuilder('Memcache')->getMock();
+ $this->storage = new MemcacheSessionHandler(
+ $this->memcache,
+ ['prefix' => self::PREFIX, 'expiretime' => self::TTL]
+ );
+ }
+
+ protected function tearDown()
+ {
+ $this->memcache = null;
+ $this->storage = null;
+ parent::tearDown();
+ }
+
+ public function testOpenSession()
+ {
+ $this->assertTrue($this->storage->open('', ''));
+ }
+
+ public function testCloseSession()
+ {
+ $this->assertTrue($this->storage->close());
+ }
+
+ public function testReadSession()
+ {
+ $this->memcache
+ ->expects($this->once())
+ ->method('get')
+ ->with(self::PREFIX.'id')
+ ;
+
+ $this->assertEquals('', $this->storage->read('id'));
+ }
+
+ public function testWriteSession()
+ {
+ $this->memcache
+ ->expects($this->once())
+ ->method('set')
+ ->with(self::PREFIX.'id', 'data', 0, $this->equalTo(time() + self::TTL, 2))
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertTrue($this->storage->write('id', 'data'));
+ }
+
+ public function testDestroySession()
+ {
+ $this->memcache
+ ->expects($this->once())
+ ->method('delete')
+ ->with(self::PREFIX.'id')
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertTrue($this->storage->destroy('id'));
+ }
+
+ public function testGcSession()
+ {
+ $this->assertTrue($this->storage->gc(123));
+ }
+
+ /**
+ * @dataProvider getOptionFixtures
+ */
+ public function testSupportedOptions($options, $supported)
+ {
+ try {
+ new MemcacheSessionHandler($this->memcache, $options);
+ $this->assertTrue($supported);
+ } catch (\InvalidArgumentException $e) {
+ $this->assertFalse($supported);
+ }
+ }
+
+ public function getOptionFixtures()
+ {
+ return [
+ [['prefix' => 'session'], true],
+ [['expiretime' => 100], true],
+ [['prefix' => 'session', 'expiretime' => 200], true],
+ [['expiretime' => 100, 'foo' => 'bar'], false],
+ ];
+ }
+
+ public function testGetConnection()
+ {
+ $method = new \ReflectionMethod($this->storage, 'getMemcache');
+ $method->setAccessible(true);
+
+ $this->assertInstanceOf('\Memcache', $method->invoke($this->storage));
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php
new file mode 100755
index 0000000..8133373
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php
@@ -0,0 +1,145 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler;
+
+/**
+ * @requires extension memcached
+ * @group time-sensitive
+ */
+class MemcachedSessionHandlerTest extends TestCase
+{
+ const PREFIX = 'prefix_';
+ const TTL = 1000;
+
+ /**
+ * @var MemcachedSessionHandler
+ */
+ protected $storage;
+
+ protected $memcached;
+
+ protected function setUp()
+ {
+ if (\defined('HHVM_VERSION')) {
+ $this->markTestSkipped('PHPUnit_MockObject cannot mock the Memcached class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
+ }
+
+ parent::setUp();
+
+ if (version_compare(phpversion('memcached'), '2.2.0', '>=') && version_compare(phpversion('memcached'), '3.0.0b1', '<')) {
+ $this->markTestSkipped('Tests can only be run with memcached extension 2.1.0 or lower, or 3.0.0b1 or higher');
+ }
+
+ $this->memcached = $this->getMockBuilder('Memcached')->getMock();
+ $this->storage = new MemcachedSessionHandler(
+ $this->memcached,
+ ['prefix' => self::PREFIX, 'expiretime' => self::TTL]
+ );
+ }
+
+ protected function tearDown()
+ {
+ $this->memcached = null;
+ $this->storage = null;
+ parent::tearDown();
+ }
+
+ public function testOpenSession()
+ {
+ $this->assertTrue($this->storage->open('', ''));
+ }
+
+ public function testCloseSession()
+ {
+ $this->memcached
+ ->expects($this->once())
+ ->method('quit')
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertTrue($this->storage->close());
+ }
+
+ public function testReadSession()
+ {
+ $this->memcached
+ ->expects($this->once())
+ ->method('get')
+ ->with(self::PREFIX.'id')
+ ;
+
+ $this->assertEquals('', $this->storage->read('id'));
+ }
+
+ public function testWriteSession()
+ {
+ $this->memcached
+ ->expects($this->once())
+ ->method('set')
+ ->with(self::PREFIX.'id', 'data', $this->equalTo(time() + self::TTL, 2))
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertTrue($this->storage->write('id', 'data'));
+ }
+
+ public function testDestroySession()
+ {
+ $this->memcached
+ ->expects($this->once())
+ ->method('delete')
+ ->with(self::PREFIX.'id')
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertTrue($this->storage->destroy('id'));
+ }
+
+ public function testGcSession()
+ {
+ $this->assertTrue($this->storage->gc(123));
+ }
+
+ /**
+ * @dataProvider getOptionFixtures
+ */
+ public function testSupportedOptions($options, $supported)
+ {
+ try {
+ new MemcachedSessionHandler($this->memcached, $options);
+ $this->assertTrue($supported);
+ } catch (\InvalidArgumentException $e) {
+ $this->assertFalse($supported);
+ }
+ }
+
+ public function getOptionFixtures()
+ {
+ return [
+ [['prefix' => 'session'], true],
+ [['expiretime' => 100], true],
+ [['prefix' => 'session', 'expiretime' => 200], true],
+ [['expiretime' => 100, 'foo' => 'bar'], false],
+ ];
+ }
+
+ public function testGetConnection()
+ {
+ $method = new \ReflectionMethod($this->storage, 'getMemcached');
+ $method->setAccessible(true);
+
+ $this->assertInstanceOf('\Memcached', $method->invoke($this->storage));
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
new file mode 100755
index 0000000..6f2742c
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
@@ -0,0 +1,333 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler;
+
+/**
+ * @author Markus Bachmann <markus.bachmann@bachi.biz>
+ * @group time-sensitive
+ * @group legacy
+ */
+class MongoDbSessionHandlerTest extends TestCase
+{
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $mongo;
+ private $storage;
+ public $options;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ if (\extension_loaded('mongodb')) {
+ if (!class_exists('MongoDB\Client')) {
+ $this->markTestSkipped('The mongodb/mongodb package is required.');
+ }
+ } elseif (!\extension_loaded('mongo')) {
+ $this->markTestSkipped('The Mongo or MongoDB extension is required.');
+ }
+
+ if (phpversion('mongodb')) {
+ $mongoClass = 'MongoDB\Client';
+ } else {
+ $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
+ }
+
+ $this->mongo = $this->getMockBuilder($mongoClass)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->options = [
+ 'id_field' => '_id',
+ 'data_field' => 'data',
+ 'time_field' => 'time',
+ 'expiry_field' => 'expires_at',
+ 'database' => 'sf2-test',
+ 'collection' => 'session-test',
+ ];
+
+ $this->storage = new MongoDbSessionHandler($this->mongo, $this->options);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testConstructorShouldThrowExceptionForInvalidMongo()
+ {
+ new MongoDbSessionHandler(new \stdClass(), $this->options);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testConstructorShouldThrowExceptionForMissingOptions()
+ {
+ new MongoDbSessionHandler($this->mongo, []);
+ }
+
+ public function testOpenMethodAlwaysReturnTrue()
+ {
+ $this->assertTrue($this->storage->open('test', 'test'), 'The "open" method should always return true');
+ }
+
+ public function testCloseMethodAlwaysReturnTrue()
+ {
+ $this->assertTrue($this->storage->close(), 'The "close" method should always return true');
+ }
+
+ public function testRead()
+ {
+ $collection = $this->createMongoCollectionMock();
+
+ $this->mongo->expects($this->once())
+ ->method('selectCollection')
+ ->with($this->options['database'], $this->options['collection'])
+ ->will($this->returnValue($collection));
+
+ // defining the timeout before the actual method call
+ // allows to test for "greater than" values in the $criteria
+ $testTimeout = time() + 1;
+
+ $collection->expects($this->once())
+ ->method('findOne')
+ ->will($this->returnCallback(function ($criteria) use ($testTimeout) {
+ $this->assertArrayHasKey($this->options['id_field'], $criteria);
+ $this->assertEquals($criteria[$this->options['id_field']], 'foo');
+
+ $this->assertArrayHasKey($this->options['expiry_field'], $criteria);
+ $this->assertArrayHasKey('$gte', $criteria[$this->options['expiry_field']]);
+
+ if (phpversion('mongodb')) {
+ $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$gte']);
+ $this->assertGreaterThanOrEqual(round((string) $criteria[$this->options['expiry_field']]['$gte'] / 1000), $testTimeout);
+ } else {
+ $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$gte']);
+ $this->assertGreaterThanOrEqual($criteria[$this->options['expiry_field']]['$gte']->sec, $testTimeout);
+ }
+
+ $fields = [
+ $this->options['id_field'] => 'foo',
+ ];
+
+ if (phpversion('mongodb')) {
+ $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary('bar', \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
+ $fields[$this->options['id_field']] = new \MongoDB\BSON\UTCDateTime(time() * 1000);
+ } else {
+ $fields[$this->options['data_field']] = new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY);
+ $fields[$this->options['id_field']] = new \MongoDate();
+ }
+
+ return $fields;
+ }));
+
+ $this->assertEquals('bar', $this->storage->read('foo'));
+ }
+
+ public function testWrite()
+ {
+ $collection = $this->createMongoCollectionMock();
+
+ $this->mongo->expects($this->once())
+ ->method('selectCollection')
+ ->with($this->options['database'], $this->options['collection'])
+ ->will($this->returnValue($collection));
+
+ $data = [];
+
+ $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
+
+ $collection->expects($this->once())
+ ->method($methodName)
+ ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
+ $this->assertEquals([$this->options['id_field'] => 'foo'], $criteria);
+
+ if (phpversion('mongodb')) {
+ $this->assertEquals(['upsert' => true], $options);
+ } else {
+ $this->assertEquals(['upsert' => true, 'multiple' => false], $options);
+ }
+
+ $data = $updateData['$set'];
+ }));
+
+ $expectedExpiry = time() + (int) ini_get('session.gc_maxlifetime');
+ $this->assertTrue($this->storage->write('foo', 'bar'));
+
+ if (phpversion('mongodb')) {
+ $this->assertEquals('bar', $data[$this->options['data_field']]->getData());
+ $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]);
+ $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]);
+ $this->assertGreaterThanOrEqual($expectedExpiry, round((string) $data[$this->options['expiry_field']] / 1000));
+ } else {
+ $this->assertEquals('bar', $data[$this->options['data_field']]->bin);
+ $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
+ $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
+ $this->assertGreaterThanOrEqual($expectedExpiry, $data[$this->options['expiry_field']]->sec);
+ }
+ }
+
+ public function testWriteWhenUsingExpiresField()
+ {
+ $this->options = [
+ 'id_field' => '_id',
+ 'data_field' => 'data',
+ 'time_field' => 'time',
+ 'database' => 'sf2-test',
+ 'collection' => 'session-test',
+ 'expiry_field' => 'expiresAt',
+ ];
+
+ $this->storage = new MongoDbSessionHandler($this->mongo, $this->options);
+
+ $collection = $this->createMongoCollectionMock();
+
+ $this->mongo->expects($this->once())
+ ->method('selectCollection')
+ ->with($this->options['database'], $this->options['collection'])
+ ->will($this->returnValue($collection));
+
+ $data = [];
+
+ $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
+
+ $collection->expects($this->once())
+ ->method($methodName)
+ ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
+ $this->assertEquals([$this->options['id_field'] => 'foo'], $criteria);
+
+ if (phpversion('mongodb')) {
+ $this->assertEquals(['upsert' => true], $options);
+ } else {
+ $this->assertEquals(['upsert' => true, 'multiple' => false], $options);
+ }
+
+ $data = $updateData['$set'];
+ }));
+
+ $this->assertTrue($this->storage->write('foo', 'bar'));
+
+ if (phpversion('mongodb')) {
+ $this->assertEquals('bar', $data[$this->options['data_field']]->getData());
+ $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]);
+ $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]);
+ } else {
+ $this->assertEquals('bar', $data[$this->options['data_field']]->bin);
+ $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
+ $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
+ }
+ }
+
+ public function testReplaceSessionData()
+ {
+ $collection = $this->createMongoCollectionMock();
+
+ $this->mongo->expects($this->once())
+ ->method('selectCollection')
+ ->with($this->options['database'], $this->options['collection'])
+ ->will($this->returnValue($collection));
+
+ $data = [];
+
+ $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
+
+ $collection->expects($this->exactly(2))
+ ->method($methodName)
+ ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
+ $data = $updateData;
+ }));
+
+ $this->storage->write('foo', 'bar');
+ $this->storage->write('foo', 'foobar');
+
+ if (phpversion('mongodb')) {
+ $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->getData());
+ } else {
+ $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin);
+ }
+ }
+
+ public function testDestroy()
+ {
+ $collection = $this->createMongoCollectionMock();
+
+ $this->mongo->expects($this->once())
+ ->method('selectCollection')
+ ->with($this->options['database'], $this->options['collection'])
+ ->will($this->returnValue($collection));
+
+ $methodName = phpversion('mongodb') ? 'deleteOne' : 'remove';
+
+ $collection->expects($this->once())
+ ->method($methodName)
+ ->with([$this->options['id_field'] => 'foo']);
+
+ $this->assertTrue($this->storage->destroy('foo'));
+ }
+
+ public function testGc()
+ {
+ $collection = $this->createMongoCollectionMock();
+
+ $this->mongo->expects($this->once())
+ ->method('selectCollection')
+ ->with($this->options['database'], $this->options['collection'])
+ ->will($this->returnValue($collection));
+
+ $methodName = phpversion('mongodb') ? 'deleteMany' : 'remove';
+
+ $collection->expects($this->once())
+ ->method($methodName)
+ ->will($this->returnCallback(function ($criteria) {
+ if (phpversion('mongodb')) {
+ $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$lt']);
+ $this->assertGreaterThanOrEqual(time() - 1, round((string) $criteria[$this->options['expiry_field']]['$lt'] / 1000));
+ } else {
+ $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$lt']);
+ $this->assertGreaterThanOrEqual(time() - 1, $criteria[$this->options['expiry_field']]['$lt']->sec);
+ }
+ }));
+
+ $this->assertTrue($this->storage->gc(1));
+ }
+
+ public function testGetConnection()
+ {
+ $method = new \ReflectionMethod($this->storage, 'getMongo');
+ $method->setAccessible(true);
+
+ if (phpversion('mongodb')) {
+ $mongoClass = 'MongoDB\Client';
+ } else {
+ $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
+ }
+
+ $this->assertInstanceOf($mongoClass, $method->invoke($this->storage));
+ }
+
+ private function createMongoCollectionMock()
+ {
+ $collectionClass = 'MongoCollection';
+ if (phpversion('mongodb')) {
+ $collectionClass = 'MongoDB\Collection';
+ }
+
+ $collection = $this->getMockBuilder($collectionClass)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ return $collection;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php
new file mode 100755
index 0000000..dc827d8
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php
@@ -0,0 +1,77 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+
+/**
+ * Test class for NativeFileSessionHandler.
+ *
+ * @author Drak <drak@zikula.org>
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class NativeFileSessionHandlerTest extends TestCase
+{
+ public function testConstruct()
+ {
+ $storage = new NativeSessionStorage(['name' => 'TESTING'], new NativeFileSessionHandler(sys_get_temp_dir()));
+
+ $this->assertEquals('files', $storage->getSaveHandler()->getSaveHandlerName());
+ $this->assertEquals('user', ini_get('session.save_handler'));
+
+ $this->assertEquals(sys_get_temp_dir(), ini_get('session.save_path'));
+ $this->assertEquals('TESTING', ini_get('session.name'));
+ }
+
+ /**
+ * @dataProvider savePathDataProvider
+ */
+ public function testConstructSavePath($savePath, $expectedSavePath, $path)
+ {
+ $handler = new NativeFileSessionHandler($savePath);
+ $this->assertEquals($expectedSavePath, ini_get('session.save_path'));
+ $this->assertTrue(is_dir(realpath($path)));
+
+ rmdir($path);
+ }
+
+ public function savePathDataProvider()
+ {
+ $base = sys_get_temp_dir();
+
+ return [
+ ["$base/foo", "$base/foo", "$base/foo"],
+ ["5;$base/foo", "5;$base/foo", "$base/foo"],
+ ["5;0600;$base/foo", "5;0600;$base/foo", "$base/foo"],
+ ];
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testConstructException()
+ {
+ $handler = new NativeFileSessionHandler('something;invalid;with;too-many-args');
+ }
+
+ public function testConstructDefault()
+ {
+ $path = ini_get('session.save_path');
+ $storage = new NativeSessionStorage(['name' => 'TESTING'], new NativeFileSessionHandler());
+
+ $this->assertEquals($path, ini_get('session.save_path'));
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php
new file mode 100755
index 0000000..4a9fb60
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler;
+
+/**
+ * Test class for NativeSessionHandler.
+ *
+ * @author Drak <drak@zikula.org>
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ * @group legacy
+ */
+class NativeSessionHandlerTest extends TestCase
+{
+ /**
+ * @expectedDeprecation The Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler class is deprecated since Symfony 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.
+ */
+ public function testConstruct()
+ {
+ $handler = new NativeSessionHandler();
+
+ $this->assertInstanceOf('SessionHandler', $handler);
+ $this->assertTrue($handler instanceof NativeSessionHandler);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php
new file mode 100755
index 0000000..0d246e1
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php
@@ -0,0 +1,59 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Session;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+
+/**
+ * Test class for NullSessionHandler.
+ *
+ * @author Drak <drak@zikula.org>
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class NullSessionHandlerTest extends TestCase
+{
+ public function testSaveHandlers()
+ {
+ $storage = $this->getStorage();
+ $this->assertEquals('user', ini_get('session.save_handler'));
+ }
+
+ public function testSession()
+ {
+ session_id('nullsessionstorage');
+ $storage = $this->getStorage();
+ $session = new Session($storage);
+ $this->assertNull($session->get('something'));
+ $session->set('something', 'unique');
+ $this->assertEquals('unique', $session->get('something'));
+ }
+
+ public function testNothingIsPersisted()
+ {
+ session_id('nullsessionstorage');
+ $storage = $this->getStorage();
+ $session = new Session($storage);
+ $session->start();
+ $this->assertEquals('nullsessionstorage', $session->getId());
+ $this->assertNull($session->get('something'));
+ }
+
+ public function getStorage()
+ {
+ return new NativeSessionStorage([], new NullSessionHandler());
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php
new file mode 100755
index 0000000..9010784
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php
@@ -0,0 +1,411 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
+
+/**
+ * @requires extension pdo_sqlite
+ * @group time-sensitive
+ */
+class PdoSessionHandlerTest extends TestCase
+{
+ private $dbFile;
+
+ protected function tearDown()
+ {
+ // make sure the temporary database file is deleted when it has been created (even when a test fails)
+ if ($this->dbFile) {
+ @unlink($this->dbFile);
+ }
+ parent::tearDown();
+ }
+
+ protected function getPersistentSqliteDsn()
+ {
+ $this->dbFile = tempnam(sys_get_temp_dir(), 'sf2_sqlite_sessions');
+
+ return 'sqlite:'.$this->dbFile;
+ }
+
+ protected function getMemorySqlitePdo()
+ {
+ $pdo = new \PDO('sqlite::memory:');
+ $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+ $storage = new PdoSessionHandler($pdo);
+ $storage->createTable();
+
+ return $pdo;
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testWrongPdoErrMode()
+ {
+ $pdo = $this->getMemorySqlitePdo();
+ $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT);
+
+ $storage = new PdoSessionHandler($pdo);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testInexistentTable()
+ {
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo(), ['db_table' => 'inexistent_table']);
+ $storage->open('', 'sid');
+ $storage->read('id');
+ $storage->write('id', 'data');
+ $storage->close();
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testCreateTableTwice()
+ {
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+ $storage->createTable();
+ }
+
+ public function testWithLazyDsnConnection()
+ {
+ $dsn = $this->getPersistentSqliteDsn();
+
+ $storage = new PdoSessionHandler($dsn);
+ $storage->createTable();
+ $storage->open('', 'sid');
+ $data = $storage->read('id');
+ $storage->write('id', 'data');
+ $storage->close();
+ $this->assertSame('', $data, 'New session returns empty string data');
+
+ $storage->open('', 'sid');
+ $data = $storage->read('id');
+ $storage->close();
+ $this->assertSame('data', $data, 'Written value can be read back correctly');
+ }
+
+ public function testWithLazySavePathConnection()
+ {
+ $dsn = $this->getPersistentSqliteDsn();
+
+ // Open is called with what ini_set('session.save_path', $dsn) would mean
+ $storage = new PdoSessionHandler(null);
+ $storage->open($dsn, 'sid');
+ $storage->createTable();
+ $data = $storage->read('id');
+ $storage->write('id', 'data');
+ $storage->close();
+ $this->assertSame('', $data, 'New session returns empty string data');
+
+ $storage->open($dsn, 'sid');
+ $data = $storage->read('id');
+ $storage->close();
+ $this->assertSame('data', $data, 'Written value can be read back correctly');
+ }
+
+ public function testReadWriteReadWithNullByte()
+ {
+ $sessionData = 'da'."\0".'ta';
+
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+ $storage->open('', 'sid');
+ $readData = $storage->read('id');
+ $storage->write('id', $sessionData);
+ $storage->close();
+ $this->assertSame('', $readData, 'New session returns empty string data');
+
+ $storage->open('', 'sid');
+ $readData = $storage->read('id');
+ $storage->close();
+ $this->assertSame($sessionData, $readData, 'Written value can be read back correctly');
+ }
+
+ public function testReadConvertsStreamToString()
+ {
+ if (\defined('HHVM_VERSION')) {
+ $this->markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
+ }
+
+ $pdo = new MockPdo('pgsql');
+ $pdo->prepareResult = $this->getMockBuilder('PDOStatement')->getMock();
+
+ $content = 'foobar';
+ $stream = $this->createStream($content);
+
+ $pdo->prepareResult->expects($this->once())->method('fetchAll')
+ ->will($this->returnValue([[$stream, 42, time()]]));
+
+ $storage = new PdoSessionHandler($pdo);
+ $result = $storage->read('foo');
+
+ $this->assertSame($content, $result);
+ }
+
+ public function testReadLockedConvertsStreamToString()
+ {
+ if (\defined('HHVM_VERSION')) {
+ $this->markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
+ }
+ if (filter_var(ini_get('session.use_strict_mode'), FILTER_VALIDATE_BOOLEAN)) {
+ $this->markTestSkipped('Strict mode needs no locking for new sessions.');
+ }
+
+ $pdo = new MockPdo('pgsql');
+ $selectStmt = $this->getMockBuilder('PDOStatement')->getMock();
+ $insertStmt = $this->getMockBuilder('PDOStatement')->getMock();
+
+ $pdo->prepareResult = function ($statement) use ($selectStmt, $insertStmt) {
+ return 0 === strpos($statement, 'INSERT') ? $insertStmt : $selectStmt;
+ };
+
+ $content = 'foobar';
+ $stream = $this->createStream($content);
+ $exception = null;
+
+ $selectStmt->expects($this->atLeast(2))->method('fetchAll')
+ ->will($this->returnCallback(function () use (&$exception, $stream) {
+ return $exception ? [[$stream, 42, time()]] : [];
+ }));
+
+ $insertStmt->expects($this->once())->method('execute')
+ ->will($this->returnCallback(function () use (&$exception) {
+ throw $exception = new \PDOException('', '23');
+ }));
+
+ $storage = new PdoSessionHandler($pdo);
+ $result = $storage->read('foo');
+
+ $this->assertSame($content, $result);
+ }
+
+ public function testReadingRequiresExactlySameId()
+ {
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+ $storage->open('', 'sid');
+ $storage->write('id', 'data');
+ $storage->write('test', 'data');
+ $storage->write('space ', 'data');
+ $storage->close();
+
+ $storage->open('', 'sid');
+ $readDataCaseSensitive = $storage->read('ID');
+ $readDataNoCharFolding = $storage->read('tést');
+ $readDataKeepSpace = $storage->read('space ');
+ $readDataExtraSpace = $storage->read('space ');
+ $storage->close();
+
+ $this->assertSame('', $readDataCaseSensitive, 'Retrieval by ID should be case-sensitive (collation setting)');
+ $this->assertSame('', $readDataNoCharFolding, 'Retrieval by ID should not do character folding (collation setting)');
+ $this->assertSame('data', $readDataKeepSpace, 'Retrieval by ID requires spaces as-is');
+ $this->assertSame('', $readDataExtraSpace, 'Retrieval by ID requires spaces as-is');
+ }
+
+ /**
+ * Simulates session_regenerate_id(true) which will require an INSERT or UPDATE (replace).
+ */
+ public function testWriteDifferentSessionIdThanRead()
+ {
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+ $storage->open('', 'sid');
+ $storage->read('id');
+ $storage->destroy('id');
+ $storage->write('new_id', 'data_of_new_session_id');
+ $storage->close();
+
+ $storage->open('', 'sid');
+ $data = $storage->read('new_id');
+ $storage->close();
+
+ $this->assertSame('data_of_new_session_id', $data, 'Data of regenerated session id is available');
+ }
+
+ public function testWrongUsageStillWorks()
+ {
+ // wrong method sequence that should no happen, but still works
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+ $storage->write('id', 'data');
+ $storage->write('other_id', 'other_data');
+ $storage->destroy('inexistent');
+ $storage->open('', 'sid');
+ $data = $storage->read('id');
+ $otherData = $storage->read('other_id');
+ $storage->close();
+
+ $this->assertSame('data', $data);
+ $this->assertSame('other_data', $otherData);
+ }
+
+ public function testSessionDestroy()
+ {
+ $pdo = $this->getMemorySqlitePdo();
+ $storage = new PdoSessionHandler($pdo);
+
+ $storage->open('', 'sid');
+ $storage->read('id');
+ $storage->write('id', 'data');
+ $storage->close();
+ $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
+
+ $storage->open('', 'sid');
+ $storage->read('id');
+ $storage->destroy('id');
+ $storage->close();
+ $this->assertEquals(0, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
+
+ $storage->open('', 'sid');
+ $data = $storage->read('id');
+ $storage->close();
+ $this->assertSame('', $data, 'Destroyed session returns empty string');
+ }
+
+ /**
+ * @runInSeparateProcess
+ */
+ public function testSessionGC()
+ {
+ $previousLifeTime = ini_set('session.gc_maxlifetime', 1000);
+ $pdo = $this->getMemorySqlitePdo();
+ $storage = new PdoSessionHandler($pdo);
+
+ $storage->open('', 'sid');
+ $storage->read('id');
+ $storage->write('id', 'data');
+ $storage->close();
+
+ $storage->open('', 'sid');
+ $storage->read('gc_id');
+ ini_set('session.gc_maxlifetime', -1); // test that you can set lifetime of a session after it has been read
+ $storage->write('gc_id', 'data');
+ $storage->close();
+ $this->assertEquals(2, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'No session pruned because gc not called');
+
+ $storage->open('', 'sid');
+ $data = $storage->read('gc_id');
+ $storage->gc(-1);
+ $storage->close();
+
+ ini_set('session.gc_maxlifetime', $previousLifeTime);
+
+ $this->assertSame('', $data, 'Session already considered garbage, so not returning data even if it is not pruned yet');
+ $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'Expired session is pruned');
+ }
+
+ public function testGetConnection()
+ {
+ $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
+
+ $method = new \ReflectionMethod($storage, 'getConnection');
+ $method->setAccessible(true);
+
+ $this->assertInstanceOf('\PDO', $method->invoke($storage));
+ }
+
+ public function testGetConnectionConnectsIfNeeded()
+ {
+ $storage = new PdoSessionHandler('sqlite::memory:');
+
+ $method = new \ReflectionMethod($storage, 'getConnection');
+ $method->setAccessible(true);
+
+ $this->assertInstanceOf('\PDO', $method->invoke($storage));
+ }
+
+ /**
+ * @dataProvider provideUrlDsnPairs
+ */
+ public function testUrlDsn($url, $expectedDsn, $expectedUser = null, $expectedPassword = null)
+ {
+ $storage = new PdoSessionHandler($url);
+
+ $this->assertAttributeEquals($expectedDsn, 'dsn', $storage);
+
+ if (null !== $expectedUser) {
+ $this->assertAttributeEquals($expectedUser, 'username', $storage);
+ }
+
+ if (null !== $expectedPassword) {
+ $this->assertAttributeEquals($expectedPassword, 'password', $storage);
+ }
+ }
+
+ public function provideUrlDsnPairs()
+ {
+ yield ['mysql://localhost/test', 'mysql:host=localhost;dbname=test;'];
+ yield ['mysql://localhost:56/test', 'mysql:host=localhost;port=56;dbname=test;'];
+ yield ['mysql2://root:pwd@localhost/test', 'mysql:host=localhost;dbname=test;', 'root', 'pwd'];
+ yield ['postgres://localhost/test', 'pgsql:host=localhost;dbname=test;'];
+ yield ['postgresql://localhost:5634/test', 'pgsql:host=localhost;port=5634;dbname=test;'];
+ yield ['postgres://root:pwd@localhost/test', 'pgsql:host=localhost;dbname=test;', 'root', 'pwd'];
+ yield 'sqlite relative path' => ['sqlite://localhost/tmp/test', 'sqlite:tmp/test'];
+ yield 'sqlite absolute path' => ['sqlite://localhost//tmp/test', 'sqlite:/tmp/test'];
+ yield 'sqlite relative path without host' => ['sqlite:///tmp/test', 'sqlite:tmp/test'];
+ yield 'sqlite absolute path without host' => ['sqlite3:////tmp/test', 'sqlite:/tmp/test'];
+ yield ['sqlite://localhost/:memory:', 'sqlite::memory:'];
+ yield ['mssql://localhost/test', 'sqlsrv:server=localhost;Database=test'];
+ yield ['mssql://localhost:56/test', 'sqlsrv:server=localhost,56;Database=test'];
+ }
+
+ private function createStream($content)
+ {
+ $stream = tmpfile();
+ fwrite($stream, $content);
+ fseek($stream, 0);
+
+ return $stream;
+ }
+}
+
+class MockPdo extends \PDO
+{
+ public $prepareResult;
+ private $driverName;
+ private $errorMode;
+
+ public function __construct($driverName = null, $errorMode = null)
+ {
+ $this->driverName = $driverName;
+ $this->errorMode = null !== $errorMode ?: \PDO::ERRMODE_EXCEPTION;
+ }
+
+ public function getAttribute($attribute)
+ {
+ if (\PDO::ATTR_ERRMODE === $attribute) {
+ return $this->errorMode;
+ }
+
+ if (\PDO::ATTR_DRIVER_NAME === $attribute) {
+ return $this->driverName;
+ }
+
+ return parent::getAttribute($attribute);
+ }
+
+ public function prepare($statement, $driverOptions = [])
+ {
+ return \is_callable($this->prepareResult)
+ ? \call_user_func($this->prepareResult, $statement, $driverOptions)
+ : $this->prepareResult;
+ }
+
+ public function beginTransaction()
+ {
+ }
+
+ public function rollBack()
+ {
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php
new file mode 100755
index 0000000..6a0d168
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php
@@ -0,0 +1,189 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
+
+class StrictSessionHandlerTest extends TestCase
+{
+ public function testOpen()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('open')
+ ->with('path', 'name')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertInstanceOf('SessionUpdateTimestampHandlerInterface', $proxy);
+ $this->assertInstanceOf(AbstractSessionHandler::class, $proxy);
+ $this->assertTrue($proxy->open('path', 'name'));
+ }
+
+ public function testCloseSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('close')
+ ->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->close());
+ }
+
+ public function testValidateIdOK()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('data');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->validateId('id'));
+ }
+
+ public function testValidateIdKO()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertFalse($proxy->validateId('id'));
+ }
+
+ public function testRead()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('data');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertSame('data', $proxy->read('id'));
+ }
+
+ public function testReadWithValidateIdOK()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('data');
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->validateId('id'));
+ $this->assertSame('data', $proxy->read('id'));
+ }
+
+ public function testReadWithValidateIdMismatch()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->exactly(2))->method('read')
+ ->withConsecutive(['id1'], ['id2'])
+ ->will($this->onConsecutiveCalls('data1', 'data2'));
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->validateId('id1'));
+ $this->assertSame('data2', $proxy->read('id2'));
+ }
+
+ public function testUpdateTimestamp()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('write')
+ ->with('id', 'data')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->updateTimestamp('id', 'data'));
+ }
+
+ public function testWrite()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('write')
+ ->with('id', 'data')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->write('id', 'data'));
+ }
+
+ public function testWriteEmptyNewSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('');
+ $handler->expects($this->never())->method('write');
+ $handler->expects($this->once())->method('destroy')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertFalse($proxy->validateId('id'));
+ $this->assertSame('', $proxy->read('id'));
+ $this->assertTrue($proxy->write('id', ''));
+ }
+
+ public function testWriteEmptyExistingSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('data');
+ $handler->expects($this->never())->method('write');
+ $handler->expects($this->once())->method('destroy')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertSame('data', $proxy->read('id'));
+ $this->assertTrue($proxy->write('id', ''));
+ }
+
+ public function testDestroy()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('destroy')
+ ->with('id')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->destroy('id'));
+ }
+
+ public function testDestroyNewSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('');
+ $handler->expects($this->once())->method('destroy')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertSame('', $proxy->read('id'));
+ $this->assertTrue($proxy->destroy('id'));
+ }
+
+ public function testDestroyNonEmptyNewSession()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('read')
+ ->with('id')->willReturn('');
+ $handler->expects($this->once())->method('write')
+ ->with('id', 'data')->willReturn(true);
+ $handler->expects($this->once())->method('destroy')
+ ->with('id')->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertSame('', $proxy->read('id'));
+ $this->assertTrue($proxy->write('id', 'data'));
+ $this->assertTrue($proxy->destroy('id'));
+ }
+
+ public function testGc()
+ {
+ $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $handler->expects($this->once())->method('gc')
+ ->with(123)->willReturn(true);
+ $proxy = new StrictSessionHandler($handler);
+
+ $this->assertTrue($proxy->gc(123));
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php
new file mode 100755
index 0000000..898a7d1
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php
@@ -0,0 +1,97 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler;
+
+/**
+ * @author Adrien Brault <adrien.brault@gmail.com>
+ *
+ * @group legacy
+ */
+class WriteCheckSessionHandlerTest extends TestCase
+{
+ public function test()
+ {
+ $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('close')
+ ->with()
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertTrue($writeCheckSessionHandler->close());
+ }
+
+ public function testWrite()
+ {
+ $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo', 'bar')
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertTrue($writeCheckSessionHandler->write('foo', 'bar'));
+ }
+
+ public function testSkippedWrite()
+ {
+ $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('read')
+ ->with('foo')
+ ->will($this->returnValue('bar'))
+ ;
+
+ $wrappedSessionHandlerMock
+ ->expects($this->never())
+ ->method('write')
+ ;
+
+ $this->assertEquals('bar', $writeCheckSessionHandler->read('foo'));
+ $this->assertTrue($writeCheckSessionHandler->write('foo', 'bar'));
+ }
+
+ public function testNonSkippedWrite()
+ {
+ $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('read')
+ ->with('foo')
+ ->will($this->returnValue('bar'))
+ ;
+
+ $wrappedSessionHandlerMock
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo', 'baZZZ')
+ ->will($this->returnValue(true))
+ ;
+
+ $this->assertEquals('bar', $writeCheckSessionHandler->read('foo'));
+ $this->assertTrue($writeCheckSessionHandler->write('foo', 'baZZZ'));
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php
new file mode 100755
index 0000000..2c4758b
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php
@@ -0,0 +1,139 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag;
+
+/**
+ * Test class for MetadataBag.
+ *
+ * @group time-sensitive
+ */
+class MetadataBagTest extends TestCase
+{
+ /**
+ * @var MetadataBag
+ */
+ protected $bag;
+
+ protected $array = [];
+
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->bag = new MetadataBag();
+ $this->array = [MetadataBag::CREATED => 1234567, MetadataBag::UPDATED => 12345678, MetadataBag::LIFETIME => 0];
+ $this->bag->initialize($this->array);
+ }
+
+ protected function tearDown()
+ {
+ $this->array = [];
+ $this->bag = null;
+ parent::tearDown();
+ }
+
+ public function testInitialize()
+ {
+ $sessionMetadata = [];
+
+ $bag1 = new MetadataBag();
+ $bag1->initialize($sessionMetadata);
+ $this->assertGreaterThanOrEqual(time(), $bag1->getCreated());
+ $this->assertEquals($bag1->getCreated(), $bag1->getLastUsed());
+
+ sleep(1);
+ $bag2 = new MetadataBag();
+ $bag2->initialize($sessionMetadata);
+ $this->assertEquals($bag1->getCreated(), $bag2->getCreated());
+ $this->assertEquals($bag1->getLastUsed(), $bag2->getLastUsed());
+ $this->assertEquals($bag2->getCreated(), $bag2->getLastUsed());
+
+ sleep(1);
+ $bag3 = new MetadataBag();
+ $bag3->initialize($sessionMetadata);
+ $this->assertEquals($bag1->getCreated(), $bag3->getCreated());
+ $this->assertGreaterThan($bag2->getLastUsed(), $bag3->getLastUsed());
+ $this->assertNotEquals($bag3->getCreated(), $bag3->getLastUsed());
+ }
+
+ public function testGetSetName()
+ {
+ $this->assertEquals('__metadata', $this->bag->getName());
+ $this->bag->setName('foo');
+ $this->assertEquals('foo', $this->bag->getName());
+ }
+
+ public function testGetStorageKey()
+ {
+ $this->assertEquals('_sf2_meta', $this->bag->getStorageKey());
+ }
+
+ public function testGetLifetime()
+ {
+ $bag = new MetadataBag();
+ $array = [MetadataBag::CREATED => 1234567, MetadataBag::UPDATED => 12345678, MetadataBag::LIFETIME => 1000];
+ $bag->initialize($array);
+ $this->assertEquals(1000, $bag->getLifetime());
+ }
+
+ public function testGetCreated()
+ {
+ $this->assertEquals(1234567, $this->bag->getCreated());
+ }
+
+ public function testGetLastUsed()
+ {
+ $this->assertLessThanOrEqual(time(), $this->bag->getLastUsed());
+ }
+
+ public function testClear()
+ {
+ $this->bag->clear();
+
+ // the clear method has no side effects, we just want to ensure it doesn't trigger any exceptions
+ $this->addToAssertionCount(1);
+ }
+
+ public function testSkipLastUsedUpdate()
+ {
+ $bag = new MetadataBag('', 30);
+ $timeStamp = time();
+
+ $created = $timeStamp - 15;
+ $sessionMetadata = [
+ MetadataBag::CREATED => $created,
+ MetadataBag::UPDATED => $created,
+ MetadataBag::LIFETIME => 1000,
+ ];
+ $bag->initialize($sessionMetadata);
+
+ $this->assertEquals($created, $sessionMetadata[MetadataBag::UPDATED]);
+ }
+
+ public function testDoesNotSkipLastUsedUpdate()
+ {
+ $bag = new MetadataBag('', 30);
+ $timeStamp = time();
+
+ $created = $timeStamp - 45;
+ $sessionMetadata = [
+ MetadataBag::CREATED => $created,
+ MetadataBag::UPDATED => $created,
+ MetadataBag::LIFETIME => 1000,
+ ];
+ $bag->initialize($sessionMetadata);
+
+ $this->assertEquals($timeStamp, $sessionMetadata[MetadataBag::UPDATED]);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php
new file mode 100755
index 0000000..2e3024e
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php
@@ -0,0 +1,131 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
+
+/**
+ * Test class for MockArraySessionStorage.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class MockArraySessionStorageTest extends TestCase
+{
+ /**
+ * @var MockArraySessionStorage
+ */
+ private $storage;
+
+ /**
+ * @var AttributeBag
+ */
+ private $attributes;
+
+ /**
+ * @var FlashBag
+ */
+ private $flashes;
+
+ private $data;
+
+ protected function setUp()
+ {
+ $this->attributes = new AttributeBag();
+ $this->flashes = new FlashBag();
+
+ $this->data = [
+ $this->attributes->getStorageKey() => ['foo' => 'bar'],
+ $this->flashes->getStorageKey() => ['notice' => 'hello'],
+ ];
+
+ $this->storage = new MockArraySessionStorage();
+ $this->storage->registerBag($this->flashes);
+ $this->storage->registerBag($this->attributes);
+ $this->storage->setSessionData($this->data);
+ }
+
+ protected function tearDown()
+ {
+ $this->data = null;
+ $this->flashes = null;
+ $this->attributes = null;
+ $this->storage = null;
+ }
+
+ public function testStart()
+ {
+ $this->assertEquals('', $this->storage->getId());
+ $this->storage->start();
+ $id = $this->storage->getId();
+ $this->assertNotEquals('', $id);
+ $this->storage->start();
+ $this->assertEquals($id, $this->storage->getId());
+ }
+
+ public function testRegenerate()
+ {
+ $this->storage->start();
+ $id = $this->storage->getId();
+ $this->storage->regenerate();
+ $this->assertNotEquals($id, $this->storage->getId());
+ $this->assertEquals(['foo' => 'bar'], $this->storage->getBag('attributes')->all());
+ $this->assertEquals(['notice' => 'hello'], $this->storage->getBag('flashes')->peekAll());
+
+ $id = $this->storage->getId();
+ $this->storage->regenerate(true);
+ $this->assertNotEquals($id, $this->storage->getId());
+ $this->assertEquals(['foo' => 'bar'], $this->storage->getBag('attributes')->all());
+ $this->assertEquals(['notice' => 'hello'], $this->storage->getBag('flashes')->peekAll());
+ }
+
+ public function testGetId()
+ {
+ $this->assertEquals('', $this->storage->getId());
+ $this->storage->start();
+ $this->assertNotEquals('', $this->storage->getId());
+ }
+
+ public function testClearClearsBags()
+ {
+ $this->storage->clear();
+
+ $this->assertSame([], $this->storage->getBag('attributes')->all());
+ $this->assertSame([], $this->storage->getBag('flashes')->peekAll());
+ }
+
+ public function testClearStartsSession()
+ {
+ $this->storage->clear();
+
+ $this->assertTrue($this->storage->isStarted());
+ }
+
+ public function testClearWithNoBagsStartsSession()
+ {
+ $storage = new MockArraySessionStorage();
+
+ $storage->clear();
+
+ $this->assertTrue($storage->isStarted());
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testUnstartedSave()
+ {
+ $this->storage->save();
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php
new file mode 100755
index 0000000..9e2692d
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php
@@ -0,0 +1,127 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage;
+
+/**
+ * Test class for MockFileSessionStorage.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class MockFileSessionStorageTest extends TestCase
+{
+ /**
+ * @var string
+ */
+ private $sessionDir;
+
+ /**
+ * @var MockFileSessionStorage
+ */
+ protected $storage;
+
+ protected function setUp()
+ {
+ $this->sessionDir = sys_get_temp_dir().'/sf2test';
+ $this->storage = $this->getStorage();
+ }
+
+ protected function tearDown()
+ {
+ $this->sessionDir = null;
+ $this->storage = null;
+ array_map('unlink', glob($this->sessionDir.'/*.session'));
+ if (is_dir($this->sessionDir)) {
+ rmdir($this->sessionDir);
+ }
+ }
+
+ public function testStart()
+ {
+ $this->assertEquals('', $this->storage->getId());
+ $this->assertTrue($this->storage->start());
+ $id = $this->storage->getId();
+ $this->assertNotEquals('', $this->storage->getId());
+ $this->assertTrue($this->storage->start());
+ $this->assertEquals($id, $this->storage->getId());
+ }
+
+ public function testRegenerate()
+ {
+ $this->storage->start();
+ $this->storage->getBag('attributes')->set('regenerate', 1234);
+ $this->storage->regenerate();
+ $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate'));
+ $this->storage->regenerate(true);
+ $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate'));
+ }
+
+ public function testGetId()
+ {
+ $this->assertEquals('', $this->storage->getId());
+ $this->storage->start();
+ $this->assertNotEquals('', $this->storage->getId());
+ }
+
+ public function testSave()
+ {
+ $this->storage->start();
+ $id = $this->storage->getId();
+ $this->assertNotEquals('108', $this->storage->getBag('attributes')->get('new'));
+ $this->assertFalse($this->storage->getBag('flashes')->has('newkey'));
+ $this->storage->getBag('attributes')->set('new', '108');
+ $this->storage->getBag('flashes')->set('newkey', 'test');
+ $this->storage->save();
+
+ $storage = $this->getStorage();
+ $storage->setId($id);
+ $storage->start();
+ $this->assertEquals('108', $storage->getBag('attributes')->get('new'));
+ $this->assertTrue($storage->getBag('flashes')->has('newkey'));
+ $this->assertEquals(['test'], $storage->getBag('flashes')->peek('newkey'));
+ }
+
+ public function testMultipleInstances()
+ {
+ $storage1 = $this->getStorage();
+ $storage1->start();
+ $storage1->getBag('attributes')->set('foo', 'bar');
+ $storage1->save();
+
+ $storage2 = $this->getStorage();
+ $storage2->setId($storage1->getId());
+ $storage2->start();
+ $this->assertEquals('bar', $storage2->getBag('attributes')->get('foo'), 'values persist between instances');
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testSaveWithoutStart()
+ {
+ $storage1 = $this->getStorage();
+ $storage1->save();
+ }
+
+ private function getStorage()
+ {
+ $storage = new MockFileSessionStorage($this->sessionDir);
+ $storage->registerBag(new FlashBag());
+ $storage->registerBag(new AttributeBag());
+
+ return $storage;
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php
new file mode 100755
index 0000000..7cc2eb7
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php
@@ -0,0 +1,296 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
+use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+
+/**
+ * Test class for NativeSessionStorage.
+ *
+ * @author Drak <drak@zikula.org>
+ *
+ * These tests require separate processes.
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class NativeSessionStorageTest extends TestCase
+{
+ private $savePath;
+
+ protected function setUp()
+ {
+ $this->iniSet('session.save_handler', 'files');
+ $this->iniSet('session.save_path', $this->savePath = sys_get_temp_dir().'/sf2test');
+ if (!is_dir($this->savePath)) {
+ mkdir($this->savePath);
+ }
+ }
+
+ protected function tearDown()
+ {
+ session_write_close();
+ array_map('unlink', glob($this->savePath.'/*'));
+ if (is_dir($this->savePath)) {
+ rmdir($this->savePath);
+ }
+
+ $this->savePath = null;
+ }
+
+ /**
+ * @return NativeSessionStorage
+ */
+ protected function getStorage(array $options = [])
+ {
+ $storage = new NativeSessionStorage($options);
+ $storage->registerBag(new AttributeBag());
+
+ return $storage;
+ }
+
+ public function testBag()
+ {
+ $storage = $this->getStorage();
+ $bag = new FlashBag();
+ $storage->registerBag($bag);
+ $this->assertSame($bag, $storage->getBag($bag->getName()));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testRegisterBagException()
+ {
+ $storage = $this->getStorage();
+ $storage->getBag('non_existing');
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testRegisterBagForAStartedSessionThrowsException()
+ {
+ $storage = $this->getStorage();
+ $storage->start();
+ $storage->registerBag(new AttributeBag());
+ }
+
+ public function testGetId()
+ {
+ $storage = $this->getStorage();
+ $this->assertSame('', $storage->getId(), 'Empty ID before starting session');
+
+ $storage->start();
+ $id = $storage->getId();
+ $this->assertInternalType('string', $id);
+ $this->assertNotSame('', $id);
+
+ $storage->save();
+ $this->assertSame($id, $storage->getId(), 'ID stays after saving session');
+ }
+
+ public function testRegenerate()
+ {
+ $storage = $this->getStorage();
+ $storage->start();
+ $id = $storage->getId();
+ $storage->getBag('attributes')->set('lucky', 7);
+ $storage->regenerate();
+ $this->assertNotEquals($id, $storage->getId());
+ $this->assertEquals(7, $storage->getBag('attributes')->get('lucky'));
+ }
+
+ public function testRegenerateDestroy()
+ {
+ $storage = $this->getStorage();
+ $storage->start();
+ $id = $storage->getId();
+ $storage->getBag('attributes')->set('legs', 11);
+ $storage->regenerate(true);
+ $this->assertNotEquals($id, $storage->getId());
+ $this->assertEquals(11, $storage->getBag('attributes')->get('legs'));
+ }
+
+ public function testSessionGlobalIsUpToDateAfterIdRegeneration()
+ {
+ $storage = $this->getStorage();
+ $storage->start();
+ $storage->getBag('attributes')->set('lucky', 7);
+ $storage->regenerate();
+ $storage->getBag('attributes')->set('lucky', 42);
+
+ $this->assertEquals(42, $_SESSION['_sf2_attributes']['lucky']);
+ }
+
+ public function testRegenerationFailureDoesNotFlagStorageAsStarted()
+ {
+ $storage = $this->getStorage();
+ $this->assertFalse($storage->regenerate());
+ $this->assertFalse($storage->isStarted());
+ }
+
+ public function testDefaultSessionCacheLimiter()
+ {
+ $this->iniSet('session.cache_limiter', 'nocache');
+
+ $storage = new NativeSessionStorage();
+ $this->assertEquals('', ini_get('session.cache_limiter'));
+ }
+
+ public function testExplicitSessionCacheLimiter()
+ {
+ $this->iniSet('session.cache_limiter', 'nocache');
+
+ $storage = new NativeSessionStorage(['cache_limiter' => 'public']);
+ $this->assertEquals('public', ini_get('session.cache_limiter'));
+ }
+
+ public function testCookieOptions()
+ {
+ $options = [
+ 'cookie_lifetime' => 123456,
+ 'cookie_path' => '/my/cookie/path',
+ 'cookie_domain' => 'symfony.example.com',
+ 'cookie_secure' => true,
+ 'cookie_httponly' => false,
+ ];
+
+ $this->getStorage($options);
+ $temp = session_get_cookie_params();
+ $gco = [];
+
+ foreach ($temp as $key => $value) {
+ $gco['cookie_'.$key] = $value;
+ }
+
+ unset($gco['cookie_samesite']);
+
+ $this->assertEquals($options, $gco);
+ }
+
+ public function testSessionOptions()
+ {
+ if (\defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM is not handled in this test case.');
+ }
+
+ $options = [
+ 'url_rewriter.tags' => 'a=href',
+ 'cache_expire' => '200',
+ ];
+
+ $this->getStorage($options);
+
+ $this->assertSame('a=href', ini_get('url_rewriter.tags'));
+ $this->assertSame('200', ini_get('session.cache_expire'));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testSetSaveHandlerException()
+ {
+ $storage = $this->getStorage();
+ $storage->setSaveHandler(new \stdClass());
+ }
+
+ public function testSetSaveHandler()
+ {
+ $this->iniSet('session.save_handler', 'files');
+ $storage = $this->getStorage();
+ $storage->setSaveHandler();
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+ $storage->setSaveHandler(null);
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+ $storage->setSaveHandler(new SessionHandlerProxy(new NativeFileSessionHandler()));
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+ $storage->setSaveHandler(new NativeFileSessionHandler());
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+ $storage->setSaveHandler(new SessionHandlerProxy(new NullSessionHandler()));
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+ $storage->setSaveHandler(new NullSessionHandler());
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testStarted()
+ {
+ $storage = $this->getStorage();
+
+ $this->assertFalse($storage->getSaveHandler()->isActive());
+ $this->assertFalse($storage->isStarted());
+
+ session_start();
+ $this->assertTrue(isset($_SESSION));
+ $this->assertTrue($storage->getSaveHandler()->isActive());
+
+ // PHP session might have started, but the storage driver has not, so false is correct here
+ $this->assertFalse($storage->isStarted());
+
+ $key = $storage->getMetadataBag()->getStorageKey();
+ $this->assertArrayNotHasKey($key, $_SESSION);
+ $storage->start();
+ }
+
+ public function testRestart()
+ {
+ $storage = $this->getStorage();
+ $storage->start();
+ $id = $storage->getId();
+ $storage->getBag('attributes')->set('lucky', 7);
+ $storage->save();
+ $storage->start();
+ $this->assertSame($id, $storage->getId(), 'Same session ID after restarting');
+ $this->assertSame(7, $storage->getBag('attributes')->get('lucky'), 'Data still available');
+ }
+
+ public function testCanCreateNativeSessionStorageWhenSessionAlreadyStarted()
+ {
+ session_start();
+ $this->getStorage();
+
+ // Assert no exception has been thrown by `getStorage()`
+ $this->addToAssertionCount(1);
+ }
+
+ public function testSetSessionOptionsOnceSessionStartedIsIgnored()
+ {
+ session_start();
+ $this->getStorage([
+ 'name' => 'something-else',
+ ]);
+
+ // Assert no exception has been thrown by `getStorage()`
+ $this->addToAssertionCount(1);
+ }
+
+ public function testGetBagsOnceSessionStartedIsIgnored()
+ {
+ session_start();
+ $bag = new AttributeBag();
+ $bag->setName('flashes');
+
+ $storage = $this->getStorage();
+ $storage->registerBag($bag);
+
+ $this->assertEquals($storage->getBag('flashes'), $bag);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php
new file mode 100755
index 0000000..752be61
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php
@@ -0,0 +1,96 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
+use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage;
+
+/**
+ * Test class for PhpSessionStorage.
+ *
+ * @author Drak <drak@zikula.org>
+ *
+ * These tests require separate processes.
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class PhpBridgeSessionStorageTest extends TestCase
+{
+ private $savePath;
+
+ protected function setUp()
+ {
+ $this->iniSet('session.save_handler', 'files');
+ $this->iniSet('session.save_path', $this->savePath = sys_get_temp_dir().'/sf2test');
+ if (!is_dir($this->savePath)) {
+ mkdir($this->savePath);
+ }
+ }
+
+ protected function tearDown()
+ {
+ session_write_close();
+ array_map('unlink', glob($this->savePath.'/*'));
+ if (is_dir($this->savePath)) {
+ rmdir($this->savePath);
+ }
+
+ $this->savePath = null;
+ }
+
+ /**
+ * @return PhpBridgeSessionStorage
+ */
+ protected function getStorage()
+ {
+ $storage = new PhpBridgeSessionStorage();
+ $storage->registerBag(new AttributeBag());
+
+ return $storage;
+ }
+
+ public function testPhpSession()
+ {
+ $storage = $this->getStorage();
+
+ $this->assertFalse($storage->getSaveHandler()->isActive());
+ $this->assertFalse($storage->isStarted());
+
+ session_start();
+ $this->assertTrue(isset($_SESSION));
+ // in PHP 5.4 we can reliably detect a session started
+ $this->assertTrue($storage->getSaveHandler()->isActive());
+ // PHP session might have started, but the storage driver has not, so false is correct here
+ $this->assertFalse($storage->isStarted());
+
+ $key = $storage->getMetadataBag()->getStorageKey();
+ $this->assertArrayNotHasKey($key, $_SESSION);
+ $storage->start();
+ $this->assertArrayHasKey($key, $_SESSION);
+ }
+
+ public function testClear()
+ {
+ $storage = $this->getStorage();
+ session_start();
+ $_SESSION['drak'] = 'loves symfony';
+ $storage->getBag('attributes')->set('symfony', 'greatness');
+ $key = $storage->getBag('attributes')->getStorageKey();
+ $this->assertEquals($_SESSION[$key], ['symfony' => 'greatness']);
+ $this->assertEquals($_SESSION['drak'], 'loves symfony');
+ $storage->clear();
+ $this->assertEquals($_SESSION[$key], []);
+ $this->assertEquals($_SESSION['drak'], 'loves symfony');
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php
new file mode 100755
index 0000000..cbb291f
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php
@@ -0,0 +1,113 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+
+/**
+ * Test class for AbstractProxy.
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class AbstractProxyTest extends TestCase
+{
+ /**
+ * @var AbstractProxy
+ */
+ protected $proxy;
+
+ protected function setUp()
+ {
+ $this->proxy = $this->getMockForAbstractClass(AbstractProxy::class);
+ }
+
+ protected function tearDown()
+ {
+ $this->proxy = null;
+ }
+
+ public function testGetSaveHandlerName()
+ {
+ $this->assertNull($this->proxy->getSaveHandlerName());
+ }
+
+ public function testIsSessionHandlerInterface()
+ {
+ $this->assertFalse($this->proxy->isSessionHandlerInterface());
+ $sh = new SessionHandlerProxy(new \SessionHandler());
+ $this->assertTrue($sh->isSessionHandlerInterface());
+ }
+
+ public function testIsWrapper()
+ {
+ $this->assertFalse($this->proxy->isWrapper());
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
+ public function testIsActive()
+ {
+ $this->assertFalse($this->proxy->isActive());
+ session_start();
+ $this->assertTrue($this->proxy->isActive());
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
+ public function testName()
+ {
+ $this->assertEquals(session_name(), $this->proxy->getName());
+ $this->proxy->setName('foo');
+ $this->assertEquals('foo', $this->proxy->getName());
+ $this->assertEquals(session_name(), $this->proxy->getName());
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ * @expectedException \LogicException
+ */
+ public function testNameException()
+ {
+ session_start();
+ $this->proxy->setName('foo');
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
+ public function testId()
+ {
+ $this->assertEquals(session_id(), $this->proxy->getId());
+ $this->proxy->setId('foo');
+ $this->assertEquals('foo', $this->proxy->getId());
+ $this->assertEquals(session_id(), $this->proxy->getId());
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ * @expectedException \LogicException
+ */
+ public function testIdException()
+ {
+ session_start();
+ $this->proxy->setId('foo');
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php
new file mode 100755
index 0000000..ed4fee6
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy;
+
+/**
+ * Test class for NativeProxy.
+ *
+ * @group legacy
+ *
+ * @author Drak <drak@zikula.org>
+ */
+class NativeProxyTest extends TestCase
+{
+ public function testIsWrapper()
+ {
+ $proxy = new NativeProxy();
+ $this->assertFalse($proxy->isWrapper());
+ }
+
+ public function testGetSaveHandlerName()
+ {
+ $name = ini_get('session.save_handler');
+ $proxy = new NativeProxy();
+ $this->assertEquals($name, $proxy->getSaveHandlerName());
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php
new file mode 100755
index 0000000..0459a8c
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php
@@ -0,0 +1,157 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
+
+/**
+ * Tests for SessionHandlerProxy class.
+ *
+ * @author Drak <drak@zikula.org>
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class SessionHandlerProxyTest extends TestCase
+{
+ /**
+ * @var \PHPUnit_Framework_MockObject_Matcher
+ */
+ private $mock;
+
+ /**
+ * @var SessionHandlerProxy
+ */
+ private $proxy;
+
+ protected function setUp()
+ {
+ $this->mock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
+ $this->proxy = new SessionHandlerProxy($this->mock);
+ }
+
+ protected function tearDown()
+ {
+ $this->mock = null;
+ $this->proxy = null;
+ }
+
+ public function testOpenTrue()
+ {
+ $this->mock->expects($this->once())
+ ->method('open')
+ ->will($this->returnValue(true));
+
+ $this->assertFalse($this->proxy->isActive());
+ $this->proxy->open('name', 'id');
+ $this->assertFalse($this->proxy->isActive());
+ }
+
+ public function testOpenFalse()
+ {
+ $this->mock->expects($this->once())
+ ->method('open')
+ ->will($this->returnValue(false));
+
+ $this->assertFalse($this->proxy->isActive());
+ $this->proxy->open('name', 'id');
+ $this->assertFalse($this->proxy->isActive());
+ }
+
+ public function testClose()
+ {
+ $this->mock->expects($this->once())
+ ->method('close')
+ ->will($this->returnValue(true));
+
+ $this->assertFalse($this->proxy->isActive());
+ $this->proxy->close();
+ $this->assertFalse($this->proxy->isActive());
+ }
+
+ public function testCloseFalse()
+ {
+ $this->mock->expects($this->once())
+ ->method('close')
+ ->will($this->returnValue(false));
+
+ $this->assertFalse($this->proxy->isActive());
+ $this->proxy->close();
+ $this->assertFalse($this->proxy->isActive());
+ }
+
+ public function testRead()
+ {
+ $this->mock->expects($this->once())
+ ->method('read');
+
+ $this->proxy->read('id');
+ }
+
+ public function testWrite()
+ {
+ $this->mock->expects($this->once())
+ ->method('write');
+
+ $this->proxy->write('id', 'data');
+ }
+
+ public function testDestroy()
+ {
+ $this->mock->expects($this->once())
+ ->method('destroy');
+
+ $this->proxy->destroy('id');
+ }
+
+ public function testGc()
+ {
+ $this->mock->expects($this->once())
+ ->method('gc');
+
+ $this->proxy->gc(86400);
+ }
+
+ /**
+ * @requires PHPUnit 5.1
+ */
+ public function testValidateId()
+ {
+ $mock = $this->getMockBuilder(['SessionHandlerInterface', 'SessionUpdateTimestampHandlerInterface'])->getMock();
+ $mock->expects($this->once())
+ ->method('validateId');
+
+ $proxy = new SessionHandlerProxy($mock);
+ $proxy->validateId('id');
+
+ $this->assertTrue($this->proxy->validateId('id'));
+ }
+
+ /**
+ * @requires PHPUnit 5.1
+ */
+ public function testUpdateTimestamp()
+ {
+ $mock = $this->getMockBuilder(['SessionHandlerInterface', 'SessionUpdateTimestampHandlerInterface'])->getMock();
+ $mock->expects($this->once())
+ ->method('updateTimestamp');
+
+ $proxy = new SessionHandlerProxy($mock);
+ $proxy->updateTimestamp('id', 'data');
+
+ $this->mock->expects($this->once())
+ ->method('write');
+
+ $this->proxy->updateTimestamp('id', 'data');
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php b/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php
new file mode 100755
index 0000000..62dfc9b
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php
@@ -0,0 +1,144 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+
+class StreamedResponseTest extends TestCase
+{
+ public function testConstructor()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; }, 404, ['Content-Type' => 'text/plain']);
+
+ $this->assertEquals(404, $response->getStatusCode());
+ $this->assertEquals('text/plain', $response->headers->get('Content-Type'));
+ }
+
+ public function testPrepareWith11Protocol()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; });
+ $request = Request::create('/');
+ $request->server->set('SERVER_PROTOCOL', 'HTTP/1.1');
+
+ $response->prepare($request);
+
+ $this->assertEquals('1.1', $response->getProtocolVersion());
+ $this->assertNotEquals('chunked', $response->headers->get('Transfer-Encoding'), 'Apache assumes responses with a Transfer-Encoding header set to chunked to already be encoded.');
+ }
+
+ public function testPrepareWith10Protocol()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; });
+ $request = Request::create('/');
+ $request->server->set('SERVER_PROTOCOL', 'HTTP/1.0');
+
+ $response->prepare($request);
+
+ $this->assertEquals('1.0', $response->getProtocolVersion());
+ $this->assertNull($response->headers->get('Transfer-Encoding'));
+ }
+
+ public function testPrepareWithHeadRequest()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; }, 200, ['Content-Length' => '123']);
+ $request = Request::create('/', 'HEAD');
+
+ $response->prepare($request);
+
+ $this->assertSame('123', $response->headers->get('Content-Length'));
+ }
+
+ public function testPrepareWithCacheHeaders()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; }, 200, ['Cache-Control' => 'max-age=600, public']);
+ $request = Request::create('/', 'GET');
+
+ $response->prepare($request);
+ $this->assertEquals('max-age=600, public', $response->headers->get('Cache-Control'));
+ }
+
+ public function testSendContent()
+ {
+ $called = 0;
+
+ $response = new StreamedResponse(function () use (&$called) { ++$called; });
+
+ $response->sendContent();
+ $this->assertEquals(1, $called);
+
+ $response->sendContent();
+ $this->assertEquals(1, $called);
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testSendContentWithNonCallable()
+ {
+ $response = new StreamedResponse(null);
+ $response->sendContent();
+ }
+
+ /**
+ * @expectedException \LogicException
+ */
+ public function testSetContent()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; });
+ $response->setContent('foo');
+ }
+
+ public function testGetContent()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; });
+ $this->assertFalse($response->getContent());
+ }
+
+ public function testCreate()
+ {
+ $response = StreamedResponse::create(function () {}, 204);
+
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response);
+ $this->assertEquals(204, $response->getStatusCode());
+ }
+
+ public function testReturnThis()
+ {
+ $response = new StreamedResponse(function () {});
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response->sendContent());
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response->sendContent());
+
+ $response = new StreamedResponse(function () {});
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response->sendHeaders());
+ $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response->sendHeaders());
+ }
+
+ public function testSetNotModified()
+ {
+ $response = new StreamedResponse(function () { echo 'foo'; });
+ $modified = $response->setNotModified();
+ $this->assertObjectHasAttribute('headers', $modified);
+ $this->assertObjectHasAttribute('content', $modified);
+ $this->assertObjectHasAttribute('version', $modified);
+ $this->assertObjectHasAttribute('statusCode', $modified);
+ $this->assertObjectHasAttribute('statusText', $modified);
+ $this->assertObjectHasAttribute('charset', $modified);
+ $this->assertEquals(304, $modified->getStatusCode());
+
+ ob_start();
+ $modified->sendContent();
+ $string = ob_get_clean();
+ $this->assertEmpty($string);
+ }
+}
diff --git a/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng b/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng
new file mode 100755
index 0000000..73708ca
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng
@@ -0,0 +1,31 @@
+<?xml version='1.0'?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+ ns="http://www.iana.org/assignments">
+
+ <include href="iana-registry.rng"/>
+
+ <start>
+ <element name="registry">
+ <ref name="registryMeta"/>
+ <element name="registry">
+ <ref name="registryMeta"/>
+ <zeroOrMore>
+ <element name="record">
+ <optional>
+ <attribute name="date"><ref name="genericDate"/></attribute>
+ </optional>
+ <optional>
+ <attribute name="updated"><ref name="genericDate"/></attribute>
+ </optional>
+ <element name="value"><ref name="genericRange"/></element>
+ <element name="description"><text/></element>
+ <ref name="references"/>
+ </element>
+ </zeroOrMore>
+ </element>
+ <ref name="people"/>
+ </element>
+ </start>
+
+</grammar>
diff --git a/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng b/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng
new file mode 100755
index 0000000..b9c3ca9
--- /dev/null
+++ b/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng
@@ -0,0 +1,198 @@
+<?xml version='1.0'?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0"
+ datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
+ ns="http://www.iana.org/assignments">
+
+ <define name="registryMeta">
+ <interleave>
+ <attribute name="id"><data type="ID"/></attribute>
+ <optional><element name="title"><ref name="text_with_references"/></element></optional>
+ <optional><element name="created"><ref name="genericDate"/></element></optional>
+ <optional><element name="updated"><data type="date"/></element></optional>
+ <optional><element name="registration_rule"><ref
+ name="text_with_references"/></element></optional>
+ <optional><element name="expert"><text/></element></optional>
+ <optional><element name="description"><ref name="text_with_references"/></element></optional>
+ <zeroOrMore><element name="note"><ref name="text_with_references"/></element></zeroOrMore>
+ <ref name="references"/>
+ <optional><element name="hide"><empty/></element></optional>
+ <zeroOrMore><element name="category"><text/></element></zeroOrMore>
+ <zeroOrMore><ref name="range"/></zeroOrMore>
+ <optional><ref name="file"/></optional>
+ </interleave>
+ </define>
+
+ <define name="range">
+ <element name="range">
+ <interleave>
+ <element name="value"><text/></element>
+ <optional><element name="hex"><text/></element></optional>
+ <element name="registration_rule"><ref name="text_with_references"/></element>
+ <optional><element name="note"><ref name="text_with_references"/></element></optional>
+ <optional><ref name="xref"/></optional>
+ </interleave>
+ </element>
+ </define>
+
+ <define name="people">
+ <element name="people">
+ <zeroOrMore>
+ <element name="person">
+ <attribute name="id"><data type="ID"/></attribute>
+ <optional><element name="name"><text/></element></optional>
+ <optional><element name="org"><text/></element></optional>
+ <zeroOrMore><element name="uri"><data type="anyURI"/></element></zeroOrMore>
+ <optional><element name="updated"><ref name="genericDate"/></element></optional>
+ </element>
+ </zeroOrMore>
+ </element>
+ </define>
+
+ <define name="xref">
+ <element name="xref">
+ <optional>
+ <attribute name="lastupdated"><ref name="genericDate"/></attribute>
+ </optional>
+ <choice>
+ <group>
+ <attribute name="type"><value>uri</value></attribute>
+ <attribute name="data"><data type="anyURI"/></attribute>
+ </group>
+ <group>
+ <attribute name="type"><value>rfc</value></attribute>
+ <attribute name="data">
+ <data type="string">
+ <param name="pattern">(rfc|bcp|std)\d+</param>
+ </data>
+ </attribute>
+ </group>
+ <group>
+ <attribute name="type"><value>rfc-errata</value></attribute>
+ <attribute name="data"><data type="positiveInteger"/></attribute>
+ </group>
+ <group>
+ <attribute name="type"><value>draft</value></attribute>
+ <attribute name="data">
+ <data type="string">
+ <param name="pattern">(draft|RFC)(-[a-zA-Z0-9]+)+</param>
+ </data>
+ </attribute>
+ </group>
+ <group>
+ <attribute name="type"><value>registry</value></attribute>
+ <attribute name="data"><data type="NCName"/></attribute>
+ </group>
+ <group>
+ <attribute name="type"><value>person</value></attribute>
+ <attribute name="data"><data type="NCName"/></attribute>
+ </group>
+ <group>
+ <attribute name="type"><value>text</value></attribute>
+ </group>
+ <group>
+ <attribute name="type"><value>note</value></attribute>
+ <attribute name="data"><data type="positiveInteger"/></attribute>
+ </group>
+ <group>
+ <attribute name="type"><value>unicode</value></attribute>
+ <attribute name="data">
+ <data type="string">
+ <param name="pattern">ucd\d+\.\d+\.\d+</param>
+ </data>
+ </attribute>
+ </group>
+ </choice>
+ <text/>
+ </element>
+ </define>
+
+ <define name="references">
+ <zeroOrMore>
+ <ref name="xref"/>
+ </zeroOrMore>
+ </define>
+
+ <define name="text_with_references">
+ <interleave>
+ <zeroOrMore>
+ <text/>
+ <optional><ref name="xref"/></optional>
+ </zeroOrMore>
+ </interleave>
+ </define>
+
+ <define name="richText">
+ <zeroOrMore>
+ <choice>
+ <interleave>
+ <ref name="text_with_references"/>
+ <optional><element name="br"><empty/></element></optional>
+ </interleave>
+ <element name="paragraph">
+ <interleave>
+ <ref name="text_with_references"/>
+ <optional><element name="br"><empty/></element></optional>
+ </interleave>
+ </element>
+ <element name="artwork"><text/></element>
+ </choice>
+ </zeroOrMore>
+ </define>
+
+ <define name="genericRange">
+ <data type="string">
+ <param name="pattern">(\d+|0x[\da-fA-F]+)(\s*-\s*(\d+|0x[\da-fA-F]+))?</param>
+ </data>
+ </define>
+
+ <define name="genericDate">
+ <choice>
+ <data type="date"/>
+ <data type="gYearMonth"/>
+ </choice>
+ </define>
+
+ <define name="hex32">
+ <data type="string">
+ <param name="pattern">0x[0-9]{8}</param>
+ </data>
+ </define>
+
+ <define name="binary">
+ <data type="string">
+ <param name="pattern">[0-1]+</param>
+ </data>
+ </define>
+
+ <define name="footnotes">
+ <zeroOrMore>
+ <element name="footnote">
+ <attribute name="anchor"><data type="positiveInteger"/></attribute>
+ <interleave>
+ <zeroOrMore>
+ <text/>
+ <optional><ref name="xref"/></optional>
+ </zeroOrMore>
+ </interleave>
+ </element>
+ </zeroOrMore>
+ </define>
+
+ <define name="file">
+ <element name="file">
+ <attribute name="type">
+ <choice>
+ <value>legacy</value>
+ <value>mib</value>
+ <value>template</value>
+ <value>json</value>
+ </choice>
+ </attribute>
+ <optional>
+ <attribute name="name"/>
+ </optional>
+ <data type="anyURI"/>
+ </element>
+ </define>
+
+</grammar>
diff --git a/vendor/symfony/http-foundation/composer.json b/vendor/symfony/http-foundation/composer.json
new file mode 100755
index 0000000..f6c6f2e
--- /dev/null
+++ b/vendor/symfony/http-foundation/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "symfony/http-foundation",
+ "type": "library",
+ "description": "Symfony HttpFoundation Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/polyfill-mbstring": "~1.1",
+ "symfony/polyfill-php70": "~1.6"
+ },
+ "require-dev": {
+ "symfony/expression-language": "~2.8|~3.0|~4.0"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/http-foundation/phpunit.xml.dist b/vendor/symfony/http-foundation/phpunit.xml.dist
new file mode 100755
index 0000000..f57bc9e
--- /dev/null
+++ b/vendor/symfony/http-foundation/phpunit.xml.dist
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
+ backupGlobals="false"
+ colors="true"
+ bootstrap="vendor/autoload.php"
+ failOnRisky="true"
+ failOnWarning="true"
+>
+ <php>
+ <ini name="error_reporting" value="-1" />
+ </php>
+
+ <testsuites>
+ <testsuite name="Symfony HttpFoundation Component Test Suite">
+ <directory>./Tests/</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>./</directory>
+ <exclude>
+ <directory>./Resources</directory>
+ <directory>./Tests</directory>
+ <directory>./vendor</directory>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/vendor/symfony/polyfill-intl-idn/Idn.php b/vendor/symfony/polyfill-intl-idn/Idn.php
new file mode 100755
index 0000000..127437e
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/Idn.php
@@ -0,0 +1,915 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com> and Trevor Rowbotham <trevor.rowbotham@pm.me>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Intl\Idn;
+
+use Exception;
+use Normalizer;
+use Symfony\Polyfill\Intl\Idn\Resources\unidata\DisallowedRanges;
+use Symfony\Polyfill\Intl\Idn\Resources\unidata\Regex;
+
+/**
+ * @see https://www.unicode.org/reports/tr46/
+ *
+ * @internal
+ */
+final class Idn
+{
+ const ERROR_EMPTY_LABEL = 1;
+ const ERROR_LABEL_TOO_LONG = 2;
+ const ERROR_DOMAIN_NAME_TOO_LONG = 4;
+ const ERROR_LEADING_HYPHEN = 8;
+ const ERROR_TRAILING_HYPHEN = 0x10;
+ const ERROR_HYPHEN_3_4 = 0x20;
+ const ERROR_LEADING_COMBINING_MARK = 0x40;
+ const ERROR_DISALLOWED = 0x80;
+ const ERROR_PUNYCODE = 0x100;
+ const ERROR_LABEL_HAS_DOT = 0x200;
+ const ERROR_INVALID_ACE_LABEL = 0x400;
+ const ERROR_BIDI = 0x800;
+ const ERROR_CONTEXTJ = 0x1000;
+ const ERROR_CONTEXTO_PUNCTUATION = 0x2000;
+ const ERROR_CONTEXTO_DIGITS = 0x4000;
+
+ const INTL_IDNA_VARIANT_2003 = 0;
+ const INTL_IDNA_VARIANT_UTS46 = 1;
+
+ const MAX_DOMAIN_SIZE = 253;
+ const MAX_LABEL_SIZE = 63;
+
+ const BASE = 36;
+ const TMIN = 1;
+ const TMAX = 26;
+ const SKEW = 38;
+ const DAMP = 700;
+ const INITIAL_BIAS = 72;
+ const INITIAL_N = 128;
+ const DELIMITER = '-';
+ const MAX_INT = 2147483647;
+
+ /**
+ * Contains the numeric value of a basic code point (for use in representing integers) in the
+ * range 0 to BASE-1, or -1 if b is does not represent a value.
+ *
+ * @var array<int, int>
+ */
+ private static $basicToDigit = array(
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1,
+
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ );
+
+ /**
+ * @var array<int, int>
+ */
+ private static $virama;
+
+ /**
+ * @var array<int, string>
+ */
+ private static $mapped;
+
+ /**
+ * @var array<int, bool>
+ */
+ private static $ignored;
+
+ /**
+ * @var array<int, string>
+ */
+ private static $deviation;
+
+ /**
+ * @var array<int, bool>
+ */
+ private static $disallowed;
+
+ /**
+ * @var array<int, string>
+ */
+ private static $disallowed_STD3_mapped;
+
+ /**
+ * @var array<int, bool>
+ */
+ private static $disallowed_STD3_valid;
+
+ /**
+ * @var bool
+ */
+ private static $mappingTableLoaded = false;
+
+ /**
+ * @see https://www.unicode.org/reports/tr46/#ToASCII
+ *
+ * @param string $domainName
+ * @param int $options
+ * @param int $variant
+ * @param array $idna_info
+ *
+ * @return string|false
+ */
+ public static function idn_to_ascii($domainName, $options = IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = array())
+ {
+ if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) {
+ @trigger_error('idn_to_ascii(): INTL_IDNA_VARIANT_2003 is deprecated', E_USER_DEPRECATED);
+ }
+
+ $options = array(
+ 'CheckHyphens' => true,
+ 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & \IDNA_CHECK_BIDI),
+ 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & \IDNA_CHECK_CONTEXTJ),
+ 'UseSTD3ASCIIRules' => 0 !== ($options & \IDNA_USE_STD3_RULES),
+ 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & \IDNA_NONTRANSITIONAL_TO_ASCII),
+ 'VerifyDnsLength' => true,
+ );
+ $info = new Info();
+ $labels = self::process((string) $domainName, $options, $info);
+
+ foreach ($labels as $i => $label) {
+ // Only convert labels to punycode that contain non-ASCII code points
+ if (1 === preg_match('/[^\x00-\x7F]/', $label)) {
+ try {
+ $label = 'xn--'.self::punycodeEncode($label);
+ } catch (Exception $e) {
+ $info->errors |= self::ERROR_PUNYCODE;
+ }
+
+ $labels[$i] = $label;
+ }
+ }
+
+ if ($options['VerifyDnsLength']) {
+ self::validateDomainAndLabelLength($labels, $info);
+ }
+
+ $idna_info = array(
+ 'result' => implode('.', $labels),
+ 'isTransitionalDifferent' => $info->transitionalDifferent,
+ 'errors' => $info->errors,
+ );
+
+ return 0 === $info->errors ? $idna_info['result'] : false;
+ }
+
+ /**
+ * @see https://www.unicode.org/reports/tr46/#ToUnicode
+ *
+ * @param string $domainName
+ * @param int $options
+ * @param int $variant
+ * @param array $idna_info
+ *
+ * @return string|false
+ */
+ public static function idn_to_utf8($domainName, $options = IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = array())
+ {
+ if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) {
+ @trigger_error('idn_to_utf8(): INTL_IDNA_VARIANT_2003 is deprecated', E_USER_DEPRECATED);
+ }
+
+ $info = new Info();
+ $labels = self::process((string) $domainName, array(
+ 'CheckHyphens' => true,
+ 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & \IDNA_CHECK_BIDI),
+ 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & \IDNA_CHECK_CONTEXTJ),
+ 'UseSTD3ASCIIRules' => 0 !== ($options & \IDNA_USE_STD3_RULES),
+ 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & \IDNA_NONTRANSITIONAL_TO_UNICODE),
+ ), $info);
+ $idna_info = array(
+ 'result' => implode('.', $labels),
+ 'isTransitionalDifferent' => $info->transitionalDifferent,
+ 'errors' => $info->errors,
+ );
+
+ return 0 === $info->errors ? $idna_info['result'] : false;
+ }
+
+ /**
+ * @param string $label
+ *
+ * @return bool
+ */
+ private static function isValidContextJ(array $codePoints, $label)
+ {
+ if (!isset(self::$virama)) {
+ self::$virama = require __DIR__.\DIRECTORY_SEPARATOR.'Resources'.\DIRECTORY_SEPARATOR.'unidata'.\DIRECTORY_SEPARATOR.'virama.php';
+ }
+
+ $offset = 0;
+
+ foreach ($codePoints as $i => $codePoint) {
+ if (0x200C !== $codePoint && 0x200D !== $codePoint) {
+ continue;
+ }
+
+ if (!isset($codePoints[$i - 1])) {
+ return false;
+ }
+
+ // If Canonical_Combining_Class(Before(cp)) .eq. Virama Then True;
+ if (isset(self::$virama[$codePoints[$i - 1]])) {
+ continue;
+ }
+
+ // If RegExpMatch((Joining_Type:{L,D})(Joining_Type:T)*\u200C(Joining_Type:T)*(Joining_Type:{R,D})) Then
+ // True;
+ // Generated RegExp = ([Joining_Type:{L,D}][Joining_Type:T]*\u200C[Joining_Type:T]*)[Joining_Type:{R,D}]
+ if (0x200C === $codePoint && 1 === preg_match(Regex::ZWNJ, $label, $matches, PREG_OFFSET_CAPTURE, $offset)) {
+ $offset += \strlen($matches[1][0]);
+
+ continue;
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @see https://www.unicode.org/reports/tr46/#ProcessingStepMap
+ *
+ * @param string $input
+ * @param array<string, bool> $options
+ *
+ * @return string
+ */
+ private static function mapCodePoints($input, array $options, Info $info)
+ {
+ $str = '';
+ $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules'];
+ $transitional = $options['Transitional_Processing'];
+
+ foreach (self::utf8Decode($input) as $codePoint) {
+ $data = self::lookupCodePointStatus($codePoint, $useSTD3ASCIIRules);
+
+ switch ($data['status']) {
+ case 'disallowed':
+ $info->errors |= self::ERROR_DISALLOWED;
+
+ // no break.
+
+ case 'valid':
+ $str .= mb_chr($codePoint, 'utf-8');
+
+ break;
+
+ case 'ignored':
+ // Do nothing.
+ break;
+
+ case 'mapped':
+ $str .= $data['mapping'];
+
+ break;
+
+ case 'deviation':
+ $info->transitionalDifferent = true;
+ $str .= ($transitional ? $data['mapping'] : mb_chr($codePoint, 'utf-8'));
+
+ break;
+ }
+ }
+
+ return $str;
+ }
+
+ /**
+ * @see https://www.unicode.org/reports/tr46/#Processing
+ *
+ * @param string $domain
+ * @param array<string, bool> $options
+ *
+ * @return array<int, string>
+ */
+ private static function process($domain, array $options, Info $info)
+ {
+ // If VerifyDnsLength is not set, we are doing ToUnicode otherwise we are doing ToASCII and
+ // we need to respect the VerifyDnsLength option.
+ $checkForEmptyLabels = !isset($options['VerifyDnsLength']) || $options['VerifyDnsLength'];
+
+ if ($checkForEmptyLabels && '' === $domain) {
+ $info->errors |= self::ERROR_EMPTY_LABEL;
+
+ return array($domain);
+ }
+
+ // Step 1. Map each code point in the domain name string
+ $domain = self::mapCodePoints($domain, $options, $info);
+
+ // Step 2. Normalize the domain name string to Unicode Normalization Form C.
+ if (!Normalizer::isNormalized($domain, Normalizer::FORM_C)) {
+ $domain = Normalizer::normalize($domain, Normalizer::FORM_C);
+ }
+
+ // Step 3. Break the string into labels at U+002E (.) FULL STOP.
+ $labels = explode('.', $domain);
+ $lastLabelIndex = \count($labels) - 1;
+
+ // Step 4. Convert and validate each label in the domain name string.
+ foreach ($labels as $i => $label) {
+ $validationOptions = $options;
+
+ if ('xn--' === substr($label, 0, 4)) {
+ try {
+ $label = self::punycodeDecode(substr($label, 4));
+ } catch (Exception $e) {
+ $info->errors |= self::ERROR_PUNYCODE;
+
+ continue;
+ }
+
+ $validationOptions['Transitional_Processing'] = false;
+ $labels[$i] = $label;
+ }
+
+ self::validateLabel($label, $info, $validationOptions, $i > 0 && $i === $lastLabelIndex);
+ }
+
+ if ($info->bidiDomain && !$info->validBidiDomain) {
+ $info->errors |= self::ERROR_BIDI;
+ }
+
+ // Any input domain name string that does not record an error has been successfully
+ // processed according to this specification. Conversely, if an input domain_name string
+ // causes an error, then the processing of the input domain_name string fails. Determining
+ // what to do with error input is up to the caller, and not in the scope of this document.
+ return $labels;
+ }
+
+ /**
+ * @see https://tools.ietf.org/html/rfc5893#section-2
+ *
+ * @param string $label
+ */
+ private static function validateBidiLabel($label, Info $info)
+ {
+ if (1 === preg_match(Regex::RTL_LABEL, $label)) {
+ $info->bidiDomain = true;
+
+ // Step 1. The first character must be a character with Bidi property L, R, or AL.
+ // If it has the R or AL property, it is an RTL label
+ if (1 !== preg_match(Regex::BIDI_STEP_1_RTL, $label)) {
+ $info->validBidiDomain = false;
+
+ return;
+ }
+
+ // Step 2. In an RTL label, only characters with the Bidi properties R, AL, AN, EN, ES,
+ // CS, ET, ON, BN, or NSM are allowed.
+ if (1 === preg_match(Regex::BIDI_STEP_2, $label)) {
+ $info->validBidiDomain = false;
+
+ return;
+ }
+
+ // Step 3. In an RTL label, the end of the label must be a character with Bidi property
+ // R, AL, EN, or AN, followed by zero or more characters with Bidi property NSM.
+ if (1 !== preg_match(Regex::BIDI_STEP_3, $label)) {
+ $info->validBidiDomain = false;
+
+ return;
+ }
+
+ // Step 4. In an RTL label, if an EN is present, no AN may be present, and vice versa.
+ if (1 === preg_match(Regex::BIDI_STEP_4_AN, $label) && 1 === preg_match(Regex::BIDI_STEP_4_EN, $label)) {
+ $info->validBidiDomain = false;
+
+ return;
+ }
+
+ return;
+ }
+
+ // We are a LTR label
+ // Step 1. The first character must be a character with Bidi property L, R, or AL.
+ // If it has the L property, it is an LTR label.
+ if (1 !== preg_match(Regex::BIDI_STEP_1_LTR, $label)) {
+ $info->validBidiDomain = false;
+
+ return;
+ }
+
+ // Step 5. In an LTR label, only characters with the Bidi properties L, EN,
+ // ES, CS, ET, ON, BN, or NSM are allowed.
+ if (1 === preg_match(Regex::BIDI_STEP_5, $label)) {
+ $info->validBidiDomain = false;
+
+ return;
+ }
+
+ // Step 6.In an LTR label, the end of the label must be a character with Bidi property L or
+ // EN, followed by zero or more characters with Bidi property NSM.
+ if (1 !== preg_match(Regex::BIDI_STEP_6, $label)) {
+ $info->validBidiDomain = false;
+
+ return;
+ }
+ }
+
+ /**
+ * @param array<int, string> $labels
+ */
+ private static function validateDomainAndLabelLength(array $labels, Info $info)
+ {
+ $maxDomainSize = self::MAX_DOMAIN_SIZE;
+ $length = \count($labels);
+
+ // Number of "." delimiters.
+ $domainLength = $length - 1;
+
+ // If the last label is empty and it is not the first label, then it is the root label.
+ // Increase the max size by 1, making it 254, to account for the root label's "."
+ // delimiter. This also means we don't need to check the last label's length for being too
+ // long.
+ if ($length > 1 && '' === $labels[$length - 1]) {
+ ++$maxDomainSize;
+ --$length;
+ }
+
+ for ($i = 0; $i < $length; ++$i) {
+ $bytes = \strlen($labels[$i]);
+ $domainLength += $bytes;
+
+ if ($bytes > self::MAX_LABEL_SIZE) {
+ $info->errors |= self::ERROR_LABEL_TOO_LONG;
+ }
+ }
+
+ if ($domainLength > $maxDomainSize) {
+ $info->errors |= self::ERROR_DOMAIN_NAME_TOO_LONG;
+ }
+ }
+
+ /**
+ * @see https://www.unicode.org/reports/tr46/#Validity_Criteria
+ *
+ * @param string $label
+ * @param array<string, bool> $options
+ * @param bool $canBeEmpty
+ */
+ private static function validateLabel($label, Info $info, array $options, $canBeEmpty)
+ {
+ if ('' === $label) {
+ if (!$canBeEmpty && (!isset($options['VerifyDnsLength']) || $options['VerifyDnsLength'])) {
+ $info->errors |= self::ERROR_EMPTY_LABEL;
+ }
+
+ return;
+ }
+
+ // Step 1. The label must be in Unicode Normalization Form C.
+ if (!Normalizer::isNormalized($label, Normalizer::FORM_C)) {
+ $info->errors |= self::ERROR_INVALID_ACE_LABEL;
+ }
+
+ $codePoints = self::utf8Decode($label);
+
+ if ($options['CheckHyphens']) {
+ // Step 2. If CheckHyphens, the label must not contain a U+002D HYPHEN-MINUS character
+ // in both the thrid and fourth positions.
+ if (isset($codePoints[2], $codePoints[3]) && 0x002D === $codePoints[2] && 0x002D === $codePoints[3]) {
+ $info->errors |= self::ERROR_HYPHEN_3_4;
+ }
+
+ // Step 3. If CheckHyphens, the label must neither begin nor end with a U+002D
+ // HYPHEN-MINUS character.
+ if ('-' === substr($label, 0, 1)) {
+ $info->errors |= self::ERROR_LEADING_HYPHEN;
+ }
+
+ if ('-' === substr($label, -1, 1)) {
+ $info->errors |= self::ERROR_TRAILING_HYPHEN;
+ }
+ }
+
+ // Step 4. The label must not contain a U+002E (.) FULL STOP.
+ if (false !== strpos($label, '.')) {
+ $info->errors |= self::ERROR_LABEL_HAS_DOT;
+ }
+
+ // Step 5. The label must not begin with a combining mark, that is: General_Category=Mark.
+ if (1 === preg_match(Regex::COMBINING_MARK, $label)) {
+ $info->errors |= self::ERROR_LEADING_COMBINING_MARK;
+ }
+
+ // Step 6. Each code point in the label must only have certain status values according to
+ // Section 5, IDNA Mapping Table:
+ $transitional = $options['Transitional_Processing'];
+ $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules'];
+
+ foreach ($codePoints as $codePoint) {
+ $data = self::lookupCodePointStatus($codePoint, $useSTD3ASCIIRules);
+ $status = $data['status'];
+
+ if ('valid' === $status || (!$transitional && 'deviation' === $status)) {
+ continue;
+ }
+
+ $info->errors |= self::ERROR_DISALLOWED;
+
+ break;
+ }
+
+ // Step 7. If CheckJoiners, the label must satisify the ContextJ rules from Appendix A, in
+ // The Unicode Code Points and Internationalized Domain Names for Applications (IDNA)
+ // [IDNA2008].
+ if ($options['CheckJoiners'] && !self::isValidContextJ($codePoints, $label)) {
+ $info->errors |= self::ERROR_CONTEXTJ;
+ }
+
+ // Step 8. If CheckBidi, and if the domain name is a Bidi domain name, then the label must
+ // satisfy all six of the numbered conditions in [IDNA2008] RFC 5893, Section 2.
+ if ($options['CheckBidi'] && (!$info->bidiDomain || $info->validBidiDomain)) {
+ self::validateBidiLabel($label, $info);
+ }
+ }
+
+ /**
+ * @see https://tools.ietf.org/html/rfc3492#section-6.2
+ *
+ * @param string $input
+ *
+ * @return string
+ */
+ private static function punycodeDecode($input)
+ {
+ $n = self::INITIAL_N;
+ $out = 0;
+ $i = 0;
+ $bias = self::INITIAL_BIAS;
+ $lastDelimIndex = strrpos($input, self::DELIMITER);
+ $b = false === $lastDelimIndex ? 0 : $lastDelimIndex;
+ $inputLength = \strlen($input);
+ $output = array();
+ $bytes = array_map('ord', str_split($input));
+
+ for ($j = 0; $j < $b; ++$j) {
+ if ($bytes[$j] > 0x7F) {
+ throw new Exception('Invalid input');
+ }
+
+ $output[$out++] = $input[$j];
+ }
+
+ if ($b > 0) {
+ ++$b;
+ }
+
+ for ($in = $b; $in < $inputLength; ++$out) {
+ $oldi = $i;
+ $w = 1;
+
+ for ($k = self::BASE; /* no condition */; $k += self::BASE) {
+ if ($in >= $inputLength) {
+ throw new Exception('Invalid input');
+ }
+
+ $digit = self::$basicToDigit[$bytes[$in++] & 0xFF];
+
+ if ($digit < 0) {
+ throw new Exception('Invalid input');
+ }
+
+ if ($digit > intdiv(self::MAX_INT - $i, $w)) {
+ throw new Exception('Integer overflow');
+ }
+
+ $i += $digit * $w;
+
+ if ($k <= $bias) {
+ $t = self::TMIN;
+ } elseif ($k >= $bias + self::TMAX) {
+ $t = self::TMAX;
+ } else {
+ $t = $k - $bias;
+ }
+
+ if ($digit < $t) {
+ break;
+ }
+
+ $baseMinusT = self::BASE - $t;
+
+ if ($w > intdiv(self::MAX_INT, $baseMinusT)) {
+ throw new Exception('Integer overflow');
+ }
+
+ $w *= $baseMinusT;
+ }
+
+ $outPlusOne = $out + 1;
+ $bias = self::adaptBias($i - $oldi, $outPlusOne, 0 === $oldi);
+
+ if (intdiv($i, $outPlusOne) > self::MAX_INT - $n) {
+ throw new Exception('Integer overflow');
+ }
+
+ $n += intdiv($i, $outPlusOne);
+ $i %= $outPlusOne;
+ array_splice($output, $i++, 0, array(mb_chr($n, 'utf-8')));
+ }
+
+ return implode('', $output);
+ }
+
+ /**
+ * @see https://tools.ietf.org/html/rfc3492#section-6.3
+ *
+ * @param string $input
+ *
+ * @return string
+ */
+ private static function punycodeEncode($input)
+ {
+ $n = self::INITIAL_N;
+ $delta = 0;
+ $out = 0;
+ $bias = self::INITIAL_BIAS;
+ $inputLength = 0;
+ $output = '';
+ $iter = self::utf8Decode($input);
+
+ foreach ($iter as $codePoint) {
+ ++$inputLength;
+
+ if ($codePoint < 0x80) {
+ $output .= \chr($codePoint);
+ ++$out;
+ }
+ }
+
+ $h = $out;
+ $b = $out;
+
+ if ($b > 0) {
+ $output .= self::DELIMITER;
+ ++$out;
+ }
+
+ while ($h < $inputLength) {
+ $m = self::MAX_INT;
+
+ foreach ($iter as $codePoint) {
+ if ($codePoint >= $n && $codePoint < $m) {
+ $m = $codePoint;
+ }
+ }
+
+ if ($m - $n > intdiv(self::MAX_INT - $delta, $h + 1)) {
+ throw new Exception('Integer overflow');
+ }
+
+ $delta += ($m - $n) * ($h + 1);
+ $n = $m;
+
+ foreach ($iter as $codePoint) {
+ if ($codePoint < $n && 0 === ++$delta) {
+ throw new Exception('Integer overflow');
+ } elseif ($codePoint === $n) {
+ $q = $delta;
+
+ for ($k = self::BASE; /* no condition */; $k += self::BASE) {
+ if ($k <= $bias) {
+ $t = self::TMIN;
+ } elseif ($k >= $bias + self::TMAX) {
+ $t = self::TMAX;
+ } else {
+ $t = $k - $bias;
+ }
+
+ if ($q < $t) {
+ break;
+ }
+
+ $qMinusT = $q - $t;
+ $baseMinusT = self::BASE - $t;
+ $output .= self::encodeDigit($t + ($qMinusT) % ($baseMinusT), false);
+ ++$out;
+ $q = intdiv($qMinusT, $baseMinusT);
+ }
+
+ $output .= self::encodeDigit($q, false);
+ ++$out;
+ $bias = self::adaptBias($delta, $h + 1, $h === $b);
+ $delta = 0;
+ ++$h;
+ }
+ }
+
+ ++$delta;
+ ++$n;
+ }
+
+ return $output;
+ }
+
+ /**
+ * @see https://tools.ietf.org/html/rfc3492#section-6.1
+ *
+ * @param int $delta
+ * @param int $numPoints
+ * @param bool $firstTime
+ *
+ * @return int
+ */
+ private static function adaptBias($delta, $numPoints, $firstTime)
+ {
+ // xxx >> 1 is a faster way of doing intdiv(xxx, 2)
+ $delta = $firstTime ? intdiv($delta, self::DAMP) : $delta >> 1;
+ $delta += intdiv($delta, $numPoints);
+ $k = 0;
+
+ while ($delta > ((self::BASE - self::TMIN) * self::TMAX) >> 1) {
+ $delta = intdiv($delta, self::BASE - self::TMIN);
+ $k += self::BASE;
+ }
+
+ return $k + intdiv((self::BASE - self::TMIN + 1) * $delta, $delta + self::SKEW);
+ }
+
+ /**
+ * @param int $d
+ * @param bool $flag
+ *
+ * @return string
+ */
+ private static function encodeDigit($d, $flag)
+ {
+ return \chr($d + 22 + 75 * ($d < 26 ? 1 : 0) - (($flag ? 1 : 0) << 5));
+ }
+
+ /**
+ * Takes a UTF-8 encoded string and converts it into a series of integer code points. Any
+ * invalid byte sequences will be replaced by a U+FFFD replacement code point.
+ *
+ * @see https://encoding.spec.whatwg.org/#utf-8-decoder
+ *
+ * @param string $input
+ *
+ * @return array<int, int>
+ */
+ private static function utf8Decode($input)
+ {
+ $bytesSeen = 0;
+ $bytesNeeded = 0;
+ $lowerBoundary = 0x80;
+ $upperBoundary = 0xBF;
+ $codePoint = 0;
+ $codePoints = array();
+ $length = \strlen($input);
+
+ for ($i = 0; $i < $length; ++$i) {
+ $byte = \ord($input[$i]);
+
+ if (0 === $bytesNeeded) {
+ if ($byte >= 0x00 && $byte <= 0x7F) {
+ $codePoints[] = $byte;
+
+ continue;
+ }
+
+ if ($byte >= 0xC2 && $byte <= 0xDF) {
+ $bytesNeeded = 1;
+ $codePoint = $byte & 0x1F;
+ } elseif ($byte >= 0xE0 && $byte <= 0xEF) {
+ if (0xE0 === $byte) {
+ $lowerBoundary = 0xA0;
+ } elseif (0xED === $byte) {
+ $upperBoundary = 0x9F;
+ }
+
+ $bytesNeeded = 2;
+ $codePoint = $byte & 0xF;
+ } elseif ($byte >= 0xF0 && $byte <= 0xF4) {
+ if (0xF0 === $byte) {
+ $lowerBoundary = 0x90;
+ } elseif (0xF4 === $byte) {
+ $upperBoundary = 0x8F;
+ }
+
+ $bytesNeeded = 3;
+ $codePoint = $byte & 0x7;
+ } else {
+ $codePoints[] = 0xFFFD;
+ }
+
+ continue;
+ }
+
+ if ($byte < $lowerBoundary || $byte > $upperBoundary) {
+ $codePoint = 0;
+ $bytesNeeded = 0;
+ $bytesSeen = 0;
+ $lowerBoundary = 0x80;
+ $upperBoundary = 0xBF;
+ --$i;
+ $codePoints[] = 0xFFFD;
+
+ continue;
+ }
+
+ $lowerBoundary = 0x80;
+ $upperBoundary = 0xBF;
+ $codePoint = ($codePoint << 6) | ($byte & 0x3F);
+
+ if (++$bytesSeen !== $bytesNeeded) {
+ continue;
+ }
+
+ $codePoints[] = $codePoint;
+ $codePoint = 0;
+ $bytesNeeded = 0;
+ $bytesSeen = 0;
+ }
+
+ // String unexpectedly ended, so append a U+FFFD code point.
+ if (0 !== $bytesNeeded) {
+ $codePoints[] = 0xFFFD;
+ }
+
+ return $codePoints;
+ }
+
+ /**
+ * @param int $codePoint
+ * @param bool $useSTD3ASCIIRules
+ *
+ * @return array{status: string, mapping?: string}
+ */
+ private static function lookupCodePointStatus($codePoint, $useSTD3ASCIIRules)
+ {
+ if (!self::$mappingTableLoaded) {
+ self::$mappingTableLoaded = true;
+ self::$mapped = require __DIR__.'/Resources/unidata/mapped.php';
+ self::$ignored = require __DIR__.'/Resources/unidata/ignored.php';
+ self::$deviation = require __DIR__.'/Resources/unidata/deviation.php';
+ self::$disallowed = require __DIR__.'/Resources/unidata/disallowed.php';
+ self::$disallowed_STD3_mapped = require __DIR__.'/Resources/unidata/disallowed_STD3_mapped.php';
+ self::$disallowed_STD3_valid = require __DIR__.'/Resources/unidata/disallowed_STD3_valid.php';
+ }
+
+ if (isset(self::$mapped[$codePoint])) {
+ return array('status' => 'mapped', 'mapping' => self::$mapped[$codePoint]);
+ }
+
+ if (isset(self::$ignored[$codePoint])) {
+ return array('status' => 'ignored');
+ }
+
+ if (isset(self::$deviation[$codePoint])) {
+ return array('status' => 'deviation', 'mapping' => self::$deviation[$codePoint]);
+ }
+
+ if (isset(self::$disallowed[$codePoint]) || DisallowedRanges::inRange($codePoint)) {
+ return array('status' => 'disallowed');
+ }
+
+ $isDisallowedMapped = isset(self::$disallowed_STD3_mapped[$codePoint]);
+
+ if ($isDisallowedMapped || isset(self::$disallowed_STD3_valid[$codePoint])) {
+ $status = 'disallowed';
+
+ if (!$useSTD3ASCIIRules) {
+ $status = $isDisallowedMapped ? 'mapped' : 'valid';
+ }
+
+ if ($isDisallowedMapped) {
+ return array('status' => $status, 'mapping' => self::$disallowed_STD3_mapped[$codePoint]);
+ }
+
+ return array('status' => $status);
+ }
+
+ return array('status' => 'valid');
+ }
+}
diff --git a/vendor/symfony/polyfill-intl-idn/Info.php b/vendor/symfony/polyfill-intl-idn/Info.php
new file mode 100755
index 0000000..25c3582
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/Info.php
@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com> and Trevor Rowbotham <trevor.rowbotham@pm.me>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Intl\Idn;
+
+/**
+ * @internal
+ */
+class Info
+{
+ public $bidiDomain = false;
+ public $errors = 0;
+ public $validBidiDomain = true;
+ public $transitionalDifferent = false;
+}
diff --git a/vendor/symfony/polyfill-intl-idn/LICENSE b/vendor/symfony/polyfill-intl-idn/LICENSE
new file mode 100755
index 0000000..03c5e25
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-2019 Fabien Potencier and Trevor Rowbotham <trevor.rowbotham@pm.me>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/polyfill-intl-idn/README.md b/vendor/symfony/polyfill-intl-idn/README.md
new file mode 100755
index 0000000..2e75f2e
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/README.md
@@ -0,0 +1,12 @@
+Symfony Polyfill / Intl: Idn
+============================
+
+This component provides [`idn_to_ascii`](https://php.net/idn-to-ascii) and [`idn_to_utf8`](https://php.net/idn-to-utf8) functions to users who run php versions without the [Intl](https://php.net/intl) extension.
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php
new file mode 100755
index 0000000..5bb70e4
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php
@@ -0,0 +1,375 @@
+<?php
+
+namespace Symfony\Polyfill\Intl\Idn\Resources\unidata;
+
+/**
+ * @internal
+ */
+final class DisallowedRanges
+{
+ /**
+ * @param int $codePoint
+ *
+ * @return bool
+ */
+ public static function inRange($codePoint)
+ {
+ if ($codePoint >= 128 && $codePoint <= 159) {
+ return true;
+ }
+
+ if ($codePoint >= 2155 && $codePoint <= 2207) {
+ return true;
+ }
+
+ if ($codePoint >= 3676 && $codePoint <= 3712) {
+ return true;
+ }
+
+ if ($codePoint >= 3808 && $codePoint <= 3839) {
+ return true;
+ }
+
+ if ($codePoint >= 4059 && $codePoint <= 4095) {
+ return true;
+ }
+
+ if ($codePoint >= 4256 && $codePoint <= 4293) {
+ return true;
+ }
+
+ if ($codePoint >= 6849 && $codePoint <= 6911) {
+ return true;
+ }
+
+ if ($codePoint >= 11859 && $codePoint <= 11903) {
+ return true;
+ }
+
+ if ($codePoint >= 42955 && $codePoint <= 42996) {
+ return true;
+ }
+
+ if ($codePoint >= 55296 && $codePoint <= 57343) {
+ return true;
+ }
+
+ if ($codePoint >= 57344 && $codePoint <= 63743) {
+ return true;
+ }
+
+ if ($codePoint >= 64218 && $codePoint <= 64255) {
+ return true;
+ }
+
+ if ($codePoint >= 64976 && $codePoint <= 65007) {
+ return true;
+ }
+
+ if ($codePoint >= 65630 && $codePoint <= 65663) {
+ return true;
+ }
+
+ if ($codePoint >= 65953 && $codePoint <= 65999) {
+ return true;
+ }
+
+ if ($codePoint >= 66046 && $codePoint <= 66175) {
+ return true;
+ }
+
+ if ($codePoint >= 66518 && $codePoint <= 66559) {
+ return true;
+ }
+
+ if ($codePoint >= 66928 && $codePoint <= 67071) {
+ return true;
+ }
+
+ if ($codePoint >= 67432 && $codePoint <= 67583) {
+ return true;
+ }
+
+ if ($codePoint >= 67760 && $codePoint <= 67807) {
+ return true;
+ }
+
+ if ($codePoint >= 67904 && $codePoint <= 67967) {
+ return true;
+ }
+
+ if ($codePoint >= 68256 && $codePoint <= 68287) {
+ return true;
+ }
+
+ if ($codePoint >= 68528 && $codePoint <= 68607) {
+ return true;
+ }
+
+ if ($codePoint >= 68681 && $codePoint <= 68735) {
+ return true;
+ }
+
+ if ($codePoint >= 68922 && $codePoint <= 69215) {
+ return true;
+ }
+
+ if ($codePoint >= 69298 && $codePoint <= 69375) {
+ return true;
+ }
+
+ if ($codePoint >= 69466 && $codePoint <= 69551) {
+ return true;
+ }
+
+ if ($codePoint >= 70207 && $codePoint <= 70271) {
+ return true;
+ }
+
+ if ($codePoint >= 70517 && $codePoint <= 70655) {
+ return true;
+ }
+
+ if ($codePoint >= 70874 && $codePoint <= 71039) {
+ return true;
+ }
+
+ if ($codePoint >= 71134 && $codePoint <= 71167) {
+ return true;
+ }
+
+ if ($codePoint >= 71370 && $codePoint <= 71423) {
+ return true;
+ }
+
+ if ($codePoint >= 71488 && $codePoint <= 71679) {
+ return true;
+ }
+
+ if ($codePoint >= 71740 && $codePoint <= 71839) {
+ return true;
+ }
+
+ if ($codePoint >= 72026 && $codePoint <= 72095) {
+ return true;
+ }
+
+ if ($codePoint >= 72441 && $codePoint <= 72703) {
+ return true;
+ }
+
+ if ($codePoint >= 72887 && $codePoint <= 72959) {
+ return true;
+ }
+
+ if ($codePoint >= 73130 && $codePoint <= 73439) {
+ return true;
+ }
+
+ if ($codePoint >= 73465 && $codePoint <= 73647) {
+ return true;
+ }
+
+ if ($codePoint >= 74650 && $codePoint <= 74751) {
+ return true;
+ }
+
+ if ($codePoint >= 75076 && $codePoint <= 77823) {
+ return true;
+ }
+
+ if ($codePoint >= 78905 && $codePoint <= 82943) {
+ return true;
+ }
+
+ if ($codePoint >= 83527 && $codePoint <= 92159) {
+ return true;
+ }
+
+ if ($codePoint >= 92784 && $codePoint <= 92879) {
+ return true;
+ }
+
+ if ($codePoint >= 93072 && $codePoint <= 93759) {
+ return true;
+ }
+
+ if ($codePoint >= 93851 && $codePoint <= 93951) {
+ return true;
+ }
+
+ if ($codePoint >= 94112 && $codePoint <= 94175) {
+ return true;
+ }
+
+ if ($codePoint >= 101590 && $codePoint <= 101631) {
+ return true;
+ }
+
+ if ($codePoint >= 101641 && $codePoint <= 110591) {
+ return true;
+ }
+
+ if ($codePoint >= 110879 && $codePoint <= 110927) {
+ return true;
+ }
+
+ if ($codePoint >= 111356 && $codePoint <= 113663) {
+ return true;
+ }
+
+ if ($codePoint >= 113828 && $codePoint <= 118783) {
+ return true;
+ }
+
+ if ($codePoint >= 119366 && $codePoint <= 119519) {
+ return true;
+ }
+
+ if ($codePoint >= 119673 && $codePoint <= 119807) {
+ return true;
+ }
+
+ if ($codePoint >= 121520 && $codePoint <= 122879) {
+ return true;
+ }
+
+ if ($codePoint >= 122923 && $codePoint <= 123135) {
+ return true;
+ }
+
+ if ($codePoint >= 123216 && $codePoint <= 123583) {
+ return true;
+ }
+
+ if ($codePoint >= 123648 && $codePoint <= 124927) {
+ return true;
+ }
+
+ if ($codePoint >= 125143 && $codePoint <= 125183) {
+ return true;
+ }
+
+ if ($codePoint >= 125280 && $codePoint <= 126064) {
+ return true;
+ }
+
+ if ($codePoint >= 126133 && $codePoint <= 126208) {
+ return true;
+ }
+
+ if ($codePoint >= 126270 && $codePoint <= 126463) {
+ return true;
+ }
+
+ if ($codePoint >= 126652 && $codePoint <= 126703) {
+ return true;
+ }
+
+ if ($codePoint >= 126706 && $codePoint <= 126975) {
+ return true;
+ }
+
+ if ($codePoint >= 127406 && $codePoint <= 127461) {
+ return true;
+ }
+
+ if ($codePoint >= 127590 && $codePoint <= 127743) {
+ return true;
+ }
+
+ if ($codePoint >= 129202 && $codePoint <= 129279) {
+ return true;
+ }
+
+ if ($codePoint >= 129751 && $codePoint <= 129791) {
+ return true;
+ }
+
+ if ($codePoint >= 129995 && $codePoint <= 130031) {
+ return true;
+ }
+
+ if ($codePoint >= 130042 && $codePoint <= 131069) {
+ return true;
+ }
+
+ if ($codePoint >= 173790 && $codePoint <= 173823) {
+ return true;
+ }
+
+ if ($codePoint >= 191457 && $codePoint <= 194559) {
+ return true;
+ }
+
+ if ($codePoint >= 195102 && $codePoint <= 196605) {
+ return true;
+ }
+
+ if ($codePoint >= 201547 && $codePoint <= 262141) {
+ return true;
+ }
+
+ if ($codePoint >= 262144 && $codePoint <= 327677) {
+ return true;
+ }
+
+ if ($codePoint >= 327680 && $codePoint <= 393213) {
+ return true;
+ }
+
+ if ($codePoint >= 393216 && $codePoint <= 458749) {
+ return true;
+ }
+
+ if ($codePoint >= 458752 && $codePoint <= 524285) {
+ return true;
+ }
+
+ if ($codePoint >= 524288 && $codePoint <= 589821) {
+ return true;
+ }
+
+ if ($codePoint >= 589824 && $codePoint <= 655357) {
+ return true;
+ }
+
+ if ($codePoint >= 655360 && $codePoint <= 720893) {
+ return true;
+ }
+
+ if ($codePoint >= 720896 && $codePoint <= 786429) {
+ return true;
+ }
+
+ if ($codePoint >= 786432 && $codePoint <= 851965) {
+ return true;
+ }
+
+ if ($codePoint >= 851968 && $codePoint <= 917501) {
+ return true;
+ }
+
+ if ($codePoint >= 917536 && $codePoint <= 917631) {
+ return true;
+ }
+
+ if ($codePoint >= 917632 && $codePoint <= 917759) {
+ return true;
+ }
+
+ if ($codePoint >= 918000 && $codePoint <= 983037) {
+ return true;
+ }
+
+ if ($codePoint >= 983040 && $codePoint <= 1048573) {
+ return true;
+ }
+
+ if ($codePoint >= 1048576 && $codePoint <= 1114109) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/Regex.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/Regex.php
new file mode 100755
index 0000000..5c1c51d
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/Regex.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Symfony\Polyfill\Intl\Idn\Resources\unidata;
+
+/**
+ * @internal
+ */
+final class Regex
+{
+ const COMBINING_MARK = '/^[\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E3}-\x{0902}\x{0903}\x{093A}\x{093B}\x{093C}\x{093E}-\x{0940}\x{0941}-\x{0948}\x{0949}-\x{094C}\x{094D}\x{094E}-\x{094F}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{0982}-\x{0983}\x{09BC}\x{09BE}-\x{09C0}\x{09C1}-\x{09C4}\x{09C7}-\x{09C8}\x{09CB}-\x{09CC}\x{09CD}\x{09D7}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A03}\x{0A3C}\x{0A3E}-\x{0A40}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0A83}\x{0ABC}\x{0ABE}-\x{0AC0}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0AC9}\x{0ACB}-\x{0ACC}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B02}-\x{0B03}\x{0B3C}\x{0B3E}\x{0B3F}\x{0B40}\x{0B41}-\x{0B44}\x{0B47}-\x{0B48}\x{0B4B}-\x{0B4C}\x{0B4D}\x{0B55}-\x{0B56}\x{0B57}\x{0B62}-\x{0B63}\x{0B82}\x{0BBE}-\x{0BBF}\x{0BC0}\x{0BC1}-\x{0BC2}\x{0BC6}-\x{0BC8}\x{0BCA}-\x{0BCC}\x{0BCD}\x{0BD7}\x{0C00}\x{0C01}-\x{0C03}\x{0C04}\x{0C3E}-\x{0C40}\x{0C41}-\x{0C44}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0C82}-\x{0C83}\x{0CBC}\x{0CBE}\x{0CBF}\x{0CC0}-\x{0CC4}\x{0CC6}\x{0CC7}-\x{0CC8}\x{0CCA}-\x{0CCB}\x{0CCC}-\x{0CCD}\x{0CD5}-\x{0CD6}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D02}-\x{0D03}\x{0D3B}-\x{0D3C}\x{0D3E}-\x{0D40}\x{0D41}-\x{0D44}\x{0D46}-\x{0D48}\x{0D4A}-\x{0D4C}\x{0D4D}\x{0D57}\x{0D62}-\x{0D63}\x{0D81}\x{0D82}-\x{0D83}\x{0DCA}\x{0DCF}-\x{0DD1}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0DD8}-\x{0DDF}\x{0DF2}-\x{0DF3}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F3E}-\x{0F3F}\x{0F71}-\x{0F7E}\x{0F7F}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102B}-\x{102C}\x{102D}-\x{1030}\x{1031}\x{1032}-\x{1037}\x{1038}\x{1039}-\x{103A}\x{103B}-\x{103C}\x{103D}-\x{103E}\x{1056}-\x{1057}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106D}\x{1071}-\x{1074}\x{1082}\x{1083}-\x{1084}\x{1085}-\x{1086}\x{1087}-\x{108C}\x{108D}\x{108F}\x{109A}-\x{109C}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B6}\x{17B7}-\x{17BD}\x{17BE}-\x{17C5}\x{17C6}\x{17C7}-\x{17C8}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1923}-\x{1926}\x{1927}-\x{1928}\x{1929}-\x{192B}\x{1930}-\x{1931}\x{1932}\x{1933}-\x{1938}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A19}-\x{1A1A}\x{1A1B}\x{1A55}\x{1A56}\x{1A57}\x{1A58}-\x{1A5E}\x{1A60}\x{1A61}\x{1A62}\x{1A63}-\x{1A64}\x{1A65}-\x{1A6C}\x{1A6D}-\x{1A72}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B04}\x{1B34}\x{1B35}\x{1B36}-\x{1B3A}\x{1B3B}\x{1B3C}\x{1B3D}-\x{1B41}\x{1B42}\x{1B43}-\x{1B44}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1B82}\x{1BA1}\x{1BA2}-\x{1BA5}\x{1BA6}-\x{1BA7}\x{1BA8}-\x{1BA9}\x{1BAA}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE7}\x{1BE8}-\x{1BE9}\x{1BEA}-\x{1BEC}\x{1BED}\x{1BEE}\x{1BEF}-\x{1BF1}\x{1BF2}-\x{1BF3}\x{1C24}-\x{1C2B}\x{1C2C}-\x{1C33}\x{1C34}-\x{1C35}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE1}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF7}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{302E}-\x{302F}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A823}-\x{A824}\x{A825}-\x{A826}\x{A827}\x{A82C}\x{A880}-\x{A881}\x{A8B4}-\x{A8C3}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A952}-\x{A953}\x{A980}-\x{A982}\x{A983}\x{A9B3}\x{A9B4}-\x{A9B5}\x{A9B6}-\x{A9B9}\x{A9BA}-\x{A9BB}\x{A9BC}-\x{A9BD}\x{A9BE}-\x{A9C0}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA2F}-\x{AA30}\x{AA31}-\x{AA32}\x{AA33}-\x{AA34}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA4D}\x{AA7B}\x{AA7C}\x{AA7D}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEB}\x{AAEC}-\x{AAED}\x{AAEE}-\x{AAEF}\x{AAF5}\x{AAF6}\x{ABE3}-\x{ABE4}\x{ABE5}\x{ABE6}-\x{ABE7}\x{ABE8}\x{ABE9}-\x{ABEA}\x{ABEC}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{11000}\x{11001}\x{11002}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{11082}\x{110B0}-\x{110B2}\x{110B3}-\x{110B6}\x{110B7}-\x{110B8}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112C}\x{1112D}-\x{11134}\x{11145}-\x{11146}\x{11173}\x{11180}-\x{11181}\x{11182}\x{111B3}-\x{111B5}\x{111B6}-\x{111BE}\x{111BF}-\x{111C0}\x{111C9}-\x{111CC}\x{111CE}\x{111CF}\x{1122C}-\x{1122E}\x{1122F}-\x{11231}\x{11232}-\x{11233}\x{11234}\x{11235}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E0}-\x{112E2}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{11302}-\x{11303}\x{1133B}-\x{1133C}\x{1133E}-\x{1133F}\x{11340}\x{11341}-\x{11344}\x{11347}-\x{11348}\x{1134B}-\x{1134D}\x{11357}\x{11362}-\x{11363}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11435}-\x{11437}\x{11438}-\x{1143F}\x{11440}-\x{11441}\x{11442}-\x{11444}\x{11445}\x{11446}\x{1145E}\x{114B0}-\x{114B2}\x{114B3}-\x{114B8}\x{114B9}\x{114BA}\x{114BB}-\x{114BE}\x{114BF}-\x{114C0}\x{114C1}\x{114C2}-\x{114C3}\x{115AF}-\x{115B1}\x{115B2}-\x{115B5}\x{115B8}-\x{115BB}\x{115BC}-\x{115BD}\x{115BE}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11630}-\x{11632}\x{11633}-\x{1163A}\x{1163B}-\x{1163C}\x{1163D}\x{1163E}\x{1163F}-\x{11640}\x{116AB}\x{116AC}\x{116AD}\x{116AE}-\x{116AF}\x{116B0}-\x{116B5}\x{116B6}\x{116B7}\x{1171D}-\x{1171F}\x{11720}-\x{11721}\x{11722}-\x{11725}\x{11726}\x{11727}-\x{1172B}\x{1182C}-\x{1182E}\x{1182F}-\x{11837}\x{11838}\x{11839}-\x{1183A}\x{11930}-\x{11935}\x{11937}-\x{11938}\x{1193B}-\x{1193C}\x{1193D}\x{1193E}\x{11940}\x{11942}\x{11943}\x{119D1}-\x{119D3}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119DC}-\x{119DF}\x{119E0}\x{119E4}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A39}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A57}-\x{11A58}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A97}\x{11A98}-\x{11A99}\x{11C2F}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3E}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CA9}\x{11CAA}-\x{11CB0}\x{11CB1}\x{11CB2}-\x{11CB3}\x{11CB4}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D8A}-\x{11D8E}\x{11D90}-\x{11D91}\x{11D93}-\x{11D94}\x{11D95}\x{11D96}\x{11D97}\x{11EF3}-\x{11EF4}\x{11EF5}-\x{11EF6}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F51}-\x{16F87}\x{16F8F}-\x{16F92}\x{16FE4}\x{16FF0}-\x{16FF1}\x{1BC9D}-\x{1BC9E}\x{1D165}-\x{1D166}\x{1D167}-\x{1D169}\x{1D16D}-\x{1D172}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{E0100}-\x{E01EF}]/u';
+
+ const RTL_LABEL = '/[\x{0590}\x{05BE}\x{05C0}\x{05C3}\x{05C6}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0608}\x{060B}\x{060D}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{0660}-\x{0669}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06DD}\x{06E5}-\x{06E6}\x{06EE}-\x{06EF}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0712}-\x{072F}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07F4}-\x{07F5}\x{07FA}\x{07FB}-\x{07FC}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{081A}\x{0824}\x{0828}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{08E2}\x{200F}\x{FB1D}\x{FB1F}-\x{FB28}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFE}-\x{FDFF}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A04}\x{10A07}-\x{10A0B}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A3B}-\x{10A3E}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}]/u';
+
+ const BIDI_STEP_1_LTR = '/^[^\x{0000}-\x{0008}\x{0009}\x{000A}\x{000B}\x{000C}\x{000D}\x{000E}-\x{001B}\x{001C}-\x{001E}\x{001F}\x{0020}\x{0021}-\x{0022}\x{0023}\x{0024}\x{0025}\x{0026}-\x{0027}\x{0028}\x{0029}\x{002A}\x{002B}\x{002C}\x{002D}\x{002E}-\x{002F}\x{0030}-\x{0039}\x{003A}\x{003B}\x{003C}-\x{003E}\x{003F}-\x{0040}\x{005B}\x{005C}\x{005D}\x{005E}\x{005F}\x{0060}\x{007B}\x{007C}\x{007D}\x{007E}\x{007F}-\x{0084}\x{0085}\x{0086}-\x{009F}\x{00A0}\x{00A1}\x{00A2}-\x{00A5}\x{00A6}\x{00A7}\x{00A8}\x{00A9}\x{00AB}\x{00AC}\x{00AD}\x{00AE}\x{00AF}\x{00B0}\x{00B1}\x{00B2}-\x{00B3}\x{00B4}\x{00B6}-\x{00B7}\x{00B8}\x{00B9}\x{00BB}\x{00BC}-\x{00BE}\x{00BF}\x{00D7}\x{00F7}\x{02B9}-\x{02BA}\x{02C2}-\x{02C5}\x{02C6}-\x{02CF}\x{02D2}-\x{02DF}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037E}\x{0384}-\x{0385}\x{0387}\x{03F6}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{058A}\x{058D}-\x{058E}\x{058F}\x{0590}\x{0591}-\x{05BD}\x{05BE}\x{05BF}\x{05C0}\x{05C1}-\x{05C2}\x{05C3}\x{05C4}-\x{05C5}\x{05C6}\x{05C7}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0606}-\x{0607}\x{0608}\x{0609}-\x{060A}\x{060B}\x{060C}\x{060D}\x{060E}-\x{060F}\x{0610}-\x{061A}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{064B}-\x{065F}\x{0660}-\x{0669}\x{066A}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0670}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06D6}-\x{06DC}\x{06DD}\x{06DE}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06E9}\x{06EA}-\x{06ED}\x{06EE}-\x{06EF}\x{06F0}-\x{06F9}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0711}\x{0712}-\x{072F}\x{0730}-\x{074A}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07A6}-\x{07B0}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07F6}\x{07F7}-\x{07F9}\x{07FA}\x{07FB}-\x{07FC}\x{07FD}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{0859}-\x{085B}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09F2}-\x{09F3}\x{09FB}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AF1}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0BF3}-\x{0BF8}\x{0BF9}\x{0BFA}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C78}-\x{0C7E}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E3F}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F3A}\x{0F3B}\x{0F3C}\x{0F3D}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1390}-\x{1399}\x{1400}\x{1680}\x{169B}\x{169C}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DB}\x{17DD}\x{17F0}-\x{17F9}\x{1800}-\x{1805}\x{1806}\x{1807}-\x{180A}\x{180B}-\x{180D}\x{180E}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1940}\x{1944}-\x{1945}\x{19DE}-\x{19FF}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{2000}-\x{200A}\x{200B}-\x{200D}\x{200F}\x{2010}-\x{2015}\x{2016}-\x{2017}\x{2018}\x{2019}\x{201A}\x{201B}-\x{201C}\x{201D}\x{201E}\x{201F}\x{2020}-\x{2027}\x{2028}\x{2029}\x{202A}\x{202B}\x{202C}\x{202D}\x{202E}\x{202F}\x{2030}-\x{2034}\x{2035}-\x{2038}\x{2039}\x{203A}\x{203B}-\x{203E}\x{203F}-\x{2040}\x{2041}-\x{2043}\x{2044}\x{2045}\x{2046}\x{2047}-\x{2051}\x{2052}\x{2053}\x{2054}\x{2055}-\x{205E}\x{205F}\x{2060}-\x{2064}\x{2065}\x{2066}\x{2067}\x{2068}\x{2069}\x{206A}-\x{206F}\x{2070}\x{2074}-\x{2079}\x{207A}-\x{207B}\x{207C}\x{207D}\x{207E}\x{2080}-\x{2089}\x{208A}-\x{208B}\x{208C}\x{208D}\x{208E}\x{20A0}-\x{20BF}\x{20C0}-\x{20CF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}\x{2116}-\x{2117}\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}\x{214B}\x{214C}-\x{214D}\x{2150}-\x{215F}\x{2189}\x{218A}-\x{218B}\x{2190}-\x{2194}\x{2195}-\x{2199}\x{219A}-\x{219B}\x{219C}-\x{219F}\x{21A0}\x{21A1}-\x{21A2}\x{21A3}\x{21A4}-\x{21A5}\x{21A6}\x{21A7}-\x{21AD}\x{21AE}\x{21AF}-\x{21CD}\x{21CE}-\x{21CF}\x{21D0}-\x{21D1}\x{21D2}\x{21D3}\x{21D4}\x{21D5}-\x{21F3}\x{21F4}-\x{2211}\x{2212}\x{2213}\x{2214}-\x{22FF}\x{2300}-\x{2307}\x{2308}\x{2309}\x{230A}\x{230B}\x{230C}-\x{231F}\x{2320}-\x{2321}\x{2322}-\x{2328}\x{2329}\x{232A}\x{232B}-\x{2335}\x{237B}\x{237C}\x{237D}-\x{2394}\x{2396}-\x{239A}\x{239B}-\x{23B3}\x{23B4}-\x{23DB}\x{23DC}-\x{23E1}\x{23E2}-\x{2426}\x{2440}-\x{244A}\x{2460}-\x{2487}\x{2488}-\x{249B}\x{24EA}-\x{24FF}\x{2500}-\x{25B6}\x{25B7}\x{25B8}-\x{25C0}\x{25C1}\x{25C2}-\x{25F7}\x{25F8}-\x{25FF}\x{2600}-\x{266E}\x{266F}\x{2670}-\x{26AB}\x{26AD}-\x{2767}\x{2768}\x{2769}\x{276A}\x{276B}\x{276C}\x{276D}\x{276E}\x{276F}\x{2770}\x{2771}\x{2772}\x{2773}\x{2774}\x{2775}\x{2776}-\x{2793}\x{2794}-\x{27BF}\x{27C0}-\x{27C4}\x{27C5}\x{27C6}\x{27C7}-\x{27E5}\x{27E6}\x{27E7}\x{27E8}\x{27E9}\x{27EA}\x{27EB}\x{27EC}\x{27ED}\x{27EE}\x{27EF}\x{27F0}-\x{27FF}\x{2900}-\x{2982}\x{2983}\x{2984}\x{2985}\x{2986}\x{2987}\x{2988}\x{2989}\x{298A}\x{298B}\x{298C}\x{298D}\x{298E}\x{298F}\x{2990}\x{2991}\x{2992}\x{2993}\x{2994}\x{2995}\x{2996}\x{2997}\x{2998}\x{2999}-\x{29D7}\x{29D8}\x{29D9}\x{29DA}\x{29DB}\x{29DC}-\x{29FB}\x{29FC}\x{29FD}\x{29FE}-\x{2AFF}\x{2B00}-\x{2B2F}\x{2B30}-\x{2B44}\x{2B45}-\x{2B46}\x{2B47}-\x{2B4C}\x{2B4D}-\x{2B73}\x{2B76}-\x{2B95}\x{2B97}-\x{2BFF}\x{2CE5}-\x{2CEA}\x{2CEF}-\x{2CF1}\x{2CF9}-\x{2CFC}\x{2CFD}\x{2CFE}-\x{2CFF}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E00}-\x{2E01}\x{2E02}\x{2E03}\x{2E04}\x{2E05}\x{2E06}-\x{2E08}\x{2E09}\x{2E0A}\x{2E0B}\x{2E0C}\x{2E0D}\x{2E0E}-\x{2E16}\x{2E17}\x{2E18}-\x{2E19}\x{2E1A}\x{2E1B}\x{2E1C}\x{2E1D}\x{2E1E}-\x{2E1F}\x{2E20}\x{2E21}\x{2E22}\x{2E23}\x{2E24}\x{2E25}\x{2E26}\x{2E27}\x{2E28}\x{2E29}\x{2E2A}-\x{2E2E}\x{2E2F}\x{2E30}-\x{2E39}\x{2E3A}-\x{2E3B}\x{2E3C}-\x{2E3F}\x{2E40}\x{2E41}\x{2E42}\x{2E43}-\x{2E4F}\x{2E50}-\x{2E51}\x{2E52}\x{2E80}-\x{2E99}\x{2E9B}-\x{2EF3}\x{2F00}-\x{2FD5}\x{2FF0}-\x{2FFB}\x{3000}\x{3001}-\x{3003}\x{3004}\x{3008}\x{3009}\x{300A}\x{300B}\x{300C}\x{300D}\x{300E}\x{300F}\x{3010}\x{3011}\x{3012}-\x{3013}\x{3014}\x{3015}\x{3016}\x{3017}\x{3018}\x{3019}\x{301A}\x{301B}\x{301C}\x{301D}\x{301E}-\x{301F}\x{3020}\x{302A}-\x{302D}\x{3030}\x{3036}-\x{3037}\x{303D}\x{303E}-\x{303F}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{31C0}-\x{31E3}\x{321D}-\x{321E}\x{3250}\x{3251}-\x{325F}\x{327C}-\x{327E}\x{32B1}-\x{32BF}\x{32CC}-\x{32CF}\x{3377}-\x{337A}\x{33DE}-\x{33DF}\x{33FF}\x{4DC0}-\x{4DFF}\x{A490}-\x{A4C6}\x{A60D}-\x{A60F}\x{A66F}\x{A670}-\x{A672}\x{A673}\x{A674}-\x{A67D}\x{A67E}\x{A67F}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A788}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A828}-\x{A82B}\x{A82C}\x{A838}\x{A839}\x{A874}-\x{A877}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{AB6A}-\x{AB6B}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1D}\x{FB1E}\x{FB1F}-\x{FB28}\x{FB29}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD3E}\x{FD3F}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDD0}-\x{FDEF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFD}\x{FDFE}-\x{FDFF}\x{FE00}-\x{FE0F}\x{FE10}-\x{FE16}\x{FE17}\x{FE18}\x{FE19}\x{FE20}-\x{FE2F}\x{FE30}\x{FE31}-\x{FE32}\x{FE33}-\x{FE34}\x{FE35}\x{FE36}\x{FE37}\x{FE38}\x{FE39}\x{FE3A}\x{FE3B}\x{FE3C}\x{FE3D}\x{FE3E}\x{FE3F}\x{FE40}\x{FE41}\x{FE42}\x{FE43}\x{FE44}\x{FE45}-\x{FE46}\x{FE47}\x{FE48}\x{FE49}-\x{FE4C}\x{FE4D}-\x{FE4F}\x{FE50}\x{FE51}\x{FE52}\x{FE54}\x{FE55}\x{FE56}-\x{FE57}\x{FE58}\x{FE59}\x{FE5A}\x{FE5B}\x{FE5C}\x{FE5D}\x{FE5E}\x{FE5F}\x{FE60}-\x{FE61}\x{FE62}\x{FE63}\x{FE64}-\x{FE66}\x{FE68}\x{FE69}\x{FE6A}\x{FE6B}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{FEFF}\x{FF01}-\x{FF02}\x{FF03}\x{FF04}\x{FF05}\x{FF06}-\x{FF07}\x{FF08}\x{FF09}\x{FF0A}\x{FF0B}\x{FF0C}\x{FF0D}\x{FF0E}-\x{FF0F}\x{FF10}-\x{FF19}\x{FF1A}\x{FF1B}\x{FF1C}-\x{FF1E}\x{FF1F}-\x{FF20}\x{FF3B}\x{FF3C}\x{FF3D}\x{FF3E}\x{FF3F}\x{FF40}\x{FF5B}\x{FF5C}\x{FF5D}\x{FF5E}\x{FF5F}\x{FF60}\x{FF61}\x{FF62}\x{FF63}\x{FF64}-\x{FF65}\x{FFE0}-\x{FFE1}\x{FFE2}\x{FFE3}\x{FFE4}\x{FFE5}-\x{FFE6}\x{FFE8}\x{FFE9}-\x{FFEC}\x{FFED}-\x{FFEE}\x{FFF0}-\x{FFF8}\x{FFF9}-\x{FFFB}\x{FFFC}-\x{FFFD}\x{FFFE}-\x{FFFF}\x{10101}\x{10140}-\x{10174}\x{10175}-\x{10178}\x{10179}-\x{10189}\x{1018A}-\x{1018B}\x{1018C}\x{10190}-\x{1019C}\x{101A0}\x{101FD}\x{102E0}\x{102E1}-\x{102FB}\x{10376}-\x{1037A}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{1091F}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A01}-\x{10A03}\x{10A04}\x{10A05}-\x{10A06}\x{10A07}-\x{10A0B}\x{10A0C}-\x{10A0F}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A38}-\x{10A3A}\x{10A3B}-\x{10A3E}\x{10A3F}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE5}-\x{10AE6}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B39}-\x{10B3F}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D24}-\x{10D27}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAB}-\x{10EAC}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F46}-\x{10F50}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{11001}\x{11038}-\x{11046}\x{11052}-\x{11065}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{11660}-\x{1166C}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{11FD5}-\x{11FDC}\x{11FDD}-\x{11FE0}\x{11FE1}-\x{11FF1}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE2}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D200}-\x{1D241}\x{1D242}-\x{1D244}\x{1D245}\x{1D300}-\x{1D356}\x{1D6DB}\x{1D715}\x{1D74F}\x{1D789}\x{1D7C3}\x{1D7CE}-\x{1D7FF}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E2FF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D0}-\x{1E8D6}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E944}-\x{1E94A}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF0}-\x{1EEF1}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}\x{1F000}-\x{1F02B}\x{1F030}-\x{1F093}\x{1F0A0}-\x{1F0AE}\x{1F0B1}-\x{1F0BF}\x{1F0C1}-\x{1F0CF}\x{1F0D1}-\x{1F0F5}\x{1F100}-\x{1F10A}\x{1F10B}-\x{1F10C}\x{1F10D}-\x{1F10F}\x{1F12F}\x{1F16A}-\x{1F16F}\x{1F1AD}\x{1F260}-\x{1F265}\x{1F300}-\x{1F3FA}\x{1F3FB}-\x{1F3FF}\x{1F400}-\x{1F6D7}\x{1F6E0}-\x{1F6EC}\x{1F6F0}-\x{1F6FC}\x{1F700}-\x{1F773}\x{1F780}-\x{1F7D8}\x{1F7E0}-\x{1F7EB}\x{1F800}-\x{1F80B}\x{1F810}-\x{1F847}\x{1F850}-\x{1F859}\x{1F860}-\x{1F887}\x{1F890}-\x{1F8AD}\x{1F8B0}-\x{1F8B1}\x{1F900}-\x{1F978}\x{1F97A}-\x{1F9CB}\x{1F9CD}-\x{1FA53}\x{1FA60}-\x{1FA6D}\x{1FA70}-\x{1FA74}\x{1FA78}-\x{1FA7A}\x{1FA80}-\x{1FA86}\x{1FA90}-\x{1FAA8}\x{1FAB0}-\x{1FAB6}\x{1FAC0}-\x{1FAC2}\x{1FAD0}-\x{1FAD6}\x{1FB00}-\x{1FB92}\x{1FB94}-\x{1FBCA}\x{1FBF0}-\x{1FBF9}\x{1FFFE}-\x{1FFFF}\x{2FFFE}-\x{2FFFF}\x{3FFFE}-\x{3FFFF}\x{4FFFE}-\x{4FFFF}\x{5FFFE}-\x{5FFFF}\x{6FFFE}-\x{6FFFF}\x{7FFFE}-\x{7FFFF}\x{8FFFE}-\x{8FFFF}\x{9FFFE}-\x{9FFFF}\x{AFFFE}-\x{AFFFF}\x{BFFFE}-\x{BFFFF}\x{CFFFE}-\x{CFFFF}\x{DFFFE}-\x{E0000}\x{E0001}\x{E0002}-\x{E001F}\x{E0020}-\x{E007F}\x{E0080}-\x{E00FF}\x{E0100}-\x{E01EF}\x{E01F0}-\x{E0FFF}\x{EFFFE}-\x{EFFFF}\x{FFFFE}-\x{FFFFF}\x{10FFFE}-\x{10FFFF}]/u';
+ const BIDI_STEP_1_RTL = '/^[\x{0590}\x{05BE}\x{05C0}\x{05C3}\x{05C6}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0608}\x{060B}\x{060D}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{066D}\x{066E}-\x{066F}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06E5}-\x{06E6}\x{06EE}-\x{06EF}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0712}-\x{072F}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07F4}-\x{07F5}\x{07FA}\x{07FB}-\x{07FC}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{081A}\x{0824}\x{0828}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{200F}\x{FB1D}\x{FB1F}-\x{FB28}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFE}-\x{FDFF}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A04}\x{10A07}-\x{10A0B}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A3B}-\x{10A3E}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D28}-\x{10D2F}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}]/u';
+ const BIDI_STEP_2 = '/[^\x{0000}-\x{0008}\x{000E}-\x{001B}\x{0021}-\x{0022}\x{0023}\x{0024}\x{0025}\x{0026}-\x{0027}\x{0028}\x{0029}\x{002A}\x{002B}\x{002C}\x{002D}\x{002E}-\x{002F}\x{0030}-\x{0039}\x{003A}\x{003B}\x{003C}-\x{003E}\x{003F}-\x{0040}\x{005B}\x{005C}\x{005D}\x{005E}\x{005F}\x{0060}\x{007B}\x{007C}\x{007D}\x{007E}\x{007F}-\x{0084}\x{0086}-\x{009F}\x{00A0}\x{00A1}\x{00A2}-\x{00A5}\x{00A6}\x{00A7}\x{00A8}\x{00A9}\x{00AB}\x{00AC}\x{00AD}\x{00AE}\x{00AF}\x{00B0}\x{00B1}\x{00B2}-\x{00B3}\x{00B4}\x{00B6}-\x{00B7}\x{00B8}\x{00B9}\x{00BB}\x{00BC}-\x{00BE}\x{00BF}\x{00D7}\x{00F7}\x{02B9}-\x{02BA}\x{02C2}-\x{02C5}\x{02C6}-\x{02CF}\x{02D2}-\x{02DF}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037E}\x{0384}-\x{0385}\x{0387}\x{03F6}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{058A}\x{058D}-\x{058E}\x{058F}\x{0590}\x{0591}-\x{05BD}\x{05BE}\x{05BF}\x{05C0}\x{05C1}-\x{05C2}\x{05C3}\x{05C4}-\x{05C5}\x{05C6}\x{05C7}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0606}-\x{0607}\x{0608}\x{0609}-\x{060A}\x{060B}\x{060C}\x{060D}\x{060E}-\x{060F}\x{0610}-\x{061A}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{064B}-\x{065F}\x{0660}-\x{0669}\x{066A}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0670}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06D6}-\x{06DC}\x{06DD}\x{06DE}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06E9}\x{06EA}-\x{06ED}\x{06EE}-\x{06EF}\x{06F0}-\x{06F9}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0711}\x{0712}-\x{072F}\x{0730}-\x{074A}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07A6}-\x{07B0}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07F6}\x{07F7}-\x{07F9}\x{07FA}\x{07FB}-\x{07FC}\x{07FD}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{0859}-\x{085B}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09F2}-\x{09F3}\x{09FB}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AF1}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0BF3}-\x{0BF8}\x{0BF9}\x{0BFA}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C78}-\x{0C7E}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E3F}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F3A}\x{0F3B}\x{0F3C}\x{0F3D}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1390}-\x{1399}\x{1400}\x{169B}\x{169C}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DB}\x{17DD}\x{17F0}-\x{17F9}\x{1800}-\x{1805}\x{1806}\x{1807}-\x{180A}\x{180B}-\x{180D}\x{180E}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1940}\x{1944}-\x{1945}\x{19DE}-\x{19FF}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200D}\x{200F}\x{2010}-\x{2015}\x{2016}-\x{2017}\x{2018}\x{2019}\x{201A}\x{201B}-\x{201C}\x{201D}\x{201E}\x{201F}\x{2020}-\x{2027}\x{202F}\x{2030}-\x{2034}\x{2035}-\x{2038}\x{2039}\x{203A}\x{203B}-\x{203E}\x{203F}-\x{2040}\x{2041}-\x{2043}\x{2044}\x{2045}\x{2046}\x{2047}-\x{2051}\x{2052}\x{2053}\x{2054}\x{2055}-\x{205E}\x{2060}-\x{2064}\x{2065}\x{206A}-\x{206F}\x{2070}\x{2074}-\x{2079}\x{207A}-\x{207B}\x{207C}\x{207D}\x{207E}\x{2080}-\x{2089}\x{208A}-\x{208B}\x{208C}\x{208D}\x{208E}\x{20A0}-\x{20BF}\x{20C0}-\x{20CF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}\x{2116}-\x{2117}\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}\x{214B}\x{214C}-\x{214D}\x{2150}-\x{215F}\x{2189}\x{218A}-\x{218B}\x{2190}-\x{2194}\x{2195}-\x{2199}\x{219A}-\x{219B}\x{219C}-\x{219F}\x{21A0}\x{21A1}-\x{21A2}\x{21A3}\x{21A4}-\x{21A5}\x{21A6}\x{21A7}-\x{21AD}\x{21AE}\x{21AF}-\x{21CD}\x{21CE}-\x{21CF}\x{21D0}-\x{21D1}\x{21D2}\x{21D3}\x{21D4}\x{21D5}-\x{21F3}\x{21F4}-\x{2211}\x{2212}\x{2213}\x{2214}-\x{22FF}\x{2300}-\x{2307}\x{2308}\x{2309}\x{230A}\x{230B}\x{230C}-\x{231F}\x{2320}-\x{2321}\x{2322}-\x{2328}\x{2329}\x{232A}\x{232B}-\x{2335}\x{237B}\x{237C}\x{237D}-\x{2394}\x{2396}-\x{239A}\x{239B}-\x{23B3}\x{23B4}-\x{23DB}\x{23DC}-\x{23E1}\x{23E2}-\x{2426}\x{2440}-\x{244A}\x{2460}-\x{2487}\x{2488}-\x{249B}\x{24EA}-\x{24FF}\x{2500}-\x{25B6}\x{25B7}\x{25B8}-\x{25C0}\x{25C1}\x{25C2}-\x{25F7}\x{25F8}-\x{25FF}\x{2600}-\x{266E}\x{266F}\x{2670}-\x{26AB}\x{26AD}-\x{2767}\x{2768}\x{2769}\x{276A}\x{276B}\x{276C}\x{276D}\x{276E}\x{276F}\x{2770}\x{2771}\x{2772}\x{2773}\x{2774}\x{2775}\x{2776}-\x{2793}\x{2794}-\x{27BF}\x{27C0}-\x{27C4}\x{27C5}\x{27C6}\x{27C7}-\x{27E5}\x{27E6}\x{27E7}\x{27E8}\x{27E9}\x{27EA}\x{27EB}\x{27EC}\x{27ED}\x{27EE}\x{27EF}\x{27F0}-\x{27FF}\x{2900}-\x{2982}\x{2983}\x{2984}\x{2985}\x{2986}\x{2987}\x{2988}\x{2989}\x{298A}\x{298B}\x{298C}\x{298D}\x{298E}\x{298F}\x{2990}\x{2991}\x{2992}\x{2993}\x{2994}\x{2995}\x{2996}\x{2997}\x{2998}\x{2999}-\x{29D7}\x{29D8}\x{29D9}\x{29DA}\x{29DB}\x{29DC}-\x{29FB}\x{29FC}\x{29FD}\x{29FE}-\x{2AFF}\x{2B00}-\x{2B2F}\x{2B30}-\x{2B44}\x{2B45}-\x{2B46}\x{2B47}-\x{2B4C}\x{2B4D}-\x{2B73}\x{2B76}-\x{2B95}\x{2B97}-\x{2BFF}\x{2CE5}-\x{2CEA}\x{2CEF}-\x{2CF1}\x{2CF9}-\x{2CFC}\x{2CFD}\x{2CFE}-\x{2CFF}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E00}-\x{2E01}\x{2E02}\x{2E03}\x{2E04}\x{2E05}\x{2E06}-\x{2E08}\x{2E09}\x{2E0A}\x{2E0B}\x{2E0C}\x{2E0D}\x{2E0E}-\x{2E16}\x{2E17}\x{2E18}-\x{2E19}\x{2E1A}\x{2E1B}\x{2E1C}\x{2E1D}\x{2E1E}-\x{2E1F}\x{2E20}\x{2E21}\x{2E22}\x{2E23}\x{2E24}\x{2E25}\x{2E26}\x{2E27}\x{2E28}\x{2E29}\x{2E2A}-\x{2E2E}\x{2E2F}\x{2E30}-\x{2E39}\x{2E3A}-\x{2E3B}\x{2E3C}-\x{2E3F}\x{2E40}\x{2E41}\x{2E42}\x{2E43}-\x{2E4F}\x{2E50}-\x{2E51}\x{2E52}\x{2E80}-\x{2E99}\x{2E9B}-\x{2EF3}\x{2F00}-\x{2FD5}\x{2FF0}-\x{2FFB}\x{3001}-\x{3003}\x{3004}\x{3008}\x{3009}\x{300A}\x{300B}\x{300C}\x{300D}\x{300E}\x{300F}\x{3010}\x{3011}\x{3012}-\x{3013}\x{3014}\x{3015}\x{3016}\x{3017}\x{3018}\x{3019}\x{301A}\x{301B}\x{301C}\x{301D}\x{301E}-\x{301F}\x{3020}\x{302A}-\x{302D}\x{3030}\x{3036}-\x{3037}\x{303D}\x{303E}-\x{303F}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{31C0}-\x{31E3}\x{321D}-\x{321E}\x{3250}\x{3251}-\x{325F}\x{327C}-\x{327E}\x{32B1}-\x{32BF}\x{32CC}-\x{32CF}\x{3377}-\x{337A}\x{33DE}-\x{33DF}\x{33FF}\x{4DC0}-\x{4DFF}\x{A490}-\x{A4C6}\x{A60D}-\x{A60F}\x{A66F}\x{A670}-\x{A672}\x{A673}\x{A674}-\x{A67D}\x{A67E}\x{A67F}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A788}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A828}-\x{A82B}\x{A82C}\x{A838}\x{A839}\x{A874}-\x{A877}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{AB6A}-\x{AB6B}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1D}\x{FB1E}\x{FB1F}-\x{FB28}\x{FB29}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD3E}\x{FD3F}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDD0}-\x{FDEF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFD}\x{FDFE}-\x{FDFF}\x{FE00}-\x{FE0F}\x{FE10}-\x{FE16}\x{FE17}\x{FE18}\x{FE19}\x{FE20}-\x{FE2F}\x{FE30}\x{FE31}-\x{FE32}\x{FE33}-\x{FE34}\x{FE35}\x{FE36}\x{FE37}\x{FE38}\x{FE39}\x{FE3A}\x{FE3B}\x{FE3C}\x{FE3D}\x{FE3E}\x{FE3F}\x{FE40}\x{FE41}\x{FE42}\x{FE43}\x{FE44}\x{FE45}-\x{FE46}\x{FE47}\x{FE48}\x{FE49}-\x{FE4C}\x{FE4D}-\x{FE4F}\x{FE50}\x{FE51}\x{FE52}\x{FE54}\x{FE55}\x{FE56}-\x{FE57}\x{FE58}\x{FE59}\x{FE5A}\x{FE5B}\x{FE5C}\x{FE5D}\x{FE5E}\x{FE5F}\x{FE60}-\x{FE61}\x{FE62}\x{FE63}\x{FE64}-\x{FE66}\x{FE68}\x{FE69}\x{FE6A}\x{FE6B}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{FEFF}\x{FF01}-\x{FF02}\x{FF03}\x{FF04}\x{FF05}\x{FF06}-\x{FF07}\x{FF08}\x{FF09}\x{FF0A}\x{FF0B}\x{FF0C}\x{FF0D}\x{FF0E}-\x{FF0F}\x{FF10}-\x{FF19}\x{FF1A}\x{FF1B}\x{FF1C}-\x{FF1E}\x{FF1F}-\x{FF20}\x{FF3B}\x{FF3C}\x{FF3D}\x{FF3E}\x{FF3F}\x{FF40}\x{FF5B}\x{FF5C}\x{FF5D}\x{FF5E}\x{FF5F}\x{FF60}\x{FF61}\x{FF62}\x{FF63}\x{FF64}-\x{FF65}\x{FFE0}-\x{FFE1}\x{FFE2}\x{FFE3}\x{FFE4}\x{FFE5}-\x{FFE6}\x{FFE8}\x{FFE9}-\x{FFEC}\x{FFED}-\x{FFEE}\x{FFF0}-\x{FFF8}\x{FFF9}-\x{FFFB}\x{FFFC}-\x{FFFD}\x{FFFE}-\x{FFFF}\x{10101}\x{10140}-\x{10174}\x{10175}-\x{10178}\x{10179}-\x{10189}\x{1018A}-\x{1018B}\x{1018C}\x{10190}-\x{1019C}\x{101A0}\x{101FD}\x{102E0}\x{102E1}-\x{102FB}\x{10376}-\x{1037A}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{1091F}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A01}-\x{10A03}\x{10A04}\x{10A05}-\x{10A06}\x{10A07}-\x{10A0B}\x{10A0C}-\x{10A0F}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A38}-\x{10A3A}\x{10A3B}-\x{10A3E}\x{10A3F}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE5}-\x{10AE6}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B39}-\x{10B3F}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D24}-\x{10D27}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAB}-\x{10EAC}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F46}-\x{10F50}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{11001}\x{11038}-\x{11046}\x{11052}-\x{11065}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{11660}-\x{1166C}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{11FD5}-\x{11FDC}\x{11FDD}-\x{11FE0}\x{11FE1}-\x{11FF1}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE2}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D200}-\x{1D241}\x{1D242}-\x{1D244}\x{1D245}\x{1D300}-\x{1D356}\x{1D6DB}\x{1D715}\x{1D74F}\x{1D789}\x{1D7C3}\x{1D7CE}-\x{1D7FF}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E2FF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D0}-\x{1E8D6}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E944}-\x{1E94A}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF0}-\x{1EEF1}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}\x{1F000}-\x{1F02B}\x{1F030}-\x{1F093}\x{1F0A0}-\x{1F0AE}\x{1F0B1}-\x{1F0BF}\x{1F0C1}-\x{1F0CF}\x{1F0D1}-\x{1F0F5}\x{1F100}-\x{1F10A}\x{1F10B}-\x{1F10C}\x{1F10D}-\x{1F10F}\x{1F12F}\x{1F16A}-\x{1F16F}\x{1F1AD}\x{1F260}-\x{1F265}\x{1F300}-\x{1F3FA}\x{1F3FB}-\x{1F3FF}\x{1F400}-\x{1F6D7}\x{1F6E0}-\x{1F6EC}\x{1F6F0}-\x{1F6FC}\x{1F700}-\x{1F773}\x{1F780}-\x{1F7D8}\x{1F7E0}-\x{1F7EB}\x{1F800}-\x{1F80B}\x{1F810}-\x{1F847}\x{1F850}-\x{1F859}\x{1F860}-\x{1F887}\x{1F890}-\x{1F8AD}\x{1F8B0}-\x{1F8B1}\x{1F900}-\x{1F978}\x{1F97A}-\x{1F9CB}\x{1F9CD}-\x{1FA53}\x{1FA60}-\x{1FA6D}\x{1FA70}-\x{1FA74}\x{1FA78}-\x{1FA7A}\x{1FA80}-\x{1FA86}\x{1FA90}-\x{1FAA8}\x{1FAB0}-\x{1FAB6}\x{1FAC0}-\x{1FAC2}\x{1FAD0}-\x{1FAD6}\x{1FB00}-\x{1FB92}\x{1FB94}-\x{1FBCA}\x{1FBF0}-\x{1FBF9}\x{1FFFE}-\x{1FFFF}\x{2FFFE}-\x{2FFFF}\x{3FFFE}-\x{3FFFF}\x{4FFFE}-\x{4FFFF}\x{5FFFE}-\x{5FFFF}\x{6FFFE}-\x{6FFFF}\x{7FFFE}-\x{7FFFF}\x{8FFFE}-\x{8FFFF}\x{9FFFE}-\x{9FFFF}\x{AFFFE}-\x{AFFFF}\x{BFFFE}-\x{BFFFF}\x{CFFFE}-\x{CFFFF}\x{DFFFE}-\x{E0000}\x{E0001}\x{E0002}-\x{E001F}\x{E0020}-\x{E007F}\x{E0080}-\x{E00FF}\x{E0100}-\x{E01EF}\x{E01F0}-\x{E0FFF}\x{EFFFE}-\x{EFFFF}\x{FFFFE}-\x{FFFFF}\x{10FFFE}-\x{10FFFF}]/u';
+ const BIDI_STEP_3 = '/[\x{0030}-\x{0039}\x{00B2}-\x{00B3}\x{00B9}\x{0590}\x{05BE}\x{05C0}\x{05C3}\x{05C6}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0608}\x{060B}\x{060D}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{0660}-\x{0669}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06DD}\x{06E5}-\x{06E6}\x{06EE}-\x{06EF}\x{06F0}-\x{06F9}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0712}-\x{072F}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07F4}-\x{07F5}\x{07FA}\x{07FB}-\x{07FC}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{081A}\x{0824}\x{0828}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{08E2}\x{200F}\x{2070}\x{2074}-\x{2079}\x{2080}-\x{2089}\x{2488}-\x{249B}\x{FB1D}\x{FB1F}-\x{FB28}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFE}-\x{FDFF}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{FF10}-\x{FF19}\x{102E1}-\x{102FB}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A04}\x{10A07}-\x{10A0B}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A3B}-\x{10A3E}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{1D7CE}-\x{1D7FF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}\x{1F100}-\x{1F10A}\x{1FBF0}-\x{1FBF9}][\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A82C}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1D167}-\x{1D169}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{E0100}-\x{E01EF}]*$/u';
+ const BIDI_STEP_4_AN = '/[\x{0600}-\x{0605}\x{0660}-\x{0669}\x{066B}-\x{066C}\x{06DD}\x{08E2}\x{10D30}-\x{10D39}\x{10E60}-\x{10E7E}]/u';
+ const BIDI_STEP_4_EN = '/[\x{0030}-\x{0039}\x{00B2}-\x{00B3}\x{00B9}\x{06F0}-\x{06F9}\x{2070}\x{2074}-\x{2079}\x{2080}-\x{2089}\x{2488}-\x{249B}\x{FF10}-\x{FF19}\x{102E1}-\x{102FB}\x{1D7CE}-\x{1D7FF}\x{1F100}-\x{1F10A}\x{1FBF0}-\x{1FBF9}]/u';
+ const BIDI_STEP_5 = '/[\x{0009}\x{000A}\x{000B}\x{000C}\x{000D}\x{001C}-\x{001E}\x{001F}\x{0020}\x{0085}\x{0590}\x{05BE}\x{05C0}\x{05C3}\x{05C6}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0608}\x{060B}\x{060D}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{0660}-\x{0669}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06DD}\x{06E5}-\x{06E6}\x{06EE}-\x{06EF}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0712}-\x{072F}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07F4}-\x{07F5}\x{07FA}\x{07FB}-\x{07FC}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{081A}\x{0824}\x{0828}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{08E2}\x{1680}\x{2000}-\x{200A}\x{200F}\x{2028}\x{2029}\x{202A}\x{202B}\x{202C}\x{202D}\x{202E}\x{205F}\x{2066}\x{2067}\x{2068}\x{2069}\x{3000}\x{FB1D}\x{FB1F}-\x{FB28}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFE}-\x{FDFF}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A04}\x{10A07}-\x{10A0B}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A3B}-\x{10A3E}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}]/u';
+ const BIDI_STEP_6 = '/[^\x{0000}-\x{0008}\x{0009}\x{000A}\x{000B}\x{000C}\x{000D}\x{000E}-\x{001B}\x{001C}-\x{001E}\x{001F}\x{0020}\x{0021}-\x{0022}\x{0023}\x{0024}\x{0025}\x{0026}-\x{0027}\x{0028}\x{0029}\x{002A}\x{002B}\x{002C}\x{002D}\x{002E}-\x{002F}\x{003A}\x{003B}\x{003C}-\x{003E}\x{003F}-\x{0040}\x{005B}\x{005C}\x{005D}\x{005E}\x{005F}\x{0060}\x{007B}\x{007C}\x{007D}\x{007E}\x{007F}-\x{0084}\x{0085}\x{0086}-\x{009F}\x{00A0}\x{00A1}\x{00A2}-\x{00A5}\x{00A6}\x{00A7}\x{00A8}\x{00A9}\x{00AB}\x{00AC}\x{00AD}\x{00AE}\x{00AF}\x{00B0}\x{00B1}\x{00B4}\x{00B6}-\x{00B7}\x{00B8}\x{00BB}\x{00BC}-\x{00BE}\x{00BF}\x{00D7}\x{00F7}\x{02B9}-\x{02BA}\x{02C2}-\x{02C5}\x{02C6}-\x{02CF}\x{02D2}-\x{02DF}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037E}\x{0384}-\x{0385}\x{0387}\x{03F6}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{058A}\x{058D}-\x{058E}\x{058F}\x{0590}\x{0591}-\x{05BD}\x{05BE}\x{05BF}\x{05C0}\x{05C1}-\x{05C2}\x{05C3}\x{05C4}-\x{05C5}\x{05C6}\x{05C7}\x{05C8}-\x{05CF}\x{05D0}-\x{05EA}\x{05EB}-\x{05EE}\x{05EF}-\x{05F2}\x{05F3}-\x{05F4}\x{05F5}-\x{05FF}\x{0600}-\x{0605}\x{0606}-\x{0607}\x{0608}\x{0609}-\x{060A}\x{060B}\x{060C}\x{060D}\x{060E}-\x{060F}\x{0610}-\x{061A}\x{061B}\x{061C}\x{061D}\x{061E}-\x{061F}\x{0620}-\x{063F}\x{0640}\x{0641}-\x{064A}\x{064B}-\x{065F}\x{0660}-\x{0669}\x{066A}\x{066B}-\x{066C}\x{066D}\x{066E}-\x{066F}\x{0670}\x{0671}-\x{06D3}\x{06D4}\x{06D5}\x{06D6}-\x{06DC}\x{06DD}\x{06DE}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06E9}\x{06EA}-\x{06ED}\x{06EE}-\x{06EF}\x{06FA}-\x{06FC}\x{06FD}-\x{06FE}\x{06FF}\x{0700}-\x{070D}\x{070E}\x{070F}\x{0710}\x{0711}\x{0712}-\x{072F}\x{0730}-\x{074A}\x{074B}-\x{074C}\x{074D}-\x{07A5}\x{07A6}-\x{07B0}\x{07B1}\x{07B2}-\x{07BF}\x{07C0}-\x{07C9}\x{07CA}-\x{07EA}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07F6}\x{07F7}-\x{07F9}\x{07FA}\x{07FB}-\x{07FC}\x{07FD}\x{07FE}-\x{07FF}\x{0800}-\x{0815}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{082E}-\x{082F}\x{0830}-\x{083E}\x{083F}\x{0840}-\x{0858}\x{0859}-\x{085B}\x{085C}-\x{085D}\x{085E}\x{085F}\x{0860}-\x{086A}\x{086B}-\x{086F}\x{0870}-\x{089F}\x{08A0}-\x{08B4}\x{08B5}\x{08B6}-\x{08C7}\x{08C8}-\x{08D2}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09F2}-\x{09F3}\x{09FB}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AF1}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0BF3}-\x{0BF8}\x{0BF9}\x{0BFA}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C78}-\x{0C7E}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E3F}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F3A}\x{0F3B}\x{0F3C}\x{0F3D}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1390}-\x{1399}\x{1400}\x{1680}\x{169B}\x{169C}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DB}\x{17DD}\x{17F0}-\x{17F9}\x{1800}-\x{1805}\x{1806}\x{1807}-\x{180A}\x{180B}-\x{180D}\x{180E}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1940}\x{1944}-\x{1945}\x{19DE}-\x{19FF}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{2000}-\x{200A}\x{200B}-\x{200D}\x{200F}\x{2010}-\x{2015}\x{2016}-\x{2017}\x{2018}\x{2019}\x{201A}\x{201B}-\x{201C}\x{201D}\x{201E}\x{201F}\x{2020}-\x{2027}\x{2028}\x{2029}\x{202A}\x{202B}\x{202C}\x{202D}\x{202E}\x{202F}\x{2030}-\x{2034}\x{2035}-\x{2038}\x{2039}\x{203A}\x{203B}-\x{203E}\x{203F}-\x{2040}\x{2041}-\x{2043}\x{2044}\x{2045}\x{2046}\x{2047}-\x{2051}\x{2052}\x{2053}\x{2054}\x{2055}-\x{205E}\x{205F}\x{2060}-\x{2064}\x{2065}\x{2066}\x{2067}\x{2068}\x{2069}\x{206A}-\x{206F}\x{207A}-\x{207B}\x{207C}\x{207D}\x{207E}\x{208A}-\x{208B}\x{208C}\x{208D}\x{208E}\x{20A0}-\x{20BF}\x{20C0}-\x{20CF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}\x{2116}-\x{2117}\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}\x{214B}\x{214C}-\x{214D}\x{2150}-\x{215F}\x{2189}\x{218A}-\x{218B}\x{2190}-\x{2194}\x{2195}-\x{2199}\x{219A}-\x{219B}\x{219C}-\x{219F}\x{21A0}\x{21A1}-\x{21A2}\x{21A3}\x{21A4}-\x{21A5}\x{21A6}\x{21A7}-\x{21AD}\x{21AE}\x{21AF}-\x{21CD}\x{21CE}-\x{21CF}\x{21D0}-\x{21D1}\x{21D2}\x{21D3}\x{21D4}\x{21D5}-\x{21F3}\x{21F4}-\x{2211}\x{2212}\x{2213}\x{2214}-\x{22FF}\x{2300}-\x{2307}\x{2308}\x{2309}\x{230A}\x{230B}\x{230C}-\x{231F}\x{2320}-\x{2321}\x{2322}-\x{2328}\x{2329}\x{232A}\x{232B}-\x{2335}\x{237B}\x{237C}\x{237D}-\x{2394}\x{2396}-\x{239A}\x{239B}-\x{23B3}\x{23B4}-\x{23DB}\x{23DC}-\x{23E1}\x{23E2}-\x{2426}\x{2440}-\x{244A}\x{2460}-\x{2487}\x{24EA}-\x{24FF}\x{2500}-\x{25B6}\x{25B7}\x{25B8}-\x{25C0}\x{25C1}\x{25C2}-\x{25F7}\x{25F8}-\x{25FF}\x{2600}-\x{266E}\x{266F}\x{2670}-\x{26AB}\x{26AD}-\x{2767}\x{2768}\x{2769}\x{276A}\x{276B}\x{276C}\x{276D}\x{276E}\x{276F}\x{2770}\x{2771}\x{2772}\x{2773}\x{2774}\x{2775}\x{2776}-\x{2793}\x{2794}-\x{27BF}\x{27C0}-\x{27C4}\x{27C5}\x{27C6}\x{27C7}-\x{27E5}\x{27E6}\x{27E7}\x{27E8}\x{27E9}\x{27EA}\x{27EB}\x{27EC}\x{27ED}\x{27EE}\x{27EF}\x{27F0}-\x{27FF}\x{2900}-\x{2982}\x{2983}\x{2984}\x{2985}\x{2986}\x{2987}\x{2988}\x{2989}\x{298A}\x{298B}\x{298C}\x{298D}\x{298E}\x{298F}\x{2990}\x{2991}\x{2992}\x{2993}\x{2994}\x{2995}\x{2996}\x{2997}\x{2998}\x{2999}-\x{29D7}\x{29D8}\x{29D9}\x{29DA}\x{29DB}\x{29DC}-\x{29FB}\x{29FC}\x{29FD}\x{29FE}-\x{2AFF}\x{2B00}-\x{2B2F}\x{2B30}-\x{2B44}\x{2B45}-\x{2B46}\x{2B47}-\x{2B4C}\x{2B4D}-\x{2B73}\x{2B76}-\x{2B95}\x{2B97}-\x{2BFF}\x{2CE5}-\x{2CEA}\x{2CEF}-\x{2CF1}\x{2CF9}-\x{2CFC}\x{2CFD}\x{2CFE}-\x{2CFF}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E00}-\x{2E01}\x{2E02}\x{2E03}\x{2E04}\x{2E05}\x{2E06}-\x{2E08}\x{2E09}\x{2E0A}\x{2E0B}\x{2E0C}\x{2E0D}\x{2E0E}-\x{2E16}\x{2E17}\x{2E18}-\x{2E19}\x{2E1A}\x{2E1B}\x{2E1C}\x{2E1D}\x{2E1E}-\x{2E1F}\x{2E20}\x{2E21}\x{2E22}\x{2E23}\x{2E24}\x{2E25}\x{2E26}\x{2E27}\x{2E28}\x{2E29}\x{2E2A}-\x{2E2E}\x{2E2F}\x{2E30}-\x{2E39}\x{2E3A}-\x{2E3B}\x{2E3C}-\x{2E3F}\x{2E40}\x{2E41}\x{2E42}\x{2E43}-\x{2E4F}\x{2E50}-\x{2E51}\x{2E52}\x{2E80}-\x{2E99}\x{2E9B}-\x{2EF3}\x{2F00}-\x{2FD5}\x{2FF0}-\x{2FFB}\x{3000}\x{3001}-\x{3003}\x{3004}\x{3008}\x{3009}\x{300A}\x{300B}\x{300C}\x{300D}\x{300E}\x{300F}\x{3010}\x{3011}\x{3012}-\x{3013}\x{3014}\x{3015}\x{3016}\x{3017}\x{3018}\x{3019}\x{301A}\x{301B}\x{301C}\x{301D}\x{301E}-\x{301F}\x{3020}\x{302A}-\x{302D}\x{3030}\x{3036}-\x{3037}\x{303D}\x{303E}-\x{303F}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{31C0}-\x{31E3}\x{321D}-\x{321E}\x{3250}\x{3251}-\x{325F}\x{327C}-\x{327E}\x{32B1}-\x{32BF}\x{32CC}-\x{32CF}\x{3377}-\x{337A}\x{33DE}-\x{33DF}\x{33FF}\x{4DC0}-\x{4DFF}\x{A490}-\x{A4C6}\x{A60D}-\x{A60F}\x{A66F}\x{A670}-\x{A672}\x{A673}\x{A674}-\x{A67D}\x{A67E}\x{A67F}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A788}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A828}-\x{A82B}\x{A82C}\x{A838}\x{A839}\x{A874}-\x{A877}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{AB6A}-\x{AB6B}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1D}\x{FB1E}\x{FB1F}-\x{FB28}\x{FB29}\x{FB2A}-\x{FB36}\x{FB37}\x{FB38}-\x{FB3C}\x{FB3D}\x{FB3E}\x{FB3F}\x{FB40}-\x{FB41}\x{FB42}\x{FB43}-\x{FB44}\x{FB45}\x{FB46}-\x{FB4F}\x{FB50}-\x{FBB1}\x{FBB2}-\x{FBC1}\x{FBC2}-\x{FBD2}\x{FBD3}-\x{FD3D}\x{FD3E}\x{FD3F}\x{FD40}-\x{FD4F}\x{FD50}-\x{FD8F}\x{FD90}-\x{FD91}\x{FD92}-\x{FDC7}\x{FDC8}-\x{FDCF}\x{FDD0}-\x{FDEF}\x{FDF0}-\x{FDFB}\x{FDFC}\x{FDFD}\x{FDFE}-\x{FDFF}\x{FE00}-\x{FE0F}\x{FE10}-\x{FE16}\x{FE17}\x{FE18}\x{FE19}\x{FE20}-\x{FE2F}\x{FE30}\x{FE31}-\x{FE32}\x{FE33}-\x{FE34}\x{FE35}\x{FE36}\x{FE37}\x{FE38}\x{FE39}\x{FE3A}\x{FE3B}\x{FE3C}\x{FE3D}\x{FE3E}\x{FE3F}\x{FE40}\x{FE41}\x{FE42}\x{FE43}\x{FE44}\x{FE45}-\x{FE46}\x{FE47}\x{FE48}\x{FE49}-\x{FE4C}\x{FE4D}-\x{FE4F}\x{FE50}\x{FE51}\x{FE52}\x{FE54}\x{FE55}\x{FE56}-\x{FE57}\x{FE58}\x{FE59}\x{FE5A}\x{FE5B}\x{FE5C}\x{FE5D}\x{FE5E}\x{FE5F}\x{FE60}-\x{FE61}\x{FE62}\x{FE63}\x{FE64}-\x{FE66}\x{FE68}\x{FE69}\x{FE6A}\x{FE6B}\x{FE70}-\x{FE74}\x{FE75}\x{FE76}-\x{FEFC}\x{FEFD}-\x{FEFE}\x{FEFF}\x{FF01}-\x{FF02}\x{FF03}\x{FF04}\x{FF05}\x{FF06}-\x{FF07}\x{FF08}\x{FF09}\x{FF0A}\x{FF0B}\x{FF0C}\x{FF0D}\x{FF0E}-\x{FF0F}\x{FF1A}\x{FF1B}\x{FF1C}-\x{FF1E}\x{FF1F}-\x{FF20}\x{FF3B}\x{FF3C}\x{FF3D}\x{FF3E}\x{FF3F}\x{FF40}\x{FF5B}\x{FF5C}\x{FF5D}\x{FF5E}\x{FF5F}\x{FF60}\x{FF61}\x{FF62}\x{FF63}\x{FF64}-\x{FF65}\x{FFE0}-\x{FFE1}\x{FFE2}\x{FFE3}\x{FFE4}\x{FFE5}-\x{FFE6}\x{FFE8}\x{FFE9}-\x{FFEC}\x{FFED}-\x{FFEE}\x{FFF0}-\x{FFF8}\x{FFF9}-\x{FFFB}\x{FFFC}-\x{FFFD}\x{FFFE}-\x{FFFF}\x{10101}\x{10140}-\x{10174}\x{10175}-\x{10178}\x{10179}-\x{10189}\x{1018A}-\x{1018B}\x{1018C}\x{10190}-\x{1019C}\x{101A0}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10800}-\x{10805}\x{10806}-\x{10807}\x{10808}\x{10809}\x{1080A}-\x{10835}\x{10836}\x{10837}-\x{10838}\x{10839}-\x{1083B}\x{1083C}\x{1083D}-\x{1083E}\x{1083F}-\x{10855}\x{10856}\x{10857}\x{10858}-\x{1085F}\x{10860}-\x{10876}\x{10877}-\x{10878}\x{10879}-\x{1087F}\x{10880}-\x{1089E}\x{1089F}-\x{108A6}\x{108A7}-\x{108AF}\x{108B0}-\x{108DF}\x{108E0}-\x{108F2}\x{108F3}\x{108F4}-\x{108F5}\x{108F6}-\x{108FA}\x{108FB}-\x{108FF}\x{10900}-\x{10915}\x{10916}-\x{1091B}\x{1091C}-\x{1091E}\x{1091F}\x{10920}-\x{10939}\x{1093A}-\x{1093E}\x{1093F}\x{10940}-\x{1097F}\x{10980}-\x{109B7}\x{109B8}-\x{109BB}\x{109BC}-\x{109BD}\x{109BE}-\x{109BF}\x{109C0}-\x{109CF}\x{109D0}-\x{109D1}\x{109D2}-\x{109FF}\x{10A00}\x{10A01}-\x{10A03}\x{10A04}\x{10A05}-\x{10A06}\x{10A07}-\x{10A0B}\x{10A0C}-\x{10A0F}\x{10A10}-\x{10A13}\x{10A14}\x{10A15}-\x{10A17}\x{10A18}\x{10A19}-\x{10A35}\x{10A36}-\x{10A37}\x{10A38}-\x{10A3A}\x{10A3B}-\x{10A3E}\x{10A3F}\x{10A40}-\x{10A48}\x{10A49}-\x{10A4F}\x{10A50}-\x{10A58}\x{10A59}-\x{10A5F}\x{10A60}-\x{10A7C}\x{10A7D}-\x{10A7E}\x{10A7F}\x{10A80}-\x{10A9C}\x{10A9D}-\x{10A9F}\x{10AA0}-\x{10ABF}\x{10AC0}-\x{10AC7}\x{10AC8}\x{10AC9}-\x{10AE4}\x{10AE5}-\x{10AE6}\x{10AE7}-\x{10AEA}\x{10AEB}-\x{10AEF}\x{10AF0}-\x{10AF6}\x{10AF7}-\x{10AFF}\x{10B00}-\x{10B35}\x{10B36}-\x{10B38}\x{10B39}-\x{10B3F}\x{10B40}-\x{10B55}\x{10B56}-\x{10B57}\x{10B58}-\x{10B5F}\x{10B60}-\x{10B72}\x{10B73}-\x{10B77}\x{10B78}-\x{10B7F}\x{10B80}-\x{10B91}\x{10B92}-\x{10B98}\x{10B99}-\x{10B9C}\x{10B9D}-\x{10BA8}\x{10BA9}-\x{10BAF}\x{10BB0}-\x{10BFF}\x{10C00}-\x{10C48}\x{10C49}-\x{10C7F}\x{10C80}-\x{10CB2}\x{10CB3}-\x{10CBF}\x{10CC0}-\x{10CF2}\x{10CF3}-\x{10CF9}\x{10CFA}-\x{10CFF}\x{10D00}-\x{10D23}\x{10D24}-\x{10D27}\x{10D28}-\x{10D2F}\x{10D30}-\x{10D39}\x{10D3A}-\x{10D3F}\x{10D40}-\x{10E5F}\x{10E60}-\x{10E7E}\x{10E7F}\x{10E80}-\x{10EA9}\x{10EAA}\x{10EAB}-\x{10EAC}\x{10EAD}\x{10EAE}-\x{10EAF}\x{10EB0}-\x{10EB1}\x{10EB2}-\x{10EFF}\x{10F00}-\x{10F1C}\x{10F1D}-\x{10F26}\x{10F27}\x{10F28}-\x{10F2F}\x{10F30}-\x{10F45}\x{10F46}-\x{10F50}\x{10F51}-\x{10F54}\x{10F55}-\x{10F59}\x{10F5A}-\x{10F6F}\x{10F70}-\x{10FAF}\x{10FB0}-\x{10FC4}\x{10FC5}-\x{10FCB}\x{10FCC}-\x{10FDF}\x{10FE0}-\x{10FF6}\x{10FF7}-\x{10FFF}\x{11001}\x{11038}-\x{11046}\x{11052}-\x{11065}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{11660}-\x{1166C}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{11FD5}-\x{11FDC}\x{11FDD}-\x{11FE0}\x{11FE1}-\x{11FF1}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE2}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D200}-\x{1D241}\x{1D242}-\x{1D244}\x{1D245}\x{1D300}-\x{1D356}\x{1D6DB}\x{1D715}\x{1D74F}\x{1D789}\x{1D7C3}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E2FF}\x{1E800}-\x{1E8C4}\x{1E8C5}-\x{1E8C6}\x{1E8C7}-\x{1E8CF}\x{1E8D0}-\x{1E8D6}\x{1E8D7}-\x{1E8FF}\x{1E900}-\x{1E943}\x{1E944}-\x{1E94A}\x{1E94B}\x{1E94C}-\x{1E94F}\x{1E950}-\x{1E959}\x{1E95A}-\x{1E95D}\x{1E95E}-\x{1E95F}\x{1E960}-\x{1EC6F}\x{1EC70}\x{1EC71}-\x{1ECAB}\x{1ECAC}\x{1ECAD}-\x{1ECAF}\x{1ECB0}\x{1ECB1}-\x{1ECB4}\x{1ECB5}-\x{1ECBF}\x{1ECC0}-\x{1ECFF}\x{1ED00}\x{1ED01}-\x{1ED2D}\x{1ED2E}\x{1ED2F}-\x{1ED3D}\x{1ED3E}-\x{1ED4F}\x{1ED50}-\x{1EDFF}\x{1EE00}-\x{1EE03}\x{1EE04}\x{1EE05}-\x{1EE1F}\x{1EE20}\x{1EE21}-\x{1EE22}\x{1EE23}\x{1EE24}\x{1EE25}-\x{1EE26}\x{1EE27}\x{1EE28}\x{1EE29}-\x{1EE32}\x{1EE33}\x{1EE34}-\x{1EE37}\x{1EE38}\x{1EE39}\x{1EE3A}\x{1EE3B}\x{1EE3C}-\x{1EE41}\x{1EE42}\x{1EE43}-\x{1EE46}\x{1EE47}\x{1EE48}\x{1EE49}\x{1EE4A}\x{1EE4B}\x{1EE4C}\x{1EE4D}-\x{1EE4F}\x{1EE50}\x{1EE51}-\x{1EE52}\x{1EE53}\x{1EE54}\x{1EE55}-\x{1EE56}\x{1EE57}\x{1EE58}\x{1EE59}\x{1EE5A}\x{1EE5B}\x{1EE5C}\x{1EE5D}\x{1EE5E}\x{1EE5F}\x{1EE60}\x{1EE61}-\x{1EE62}\x{1EE63}\x{1EE64}\x{1EE65}-\x{1EE66}\x{1EE67}-\x{1EE6A}\x{1EE6B}\x{1EE6C}-\x{1EE72}\x{1EE73}\x{1EE74}-\x{1EE77}\x{1EE78}\x{1EE79}-\x{1EE7C}\x{1EE7D}\x{1EE7E}\x{1EE7F}\x{1EE80}-\x{1EE89}\x{1EE8A}\x{1EE8B}-\x{1EE9B}\x{1EE9C}-\x{1EEA0}\x{1EEA1}-\x{1EEA3}\x{1EEA4}\x{1EEA5}-\x{1EEA9}\x{1EEAA}\x{1EEAB}-\x{1EEBB}\x{1EEBC}-\x{1EEEF}\x{1EEF0}-\x{1EEF1}\x{1EEF2}-\x{1EEFF}\x{1EF00}-\x{1EFFF}\x{1F000}-\x{1F02B}\x{1F030}-\x{1F093}\x{1F0A0}-\x{1F0AE}\x{1F0B1}-\x{1F0BF}\x{1F0C1}-\x{1F0CF}\x{1F0D1}-\x{1F0F5}\x{1F10B}-\x{1F10C}\x{1F10D}-\x{1F10F}\x{1F12F}\x{1F16A}-\x{1F16F}\x{1F1AD}\x{1F260}-\x{1F265}\x{1F300}-\x{1F3FA}\x{1F3FB}-\x{1F3FF}\x{1F400}-\x{1F6D7}\x{1F6E0}-\x{1F6EC}\x{1F6F0}-\x{1F6FC}\x{1F700}-\x{1F773}\x{1F780}-\x{1F7D8}\x{1F7E0}-\x{1F7EB}\x{1F800}-\x{1F80B}\x{1F810}-\x{1F847}\x{1F850}-\x{1F859}\x{1F860}-\x{1F887}\x{1F890}-\x{1F8AD}\x{1F8B0}-\x{1F8B1}\x{1F900}-\x{1F978}\x{1F97A}-\x{1F9CB}\x{1F9CD}-\x{1FA53}\x{1FA60}-\x{1FA6D}\x{1FA70}-\x{1FA74}\x{1FA78}-\x{1FA7A}\x{1FA80}-\x{1FA86}\x{1FA90}-\x{1FAA8}\x{1FAB0}-\x{1FAB6}\x{1FAC0}-\x{1FAC2}\x{1FAD0}-\x{1FAD6}\x{1FB00}-\x{1FB92}\x{1FB94}-\x{1FBCA}\x{1FFFE}-\x{1FFFF}\x{2FFFE}-\x{2FFFF}\x{3FFFE}-\x{3FFFF}\x{4FFFE}-\x{4FFFF}\x{5FFFE}-\x{5FFFF}\x{6FFFE}-\x{6FFFF}\x{7FFFE}-\x{7FFFF}\x{8FFFE}-\x{8FFFF}\x{9FFFE}-\x{9FFFF}\x{AFFFE}-\x{AFFFF}\x{BFFFE}-\x{BFFFF}\x{CFFFE}-\x{CFFFF}\x{DFFFE}-\x{E0000}\x{E0001}\x{E0002}-\x{E001F}\x{E0020}-\x{E007F}\x{E0080}-\x{E00FF}\x{E0100}-\x{E01EF}\x{E01F0}-\x{E0FFF}\x{EFFFE}-\x{EFFFF}\x{FFFFE}-\x{FFFFF}\x{10FFFE}-\x{10FFFF}][\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A82C}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A06}\x{11A09}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1D167}-\x{1D169}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{E0100}-\x{E01EF}]*$/u';
+
+ const ZWNJ = '/([\x{A872}\x{10ACD}\x{10AD7}\x{10D00}\x{10FCB}\x{0620}\x{0626}\x{0628}\x{062A}-\x{062E}\x{0633}-\x{063F}\x{0641}-\x{0647}\x{0649}-\x{064A}\x{066E}-\x{066F}\x{0678}-\x{0687}\x{069A}-\x{06BF}\x{06C1}-\x{06C2}\x{06CC}\x{06CE}\x{06D0}-\x{06D1}\x{06FA}-\x{06FC}\x{06FF}\x{0712}-\x{0714}\x{071A}-\x{071D}\x{071F}-\x{0727}\x{0729}\x{072B}\x{072D}-\x{072E}\x{074E}-\x{0758}\x{075C}-\x{076A}\x{076D}-\x{0770}\x{0772}\x{0775}-\x{0777}\x{077A}-\x{077F}\x{07CA}-\x{07EA}\x{0841}-\x{0845}\x{0848}\x{084A}-\x{0853}\x{0855}\x{0860}\x{0862}-\x{0865}\x{0868}\x{08A0}-\x{08A9}\x{08AF}-\x{08B0}\x{08B3}-\x{08B4}\x{08B6}-\x{08B8}\x{08BA}-\x{08C7}\x{1807}\x{1820}-\x{1842}\x{1843}\x{1844}-\x{1878}\x{1887}-\x{18A8}\x{18AA}\x{A840}-\x{A871}\x{10AC0}-\x{10AC4}\x{10AD3}-\x{10AD6}\x{10AD8}-\x{10ADC}\x{10ADE}-\x{10AE0}\x{10AEB}-\x{10AEE}\x{10B80}\x{10B82}\x{10B86}-\x{10B88}\x{10B8A}-\x{10B8B}\x{10B8D}\x{10B90}\x{10BAD}-\x{10BAE}\x{10D01}-\x{10D21}\x{10D23}\x{10F30}-\x{10F32}\x{10F34}-\x{10F44}\x{10F51}-\x{10F53}\x{10FB0}\x{10FB2}-\x{10FB3}\x{10FB8}\x{10FBB}-\x{10FBC}\x{10FBE}-\x{10FBF}\x{10FC1}\x{10FC4}\x{10FCA}\x{1E900}-\x{1E943}][\x{00AD}\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{061C}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{200B}\x{200E}-\x{200F}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{206A}-\x{206F}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A82C}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{FEFF}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{13430}-\x{13438}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1E94B}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}]*\x{200C}[\x{00AD}\x{0300}-\x{036F}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{0610}-\x{061A}\x{061C}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DF}-\x{06E4}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07FD}\x{0816}-\x{0819}\x{081B}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B55}-\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0D81}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17DD}\x{180B}-\x{180D}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1ABF}-\x{1AC0}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{200B}\x{200E}-\x{200F}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{206A}-\x{206F}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2CEF}-\x{2CF1}\x{2D7F}\x{2DE0}-\x{2DFF}\x{302A}-\x{302D}\x{3099}-\x{309A}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A82C}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}-\x{A9BD}\x{A9E5}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AAEC}-\x{AAED}\x{AAF6}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FE00}-\x{FE0F}\x{FE20}-\x{FE2F}\x{FEFF}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10EAB}-\x{10EAC}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{111CF}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{1193B}-\x{1193C}\x{1193E}\x{11943}\x{119D4}-\x{119D7}\x{119DA}-\x{119DB}\x{119E0}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{13430}-\x{13438}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16F4F}\x{16F8F}-\x{16F92}\x{16FE4}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E130}-\x{1E136}\x{1E2EC}-\x{1E2EF}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1E94B}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}]*)[\x{0622}-\x{0625}\x{0627}\x{0629}\x{062F}-\x{0632}\x{0648}\x{0671}-\x{0673}\x{0675}-\x{0677}\x{0688}-\x{0699}\x{06C0}\x{06C3}-\x{06CB}\x{06CD}\x{06CF}\x{06D2}-\x{06D3}\x{06D5}\x{06EE}-\x{06EF}\x{0710}\x{0715}-\x{0719}\x{071E}\x{0728}\x{072A}\x{072C}\x{072F}\x{074D}\x{0759}-\x{075B}\x{076B}-\x{076C}\x{0771}\x{0773}-\x{0774}\x{0778}-\x{0779}\x{0840}\x{0846}-\x{0847}\x{0849}\x{0854}\x{0856}-\x{0858}\x{0867}\x{0869}-\x{086A}\x{08AA}-\x{08AC}\x{08AE}\x{08B1}-\x{08B2}\x{08B9}\x{10AC5}\x{10AC7}\x{10AC9}-\x{10ACA}\x{10ACE}-\x{10AD2}\x{10ADD}\x{10AE1}\x{10AE4}\x{10AEF}\x{10B81}\x{10B83}-\x{10B85}\x{10B89}\x{10B8C}\x{10B8E}-\x{10B8F}\x{10B91}\x{10BA9}-\x{10BAC}\x{10D22}\x{10F33}\x{10F54}\x{10FB4}-\x{10FB6}\x{10FB9}-\x{10FBA}\x{10FBD}\x{10FC2}-\x{10FC3}\x{10FC9}\x{0620}\x{0626}\x{0628}\x{062A}-\x{062E}\x{0633}-\x{063F}\x{0641}-\x{0647}\x{0649}-\x{064A}\x{066E}-\x{066F}\x{0678}-\x{0687}\x{069A}-\x{06BF}\x{06C1}-\x{06C2}\x{06CC}\x{06CE}\x{06D0}-\x{06D1}\x{06FA}-\x{06FC}\x{06FF}\x{0712}-\x{0714}\x{071A}-\x{071D}\x{071F}-\x{0727}\x{0729}\x{072B}\x{072D}-\x{072E}\x{074E}-\x{0758}\x{075C}-\x{076A}\x{076D}-\x{0770}\x{0772}\x{0775}-\x{0777}\x{077A}-\x{077F}\x{07CA}-\x{07EA}\x{0841}-\x{0845}\x{0848}\x{084A}-\x{0853}\x{0855}\x{0860}\x{0862}-\x{0865}\x{0868}\x{08A0}-\x{08A9}\x{08AF}-\x{08B0}\x{08B3}-\x{08B4}\x{08B6}-\x{08B8}\x{08BA}-\x{08C7}\x{1807}\x{1820}-\x{1842}\x{1843}\x{1844}-\x{1878}\x{1887}-\x{18A8}\x{18AA}\x{A840}-\x{A871}\x{10AC0}-\x{10AC4}\x{10AD3}-\x{10AD6}\x{10AD8}-\x{10ADC}\x{10ADE}-\x{10AE0}\x{10AEB}-\x{10AEE}\x{10B80}\x{10B82}\x{10B86}-\x{10B88}\x{10B8A}-\x{10B8B}\x{10B8D}\x{10B90}\x{10BAD}-\x{10BAE}\x{10D01}-\x{10D21}\x{10D23}\x{10F30}-\x{10F32}\x{10F34}-\x{10F44}\x{10F51}-\x{10F53}\x{10FB0}\x{10FB2}-\x{10FB3}\x{10FB8}\x{10FBB}-\x{10FBC}\x{10FBE}-\x{10FBF}\x{10FC1}\x{10FC4}\x{10FCA}\x{1E900}-\x{1E943}]/u';
+}
diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/deviation.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/deviation.php
new file mode 100755
index 0000000..0bbd335
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/deviation.php
@@ -0,0 +1,8 @@
+<?php
+
+return array (
+ 223 => 'ss',
+ 962 => 'σ',
+ 8204 => '',
+ 8205 => '',
+);
diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php
new file mode 100755
index 0000000..25a5f56
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php
@@ -0,0 +1,2638 @@
+<?php
+
+return array (
+ 888 => true,
+ 889 => true,
+ 896 => true,
+ 897 => true,
+ 898 => true,
+ 899 => true,
+ 907 => true,
+ 909 => true,
+ 930 => true,
+ 1216 => true,
+ 1328 => true,
+ 1367 => true,
+ 1368 => true,
+ 1419 => true,
+ 1420 => true,
+ 1424 => true,
+ 1480 => true,
+ 1481 => true,
+ 1482 => true,
+ 1483 => true,
+ 1484 => true,
+ 1485 => true,
+ 1486 => true,
+ 1487 => true,
+ 1515 => true,
+ 1516 => true,
+ 1517 => true,
+ 1518 => true,
+ 1525 => true,
+ 1526 => true,
+ 1527 => true,
+ 1528 => true,
+ 1529 => true,
+ 1530 => true,
+ 1531 => true,
+ 1532 => true,
+ 1533 => true,
+ 1534 => true,
+ 1535 => true,
+ 1536 => true,
+ 1537 => true,
+ 1538 => true,
+ 1539 => true,
+ 1540 => true,
+ 1541 => true,
+ 1564 => true,
+ 1565 => true,
+ 1757 => true,
+ 1806 => true,
+ 1807 => true,
+ 1867 => true,
+ 1868 => true,
+ 1970 => true,
+ 1971 => true,
+ 1972 => true,
+ 1973 => true,
+ 1974 => true,
+ 1975 => true,
+ 1976 => true,
+ 1977 => true,
+ 1978 => true,
+ 1979 => true,
+ 1980 => true,
+ 1981 => true,
+ 1982 => true,
+ 1983 => true,
+ 2043 => true,
+ 2044 => true,
+ 2094 => true,
+ 2095 => true,
+ 2111 => true,
+ 2140 => true,
+ 2141 => true,
+ 2143 => true,
+ 2229 => true,
+ 2248 => true,
+ 2249 => true,
+ 2250 => true,
+ 2251 => true,
+ 2252 => true,
+ 2253 => true,
+ 2254 => true,
+ 2255 => true,
+ 2256 => true,
+ 2257 => true,
+ 2258 => true,
+ 2274 => true,
+ 2436 => true,
+ 2445 => true,
+ 2446 => true,
+ 2449 => true,
+ 2450 => true,
+ 2473 => true,
+ 2481 => true,
+ 2483 => true,
+ 2484 => true,
+ 2485 => true,
+ 2490 => true,
+ 2491 => true,
+ 2501 => true,
+ 2502 => true,
+ 2505 => true,
+ 2506 => true,
+ 2511 => true,
+ 2512 => true,
+ 2513 => true,
+ 2514 => true,
+ 2515 => true,
+ 2516 => true,
+ 2517 => true,
+ 2518 => true,
+ 2520 => true,
+ 2521 => true,
+ 2522 => true,
+ 2523 => true,
+ 2526 => true,
+ 2532 => true,
+ 2533 => true,
+ 2559 => true,
+ 2560 => true,
+ 2564 => true,
+ 2571 => true,
+ 2572 => true,
+ 2573 => true,
+ 2574 => true,
+ 2577 => true,
+ 2578 => true,
+ 2601 => true,
+ 2609 => true,
+ 2612 => true,
+ 2615 => true,
+ 2618 => true,
+ 2619 => true,
+ 2621 => true,
+ 2627 => true,
+ 2628 => true,
+ 2629 => true,
+ 2630 => true,
+ 2633 => true,
+ 2634 => true,
+ 2638 => true,
+ 2639 => true,
+ 2640 => true,
+ 2642 => true,
+ 2643 => true,
+ 2644 => true,
+ 2645 => true,
+ 2646 => true,
+ 2647 => true,
+ 2648 => true,
+ 2653 => true,
+ 2655 => true,
+ 2656 => true,
+ 2657 => true,
+ 2658 => true,
+ 2659 => true,
+ 2660 => true,
+ 2661 => true,
+ 2679 => true,
+ 2680 => true,
+ 2681 => true,
+ 2682 => true,
+ 2683 => true,
+ 2684 => true,
+ 2685 => true,
+ 2686 => true,
+ 2687 => true,
+ 2688 => true,
+ 2692 => true,
+ 2702 => true,
+ 2706 => true,
+ 2729 => true,
+ 2737 => true,
+ 2740 => true,
+ 2746 => true,
+ 2747 => true,
+ 2758 => true,
+ 2762 => true,
+ 2766 => true,
+ 2767 => true,
+ 2769 => true,
+ 2770 => true,
+ 2771 => true,
+ 2772 => true,
+ 2773 => true,
+ 2774 => true,
+ 2775 => true,
+ 2776 => true,
+ 2777 => true,
+ 2778 => true,
+ 2779 => true,
+ 2780 => true,
+ 2781 => true,
+ 2782 => true,
+ 2783 => true,
+ 2788 => true,
+ 2789 => true,
+ 2802 => true,
+ 2803 => true,
+ 2804 => true,
+ 2805 => true,
+ 2806 => true,
+ 2807 => true,
+ 2808 => true,
+ 2816 => true,
+ 2820 => true,
+ 2829 => true,
+ 2830 => true,
+ 2833 => true,
+ 2834 => true,
+ 2857 => true,
+ 2865 => true,
+ 2868 => true,
+ 2874 => true,
+ 2875 => true,
+ 2885 => true,
+ 2886 => true,
+ 2889 => true,
+ 2890 => true,
+ 2894 => true,
+ 2895 => true,
+ 2896 => true,
+ 2897 => true,
+ 2898 => true,
+ 2899 => true,
+ 2900 => true,
+ 2904 => true,
+ 2905 => true,
+ 2906 => true,
+ 2907 => true,
+ 2910 => true,
+ 2916 => true,
+ 2917 => true,
+ 2936 => true,
+ 2937 => true,
+ 2938 => true,
+ 2939 => true,
+ 2940 => true,
+ 2941 => true,
+ 2942 => true,
+ 2943 => true,
+ 2944 => true,
+ 2945 => true,
+ 2948 => true,
+ 2955 => true,
+ 2956 => true,
+ 2957 => true,
+ 2961 => true,
+ 2966 => true,
+ 2967 => true,
+ 2968 => true,
+ 2971 => true,
+ 2973 => true,
+ 2976 => true,
+ 2977 => true,
+ 2978 => true,
+ 2981 => true,
+ 2982 => true,
+ 2983 => true,
+ 2987 => true,
+ 2988 => true,
+ 2989 => true,
+ 3002 => true,
+ 3003 => true,
+ 3004 => true,
+ 3005 => true,
+ 3011 => true,
+ 3012 => true,
+ 3013 => true,
+ 3017 => true,
+ 3022 => true,
+ 3023 => true,
+ 3025 => true,
+ 3026 => true,
+ 3027 => true,
+ 3028 => true,
+ 3029 => true,
+ 3030 => true,
+ 3032 => true,
+ 3033 => true,
+ 3034 => true,
+ 3035 => true,
+ 3036 => true,
+ 3037 => true,
+ 3038 => true,
+ 3039 => true,
+ 3040 => true,
+ 3041 => true,
+ 3042 => true,
+ 3043 => true,
+ 3044 => true,
+ 3045 => true,
+ 3067 => true,
+ 3068 => true,
+ 3069 => true,
+ 3070 => true,
+ 3071 => true,
+ 3085 => true,
+ 3089 => true,
+ 3113 => true,
+ 3130 => true,
+ 3131 => true,
+ 3132 => true,
+ 3141 => true,
+ 3145 => true,
+ 3150 => true,
+ 3151 => true,
+ 3152 => true,
+ 3153 => true,
+ 3154 => true,
+ 3155 => true,
+ 3156 => true,
+ 3159 => true,
+ 3163 => true,
+ 3164 => true,
+ 3165 => true,
+ 3166 => true,
+ 3167 => true,
+ 3172 => true,
+ 3173 => true,
+ 3184 => true,
+ 3185 => true,
+ 3186 => true,
+ 3187 => true,
+ 3188 => true,
+ 3189 => true,
+ 3190 => true,
+ 3213 => true,
+ 3217 => true,
+ 3241 => true,
+ 3252 => true,
+ 3258 => true,
+ 3259 => true,
+ 3269 => true,
+ 3273 => true,
+ 3278 => true,
+ 3279 => true,
+ 3280 => true,
+ 3281 => true,
+ 3282 => true,
+ 3283 => true,
+ 3284 => true,
+ 3287 => true,
+ 3288 => true,
+ 3289 => true,
+ 3290 => true,
+ 3291 => true,
+ 3292 => true,
+ 3293 => true,
+ 3295 => true,
+ 3300 => true,
+ 3301 => true,
+ 3312 => true,
+ 3315 => true,
+ 3316 => true,
+ 3317 => true,
+ 3318 => true,
+ 3319 => true,
+ 3320 => true,
+ 3321 => true,
+ 3322 => true,
+ 3323 => true,
+ 3324 => true,
+ 3325 => true,
+ 3326 => true,
+ 3327 => true,
+ 3341 => true,
+ 3345 => true,
+ 3397 => true,
+ 3401 => true,
+ 3408 => true,
+ 3409 => true,
+ 3410 => true,
+ 3411 => true,
+ 3428 => true,
+ 3429 => true,
+ 3456 => true,
+ 3460 => true,
+ 3479 => true,
+ 3480 => true,
+ 3481 => true,
+ 3506 => true,
+ 3516 => true,
+ 3518 => true,
+ 3519 => true,
+ 3527 => true,
+ 3528 => true,
+ 3529 => true,
+ 3531 => true,
+ 3532 => true,
+ 3533 => true,
+ 3534 => true,
+ 3541 => true,
+ 3543 => true,
+ 3552 => true,
+ 3553 => true,
+ 3554 => true,
+ 3555 => true,
+ 3556 => true,
+ 3557 => true,
+ 3568 => true,
+ 3569 => true,
+ 3573 => true,
+ 3574 => true,
+ 3575 => true,
+ 3576 => true,
+ 3577 => true,
+ 3578 => true,
+ 3579 => true,
+ 3580 => true,
+ 3581 => true,
+ 3582 => true,
+ 3583 => true,
+ 3584 => true,
+ 3643 => true,
+ 3644 => true,
+ 3645 => true,
+ 3646 => true,
+ 3715 => true,
+ 3717 => true,
+ 3723 => true,
+ 3748 => true,
+ 3750 => true,
+ 3774 => true,
+ 3775 => true,
+ 3781 => true,
+ 3783 => true,
+ 3790 => true,
+ 3791 => true,
+ 3802 => true,
+ 3803 => true,
+ 3912 => true,
+ 3949 => true,
+ 3950 => true,
+ 3951 => true,
+ 3952 => true,
+ 3992 => true,
+ 4029 => true,
+ 4045 => true,
+ 4294 => true,
+ 4296 => true,
+ 4297 => true,
+ 4298 => true,
+ 4299 => true,
+ 4300 => true,
+ 4302 => true,
+ 4303 => true,
+ 4447 => true,
+ 4448 => true,
+ 4681 => true,
+ 4686 => true,
+ 4687 => true,
+ 4695 => true,
+ 4697 => true,
+ 4702 => true,
+ 4703 => true,
+ 4745 => true,
+ 4750 => true,
+ 4751 => true,
+ 4785 => true,
+ 4790 => true,
+ 4791 => true,
+ 4799 => true,
+ 4801 => true,
+ 4806 => true,
+ 4807 => true,
+ 4823 => true,
+ 4881 => true,
+ 4886 => true,
+ 4887 => true,
+ 4955 => true,
+ 4956 => true,
+ 4989 => true,
+ 4990 => true,
+ 4991 => true,
+ 5018 => true,
+ 5019 => true,
+ 5020 => true,
+ 5021 => true,
+ 5022 => true,
+ 5023 => true,
+ 5110 => true,
+ 5111 => true,
+ 5118 => true,
+ 5119 => true,
+ 5760 => true,
+ 5789 => true,
+ 5790 => true,
+ 5791 => true,
+ 5881 => true,
+ 5882 => true,
+ 5883 => true,
+ 5884 => true,
+ 5885 => true,
+ 5886 => true,
+ 5887 => true,
+ 5901 => true,
+ 5909 => true,
+ 5910 => true,
+ 5911 => true,
+ 5912 => true,
+ 5913 => true,
+ 5914 => true,
+ 5915 => true,
+ 5916 => true,
+ 5917 => true,
+ 5918 => true,
+ 5919 => true,
+ 5943 => true,
+ 5944 => true,
+ 5945 => true,
+ 5946 => true,
+ 5947 => true,
+ 5948 => true,
+ 5949 => true,
+ 5950 => true,
+ 5951 => true,
+ 5972 => true,
+ 5973 => true,
+ 5974 => true,
+ 5975 => true,
+ 5976 => true,
+ 5977 => true,
+ 5978 => true,
+ 5979 => true,
+ 5980 => true,
+ 5981 => true,
+ 5982 => true,
+ 5983 => true,
+ 5997 => true,
+ 6001 => true,
+ 6004 => true,
+ 6005 => true,
+ 6006 => true,
+ 6007 => true,
+ 6008 => true,
+ 6009 => true,
+ 6010 => true,
+ 6011 => true,
+ 6012 => true,
+ 6013 => true,
+ 6014 => true,
+ 6015 => true,
+ 6068 => true,
+ 6069 => true,
+ 6110 => true,
+ 6111 => true,
+ 6122 => true,
+ 6123 => true,
+ 6124 => true,
+ 6125 => true,
+ 6126 => true,
+ 6127 => true,
+ 6138 => true,
+ 6139 => true,
+ 6140 => true,
+ 6141 => true,
+ 6142 => true,
+ 6143 => true,
+ 6150 => true,
+ 6158 => true,
+ 6159 => true,
+ 6170 => true,
+ 6171 => true,
+ 6172 => true,
+ 6173 => true,
+ 6174 => true,
+ 6175 => true,
+ 6265 => true,
+ 6266 => true,
+ 6267 => true,
+ 6268 => true,
+ 6269 => true,
+ 6270 => true,
+ 6271 => true,
+ 6315 => true,
+ 6316 => true,
+ 6317 => true,
+ 6318 => true,
+ 6319 => true,
+ 6390 => true,
+ 6391 => true,
+ 6392 => true,
+ 6393 => true,
+ 6394 => true,
+ 6395 => true,
+ 6396 => true,
+ 6397 => true,
+ 6398 => true,
+ 6399 => true,
+ 6431 => true,
+ 6444 => true,
+ 6445 => true,
+ 6446 => true,
+ 6447 => true,
+ 6460 => true,
+ 6461 => true,
+ 6462 => true,
+ 6463 => true,
+ 6465 => true,
+ 6466 => true,
+ 6467 => true,
+ 6510 => true,
+ 6511 => true,
+ 6517 => true,
+ 6518 => true,
+ 6519 => true,
+ 6520 => true,
+ 6521 => true,
+ 6522 => true,
+ 6523 => true,
+ 6524 => true,
+ 6525 => true,
+ 6526 => true,
+ 6527 => true,
+ 6572 => true,
+ 6573 => true,
+ 6574 => true,
+ 6575 => true,
+ 6602 => true,
+ 6603 => true,
+ 6604 => true,
+ 6605 => true,
+ 6606 => true,
+ 6607 => true,
+ 6619 => true,
+ 6620 => true,
+ 6621 => true,
+ 6684 => true,
+ 6685 => true,
+ 6751 => true,
+ 6781 => true,
+ 6782 => true,
+ 6794 => true,
+ 6795 => true,
+ 6796 => true,
+ 6797 => true,
+ 6798 => true,
+ 6799 => true,
+ 6810 => true,
+ 6811 => true,
+ 6812 => true,
+ 6813 => true,
+ 6814 => true,
+ 6815 => true,
+ 6830 => true,
+ 6831 => true,
+ 6988 => true,
+ 6989 => true,
+ 6990 => true,
+ 6991 => true,
+ 7037 => true,
+ 7038 => true,
+ 7039 => true,
+ 7156 => true,
+ 7157 => true,
+ 7158 => true,
+ 7159 => true,
+ 7160 => true,
+ 7161 => true,
+ 7162 => true,
+ 7163 => true,
+ 7224 => true,
+ 7225 => true,
+ 7226 => true,
+ 7242 => true,
+ 7243 => true,
+ 7244 => true,
+ 7305 => true,
+ 7306 => true,
+ 7307 => true,
+ 7308 => true,
+ 7309 => true,
+ 7310 => true,
+ 7311 => true,
+ 7355 => true,
+ 7356 => true,
+ 7368 => true,
+ 7369 => true,
+ 7370 => true,
+ 7371 => true,
+ 7372 => true,
+ 7373 => true,
+ 7374 => true,
+ 7375 => true,
+ 7419 => true,
+ 7420 => true,
+ 7421 => true,
+ 7422 => true,
+ 7423 => true,
+ 7674 => true,
+ 7958 => true,
+ 7959 => true,
+ 7966 => true,
+ 7967 => true,
+ 8006 => true,
+ 8007 => true,
+ 8014 => true,
+ 8015 => true,
+ 8024 => true,
+ 8026 => true,
+ 8028 => true,
+ 8030 => true,
+ 8062 => true,
+ 8063 => true,
+ 8117 => true,
+ 8133 => true,
+ 8148 => true,
+ 8149 => true,
+ 8156 => true,
+ 8176 => true,
+ 8177 => true,
+ 8181 => true,
+ 8191 => true,
+ 8206 => true,
+ 8207 => true,
+ 8228 => true,
+ 8229 => true,
+ 8230 => true,
+ 8232 => true,
+ 8233 => true,
+ 8234 => true,
+ 8235 => true,
+ 8236 => true,
+ 8237 => true,
+ 8238 => true,
+ 8289 => true,
+ 8290 => true,
+ 8291 => true,
+ 8293 => true,
+ 8294 => true,
+ 8295 => true,
+ 8296 => true,
+ 8297 => true,
+ 8298 => true,
+ 8299 => true,
+ 8300 => true,
+ 8301 => true,
+ 8302 => true,
+ 8303 => true,
+ 8306 => true,
+ 8307 => true,
+ 8335 => true,
+ 8349 => true,
+ 8350 => true,
+ 8351 => true,
+ 8384 => true,
+ 8385 => true,
+ 8386 => true,
+ 8387 => true,
+ 8388 => true,
+ 8389 => true,
+ 8390 => true,
+ 8391 => true,
+ 8392 => true,
+ 8393 => true,
+ 8394 => true,
+ 8395 => true,
+ 8396 => true,
+ 8397 => true,
+ 8398 => true,
+ 8399 => true,
+ 8433 => true,
+ 8434 => true,
+ 8435 => true,
+ 8436 => true,
+ 8437 => true,
+ 8438 => true,
+ 8439 => true,
+ 8440 => true,
+ 8441 => true,
+ 8442 => true,
+ 8443 => true,
+ 8444 => true,
+ 8445 => true,
+ 8446 => true,
+ 8447 => true,
+ 8498 => true,
+ 8579 => true,
+ 8588 => true,
+ 8589 => true,
+ 8590 => true,
+ 8591 => true,
+ 9255 => true,
+ 9256 => true,
+ 9257 => true,
+ 9258 => true,
+ 9259 => true,
+ 9260 => true,
+ 9261 => true,
+ 9262 => true,
+ 9263 => true,
+ 9264 => true,
+ 9265 => true,
+ 9266 => true,
+ 9267 => true,
+ 9268 => true,
+ 9269 => true,
+ 9270 => true,
+ 9271 => true,
+ 9272 => true,
+ 9273 => true,
+ 9274 => true,
+ 9275 => true,
+ 9276 => true,
+ 9277 => true,
+ 9278 => true,
+ 9279 => true,
+ 9291 => true,
+ 9292 => true,
+ 9293 => true,
+ 9294 => true,
+ 9295 => true,
+ 9296 => true,
+ 9297 => true,
+ 9298 => true,
+ 9299 => true,
+ 9300 => true,
+ 9301 => true,
+ 9302 => true,
+ 9303 => true,
+ 9304 => true,
+ 9305 => true,
+ 9306 => true,
+ 9307 => true,
+ 9308 => true,
+ 9309 => true,
+ 9310 => true,
+ 9311 => true,
+ 9352 => true,
+ 9353 => true,
+ 9354 => true,
+ 9355 => true,
+ 9356 => true,
+ 9357 => true,
+ 9358 => true,
+ 9359 => true,
+ 9360 => true,
+ 9361 => true,
+ 9362 => true,
+ 9363 => true,
+ 9364 => true,
+ 9365 => true,
+ 9366 => true,
+ 9367 => true,
+ 9368 => true,
+ 9369 => true,
+ 9370 => true,
+ 9371 => true,
+ 11124 => true,
+ 11125 => true,
+ 11158 => true,
+ 11311 => true,
+ 11359 => true,
+ 11508 => true,
+ 11509 => true,
+ 11510 => true,
+ 11511 => true,
+ 11512 => true,
+ 11558 => true,
+ 11560 => true,
+ 11561 => true,
+ 11562 => true,
+ 11563 => true,
+ 11564 => true,
+ 11566 => true,
+ 11567 => true,
+ 11624 => true,
+ 11625 => true,
+ 11626 => true,
+ 11627 => true,
+ 11628 => true,
+ 11629 => true,
+ 11630 => true,
+ 11633 => true,
+ 11634 => true,
+ 11635 => true,
+ 11636 => true,
+ 11637 => true,
+ 11638 => true,
+ 11639 => true,
+ 11640 => true,
+ 11641 => true,
+ 11642 => true,
+ 11643 => true,
+ 11644 => true,
+ 11645 => true,
+ 11646 => true,
+ 11671 => true,
+ 11672 => true,
+ 11673 => true,
+ 11674 => true,
+ 11675 => true,
+ 11676 => true,
+ 11677 => true,
+ 11678 => true,
+ 11679 => true,
+ 11687 => true,
+ 11695 => true,
+ 11703 => true,
+ 11711 => true,
+ 11719 => true,
+ 11727 => true,
+ 11735 => true,
+ 11743 => true,
+ 11930 => true,
+ 12020 => true,
+ 12021 => true,
+ 12022 => true,
+ 12023 => true,
+ 12024 => true,
+ 12025 => true,
+ 12026 => true,
+ 12027 => true,
+ 12028 => true,
+ 12029 => true,
+ 12030 => true,
+ 12031 => true,
+ 12246 => true,
+ 12247 => true,
+ 12248 => true,
+ 12249 => true,
+ 12250 => true,
+ 12251 => true,
+ 12252 => true,
+ 12253 => true,
+ 12254 => true,
+ 12255 => true,
+ 12256 => true,
+ 12257 => true,
+ 12258 => true,
+ 12259 => true,
+ 12260 => true,
+ 12261 => true,
+ 12262 => true,
+ 12263 => true,
+ 12264 => true,
+ 12265 => true,
+ 12266 => true,
+ 12267 => true,
+ 12268 => true,
+ 12269 => true,
+ 12270 => true,
+ 12271 => true,
+ 12272 => true,
+ 12273 => true,
+ 12274 => true,
+ 12275 => true,
+ 12276 => true,
+ 12277 => true,
+ 12278 => true,
+ 12279 => true,
+ 12280 => true,
+ 12281 => true,
+ 12282 => true,
+ 12283 => true,
+ 12284 => true,
+ 12285 => true,
+ 12286 => true,
+ 12287 => true,
+ 12352 => true,
+ 12439 => true,
+ 12440 => true,
+ 12544 => true,
+ 12545 => true,
+ 12546 => true,
+ 12547 => true,
+ 12548 => true,
+ 12592 => true,
+ 12644 => true,
+ 12687 => true,
+ 12772 => true,
+ 12773 => true,
+ 12774 => true,
+ 12775 => true,
+ 12776 => true,
+ 12777 => true,
+ 12778 => true,
+ 12779 => true,
+ 12780 => true,
+ 12781 => true,
+ 12782 => true,
+ 12783 => true,
+ 12831 => true,
+ 13250 => true,
+ 13255 => true,
+ 13272 => true,
+ 40957 => true,
+ 40958 => true,
+ 40959 => true,
+ 42125 => true,
+ 42126 => true,
+ 42127 => true,
+ 42183 => true,
+ 42184 => true,
+ 42185 => true,
+ 42186 => true,
+ 42187 => true,
+ 42188 => true,
+ 42189 => true,
+ 42190 => true,
+ 42191 => true,
+ 42540 => true,
+ 42541 => true,
+ 42542 => true,
+ 42543 => true,
+ 42544 => true,
+ 42545 => true,
+ 42546 => true,
+ 42547 => true,
+ 42548 => true,
+ 42549 => true,
+ 42550 => true,
+ 42551 => true,
+ 42552 => true,
+ 42553 => true,
+ 42554 => true,
+ 42555 => true,
+ 42556 => true,
+ 42557 => true,
+ 42558 => true,
+ 42559 => true,
+ 42744 => true,
+ 42745 => true,
+ 42746 => true,
+ 42747 => true,
+ 42748 => true,
+ 42749 => true,
+ 42750 => true,
+ 42751 => true,
+ 42944 => true,
+ 42945 => true,
+ 43053 => true,
+ 43054 => true,
+ 43055 => true,
+ 43066 => true,
+ 43067 => true,
+ 43068 => true,
+ 43069 => true,
+ 43070 => true,
+ 43071 => true,
+ 43128 => true,
+ 43129 => true,
+ 43130 => true,
+ 43131 => true,
+ 43132 => true,
+ 43133 => true,
+ 43134 => true,
+ 43135 => true,
+ 43206 => true,
+ 43207 => true,
+ 43208 => true,
+ 43209 => true,
+ 43210 => true,
+ 43211 => true,
+ 43212 => true,
+ 43213 => true,
+ 43226 => true,
+ 43227 => true,
+ 43228 => true,
+ 43229 => true,
+ 43230 => true,
+ 43231 => true,
+ 43348 => true,
+ 43349 => true,
+ 43350 => true,
+ 43351 => true,
+ 43352 => true,
+ 43353 => true,
+ 43354 => true,
+ 43355 => true,
+ 43356 => true,
+ 43357 => true,
+ 43358 => true,
+ 43389 => true,
+ 43390 => true,
+ 43391 => true,
+ 43470 => true,
+ 43482 => true,
+ 43483 => true,
+ 43484 => true,
+ 43485 => true,
+ 43519 => true,
+ 43575 => true,
+ 43576 => true,
+ 43577 => true,
+ 43578 => true,
+ 43579 => true,
+ 43580 => true,
+ 43581 => true,
+ 43582 => true,
+ 43583 => true,
+ 43598 => true,
+ 43599 => true,
+ 43610 => true,
+ 43611 => true,
+ 43715 => true,
+ 43716 => true,
+ 43717 => true,
+ 43718 => true,
+ 43719 => true,
+ 43720 => true,
+ 43721 => true,
+ 43722 => true,
+ 43723 => true,
+ 43724 => true,
+ 43725 => true,
+ 43726 => true,
+ 43727 => true,
+ 43728 => true,
+ 43729 => true,
+ 43730 => true,
+ 43731 => true,
+ 43732 => true,
+ 43733 => true,
+ 43734 => true,
+ 43735 => true,
+ 43736 => true,
+ 43737 => true,
+ 43738 => true,
+ 43767 => true,
+ 43768 => true,
+ 43769 => true,
+ 43770 => true,
+ 43771 => true,
+ 43772 => true,
+ 43773 => true,
+ 43774 => true,
+ 43775 => true,
+ 43776 => true,
+ 43783 => true,
+ 43784 => true,
+ 43791 => true,
+ 43792 => true,
+ 43799 => true,
+ 43800 => true,
+ 43801 => true,
+ 43802 => true,
+ 43803 => true,
+ 43804 => true,
+ 43805 => true,
+ 43806 => true,
+ 43807 => true,
+ 43815 => true,
+ 43823 => true,
+ 43884 => true,
+ 43885 => true,
+ 43886 => true,
+ 43887 => true,
+ 44014 => true,
+ 44015 => true,
+ 44026 => true,
+ 44027 => true,
+ 44028 => true,
+ 44029 => true,
+ 44030 => true,
+ 44031 => true,
+ 55204 => true,
+ 55205 => true,
+ 55206 => true,
+ 55207 => true,
+ 55208 => true,
+ 55209 => true,
+ 55210 => true,
+ 55211 => true,
+ 55212 => true,
+ 55213 => true,
+ 55214 => true,
+ 55215 => true,
+ 55239 => true,
+ 55240 => true,
+ 55241 => true,
+ 55242 => true,
+ 55292 => true,
+ 55293 => true,
+ 55294 => true,
+ 55295 => true,
+ 64110 => true,
+ 64111 => true,
+ 64263 => true,
+ 64264 => true,
+ 64265 => true,
+ 64266 => true,
+ 64267 => true,
+ 64268 => true,
+ 64269 => true,
+ 64270 => true,
+ 64271 => true,
+ 64272 => true,
+ 64273 => true,
+ 64274 => true,
+ 64280 => true,
+ 64281 => true,
+ 64282 => true,
+ 64283 => true,
+ 64284 => true,
+ 64311 => true,
+ 64317 => true,
+ 64319 => true,
+ 64322 => true,
+ 64325 => true,
+ 64450 => true,
+ 64451 => true,
+ 64452 => true,
+ 64453 => true,
+ 64454 => true,
+ 64455 => true,
+ 64456 => true,
+ 64457 => true,
+ 64458 => true,
+ 64459 => true,
+ 64460 => true,
+ 64461 => true,
+ 64462 => true,
+ 64463 => true,
+ 64464 => true,
+ 64465 => true,
+ 64466 => true,
+ 64832 => true,
+ 64833 => true,
+ 64834 => true,
+ 64835 => true,
+ 64836 => true,
+ 64837 => true,
+ 64838 => true,
+ 64839 => true,
+ 64840 => true,
+ 64841 => true,
+ 64842 => true,
+ 64843 => true,
+ 64844 => true,
+ 64845 => true,
+ 64846 => true,
+ 64847 => true,
+ 64912 => true,
+ 64913 => true,
+ 64968 => true,
+ 64969 => true,
+ 64970 => true,
+ 64971 => true,
+ 64972 => true,
+ 64973 => true,
+ 64974 => true,
+ 64975 => true,
+ 65022 => true,
+ 65023 => true,
+ 65042 => true,
+ 65049 => true,
+ 65050 => true,
+ 65051 => true,
+ 65052 => true,
+ 65053 => true,
+ 65054 => true,
+ 65055 => true,
+ 65072 => true,
+ 65106 => true,
+ 65107 => true,
+ 65127 => true,
+ 65132 => true,
+ 65133 => true,
+ 65134 => true,
+ 65135 => true,
+ 65141 => true,
+ 65277 => true,
+ 65278 => true,
+ 65280 => true,
+ 65440 => true,
+ 65471 => true,
+ 65472 => true,
+ 65473 => true,
+ 65480 => true,
+ 65481 => true,
+ 65488 => true,
+ 65489 => true,
+ 65496 => true,
+ 65497 => true,
+ 65501 => true,
+ 65502 => true,
+ 65503 => true,
+ 65511 => true,
+ 65519 => true,
+ 65520 => true,
+ 65521 => true,
+ 65522 => true,
+ 65523 => true,
+ 65524 => true,
+ 65525 => true,
+ 65526 => true,
+ 65527 => true,
+ 65528 => true,
+ 65529 => true,
+ 65530 => true,
+ 65531 => true,
+ 65532 => true,
+ 65533 => true,
+ 65534 => true,
+ 65535 => true,
+ 65548 => true,
+ 65575 => true,
+ 65595 => true,
+ 65598 => true,
+ 65614 => true,
+ 65615 => true,
+ 65787 => true,
+ 65788 => true,
+ 65789 => true,
+ 65790 => true,
+ 65791 => true,
+ 65795 => true,
+ 65796 => true,
+ 65797 => true,
+ 65798 => true,
+ 65844 => true,
+ 65845 => true,
+ 65846 => true,
+ 65935 => true,
+ 65949 => true,
+ 65950 => true,
+ 65951 => true,
+ 66205 => true,
+ 66206 => true,
+ 66207 => true,
+ 66257 => true,
+ 66258 => true,
+ 66259 => true,
+ 66260 => true,
+ 66261 => true,
+ 66262 => true,
+ 66263 => true,
+ 66264 => true,
+ 66265 => true,
+ 66266 => true,
+ 66267 => true,
+ 66268 => true,
+ 66269 => true,
+ 66270 => true,
+ 66271 => true,
+ 66300 => true,
+ 66301 => true,
+ 66302 => true,
+ 66303 => true,
+ 66340 => true,
+ 66341 => true,
+ 66342 => true,
+ 66343 => true,
+ 66344 => true,
+ 66345 => true,
+ 66346 => true,
+ 66347 => true,
+ 66348 => true,
+ 66379 => true,
+ 66380 => true,
+ 66381 => true,
+ 66382 => true,
+ 66383 => true,
+ 66427 => true,
+ 66428 => true,
+ 66429 => true,
+ 66430 => true,
+ 66431 => true,
+ 66462 => true,
+ 66500 => true,
+ 66501 => true,
+ 66502 => true,
+ 66503 => true,
+ 66718 => true,
+ 66719 => true,
+ 66730 => true,
+ 66731 => true,
+ 66732 => true,
+ 66733 => true,
+ 66734 => true,
+ 66735 => true,
+ 66772 => true,
+ 66773 => true,
+ 66774 => true,
+ 66775 => true,
+ 66812 => true,
+ 66813 => true,
+ 66814 => true,
+ 66815 => true,
+ 66856 => true,
+ 66857 => true,
+ 66858 => true,
+ 66859 => true,
+ 66860 => true,
+ 66861 => true,
+ 66862 => true,
+ 66863 => true,
+ 66916 => true,
+ 66917 => true,
+ 66918 => true,
+ 66919 => true,
+ 66920 => true,
+ 66921 => true,
+ 66922 => true,
+ 66923 => true,
+ 66924 => true,
+ 66925 => true,
+ 66926 => true,
+ 67383 => true,
+ 67384 => true,
+ 67385 => true,
+ 67386 => true,
+ 67387 => true,
+ 67388 => true,
+ 67389 => true,
+ 67390 => true,
+ 67391 => true,
+ 67414 => true,
+ 67415 => true,
+ 67416 => true,
+ 67417 => true,
+ 67418 => true,
+ 67419 => true,
+ 67420 => true,
+ 67421 => true,
+ 67422 => true,
+ 67423 => true,
+ 67590 => true,
+ 67591 => true,
+ 67593 => true,
+ 67638 => true,
+ 67641 => true,
+ 67642 => true,
+ 67643 => true,
+ 67645 => true,
+ 67646 => true,
+ 67670 => true,
+ 67743 => true,
+ 67744 => true,
+ 67745 => true,
+ 67746 => true,
+ 67747 => true,
+ 67748 => true,
+ 67749 => true,
+ 67750 => true,
+ 67827 => true,
+ 67830 => true,
+ 67831 => true,
+ 67832 => true,
+ 67833 => true,
+ 67834 => true,
+ 67868 => true,
+ 67869 => true,
+ 67870 => true,
+ 67898 => true,
+ 67899 => true,
+ 67900 => true,
+ 67901 => true,
+ 67902 => true,
+ 68024 => true,
+ 68025 => true,
+ 68026 => true,
+ 68027 => true,
+ 68048 => true,
+ 68049 => true,
+ 68100 => true,
+ 68103 => true,
+ 68104 => true,
+ 68105 => true,
+ 68106 => true,
+ 68107 => true,
+ 68116 => true,
+ 68120 => true,
+ 68150 => true,
+ 68151 => true,
+ 68155 => true,
+ 68156 => true,
+ 68157 => true,
+ 68158 => true,
+ 68169 => true,
+ 68170 => true,
+ 68171 => true,
+ 68172 => true,
+ 68173 => true,
+ 68174 => true,
+ 68175 => true,
+ 68185 => true,
+ 68186 => true,
+ 68187 => true,
+ 68188 => true,
+ 68189 => true,
+ 68190 => true,
+ 68191 => true,
+ 68327 => true,
+ 68328 => true,
+ 68329 => true,
+ 68330 => true,
+ 68343 => true,
+ 68344 => true,
+ 68345 => true,
+ 68346 => true,
+ 68347 => true,
+ 68348 => true,
+ 68349 => true,
+ 68350 => true,
+ 68351 => true,
+ 68406 => true,
+ 68407 => true,
+ 68408 => true,
+ 68438 => true,
+ 68439 => true,
+ 68467 => true,
+ 68468 => true,
+ 68469 => true,
+ 68470 => true,
+ 68471 => true,
+ 68498 => true,
+ 68499 => true,
+ 68500 => true,
+ 68501 => true,
+ 68502 => true,
+ 68503 => true,
+ 68504 => true,
+ 68509 => true,
+ 68510 => true,
+ 68511 => true,
+ 68512 => true,
+ 68513 => true,
+ 68514 => true,
+ 68515 => true,
+ 68516 => true,
+ 68517 => true,
+ 68518 => true,
+ 68519 => true,
+ 68520 => true,
+ 68787 => true,
+ 68788 => true,
+ 68789 => true,
+ 68790 => true,
+ 68791 => true,
+ 68792 => true,
+ 68793 => true,
+ 68794 => true,
+ 68795 => true,
+ 68796 => true,
+ 68797 => true,
+ 68798 => true,
+ 68799 => true,
+ 68851 => true,
+ 68852 => true,
+ 68853 => true,
+ 68854 => true,
+ 68855 => true,
+ 68856 => true,
+ 68857 => true,
+ 68904 => true,
+ 68905 => true,
+ 68906 => true,
+ 68907 => true,
+ 68908 => true,
+ 68909 => true,
+ 68910 => true,
+ 68911 => true,
+ 69247 => true,
+ 69290 => true,
+ 69294 => true,
+ 69295 => true,
+ 69416 => true,
+ 69417 => true,
+ 69418 => true,
+ 69419 => true,
+ 69420 => true,
+ 69421 => true,
+ 69422 => true,
+ 69423 => true,
+ 69580 => true,
+ 69581 => true,
+ 69582 => true,
+ 69583 => true,
+ 69584 => true,
+ 69585 => true,
+ 69586 => true,
+ 69587 => true,
+ 69588 => true,
+ 69589 => true,
+ 69590 => true,
+ 69591 => true,
+ 69592 => true,
+ 69593 => true,
+ 69594 => true,
+ 69595 => true,
+ 69596 => true,
+ 69597 => true,
+ 69598 => true,
+ 69599 => true,
+ 69623 => true,
+ 69624 => true,
+ 69625 => true,
+ 69626 => true,
+ 69627 => true,
+ 69628 => true,
+ 69629 => true,
+ 69630 => true,
+ 69631 => true,
+ 69710 => true,
+ 69711 => true,
+ 69712 => true,
+ 69713 => true,
+ 69744 => true,
+ 69745 => true,
+ 69746 => true,
+ 69747 => true,
+ 69748 => true,
+ 69749 => true,
+ 69750 => true,
+ 69751 => true,
+ 69752 => true,
+ 69753 => true,
+ 69754 => true,
+ 69755 => true,
+ 69756 => true,
+ 69757 => true,
+ 69758 => true,
+ 69821 => true,
+ 69826 => true,
+ 69827 => true,
+ 69828 => true,
+ 69829 => true,
+ 69830 => true,
+ 69831 => true,
+ 69832 => true,
+ 69833 => true,
+ 69834 => true,
+ 69835 => true,
+ 69836 => true,
+ 69837 => true,
+ 69838 => true,
+ 69839 => true,
+ 69865 => true,
+ 69866 => true,
+ 69867 => true,
+ 69868 => true,
+ 69869 => true,
+ 69870 => true,
+ 69871 => true,
+ 69882 => true,
+ 69883 => true,
+ 69884 => true,
+ 69885 => true,
+ 69886 => true,
+ 69887 => true,
+ 69941 => true,
+ 69960 => true,
+ 69961 => true,
+ 69962 => true,
+ 69963 => true,
+ 69964 => true,
+ 69965 => true,
+ 69966 => true,
+ 69967 => true,
+ 70007 => true,
+ 70008 => true,
+ 70009 => true,
+ 70010 => true,
+ 70011 => true,
+ 70012 => true,
+ 70013 => true,
+ 70014 => true,
+ 70015 => true,
+ 70112 => true,
+ 70133 => true,
+ 70134 => true,
+ 70135 => true,
+ 70136 => true,
+ 70137 => true,
+ 70138 => true,
+ 70139 => true,
+ 70140 => true,
+ 70141 => true,
+ 70142 => true,
+ 70143 => true,
+ 70162 => true,
+ 70279 => true,
+ 70281 => true,
+ 70286 => true,
+ 70302 => true,
+ 70314 => true,
+ 70315 => true,
+ 70316 => true,
+ 70317 => true,
+ 70318 => true,
+ 70319 => true,
+ 70379 => true,
+ 70380 => true,
+ 70381 => true,
+ 70382 => true,
+ 70383 => true,
+ 70394 => true,
+ 70395 => true,
+ 70396 => true,
+ 70397 => true,
+ 70398 => true,
+ 70399 => true,
+ 70404 => true,
+ 70413 => true,
+ 70414 => true,
+ 70417 => true,
+ 70418 => true,
+ 70441 => true,
+ 70449 => true,
+ 70452 => true,
+ 70458 => true,
+ 70469 => true,
+ 70470 => true,
+ 70473 => true,
+ 70474 => true,
+ 70478 => true,
+ 70479 => true,
+ 70481 => true,
+ 70482 => true,
+ 70483 => true,
+ 70484 => true,
+ 70485 => true,
+ 70486 => true,
+ 70488 => true,
+ 70489 => true,
+ 70490 => true,
+ 70491 => true,
+ 70492 => true,
+ 70500 => true,
+ 70501 => true,
+ 70509 => true,
+ 70510 => true,
+ 70511 => true,
+ 70748 => true,
+ 70754 => true,
+ 70755 => true,
+ 70756 => true,
+ 70757 => true,
+ 70758 => true,
+ 70759 => true,
+ 70760 => true,
+ 70761 => true,
+ 70762 => true,
+ 70763 => true,
+ 70764 => true,
+ 70765 => true,
+ 70766 => true,
+ 70767 => true,
+ 70768 => true,
+ 70769 => true,
+ 70770 => true,
+ 70771 => true,
+ 70772 => true,
+ 70773 => true,
+ 70774 => true,
+ 70775 => true,
+ 70776 => true,
+ 70777 => true,
+ 70778 => true,
+ 70779 => true,
+ 70780 => true,
+ 70781 => true,
+ 70782 => true,
+ 70783 => true,
+ 70856 => true,
+ 70857 => true,
+ 70858 => true,
+ 70859 => true,
+ 70860 => true,
+ 70861 => true,
+ 70862 => true,
+ 70863 => true,
+ 71094 => true,
+ 71095 => true,
+ 71237 => true,
+ 71238 => true,
+ 71239 => true,
+ 71240 => true,
+ 71241 => true,
+ 71242 => true,
+ 71243 => true,
+ 71244 => true,
+ 71245 => true,
+ 71246 => true,
+ 71247 => true,
+ 71258 => true,
+ 71259 => true,
+ 71260 => true,
+ 71261 => true,
+ 71262 => true,
+ 71263 => true,
+ 71277 => true,
+ 71278 => true,
+ 71279 => true,
+ 71280 => true,
+ 71281 => true,
+ 71282 => true,
+ 71283 => true,
+ 71284 => true,
+ 71285 => true,
+ 71286 => true,
+ 71287 => true,
+ 71288 => true,
+ 71289 => true,
+ 71290 => true,
+ 71291 => true,
+ 71292 => true,
+ 71293 => true,
+ 71294 => true,
+ 71295 => true,
+ 71353 => true,
+ 71354 => true,
+ 71355 => true,
+ 71356 => true,
+ 71357 => true,
+ 71358 => true,
+ 71359 => true,
+ 71451 => true,
+ 71452 => true,
+ 71468 => true,
+ 71469 => true,
+ 71470 => true,
+ 71471 => true,
+ 71923 => true,
+ 71924 => true,
+ 71925 => true,
+ 71926 => true,
+ 71927 => true,
+ 71928 => true,
+ 71929 => true,
+ 71930 => true,
+ 71931 => true,
+ 71932 => true,
+ 71933 => true,
+ 71934 => true,
+ 71943 => true,
+ 71944 => true,
+ 71946 => true,
+ 71947 => true,
+ 71956 => true,
+ 71959 => true,
+ 71990 => true,
+ 71993 => true,
+ 71994 => true,
+ 72007 => true,
+ 72008 => true,
+ 72009 => true,
+ 72010 => true,
+ 72011 => true,
+ 72012 => true,
+ 72013 => true,
+ 72014 => true,
+ 72015 => true,
+ 72104 => true,
+ 72105 => true,
+ 72152 => true,
+ 72153 => true,
+ 72165 => true,
+ 72166 => true,
+ 72167 => true,
+ 72168 => true,
+ 72169 => true,
+ 72170 => true,
+ 72171 => true,
+ 72172 => true,
+ 72173 => true,
+ 72174 => true,
+ 72175 => true,
+ 72176 => true,
+ 72177 => true,
+ 72178 => true,
+ 72179 => true,
+ 72180 => true,
+ 72181 => true,
+ 72182 => true,
+ 72183 => true,
+ 72184 => true,
+ 72185 => true,
+ 72186 => true,
+ 72187 => true,
+ 72188 => true,
+ 72189 => true,
+ 72190 => true,
+ 72191 => true,
+ 72264 => true,
+ 72265 => true,
+ 72266 => true,
+ 72267 => true,
+ 72268 => true,
+ 72269 => true,
+ 72270 => true,
+ 72271 => true,
+ 72355 => true,
+ 72356 => true,
+ 72357 => true,
+ 72358 => true,
+ 72359 => true,
+ 72360 => true,
+ 72361 => true,
+ 72362 => true,
+ 72363 => true,
+ 72364 => true,
+ 72365 => true,
+ 72366 => true,
+ 72367 => true,
+ 72368 => true,
+ 72369 => true,
+ 72370 => true,
+ 72371 => true,
+ 72372 => true,
+ 72373 => true,
+ 72374 => true,
+ 72375 => true,
+ 72376 => true,
+ 72377 => true,
+ 72378 => true,
+ 72379 => true,
+ 72380 => true,
+ 72381 => true,
+ 72382 => true,
+ 72383 => true,
+ 72713 => true,
+ 72759 => true,
+ 72774 => true,
+ 72775 => true,
+ 72776 => true,
+ 72777 => true,
+ 72778 => true,
+ 72779 => true,
+ 72780 => true,
+ 72781 => true,
+ 72782 => true,
+ 72783 => true,
+ 72813 => true,
+ 72814 => true,
+ 72815 => true,
+ 72848 => true,
+ 72849 => true,
+ 72872 => true,
+ 72967 => true,
+ 72970 => true,
+ 73015 => true,
+ 73016 => true,
+ 73017 => true,
+ 73019 => true,
+ 73022 => true,
+ 73032 => true,
+ 73033 => true,
+ 73034 => true,
+ 73035 => true,
+ 73036 => true,
+ 73037 => true,
+ 73038 => true,
+ 73039 => true,
+ 73050 => true,
+ 73051 => true,
+ 73052 => true,
+ 73053 => true,
+ 73054 => true,
+ 73055 => true,
+ 73062 => true,
+ 73065 => true,
+ 73103 => true,
+ 73106 => true,
+ 73113 => true,
+ 73114 => true,
+ 73115 => true,
+ 73116 => true,
+ 73117 => true,
+ 73118 => true,
+ 73119 => true,
+ 73649 => true,
+ 73650 => true,
+ 73651 => true,
+ 73652 => true,
+ 73653 => true,
+ 73654 => true,
+ 73655 => true,
+ 73656 => true,
+ 73657 => true,
+ 73658 => true,
+ 73659 => true,
+ 73660 => true,
+ 73661 => true,
+ 73662 => true,
+ 73663 => true,
+ 73714 => true,
+ 73715 => true,
+ 73716 => true,
+ 73717 => true,
+ 73718 => true,
+ 73719 => true,
+ 73720 => true,
+ 73721 => true,
+ 73722 => true,
+ 73723 => true,
+ 73724 => true,
+ 73725 => true,
+ 73726 => true,
+ 74863 => true,
+ 74869 => true,
+ 74870 => true,
+ 74871 => true,
+ 74872 => true,
+ 74873 => true,
+ 74874 => true,
+ 74875 => true,
+ 74876 => true,
+ 74877 => true,
+ 74878 => true,
+ 74879 => true,
+ 78895 => true,
+ 78896 => true,
+ 78897 => true,
+ 78898 => true,
+ 78899 => true,
+ 78900 => true,
+ 78901 => true,
+ 78902 => true,
+ 78903 => true,
+ 78904 => true,
+ 92729 => true,
+ 92730 => true,
+ 92731 => true,
+ 92732 => true,
+ 92733 => true,
+ 92734 => true,
+ 92735 => true,
+ 92767 => true,
+ 92778 => true,
+ 92779 => true,
+ 92780 => true,
+ 92781 => true,
+ 92910 => true,
+ 92911 => true,
+ 92918 => true,
+ 92919 => true,
+ 92920 => true,
+ 92921 => true,
+ 92922 => true,
+ 92923 => true,
+ 92924 => true,
+ 92925 => true,
+ 92926 => true,
+ 92927 => true,
+ 92998 => true,
+ 92999 => true,
+ 93000 => true,
+ 93001 => true,
+ 93002 => true,
+ 93003 => true,
+ 93004 => true,
+ 93005 => true,
+ 93006 => true,
+ 93007 => true,
+ 93018 => true,
+ 93026 => true,
+ 93048 => true,
+ 93049 => true,
+ 93050 => true,
+ 93051 => true,
+ 93052 => true,
+ 94027 => true,
+ 94028 => true,
+ 94029 => true,
+ 94030 => true,
+ 94088 => true,
+ 94089 => true,
+ 94090 => true,
+ 94091 => true,
+ 94092 => true,
+ 94093 => true,
+ 94094 => true,
+ 94181 => true,
+ 94182 => true,
+ 94183 => true,
+ 94184 => true,
+ 94185 => true,
+ 94186 => true,
+ 94187 => true,
+ 94188 => true,
+ 94189 => true,
+ 94190 => true,
+ 94191 => true,
+ 94194 => true,
+ 94195 => true,
+ 94196 => true,
+ 94197 => true,
+ 94198 => true,
+ 94199 => true,
+ 94200 => true,
+ 94201 => true,
+ 94202 => true,
+ 94203 => true,
+ 94204 => true,
+ 94205 => true,
+ 94206 => true,
+ 94207 => true,
+ 100344 => true,
+ 100345 => true,
+ 100346 => true,
+ 100347 => true,
+ 100348 => true,
+ 100349 => true,
+ 100350 => true,
+ 100351 => true,
+ 110931 => true,
+ 110932 => true,
+ 110933 => true,
+ 110934 => true,
+ 110935 => true,
+ 110936 => true,
+ 110937 => true,
+ 110938 => true,
+ 110939 => true,
+ 110940 => true,
+ 110941 => true,
+ 110942 => true,
+ 110943 => true,
+ 110944 => true,
+ 110945 => true,
+ 110946 => true,
+ 110947 => true,
+ 110952 => true,
+ 110953 => true,
+ 110954 => true,
+ 110955 => true,
+ 110956 => true,
+ 110957 => true,
+ 110958 => true,
+ 110959 => true,
+ 113771 => true,
+ 113772 => true,
+ 113773 => true,
+ 113774 => true,
+ 113775 => true,
+ 113789 => true,
+ 113790 => true,
+ 113791 => true,
+ 113801 => true,
+ 113802 => true,
+ 113803 => true,
+ 113804 => true,
+ 113805 => true,
+ 113806 => true,
+ 113807 => true,
+ 113818 => true,
+ 113819 => true,
+ 119030 => true,
+ 119031 => true,
+ 119032 => true,
+ 119033 => true,
+ 119034 => true,
+ 119035 => true,
+ 119036 => true,
+ 119037 => true,
+ 119038 => true,
+ 119039 => true,
+ 119079 => true,
+ 119080 => true,
+ 119155 => true,
+ 119156 => true,
+ 119157 => true,
+ 119158 => true,
+ 119159 => true,
+ 119160 => true,
+ 119161 => true,
+ 119162 => true,
+ 119273 => true,
+ 119274 => true,
+ 119275 => true,
+ 119276 => true,
+ 119277 => true,
+ 119278 => true,
+ 119279 => true,
+ 119280 => true,
+ 119281 => true,
+ 119282 => true,
+ 119283 => true,
+ 119284 => true,
+ 119285 => true,
+ 119286 => true,
+ 119287 => true,
+ 119288 => true,
+ 119289 => true,
+ 119290 => true,
+ 119291 => true,
+ 119292 => true,
+ 119293 => true,
+ 119294 => true,
+ 119295 => true,
+ 119540 => true,
+ 119541 => true,
+ 119542 => true,
+ 119543 => true,
+ 119544 => true,
+ 119545 => true,
+ 119546 => true,
+ 119547 => true,
+ 119548 => true,
+ 119549 => true,
+ 119550 => true,
+ 119551 => true,
+ 119639 => true,
+ 119640 => true,
+ 119641 => true,
+ 119642 => true,
+ 119643 => true,
+ 119644 => true,
+ 119645 => true,
+ 119646 => true,
+ 119647 => true,
+ 119893 => true,
+ 119965 => true,
+ 119968 => true,
+ 119969 => true,
+ 119971 => true,
+ 119972 => true,
+ 119975 => true,
+ 119976 => true,
+ 119981 => true,
+ 119994 => true,
+ 119996 => true,
+ 120004 => true,
+ 120070 => true,
+ 120075 => true,
+ 120076 => true,
+ 120085 => true,
+ 120093 => true,
+ 120122 => true,
+ 120127 => true,
+ 120133 => true,
+ 120135 => true,
+ 120136 => true,
+ 120137 => true,
+ 120145 => true,
+ 120486 => true,
+ 120487 => true,
+ 120780 => true,
+ 120781 => true,
+ 121484 => true,
+ 121485 => true,
+ 121486 => true,
+ 121487 => true,
+ 121488 => true,
+ 121489 => true,
+ 121490 => true,
+ 121491 => true,
+ 121492 => true,
+ 121493 => true,
+ 121494 => true,
+ 121495 => true,
+ 121496 => true,
+ 121497 => true,
+ 121498 => true,
+ 121504 => true,
+ 122887 => true,
+ 122905 => true,
+ 122906 => true,
+ 122914 => true,
+ 122917 => true,
+ 123181 => true,
+ 123182 => true,
+ 123183 => true,
+ 123198 => true,
+ 123199 => true,
+ 123210 => true,
+ 123211 => true,
+ 123212 => true,
+ 123213 => true,
+ 123642 => true,
+ 123643 => true,
+ 123644 => true,
+ 123645 => true,
+ 123646 => true,
+ 125125 => true,
+ 125126 => true,
+ 125260 => true,
+ 125261 => true,
+ 125262 => true,
+ 125263 => true,
+ 125274 => true,
+ 125275 => true,
+ 125276 => true,
+ 125277 => true,
+ 126468 => true,
+ 126496 => true,
+ 126499 => true,
+ 126501 => true,
+ 126502 => true,
+ 126504 => true,
+ 126515 => true,
+ 126520 => true,
+ 126522 => true,
+ 126524 => true,
+ 126525 => true,
+ 126526 => true,
+ 126527 => true,
+ 126528 => true,
+ 126529 => true,
+ 126531 => true,
+ 126532 => true,
+ 126533 => true,
+ 126534 => true,
+ 126536 => true,
+ 126538 => true,
+ 126540 => true,
+ 126544 => true,
+ 126547 => true,
+ 126549 => true,
+ 126550 => true,
+ 126552 => true,
+ 126554 => true,
+ 126556 => true,
+ 126558 => true,
+ 126560 => true,
+ 126563 => true,
+ 126565 => true,
+ 126566 => true,
+ 126571 => true,
+ 126579 => true,
+ 126584 => true,
+ 126589 => true,
+ 126591 => true,
+ 126602 => true,
+ 126620 => true,
+ 126621 => true,
+ 126622 => true,
+ 126623 => true,
+ 126624 => true,
+ 126628 => true,
+ 126634 => true,
+ 127020 => true,
+ 127021 => true,
+ 127022 => true,
+ 127023 => true,
+ 127124 => true,
+ 127125 => true,
+ 127126 => true,
+ 127127 => true,
+ 127128 => true,
+ 127129 => true,
+ 127130 => true,
+ 127131 => true,
+ 127132 => true,
+ 127133 => true,
+ 127134 => true,
+ 127135 => true,
+ 127151 => true,
+ 127152 => true,
+ 127168 => true,
+ 127184 => true,
+ 127222 => true,
+ 127223 => true,
+ 127224 => true,
+ 127225 => true,
+ 127226 => true,
+ 127227 => true,
+ 127228 => true,
+ 127229 => true,
+ 127230 => true,
+ 127231 => true,
+ 127232 => true,
+ 127491 => true,
+ 127492 => true,
+ 127493 => true,
+ 127494 => true,
+ 127495 => true,
+ 127496 => true,
+ 127497 => true,
+ 127498 => true,
+ 127499 => true,
+ 127500 => true,
+ 127501 => true,
+ 127502 => true,
+ 127503 => true,
+ 127548 => true,
+ 127549 => true,
+ 127550 => true,
+ 127551 => true,
+ 127561 => true,
+ 127562 => true,
+ 127563 => true,
+ 127564 => true,
+ 127565 => true,
+ 127566 => true,
+ 127567 => true,
+ 127570 => true,
+ 127571 => true,
+ 127572 => true,
+ 127573 => true,
+ 127574 => true,
+ 127575 => true,
+ 127576 => true,
+ 127577 => true,
+ 127578 => true,
+ 127579 => true,
+ 127580 => true,
+ 127581 => true,
+ 127582 => true,
+ 127583 => true,
+ 128728 => true,
+ 128729 => true,
+ 128730 => true,
+ 128731 => true,
+ 128732 => true,
+ 128733 => true,
+ 128734 => true,
+ 128735 => true,
+ 128749 => true,
+ 128750 => true,
+ 128751 => true,
+ 128765 => true,
+ 128766 => true,
+ 128767 => true,
+ 128884 => true,
+ 128885 => true,
+ 128886 => true,
+ 128887 => true,
+ 128888 => true,
+ 128889 => true,
+ 128890 => true,
+ 128891 => true,
+ 128892 => true,
+ 128893 => true,
+ 128894 => true,
+ 128895 => true,
+ 128985 => true,
+ 128986 => true,
+ 128987 => true,
+ 128988 => true,
+ 128989 => true,
+ 128990 => true,
+ 128991 => true,
+ 129004 => true,
+ 129005 => true,
+ 129006 => true,
+ 129007 => true,
+ 129008 => true,
+ 129009 => true,
+ 129010 => true,
+ 129011 => true,
+ 129012 => true,
+ 129013 => true,
+ 129014 => true,
+ 129015 => true,
+ 129016 => true,
+ 129017 => true,
+ 129018 => true,
+ 129019 => true,
+ 129020 => true,
+ 129021 => true,
+ 129022 => true,
+ 129023 => true,
+ 129036 => true,
+ 129037 => true,
+ 129038 => true,
+ 129039 => true,
+ 129096 => true,
+ 129097 => true,
+ 129098 => true,
+ 129099 => true,
+ 129100 => true,
+ 129101 => true,
+ 129102 => true,
+ 129103 => true,
+ 129114 => true,
+ 129115 => true,
+ 129116 => true,
+ 129117 => true,
+ 129118 => true,
+ 129119 => true,
+ 129160 => true,
+ 129161 => true,
+ 129162 => true,
+ 129163 => true,
+ 129164 => true,
+ 129165 => true,
+ 129166 => true,
+ 129167 => true,
+ 129198 => true,
+ 129199 => true,
+ 129401 => true,
+ 129484 => true,
+ 129620 => true,
+ 129621 => true,
+ 129622 => true,
+ 129623 => true,
+ 129624 => true,
+ 129625 => true,
+ 129626 => true,
+ 129627 => true,
+ 129628 => true,
+ 129629 => true,
+ 129630 => true,
+ 129631 => true,
+ 129646 => true,
+ 129647 => true,
+ 129653 => true,
+ 129654 => true,
+ 129655 => true,
+ 129659 => true,
+ 129660 => true,
+ 129661 => true,
+ 129662 => true,
+ 129663 => true,
+ 129671 => true,
+ 129672 => true,
+ 129673 => true,
+ 129674 => true,
+ 129675 => true,
+ 129676 => true,
+ 129677 => true,
+ 129678 => true,
+ 129679 => true,
+ 129705 => true,
+ 129706 => true,
+ 129707 => true,
+ 129708 => true,
+ 129709 => true,
+ 129710 => true,
+ 129711 => true,
+ 129719 => true,
+ 129720 => true,
+ 129721 => true,
+ 129722 => true,
+ 129723 => true,
+ 129724 => true,
+ 129725 => true,
+ 129726 => true,
+ 129727 => true,
+ 129731 => true,
+ 129732 => true,
+ 129733 => true,
+ 129734 => true,
+ 129735 => true,
+ 129736 => true,
+ 129737 => true,
+ 129738 => true,
+ 129739 => true,
+ 129740 => true,
+ 129741 => true,
+ 129742 => true,
+ 129743 => true,
+ 129939 => true,
+ 131070 => true,
+ 131071 => true,
+ 177973 => true,
+ 177974 => true,
+ 177975 => true,
+ 177976 => true,
+ 177977 => true,
+ 177978 => true,
+ 177979 => true,
+ 177980 => true,
+ 177981 => true,
+ 177982 => true,
+ 177983 => true,
+ 178206 => true,
+ 178207 => true,
+ 183970 => true,
+ 183971 => true,
+ 183972 => true,
+ 183973 => true,
+ 183974 => true,
+ 183975 => true,
+ 183976 => true,
+ 183977 => true,
+ 183978 => true,
+ 183979 => true,
+ 183980 => true,
+ 183981 => true,
+ 183982 => true,
+ 183983 => true,
+ 194664 => true,
+ 194676 => true,
+ 194847 => true,
+ 194911 => true,
+ 195007 => true,
+ 196606 => true,
+ 196607 => true,
+ 262142 => true,
+ 262143 => true,
+ 327678 => true,
+ 327679 => true,
+ 393214 => true,
+ 393215 => true,
+ 458750 => true,
+ 458751 => true,
+ 524286 => true,
+ 524287 => true,
+ 589822 => true,
+ 589823 => true,
+ 655358 => true,
+ 655359 => true,
+ 720894 => true,
+ 720895 => true,
+ 786430 => true,
+ 786431 => true,
+ 851966 => true,
+ 851967 => true,
+ 917502 => true,
+ 917503 => true,
+ 917504 => true,
+ 917505 => true,
+ 917506 => true,
+ 917507 => true,
+ 917508 => true,
+ 917509 => true,
+ 917510 => true,
+ 917511 => true,
+ 917512 => true,
+ 917513 => true,
+ 917514 => true,
+ 917515 => true,
+ 917516 => true,
+ 917517 => true,
+ 917518 => true,
+ 917519 => true,
+ 917520 => true,
+ 917521 => true,
+ 917522 => true,
+ 917523 => true,
+ 917524 => true,
+ 917525 => true,
+ 917526 => true,
+ 917527 => true,
+ 917528 => true,
+ 917529 => true,
+ 917530 => true,
+ 917531 => true,
+ 917532 => true,
+ 917533 => true,
+ 917534 => true,
+ 917535 => true,
+ 983038 => true,
+ 983039 => true,
+ 1048574 => true,
+ 1048575 => true,
+ 1114110 => true,
+ 1114111 => true,
+);
diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php
new file mode 100755
index 0000000..54f21cc
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php
@@ -0,0 +1,308 @@
+<?php
+
+return array (
+ 160 => ' ',
+ 168 => ' ̈',
+ 175 => ' ̄',
+ 180 => ' ́',
+ 184 => ' ̧',
+ 728 => ' ̆',
+ 729 => ' ̇',
+ 730 => ' ̊',
+ 731 => ' ̨',
+ 732 => ' ̃',
+ 733 => ' ̋',
+ 890 => ' ι',
+ 894 => ';',
+ 900 => ' ́',
+ 901 => ' ̈́',
+ 8125 => ' ̓',
+ 8127 => ' ̓',
+ 8128 => ' ͂',
+ 8129 => ' ̈͂',
+ 8141 => ' ̓̀',
+ 8142 => ' ̓́',
+ 8143 => ' ̓͂',
+ 8157 => ' ̔̀',
+ 8158 => ' ̔́',
+ 8159 => ' ̔͂',
+ 8173 => ' ̈̀',
+ 8174 => ' ̈́',
+ 8175 => '`',
+ 8189 => ' ́',
+ 8190 => ' ̔',
+ 8192 => ' ',
+ 8193 => ' ',
+ 8194 => ' ',
+ 8195 => ' ',
+ 8196 => ' ',
+ 8197 => ' ',
+ 8198 => ' ',
+ 8199 => ' ',
+ 8200 => ' ',
+ 8201 => ' ',
+ 8202 => ' ',
+ 8215 => ' ̳',
+ 8239 => ' ',
+ 8252 => '!!',
+ 8254 => ' ̅',
+ 8263 => '??',
+ 8264 => '?!',
+ 8265 => '!?',
+ 8287 => ' ',
+ 8314 => '+',
+ 8316 => '=',
+ 8317 => '(',
+ 8318 => ')',
+ 8330 => '+',
+ 8332 => '=',
+ 8333 => '(',
+ 8334 => ')',
+ 8448 => 'a/c',
+ 8449 => 'a/s',
+ 8453 => 'c/o',
+ 8454 => 'c/u',
+ 9332 => '(1)',
+ 9333 => '(2)',
+ 9334 => '(3)',
+ 9335 => '(4)',
+ 9336 => '(5)',
+ 9337 => '(6)',
+ 9338 => '(7)',
+ 9339 => '(8)',
+ 9340 => '(9)',
+ 9341 => '(10)',
+ 9342 => '(11)',
+ 9343 => '(12)',
+ 9344 => '(13)',
+ 9345 => '(14)',
+ 9346 => '(15)',
+ 9347 => '(16)',
+ 9348 => '(17)',
+ 9349 => '(18)',
+ 9350 => '(19)',
+ 9351 => '(20)',
+ 9372 => '(a)',
+ 9373 => '(b)',
+ 9374 => '(c)',
+ 9375 => '(d)',
+ 9376 => '(e)',
+ 9377 => '(f)',
+ 9378 => '(g)',
+ 9379 => '(h)',
+ 9380 => '(i)',
+ 9381 => '(j)',
+ 9382 => '(k)',
+ 9383 => '(l)',
+ 9384 => '(m)',
+ 9385 => '(n)',
+ 9386 => '(o)',
+ 9387 => '(p)',
+ 9388 => '(q)',
+ 9389 => '(r)',
+ 9390 => '(s)',
+ 9391 => '(t)',
+ 9392 => '(u)',
+ 9393 => '(v)',
+ 9394 => '(w)',
+ 9395 => '(x)',
+ 9396 => '(y)',
+ 9397 => '(z)',
+ 10868 => '::=',
+ 10869 => '==',
+ 10870 => '===',
+ 12288 => ' ',
+ 12443 => ' ゙',
+ 12444 => ' ゚',
+ 12800 => '(ᄀ)',
+ 12801 => '(ᄂ)',
+ 12802 => '(ᄃ)',
+ 12803 => '(ᄅ)',
+ 12804 => '(ᄆ)',
+ 12805 => '(ᄇ)',
+ 12806 => '(ᄉ)',
+ 12807 => '(ᄋ)',
+ 12808 => '(ᄌ)',
+ 12809 => '(ᄎ)',
+ 12810 => '(ᄏ)',
+ 12811 => '(ᄐ)',
+ 12812 => '(ᄑ)',
+ 12813 => '(ᄒ)',
+ 12814 => '(가)',
+ 12815 => '(나)',
+ 12816 => '(다)',
+ 12817 => '(라)',
+ 12818 => '(마)',
+ 12819 => '(바)',
+ 12820 => '(사)',
+ 12821 => '(아)',
+ 12822 => '(자)',
+ 12823 => '(차)',
+ 12824 => '(카)',
+ 12825 => '(타)',
+ 12826 => '(파)',
+ 12827 => '(하)',
+ 12828 => '(주)',
+ 12829 => '(오전)',
+ 12830 => '(오후)',
+ 12832 => '(一)',
+ 12833 => '(二)',
+ 12834 => '(三)',
+ 12835 => '(四)',
+ 12836 => '(五)',
+ 12837 => '(六)',
+ 12838 => '(七)',
+ 12839 => '(八)',
+ 12840 => '(九)',
+ 12841 => '(十)',
+ 12842 => '(月)',
+ 12843 => '(火)',
+ 12844 => '(水)',
+ 12845 => '(木)',
+ 12846 => '(金)',
+ 12847 => '(土)',
+ 12848 => '(日)',
+ 12849 => '(株)',
+ 12850 => '(有)',
+ 12851 => '(社)',
+ 12852 => '(名)',
+ 12853 => '(特)',
+ 12854 => '(財)',
+ 12855 => '(祝)',
+ 12856 => '(労)',
+ 12857 => '(代)',
+ 12858 => '(呼)',
+ 12859 => '(学)',
+ 12860 => '(監)',
+ 12861 => '(企)',
+ 12862 => '(資)',
+ 12863 => '(協)',
+ 12864 => '(祭)',
+ 12865 => '(休)',
+ 12866 => '(自)',
+ 12867 => '(至)',
+ 64297 => '+',
+ 64606 => ' ٌّ',
+ 64607 => ' ٍّ',
+ 64608 => ' َّ',
+ 64609 => ' ُّ',
+ 64610 => ' ِّ',
+ 64611 => ' ّٰ',
+ 65018 => 'صلى الله عليه وسلم',
+ 65019 => 'جل جلاله',
+ 65040 => ',',
+ 65043 => ':',
+ 65044 => ';',
+ 65045 => '!',
+ 65046 => '?',
+ 65075 => '_',
+ 65076 => '_',
+ 65077 => '(',
+ 65078 => ')',
+ 65079 => '{',
+ 65080 => '}',
+ 65095 => '[',
+ 65096 => ']',
+ 65097 => ' ̅',
+ 65098 => ' ̅',
+ 65099 => ' ̅',
+ 65100 => ' ̅',
+ 65101 => '_',
+ 65102 => '_',
+ 65103 => '_',
+ 65104 => ',',
+ 65108 => ';',
+ 65109 => ':',
+ 65110 => '?',
+ 65111 => '!',
+ 65113 => '(',
+ 65114 => ')',
+ 65115 => '{',
+ 65116 => '}',
+ 65119 => '#',
+ 65120 => '&',
+ 65121 => '*',
+ 65122 => '+',
+ 65124 => '<',
+ 65125 => '>',
+ 65126 => '=',
+ 65128 => '\\',
+ 65129 => '$',
+ 65130 => '%',
+ 65131 => '@',
+ 65136 => ' ً',
+ 65138 => ' ٌ',
+ 65140 => ' ٍ',
+ 65142 => ' َ',
+ 65144 => ' ُ',
+ 65146 => ' ِ',
+ 65148 => ' ّ',
+ 65150 => ' ْ',
+ 65281 => '!',
+ 65282 => '"',
+ 65283 => '#',
+ 65284 => '$',
+ 65285 => '%',
+ 65286 => '&',
+ 65287 => '\'',
+ 65288 => '(',
+ 65289 => ')',
+ 65290 => '*',
+ 65291 => '+',
+ 65292 => ',',
+ 65295 => '/',
+ 65306 => ':',
+ 65307 => ';',
+ 65308 => '<',
+ 65309 => '=',
+ 65310 => '>',
+ 65311 => '?',
+ 65312 => '@',
+ 65339 => '[',
+ 65340 => '\\',
+ 65341 => ']',
+ 65342 => '^',
+ 65343 => '_',
+ 65344 => '`',
+ 65371 => '{',
+ 65372 => '|',
+ 65373 => '}',
+ 65374 => '~',
+ 65507 => ' ̄',
+ 127233 => '0,',
+ 127234 => '1,',
+ 127235 => '2,',
+ 127236 => '3,',
+ 127237 => '4,',
+ 127238 => '5,',
+ 127239 => '6,',
+ 127240 => '7,',
+ 127241 => '8,',
+ 127242 => '9,',
+ 127248 => '(a)',
+ 127249 => '(b)',
+ 127250 => '(c)',
+ 127251 => '(d)',
+ 127252 => '(e)',
+ 127253 => '(f)',
+ 127254 => '(g)',
+ 127255 => '(h)',
+ 127256 => '(i)',
+ 127257 => '(j)',
+ 127258 => '(k)',
+ 127259 => '(l)',
+ 127260 => '(m)',
+ 127261 => '(n)',
+ 127262 => '(o)',
+ 127263 => '(p)',
+ 127264 => '(q)',
+ 127265 => '(r)',
+ 127266 => '(s)',
+ 127267 => '(t)',
+ 127268 => '(u)',
+ 127269 => '(v)',
+ 127270 => '(w)',
+ 127271 => '(x)',
+ 127272 => '(y)',
+ 127273 => '(z)',
+);
diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php
new file mode 100755
index 0000000..223396e
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php
@@ -0,0 +1,71 @@
+<?php
+
+return array (
+ 0 => true,
+ 1 => true,
+ 2 => true,
+ 3 => true,
+ 4 => true,
+ 5 => true,
+ 6 => true,
+ 7 => true,
+ 8 => true,
+ 9 => true,
+ 10 => true,
+ 11 => true,
+ 12 => true,
+ 13 => true,
+ 14 => true,
+ 15 => true,
+ 16 => true,
+ 17 => true,
+ 18 => true,
+ 19 => true,
+ 20 => true,
+ 21 => true,
+ 22 => true,
+ 23 => true,
+ 24 => true,
+ 25 => true,
+ 26 => true,
+ 27 => true,
+ 28 => true,
+ 29 => true,
+ 30 => true,
+ 31 => true,
+ 32 => true,
+ 33 => true,
+ 34 => true,
+ 35 => true,
+ 36 => true,
+ 37 => true,
+ 38 => true,
+ 39 => true,
+ 40 => true,
+ 41 => true,
+ 42 => true,
+ 43 => true,
+ 44 => true,
+ 47 => true,
+ 58 => true,
+ 59 => true,
+ 60 => true,
+ 61 => true,
+ 62 => true,
+ 63 => true,
+ 64 => true,
+ 91 => true,
+ 92 => true,
+ 93 => true,
+ 94 => true,
+ 95 => true,
+ 96 => true,
+ 123 => true,
+ 124 => true,
+ 125 => true,
+ 126 => true,
+ 127 => true,
+ 8800 => true,
+ 8814 => true,
+ 8815 => true,
+);
diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/ignored.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/ignored.php
new file mode 100755
index 0000000..b377844
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/ignored.php
@@ -0,0 +1,273 @@
+<?php
+
+return array (
+ 173 => true,
+ 847 => true,
+ 6155 => true,
+ 6156 => true,
+ 6157 => true,
+ 8203 => true,
+ 8288 => true,
+ 8292 => true,
+ 65024 => true,
+ 65025 => true,
+ 65026 => true,
+ 65027 => true,
+ 65028 => true,
+ 65029 => true,
+ 65030 => true,
+ 65031 => true,
+ 65032 => true,
+ 65033 => true,
+ 65034 => true,
+ 65035 => true,
+ 65036 => true,
+ 65037 => true,
+ 65038 => true,
+ 65039 => true,
+ 65279 => true,
+ 113824 => true,
+ 113825 => true,
+ 113826 => true,
+ 113827 => true,
+ 917760 => true,
+ 917761 => true,
+ 917762 => true,
+ 917763 => true,
+ 917764 => true,
+ 917765 => true,
+ 917766 => true,
+ 917767 => true,
+ 917768 => true,
+ 917769 => true,
+ 917770 => true,
+ 917771 => true,
+ 917772 => true,
+ 917773 => true,
+ 917774 => true,
+ 917775 => true,
+ 917776 => true,
+ 917777 => true,
+ 917778 => true,
+ 917779 => true,
+ 917780 => true,
+ 917781 => true,
+ 917782 => true,
+ 917783 => true,
+ 917784 => true,
+ 917785 => true,
+ 917786 => true,
+ 917787 => true,
+ 917788 => true,
+ 917789 => true,
+ 917790 => true,
+ 917791 => true,
+ 917792 => true,
+ 917793 => true,
+ 917794 => true,
+ 917795 => true,
+ 917796 => true,
+ 917797 => true,
+ 917798 => true,
+ 917799 => true,
+ 917800 => true,
+ 917801 => true,
+ 917802 => true,
+ 917803 => true,
+ 917804 => true,
+ 917805 => true,
+ 917806 => true,
+ 917807 => true,
+ 917808 => true,
+ 917809 => true,
+ 917810 => true,
+ 917811 => true,
+ 917812 => true,
+ 917813 => true,
+ 917814 => true,
+ 917815 => true,
+ 917816 => true,
+ 917817 => true,
+ 917818 => true,
+ 917819 => true,
+ 917820 => true,
+ 917821 => true,
+ 917822 => true,
+ 917823 => true,
+ 917824 => true,
+ 917825 => true,
+ 917826 => true,
+ 917827 => true,
+ 917828 => true,
+ 917829 => true,
+ 917830 => true,
+ 917831 => true,
+ 917832 => true,
+ 917833 => true,
+ 917834 => true,
+ 917835 => true,
+ 917836 => true,
+ 917837 => true,
+ 917838 => true,
+ 917839 => true,
+ 917840 => true,
+ 917841 => true,
+ 917842 => true,
+ 917843 => true,
+ 917844 => true,
+ 917845 => true,
+ 917846 => true,
+ 917847 => true,
+ 917848 => true,
+ 917849 => true,
+ 917850 => true,
+ 917851 => true,
+ 917852 => true,
+ 917853 => true,
+ 917854 => true,
+ 917855 => true,
+ 917856 => true,
+ 917857 => true,
+ 917858 => true,
+ 917859 => true,
+ 917860 => true,
+ 917861 => true,
+ 917862 => true,
+ 917863 => true,
+ 917864 => true,
+ 917865 => true,
+ 917866 => true,
+ 917867 => true,
+ 917868 => true,
+ 917869 => true,
+ 917870 => true,
+ 917871 => true,
+ 917872 => true,
+ 917873 => true,
+ 917874 => true,
+ 917875 => true,
+ 917876 => true,
+ 917877 => true,
+ 917878 => true,
+ 917879 => true,
+ 917880 => true,
+ 917881 => true,
+ 917882 => true,
+ 917883 => true,
+ 917884 => true,
+ 917885 => true,
+ 917886 => true,
+ 917887 => true,
+ 917888 => true,
+ 917889 => true,
+ 917890 => true,
+ 917891 => true,
+ 917892 => true,
+ 917893 => true,
+ 917894 => true,
+ 917895 => true,
+ 917896 => true,
+ 917897 => true,
+ 917898 => true,
+ 917899 => true,
+ 917900 => true,
+ 917901 => true,
+ 917902 => true,
+ 917903 => true,
+ 917904 => true,
+ 917905 => true,
+ 917906 => true,
+ 917907 => true,
+ 917908 => true,
+ 917909 => true,
+ 917910 => true,
+ 917911 => true,
+ 917912 => true,
+ 917913 => true,
+ 917914 => true,
+ 917915 => true,
+ 917916 => true,
+ 917917 => true,
+ 917918 => true,
+ 917919 => true,
+ 917920 => true,
+ 917921 => true,
+ 917922 => true,
+ 917923 => true,
+ 917924 => true,
+ 917925 => true,
+ 917926 => true,
+ 917927 => true,
+ 917928 => true,
+ 917929 => true,
+ 917930 => true,
+ 917931 => true,
+ 917932 => true,
+ 917933 => true,
+ 917934 => true,
+ 917935 => true,
+ 917936 => true,
+ 917937 => true,
+ 917938 => true,
+ 917939 => true,
+ 917940 => true,
+ 917941 => true,
+ 917942 => true,
+ 917943 => true,
+ 917944 => true,
+ 917945 => true,
+ 917946 => true,
+ 917947 => true,
+ 917948 => true,
+ 917949 => true,
+ 917950 => true,
+ 917951 => true,
+ 917952 => true,
+ 917953 => true,
+ 917954 => true,
+ 917955 => true,
+ 917956 => true,
+ 917957 => true,
+ 917958 => true,
+ 917959 => true,
+ 917960 => true,
+ 917961 => true,
+ 917962 => true,
+ 917963 => true,
+ 917964 => true,
+ 917965 => true,
+ 917966 => true,
+ 917967 => true,
+ 917968 => true,
+ 917969 => true,
+ 917970 => true,
+ 917971 => true,
+ 917972 => true,
+ 917973 => true,
+ 917974 => true,
+ 917975 => true,
+ 917976 => true,
+ 917977 => true,
+ 917978 => true,
+ 917979 => true,
+ 917980 => true,
+ 917981 => true,
+ 917982 => true,
+ 917983 => true,
+ 917984 => true,
+ 917985 => true,
+ 917986 => true,
+ 917987 => true,
+ 917988 => true,
+ 917989 => true,
+ 917990 => true,
+ 917991 => true,
+ 917992 => true,
+ 917993 => true,
+ 917994 => true,
+ 917995 => true,
+ 917996 => true,
+ 917997 => true,
+ 917998 => true,
+ 917999 => true,
+);
diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/mapped.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/mapped.php
new file mode 100755
index 0000000..9b85fe9
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/mapped.php
@@ -0,0 +1,5778 @@
+<?php
+
+return array (
+ 65 => 'a',
+ 66 => 'b',
+ 67 => 'c',
+ 68 => 'd',
+ 69 => 'e',
+ 70 => 'f',
+ 71 => 'g',
+ 72 => 'h',
+ 73 => 'i',
+ 74 => 'j',
+ 75 => 'k',
+ 76 => 'l',
+ 77 => 'm',
+ 78 => 'n',
+ 79 => 'o',
+ 80 => 'p',
+ 81 => 'q',
+ 82 => 'r',
+ 83 => 's',
+ 84 => 't',
+ 85 => 'u',
+ 86 => 'v',
+ 87 => 'w',
+ 88 => 'x',
+ 89 => 'y',
+ 90 => 'z',
+ 170 => 'a',
+ 178 => '2',
+ 179 => '3',
+ 181 => 'μ',
+ 185 => '1',
+ 186 => 'o',
+ 188 => '1⁄4',
+ 189 => '1⁄2',
+ 190 => '3⁄4',
+ 192 => 'à',
+ 193 => 'á',
+ 194 => 'â',
+ 195 => 'ã',
+ 196 => 'ä',
+ 197 => 'å',
+ 198 => 'æ',
+ 199 => 'ç',
+ 200 => 'è',
+ 201 => 'é',
+ 202 => 'ê',
+ 203 => 'ë',
+ 204 => 'ì',
+ 205 => 'í',
+ 206 => 'î',
+ 207 => 'ï',
+ 208 => 'ð',
+ 209 => 'ñ',
+ 210 => 'ò',
+ 211 => 'ó',
+ 212 => 'ô',
+ 213 => 'õ',
+ 214 => 'ö',
+ 216 => 'ø',
+ 217 => 'ù',
+ 218 => 'ú',
+ 219 => 'û',
+ 220 => 'ü',
+ 221 => 'ý',
+ 222 => 'þ',
+ 256 => 'ā',
+ 258 => 'ă',
+ 260 => 'ą',
+ 262 => 'ć',
+ 264 => 'ĉ',
+ 266 => 'ċ',
+ 268 => 'č',
+ 270 => 'ď',
+ 272 => 'đ',
+ 274 => 'ē',
+ 276 => 'ĕ',
+ 278 => 'ė',
+ 280 => 'ę',
+ 282 => 'ě',
+ 284 => 'ĝ',
+ 286 => 'ğ',
+ 288 => 'ġ',
+ 290 => 'ģ',
+ 292 => 'ĥ',
+ 294 => 'ħ',
+ 296 => 'ĩ',
+ 298 => 'ī',
+ 300 => 'ĭ',
+ 302 => 'į',
+ 304 => 'i̇',
+ 306 => 'ij',
+ 307 => 'ij',
+ 308 => 'ĵ',
+ 310 => 'ķ',
+ 313 => 'ĺ',
+ 315 => 'ļ',
+ 317 => 'ľ',
+ 319 => 'l·',
+ 320 => 'l·',
+ 321 => 'ł',
+ 323 => 'ń',
+ 325 => 'ņ',
+ 327 => 'ň',
+ 329 => 'ʼn',
+ 330 => 'ŋ',
+ 332 => 'ō',
+ 334 => 'ŏ',
+ 336 => 'ő',
+ 338 => 'œ',
+ 340 => 'ŕ',
+ 342 => 'ŗ',
+ 344 => 'ř',
+ 346 => 'ś',
+ 348 => 'ŝ',
+ 350 => 'ş',
+ 352 => 'š',
+ 354 => 'ţ',
+ 356 => 'ť',
+ 358 => 'ŧ',
+ 360 => 'ũ',
+ 362 => 'ū',
+ 364 => 'ŭ',
+ 366 => 'ů',
+ 368 => 'ű',
+ 370 => 'ų',
+ 372 => 'ŵ',
+ 374 => 'ŷ',
+ 376 => 'ÿ',
+ 377 => 'ź',
+ 379 => 'ż',
+ 381 => 'ž',
+ 383 => 's',
+ 385 => 'ɓ',
+ 386 => 'ƃ',
+ 388 => 'ƅ',
+ 390 => 'ɔ',
+ 391 => 'ƈ',
+ 393 => 'ɖ',
+ 394 => 'ɗ',
+ 395 => 'ƌ',
+ 398 => 'ǝ',
+ 399 => 'ə',
+ 400 => 'ɛ',
+ 401 => 'ƒ',
+ 403 => 'ɠ',
+ 404 => 'ɣ',
+ 406 => 'ɩ',
+ 407 => 'ɨ',
+ 408 => 'ƙ',
+ 412 => 'ɯ',
+ 413 => 'ɲ',
+ 415 => 'ɵ',
+ 416 => 'ơ',
+ 418 => 'ƣ',
+ 420 => 'ƥ',
+ 422 => 'ʀ',
+ 423 => 'ƨ',
+ 425 => 'ʃ',
+ 428 => 'ƭ',
+ 430 => 'ʈ',
+ 431 => 'ư',
+ 433 => 'ʊ',
+ 434 => 'ʋ',
+ 435 => 'ƴ',
+ 437 => 'ƶ',
+ 439 => 'ʒ',
+ 440 => 'ƹ',
+ 444 => 'ƽ',
+ 452 => 'dž',
+ 453 => 'dž',
+ 454 => 'dž',
+ 455 => 'lj',
+ 456 => 'lj',
+ 457 => 'lj',
+ 458 => 'nj',
+ 459 => 'nj',
+ 460 => 'nj',
+ 461 => 'ǎ',
+ 463 => 'ǐ',
+ 465 => 'ǒ',
+ 467 => 'ǔ',
+ 469 => 'ǖ',
+ 471 => 'ǘ',
+ 473 => 'ǚ',
+ 475 => 'ǜ',
+ 478 => 'ǟ',
+ 480 => 'ǡ',
+ 482 => 'ǣ',
+ 484 => 'ǥ',
+ 486 => 'ǧ',
+ 488 => 'ǩ',
+ 490 => 'ǫ',
+ 492 => 'ǭ',
+ 494 => 'ǯ',
+ 497 => 'dz',
+ 498 => 'dz',
+ 499 => 'dz',
+ 500 => 'ǵ',
+ 502 => 'ƕ',
+ 503 => 'ƿ',
+ 504 => 'ǹ',
+ 506 => 'ǻ',
+ 508 => 'ǽ',
+ 510 => 'ǿ',
+ 512 => 'ȁ',
+ 514 => 'ȃ',
+ 516 => 'ȅ',
+ 518 => 'ȇ',
+ 520 => 'ȉ',
+ 522 => 'ȋ',
+ 524 => 'ȍ',
+ 526 => 'ȏ',
+ 528 => 'ȑ',
+ 530 => 'ȓ',
+ 532 => 'ȕ',
+ 534 => 'ȗ',
+ 536 => 'ș',
+ 538 => 'ț',
+ 540 => 'ȝ',
+ 542 => 'ȟ',
+ 544 => 'ƞ',
+ 546 => 'ȣ',
+ 548 => 'ȥ',
+ 550 => 'ȧ',
+ 552 => 'ȩ',
+ 554 => 'ȫ',
+ 556 => 'ȭ',
+ 558 => 'ȯ',
+ 560 => 'ȱ',
+ 562 => 'ȳ',
+ 570 => 'ⱥ',
+ 571 => 'ȼ',
+ 573 => 'ƚ',
+ 574 => 'ⱦ',
+ 577 => 'ɂ',
+ 579 => 'ƀ',
+ 580 => 'ʉ',
+ 581 => 'ʌ',
+ 582 => 'ɇ',
+ 584 => 'ɉ',
+ 586 => 'ɋ',
+ 588 => 'ɍ',
+ 590 => 'ɏ',
+ 688 => 'h',
+ 689 => 'ɦ',
+ 690 => 'j',
+ 691 => 'r',
+ 692 => 'ɹ',
+ 693 => 'ɻ',
+ 694 => 'ʁ',
+ 695 => 'w',
+ 696 => 'y',
+ 736 => 'ɣ',
+ 737 => 'l',
+ 738 => 's',
+ 739 => 'x',
+ 740 => 'ʕ',
+ 832 => '̀',
+ 833 => '́',
+ 835 => '̓',
+ 836 => '̈́',
+ 837 => 'ι',
+ 880 => 'ͱ',
+ 882 => 'ͳ',
+ 884 => 'ʹ',
+ 886 => 'ͷ',
+ 895 => 'ϳ',
+ 902 => 'ά',
+ 903 => '·',
+ 904 => 'έ',
+ 905 => 'ή',
+ 906 => 'ί',
+ 908 => 'ό',
+ 910 => 'ύ',
+ 911 => 'ώ',
+ 913 => 'α',
+ 914 => 'β',
+ 915 => 'γ',
+ 916 => 'δ',
+ 917 => 'ε',
+ 918 => 'ζ',
+ 919 => 'η',
+ 920 => 'θ',
+ 921 => 'ι',
+ 922 => 'κ',
+ 923 => 'λ',
+ 924 => 'μ',
+ 925 => 'ν',
+ 926 => 'ξ',
+ 927 => 'ο',
+ 928 => 'π',
+ 929 => 'ρ',
+ 931 => 'σ',
+ 932 => 'τ',
+ 933 => 'υ',
+ 934 => 'φ',
+ 935 => 'χ',
+ 936 => 'ψ',
+ 937 => 'ω',
+ 938 => 'ϊ',
+ 939 => 'ϋ',
+ 975 => 'ϗ',
+ 976 => 'β',
+ 977 => 'θ',
+ 978 => 'υ',
+ 979 => 'ύ',
+ 980 => 'ϋ',
+ 981 => 'φ',
+ 982 => 'π',
+ 984 => 'ϙ',
+ 986 => 'ϛ',
+ 988 => 'ϝ',
+ 990 => 'ϟ',
+ 992 => 'ϡ',
+ 994 => 'ϣ',
+ 996 => 'ϥ',
+ 998 => 'ϧ',
+ 1000 => 'ϩ',
+ 1002 => 'ϫ',
+ 1004 => 'ϭ',
+ 1006 => 'ϯ',
+ 1008 => 'κ',
+ 1009 => 'ρ',
+ 1010 => 'σ',
+ 1012 => 'θ',
+ 1013 => 'ε',
+ 1015 => 'ϸ',
+ 1017 => 'σ',
+ 1018 => 'ϻ',
+ 1021 => 'ͻ',
+ 1022 => 'ͼ',
+ 1023 => 'ͽ',
+ 1024 => 'ѐ',
+ 1025 => 'ё',
+ 1026 => 'ђ',
+ 1027 => 'ѓ',
+ 1028 => 'є',
+ 1029 => 'ѕ',
+ 1030 => 'і',
+ 1031 => 'ї',
+ 1032 => 'ј',
+ 1033 => 'љ',
+ 1034 => 'њ',
+ 1035 => 'ћ',
+ 1036 => 'ќ',
+ 1037 => 'ѝ',
+ 1038 => 'ў',
+ 1039 => 'џ',
+ 1040 => 'а',
+ 1041 => 'б',
+ 1042 => 'в',
+ 1043 => 'г',
+ 1044 => 'д',
+ 1045 => 'е',
+ 1046 => 'ж',
+ 1047 => 'з',
+ 1048 => 'и',
+ 1049 => 'й',
+ 1050 => 'к',
+ 1051 => 'л',
+ 1052 => 'м',
+ 1053 => 'н',
+ 1054 => 'о',
+ 1055 => 'п',
+ 1056 => 'р',
+ 1057 => 'с',
+ 1058 => 'т',
+ 1059 => 'у',
+ 1060 => 'ф',
+ 1061 => 'х',
+ 1062 => 'ц',
+ 1063 => 'ч',
+ 1064 => 'ш',
+ 1065 => 'щ',
+ 1066 => 'ъ',
+ 1067 => 'ы',
+ 1068 => 'ь',
+ 1069 => 'э',
+ 1070 => 'ю',
+ 1071 => 'я',
+ 1120 => 'ѡ',
+ 1122 => 'ѣ',
+ 1124 => 'ѥ',
+ 1126 => 'ѧ',
+ 1128 => 'ѩ',
+ 1130 => 'ѫ',
+ 1132 => 'ѭ',
+ 1134 => 'ѯ',
+ 1136 => 'ѱ',
+ 1138 => 'ѳ',
+ 1140 => 'ѵ',
+ 1142 => 'ѷ',
+ 1144 => 'ѹ',
+ 1146 => 'ѻ',
+ 1148 => 'ѽ',
+ 1150 => 'ѿ',
+ 1152 => 'ҁ',
+ 1162 => 'ҋ',
+ 1164 => 'ҍ',
+ 1166 => 'ҏ',
+ 1168 => 'ґ',
+ 1170 => 'ғ',
+ 1172 => 'ҕ',
+ 1174 => 'җ',
+ 1176 => 'ҙ',
+ 1178 => 'қ',
+ 1180 => 'ҝ',
+ 1182 => 'ҟ',
+ 1184 => 'ҡ',
+ 1186 => 'ң',
+ 1188 => 'ҥ',
+ 1190 => 'ҧ',
+ 1192 => 'ҩ',
+ 1194 => 'ҫ',
+ 1196 => 'ҭ',
+ 1198 => 'ү',
+ 1200 => 'ұ',
+ 1202 => 'ҳ',
+ 1204 => 'ҵ',
+ 1206 => 'ҷ',
+ 1208 => 'ҹ',
+ 1210 => 'һ',
+ 1212 => 'ҽ',
+ 1214 => 'ҿ',
+ 1217 => 'ӂ',
+ 1219 => 'ӄ',
+ 1221 => 'ӆ',
+ 1223 => 'ӈ',
+ 1225 => 'ӊ',
+ 1227 => 'ӌ',
+ 1229 => 'ӎ',
+ 1232 => 'ӑ',
+ 1234 => 'ӓ',
+ 1236 => 'ӕ',
+ 1238 => 'ӗ',
+ 1240 => 'ә',
+ 1242 => 'ӛ',
+ 1244 => 'ӝ',
+ 1246 => 'ӟ',
+ 1248 => 'ӡ',
+ 1250 => 'ӣ',
+ 1252 => 'ӥ',
+ 1254 => 'ӧ',
+ 1256 => 'ө',
+ 1258 => 'ӫ',
+ 1260 => 'ӭ',
+ 1262 => 'ӯ',
+ 1264 => 'ӱ',
+ 1266 => 'ӳ',
+ 1268 => 'ӵ',
+ 1270 => 'ӷ',
+ 1272 => 'ӹ',
+ 1274 => 'ӻ',
+ 1276 => 'ӽ',
+ 1278 => 'ӿ',
+ 1280 => 'ԁ',
+ 1282 => 'ԃ',
+ 1284 => 'ԅ',
+ 1286 => 'ԇ',
+ 1288 => 'ԉ',
+ 1290 => 'ԋ',
+ 1292 => 'ԍ',
+ 1294 => 'ԏ',
+ 1296 => 'ԑ',
+ 1298 => 'ԓ',
+ 1300 => 'ԕ',
+ 1302 => 'ԗ',
+ 1304 => 'ԙ',
+ 1306 => 'ԛ',
+ 1308 => 'ԝ',
+ 1310 => 'ԟ',
+ 1312 => 'ԡ',
+ 1314 => 'ԣ',
+ 1316 => 'ԥ',
+ 1318 => 'ԧ',
+ 1320 => 'ԩ',
+ 1322 => 'ԫ',
+ 1324 => 'ԭ',
+ 1326 => 'ԯ',
+ 1329 => 'ա',
+ 1330 => 'բ',
+ 1331 => 'գ',
+ 1332 => 'դ',
+ 1333 => 'ե',
+ 1334 => 'զ',
+ 1335 => 'է',
+ 1336 => 'ը',
+ 1337 => 'թ',
+ 1338 => 'ժ',
+ 1339 => 'ի',
+ 1340 => 'լ',
+ 1341 => 'խ',
+ 1342 => 'ծ',
+ 1343 => 'կ',
+ 1344 => 'հ',
+ 1345 => 'ձ',
+ 1346 => 'ղ',
+ 1347 => 'ճ',
+ 1348 => 'մ',
+ 1349 => 'յ',
+ 1350 => 'ն',
+ 1351 => 'շ',
+ 1352 => 'ո',
+ 1353 => 'չ',
+ 1354 => 'պ',
+ 1355 => 'ջ',
+ 1356 => 'ռ',
+ 1357 => 'ս',
+ 1358 => 'վ',
+ 1359 => 'տ',
+ 1360 => 'ր',
+ 1361 => 'ց',
+ 1362 => 'ւ',
+ 1363 => 'փ',
+ 1364 => 'ք',
+ 1365 => 'օ',
+ 1366 => 'ֆ',
+ 1415 => 'եւ',
+ 1653 => 'اٴ',
+ 1654 => 'وٴ',
+ 1655 => 'ۇٴ',
+ 1656 => 'يٴ',
+ 2392 => 'क़',
+ 2393 => 'ख़',
+ 2394 => 'ग़',
+ 2395 => 'ज़',
+ 2396 => 'ड़',
+ 2397 => 'ढ़',
+ 2398 => 'फ़',
+ 2399 => 'य़',
+ 2524 => 'ড়',
+ 2525 => 'ঢ়',
+ 2527 => 'য়',
+ 2611 => 'ਲ਼',
+ 2614 => 'ਸ਼',
+ 2649 => 'ਖ਼',
+ 2650 => 'ਗ਼',
+ 2651 => 'ਜ਼',
+ 2654 => 'ਫ਼',
+ 2908 => 'ଡ଼',
+ 2909 => 'ଢ଼',
+ 3635 => 'ํา',
+ 3763 => 'ໍາ',
+ 3804 => 'ຫນ',
+ 3805 => 'ຫມ',
+ 3852 => '་',
+ 3907 => 'གྷ',
+ 3917 => 'ཌྷ',
+ 3922 => 'དྷ',
+ 3927 => 'བྷ',
+ 3932 => 'ཛྷ',
+ 3945 => 'ཀྵ',
+ 3955 => 'ཱི',
+ 3957 => 'ཱུ',
+ 3958 => 'ྲྀ',
+ 3959 => 'ྲཱྀ',
+ 3960 => 'ླྀ',
+ 3961 => 'ླཱྀ',
+ 3969 => 'ཱྀ',
+ 3987 => 'ྒྷ',
+ 3997 => 'ྜྷ',
+ 4002 => 'ྡྷ',
+ 4007 => 'ྦྷ',
+ 4012 => 'ྫྷ',
+ 4025 => 'ྐྵ',
+ 4295 => 'ⴧ',
+ 4301 => 'ⴭ',
+ 4348 => 'ნ',
+ 5112 => 'Ᏸ',
+ 5113 => 'Ᏹ',
+ 5114 => 'Ᏺ',
+ 5115 => 'Ᏻ',
+ 5116 => 'Ᏼ',
+ 5117 => 'Ᏽ',
+ 7296 => 'в',
+ 7297 => 'д',
+ 7298 => 'о',
+ 7299 => 'с',
+ 7300 => 'т',
+ 7301 => 'т',
+ 7302 => 'ъ',
+ 7303 => 'ѣ',
+ 7304 => 'ꙋ',
+ 7312 => 'ა',
+ 7313 => 'ბ',
+ 7314 => 'გ',
+ 7315 => 'დ',
+ 7316 => 'ე',
+ 7317 => 'ვ',
+ 7318 => 'ზ',
+ 7319 => 'თ',
+ 7320 => 'ი',
+ 7321 => 'კ',
+ 7322 => 'ლ',
+ 7323 => 'მ',
+ 7324 => 'ნ',
+ 7325 => 'ო',
+ 7326 => 'პ',
+ 7327 => 'ჟ',
+ 7328 => 'რ',
+ 7329 => 'ს',
+ 7330 => 'ტ',
+ 7331 => 'უ',
+ 7332 => 'ფ',
+ 7333 => 'ქ',
+ 7334 => 'ღ',
+ 7335 => 'ყ',
+ 7336 => 'შ',
+ 7337 => 'ჩ',
+ 7338 => 'ც',
+ 7339 => 'ძ',
+ 7340 => 'წ',
+ 7341 => 'ჭ',
+ 7342 => 'ხ',
+ 7343 => 'ჯ',
+ 7344 => 'ჰ',
+ 7345 => 'ჱ',
+ 7346 => 'ჲ',
+ 7347 => 'ჳ',
+ 7348 => 'ჴ',
+ 7349 => 'ჵ',
+ 7350 => 'ჶ',
+ 7351 => 'ჷ',
+ 7352 => 'ჸ',
+ 7353 => 'ჹ',
+ 7354 => 'ჺ',
+ 7357 => 'ჽ',
+ 7358 => 'ჾ',
+ 7359 => 'ჿ',
+ 7468 => 'a',
+ 7469 => 'æ',
+ 7470 => 'b',
+ 7472 => 'd',
+ 7473 => 'e',
+ 7474 => 'ǝ',
+ 7475 => 'g',
+ 7476 => 'h',
+ 7477 => 'i',
+ 7478 => 'j',
+ 7479 => 'k',
+ 7480 => 'l',
+ 7481 => 'm',
+ 7482 => 'n',
+ 7484 => 'o',
+ 7485 => 'ȣ',
+ 7486 => 'p',
+ 7487 => 'r',
+ 7488 => 't',
+ 7489 => 'u',
+ 7490 => 'w',
+ 7491 => 'a',
+ 7492 => 'ɐ',
+ 7493 => 'ɑ',
+ 7494 => 'ᴂ',
+ 7495 => 'b',
+ 7496 => 'd',
+ 7497 => 'e',
+ 7498 => 'ə',
+ 7499 => 'ɛ',
+ 7500 => 'ɜ',
+ 7501 => 'g',
+ 7503 => 'k',
+ 7504 => 'm',
+ 7505 => 'ŋ',
+ 7506 => 'o',
+ 7507 => 'ɔ',
+ 7508 => 'ᴖ',
+ 7509 => 'ᴗ',
+ 7510 => 'p',
+ 7511 => 't',
+ 7512 => 'u',
+ 7513 => 'ᴝ',
+ 7514 => 'ɯ',
+ 7515 => 'v',
+ 7516 => 'ᴥ',
+ 7517 => 'β',
+ 7518 => 'γ',
+ 7519 => 'δ',
+ 7520 => 'φ',
+ 7521 => 'χ',
+ 7522 => 'i',
+ 7523 => 'r',
+ 7524 => 'u',
+ 7525 => 'v',
+ 7526 => 'β',
+ 7527 => 'γ',
+ 7528 => 'ρ',
+ 7529 => 'φ',
+ 7530 => 'χ',
+ 7544 => 'н',
+ 7579 => 'ɒ',
+ 7580 => 'c',
+ 7581 => 'ɕ',
+ 7582 => 'ð',
+ 7583 => 'ɜ',
+ 7584 => 'f',
+ 7585 => 'ɟ',
+ 7586 => 'ɡ',
+ 7587 => 'ɥ',
+ 7588 => 'ɨ',
+ 7589 => 'ɩ',
+ 7590 => 'ɪ',
+ 7591 => 'ᵻ',
+ 7592 => 'ʝ',
+ 7593 => 'ɭ',
+ 7594 => 'ᶅ',
+ 7595 => 'ʟ',
+ 7596 => 'ɱ',
+ 7597 => 'ɰ',
+ 7598 => 'ɲ',
+ 7599 => 'ɳ',
+ 7600 => 'ɴ',
+ 7601 => 'ɵ',
+ 7602 => 'ɸ',
+ 7603 => 'ʂ',
+ 7604 => 'ʃ',
+ 7605 => 'ƫ',
+ 7606 => 'ʉ',
+ 7607 => 'ʊ',
+ 7608 => 'ᴜ',
+ 7609 => 'ʋ',
+ 7610 => 'ʌ',
+ 7611 => 'z',
+ 7612 => 'ʐ',
+ 7613 => 'ʑ',
+ 7614 => 'ʒ',
+ 7615 => 'θ',
+ 7680 => 'ḁ',
+ 7682 => 'ḃ',
+ 7684 => 'ḅ',
+ 7686 => 'ḇ',
+ 7688 => 'ḉ',
+ 7690 => 'ḋ',
+ 7692 => 'ḍ',
+ 7694 => 'ḏ',
+ 7696 => 'ḑ',
+ 7698 => 'ḓ',
+ 7700 => 'ḕ',
+ 7702 => 'ḗ',
+ 7704 => 'ḙ',
+ 7706 => 'ḛ',
+ 7708 => 'ḝ',
+ 7710 => 'ḟ',
+ 7712 => 'ḡ',
+ 7714 => 'ḣ',
+ 7716 => 'ḥ',
+ 7718 => 'ḧ',
+ 7720 => 'ḩ',
+ 7722 => 'ḫ',
+ 7724 => 'ḭ',
+ 7726 => 'ḯ',
+ 7728 => 'ḱ',
+ 7730 => 'ḳ',
+ 7732 => 'ḵ',
+ 7734 => 'ḷ',
+ 7736 => 'ḹ',
+ 7738 => 'ḻ',
+ 7740 => 'ḽ',
+ 7742 => 'ḿ',
+ 7744 => 'ṁ',
+ 7746 => 'ṃ',
+ 7748 => 'ṅ',
+ 7750 => 'ṇ',
+ 7752 => 'ṉ',
+ 7754 => 'ṋ',
+ 7756 => 'ṍ',
+ 7758 => 'ṏ',
+ 7760 => 'ṑ',
+ 7762 => 'ṓ',
+ 7764 => 'ṕ',
+ 7766 => 'ṗ',
+ 7768 => 'ṙ',
+ 7770 => 'ṛ',
+ 7772 => 'ṝ',
+ 7774 => 'ṟ',
+ 7776 => 'ṡ',
+ 7778 => 'ṣ',
+ 7780 => 'ṥ',
+ 7782 => 'ṧ',
+ 7784 => 'ṩ',
+ 7786 => 'ṫ',
+ 7788 => 'ṭ',
+ 7790 => 'ṯ',
+ 7792 => 'ṱ',
+ 7794 => 'ṳ',
+ 7796 => 'ṵ',
+ 7798 => 'ṷ',
+ 7800 => 'ṹ',
+ 7802 => 'ṻ',
+ 7804 => 'ṽ',
+ 7806 => 'ṿ',
+ 7808 => 'ẁ',
+ 7810 => 'ẃ',
+ 7812 => 'ẅ',
+ 7814 => 'ẇ',
+ 7816 => 'ẉ',
+ 7818 => 'ẋ',
+ 7820 => 'ẍ',
+ 7822 => 'ẏ',
+ 7824 => 'ẑ',
+ 7826 => 'ẓ',
+ 7828 => 'ẕ',
+ 7834 => 'aʾ',
+ 7835 => 'ṡ',
+ 7838 => 'ss',
+ 7840 => 'ạ',
+ 7842 => 'ả',
+ 7844 => 'ấ',
+ 7846 => 'ầ',
+ 7848 => 'ẩ',
+ 7850 => 'ẫ',
+ 7852 => 'ậ',
+ 7854 => 'ắ',
+ 7856 => 'ằ',
+ 7858 => 'ẳ',
+ 7860 => 'ẵ',
+ 7862 => 'ặ',
+ 7864 => 'ẹ',
+ 7866 => 'ẻ',
+ 7868 => 'ẽ',
+ 7870 => 'ế',
+ 7872 => 'ề',
+ 7874 => 'ể',
+ 7876 => 'ễ',
+ 7878 => 'ệ',
+ 7880 => 'ỉ',
+ 7882 => 'ị',
+ 7884 => 'ọ',
+ 7886 => 'ỏ',
+ 7888 => 'ố',
+ 7890 => 'ồ',
+ 7892 => 'ổ',
+ 7894 => 'ỗ',
+ 7896 => 'ộ',
+ 7898 => 'ớ',
+ 7900 => 'ờ',
+ 7902 => 'ở',
+ 7904 => 'ỡ',
+ 7906 => 'ợ',
+ 7908 => 'ụ',
+ 7910 => 'ủ',
+ 7912 => 'ứ',
+ 7914 => 'ừ',
+ 7916 => 'ử',
+ 7918 => 'ữ',
+ 7920 => 'ự',
+ 7922 => 'ỳ',
+ 7924 => 'ỵ',
+ 7926 => 'ỷ',
+ 7928 => 'ỹ',
+ 7930 => 'ỻ',
+ 7932 => 'ỽ',
+ 7934 => 'ỿ',
+ 7944 => 'ἀ',
+ 7945 => 'ἁ',
+ 7946 => 'ἂ',
+ 7947 => 'ἃ',
+ 7948 => 'ἄ',
+ 7949 => 'ἅ',
+ 7950 => 'ἆ',
+ 7951 => 'ἇ',
+ 7960 => 'ἐ',
+ 7961 => 'ἑ',
+ 7962 => 'ἒ',
+ 7963 => 'ἓ',
+ 7964 => 'ἔ',
+ 7965 => 'ἕ',
+ 7976 => 'ἠ',
+ 7977 => 'ἡ',
+ 7978 => 'ἢ',
+ 7979 => 'ἣ',
+ 7980 => 'ἤ',
+ 7981 => 'ἥ',
+ 7982 => 'ἦ',
+ 7983 => 'ἧ',
+ 7992 => 'ἰ',
+ 7993 => 'ἱ',
+ 7994 => 'ἲ',
+ 7995 => 'ἳ',
+ 7996 => 'ἴ',
+ 7997 => 'ἵ',
+ 7998 => 'ἶ',
+ 7999 => 'ἷ',
+ 8008 => 'ὀ',
+ 8009 => 'ὁ',
+ 8010 => 'ὂ',
+ 8011 => 'ὃ',
+ 8012 => 'ὄ',
+ 8013 => 'ὅ',
+ 8025 => 'ὑ',
+ 8027 => 'ὓ',
+ 8029 => 'ὕ',
+ 8031 => 'ὗ',
+ 8040 => 'ὠ',
+ 8041 => 'ὡ',
+ 8042 => 'ὢ',
+ 8043 => 'ὣ',
+ 8044 => 'ὤ',
+ 8045 => 'ὥ',
+ 8046 => 'ὦ',
+ 8047 => 'ὧ',
+ 8049 => 'ά',
+ 8051 => 'έ',
+ 8053 => 'ή',
+ 8055 => 'ί',
+ 8057 => 'ό',
+ 8059 => 'ύ',
+ 8061 => 'ώ',
+ 8064 => 'ἀι',
+ 8065 => 'ἁι',
+ 8066 => 'ἂι',
+ 8067 => 'ἃι',
+ 8068 => 'ἄι',
+ 8069 => 'ἅι',
+ 8070 => 'ἆι',
+ 8071 => 'ἇι',
+ 8072 => 'ἀι',
+ 8073 => 'ἁι',
+ 8074 => 'ἂι',
+ 8075 => 'ἃι',
+ 8076 => 'ἄι',
+ 8077 => 'ἅι',
+ 8078 => 'ἆι',
+ 8079 => 'ἇι',
+ 8080 => 'ἠι',
+ 8081 => 'ἡι',
+ 8082 => 'ἢι',
+ 8083 => 'ἣι',
+ 8084 => 'ἤι',
+ 8085 => 'ἥι',
+ 8086 => 'ἦι',
+ 8087 => 'ἧι',
+ 8088 => 'ἠι',
+ 8089 => 'ἡι',
+ 8090 => 'ἢι',
+ 8091 => 'ἣι',
+ 8092 => 'ἤι',
+ 8093 => 'ἥι',
+ 8094 => 'ἦι',
+ 8095 => 'ἧι',
+ 8096 => 'ὠι',
+ 8097 => 'ὡι',
+ 8098 => 'ὢι',
+ 8099 => 'ὣι',
+ 8100 => 'ὤι',
+ 8101 => 'ὥι',
+ 8102 => 'ὦι',
+ 8103 => 'ὧι',
+ 8104 => 'ὠι',
+ 8105 => 'ὡι',
+ 8106 => 'ὢι',
+ 8107 => 'ὣι',
+ 8108 => 'ὤι',
+ 8109 => 'ὥι',
+ 8110 => 'ὦι',
+ 8111 => 'ὧι',
+ 8114 => 'ὰι',
+ 8115 => 'αι',
+ 8116 => 'άι',
+ 8119 => 'ᾶι',
+ 8120 => 'ᾰ',
+ 8121 => 'ᾱ',
+ 8122 => 'ὰ',
+ 8123 => 'ά',
+ 8124 => 'αι',
+ 8126 => 'ι',
+ 8130 => 'ὴι',
+ 8131 => 'ηι',
+ 8132 => 'ήι',
+ 8135 => 'ῆι',
+ 8136 => 'ὲ',
+ 8137 => 'έ',
+ 8138 => 'ὴ',
+ 8139 => 'ή',
+ 8140 => 'ηι',
+ 8147 => 'ΐ',
+ 8152 => 'ῐ',
+ 8153 => 'ῑ',
+ 8154 => 'ὶ',
+ 8155 => 'ί',
+ 8163 => 'ΰ',
+ 8168 => 'ῠ',
+ 8169 => 'ῡ',
+ 8170 => 'ὺ',
+ 8171 => 'ύ',
+ 8172 => 'ῥ',
+ 8178 => 'ὼι',
+ 8179 => 'ωι',
+ 8180 => 'ώι',
+ 8183 => 'ῶι',
+ 8184 => 'ὸ',
+ 8185 => 'ό',
+ 8186 => 'ὼ',
+ 8187 => 'ώ',
+ 8188 => 'ωι',
+ 8209 => '‐',
+ 8243 => '′′',
+ 8244 => '′′′',
+ 8246 => '‵‵',
+ 8247 => '‵‵‵',
+ 8279 => '′′′′',
+ 8304 => '0',
+ 8305 => 'i',
+ 8308 => '4',
+ 8309 => '5',
+ 8310 => '6',
+ 8311 => '7',
+ 8312 => '8',
+ 8313 => '9',
+ 8315 => '−',
+ 8319 => 'n',
+ 8320 => '0',
+ 8321 => '1',
+ 8322 => '2',
+ 8323 => '3',
+ 8324 => '4',
+ 8325 => '5',
+ 8326 => '6',
+ 8327 => '7',
+ 8328 => '8',
+ 8329 => '9',
+ 8331 => '−',
+ 8336 => 'a',
+ 8337 => 'e',
+ 8338 => 'o',
+ 8339 => 'x',
+ 8340 => 'ə',
+ 8341 => 'h',
+ 8342 => 'k',
+ 8343 => 'l',
+ 8344 => 'm',
+ 8345 => 'n',
+ 8346 => 'p',
+ 8347 => 's',
+ 8348 => 't',
+ 8360 => 'rs',
+ 8450 => 'c',
+ 8451 => '°c',
+ 8455 => 'ɛ',
+ 8457 => '°f',
+ 8458 => 'g',
+ 8459 => 'h',
+ 8460 => 'h',
+ 8461 => 'h',
+ 8462 => 'h',
+ 8463 => 'ħ',
+ 8464 => 'i',
+ 8465 => 'i',
+ 8466 => 'l',
+ 8467 => 'l',
+ 8469 => 'n',
+ 8470 => 'no',
+ 8473 => 'p',
+ 8474 => 'q',
+ 8475 => 'r',
+ 8476 => 'r',
+ 8477 => 'r',
+ 8480 => 'sm',
+ 8481 => 'tel',
+ 8482 => 'tm',
+ 8484 => 'z',
+ 8486 => 'ω',
+ 8488 => 'z',
+ 8490 => 'k',
+ 8491 => 'å',
+ 8492 => 'b',
+ 8493 => 'c',
+ 8495 => 'e',
+ 8496 => 'e',
+ 8497 => 'f',
+ 8499 => 'm',
+ 8500 => 'o',
+ 8501 => 'א',
+ 8502 => 'ב',
+ 8503 => 'ג',
+ 8504 => 'ד',
+ 8505 => 'i',
+ 8507 => 'fax',
+ 8508 => 'π',
+ 8509 => 'γ',
+ 8510 => 'γ',
+ 8511 => 'π',
+ 8512 => '∑',
+ 8517 => 'd',
+ 8518 => 'd',
+ 8519 => 'e',
+ 8520 => 'i',
+ 8521 => 'j',
+ 8528 => '1⁄7',
+ 8529 => '1⁄9',
+ 8530 => '1⁄10',
+ 8531 => '1⁄3',
+ 8532 => '2⁄3',
+ 8533 => '1⁄5',
+ 8534 => '2⁄5',
+ 8535 => '3⁄5',
+ 8536 => '4⁄5',
+ 8537 => '1⁄6',
+ 8538 => '5⁄6',
+ 8539 => '1⁄8',
+ 8540 => '3⁄8',
+ 8541 => '5⁄8',
+ 8542 => '7⁄8',
+ 8543 => '1⁄',
+ 8544 => 'i',
+ 8545 => 'ii',
+ 8546 => 'iii',
+ 8547 => 'iv',
+ 8548 => 'v',
+ 8549 => 'vi',
+ 8550 => 'vii',
+ 8551 => 'viii',
+ 8552 => 'ix',
+ 8553 => 'x',
+ 8554 => 'xi',
+ 8555 => 'xii',
+ 8556 => 'l',
+ 8557 => 'c',
+ 8558 => 'd',
+ 8559 => 'm',
+ 8560 => 'i',
+ 8561 => 'ii',
+ 8562 => 'iii',
+ 8563 => 'iv',
+ 8564 => 'v',
+ 8565 => 'vi',
+ 8566 => 'vii',
+ 8567 => 'viii',
+ 8568 => 'ix',
+ 8569 => 'x',
+ 8570 => 'xi',
+ 8571 => 'xii',
+ 8572 => 'l',
+ 8573 => 'c',
+ 8574 => 'd',
+ 8575 => 'm',
+ 8585 => '0⁄3',
+ 8748 => '∫∫',
+ 8749 => '∫∫∫',
+ 8751 => '∮∮',
+ 8752 => '∮∮∮',
+ 9001 => '〈',
+ 9002 => '〉',
+ 9312 => '1',
+ 9313 => '2',
+ 9314 => '3',
+ 9315 => '4',
+ 9316 => '5',
+ 9317 => '6',
+ 9318 => '7',
+ 9319 => '8',
+ 9320 => '9',
+ 9321 => '10',
+ 9322 => '11',
+ 9323 => '12',
+ 9324 => '13',
+ 9325 => '14',
+ 9326 => '15',
+ 9327 => '16',
+ 9328 => '17',
+ 9329 => '18',
+ 9330 => '19',
+ 9331 => '20',
+ 9398 => 'a',
+ 9399 => 'b',
+ 9400 => 'c',
+ 9401 => 'd',
+ 9402 => 'e',
+ 9403 => 'f',
+ 9404 => 'g',
+ 9405 => 'h',
+ 9406 => 'i',
+ 9407 => 'j',
+ 9408 => 'k',
+ 9409 => 'l',
+ 9410 => 'm',
+ 9411 => 'n',
+ 9412 => 'o',
+ 9413 => 'p',
+ 9414 => 'q',
+ 9415 => 'r',
+ 9416 => 's',
+ 9417 => 't',
+ 9418 => 'u',
+ 9419 => 'v',
+ 9420 => 'w',
+ 9421 => 'x',
+ 9422 => 'y',
+ 9423 => 'z',
+ 9424 => 'a',
+ 9425 => 'b',
+ 9426 => 'c',
+ 9427 => 'd',
+ 9428 => 'e',
+ 9429 => 'f',
+ 9430 => 'g',
+ 9431 => 'h',
+ 9432 => 'i',
+ 9433 => 'j',
+ 9434 => 'k',
+ 9435 => 'l',
+ 9436 => 'm',
+ 9437 => 'n',
+ 9438 => 'o',
+ 9439 => 'p',
+ 9440 => 'q',
+ 9441 => 'r',
+ 9442 => 's',
+ 9443 => 't',
+ 9444 => 'u',
+ 9445 => 'v',
+ 9446 => 'w',
+ 9447 => 'x',
+ 9448 => 'y',
+ 9449 => 'z',
+ 9450 => '0',
+ 10764 => '∫∫∫∫',
+ 10972 => '⫝̸',
+ 11264 => 'ⰰ',
+ 11265 => 'ⰱ',
+ 11266 => 'ⰲ',
+ 11267 => 'ⰳ',
+ 11268 => 'ⰴ',
+ 11269 => 'ⰵ',
+ 11270 => 'ⰶ',
+ 11271 => 'ⰷ',
+ 11272 => 'ⰸ',
+ 11273 => 'ⰹ',
+ 11274 => 'ⰺ',
+ 11275 => 'ⰻ',
+ 11276 => 'ⰼ',
+ 11277 => 'ⰽ',
+ 11278 => 'ⰾ',
+ 11279 => 'ⰿ',
+ 11280 => 'ⱀ',
+ 11281 => 'ⱁ',
+ 11282 => 'ⱂ',
+ 11283 => 'ⱃ',
+ 11284 => 'ⱄ',
+ 11285 => 'ⱅ',
+ 11286 => 'ⱆ',
+ 11287 => 'ⱇ',
+ 11288 => 'ⱈ',
+ 11289 => 'ⱉ',
+ 11290 => 'ⱊ',
+ 11291 => 'ⱋ',
+ 11292 => 'ⱌ',
+ 11293 => 'ⱍ',
+ 11294 => 'ⱎ',
+ 11295 => 'ⱏ',
+ 11296 => 'ⱐ',
+ 11297 => 'ⱑ',
+ 11298 => 'ⱒ',
+ 11299 => 'ⱓ',
+ 11300 => 'ⱔ',
+ 11301 => 'ⱕ',
+ 11302 => 'ⱖ',
+ 11303 => 'ⱗ',
+ 11304 => 'ⱘ',
+ 11305 => 'ⱙ',
+ 11306 => 'ⱚ',
+ 11307 => 'ⱛ',
+ 11308 => 'ⱜ',
+ 11309 => 'ⱝ',
+ 11310 => 'ⱞ',
+ 11360 => 'ⱡ',
+ 11362 => 'ɫ',
+ 11363 => 'ᵽ',
+ 11364 => 'ɽ',
+ 11367 => 'ⱨ',
+ 11369 => 'ⱪ',
+ 11371 => 'ⱬ',
+ 11373 => 'ɑ',
+ 11374 => 'ɱ',
+ 11375 => 'ɐ',
+ 11376 => 'ɒ',
+ 11378 => 'ⱳ',
+ 11381 => 'ⱶ',
+ 11388 => 'j',
+ 11389 => 'v',
+ 11390 => 'ȿ',
+ 11391 => 'ɀ',
+ 11392 => 'ⲁ',
+ 11394 => 'ⲃ',
+ 11396 => 'ⲅ',
+ 11398 => 'ⲇ',
+ 11400 => 'ⲉ',
+ 11402 => 'ⲋ',
+ 11404 => 'ⲍ',
+ 11406 => 'ⲏ',
+ 11408 => 'ⲑ',
+ 11410 => 'ⲓ',
+ 11412 => 'ⲕ',
+ 11414 => 'ⲗ',
+ 11416 => 'ⲙ',
+ 11418 => 'ⲛ',
+ 11420 => 'ⲝ',
+ 11422 => 'ⲟ',
+ 11424 => 'ⲡ',
+ 11426 => 'ⲣ',
+ 11428 => 'ⲥ',
+ 11430 => 'ⲧ',
+ 11432 => 'ⲩ',
+ 11434 => 'ⲫ',
+ 11436 => 'ⲭ',
+ 11438 => 'ⲯ',
+ 11440 => 'ⲱ',
+ 11442 => 'ⲳ',
+ 11444 => 'ⲵ',
+ 11446 => 'ⲷ',
+ 11448 => 'ⲹ',
+ 11450 => 'ⲻ',
+ 11452 => 'ⲽ',
+ 11454 => 'ⲿ',
+ 11456 => 'ⳁ',
+ 11458 => 'ⳃ',
+ 11460 => 'ⳅ',
+ 11462 => 'ⳇ',
+ 11464 => 'ⳉ',
+ 11466 => 'ⳋ',
+ 11468 => 'ⳍ',
+ 11470 => 'ⳏ',
+ 11472 => 'ⳑ',
+ 11474 => 'ⳓ',
+ 11476 => 'ⳕ',
+ 11478 => 'ⳗ',
+ 11480 => 'ⳙ',
+ 11482 => 'ⳛ',
+ 11484 => 'ⳝ',
+ 11486 => 'ⳟ',
+ 11488 => 'ⳡ',
+ 11490 => 'ⳣ',
+ 11499 => 'ⳬ',
+ 11501 => 'ⳮ',
+ 11506 => 'ⳳ',
+ 11631 => 'ⵡ',
+ 11935 => '母',
+ 12019 => '龟',
+ 12032 => '一',
+ 12033 => '丨',
+ 12034 => '丶',
+ 12035 => '丿',
+ 12036 => '乙',
+ 12037 => '亅',
+ 12038 => '二',
+ 12039 => '亠',
+ 12040 => '人',
+ 12041 => '儿',
+ 12042 => '入',
+ 12043 => '八',
+ 12044 => '冂',
+ 12045 => '冖',
+ 12046 => '冫',
+ 12047 => '几',
+ 12048 => '凵',
+ 12049 => '刀',
+ 12050 => '力',
+ 12051 => '勹',
+ 12052 => '匕',
+ 12053 => '匚',
+ 12054 => '匸',
+ 12055 => '十',
+ 12056 => '卜',
+ 12057 => '卩',
+ 12058 => '厂',
+ 12059 => '厶',
+ 12060 => '又',
+ 12061 => '口',
+ 12062 => '囗',
+ 12063 => '土',
+ 12064 => '士',
+ 12065 => '夂',
+ 12066 => '夊',
+ 12067 => '夕',
+ 12068 => '大',
+ 12069 => '女',
+ 12070 => '子',
+ 12071 => '宀',
+ 12072 => '寸',
+ 12073 => '小',
+ 12074 => '尢',
+ 12075 => '尸',
+ 12076 => '屮',
+ 12077 => '山',
+ 12078 => '巛',
+ 12079 => '工',
+ 12080 => '己',
+ 12081 => '巾',
+ 12082 => '干',
+ 12083 => '幺',
+ 12084 => '广',
+ 12085 => '廴',
+ 12086 => '廾',
+ 12087 => '弋',
+ 12088 => '弓',
+ 12089 => '彐',
+ 12090 => '彡',
+ 12091 => '彳',
+ 12092 => '心',
+ 12093 => '戈',
+ 12094 => '戶',
+ 12095 => '手',
+ 12096 => '支',
+ 12097 => '攴',
+ 12098 => '文',
+ 12099 => '斗',
+ 12100 => '斤',
+ 12101 => '方',
+ 12102 => '无',
+ 12103 => '日',
+ 12104 => '曰',
+ 12105 => '月',
+ 12106 => '木',
+ 12107 => '欠',
+ 12108 => '止',
+ 12109 => '歹',
+ 12110 => '殳',
+ 12111 => '毋',
+ 12112 => '比',
+ 12113 => '毛',
+ 12114 => '氏',
+ 12115 => '气',
+ 12116 => '水',
+ 12117 => '火',
+ 12118 => '爪',
+ 12119 => '父',
+ 12120 => '爻',
+ 12121 => '爿',
+ 12122 => '片',
+ 12123 => '牙',
+ 12124 => '牛',
+ 12125 => '犬',
+ 12126 => '玄',
+ 12127 => '玉',
+ 12128 => '瓜',
+ 12129 => '瓦',
+ 12130 => '甘',
+ 12131 => '生',
+ 12132 => '用',
+ 12133 => '田',
+ 12134 => '疋',
+ 12135 => '疒',
+ 12136 => '癶',
+ 12137 => '白',
+ 12138 => '皮',
+ 12139 => '皿',
+ 12140 => '目',
+ 12141 => '矛',
+ 12142 => '矢',
+ 12143 => '石',
+ 12144 => '示',
+ 12145 => '禸',
+ 12146 => '禾',
+ 12147 => '穴',
+ 12148 => '立',
+ 12149 => '竹',
+ 12150 => '米',
+ 12151 => '糸',
+ 12152 => '缶',
+ 12153 => '网',
+ 12154 => '羊',
+ 12155 => '羽',
+ 12156 => '老',
+ 12157 => '而',
+ 12158 => '耒',
+ 12159 => '耳',
+ 12160 => '聿',
+ 12161 => '肉',
+ 12162 => '臣',
+ 12163 => '自',
+ 12164 => '至',
+ 12165 => '臼',
+ 12166 => '舌',
+ 12167 => '舛',
+ 12168 => '舟',
+ 12169 => '艮',
+ 12170 => '色',
+ 12171 => '艸',
+ 12172 => '虍',
+ 12173 => '虫',
+ 12174 => '血',
+ 12175 => '行',
+ 12176 => '衣',
+ 12177 => '襾',
+ 12178 => '見',
+ 12179 => '角',
+ 12180 => '言',
+ 12181 => '谷',
+ 12182 => '豆',
+ 12183 => '豕',
+ 12184 => '豸',
+ 12185 => '貝',
+ 12186 => '赤',
+ 12187 => '走',
+ 12188 => '足',
+ 12189 => '身',
+ 12190 => '車',
+ 12191 => '辛',
+ 12192 => '辰',
+ 12193 => '辵',
+ 12194 => '邑',
+ 12195 => '酉',
+ 12196 => '釆',
+ 12197 => '里',
+ 12198 => '金',
+ 12199 => '長',
+ 12200 => '門',
+ 12201 => '阜',
+ 12202 => '隶',
+ 12203 => '隹',
+ 12204 => '雨',
+ 12205 => '靑',
+ 12206 => '非',
+ 12207 => '面',
+ 12208 => '革',
+ 12209 => '韋',
+ 12210 => '韭',
+ 12211 => '音',
+ 12212 => '頁',
+ 12213 => '風',
+ 12214 => '飛',
+ 12215 => '食',
+ 12216 => '首',
+ 12217 => '香',
+ 12218 => '馬',
+ 12219 => '骨',
+ 12220 => '高',
+ 12221 => '髟',
+ 12222 => '鬥',
+ 12223 => '鬯',
+ 12224 => '鬲',
+ 12225 => '鬼',
+ 12226 => '魚',
+ 12227 => '鳥',
+ 12228 => '鹵',
+ 12229 => '鹿',
+ 12230 => '麥',
+ 12231 => '麻',
+ 12232 => '黃',
+ 12233 => '黍',
+ 12234 => '黑',
+ 12235 => '黹',
+ 12236 => '黽',
+ 12237 => '鼎',
+ 12238 => '鼓',
+ 12239 => '鼠',
+ 12240 => '鼻',
+ 12241 => '齊',
+ 12242 => '齒',
+ 12243 => '龍',
+ 12244 => '龜',
+ 12245 => '龠',
+ 12290 => '.',
+ 12342 => '〒',
+ 12344 => '十',
+ 12345 => '卄',
+ 12346 => '卅',
+ 12447 => 'より',
+ 12543 => 'コト',
+ 12593 => 'ᄀ',
+ 12594 => 'ᄁ',
+ 12595 => 'ᆪ',
+ 12596 => 'ᄂ',
+ 12597 => 'ᆬ',
+ 12598 => 'ᆭ',
+ 12599 => 'ᄃ',
+ 12600 => 'ᄄ',
+ 12601 => 'ᄅ',
+ 12602 => 'ᆰ',
+ 12603 => 'ᆱ',
+ 12604 => 'ᆲ',
+ 12605 => 'ᆳ',
+ 12606 => 'ᆴ',
+ 12607 => 'ᆵ',
+ 12608 => 'ᄚ',
+ 12609 => 'ᄆ',
+ 12610 => 'ᄇ',
+ 12611 => 'ᄈ',
+ 12612 => 'ᄡ',
+ 12613 => 'ᄉ',
+ 12614 => 'ᄊ',
+ 12615 => 'ᄋ',
+ 12616 => 'ᄌ',
+ 12617 => 'ᄍ',
+ 12618 => 'ᄎ',
+ 12619 => 'ᄏ',
+ 12620 => 'ᄐ',
+ 12621 => 'ᄑ',
+ 12622 => 'ᄒ',
+ 12623 => 'ᅡ',
+ 12624 => 'ᅢ',
+ 12625 => 'ᅣ',
+ 12626 => 'ᅤ',
+ 12627 => 'ᅥ',
+ 12628 => 'ᅦ',
+ 12629 => 'ᅧ',
+ 12630 => 'ᅨ',
+ 12631 => 'ᅩ',
+ 12632 => 'ᅪ',
+ 12633 => 'ᅫ',
+ 12634 => 'ᅬ',
+ 12635 => 'ᅭ',
+ 12636 => 'ᅮ',
+ 12637 => 'ᅯ',
+ 12638 => 'ᅰ',
+ 12639 => 'ᅱ',
+ 12640 => 'ᅲ',
+ 12641 => 'ᅳ',
+ 12642 => 'ᅴ',
+ 12643 => 'ᅵ',
+ 12645 => 'ᄔ',
+ 12646 => 'ᄕ',
+ 12647 => 'ᇇ',
+ 12648 => 'ᇈ',
+ 12649 => 'ᇌ',
+ 12650 => 'ᇎ',
+ 12651 => 'ᇓ',
+ 12652 => 'ᇗ',
+ 12653 => 'ᇙ',
+ 12654 => 'ᄜ',
+ 12655 => 'ᇝ',
+ 12656 => 'ᇟ',
+ 12657 => 'ᄝ',
+ 12658 => 'ᄞ',
+ 12659 => 'ᄠ',
+ 12660 => 'ᄢ',
+ 12661 => 'ᄣ',
+ 12662 => 'ᄧ',
+ 12663 => 'ᄩ',
+ 12664 => 'ᄫ',
+ 12665 => 'ᄬ',
+ 12666 => 'ᄭ',
+ 12667 => 'ᄮ',
+ 12668 => 'ᄯ',
+ 12669 => 'ᄲ',
+ 12670 => 'ᄶ',
+ 12671 => 'ᅀ',
+ 12672 => 'ᅇ',
+ 12673 => 'ᅌ',
+ 12674 => 'ᇱ',
+ 12675 => 'ᇲ',
+ 12676 => 'ᅗ',
+ 12677 => 'ᅘ',
+ 12678 => 'ᅙ',
+ 12679 => 'ᆄ',
+ 12680 => 'ᆅ',
+ 12681 => 'ᆈ',
+ 12682 => 'ᆑ',
+ 12683 => 'ᆒ',
+ 12684 => 'ᆔ',
+ 12685 => 'ᆞ',
+ 12686 => 'ᆡ',
+ 12690 => '一',
+ 12691 => '二',
+ 12692 => '三',
+ 12693 => '四',
+ 12694 => '上',
+ 12695 => '中',
+ 12696 => '下',
+ 12697 => '甲',
+ 12698 => '乙',
+ 12699 => '丙',
+ 12700 => '丁',
+ 12701 => '天',
+ 12702 => '地',
+ 12703 => '人',
+ 12868 => '問',
+ 12869 => '幼',
+ 12870 => '文',
+ 12871 => '箏',
+ 12880 => 'pte',
+ 12881 => '21',
+ 12882 => '22',
+ 12883 => '23',
+ 12884 => '24',
+ 12885 => '25',
+ 12886 => '26',
+ 12887 => '27',
+ 12888 => '28',
+ 12889 => '29',
+ 12890 => '30',
+ 12891 => '31',
+ 12892 => '32',
+ 12893 => '33',
+ 12894 => '34',
+ 12895 => '35',
+ 12896 => 'ᄀ',
+ 12897 => 'ᄂ',
+ 12898 => 'ᄃ',
+ 12899 => 'ᄅ',
+ 12900 => 'ᄆ',
+ 12901 => 'ᄇ',
+ 12902 => 'ᄉ',
+ 12903 => 'ᄋ',
+ 12904 => 'ᄌ',
+ 12905 => 'ᄎ',
+ 12906 => 'ᄏ',
+ 12907 => 'ᄐ',
+ 12908 => 'ᄑ',
+ 12909 => 'ᄒ',
+ 12910 => '가',
+ 12911 => '나',
+ 12912 => '다',
+ 12913 => '라',
+ 12914 => '마',
+ 12915 => '바',
+ 12916 => '사',
+ 12917 => '아',
+ 12918 => '자',
+ 12919 => '차',
+ 12920 => '카',
+ 12921 => '타',
+ 12922 => '파',
+ 12923 => '하',
+ 12924 => '참고',
+ 12925 => '주의',
+ 12926 => '우',
+ 12928 => '一',
+ 12929 => '二',
+ 12930 => '三',
+ 12931 => '四',
+ 12932 => '五',
+ 12933 => '六',
+ 12934 => '七',
+ 12935 => '八',
+ 12936 => '九',
+ 12937 => '十',
+ 12938 => '月',
+ 12939 => '火',
+ 12940 => '水',
+ 12941 => '木',
+ 12942 => '金',
+ 12943 => '土',
+ 12944 => '日',
+ 12945 => '株',
+ 12946 => '有',
+ 12947 => '社',
+ 12948 => '名',
+ 12949 => '特',
+ 12950 => '財',
+ 12951 => '祝',
+ 12952 => '労',
+ 12953 => '秘',
+ 12954 => '男',
+ 12955 => '女',
+ 12956 => '適',
+ 12957 => '優',
+ 12958 => '印',
+ 12959 => '注',
+ 12960 => '項',
+ 12961 => '休',
+ 12962 => '写',
+ 12963 => '正',
+ 12964 => '上',
+ 12965 => '中',
+ 12966 => '下',
+ 12967 => '左',
+ 12968 => '右',
+ 12969 => '医',
+ 12970 => '宗',
+ 12971 => '学',
+ 12972 => '監',
+ 12973 => '企',
+ 12974 => '資',
+ 12975 => '協',
+ 12976 => '夜',
+ 12977 => '36',
+ 12978 => '37',
+ 12979 => '38',
+ 12980 => '39',
+ 12981 => '40',
+ 12982 => '41',
+ 12983 => '42',
+ 12984 => '43',
+ 12985 => '44',
+ 12986 => '45',
+ 12987 => '46',
+ 12988 => '47',
+ 12989 => '48',
+ 12990 => '49',
+ 12991 => '50',
+ 12992 => '1月',
+ 12993 => '2月',
+ 12994 => '3月',
+ 12995 => '4月',
+ 12996 => '5月',
+ 12997 => '6月',
+ 12998 => '7月',
+ 12999 => '8月',
+ 13000 => '9月',
+ 13001 => '10月',
+ 13002 => '11月',
+ 13003 => '12月',
+ 13004 => 'hg',
+ 13005 => 'erg',
+ 13006 => 'ev',
+ 13007 => 'ltd',
+ 13008 => 'ア',
+ 13009 => 'イ',
+ 13010 => 'ウ',
+ 13011 => 'エ',
+ 13012 => 'オ',
+ 13013 => 'カ',
+ 13014 => 'キ',
+ 13015 => 'ク',
+ 13016 => 'ケ',
+ 13017 => 'コ',
+ 13018 => 'サ',
+ 13019 => 'シ',
+ 13020 => 'ス',
+ 13021 => 'セ',
+ 13022 => 'ソ',
+ 13023 => 'タ',
+ 13024 => 'チ',
+ 13025 => 'ツ',
+ 13026 => 'テ',
+ 13027 => 'ト',
+ 13028 => 'ナ',
+ 13029 => 'ニ',
+ 13030 => 'ヌ',
+ 13031 => 'ネ',
+ 13032 => 'ノ',
+ 13033 => 'ハ',
+ 13034 => 'ヒ',
+ 13035 => 'フ',
+ 13036 => 'ヘ',
+ 13037 => 'ホ',
+ 13038 => 'マ',
+ 13039 => 'ミ',
+ 13040 => 'ム',
+ 13041 => 'メ',
+ 13042 => 'モ',
+ 13043 => 'ヤ',
+ 13044 => 'ユ',
+ 13045 => 'ヨ',
+ 13046 => 'ラ',
+ 13047 => 'リ',
+ 13048 => 'ル',
+ 13049 => 'レ',
+ 13050 => 'ロ',
+ 13051 => 'ワ',
+ 13052 => 'ヰ',
+ 13053 => 'ヱ',
+ 13054 => 'ヲ',
+ 13055 => '令和',
+ 13056 => 'アパート',
+ 13057 => 'アルファ',
+ 13058 => 'アンペア',
+ 13059 => 'アール',
+ 13060 => 'イニング',
+ 13061 => 'インチ',
+ 13062 => 'ウォン',
+ 13063 => 'エスクード',
+ 13064 => 'エーカー',
+ 13065 => 'オンス',
+ 13066 => 'オーム',
+ 13067 => 'カイリ',
+ 13068 => 'カラット',
+ 13069 => 'カロリー',
+ 13070 => 'ガロン',
+ 13071 => 'ガンマ',
+ 13072 => 'ギガ',
+ 13073 => 'ギニー',
+ 13074 => 'キュリー',
+ 13075 => 'ギルダー',
+ 13076 => 'キロ',
+ 13077 => 'キログラム',
+ 13078 => 'キロメートル',
+ 13079 => 'キロワット',
+ 13080 => 'グラム',
+ 13081 => 'グラムトン',
+ 13082 => 'クルゼイロ',
+ 13083 => 'クローネ',
+ 13084 => 'ケース',
+ 13085 => 'コルナ',
+ 13086 => 'コーポ',
+ 13087 => 'サイクル',
+ 13088 => 'サンチーム',
+ 13089 => 'シリング',
+ 13090 => 'センチ',
+ 13091 => 'セント',
+ 13092 => 'ダース',
+ 13093 => 'デシ',
+ 13094 => 'ドル',
+ 13095 => 'トン',
+ 13096 => 'ナノ',
+ 13097 => 'ノット',
+ 13098 => 'ハイツ',
+ 13099 => 'パーセント',
+ 13100 => 'パーツ',
+ 13101 => 'バーレル',
+ 13102 => 'ピアストル',
+ 13103 => 'ピクル',
+ 13104 => 'ピコ',
+ 13105 => 'ビル',
+ 13106 => 'ファラッド',
+ 13107 => 'フィート',
+ 13108 => 'ブッシェル',
+ 13109 => 'フラン',
+ 13110 => 'ヘクタール',
+ 13111 => 'ペソ',
+ 13112 => 'ペニヒ',
+ 13113 => 'ヘルツ',
+ 13114 => 'ペンス',
+ 13115 => 'ページ',
+ 13116 => 'ベータ',
+ 13117 => 'ポイント',
+ 13118 => 'ボルト',
+ 13119 => 'ホン',
+ 13120 => 'ポンド',
+ 13121 => 'ホール',
+ 13122 => 'ホーン',
+ 13123 => 'マイクロ',
+ 13124 => 'マイル',
+ 13125 => 'マッハ',
+ 13126 => 'マルク',
+ 13127 => 'マンション',
+ 13128 => 'ミクロン',
+ 13129 => 'ミリ',
+ 13130 => 'ミリバール',
+ 13131 => 'メガ',
+ 13132 => 'メガトン',
+ 13133 => 'メートル',
+ 13134 => 'ヤード',
+ 13135 => 'ヤール',
+ 13136 => 'ユアン',
+ 13137 => 'リットル',
+ 13138 => 'リラ',
+ 13139 => 'ルピー',
+ 13140 => 'ルーブル',
+ 13141 => 'レム',
+ 13142 => 'レントゲン',
+ 13143 => 'ワット',
+ 13144 => '0点',
+ 13145 => '1点',
+ 13146 => '2点',
+ 13147 => '3点',
+ 13148 => '4点',
+ 13149 => '5点',
+ 13150 => '6点',
+ 13151 => '7点',
+ 13152 => '8点',
+ 13153 => '9点',
+ 13154 => '10点',
+ 13155 => '11点',
+ 13156 => '12点',
+ 13157 => '13点',
+ 13158 => '14点',
+ 13159 => '15点',
+ 13160 => '16点',
+ 13161 => '17点',
+ 13162 => '18点',
+ 13163 => '19点',
+ 13164 => '20点',
+ 13165 => '21点',
+ 13166 => '22点',
+ 13167 => '23点',
+ 13168 => '24点',
+ 13169 => 'hpa',
+ 13170 => 'da',
+ 13171 => 'au',
+ 13172 => 'bar',
+ 13173 => 'ov',
+ 13174 => 'pc',
+ 13175 => 'dm',
+ 13176 => 'dm2',
+ 13177 => 'dm3',
+ 13178 => 'iu',
+ 13179 => '平成',
+ 13180 => '昭和',
+ 13181 => '大正',
+ 13182 => '明治',
+ 13183 => '株式会社',
+ 13184 => 'pa',
+ 13185 => 'na',
+ 13186 => 'μa',
+ 13187 => 'ma',
+ 13188 => 'ka',
+ 13189 => 'kb',
+ 13190 => 'mb',
+ 13191 => 'gb',
+ 13192 => 'cal',
+ 13193 => 'kcal',
+ 13194 => 'pf',
+ 13195 => 'nf',
+ 13196 => 'μf',
+ 13197 => 'μg',
+ 13198 => 'mg',
+ 13199 => 'kg',
+ 13200 => 'hz',
+ 13201 => 'khz',
+ 13202 => 'mhz',
+ 13203 => 'ghz',
+ 13204 => 'thz',
+ 13205 => 'μl',
+ 13206 => 'ml',
+ 13207 => 'dl',
+ 13208 => 'kl',
+ 13209 => 'fm',
+ 13210 => 'nm',
+ 13211 => 'μm',
+ 13212 => 'mm',
+ 13213 => 'cm',
+ 13214 => 'km',
+ 13215 => 'mm2',
+ 13216 => 'cm2',
+ 13217 => 'm2',
+ 13218 => 'km2',
+ 13219 => 'mm3',
+ 13220 => 'cm3',
+ 13221 => 'm3',
+ 13222 => 'km3',
+ 13223 => 'm∕s',
+ 13224 => 'm∕s2',
+ 13225 => 'pa',
+ 13226 => 'kpa',
+ 13227 => 'mpa',
+ 13228 => 'gpa',
+ 13229 => 'rad',
+ 13230 => 'rad∕s',
+ 13231 => 'rad∕s2',
+ 13232 => 'ps',
+ 13233 => 'ns',
+ 13234 => 'μs',
+ 13235 => 'ms',
+ 13236 => 'pv',
+ 13237 => 'nv',
+ 13238 => 'μv',
+ 13239 => 'mv',
+ 13240 => 'kv',
+ 13241 => 'mv',
+ 13242 => 'pw',
+ 13243 => 'nw',
+ 13244 => 'μw',
+ 13245 => 'mw',
+ 13246 => 'kw',
+ 13247 => 'mw',
+ 13248 => 'kω',
+ 13249 => 'mω',
+ 13251 => 'bq',
+ 13252 => 'cc',
+ 13253 => 'cd',
+ 13254 => 'c∕kg',
+ 13256 => 'db',
+ 13257 => 'gy',
+ 13258 => 'ha',
+ 13259 => 'hp',
+ 13260 => 'in',
+ 13261 => 'kk',
+ 13262 => 'km',
+ 13263 => 'kt',
+ 13264 => 'lm',
+ 13265 => 'ln',
+ 13266 => 'log',
+ 13267 => 'lx',
+ 13268 => 'mb',
+ 13269 => 'mil',
+ 13270 => 'mol',
+ 13271 => 'ph',
+ 13273 => 'ppm',
+ 13274 => 'pr',
+ 13275 => 'sr',
+ 13276 => 'sv',
+ 13277 => 'wb',
+ 13278 => 'v∕m',
+ 13279 => 'a∕m',
+ 13280 => '1日',
+ 13281 => '2日',
+ 13282 => '3日',
+ 13283 => '4日',
+ 13284 => '5日',
+ 13285 => '6日',
+ 13286 => '7日',
+ 13287 => '8日',
+ 13288 => '9日',
+ 13289 => '10日',
+ 13290 => '11日',
+ 13291 => '12日',
+ 13292 => '13日',
+ 13293 => '14日',
+ 13294 => '15日',
+ 13295 => '16日',
+ 13296 => '17日',
+ 13297 => '18日',
+ 13298 => '19日',
+ 13299 => '20日',
+ 13300 => '21日',
+ 13301 => '22日',
+ 13302 => '23日',
+ 13303 => '24日',
+ 13304 => '25日',
+ 13305 => '26日',
+ 13306 => '27日',
+ 13307 => '28日',
+ 13308 => '29日',
+ 13309 => '30日',
+ 13310 => '31日',
+ 13311 => 'gal',
+ 42560 => 'ꙁ',
+ 42562 => 'ꙃ',
+ 42564 => 'ꙅ',
+ 42566 => 'ꙇ',
+ 42568 => 'ꙉ',
+ 42570 => 'ꙋ',
+ 42572 => 'ꙍ',
+ 42574 => 'ꙏ',
+ 42576 => 'ꙑ',
+ 42578 => 'ꙓ',
+ 42580 => 'ꙕ',
+ 42582 => 'ꙗ',
+ 42584 => 'ꙙ',
+ 42586 => 'ꙛ',
+ 42588 => 'ꙝ',
+ 42590 => 'ꙟ',
+ 42592 => 'ꙡ',
+ 42594 => 'ꙣ',
+ 42596 => 'ꙥ',
+ 42598 => 'ꙧ',
+ 42600 => 'ꙩ',
+ 42602 => 'ꙫ',
+ 42604 => 'ꙭ',
+ 42624 => 'ꚁ',
+ 42626 => 'ꚃ',
+ 42628 => 'ꚅ',
+ 42630 => 'ꚇ',
+ 42632 => 'ꚉ',
+ 42634 => 'ꚋ',
+ 42636 => 'ꚍ',
+ 42638 => 'ꚏ',
+ 42640 => 'ꚑ',
+ 42642 => 'ꚓ',
+ 42644 => 'ꚕ',
+ 42646 => 'ꚗ',
+ 42648 => 'ꚙ',
+ 42650 => 'ꚛ',
+ 42652 => 'ъ',
+ 42653 => 'ь',
+ 42786 => 'ꜣ',
+ 42788 => 'ꜥ',
+ 42790 => 'ꜧ',
+ 42792 => 'ꜩ',
+ 42794 => 'ꜫ',
+ 42796 => 'ꜭ',
+ 42798 => 'ꜯ',
+ 42802 => 'ꜳ',
+ 42804 => 'ꜵ',
+ 42806 => 'ꜷ',
+ 42808 => 'ꜹ',
+ 42810 => 'ꜻ',
+ 42812 => 'ꜽ',
+ 42814 => 'ꜿ',
+ 42816 => 'ꝁ',
+ 42818 => 'ꝃ',
+ 42820 => 'ꝅ',
+ 42822 => 'ꝇ',
+ 42824 => 'ꝉ',
+ 42826 => 'ꝋ',
+ 42828 => 'ꝍ',
+ 42830 => 'ꝏ',
+ 42832 => 'ꝑ',
+ 42834 => 'ꝓ',
+ 42836 => 'ꝕ',
+ 42838 => 'ꝗ',
+ 42840 => 'ꝙ',
+ 42842 => 'ꝛ',
+ 42844 => 'ꝝ',
+ 42846 => 'ꝟ',
+ 42848 => 'ꝡ',
+ 42850 => 'ꝣ',
+ 42852 => 'ꝥ',
+ 42854 => 'ꝧ',
+ 42856 => 'ꝩ',
+ 42858 => 'ꝫ',
+ 42860 => 'ꝭ',
+ 42862 => 'ꝯ',
+ 42864 => 'ꝯ',
+ 42873 => 'ꝺ',
+ 42875 => 'ꝼ',
+ 42877 => 'ᵹ',
+ 42878 => 'ꝿ',
+ 42880 => 'ꞁ',
+ 42882 => 'ꞃ',
+ 42884 => 'ꞅ',
+ 42886 => 'ꞇ',
+ 42891 => 'ꞌ',
+ 42893 => 'ɥ',
+ 42896 => 'ꞑ',
+ 42898 => 'ꞓ',
+ 42902 => 'ꞗ',
+ 42904 => 'ꞙ',
+ 42906 => 'ꞛ',
+ 42908 => 'ꞝ',
+ 42910 => 'ꞟ',
+ 42912 => 'ꞡ',
+ 42914 => 'ꞣ',
+ 42916 => 'ꞥ',
+ 42918 => 'ꞧ',
+ 42920 => 'ꞩ',
+ 42922 => 'ɦ',
+ 42923 => 'ɜ',
+ 42924 => 'ɡ',
+ 42925 => 'ɬ',
+ 42926 => 'ɪ',
+ 42928 => 'ʞ',
+ 42929 => 'ʇ',
+ 42930 => 'ʝ',
+ 42931 => 'ꭓ',
+ 42932 => 'ꞵ',
+ 42934 => 'ꞷ',
+ 42936 => 'ꞹ',
+ 42938 => 'ꞻ',
+ 42940 => 'ꞽ',
+ 42942 => 'ꞿ',
+ 42946 => 'ꟃ',
+ 42948 => 'ꞔ',
+ 42949 => 'ʂ',
+ 42950 => 'ᶎ',
+ 42951 => 'ꟈ',
+ 42953 => 'ꟊ',
+ 42997 => 'ꟶ',
+ 43000 => 'ħ',
+ 43001 => 'œ',
+ 43868 => 'ꜧ',
+ 43869 => 'ꬷ',
+ 43870 => 'ɫ',
+ 43871 => 'ꭒ',
+ 43881 => 'ʍ',
+ 43888 => 'Ꭰ',
+ 43889 => 'Ꭱ',
+ 43890 => 'Ꭲ',
+ 43891 => 'Ꭳ',
+ 43892 => 'Ꭴ',
+ 43893 => 'Ꭵ',
+ 43894 => 'Ꭶ',
+ 43895 => 'Ꭷ',
+ 43896 => 'Ꭸ',
+ 43897 => 'Ꭹ',
+ 43898 => 'Ꭺ',
+ 43899 => 'Ꭻ',
+ 43900 => 'Ꭼ',
+ 43901 => 'Ꭽ',
+ 43902 => 'Ꭾ',
+ 43903 => 'Ꭿ',
+ 43904 => 'Ꮀ',
+ 43905 => 'Ꮁ',
+ 43906 => 'Ꮂ',
+ 43907 => 'Ꮃ',
+ 43908 => 'Ꮄ',
+ 43909 => 'Ꮅ',
+ 43910 => 'Ꮆ',
+ 43911 => 'Ꮇ',
+ 43912 => 'Ꮈ',
+ 43913 => 'Ꮉ',
+ 43914 => 'Ꮊ',
+ 43915 => 'Ꮋ',
+ 43916 => 'Ꮌ',
+ 43917 => 'Ꮍ',
+ 43918 => 'Ꮎ',
+ 43919 => 'Ꮏ',
+ 43920 => 'Ꮐ',
+ 43921 => 'Ꮑ',
+ 43922 => 'Ꮒ',
+ 43923 => 'Ꮓ',
+ 43924 => 'Ꮔ',
+ 43925 => 'Ꮕ',
+ 43926 => 'Ꮖ',
+ 43927 => 'Ꮗ',
+ 43928 => 'Ꮘ',
+ 43929 => 'Ꮙ',
+ 43930 => 'Ꮚ',
+ 43931 => 'Ꮛ',
+ 43932 => 'Ꮜ',
+ 43933 => 'Ꮝ',
+ 43934 => 'Ꮞ',
+ 43935 => 'Ꮟ',
+ 43936 => 'Ꮠ',
+ 43937 => 'Ꮡ',
+ 43938 => 'Ꮢ',
+ 43939 => 'Ꮣ',
+ 43940 => 'Ꮤ',
+ 43941 => 'Ꮥ',
+ 43942 => 'Ꮦ',
+ 43943 => 'Ꮧ',
+ 43944 => 'Ꮨ',
+ 43945 => 'Ꮩ',
+ 43946 => 'Ꮪ',
+ 43947 => 'Ꮫ',
+ 43948 => 'Ꮬ',
+ 43949 => 'Ꮭ',
+ 43950 => 'Ꮮ',
+ 43951 => 'Ꮯ',
+ 43952 => 'Ꮰ',
+ 43953 => 'Ꮱ',
+ 43954 => 'Ꮲ',
+ 43955 => 'Ꮳ',
+ 43956 => 'Ꮴ',
+ 43957 => 'Ꮵ',
+ 43958 => 'Ꮶ',
+ 43959 => 'Ꮷ',
+ 43960 => 'Ꮸ',
+ 43961 => 'Ꮹ',
+ 43962 => 'Ꮺ',
+ 43963 => 'Ꮻ',
+ 43964 => 'Ꮼ',
+ 43965 => 'Ꮽ',
+ 43966 => 'Ꮾ',
+ 43967 => 'Ꮿ',
+ 63744 => '豈',
+ 63745 => '更',
+ 63746 => '車',
+ 63747 => '賈',
+ 63748 => '滑',
+ 63749 => '串',
+ 63750 => '句',
+ 63751 => '龜',
+ 63752 => '龜',
+ 63753 => '契',
+ 63754 => '金',
+ 63755 => '喇',
+ 63756 => '奈',
+ 63757 => '懶',
+ 63758 => '癩',
+ 63759 => '羅',
+ 63760 => '蘿',
+ 63761 => '螺',
+ 63762 => '裸',
+ 63763 => '邏',
+ 63764 => '樂',
+ 63765 => '洛',
+ 63766 => '烙',
+ 63767 => '珞',
+ 63768 => '落',
+ 63769 => '酪',
+ 63770 => '駱',
+ 63771 => '亂',
+ 63772 => '卵',
+ 63773 => '欄',
+ 63774 => '爛',
+ 63775 => '蘭',
+ 63776 => '鸞',
+ 63777 => '嵐',
+ 63778 => '濫',
+ 63779 => '藍',
+ 63780 => '襤',
+ 63781 => '拉',
+ 63782 => '臘',
+ 63783 => '蠟',
+ 63784 => '廊',
+ 63785 => '朗',
+ 63786 => '浪',
+ 63787 => '狼',
+ 63788 => '郎',
+ 63789 => '來',
+ 63790 => '冷',
+ 63791 => '勞',
+ 63792 => '擄',
+ 63793 => '櫓',
+ 63794 => '爐',
+ 63795 => '盧',
+ 63796 => '老',
+ 63797 => '蘆',
+ 63798 => '虜',
+ 63799 => '路',
+ 63800 => '露',
+ 63801 => '魯',
+ 63802 => '鷺',
+ 63803 => '碌',
+ 63804 => '祿',
+ 63805 => '綠',
+ 63806 => '菉',
+ 63807 => '錄',
+ 63808 => '鹿',
+ 63809 => '論',
+ 63810 => '壟',
+ 63811 => '弄',
+ 63812 => '籠',
+ 63813 => '聾',
+ 63814 => '牢',
+ 63815 => '磊',
+ 63816 => '賂',
+ 63817 => '雷',
+ 63818 => '壘',
+ 63819 => '屢',
+ 63820 => '樓',
+ 63821 => '淚',
+ 63822 => '漏',
+ 63823 => '累',
+ 63824 => '縷',
+ 63825 => '陋',
+ 63826 => '勒',
+ 63827 => '肋',
+ 63828 => '凜',
+ 63829 => '凌',
+ 63830 => '稜',
+ 63831 => '綾',
+ 63832 => '菱',
+ 63833 => '陵',
+ 63834 => '讀',
+ 63835 => '拏',
+ 63836 => '樂',
+ 63837 => '諾',
+ 63838 => '丹',
+ 63839 => '寧',
+ 63840 => '怒',
+ 63841 => '率',
+ 63842 => '異',
+ 63843 => '北',
+ 63844 => '磻',
+ 63845 => '便',
+ 63846 => '復',
+ 63847 => '不',
+ 63848 => '泌',
+ 63849 => '數',
+ 63850 => '索',
+ 63851 => '參',
+ 63852 => '塞',
+ 63853 => '省',
+ 63854 => '葉',
+ 63855 => '說',
+ 63856 => '殺',
+ 63857 => '辰',
+ 63858 => '沈',
+ 63859 => '拾',
+ 63860 => '若',
+ 63861 => '掠',
+ 63862 => '略',
+ 63863 => '亮',
+ 63864 => '兩',
+ 63865 => '凉',
+ 63866 => '梁',
+ 63867 => '糧',
+ 63868 => '良',
+ 63869 => '諒',
+ 63870 => '量',
+ 63871 => '勵',
+ 63872 => '呂',
+ 63873 => '女',
+ 63874 => '廬',
+ 63875 => '旅',
+ 63876 => '濾',
+ 63877 => '礪',
+ 63878 => '閭',
+ 63879 => '驪',
+ 63880 => '麗',
+ 63881 => '黎',
+ 63882 => '力',
+ 63883 => '曆',
+ 63884 => '歷',
+ 63885 => '轢',
+ 63886 => '年',
+ 63887 => '憐',
+ 63888 => '戀',
+ 63889 => '撚',
+ 63890 => '漣',
+ 63891 => '煉',
+ 63892 => '璉',
+ 63893 => '秊',
+ 63894 => '練',
+ 63895 => '聯',
+ 63896 => '輦',
+ 63897 => '蓮',
+ 63898 => '連',
+ 63899 => '鍊',
+ 63900 => '列',
+ 63901 => '劣',
+ 63902 => '咽',
+ 63903 => '烈',
+ 63904 => '裂',
+ 63905 => '說',
+ 63906 => '廉',
+ 63907 => '念',
+ 63908 => '捻',
+ 63909 => '殮',
+ 63910 => '簾',
+ 63911 => '獵',
+ 63912 => '令',
+ 63913 => '囹',
+ 63914 => '寧',
+ 63915 => '嶺',
+ 63916 => '怜',
+ 63917 => '玲',
+ 63918 => '瑩',
+ 63919 => '羚',
+ 63920 => '聆',
+ 63921 => '鈴',
+ 63922 => '零',
+ 63923 => '靈',
+ 63924 => '領',
+ 63925 => '例',
+ 63926 => '禮',
+ 63927 => '醴',
+ 63928 => '隸',
+ 63929 => '惡',
+ 63930 => '了',
+ 63931 => '僚',
+ 63932 => '寮',
+ 63933 => '尿',
+ 63934 => '料',
+ 63935 => '樂',
+ 63936 => '燎',
+ 63937 => '療',
+ 63938 => '蓼',
+ 63939 => '遼',
+ 63940 => '龍',
+ 63941 => '暈',
+ 63942 => '阮',
+ 63943 => '劉',
+ 63944 => '杻',
+ 63945 => '柳',
+ 63946 => '流',
+ 63947 => '溜',
+ 63948 => '琉',
+ 63949 => '留',
+ 63950 => '硫',
+ 63951 => '紐',
+ 63952 => '類',
+ 63953 => '六',
+ 63954 => '戮',
+ 63955 => '陸',
+ 63956 => '倫',
+ 63957 => '崙',
+ 63958 => '淪',
+ 63959 => '輪',
+ 63960 => '律',
+ 63961 => '慄',
+ 63962 => '栗',
+ 63963 => '率',
+ 63964 => '隆',
+ 63965 => '利',
+ 63966 => '吏',
+ 63967 => '履',
+ 63968 => '易',
+ 63969 => '李',
+ 63970 => '梨',
+ 63971 => '泥',
+ 63972 => '理',
+ 63973 => '痢',
+ 63974 => '罹',
+ 63975 => '裏',
+ 63976 => '裡',
+ 63977 => '里',
+ 63978 => '離',
+ 63979 => '匿',
+ 63980 => '溺',
+ 63981 => '吝',
+ 63982 => '燐',
+ 63983 => '璘',
+ 63984 => '藺',
+ 63985 => '隣',
+ 63986 => '鱗',
+ 63987 => '麟',
+ 63988 => '林',
+ 63989 => '淋',
+ 63990 => '臨',
+ 63991 => '立',
+ 63992 => '笠',
+ 63993 => '粒',
+ 63994 => '狀',
+ 63995 => '炙',
+ 63996 => '識',
+ 63997 => '什',
+ 63998 => '茶',
+ 63999 => '刺',
+ 64000 => '切',
+ 64001 => '度',
+ 64002 => '拓',
+ 64003 => '糖',
+ 64004 => '宅',
+ 64005 => '洞',
+ 64006 => '暴',
+ 64007 => '輻',
+ 64008 => '行',
+ 64009 => '降',
+ 64010 => '見',
+ 64011 => '廓',
+ 64012 => '兀',
+ 64013 => '嗀',
+ 64016 => '塚',
+ 64018 => '晴',
+ 64021 => '凞',
+ 64022 => '猪',
+ 64023 => '益',
+ 64024 => '礼',
+ 64025 => '神',
+ 64026 => '祥',
+ 64027 => '福',
+ 64028 => '靖',
+ 64029 => '精',
+ 64030 => '羽',
+ 64032 => '蘒',
+ 64034 => '諸',
+ 64037 => '逸',
+ 64038 => '都',
+ 64042 => '飯',
+ 64043 => '飼',
+ 64044 => '館',
+ 64045 => '鶴',
+ 64046 => '郞',
+ 64047 => '隷',
+ 64048 => '侮',
+ 64049 => '僧',
+ 64050 => '免',
+ 64051 => '勉',
+ 64052 => '勤',
+ 64053 => '卑',
+ 64054 => '喝',
+ 64055 => '嘆',
+ 64056 => '器',
+ 64057 => '塀',
+ 64058 => '墨',
+ 64059 => '層',
+ 64060 => '屮',
+ 64061 => '悔',
+ 64062 => '慨',
+ 64063 => '憎',
+ 64064 => '懲',
+ 64065 => '敏',
+ 64066 => '既',
+ 64067 => '暑',
+ 64068 => '梅',
+ 64069 => '海',
+ 64070 => '渚',
+ 64071 => '漢',
+ 64072 => '煮',
+ 64073 => '爫',
+ 64074 => '琢',
+ 64075 => '碑',
+ 64076 => '社',
+ 64077 => '祉',
+ 64078 => '祈',
+ 64079 => '祐',
+ 64080 => '祖',
+ 64081 => '祝',
+ 64082 => '禍',
+ 64083 => '禎',
+ 64084 => '穀',
+ 64085 => '突',
+ 64086 => '節',
+ 64087 => '練',
+ 64088 => '縉',
+ 64089 => '繁',
+ 64090 => '署',
+ 64091 => '者',
+ 64092 => '臭',
+ 64093 => '艹',
+ 64094 => '艹',
+ 64095 => '著',
+ 64096 => '褐',
+ 64097 => '視',
+ 64098 => '謁',
+ 64099 => '謹',
+ 64100 => '賓',
+ 64101 => '贈',
+ 64102 => '辶',
+ 64103 => '逸',
+ 64104 => '難',
+ 64105 => '響',
+ 64106 => '頻',
+ 64107 => '恵',
+ 64108 => '𤋮',
+ 64109 => '舘',
+ 64112 => '並',
+ 64113 => '况',
+ 64114 => '全',
+ 64115 => '侀',
+ 64116 => '充',
+ 64117 => '冀',
+ 64118 => '勇',
+ 64119 => '勺',
+ 64120 => '喝',
+ 64121 => '啕',
+ 64122 => '喙',
+ 64123 => '嗢',
+ 64124 => '塚',
+ 64125 => '墳',
+ 64126 => '奄',
+ 64127 => '奔',
+ 64128 => '婢',
+ 64129 => '嬨',
+ 64130 => '廒',
+ 64131 => '廙',
+ 64132 => '彩',
+ 64133 => '徭',
+ 64134 => '惘',
+ 64135 => '慎',
+ 64136 => '愈',
+ 64137 => '憎',
+ 64138 => '慠',
+ 64139 => '懲',
+ 64140 => '戴',
+ 64141 => '揄',
+ 64142 => '搜',
+ 64143 => '摒',
+ 64144 => '敖',
+ 64145 => '晴',
+ 64146 => '朗',
+ 64147 => '望',
+ 64148 => '杖',
+ 64149 => '歹',
+ 64150 => '殺',
+ 64151 => '流',
+ 64152 => '滛',
+ 64153 => '滋',
+ 64154 => '漢',
+ 64155 => '瀞',
+ 64156 => '煮',
+ 64157 => '瞧',
+ 64158 => '爵',
+ 64159 => '犯',
+ 64160 => '猪',
+ 64161 => '瑱',
+ 64162 => '甆',
+ 64163 => '画',
+ 64164 => '瘝',
+ 64165 => '瘟',
+ 64166 => '益',
+ 64167 => '盛',
+ 64168 => '直',
+ 64169 => '睊',
+ 64170 => '着',
+ 64171 => '磌',
+ 64172 => '窱',
+ 64173 => '節',
+ 64174 => '类',
+ 64175 => '絛',
+ 64176 => '練',
+ 64177 => '缾',
+ 64178 => '者',
+ 64179 => '荒',
+ 64180 => '華',
+ 64181 => '蝹',
+ 64182 => '襁',
+ 64183 => '覆',
+ 64184 => '視',
+ 64185 => '調',
+ 64186 => '諸',
+ 64187 => '請',
+ 64188 => '謁',
+ 64189 => '諾',
+ 64190 => '諭',
+ 64191 => '謹',
+ 64192 => '變',
+ 64193 => '贈',
+ 64194 => '輸',
+ 64195 => '遲',
+ 64196 => '醙',
+ 64197 => '鉶',
+ 64198 => '陼',
+ 64199 => '難',
+ 64200 => '靖',
+ 64201 => '韛',
+ 64202 => '響',
+ 64203 => '頋',
+ 64204 => '頻',
+ 64205 => '鬒',
+ 64206 => '龜',
+ 64207 => '𢡊',
+ 64208 => '𢡄',
+ 64209 => '𣏕',
+ 64210 => '㮝',
+ 64211 => '䀘',
+ 64212 => '䀹',
+ 64213 => '𥉉',
+ 64214 => '𥳐',
+ 64215 => '𧻓',
+ 64216 => '齃',
+ 64217 => '龎',
+ 64256 => 'ff',
+ 64257 => 'fi',
+ 64258 => 'fl',
+ 64259 => 'ffi',
+ 64260 => 'ffl',
+ 64261 => 'st',
+ 64262 => 'st',
+ 64275 => 'մն',
+ 64276 => 'մե',
+ 64277 => 'մի',
+ 64278 => 'վն',
+ 64279 => 'մխ',
+ 64285 => 'יִ',
+ 64287 => 'ײַ',
+ 64288 => 'ע',
+ 64289 => 'א',
+ 64290 => 'ד',
+ 64291 => 'ה',
+ 64292 => 'כ',
+ 64293 => 'ל',
+ 64294 => 'ם',
+ 64295 => 'ר',
+ 64296 => 'ת',
+ 64298 => 'שׁ',
+ 64299 => 'שׂ',
+ 64300 => 'שּׁ',
+ 64301 => 'שּׂ',
+ 64302 => 'אַ',
+ 64303 => 'אָ',
+ 64304 => 'אּ',
+ 64305 => 'בּ',
+ 64306 => 'גּ',
+ 64307 => 'דּ',
+ 64308 => 'הּ',
+ 64309 => 'וּ',
+ 64310 => 'זּ',
+ 64312 => 'טּ',
+ 64313 => 'יּ',
+ 64314 => 'ךּ',
+ 64315 => 'כּ',
+ 64316 => 'לּ',
+ 64318 => 'מּ',
+ 64320 => 'נּ',
+ 64321 => 'סּ',
+ 64323 => 'ףּ',
+ 64324 => 'פּ',
+ 64326 => 'צּ',
+ 64327 => 'קּ',
+ 64328 => 'רּ',
+ 64329 => 'שּ',
+ 64330 => 'תּ',
+ 64331 => 'וֹ',
+ 64332 => 'בֿ',
+ 64333 => 'כֿ',
+ 64334 => 'פֿ',
+ 64335 => 'אל',
+ 64336 => 'ٱ',
+ 64337 => 'ٱ',
+ 64338 => 'ٻ',
+ 64339 => 'ٻ',
+ 64340 => 'ٻ',
+ 64341 => 'ٻ',
+ 64342 => 'پ',
+ 64343 => 'پ',
+ 64344 => 'پ',
+ 64345 => 'پ',
+ 64346 => 'ڀ',
+ 64347 => 'ڀ',
+ 64348 => 'ڀ',
+ 64349 => 'ڀ',
+ 64350 => 'ٺ',
+ 64351 => 'ٺ',
+ 64352 => 'ٺ',
+ 64353 => 'ٺ',
+ 64354 => 'ٿ',
+ 64355 => 'ٿ',
+ 64356 => 'ٿ',
+ 64357 => 'ٿ',
+ 64358 => 'ٹ',
+ 64359 => 'ٹ',
+ 64360 => 'ٹ',
+ 64361 => 'ٹ',
+ 64362 => 'ڤ',
+ 64363 => 'ڤ',
+ 64364 => 'ڤ',
+ 64365 => 'ڤ',
+ 64366 => 'ڦ',
+ 64367 => 'ڦ',
+ 64368 => 'ڦ',
+ 64369 => 'ڦ',
+ 64370 => 'ڄ',
+ 64371 => 'ڄ',
+ 64372 => 'ڄ',
+ 64373 => 'ڄ',
+ 64374 => 'ڃ',
+ 64375 => 'ڃ',
+ 64376 => 'ڃ',
+ 64377 => 'ڃ',
+ 64378 => 'چ',
+ 64379 => 'چ',
+ 64380 => 'چ',
+ 64381 => 'چ',
+ 64382 => 'ڇ',
+ 64383 => 'ڇ',
+ 64384 => 'ڇ',
+ 64385 => 'ڇ',
+ 64386 => 'ڍ',
+ 64387 => 'ڍ',
+ 64388 => 'ڌ',
+ 64389 => 'ڌ',
+ 64390 => 'ڎ',
+ 64391 => 'ڎ',
+ 64392 => 'ڈ',
+ 64393 => 'ڈ',
+ 64394 => 'ژ',
+ 64395 => 'ژ',
+ 64396 => 'ڑ',
+ 64397 => 'ڑ',
+ 64398 => 'ک',
+ 64399 => 'ک',
+ 64400 => 'ک',
+ 64401 => 'ک',
+ 64402 => 'گ',
+ 64403 => 'گ',
+ 64404 => 'گ',
+ 64405 => 'گ',
+ 64406 => 'ڳ',
+ 64407 => 'ڳ',
+ 64408 => 'ڳ',
+ 64409 => 'ڳ',
+ 64410 => 'ڱ',
+ 64411 => 'ڱ',
+ 64412 => 'ڱ',
+ 64413 => 'ڱ',
+ 64414 => 'ں',
+ 64415 => 'ں',
+ 64416 => 'ڻ',
+ 64417 => 'ڻ',
+ 64418 => 'ڻ',
+ 64419 => 'ڻ',
+ 64420 => 'ۀ',
+ 64421 => 'ۀ',
+ 64422 => 'ہ',
+ 64423 => 'ہ',
+ 64424 => 'ہ',
+ 64425 => 'ہ',
+ 64426 => 'ھ',
+ 64427 => 'ھ',
+ 64428 => 'ھ',
+ 64429 => 'ھ',
+ 64430 => 'ے',
+ 64431 => 'ے',
+ 64432 => 'ۓ',
+ 64433 => 'ۓ',
+ 64467 => 'ڭ',
+ 64468 => 'ڭ',
+ 64469 => 'ڭ',
+ 64470 => 'ڭ',
+ 64471 => 'ۇ',
+ 64472 => 'ۇ',
+ 64473 => 'ۆ',
+ 64474 => 'ۆ',
+ 64475 => 'ۈ',
+ 64476 => 'ۈ',
+ 64477 => 'ۇٴ',
+ 64478 => 'ۋ',
+ 64479 => 'ۋ',
+ 64480 => 'ۅ',
+ 64481 => 'ۅ',
+ 64482 => 'ۉ',
+ 64483 => 'ۉ',
+ 64484 => 'ې',
+ 64485 => 'ې',
+ 64486 => 'ې',
+ 64487 => 'ې',
+ 64488 => 'ى',
+ 64489 => 'ى',
+ 64490 => 'ئا',
+ 64491 => 'ئا',
+ 64492 => 'ئە',
+ 64493 => 'ئە',
+ 64494 => 'ئو',
+ 64495 => 'ئو',
+ 64496 => 'ئۇ',
+ 64497 => 'ئۇ',
+ 64498 => 'ئۆ',
+ 64499 => 'ئۆ',
+ 64500 => 'ئۈ',
+ 64501 => 'ئۈ',
+ 64502 => 'ئې',
+ 64503 => 'ئې',
+ 64504 => 'ئې',
+ 64505 => 'ئى',
+ 64506 => 'ئى',
+ 64507 => 'ئى',
+ 64508 => 'ی',
+ 64509 => 'ی',
+ 64510 => 'ی',
+ 64511 => 'ی',
+ 64512 => 'ئج',
+ 64513 => 'ئح',
+ 64514 => 'ئم',
+ 64515 => 'ئى',
+ 64516 => 'ئي',
+ 64517 => 'بج',
+ 64518 => 'بح',
+ 64519 => 'بخ',
+ 64520 => 'بم',
+ 64521 => 'بى',
+ 64522 => 'بي',
+ 64523 => 'تج',
+ 64524 => 'تح',
+ 64525 => 'تخ',
+ 64526 => 'تم',
+ 64527 => 'تى',
+ 64528 => 'تي',
+ 64529 => 'ثج',
+ 64530 => 'ثم',
+ 64531 => 'ثى',
+ 64532 => 'ثي',
+ 64533 => 'جح',
+ 64534 => 'جم',
+ 64535 => 'حج',
+ 64536 => 'حم',
+ 64537 => 'خج',
+ 64538 => 'خح',
+ 64539 => 'خم',
+ 64540 => 'سج',
+ 64541 => 'سح',
+ 64542 => 'سخ',
+ 64543 => 'سم',
+ 64544 => 'صح',
+ 64545 => 'صم',
+ 64546 => 'ضج',
+ 64547 => 'ضح',
+ 64548 => 'ضخ',
+ 64549 => 'ضم',
+ 64550 => 'طح',
+ 64551 => 'طم',
+ 64552 => 'ظم',
+ 64553 => 'عج',
+ 64554 => 'عم',
+ 64555 => 'غج',
+ 64556 => 'غم',
+ 64557 => 'فج',
+ 64558 => 'فح',
+ 64559 => 'فخ',
+ 64560 => 'فم',
+ 64561 => 'فى',
+ 64562 => 'في',
+ 64563 => 'قح',
+ 64564 => 'قم',
+ 64565 => 'قى',
+ 64566 => 'قي',
+ 64567 => 'كا',
+ 64568 => 'كج',
+ 64569 => 'كح',
+ 64570 => 'كخ',
+ 64571 => 'كل',
+ 64572 => 'كم',
+ 64573 => 'كى',
+ 64574 => 'كي',
+ 64575 => 'لج',
+ 64576 => 'لح',
+ 64577 => 'لخ',
+ 64578 => 'لم',
+ 64579 => 'لى',
+ 64580 => 'لي',
+ 64581 => 'مج',
+ 64582 => 'مح',
+ 64583 => 'مخ',
+ 64584 => 'مم',
+ 64585 => 'مى',
+ 64586 => 'مي',
+ 64587 => 'نج',
+ 64588 => 'نح',
+ 64589 => 'نخ',
+ 64590 => 'نم',
+ 64591 => 'نى',
+ 64592 => 'ني',
+ 64593 => 'هج',
+ 64594 => 'هم',
+ 64595 => 'هى',
+ 64596 => 'هي',
+ 64597 => 'يج',
+ 64598 => 'يح',
+ 64599 => 'يخ',
+ 64600 => 'يم',
+ 64601 => 'يى',
+ 64602 => 'يي',
+ 64603 => 'ذٰ',
+ 64604 => 'رٰ',
+ 64605 => 'ىٰ',
+ 64612 => 'ئر',
+ 64613 => 'ئز',
+ 64614 => 'ئم',
+ 64615 => 'ئن',
+ 64616 => 'ئى',
+ 64617 => 'ئي',
+ 64618 => 'بر',
+ 64619 => 'بز',
+ 64620 => 'بم',
+ 64621 => 'بن',
+ 64622 => 'بى',
+ 64623 => 'بي',
+ 64624 => 'تر',
+ 64625 => 'تز',
+ 64626 => 'تم',
+ 64627 => 'تن',
+ 64628 => 'تى',
+ 64629 => 'تي',
+ 64630 => 'ثر',
+ 64631 => 'ثز',
+ 64632 => 'ثم',
+ 64633 => 'ثن',
+ 64634 => 'ثى',
+ 64635 => 'ثي',
+ 64636 => 'فى',
+ 64637 => 'في',
+ 64638 => 'قى',
+ 64639 => 'قي',
+ 64640 => 'كا',
+ 64641 => 'كل',
+ 64642 => 'كم',
+ 64643 => 'كى',
+ 64644 => 'كي',
+ 64645 => 'لم',
+ 64646 => 'لى',
+ 64647 => 'لي',
+ 64648 => 'ما',
+ 64649 => 'مم',
+ 64650 => 'نر',
+ 64651 => 'نز',
+ 64652 => 'نم',
+ 64653 => 'نن',
+ 64654 => 'نى',
+ 64655 => 'ني',
+ 64656 => 'ىٰ',
+ 64657 => 'ير',
+ 64658 => 'يز',
+ 64659 => 'يم',
+ 64660 => 'ين',
+ 64661 => 'يى',
+ 64662 => 'يي',
+ 64663 => 'ئج',
+ 64664 => 'ئح',
+ 64665 => 'ئخ',
+ 64666 => 'ئم',
+ 64667 => 'ئه',
+ 64668 => 'بج',
+ 64669 => 'بح',
+ 64670 => 'بخ',
+ 64671 => 'بم',
+ 64672 => 'به',
+ 64673 => 'تج',
+ 64674 => 'تح',
+ 64675 => 'تخ',
+ 64676 => 'تم',
+ 64677 => 'ته',
+ 64678 => 'ثم',
+ 64679 => 'جح',
+ 64680 => 'جم',
+ 64681 => 'حج',
+ 64682 => 'حم',
+ 64683 => 'خج',
+ 64684 => 'خم',
+ 64685 => 'سج',
+ 64686 => 'سح',
+ 64687 => 'سخ',
+ 64688 => 'سم',
+ 64689 => 'صح',
+ 64690 => 'صخ',
+ 64691 => 'صم',
+ 64692 => 'ضج',
+ 64693 => 'ضح',
+ 64694 => 'ضخ',
+ 64695 => 'ضم',
+ 64696 => 'طح',
+ 64697 => 'ظم',
+ 64698 => 'عج',
+ 64699 => 'عم',
+ 64700 => 'غج',
+ 64701 => 'غم',
+ 64702 => 'فج',
+ 64703 => 'فح',
+ 64704 => 'فخ',
+ 64705 => 'فم',
+ 64706 => 'قح',
+ 64707 => 'قم',
+ 64708 => 'كج',
+ 64709 => 'كح',
+ 64710 => 'كخ',
+ 64711 => 'كل',
+ 64712 => 'كم',
+ 64713 => 'لج',
+ 64714 => 'لح',
+ 64715 => 'لخ',
+ 64716 => 'لم',
+ 64717 => 'له',
+ 64718 => 'مج',
+ 64719 => 'مح',
+ 64720 => 'مخ',
+ 64721 => 'مم',
+ 64722 => 'نج',
+ 64723 => 'نح',
+ 64724 => 'نخ',
+ 64725 => 'نم',
+ 64726 => 'نه',
+ 64727 => 'هج',
+ 64728 => 'هم',
+ 64729 => 'هٰ',
+ 64730 => 'يج',
+ 64731 => 'يح',
+ 64732 => 'يخ',
+ 64733 => 'يم',
+ 64734 => 'يه',
+ 64735 => 'ئم',
+ 64736 => 'ئه',
+ 64737 => 'بم',
+ 64738 => 'به',
+ 64739 => 'تم',
+ 64740 => 'ته',
+ 64741 => 'ثم',
+ 64742 => 'ثه',
+ 64743 => 'سم',
+ 64744 => 'سه',
+ 64745 => 'شم',
+ 64746 => 'شه',
+ 64747 => 'كل',
+ 64748 => 'كم',
+ 64749 => 'لم',
+ 64750 => 'نم',
+ 64751 => 'نه',
+ 64752 => 'يم',
+ 64753 => 'يه',
+ 64754 => 'ـَّ',
+ 64755 => 'ـُّ',
+ 64756 => 'ـِّ',
+ 64757 => 'طى',
+ 64758 => 'طي',
+ 64759 => 'عى',
+ 64760 => 'عي',
+ 64761 => 'غى',
+ 64762 => 'غي',
+ 64763 => 'سى',
+ 64764 => 'سي',
+ 64765 => 'شى',
+ 64766 => 'شي',
+ 64767 => 'حى',
+ 64768 => 'حي',
+ 64769 => 'جى',
+ 64770 => 'جي',
+ 64771 => 'خى',
+ 64772 => 'خي',
+ 64773 => 'صى',
+ 64774 => 'صي',
+ 64775 => 'ضى',
+ 64776 => 'ضي',
+ 64777 => 'شج',
+ 64778 => 'شح',
+ 64779 => 'شخ',
+ 64780 => 'شم',
+ 64781 => 'شر',
+ 64782 => 'سر',
+ 64783 => 'صر',
+ 64784 => 'ضر',
+ 64785 => 'طى',
+ 64786 => 'طي',
+ 64787 => 'عى',
+ 64788 => 'عي',
+ 64789 => 'غى',
+ 64790 => 'غي',
+ 64791 => 'سى',
+ 64792 => 'سي',
+ 64793 => 'شى',
+ 64794 => 'شي',
+ 64795 => 'حى',
+ 64796 => 'حي',
+ 64797 => 'جى',
+ 64798 => 'جي',
+ 64799 => 'خى',
+ 64800 => 'خي',
+ 64801 => 'صى',
+ 64802 => 'صي',
+ 64803 => 'ضى',
+ 64804 => 'ضي',
+ 64805 => 'شج',
+ 64806 => 'شح',
+ 64807 => 'شخ',
+ 64808 => 'شم',
+ 64809 => 'شر',
+ 64810 => 'سر',
+ 64811 => 'صر',
+ 64812 => 'ضر',
+ 64813 => 'شج',
+ 64814 => 'شح',
+ 64815 => 'شخ',
+ 64816 => 'شم',
+ 64817 => 'سه',
+ 64818 => 'شه',
+ 64819 => 'طم',
+ 64820 => 'سج',
+ 64821 => 'سح',
+ 64822 => 'سخ',
+ 64823 => 'شج',
+ 64824 => 'شح',
+ 64825 => 'شخ',
+ 64826 => 'طم',
+ 64827 => 'ظم',
+ 64828 => 'اً',
+ 64829 => 'اً',
+ 64848 => 'تجم',
+ 64849 => 'تحج',
+ 64850 => 'تحج',
+ 64851 => 'تحم',
+ 64852 => 'تخم',
+ 64853 => 'تمج',
+ 64854 => 'تمح',
+ 64855 => 'تمخ',
+ 64856 => 'جمح',
+ 64857 => 'جمح',
+ 64858 => 'حمي',
+ 64859 => 'حمى',
+ 64860 => 'سحج',
+ 64861 => 'سجح',
+ 64862 => 'سجى',
+ 64863 => 'سمح',
+ 64864 => 'سمح',
+ 64865 => 'سمج',
+ 64866 => 'سمم',
+ 64867 => 'سمم',
+ 64868 => 'صحح',
+ 64869 => 'صحح',
+ 64870 => 'صمم',
+ 64871 => 'شحم',
+ 64872 => 'شحم',
+ 64873 => 'شجي',
+ 64874 => 'شمخ',
+ 64875 => 'شمخ',
+ 64876 => 'شمم',
+ 64877 => 'شمم',
+ 64878 => 'ضحى',
+ 64879 => 'ضخم',
+ 64880 => 'ضخم',
+ 64881 => 'طمح',
+ 64882 => 'طمح',
+ 64883 => 'طمم',
+ 64884 => 'طمي',
+ 64885 => 'عجم',
+ 64886 => 'عمم',
+ 64887 => 'عمم',
+ 64888 => 'عمى',
+ 64889 => 'غمم',
+ 64890 => 'غمي',
+ 64891 => 'غمى',
+ 64892 => 'فخم',
+ 64893 => 'فخم',
+ 64894 => 'قمح',
+ 64895 => 'قمم',
+ 64896 => 'لحم',
+ 64897 => 'لحي',
+ 64898 => 'لحى',
+ 64899 => 'لجج',
+ 64900 => 'لجج',
+ 64901 => 'لخم',
+ 64902 => 'لخم',
+ 64903 => 'لمح',
+ 64904 => 'لمح',
+ 64905 => 'محج',
+ 64906 => 'محم',
+ 64907 => 'محي',
+ 64908 => 'مجح',
+ 64909 => 'مجم',
+ 64910 => 'مخج',
+ 64911 => 'مخم',
+ 64914 => 'مجخ',
+ 64915 => 'همج',
+ 64916 => 'همم',
+ 64917 => 'نحم',
+ 64918 => 'نحى',
+ 64919 => 'نجم',
+ 64920 => 'نجم',
+ 64921 => 'نجى',
+ 64922 => 'نمي',
+ 64923 => 'نمى',
+ 64924 => 'يمم',
+ 64925 => 'يمم',
+ 64926 => 'بخي',
+ 64927 => 'تجي',
+ 64928 => 'تجى',
+ 64929 => 'تخي',
+ 64930 => 'تخى',
+ 64931 => 'تمي',
+ 64932 => 'تمى',
+ 64933 => 'جمي',
+ 64934 => 'جحى',
+ 64935 => 'جمى',
+ 64936 => 'سخى',
+ 64937 => 'صحي',
+ 64938 => 'شحي',
+ 64939 => 'ضحي',
+ 64940 => 'لجي',
+ 64941 => 'لمي',
+ 64942 => 'يحي',
+ 64943 => 'يجي',
+ 64944 => 'يمي',
+ 64945 => 'ممي',
+ 64946 => 'قمي',
+ 64947 => 'نحي',
+ 64948 => 'قمح',
+ 64949 => 'لحم',
+ 64950 => 'عمي',
+ 64951 => 'كمي',
+ 64952 => 'نجح',
+ 64953 => 'مخي',
+ 64954 => 'لجم',
+ 64955 => 'كمم',
+ 64956 => 'لجم',
+ 64957 => 'نجح',
+ 64958 => 'جحي',
+ 64959 => 'حجي',
+ 64960 => 'مجي',
+ 64961 => 'فمي',
+ 64962 => 'بحي',
+ 64963 => 'كمم',
+ 64964 => 'عجم',
+ 64965 => 'صمم',
+ 64966 => 'سخي',
+ 64967 => 'نجي',
+ 65008 => 'صلے',
+ 65009 => 'قلے',
+ 65010 => 'الله',
+ 65011 => 'اكبر',
+ 65012 => 'محمد',
+ 65013 => 'صلعم',
+ 65014 => 'رسول',
+ 65015 => 'عليه',
+ 65016 => 'وسلم',
+ 65017 => 'صلى',
+ 65020 => 'ریال',
+ 65041 => '、',
+ 65047 => '〖',
+ 65048 => '〗',
+ 65073 => '—',
+ 65074 => '–',
+ 65081 => '〔',
+ 65082 => '〕',
+ 65083 => '【',
+ 65084 => '】',
+ 65085 => '《',
+ 65086 => '》',
+ 65087 => '〈',
+ 65088 => '〉',
+ 65089 => '「',
+ 65090 => '」',
+ 65091 => '『',
+ 65092 => '』',
+ 65105 => '、',
+ 65112 => '—',
+ 65117 => '〔',
+ 65118 => '〕',
+ 65123 => '-',
+ 65137 => 'ـً',
+ 65143 => 'ـَ',
+ 65145 => 'ـُ',
+ 65147 => 'ـِ',
+ 65149 => 'ـّ',
+ 65151 => 'ـْ',
+ 65152 => 'ء',
+ 65153 => 'آ',
+ 65154 => 'آ',
+ 65155 => 'أ',
+ 65156 => 'أ',
+ 65157 => 'ؤ',
+ 65158 => 'ؤ',
+ 65159 => 'إ',
+ 65160 => 'إ',
+ 65161 => 'ئ',
+ 65162 => 'ئ',
+ 65163 => 'ئ',
+ 65164 => 'ئ',
+ 65165 => 'ا',
+ 65166 => 'ا',
+ 65167 => 'ب',
+ 65168 => 'ب',
+ 65169 => 'ب',
+ 65170 => 'ب',
+ 65171 => 'ة',
+ 65172 => 'ة',
+ 65173 => 'ت',
+ 65174 => 'ت',
+ 65175 => 'ت',
+ 65176 => 'ت',
+ 65177 => 'ث',
+ 65178 => 'ث',
+ 65179 => 'ث',
+ 65180 => 'ث',
+ 65181 => 'ج',
+ 65182 => 'ج',
+ 65183 => 'ج',
+ 65184 => 'ج',
+ 65185 => 'ح',
+ 65186 => 'ح',
+ 65187 => 'ح',
+ 65188 => 'ح',
+ 65189 => 'خ',
+ 65190 => 'خ',
+ 65191 => 'خ',
+ 65192 => 'خ',
+ 65193 => 'د',
+ 65194 => 'د',
+ 65195 => 'ذ',
+ 65196 => 'ذ',
+ 65197 => 'ر',
+ 65198 => 'ر',
+ 65199 => 'ز',
+ 65200 => 'ز',
+ 65201 => 'س',
+ 65202 => 'س',
+ 65203 => 'س',
+ 65204 => 'س',
+ 65205 => 'ش',
+ 65206 => 'ش',
+ 65207 => 'ش',
+ 65208 => 'ش',
+ 65209 => 'ص',
+ 65210 => 'ص',
+ 65211 => 'ص',
+ 65212 => 'ص',
+ 65213 => 'ض',
+ 65214 => 'ض',
+ 65215 => 'ض',
+ 65216 => 'ض',
+ 65217 => 'ط',
+ 65218 => 'ط',
+ 65219 => 'ط',
+ 65220 => 'ط',
+ 65221 => 'ظ',
+ 65222 => 'ظ',
+ 65223 => 'ظ',
+ 65224 => 'ظ',
+ 65225 => 'ع',
+ 65226 => 'ع',
+ 65227 => 'ع',
+ 65228 => 'ع',
+ 65229 => 'غ',
+ 65230 => 'غ',
+ 65231 => 'غ',
+ 65232 => 'غ',
+ 65233 => 'ف',
+ 65234 => 'ف',
+ 65235 => 'ف',
+ 65236 => 'ف',
+ 65237 => 'ق',
+ 65238 => 'ق',
+ 65239 => 'ق',
+ 65240 => 'ق',
+ 65241 => 'ك',
+ 65242 => 'ك',
+ 65243 => 'ك',
+ 65244 => 'ك',
+ 65245 => 'ل',
+ 65246 => 'ل',
+ 65247 => 'ل',
+ 65248 => 'ل',
+ 65249 => 'م',
+ 65250 => 'م',
+ 65251 => 'م',
+ 65252 => 'م',
+ 65253 => 'ن',
+ 65254 => 'ن',
+ 65255 => 'ن',
+ 65256 => 'ن',
+ 65257 => 'ه',
+ 65258 => 'ه',
+ 65259 => 'ه',
+ 65260 => 'ه',
+ 65261 => 'و',
+ 65262 => 'و',
+ 65263 => 'ى',
+ 65264 => 'ى',
+ 65265 => 'ي',
+ 65266 => 'ي',
+ 65267 => 'ي',
+ 65268 => 'ي',
+ 65269 => 'لآ',
+ 65270 => 'لآ',
+ 65271 => 'لأ',
+ 65272 => 'لأ',
+ 65273 => 'لإ',
+ 65274 => 'لإ',
+ 65275 => 'لا',
+ 65276 => 'لا',
+ 65293 => '-',
+ 65294 => '.',
+ 65296 => '0',
+ 65297 => '1',
+ 65298 => '2',
+ 65299 => '3',
+ 65300 => '4',
+ 65301 => '5',
+ 65302 => '6',
+ 65303 => '7',
+ 65304 => '8',
+ 65305 => '9',
+ 65313 => 'a',
+ 65314 => 'b',
+ 65315 => 'c',
+ 65316 => 'd',
+ 65317 => 'e',
+ 65318 => 'f',
+ 65319 => 'g',
+ 65320 => 'h',
+ 65321 => 'i',
+ 65322 => 'j',
+ 65323 => 'k',
+ 65324 => 'l',
+ 65325 => 'm',
+ 65326 => 'n',
+ 65327 => 'o',
+ 65328 => 'p',
+ 65329 => 'q',
+ 65330 => 'r',
+ 65331 => 's',
+ 65332 => 't',
+ 65333 => 'u',
+ 65334 => 'v',
+ 65335 => 'w',
+ 65336 => 'x',
+ 65337 => 'y',
+ 65338 => 'z',
+ 65345 => 'a',
+ 65346 => 'b',
+ 65347 => 'c',
+ 65348 => 'd',
+ 65349 => 'e',
+ 65350 => 'f',
+ 65351 => 'g',
+ 65352 => 'h',
+ 65353 => 'i',
+ 65354 => 'j',
+ 65355 => 'k',
+ 65356 => 'l',
+ 65357 => 'm',
+ 65358 => 'n',
+ 65359 => 'o',
+ 65360 => 'p',
+ 65361 => 'q',
+ 65362 => 'r',
+ 65363 => 's',
+ 65364 => 't',
+ 65365 => 'u',
+ 65366 => 'v',
+ 65367 => 'w',
+ 65368 => 'x',
+ 65369 => 'y',
+ 65370 => 'z',
+ 65375 => '⦅',
+ 65376 => '⦆',
+ 65377 => '.',
+ 65378 => '「',
+ 65379 => '」',
+ 65380 => '、',
+ 65381 => '・',
+ 65382 => 'ヲ',
+ 65383 => 'ァ',
+ 65384 => 'ィ',
+ 65385 => 'ゥ',
+ 65386 => 'ェ',
+ 65387 => 'ォ',
+ 65388 => 'ャ',
+ 65389 => 'ュ',
+ 65390 => 'ョ',
+ 65391 => 'ッ',
+ 65392 => 'ー',
+ 65393 => 'ア',
+ 65394 => 'イ',
+ 65395 => 'ウ',
+ 65396 => 'エ',
+ 65397 => 'オ',
+ 65398 => 'カ',
+ 65399 => 'キ',
+ 65400 => 'ク',
+ 65401 => 'ケ',
+ 65402 => 'コ',
+ 65403 => 'サ',
+ 65404 => 'シ',
+ 65405 => 'ス',
+ 65406 => 'セ',
+ 65407 => 'ソ',
+ 65408 => 'タ',
+ 65409 => 'チ',
+ 65410 => 'ツ',
+ 65411 => 'テ',
+ 65412 => 'ト',
+ 65413 => 'ナ',
+ 65414 => 'ニ',
+ 65415 => 'ヌ',
+ 65416 => 'ネ',
+ 65417 => 'ノ',
+ 65418 => 'ハ',
+ 65419 => 'ヒ',
+ 65420 => 'フ',
+ 65421 => 'ヘ',
+ 65422 => 'ホ',
+ 65423 => 'マ',
+ 65424 => 'ミ',
+ 65425 => 'ム',
+ 65426 => 'メ',
+ 65427 => 'モ',
+ 65428 => 'ヤ',
+ 65429 => 'ユ',
+ 65430 => 'ヨ',
+ 65431 => 'ラ',
+ 65432 => 'リ',
+ 65433 => 'ル',
+ 65434 => 'レ',
+ 65435 => 'ロ',
+ 65436 => 'ワ',
+ 65437 => 'ン',
+ 65438 => '゙',
+ 65439 => '゚',
+ 65441 => 'ᄀ',
+ 65442 => 'ᄁ',
+ 65443 => 'ᆪ',
+ 65444 => 'ᄂ',
+ 65445 => 'ᆬ',
+ 65446 => 'ᆭ',
+ 65447 => 'ᄃ',
+ 65448 => 'ᄄ',
+ 65449 => 'ᄅ',
+ 65450 => 'ᆰ',
+ 65451 => 'ᆱ',
+ 65452 => 'ᆲ',
+ 65453 => 'ᆳ',
+ 65454 => 'ᆴ',
+ 65455 => 'ᆵ',
+ 65456 => 'ᄚ',
+ 65457 => 'ᄆ',
+ 65458 => 'ᄇ',
+ 65459 => 'ᄈ',
+ 65460 => 'ᄡ',
+ 65461 => 'ᄉ',
+ 65462 => 'ᄊ',
+ 65463 => 'ᄋ',
+ 65464 => 'ᄌ',
+ 65465 => 'ᄍ',
+ 65466 => 'ᄎ',
+ 65467 => 'ᄏ',
+ 65468 => 'ᄐ',
+ 65469 => 'ᄑ',
+ 65470 => 'ᄒ',
+ 65474 => 'ᅡ',
+ 65475 => 'ᅢ',
+ 65476 => 'ᅣ',
+ 65477 => 'ᅤ',
+ 65478 => 'ᅥ',
+ 65479 => 'ᅦ',
+ 65482 => 'ᅧ',
+ 65483 => 'ᅨ',
+ 65484 => 'ᅩ',
+ 65485 => 'ᅪ',
+ 65486 => 'ᅫ',
+ 65487 => 'ᅬ',
+ 65490 => 'ᅭ',
+ 65491 => 'ᅮ',
+ 65492 => 'ᅯ',
+ 65493 => 'ᅰ',
+ 65494 => 'ᅱ',
+ 65495 => 'ᅲ',
+ 65498 => 'ᅳ',
+ 65499 => 'ᅴ',
+ 65500 => 'ᅵ',
+ 65504 => '¢',
+ 65505 => '£',
+ 65506 => '¬',
+ 65508 => '¦',
+ 65509 => '¥',
+ 65510 => '₩',
+ 65512 => '│',
+ 65513 => '←',
+ 65514 => '↑',
+ 65515 => '→',
+ 65516 => '↓',
+ 65517 => '■',
+ 65518 => '○',
+ 66560 => '𐐨',
+ 66561 => '𐐩',
+ 66562 => '𐐪',
+ 66563 => '𐐫',
+ 66564 => '𐐬',
+ 66565 => '𐐭',
+ 66566 => '𐐮',
+ 66567 => '𐐯',
+ 66568 => '𐐰',
+ 66569 => '𐐱',
+ 66570 => '𐐲',
+ 66571 => '𐐳',
+ 66572 => '𐐴',
+ 66573 => '𐐵',
+ 66574 => '𐐶',
+ 66575 => '𐐷',
+ 66576 => '𐐸',
+ 66577 => '𐐹',
+ 66578 => '𐐺',
+ 66579 => '𐐻',
+ 66580 => '𐐼',
+ 66581 => '𐐽',
+ 66582 => '𐐾',
+ 66583 => '𐐿',
+ 66584 => '𐑀',
+ 66585 => '𐑁',
+ 66586 => '𐑂',
+ 66587 => '𐑃',
+ 66588 => '𐑄',
+ 66589 => '𐑅',
+ 66590 => '𐑆',
+ 66591 => '𐑇',
+ 66592 => '𐑈',
+ 66593 => '𐑉',
+ 66594 => '𐑊',
+ 66595 => '𐑋',
+ 66596 => '𐑌',
+ 66597 => '𐑍',
+ 66598 => '𐑎',
+ 66599 => '𐑏',
+ 66736 => '𐓘',
+ 66737 => '𐓙',
+ 66738 => '𐓚',
+ 66739 => '𐓛',
+ 66740 => '𐓜',
+ 66741 => '𐓝',
+ 66742 => '𐓞',
+ 66743 => '𐓟',
+ 66744 => '𐓠',
+ 66745 => '𐓡',
+ 66746 => '𐓢',
+ 66747 => '𐓣',
+ 66748 => '𐓤',
+ 66749 => '𐓥',
+ 66750 => '𐓦',
+ 66751 => '𐓧',
+ 66752 => '𐓨',
+ 66753 => '𐓩',
+ 66754 => '𐓪',
+ 66755 => '𐓫',
+ 66756 => '𐓬',
+ 66757 => '𐓭',
+ 66758 => '𐓮',
+ 66759 => '𐓯',
+ 66760 => '𐓰',
+ 66761 => '𐓱',
+ 66762 => '𐓲',
+ 66763 => '𐓳',
+ 66764 => '𐓴',
+ 66765 => '𐓵',
+ 66766 => '𐓶',
+ 66767 => '𐓷',
+ 66768 => '𐓸',
+ 66769 => '𐓹',
+ 66770 => '𐓺',
+ 66771 => '𐓻',
+ 68736 => '𐳀',
+ 68737 => '𐳁',
+ 68738 => '𐳂',
+ 68739 => '𐳃',
+ 68740 => '𐳄',
+ 68741 => '𐳅',
+ 68742 => '𐳆',
+ 68743 => '𐳇',
+ 68744 => '𐳈',
+ 68745 => '𐳉',
+ 68746 => '𐳊',
+ 68747 => '𐳋',
+ 68748 => '𐳌',
+ 68749 => '𐳍',
+ 68750 => '𐳎',
+ 68751 => '𐳏',
+ 68752 => '𐳐',
+ 68753 => '𐳑',
+ 68754 => '𐳒',
+ 68755 => '𐳓',
+ 68756 => '𐳔',
+ 68757 => '𐳕',
+ 68758 => '𐳖',
+ 68759 => '𐳗',
+ 68760 => '𐳘',
+ 68761 => '𐳙',
+ 68762 => '𐳚',
+ 68763 => '𐳛',
+ 68764 => '𐳜',
+ 68765 => '𐳝',
+ 68766 => '𐳞',
+ 68767 => '𐳟',
+ 68768 => '𐳠',
+ 68769 => '𐳡',
+ 68770 => '𐳢',
+ 68771 => '𐳣',
+ 68772 => '𐳤',
+ 68773 => '𐳥',
+ 68774 => '𐳦',
+ 68775 => '𐳧',
+ 68776 => '𐳨',
+ 68777 => '𐳩',
+ 68778 => '𐳪',
+ 68779 => '𐳫',
+ 68780 => '𐳬',
+ 68781 => '𐳭',
+ 68782 => '𐳮',
+ 68783 => '𐳯',
+ 68784 => '𐳰',
+ 68785 => '𐳱',
+ 68786 => '𐳲',
+ 71840 => '𑣀',
+ 71841 => '𑣁',
+ 71842 => '𑣂',
+ 71843 => '𑣃',
+ 71844 => '𑣄',
+ 71845 => '𑣅',
+ 71846 => '𑣆',
+ 71847 => '𑣇',
+ 71848 => '𑣈',
+ 71849 => '𑣉',
+ 71850 => '𑣊',
+ 71851 => '𑣋',
+ 71852 => '𑣌',
+ 71853 => '𑣍',
+ 71854 => '𑣎',
+ 71855 => '𑣏',
+ 71856 => '𑣐',
+ 71857 => '𑣑',
+ 71858 => '𑣒',
+ 71859 => '𑣓',
+ 71860 => '𑣔',
+ 71861 => '𑣕',
+ 71862 => '𑣖',
+ 71863 => '𑣗',
+ 71864 => '𑣘',
+ 71865 => '𑣙',
+ 71866 => '𑣚',
+ 71867 => '𑣛',
+ 71868 => '𑣜',
+ 71869 => '𑣝',
+ 71870 => '𑣞',
+ 71871 => '𑣟',
+ 93760 => '𖹠',
+ 93761 => '𖹡',
+ 93762 => '𖹢',
+ 93763 => '𖹣',
+ 93764 => '𖹤',
+ 93765 => '𖹥',
+ 93766 => '𖹦',
+ 93767 => '𖹧',
+ 93768 => '𖹨',
+ 93769 => '𖹩',
+ 93770 => '𖹪',
+ 93771 => '𖹫',
+ 93772 => '𖹬',
+ 93773 => '𖹭',
+ 93774 => '𖹮',
+ 93775 => '𖹯',
+ 93776 => '𖹰',
+ 93777 => '𖹱',
+ 93778 => '𖹲',
+ 93779 => '𖹳',
+ 93780 => '𖹴',
+ 93781 => '𖹵',
+ 93782 => '𖹶',
+ 93783 => '𖹷',
+ 93784 => '𖹸',
+ 93785 => '𖹹',
+ 93786 => '𖹺',
+ 93787 => '𖹻',
+ 93788 => '𖹼',
+ 93789 => '𖹽',
+ 93790 => '𖹾',
+ 93791 => '𖹿',
+ 119134 => '𝅗𝅥',
+ 119135 => '𝅘𝅥',
+ 119136 => '𝅘𝅥𝅮',
+ 119137 => '𝅘𝅥𝅯',
+ 119138 => '𝅘𝅥𝅰',
+ 119139 => '𝅘𝅥𝅱',
+ 119140 => '𝅘𝅥𝅲',
+ 119227 => '𝆹𝅥',
+ 119228 => '𝆺𝅥',
+ 119229 => '𝆹𝅥𝅮',
+ 119230 => '𝆺𝅥𝅮',
+ 119231 => '𝆹𝅥𝅯',
+ 119232 => '𝆺𝅥𝅯',
+ 119808 => 'a',
+ 119809 => 'b',
+ 119810 => 'c',
+ 119811 => 'd',
+ 119812 => 'e',
+ 119813 => 'f',
+ 119814 => 'g',
+ 119815 => 'h',
+ 119816 => 'i',
+ 119817 => 'j',
+ 119818 => 'k',
+ 119819 => 'l',
+ 119820 => 'm',
+ 119821 => 'n',
+ 119822 => 'o',
+ 119823 => 'p',
+ 119824 => 'q',
+ 119825 => 'r',
+ 119826 => 's',
+ 119827 => 't',
+ 119828 => 'u',
+ 119829 => 'v',
+ 119830 => 'w',
+ 119831 => 'x',
+ 119832 => 'y',
+ 119833 => 'z',
+ 119834 => 'a',
+ 119835 => 'b',
+ 119836 => 'c',
+ 119837 => 'd',
+ 119838 => 'e',
+ 119839 => 'f',
+ 119840 => 'g',
+ 119841 => 'h',
+ 119842 => 'i',
+ 119843 => 'j',
+ 119844 => 'k',
+ 119845 => 'l',
+ 119846 => 'm',
+ 119847 => 'n',
+ 119848 => 'o',
+ 119849 => 'p',
+ 119850 => 'q',
+ 119851 => 'r',
+ 119852 => 's',
+ 119853 => 't',
+ 119854 => 'u',
+ 119855 => 'v',
+ 119856 => 'w',
+ 119857 => 'x',
+ 119858 => 'y',
+ 119859 => 'z',
+ 119860 => 'a',
+ 119861 => 'b',
+ 119862 => 'c',
+ 119863 => 'd',
+ 119864 => 'e',
+ 119865 => 'f',
+ 119866 => 'g',
+ 119867 => 'h',
+ 119868 => 'i',
+ 119869 => 'j',
+ 119870 => 'k',
+ 119871 => 'l',
+ 119872 => 'm',
+ 119873 => 'n',
+ 119874 => 'o',
+ 119875 => 'p',
+ 119876 => 'q',
+ 119877 => 'r',
+ 119878 => 's',
+ 119879 => 't',
+ 119880 => 'u',
+ 119881 => 'v',
+ 119882 => 'w',
+ 119883 => 'x',
+ 119884 => 'y',
+ 119885 => 'z',
+ 119886 => 'a',
+ 119887 => 'b',
+ 119888 => 'c',
+ 119889 => 'd',
+ 119890 => 'e',
+ 119891 => 'f',
+ 119892 => 'g',
+ 119894 => 'i',
+ 119895 => 'j',
+ 119896 => 'k',
+ 119897 => 'l',
+ 119898 => 'm',
+ 119899 => 'n',
+ 119900 => 'o',
+ 119901 => 'p',
+ 119902 => 'q',
+ 119903 => 'r',
+ 119904 => 's',
+ 119905 => 't',
+ 119906 => 'u',
+ 119907 => 'v',
+ 119908 => 'w',
+ 119909 => 'x',
+ 119910 => 'y',
+ 119911 => 'z',
+ 119912 => 'a',
+ 119913 => 'b',
+ 119914 => 'c',
+ 119915 => 'd',
+ 119916 => 'e',
+ 119917 => 'f',
+ 119918 => 'g',
+ 119919 => 'h',
+ 119920 => 'i',
+ 119921 => 'j',
+ 119922 => 'k',
+ 119923 => 'l',
+ 119924 => 'm',
+ 119925 => 'n',
+ 119926 => 'o',
+ 119927 => 'p',
+ 119928 => 'q',
+ 119929 => 'r',
+ 119930 => 's',
+ 119931 => 't',
+ 119932 => 'u',
+ 119933 => 'v',
+ 119934 => 'w',
+ 119935 => 'x',
+ 119936 => 'y',
+ 119937 => 'z',
+ 119938 => 'a',
+ 119939 => 'b',
+ 119940 => 'c',
+ 119941 => 'd',
+ 119942 => 'e',
+ 119943 => 'f',
+ 119944 => 'g',
+ 119945 => 'h',
+ 119946 => 'i',
+ 119947 => 'j',
+ 119948 => 'k',
+ 119949 => 'l',
+ 119950 => 'm',
+ 119951 => 'n',
+ 119952 => 'o',
+ 119953 => 'p',
+ 119954 => 'q',
+ 119955 => 'r',
+ 119956 => 's',
+ 119957 => 't',
+ 119958 => 'u',
+ 119959 => 'v',
+ 119960 => 'w',
+ 119961 => 'x',
+ 119962 => 'y',
+ 119963 => 'z',
+ 119964 => 'a',
+ 119966 => 'c',
+ 119967 => 'd',
+ 119970 => 'g',
+ 119973 => 'j',
+ 119974 => 'k',
+ 119977 => 'n',
+ 119978 => 'o',
+ 119979 => 'p',
+ 119980 => 'q',
+ 119982 => 's',
+ 119983 => 't',
+ 119984 => 'u',
+ 119985 => 'v',
+ 119986 => 'w',
+ 119987 => 'x',
+ 119988 => 'y',
+ 119989 => 'z',
+ 119990 => 'a',
+ 119991 => 'b',
+ 119992 => 'c',
+ 119993 => 'd',
+ 119995 => 'f',
+ 119997 => 'h',
+ 119998 => 'i',
+ 119999 => 'j',
+ 120000 => 'k',
+ 120001 => 'l',
+ 120002 => 'm',
+ 120003 => 'n',
+ 120005 => 'p',
+ 120006 => 'q',
+ 120007 => 'r',
+ 120008 => 's',
+ 120009 => 't',
+ 120010 => 'u',
+ 120011 => 'v',
+ 120012 => 'w',
+ 120013 => 'x',
+ 120014 => 'y',
+ 120015 => 'z',
+ 120016 => 'a',
+ 120017 => 'b',
+ 120018 => 'c',
+ 120019 => 'd',
+ 120020 => 'e',
+ 120021 => 'f',
+ 120022 => 'g',
+ 120023 => 'h',
+ 120024 => 'i',
+ 120025 => 'j',
+ 120026 => 'k',
+ 120027 => 'l',
+ 120028 => 'm',
+ 120029 => 'n',
+ 120030 => 'o',
+ 120031 => 'p',
+ 120032 => 'q',
+ 120033 => 'r',
+ 120034 => 's',
+ 120035 => 't',
+ 120036 => 'u',
+ 120037 => 'v',
+ 120038 => 'w',
+ 120039 => 'x',
+ 120040 => 'y',
+ 120041 => 'z',
+ 120042 => 'a',
+ 120043 => 'b',
+ 120044 => 'c',
+ 120045 => 'd',
+ 120046 => 'e',
+ 120047 => 'f',
+ 120048 => 'g',
+ 120049 => 'h',
+ 120050 => 'i',
+ 120051 => 'j',
+ 120052 => 'k',
+ 120053 => 'l',
+ 120054 => 'm',
+ 120055 => 'n',
+ 120056 => 'o',
+ 120057 => 'p',
+ 120058 => 'q',
+ 120059 => 'r',
+ 120060 => 's',
+ 120061 => 't',
+ 120062 => 'u',
+ 120063 => 'v',
+ 120064 => 'w',
+ 120065 => 'x',
+ 120066 => 'y',
+ 120067 => 'z',
+ 120068 => 'a',
+ 120069 => 'b',
+ 120071 => 'd',
+ 120072 => 'e',
+ 120073 => 'f',
+ 120074 => 'g',
+ 120077 => 'j',
+ 120078 => 'k',
+ 120079 => 'l',
+ 120080 => 'm',
+ 120081 => 'n',
+ 120082 => 'o',
+ 120083 => 'p',
+ 120084 => 'q',
+ 120086 => 's',
+ 120087 => 't',
+ 120088 => 'u',
+ 120089 => 'v',
+ 120090 => 'w',
+ 120091 => 'x',
+ 120092 => 'y',
+ 120094 => 'a',
+ 120095 => 'b',
+ 120096 => 'c',
+ 120097 => 'd',
+ 120098 => 'e',
+ 120099 => 'f',
+ 120100 => 'g',
+ 120101 => 'h',
+ 120102 => 'i',
+ 120103 => 'j',
+ 120104 => 'k',
+ 120105 => 'l',
+ 120106 => 'm',
+ 120107 => 'n',
+ 120108 => 'o',
+ 120109 => 'p',
+ 120110 => 'q',
+ 120111 => 'r',
+ 120112 => 's',
+ 120113 => 't',
+ 120114 => 'u',
+ 120115 => 'v',
+ 120116 => 'w',
+ 120117 => 'x',
+ 120118 => 'y',
+ 120119 => 'z',
+ 120120 => 'a',
+ 120121 => 'b',
+ 120123 => 'd',
+ 120124 => 'e',
+ 120125 => 'f',
+ 120126 => 'g',
+ 120128 => 'i',
+ 120129 => 'j',
+ 120130 => 'k',
+ 120131 => 'l',
+ 120132 => 'm',
+ 120134 => 'o',
+ 120138 => 's',
+ 120139 => 't',
+ 120140 => 'u',
+ 120141 => 'v',
+ 120142 => 'w',
+ 120143 => 'x',
+ 120144 => 'y',
+ 120146 => 'a',
+ 120147 => 'b',
+ 120148 => 'c',
+ 120149 => 'd',
+ 120150 => 'e',
+ 120151 => 'f',
+ 120152 => 'g',
+ 120153 => 'h',
+ 120154 => 'i',
+ 120155 => 'j',
+ 120156 => 'k',
+ 120157 => 'l',
+ 120158 => 'm',
+ 120159 => 'n',
+ 120160 => 'o',
+ 120161 => 'p',
+ 120162 => 'q',
+ 120163 => 'r',
+ 120164 => 's',
+ 120165 => 't',
+ 120166 => 'u',
+ 120167 => 'v',
+ 120168 => 'w',
+ 120169 => 'x',
+ 120170 => 'y',
+ 120171 => 'z',
+ 120172 => 'a',
+ 120173 => 'b',
+ 120174 => 'c',
+ 120175 => 'd',
+ 120176 => 'e',
+ 120177 => 'f',
+ 120178 => 'g',
+ 120179 => 'h',
+ 120180 => 'i',
+ 120181 => 'j',
+ 120182 => 'k',
+ 120183 => 'l',
+ 120184 => 'm',
+ 120185 => 'n',
+ 120186 => 'o',
+ 120187 => 'p',
+ 120188 => 'q',
+ 120189 => 'r',
+ 120190 => 's',
+ 120191 => 't',
+ 120192 => 'u',
+ 120193 => 'v',
+ 120194 => 'w',
+ 120195 => 'x',
+ 120196 => 'y',
+ 120197 => 'z',
+ 120198 => 'a',
+ 120199 => 'b',
+ 120200 => 'c',
+ 120201 => 'd',
+ 120202 => 'e',
+ 120203 => 'f',
+ 120204 => 'g',
+ 120205 => 'h',
+ 120206 => 'i',
+ 120207 => 'j',
+ 120208 => 'k',
+ 120209 => 'l',
+ 120210 => 'm',
+ 120211 => 'n',
+ 120212 => 'o',
+ 120213 => 'p',
+ 120214 => 'q',
+ 120215 => 'r',
+ 120216 => 's',
+ 120217 => 't',
+ 120218 => 'u',
+ 120219 => 'v',
+ 120220 => 'w',
+ 120221 => 'x',
+ 120222 => 'y',
+ 120223 => 'z',
+ 120224 => 'a',
+ 120225 => 'b',
+ 120226 => 'c',
+ 120227 => 'd',
+ 120228 => 'e',
+ 120229 => 'f',
+ 120230 => 'g',
+ 120231 => 'h',
+ 120232 => 'i',
+ 120233 => 'j',
+ 120234 => 'k',
+ 120235 => 'l',
+ 120236 => 'm',
+ 120237 => 'n',
+ 120238 => 'o',
+ 120239 => 'p',
+ 120240 => 'q',
+ 120241 => 'r',
+ 120242 => 's',
+ 120243 => 't',
+ 120244 => 'u',
+ 120245 => 'v',
+ 120246 => 'w',
+ 120247 => 'x',
+ 120248 => 'y',
+ 120249 => 'z',
+ 120250 => 'a',
+ 120251 => 'b',
+ 120252 => 'c',
+ 120253 => 'd',
+ 120254 => 'e',
+ 120255 => 'f',
+ 120256 => 'g',
+ 120257 => 'h',
+ 120258 => 'i',
+ 120259 => 'j',
+ 120260 => 'k',
+ 120261 => 'l',
+ 120262 => 'm',
+ 120263 => 'n',
+ 120264 => 'o',
+ 120265 => 'p',
+ 120266 => 'q',
+ 120267 => 'r',
+ 120268 => 's',
+ 120269 => 't',
+ 120270 => 'u',
+ 120271 => 'v',
+ 120272 => 'w',
+ 120273 => 'x',
+ 120274 => 'y',
+ 120275 => 'z',
+ 120276 => 'a',
+ 120277 => 'b',
+ 120278 => 'c',
+ 120279 => 'd',
+ 120280 => 'e',
+ 120281 => 'f',
+ 120282 => 'g',
+ 120283 => 'h',
+ 120284 => 'i',
+ 120285 => 'j',
+ 120286 => 'k',
+ 120287 => 'l',
+ 120288 => 'm',
+ 120289 => 'n',
+ 120290 => 'o',
+ 120291 => 'p',
+ 120292 => 'q',
+ 120293 => 'r',
+ 120294 => 's',
+ 120295 => 't',
+ 120296 => 'u',
+ 120297 => 'v',
+ 120298 => 'w',
+ 120299 => 'x',
+ 120300 => 'y',
+ 120301 => 'z',
+ 120302 => 'a',
+ 120303 => 'b',
+ 120304 => 'c',
+ 120305 => 'd',
+ 120306 => 'e',
+ 120307 => 'f',
+ 120308 => 'g',
+ 120309 => 'h',
+ 120310 => 'i',
+ 120311 => 'j',
+ 120312 => 'k',
+ 120313 => 'l',
+ 120314 => 'm',
+ 120315 => 'n',
+ 120316 => 'o',
+ 120317 => 'p',
+ 120318 => 'q',
+ 120319 => 'r',
+ 120320 => 's',
+ 120321 => 't',
+ 120322 => 'u',
+ 120323 => 'v',
+ 120324 => 'w',
+ 120325 => 'x',
+ 120326 => 'y',
+ 120327 => 'z',
+ 120328 => 'a',
+ 120329 => 'b',
+ 120330 => 'c',
+ 120331 => 'd',
+ 120332 => 'e',
+ 120333 => 'f',
+ 120334 => 'g',
+ 120335 => 'h',
+ 120336 => 'i',
+ 120337 => 'j',
+ 120338 => 'k',
+ 120339 => 'l',
+ 120340 => 'm',
+ 120341 => 'n',
+ 120342 => 'o',
+ 120343 => 'p',
+ 120344 => 'q',
+ 120345 => 'r',
+ 120346 => 's',
+ 120347 => 't',
+ 120348 => 'u',
+ 120349 => 'v',
+ 120350 => 'w',
+ 120351 => 'x',
+ 120352 => 'y',
+ 120353 => 'z',
+ 120354 => 'a',
+ 120355 => 'b',
+ 120356 => 'c',
+ 120357 => 'd',
+ 120358 => 'e',
+ 120359 => 'f',
+ 120360 => 'g',
+ 120361 => 'h',
+ 120362 => 'i',
+ 120363 => 'j',
+ 120364 => 'k',
+ 120365 => 'l',
+ 120366 => 'm',
+ 120367 => 'n',
+ 120368 => 'o',
+ 120369 => 'p',
+ 120370 => 'q',
+ 120371 => 'r',
+ 120372 => 's',
+ 120373 => 't',
+ 120374 => 'u',
+ 120375 => 'v',
+ 120376 => 'w',
+ 120377 => 'x',
+ 120378 => 'y',
+ 120379 => 'z',
+ 120380 => 'a',
+ 120381 => 'b',
+ 120382 => 'c',
+ 120383 => 'd',
+ 120384 => 'e',
+ 120385 => 'f',
+ 120386 => 'g',
+ 120387 => 'h',
+ 120388 => 'i',
+ 120389 => 'j',
+ 120390 => 'k',
+ 120391 => 'l',
+ 120392 => 'm',
+ 120393 => 'n',
+ 120394 => 'o',
+ 120395 => 'p',
+ 120396 => 'q',
+ 120397 => 'r',
+ 120398 => 's',
+ 120399 => 't',
+ 120400 => 'u',
+ 120401 => 'v',
+ 120402 => 'w',
+ 120403 => 'x',
+ 120404 => 'y',
+ 120405 => 'z',
+ 120406 => 'a',
+ 120407 => 'b',
+ 120408 => 'c',
+ 120409 => 'd',
+ 120410 => 'e',
+ 120411 => 'f',
+ 120412 => 'g',
+ 120413 => 'h',
+ 120414 => 'i',
+ 120415 => 'j',
+ 120416 => 'k',
+ 120417 => 'l',
+ 120418 => 'm',
+ 120419 => 'n',
+ 120420 => 'o',
+ 120421 => 'p',
+ 120422 => 'q',
+ 120423 => 'r',
+ 120424 => 's',
+ 120425 => 't',
+ 120426 => 'u',
+ 120427 => 'v',
+ 120428 => 'w',
+ 120429 => 'x',
+ 120430 => 'y',
+ 120431 => 'z',
+ 120432 => 'a',
+ 120433 => 'b',
+ 120434 => 'c',
+ 120435 => 'd',
+ 120436 => 'e',
+ 120437 => 'f',
+ 120438 => 'g',
+ 120439 => 'h',
+ 120440 => 'i',
+ 120441 => 'j',
+ 120442 => 'k',
+ 120443 => 'l',
+ 120444 => 'm',
+ 120445 => 'n',
+ 120446 => 'o',
+ 120447 => 'p',
+ 120448 => 'q',
+ 120449 => 'r',
+ 120450 => 's',
+ 120451 => 't',
+ 120452 => 'u',
+ 120453 => 'v',
+ 120454 => 'w',
+ 120455 => 'x',
+ 120456 => 'y',
+ 120457 => 'z',
+ 120458 => 'a',
+ 120459 => 'b',
+ 120460 => 'c',
+ 120461 => 'd',
+ 120462 => 'e',
+ 120463 => 'f',
+ 120464 => 'g',
+ 120465 => 'h',
+ 120466 => 'i',
+ 120467 => 'j',
+ 120468 => 'k',
+ 120469 => 'l',
+ 120470 => 'm',
+ 120471 => 'n',
+ 120472 => 'o',
+ 120473 => 'p',
+ 120474 => 'q',
+ 120475 => 'r',
+ 120476 => 's',
+ 120477 => 't',
+ 120478 => 'u',
+ 120479 => 'v',
+ 120480 => 'w',
+ 120481 => 'x',
+ 120482 => 'y',
+ 120483 => 'z',
+ 120484 => 'ı',
+ 120485 => 'ȷ',
+ 120488 => 'α',
+ 120489 => 'β',
+ 120490 => 'γ',
+ 120491 => 'δ',
+ 120492 => 'ε',
+ 120493 => 'ζ',
+ 120494 => 'η',
+ 120495 => 'θ',
+ 120496 => 'ι',
+ 120497 => 'κ',
+ 120498 => 'λ',
+ 120499 => 'μ',
+ 120500 => 'ν',
+ 120501 => 'ξ',
+ 120502 => 'ο',
+ 120503 => 'π',
+ 120504 => 'ρ',
+ 120505 => 'θ',
+ 120506 => 'σ',
+ 120507 => 'τ',
+ 120508 => 'υ',
+ 120509 => 'φ',
+ 120510 => 'χ',
+ 120511 => 'ψ',
+ 120512 => 'ω',
+ 120513 => '∇',
+ 120514 => 'α',
+ 120515 => 'β',
+ 120516 => 'γ',
+ 120517 => 'δ',
+ 120518 => 'ε',
+ 120519 => 'ζ',
+ 120520 => 'η',
+ 120521 => 'θ',
+ 120522 => 'ι',
+ 120523 => 'κ',
+ 120524 => 'λ',
+ 120525 => 'μ',
+ 120526 => 'ν',
+ 120527 => 'ξ',
+ 120528 => 'ο',
+ 120529 => 'π',
+ 120530 => 'ρ',
+ 120531 => 'σ',
+ 120532 => 'σ',
+ 120533 => 'τ',
+ 120534 => 'υ',
+ 120535 => 'φ',
+ 120536 => 'χ',
+ 120537 => 'ψ',
+ 120538 => 'ω',
+ 120539 => '∂',
+ 120540 => 'ε',
+ 120541 => 'θ',
+ 120542 => 'κ',
+ 120543 => 'φ',
+ 120544 => 'ρ',
+ 120545 => 'π',
+ 120546 => 'α',
+ 120547 => 'β',
+ 120548 => 'γ',
+ 120549 => 'δ',
+ 120550 => 'ε',
+ 120551 => 'ζ',
+ 120552 => 'η',
+ 120553 => 'θ',
+ 120554 => 'ι',
+ 120555 => 'κ',
+ 120556 => 'λ',
+ 120557 => 'μ',
+ 120558 => 'ν',
+ 120559 => 'ξ',
+ 120560 => 'ο',
+ 120561 => 'π',
+ 120562 => 'ρ',
+ 120563 => 'θ',
+ 120564 => 'σ',
+ 120565 => 'τ',
+ 120566 => 'υ',
+ 120567 => 'φ',
+ 120568 => 'χ',
+ 120569 => 'ψ',
+ 120570 => 'ω',
+ 120571 => '∇',
+ 120572 => 'α',
+ 120573 => 'β',
+ 120574 => 'γ',
+ 120575 => 'δ',
+ 120576 => 'ε',
+ 120577 => 'ζ',
+ 120578 => 'η',
+ 120579 => 'θ',
+ 120580 => 'ι',
+ 120581 => 'κ',
+ 120582 => 'λ',
+ 120583 => 'μ',
+ 120584 => 'ν',
+ 120585 => 'ξ',
+ 120586 => 'ο',
+ 120587 => 'π',
+ 120588 => 'ρ',
+ 120589 => 'σ',
+ 120590 => 'σ',
+ 120591 => 'τ',
+ 120592 => 'υ',
+ 120593 => 'φ',
+ 120594 => 'χ',
+ 120595 => 'ψ',
+ 120596 => 'ω',
+ 120597 => '∂',
+ 120598 => 'ε',
+ 120599 => 'θ',
+ 120600 => 'κ',
+ 120601 => 'φ',
+ 120602 => 'ρ',
+ 120603 => 'π',
+ 120604 => 'α',
+ 120605 => 'β',
+ 120606 => 'γ',
+ 120607 => 'δ',
+ 120608 => 'ε',
+ 120609 => 'ζ',
+ 120610 => 'η',
+ 120611 => 'θ',
+ 120612 => 'ι',
+ 120613 => 'κ',
+ 120614 => 'λ',
+ 120615 => 'μ',
+ 120616 => 'ν',
+ 120617 => 'ξ',
+ 120618 => 'ο',
+ 120619 => 'π',
+ 120620 => 'ρ',
+ 120621 => 'θ',
+ 120622 => 'σ',
+ 120623 => 'τ',
+ 120624 => 'υ',
+ 120625 => 'φ',
+ 120626 => 'χ',
+ 120627 => 'ψ',
+ 120628 => 'ω',
+ 120629 => '∇',
+ 120630 => 'α',
+ 120631 => 'β',
+ 120632 => 'γ',
+ 120633 => 'δ',
+ 120634 => 'ε',
+ 120635 => 'ζ',
+ 120636 => 'η',
+ 120637 => 'θ',
+ 120638 => 'ι',
+ 120639 => 'κ',
+ 120640 => 'λ',
+ 120641 => 'μ',
+ 120642 => 'ν',
+ 120643 => 'ξ',
+ 120644 => 'ο',
+ 120645 => 'π',
+ 120646 => 'ρ',
+ 120647 => 'σ',
+ 120648 => 'σ',
+ 120649 => 'τ',
+ 120650 => 'υ',
+ 120651 => 'φ',
+ 120652 => 'χ',
+ 120653 => 'ψ',
+ 120654 => 'ω',
+ 120655 => '∂',
+ 120656 => 'ε',
+ 120657 => 'θ',
+ 120658 => 'κ',
+ 120659 => 'φ',
+ 120660 => 'ρ',
+ 120661 => 'π',
+ 120662 => 'α',
+ 120663 => 'β',
+ 120664 => 'γ',
+ 120665 => 'δ',
+ 120666 => 'ε',
+ 120667 => 'ζ',
+ 120668 => 'η',
+ 120669 => 'θ',
+ 120670 => 'ι',
+ 120671 => 'κ',
+ 120672 => 'λ',
+ 120673 => 'μ',
+ 120674 => 'ν',
+ 120675 => 'ξ',
+ 120676 => 'ο',
+ 120677 => 'π',
+ 120678 => 'ρ',
+ 120679 => 'θ',
+ 120680 => 'σ',
+ 120681 => 'τ',
+ 120682 => 'υ',
+ 120683 => 'φ',
+ 120684 => 'χ',
+ 120685 => 'ψ',
+ 120686 => 'ω',
+ 120687 => '∇',
+ 120688 => 'α',
+ 120689 => 'β',
+ 120690 => 'γ',
+ 120691 => 'δ',
+ 120692 => 'ε',
+ 120693 => 'ζ',
+ 120694 => 'η',
+ 120695 => 'θ',
+ 120696 => 'ι',
+ 120697 => 'κ',
+ 120698 => 'λ',
+ 120699 => 'μ',
+ 120700 => 'ν',
+ 120701 => 'ξ',
+ 120702 => 'ο',
+ 120703 => 'π',
+ 120704 => 'ρ',
+ 120705 => 'σ',
+ 120706 => 'σ',
+ 120707 => 'τ',
+ 120708 => 'υ',
+ 120709 => 'φ',
+ 120710 => 'χ',
+ 120711 => 'ψ',
+ 120712 => 'ω',
+ 120713 => '∂',
+ 120714 => 'ε',
+ 120715 => 'θ',
+ 120716 => 'κ',
+ 120717 => 'φ',
+ 120718 => 'ρ',
+ 120719 => 'π',
+ 120720 => 'α',
+ 120721 => 'β',
+ 120722 => 'γ',
+ 120723 => 'δ',
+ 120724 => 'ε',
+ 120725 => 'ζ',
+ 120726 => 'η',
+ 120727 => 'θ',
+ 120728 => 'ι',
+ 120729 => 'κ',
+ 120730 => 'λ',
+ 120731 => 'μ',
+ 120732 => 'ν',
+ 120733 => 'ξ',
+ 120734 => 'ο',
+ 120735 => 'π',
+ 120736 => 'ρ',
+ 120737 => 'θ',
+ 120738 => 'σ',
+ 120739 => 'τ',
+ 120740 => 'υ',
+ 120741 => 'φ',
+ 120742 => 'χ',
+ 120743 => 'ψ',
+ 120744 => 'ω',
+ 120745 => '∇',
+ 120746 => 'α',
+ 120747 => 'β',
+ 120748 => 'γ',
+ 120749 => 'δ',
+ 120750 => 'ε',
+ 120751 => 'ζ',
+ 120752 => 'η',
+ 120753 => 'θ',
+ 120754 => 'ι',
+ 120755 => 'κ',
+ 120756 => 'λ',
+ 120757 => 'μ',
+ 120758 => 'ν',
+ 120759 => 'ξ',
+ 120760 => 'ο',
+ 120761 => 'π',
+ 120762 => 'ρ',
+ 120763 => 'σ',
+ 120764 => 'σ',
+ 120765 => 'τ',
+ 120766 => 'υ',
+ 120767 => 'φ',
+ 120768 => 'χ',
+ 120769 => 'ψ',
+ 120770 => 'ω',
+ 120771 => '∂',
+ 120772 => 'ε',
+ 120773 => 'θ',
+ 120774 => 'κ',
+ 120775 => 'φ',
+ 120776 => 'ρ',
+ 120777 => 'π',
+ 120778 => 'ϝ',
+ 120779 => 'ϝ',
+ 120782 => '0',
+ 120783 => '1',
+ 120784 => '2',
+ 120785 => '3',
+ 120786 => '4',
+ 120787 => '5',
+ 120788 => '6',
+ 120789 => '7',
+ 120790 => '8',
+ 120791 => '9',
+ 120792 => '0',
+ 120793 => '1',
+ 120794 => '2',
+ 120795 => '3',
+ 120796 => '4',
+ 120797 => '5',
+ 120798 => '6',
+ 120799 => '7',
+ 120800 => '8',
+ 120801 => '9',
+ 120802 => '0',
+ 120803 => '1',
+ 120804 => '2',
+ 120805 => '3',
+ 120806 => '4',
+ 120807 => '5',
+ 120808 => '6',
+ 120809 => '7',
+ 120810 => '8',
+ 120811 => '9',
+ 120812 => '0',
+ 120813 => '1',
+ 120814 => '2',
+ 120815 => '3',
+ 120816 => '4',
+ 120817 => '5',
+ 120818 => '6',
+ 120819 => '7',
+ 120820 => '8',
+ 120821 => '9',
+ 120822 => '0',
+ 120823 => '1',
+ 120824 => '2',
+ 120825 => '3',
+ 120826 => '4',
+ 120827 => '5',
+ 120828 => '6',
+ 120829 => '7',
+ 120830 => '8',
+ 120831 => '9',
+ 125184 => '𞤢',
+ 125185 => '𞤣',
+ 125186 => '𞤤',
+ 125187 => '𞤥',
+ 125188 => '𞤦',
+ 125189 => '𞤧',
+ 125190 => '𞤨',
+ 125191 => '𞤩',
+ 125192 => '𞤪',
+ 125193 => '𞤫',
+ 125194 => '𞤬',
+ 125195 => '𞤭',
+ 125196 => '𞤮',
+ 125197 => '𞤯',
+ 125198 => '𞤰',
+ 125199 => '𞤱',
+ 125200 => '𞤲',
+ 125201 => '𞤳',
+ 125202 => '𞤴',
+ 125203 => '𞤵',
+ 125204 => '𞤶',
+ 125205 => '𞤷',
+ 125206 => '𞤸',
+ 125207 => '𞤹',
+ 125208 => '𞤺',
+ 125209 => '𞤻',
+ 125210 => '𞤼',
+ 125211 => '𞤽',
+ 125212 => '𞤾',
+ 125213 => '𞤿',
+ 125214 => '𞥀',
+ 125215 => '𞥁',
+ 125216 => '𞥂',
+ 125217 => '𞥃',
+ 126464 => 'ا',
+ 126465 => 'ب',
+ 126466 => 'ج',
+ 126467 => 'د',
+ 126469 => 'و',
+ 126470 => 'ز',
+ 126471 => 'ح',
+ 126472 => 'ط',
+ 126473 => 'ي',
+ 126474 => 'ك',
+ 126475 => 'ل',
+ 126476 => 'م',
+ 126477 => 'ن',
+ 126478 => 'س',
+ 126479 => 'ع',
+ 126480 => 'ف',
+ 126481 => 'ص',
+ 126482 => 'ق',
+ 126483 => 'ر',
+ 126484 => 'ش',
+ 126485 => 'ت',
+ 126486 => 'ث',
+ 126487 => 'خ',
+ 126488 => 'ذ',
+ 126489 => 'ض',
+ 126490 => 'ظ',
+ 126491 => 'غ',
+ 126492 => 'ٮ',
+ 126493 => 'ں',
+ 126494 => 'ڡ',
+ 126495 => 'ٯ',
+ 126497 => 'ب',
+ 126498 => 'ج',
+ 126500 => 'ه',
+ 126503 => 'ح',
+ 126505 => 'ي',
+ 126506 => 'ك',
+ 126507 => 'ل',
+ 126508 => 'م',
+ 126509 => 'ن',
+ 126510 => 'س',
+ 126511 => 'ع',
+ 126512 => 'ف',
+ 126513 => 'ص',
+ 126514 => 'ق',
+ 126516 => 'ش',
+ 126517 => 'ت',
+ 126518 => 'ث',
+ 126519 => 'خ',
+ 126521 => 'ض',
+ 126523 => 'غ',
+ 126530 => 'ج',
+ 126535 => 'ح',
+ 126537 => 'ي',
+ 126539 => 'ل',
+ 126541 => 'ن',
+ 126542 => 'س',
+ 126543 => 'ع',
+ 126545 => 'ص',
+ 126546 => 'ق',
+ 126548 => 'ش',
+ 126551 => 'خ',
+ 126553 => 'ض',
+ 126555 => 'غ',
+ 126557 => 'ں',
+ 126559 => 'ٯ',
+ 126561 => 'ب',
+ 126562 => 'ج',
+ 126564 => 'ه',
+ 126567 => 'ح',
+ 126568 => 'ط',
+ 126569 => 'ي',
+ 126570 => 'ك',
+ 126572 => 'م',
+ 126573 => 'ن',
+ 126574 => 'س',
+ 126575 => 'ع',
+ 126576 => 'ف',
+ 126577 => 'ص',
+ 126578 => 'ق',
+ 126580 => 'ش',
+ 126581 => 'ت',
+ 126582 => 'ث',
+ 126583 => 'خ',
+ 126585 => 'ض',
+ 126586 => 'ظ',
+ 126587 => 'غ',
+ 126588 => 'ٮ',
+ 126590 => 'ڡ',
+ 126592 => 'ا',
+ 126593 => 'ب',
+ 126594 => 'ج',
+ 126595 => 'د',
+ 126596 => 'ه',
+ 126597 => 'و',
+ 126598 => 'ز',
+ 126599 => 'ح',
+ 126600 => 'ط',
+ 126601 => 'ي',
+ 126603 => 'ل',
+ 126604 => 'م',
+ 126605 => 'ن',
+ 126606 => 'س',
+ 126607 => 'ع',
+ 126608 => 'ف',
+ 126609 => 'ص',
+ 126610 => 'ق',
+ 126611 => 'ر',
+ 126612 => 'ش',
+ 126613 => 'ت',
+ 126614 => 'ث',
+ 126615 => 'خ',
+ 126616 => 'ذ',
+ 126617 => 'ض',
+ 126618 => 'ظ',
+ 126619 => 'غ',
+ 126625 => 'ب',
+ 126626 => 'ج',
+ 126627 => 'د',
+ 126629 => 'و',
+ 126630 => 'ز',
+ 126631 => 'ح',
+ 126632 => 'ط',
+ 126633 => 'ي',
+ 126635 => 'ل',
+ 126636 => 'م',
+ 126637 => 'ن',
+ 126638 => 'س',
+ 126639 => 'ع',
+ 126640 => 'ف',
+ 126641 => 'ص',
+ 126642 => 'ق',
+ 126643 => 'ر',
+ 126644 => 'ش',
+ 126645 => 'ت',
+ 126646 => 'ث',
+ 126647 => 'خ',
+ 126648 => 'ذ',
+ 126649 => 'ض',
+ 126650 => 'ظ',
+ 126651 => 'غ',
+ 127274 => '〔s〕',
+ 127275 => 'c',
+ 127276 => 'r',
+ 127277 => 'cd',
+ 127278 => 'wz',
+ 127280 => 'a',
+ 127281 => 'b',
+ 127282 => 'c',
+ 127283 => 'd',
+ 127284 => 'e',
+ 127285 => 'f',
+ 127286 => 'g',
+ 127287 => 'h',
+ 127288 => 'i',
+ 127289 => 'j',
+ 127290 => 'k',
+ 127291 => 'l',
+ 127292 => 'm',
+ 127293 => 'n',
+ 127294 => 'o',
+ 127295 => 'p',
+ 127296 => 'q',
+ 127297 => 'r',
+ 127298 => 's',
+ 127299 => 't',
+ 127300 => 'u',
+ 127301 => 'v',
+ 127302 => 'w',
+ 127303 => 'x',
+ 127304 => 'y',
+ 127305 => 'z',
+ 127306 => 'hv',
+ 127307 => 'mv',
+ 127308 => 'sd',
+ 127309 => 'ss',
+ 127310 => 'ppv',
+ 127311 => 'wc',
+ 127338 => 'mc',
+ 127339 => 'md',
+ 127340 => 'mr',
+ 127376 => 'dj',
+ 127488 => 'ほか',
+ 127489 => 'ココ',
+ 127490 => 'サ',
+ 127504 => '手',
+ 127505 => '字',
+ 127506 => '双',
+ 127507 => 'デ',
+ 127508 => '二',
+ 127509 => '多',
+ 127510 => '解',
+ 127511 => '天',
+ 127512 => '交',
+ 127513 => '映',
+ 127514 => '無',
+ 127515 => '料',
+ 127516 => '前',
+ 127517 => '後',
+ 127518 => '再',
+ 127519 => '新',
+ 127520 => '初',
+ 127521 => '終',
+ 127522 => '生',
+ 127523 => '販',
+ 127524 => '声',
+ 127525 => '吹',
+ 127526 => '演',
+ 127527 => '投',
+ 127528 => '捕',
+ 127529 => '一',
+ 127530 => '三',
+ 127531 => '遊',
+ 127532 => '左',
+ 127533 => '中',
+ 127534 => '右',
+ 127535 => '指',
+ 127536 => '走',
+ 127537 => '打',
+ 127538 => '禁',
+ 127539 => '空',
+ 127540 => '合',
+ 127541 => '満',
+ 127542 => '有',
+ 127543 => '月',
+ 127544 => '申',
+ 127545 => '割',
+ 127546 => '営',
+ 127547 => '配',
+ 127552 => '〔本〕',
+ 127553 => '〔三〕',
+ 127554 => '〔二〕',
+ 127555 => '〔安〕',
+ 127556 => '〔点〕',
+ 127557 => '〔打〕',
+ 127558 => '〔盗〕',
+ 127559 => '〔勝〕',
+ 127560 => '〔敗〕',
+ 127568 => '得',
+ 127569 => '可',
+ 130032 => '0',
+ 130033 => '1',
+ 130034 => '2',
+ 130035 => '3',
+ 130036 => '4',
+ 130037 => '5',
+ 130038 => '6',
+ 130039 => '7',
+ 130040 => '8',
+ 130041 => '9',
+ 194560 => '丽',
+ 194561 => '丸',
+ 194562 => '乁',
+ 194563 => '𠄢',
+ 194564 => '你',
+ 194565 => '侮',
+ 194566 => '侻',
+ 194567 => '倂',
+ 194568 => '偺',
+ 194569 => '備',
+ 194570 => '僧',
+ 194571 => '像',
+ 194572 => '㒞',
+ 194573 => '𠘺',
+ 194574 => '免',
+ 194575 => '兔',
+ 194576 => '兤',
+ 194577 => '具',
+ 194578 => '𠔜',
+ 194579 => '㒹',
+ 194580 => '內',
+ 194581 => '再',
+ 194582 => '𠕋',
+ 194583 => '冗',
+ 194584 => '冤',
+ 194585 => '仌',
+ 194586 => '冬',
+ 194587 => '况',
+ 194588 => '𩇟',
+ 194589 => '凵',
+ 194590 => '刃',
+ 194591 => '㓟',
+ 194592 => '刻',
+ 194593 => '剆',
+ 194594 => '割',
+ 194595 => '剷',
+ 194596 => '㔕',
+ 194597 => '勇',
+ 194598 => '勉',
+ 194599 => '勤',
+ 194600 => '勺',
+ 194601 => '包',
+ 194602 => '匆',
+ 194603 => '北',
+ 194604 => '卉',
+ 194605 => '卑',
+ 194606 => '博',
+ 194607 => '即',
+ 194608 => '卽',
+ 194609 => '卿',
+ 194610 => '卿',
+ 194611 => '卿',
+ 194612 => '𠨬',
+ 194613 => '灰',
+ 194614 => '及',
+ 194615 => '叟',
+ 194616 => '𠭣',
+ 194617 => '叫',
+ 194618 => '叱',
+ 194619 => '吆',
+ 194620 => '咞',
+ 194621 => '吸',
+ 194622 => '呈',
+ 194623 => '周',
+ 194624 => '咢',
+ 194625 => '哶',
+ 194626 => '唐',
+ 194627 => '啓',
+ 194628 => '啣',
+ 194629 => '善',
+ 194630 => '善',
+ 194631 => '喙',
+ 194632 => '喫',
+ 194633 => '喳',
+ 194634 => '嗂',
+ 194635 => '圖',
+ 194636 => '嘆',
+ 194637 => '圗',
+ 194638 => '噑',
+ 194639 => '噴',
+ 194640 => '切',
+ 194641 => '壮',
+ 194642 => '城',
+ 194643 => '埴',
+ 194644 => '堍',
+ 194645 => '型',
+ 194646 => '堲',
+ 194647 => '報',
+ 194648 => '墬',
+ 194649 => '𡓤',
+ 194650 => '売',
+ 194651 => '壷',
+ 194652 => '夆',
+ 194653 => '多',
+ 194654 => '夢',
+ 194655 => '奢',
+ 194656 => '𡚨',
+ 194657 => '𡛪',
+ 194658 => '姬',
+ 194659 => '娛',
+ 194660 => '娧',
+ 194661 => '姘',
+ 194662 => '婦',
+ 194663 => '㛮',
+ 194665 => '嬈',
+ 194666 => '嬾',
+ 194667 => '嬾',
+ 194668 => '𡧈',
+ 194669 => '寃',
+ 194670 => '寘',
+ 194671 => '寧',
+ 194672 => '寳',
+ 194673 => '𡬘',
+ 194674 => '寿',
+ 194675 => '将',
+ 194677 => '尢',
+ 194678 => '㞁',
+ 194679 => '屠',
+ 194680 => '屮',
+ 194681 => '峀',
+ 194682 => '岍',
+ 194683 => '𡷤',
+ 194684 => '嵃',
+ 194685 => '𡷦',
+ 194686 => '嵮',
+ 194687 => '嵫',
+ 194688 => '嵼',
+ 194689 => '巡',
+ 194690 => '巢',
+ 194691 => '㠯',
+ 194692 => '巽',
+ 194693 => '帨',
+ 194694 => '帽',
+ 194695 => '幩',
+ 194696 => '㡢',
+ 194697 => '𢆃',
+ 194698 => '㡼',
+ 194699 => '庰',
+ 194700 => '庳',
+ 194701 => '庶',
+ 194702 => '廊',
+ 194703 => '𪎒',
+ 194704 => '廾',
+ 194705 => '𢌱',
+ 194706 => '𢌱',
+ 194707 => '舁',
+ 194708 => '弢',
+ 194709 => '弢',
+ 194710 => '㣇',
+ 194711 => '𣊸',
+ 194712 => '𦇚',
+ 194713 => '形',
+ 194714 => '彫',
+ 194715 => '㣣',
+ 194716 => '徚',
+ 194717 => '忍',
+ 194718 => '志',
+ 194719 => '忹',
+ 194720 => '悁',
+ 194721 => '㤺',
+ 194722 => '㤜',
+ 194723 => '悔',
+ 194724 => '𢛔',
+ 194725 => '惇',
+ 194726 => '慈',
+ 194727 => '慌',
+ 194728 => '慎',
+ 194729 => '慌',
+ 194730 => '慺',
+ 194731 => '憎',
+ 194732 => '憲',
+ 194733 => '憤',
+ 194734 => '憯',
+ 194735 => '懞',
+ 194736 => '懲',
+ 194737 => '懶',
+ 194738 => '成',
+ 194739 => '戛',
+ 194740 => '扝',
+ 194741 => '抱',
+ 194742 => '拔',
+ 194743 => '捐',
+ 194744 => '𢬌',
+ 194745 => '挽',
+ 194746 => '拼',
+ 194747 => '捨',
+ 194748 => '掃',
+ 194749 => '揤',
+ 194750 => '𢯱',
+ 194751 => '搢',
+ 194752 => '揅',
+ 194753 => '掩',
+ 194754 => '㨮',
+ 194755 => '摩',
+ 194756 => '摾',
+ 194757 => '撝',
+ 194758 => '摷',
+ 194759 => '㩬',
+ 194760 => '敏',
+ 194761 => '敬',
+ 194762 => '𣀊',
+ 194763 => '旣',
+ 194764 => '書',
+ 194765 => '晉',
+ 194766 => '㬙',
+ 194767 => '暑',
+ 194768 => '㬈',
+ 194769 => '㫤',
+ 194770 => '冒',
+ 194771 => '冕',
+ 194772 => '最',
+ 194773 => '暜',
+ 194774 => '肭',
+ 194775 => '䏙',
+ 194776 => '朗',
+ 194777 => '望',
+ 194778 => '朡',
+ 194779 => '杞',
+ 194780 => '杓',
+ 194781 => '𣏃',
+ 194782 => '㭉',
+ 194783 => '柺',
+ 194784 => '枅',
+ 194785 => '桒',
+ 194786 => '梅',
+ 194787 => '𣑭',
+ 194788 => '梎',
+ 194789 => '栟',
+ 194790 => '椔',
+ 194791 => '㮝',
+ 194792 => '楂',
+ 194793 => '榣',
+ 194794 => '槪',
+ 194795 => '檨',
+ 194796 => '𣚣',
+ 194797 => '櫛',
+ 194798 => '㰘',
+ 194799 => '次',
+ 194800 => '𣢧',
+ 194801 => '歔',
+ 194802 => '㱎',
+ 194803 => '歲',
+ 194804 => '殟',
+ 194805 => '殺',
+ 194806 => '殻',
+ 194807 => '𣪍',
+ 194808 => '𡴋',
+ 194809 => '𣫺',
+ 194810 => '汎',
+ 194811 => '𣲼',
+ 194812 => '沿',
+ 194813 => '泍',
+ 194814 => '汧',
+ 194815 => '洖',
+ 194816 => '派',
+ 194817 => '海',
+ 194818 => '流',
+ 194819 => '浩',
+ 194820 => '浸',
+ 194821 => '涅',
+ 194822 => '𣴞',
+ 194823 => '洴',
+ 194824 => '港',
+ 194825 => '湮',
+ 194826 => '㴳',
+ 194827 => '滋',
+ 194828 => '滇',
+ 194829 => '𣻑',
+ 194830 => '淹',
+ 194831 => '潮',
+ 194832 => '𣽞',
+ 194833 => '𣾎',
+ 194834 => '濆',
+ 194835 => '瀹',
+ 194836 => '瀞',
+ 194837 => '瀛',
+ 194838 => '㶖',
+ 194839 => '灊',
+ 194840 => '災',
+ 194841 => '灷',
+ 194842 => '炭',
+ 194843 => '𠔥',
+ 194844 => '煅',
+ 194845 => '𤉣',
+ 194846 => '熜',
+ 194848 => '爨',
+ 194849 => '爵',
+ 194850 => '牐',
+ 194851 => '𤘈',
+ 194852 => '犀',
+ 194853 => '犕',
+ 194854 => '𤜵',
+ 194855 => '𤠔',
+ 194856 => '獺',
+ 194857 => '王',
+ 194858 => '㺬',
+ 194859 => '玥',
+ 194860 => '㺸',
+ 194861 => '㺸',
+ 194862 => '瑇',
+ 194863 => '瑜',
+ 194864 => '瑱',
+ 194865 => '璅',
+ 194866 => '瓊',
+ 194867 => '㼛',
+ 194868 => '甤',
+ 194869 => '𤰶',
+ 194870 => '甾',
+ 194871 => '𤲒',
+ 194872 => '異',
+ 194873 => '𢆟',
+ 194874 => '瘐',
+ 194875 => '𤾡',
+ 194876 => '𤾸',
+ 194877 => '𥁄',
+ 194878 => '㿼',
+ 194879 => '䀈',
+ 194880 => '直',
+ 194881 => '𥃳',
+ 194882 => '𥃲',
+ 194883 => '𥄙',
+ 194884 => '𥄳',
+ 194885 => '眞',
+ 194886 => '真',
+ 194887 => '真',
+ 194888 => '睊',
+ 194889 => '䀹',
+ 194890 => '瞋',
+ 194891 => '䁆',
+ 194892 => '䂖',
+ 194893 => '𥐝',
+ 194894 => '硎',
+ 194895 => '碌',
+ 194896 => '磌',
+ 194897 => '䃣',
+ 194898 => '𥘦',
+ 194899 => '祖',
+ 194900 => '𥚚',
+ 194901 => '𥛅',
+ 194902 => '福',
+ 194903 => '秫',
+ 194904 => '䄯',
+ 194905 => '穀',
+ 194906 => '穊',
+ 194907 => '穏',
+ 194908 => '𥥼',
+ 194909 => '𥪧',
+ 194910 => '𥪧',
+ 194912 => '䈂',
+ 194913 => '𥮫',
+ 194914 => '篆',
+ 194915 => '築',
+ 194916 => '䈧',
+ 194917 => '𥲀',
+ 194918 => '糒',
+ 194919 => '䊠',
+ 194920 => '糨',
+ 194921 => '糣',
+ 194922 => '紀',
+ 194923 => '𥾆',
+ 194924 => '絣',
+ 194925 => '䌁',
+ 194926 => '緇',
+ 194927 => '縂',
+ 194928 => '繅',
+ 194929 => '䌴',
+ 194930 => '𦈨',
+ 194931 => '𦉇',
+ 194932 => '䍙',
+ 194933 => '𦋙',
+ 194934 => '罺',
+ 194935 => '𦌾',
+ 194936 => '羕',
+ 194937 => '翺',
+ 194938 => '者',
+ 194939 => '𦓚',
+ 194940 => '𦔣',
+ 194941 => '聠',
+ 194942 => '𦖨',
+ 194943 => '聰',
+ 194944 => '𣍟',
+ 194945 => '䏕',
+ 194946 => '育',
+ 194947 => '脃',
+ 194948 => '䐋',
+ 194949 => '脾',
+ 194950 => '媵',
+ 194951 => '𦞧',
+ 194952 => '𦞵',
+ 194953 => '𣎓',
+ 194954 => '𣎜',
+ 194955 => '舁',
+ 194956 => '舄',
+ 194957 => '辞',
+ 194958 => '䑫',
+ 194959 => '芑',
+ 194960 => '芋',
+ 194961 => '芝',
+ 194962 => '劳',
+ 194963 => '花',
+ 194964 => '芳',
+ 194965 => '芽',
+ 194966 => '苦',
+ 194967 => '𦬼',
+ 194968 => '若',
+ 194969 => '茝',
+ 194970 => '荣',
+ 194971 => '莭',
+ 194972 => '茣',
+ 194973 => '莽',
+ 194974 => '菧',
+ 194975 => '著',
+ 194976 => '荓',
+ 194977 => '菊',
+ 194978 => '菌',
+ 194979 => '菜',
+ 194980 => '𦰶',
+ 194981 => '𦵫',
+ 194982 => '𦳕',
+ 194983 => '䔫',
+ 194984 => '蓱',
+ 194985 => '蓳',
+ 194986 => '蔖',
+ 194987 => '𧏊',
+ 194988 => '蕤',
+ 194989 => '𦼬',
+ 194990 => '䕝',
+ 194991 => '䕡',
+ 194992 => '𦾱',
+ 194993 => '𧃒',
+ 194994 => '䕫',
+ 194995 => '虐',
+ 194996 => '虜',
+ 194997 => '虧',
+ 194998 => '虩',
+ 194999 => '蚩',
+ 195000 => '蚈',
+ 195001 => '蜎',
+ 195002 => '蛢',
+ 195003 => '蝹',
+ 195004 => '蜨',
+ 195005 => '蝫',
+ 195006 => '螆',
+ 195008 => '蟡',
+ 195009 => '蠁',
+ 195010 => '䗹',
+ 195011 => '衠',
+ 195012 => '衣',
+ 195013 => '𧙧',
+ 195014 => '裗',
+ 195015 => '裞',
+ 195016 => '䘵',
+ 195017 => '裺',
+ 195018 => '㒻',
+ 195019 => '𧢮',
+ 195020 => '𧥦',
+ 195021 => '䚾',
+ 195022 => '䛇',
+ 195023 => '誠',
+ 195024 => '諭',
+ 195025 => '變',
+ 195026 => '豕',
+ 195027 => '𧲨',
+ 195028 => '貫',
+ 195029 => '賁',
+ 195030 => '贛',
+ 195031 => '起',
+ 195032 => '𧼯',
+ 195033 => '𠠄',
+ 195034 => '跋',
+ 195035 => '趼',
+ 195036 => '跰',
+ 195037 => '𠣞',
+ 195038 => '軔',
+ 195039 => '輸',
+ 195040 => '𨗒',
+ 195041 => '𨗭',
+ 195042 => '邔',
+ 195043 => '郱',
+ 195044 => '鄑',
+ 195045 => '𨜮',
+ 195046 => '鄛',
+ 195047 => '鈸',
+ 195048 => '鋗',
+ 195049 => '鋘',
+ 195050 => '鉼',
+ 195051 => '鏹',
+ 195052 => '鐕',
+ 195053 => '𨯺',
+ 195054 => '開',
+ 195055 => '䦕',
+ 195056 => '閷',
+ 195057 => '𨵷',
+ 195058 => '䧦',
+ 195059 => '雃',
+ 195060 => '嶲',
+ 195061 => '霣',
+ 195062 => '𩅅',
+ 195063 => '𩈚',
+ 195064 => '䩮',
+ 195065 => '䩶',
+ 195066 => '韠',
+ 195067 => '𩐊',
+ 195068 => '䪲',
+ 195069 => '𩒖',
+ 195070 => '頋',
+ 195071 => '頋',
+ 195072 => '頩',
+ 195073 => '𩖶',
+ 195074 => '飢',
+ 195075 => '䬳',
+ 195076 => '餩',
+ 195077 => '馧',
+ 195078 => '駂',
+ 195079 => '駾',
+ 195080 => '䯎',
+ 195081 => '𩬰',
+ 195082 => '鬒',
+ 195083 => '鱀',
+ 195084 => '鳽',
+ 195085 => '䳎',
+ 195086 => '䳭',
+ 195087 => '鵧',
+ 195088 => '𪃎',
+ 195089 => '䳸',
+ 195090 => '𪄅',
+ 195091 => '𪈎',
+ 195092 => '𪊑',
+ 195093 => '麻',
+ 195094 => '䵖',
+ 195095 => '黹',
+ 195096 => '黾',
+ 195097 => '鼅',
+ 195098 => '鼏',
+ 195099 => '鼖',
+ 195100 => '鼻',
+ 195101 => '𪘀',
+);
diff --git a/vendor/symfony/polyfill-intl-idn/Resources/unidata/virama.php b/vendor/symfony/polyfill-intl-idn/Resources/unidata/virama.php
new file mode 100755
index 0000000..1958e37
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/Resources/unidata/virama.php
@@ -0,0 +1,65 @@
+<?php
+
+return array (
+ 2381 => 9,
+ 2509 => 9,
+ 2637 => 9,
+ 2765 => 9,
+ 2893 => 9,
+ 3021 => 9,
+ 3149 => 9,
+ 3277 => 9,
+ 3387 => 9,
+ 3388 => 9,
+ 3405 => 9,
+ 3530 => 9,
+ 3642 => 9,
+ 3770 => 9,
+ 3972 => 9,
+ 4153 => 9,
+ 4154 => 9,
+ 5908 => 9,
+ 5940 => 9,
+ 6098 => 9,
+ 6752 => 9,
+ 6980 => 9,
+ 7082 => 9,
+ 7083 => 9,
+ 7154 => 9,
+ 7155 => 9,
+ 11647 => 9,
+ 43014 => 9,
+ 43052 => 9,
+ 43204 => 9,
+ 43347 => 9,
+ 43456 => 9,
+ 43766 => 9,
+ 44013 => 9,
+ 68159 => 9,
+ 69702 => 9,
+ 69759 => 9,
+ 69817 => 9,
+ 69939 => 9,
+ 69940 => 9,
+ 70080 => 9,
+ 70197 => 9,
+ 70378 => 9,
+ 70477 => 9,
+ 70722 => 9,
+ 70850 => 9,
+ 71103 => 9,
+ 71231 => 9,
+ 71350 => 9,
+ 71467 => 9,
+ 71737 => 9,
+ 71997 => 9,
+ 71998 => 9,
+ 72160 => 9,
+ 72244 => 9,
+ 72263 => 9,
+ 72345 => 9,
+ 72767 => 9,
+ 73028 => 9,
+ 73029 => 9,
+ 73111 => 9,
+);
diff --git a/vendor/symfony/polyfill-intl-idn/bootstrap.php b/vendor/symfony/polyfill-intl-idn/bootstrap.php
new file mode 100755
index 0000000..f02d5de
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/bootstrap.php
@@ -0,0 +1,141 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Intl\Idn as p;
+
+if (extension_loaded('intl')) {
+ return;
+}
+
+if (!defined('U_IDNA_PROHIBITED_ERROR')) {
+ define('U_IDNA_PROHIBITED_ERROR', 66560);
+}
+if (!defined('U_IDNA_ERROR_START')) {
+ define('U_IDNA_ERROR_START', 66560);
+}
+if (!defined('U_IDNA_UNASSIGNED_ERROR')) {
+ define('U_IDNA_UNASSIGNED_ERROR', 66561);
+}
+if (!defined('U_IDNA_CHECK_BIDI_ERROR')) {
+ define('U_IDNA_CHECK_BIDI_ERROR', 66562);
+}
+if (!defined('U_IDNA_STD3_ASCII_RULES_ERROR')) {
+ define('U_IDNA_STD3_ASCII_RULES_ERROR', 66563);
+}
+if (!defined('U_IDNA_ACE_PREFIX_ERROR')) {
+ define('U_IDNA_ACE_PREFIX_ERROR', 66564);
+}
+if (!defined('U_IDNA_VERIFICATION_ERROR')) {
+ define('U_IDNA_VERIFICATION_ERROR', 66565);
+}
+if (!defined('U_IDNA_LABEL_TOO_LONG_ERROR')) {
+ define('U_IDNA_LABEL_TOO_LONG_ERROR', 66566);
+}
+if (!defined('U_IDNA_ZERO_LENGTH_LABEL_ERROR')) {
+ define('U_IDNA_ZERO_LENGTH_LABEL_ERROR', 66567);
+}
+if (!defined('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR')) {
+ define('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR', 66568);
+}
+if (!defined('U_IDNA_ERROR_LIMIT')) {
+ define('U_IDNA_ERROR_LIMIT', 66569);
+}
+if (!defined('U_STRINGPREP_PROHIBITED_ERROR')) {
+ define('U_STRINGPREP_PROHIBITED_ERROR', 66560);
+}
+if (!defined('U_STRINGPREP_UNASSIGNED_ERROR')) {
+ define('U_STRINGPREP_UNASSIGNED_ERROR', 66561);
+}
+if (!defined('U_STRINGPREP_CHECK_BIDI_ERROR')) {
+ define('U_STRINGPREP_CHECK_BIDI_ERROR', 66562);
+}
+if (!defined('IDNA_DEFAULT')) {
+ define('IDNA_DEFAULT', 0);
+}
+if (!defined('IDNA_ALLOW_UNASSIGNED')) {
+ define('IDNA_ALLOW_UNASSIGNED', 1);
+}
+if (!defined('IDNA_USE_STD3_RULES')) {
+ define('IDNA_USE_STD3_RULES', 2);
+}
+if (!defined('IDNA_CHECK_BIDI')) {
+ define('IDNA_CHECK_BIDI', 4);
+}
+if (!defined('IDNA_CHECK_CONTEXTJ')) {
+ define('IDNA_CHECK_CONTEXTJ', 8);
+}
+if (!defined('IDNA_NONTRANSITIONAL_TO_ASCII')) {
+ define('IDNA_NONTRANSITIONAL_TO_ASCII', 16);
+}
+if (!defined('IDNA_NONTRANSITIONAL_TO_UNICODE')) {
+ define('IDNA_NONTRANSITIONAL_TO_UNICODE', 32);
+}
+if (!defined('INTL_IDNA_VARIANT_2003')) {
+ define('INTL_IDNA_VARIANT_2003', 0);
+}
+if (!defined('INTL_IDNA_VARIANT_UTS46')) {
+ define('INTL_IDNA_VARIANT_UTS46', 1);
+}
+if (!defined('IDNA_ERROR_EMPTY_LABEL')) {
+ define('IDNA_ERROR_EMPTY_LABEL', 1);
+}
+if (!defined('IDNA_ERROR_LABEL_TOO_LONG')) {
+ define('IDNA_ERROR_LABEL_TOO_LONG', 2);
+}
+if (!defined('IDNA_ERROR_DOMAIN_NAME_TOO_LONG')) {
+ define('IDNA_ERROR_DOMAIN_NAME_TOO_LONG', 4);
+}
+if (!defined('IDNA_ERROR_LEADING_HYPHEN')) {
+ define('IDNA_ERROR_LEADING_HYPHEN', 8);
+}
+if (!defined('IDNA_ERROR_TRAILING_HYPHEN')) {
+ define('IDNA_ERROR_TRAILING_HYPHEN', 16);
+}
+if (!defined('IDNA_ERROR_HYPHEN_3_4')) {
+ define('IDNA_ERROR_HYPHEN_3_4', 32);
+}
+if (!defined('IDNA_ERROR_LEADING_COMBINING_MARK')) {
+ define('IDNA_ERROR_LEADING_COMBINING_MARK', 64);
+}
+if (!defined('IDNA_ERROR_DISALLOWED')) {
+ define('IDNA_ERROR_DISALLOWED', 128);
+}
+if (!defined('IDNA_ERROR_PUNYCODE')) {
+ define('IDNA_ERROR_PUNYCODE', 256);
+}
+if (!defined('IDNA_ERROR_LABEL_HAS_DOT')) {
+ define('IDNA_ERROR_LABEL_HAS_DOT', 512);
+}
+if (!defined('IDNA_ERROR_INVALID_ACE_LABEL')) {
+ define('IDNA_ERROR_INVALID_ACE_LABEL', 1024);
+}
+if (!defined('IDNA_ERROR_BIDI')) {
+ define('IDNA_ERROR_BIDI', 2048);
+}
+if (!defined('IDNA_ERROR_CONTEXTJ')) {
+ define('IDNA_ERROR_CONTEXTJ', 4096);
+}
+
+if (PHP_VERSION_ID < 70400) {
+ if (!function_exists('idn_to_ascii')) {
+ function idn_to_ascii($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_2003, &$idna_info = array()) { return p\Idn::idn_to_ascii($domain, $options, $variant, $idna_info); }
+ }
+ if (!function_exists('idn_to_utf8')) {
+ function idn_to_utf8($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_2003, &$idna_info = array()) { return p\Idn::idn_to_utf8($domain, $options, $variant, $idna_info); }
+ }
+} else {
+ if (!function_exists('idn_to_ascii')) {
+ function idn_to_ascii($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = array()) { return p\Idn::idn_to_ascii($domain, $options, $variant, $idna_info); }
+ }
+ if (!function_exists('idn_to_utf8')) {
+ function idn_to_utf8($domain, $options = IDNA_DEFAULT, $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = array()) { return p\Idn::idn_to_utf8($domain, $options, $variant, $idna_info); }
+ }
+}
diff --git a/vendor/symfony/polyfill-intl-idn/composer.json b/vendor/symfony/polyfill-intl-idn/composer.json
new file mode 100755
index 0000000..af34193
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-idn/composer.json
@@ -0,0 +1,45 @@
+{
+ "name": "symfony/polyfill-intl-idn",
+ "type": "library",
+ "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+ "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "idn"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Laurent Bassin",
+ "email": "laurent@bassin.info"
+ },
+ {
+ "name": "Trevor Rowbotham",
+ "email": "trevor.rowbotham@pm.me"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3",
+ "symfony/polyfill-intl-normalizer": "^1.10",
+ "symfony/polyfill-php70": "^1.10",
+ "symfony/polyfill-php72": "^1.10"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Intl\\Idn\\": "" },
+ "files": [ "bootstrap.php" ]
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.18-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ }
+}
diff --git a/vendor/symfony/polyfill-intl-normalizer/LICENSE b/vendor/symfony/polyfill-intl-normalizer/LICENSE
new file mode 100755
index 0000000..4cd8bdd
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-normalizer/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/polyfill-intl-normalizer/Normalizer.php b/vendor/symfony/polyfill-intl-normalizer/Normalizer.php
new file mode 100755
index 0000000..a60fae6
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-normalizer/Normalizer.php
@@ -0,0 +1,308 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Intl\Normalizer;
+
+/**
+ * Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension.
+ *
+ * It has been validated with Unicode 6.3 Normalization Conformance Test.
+ * See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+class Normalizer
+{
+ const FORM_D = \Normalizer::FORM_D;
+ const FORM_KD = \Normalizer::FORM_KD;
+ const FORM_C = \Normalizer::FORM_C;
+ const FORM_KC = \Normalizer::FORM_KC;
+ const NFD = \Normalizer::NFD;
+ const NFKD = \Normalizer::NFKD;
+ const NFC = \Normalizer::NFC;
+ const NFKC = \Normalizer::NFKC;
+
+ private static $C;
+ private static $D;
+ private static $KD;
+ private static $cC;
+ private static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);
+ private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
+
+ public static function isNormalized($s, $form = self::NFC)
+ {
+ if (!\in_array($form, array(self::NFD, self::NFKD, self::NFC, self::NFKC))) {
+ return false;
+ }
+ $s = (string) $s;
+ if (!isset($s[strspn($s, self::$ASCII)])) {
+ return true;
+ }
+ if (self::NFC == $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) {
+ return true;
+ }
+
+ return self::normalize($s, $form) === $s;
+ }
+
+ public static function normalize($s, $form = self::NFC)
+ {
+ $s = (string) $s;
+ if (!preg_match('//u', $s)) {
+ return false;
+ }
+
+ switch ($form) {
+ case self::NFC: $C = true; $K = false; break;
+ case self::NFD: $C = false; $K = false; break;
+ case self::NFKC: $C = true; $K = true; break;
+ case self::NFKD: $C = false; $K = true; break;
+ default:
+ if (\defined('Normalizer::NONE') && \Normalizer::NONE == $form) {
+ return $s;
+ }
+
+ return false;
+ }
+
+ if ('' === $s) {
+ return '';
+ }
+
+ if ($K && null === self::$KD) {
+ self::$KD = self::getData('compatibilityDecomposition');
+ }
+
+ if (null === self::$D) {
+ self::$D = self::getData('canonicalDecomposition');
+ self::$cC = self::getData('combiningClass');
+ }
+
+ if (null !== $mbEncoding = (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) ? mb_internal_encoding() : null) {
+ mb_internal_encoding('8bit');
+ }
+
+ $r = self::decompose($s, $K);
+
+ if ($C) {
+ if (null === self::$C) {
+ self::$C = self::getData('canonicalComposition');
+ }
+
+ $r = self::recompose($r);
+ }
+ if (null !== $mbEncoding) {
+ mb_internal_encoding($mbEncoding);
+ }
+
+ return $r;
+ }
+
+ private static function recompose($s)
+ {
+ $ASCII = self::$ASCII;
+ $compMap = self::$C;
+ $combClass = self::$cC;
+ $ulenMask = self::$ulenMask;
+
+ $result = $tail = '';
+
+ $i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xF0"];
+ $len = \strlen($s);
+
+ $lastUchr = substr($s, 0, $i);
+ $lastUcls = isset($combClass[$lastUchr]) ? 256 : 0;
+
+ while ($i < $len) {
+ if ($s[$i] < "\x80") {
+ // ASCII chars
+
+ if ($tail) {
+ $lastUchr .= $tail;
+ $tail = '';
+ }
+
+ if ($j = strspn($s, $ASCII, $i + 1)) {
+ $lastUchr .= substr($s, $i, $j);
+ $i += $j;
+ }
+
+ $result .= $lastUchr;
+ $lastUchr = $s[$i];
+ $lastUcls = 0;
+ ++$i;
+ continue;
+ }
+
+ $ulen = $ulenMask[$s[$i] & "\xF0"];
+ $uchr = substr($s, $i, $ulen);
+
+ if ($lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr
+ || $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr
+ || $lastUcls) {
+ // Table lookup and combining chars composition
+
+ $ucls = isset($combClass[$uchr]) ? $combClass[$uchr] : 0;
+
+ if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) {
+ $lastUchr = $compMap[$lastUchr.$uchr];
+ } elseif ($lastUcls = $ucls) {
+ $tail .= $uchr;
+ } else {
+ if ($tail) {
+ $lastUchr .= $tail;
+ $tail = '';
+ }
+
+ $result .= $lastUchr;
+ $lastUchr = $uchr;
+ }
+ } else {
+ // Hangul chars
+
+ $L = \ord($lastUchr[2]) - 0x80;
+ $V = \ord($uchr[2]) - 0xA1;
+ $T = 0;
+
+ $uchr = substr($s, $i + $ulen, 3);
+
+ if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82") {
+ $T = \ord($uchr[2]) - 0xA7;
+ 0 > $T && $T += 0x40;
+ $ulen += 3;
+ }
+
+ $L = 0xAC00 + ($L * 21 + $V) * 28 + $T;
+ $lastUchr = \chr(0xE0 | $L >> 12).\chr(0x80 | $L >> 6 & 0x3F).\chr(0x80 | $L & 0x3F);
+ }
+
+ $i += $ulen;
+ }
+
+ return $result.$lastUchr.$tail;
+ }
+
+ private static function decompose($s, $c)
+ {
+ $result = '';
+
+ $ASCII = self::$ASCII;
+ $decompMap = self::$D;
+ $combClass = self::$cC;
+ $ulenMask = self::$ulenMask;
+ if ($c) {
+ $compatMap = self::$KD;
+ }
+
+ $c = array();
+ $i = 0;
+ $len = \strlen($s);
+
+ while ($i < $len) {
+ if ($s[$i] < "\x80") {
+ // ASCII chars
+
+ if ($c) {
+ ksort($c);
+ $result .= implode('', $c);
+ $c = array();
+ }
+
+ $j = 1 + strspn($s, $ASCII, $i + 1);
+ $result .= substr($s, $i, $j);
+ $i += $j;
+ continue;
+ }
+
+ $ulen = $ulenMask[$s[$i] & "\xF0"];
+ $uchr = substr($s, $i, $ulen);
+ $i += $ulen;
+
+ if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) {
+ // Table lookup
+
+ if ($uchr !== $j = isset($compatMap[$uchr]) ? $compatMap[$uchr] : (isset($decompMap[$uchr]) ? $decompMap[$uchr] : $uchr)) {
+ $uchr = $j;
+
+ $j = \strlen($uchr);
+ $ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xF0"];
+
+ if ($ulen != $j) {
+ // Put trailing chars in $s
+
+ $j -= $ulen;
+ $i -= $j;
+
+ if (0 > $i) {
+ $s = str_repeat(' ', -$i).$s;
+ $len -= $i;
+ $i = 0;
+ }
+
+ while ($j--) {
+ $s[$i + $j] = $uchr[$ulen + $j];
+ }
+
+ $uchr = substr($uchr, 0, $ulen);
+ }
+ }
+ if (isset($combClass[$uchr])) {
+ // Combining chars, for sorting
+
+ if (!isset($c[$combClass[$uchr]])) {
+ $c[$combClass[$uchr]] = '';
+ }
+ $c[$combClass[$uchr]] .= $uchr;
+ continue;
+ }
+ } else {
+ // Hangul chars
+
+ $uchr = unpack('C*', $uchr);
+ $j = (($uchr[1] - 224) << 12) + (($uchr[2] - 128) << 6) + $uchr[3] - 0xAC80;
+
+ $uchr = "\xE1\x84".\chr(0x80 + (int) ($j / 588))
+ ."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28));
+
+ if ($j %= 28) {
+ $uchr .= $j < 25
+ ? ("\xE1\x86".\chr(0xA7 + $j))
+ : ("\xE1\x87".\chr(0x67 + $j));
+ }
+ }
+ if ($c) {
+ ksort($c);
+ $result .= implode('', $c);
+ $c = array();
+ }
+
+ $result .= $uchr;
+ }
+
+ if ($c) {
+ ksort($c);
+ $result .= implode('', $c);
+ }
+
+ return $result;
+ }
+
+ private static function getData($file)
+ {
+ if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
+ return require $file;
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/symfony/polyfill-intl-normalizer/README.md b/vendor/symfony/polyfill-intl-normalizer/README.md
new file mode 100755
index 0000000..15060c5
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-normalizer/README.md
@@ -0,0 +1,14 @@
+Symfony Polyfill / Intl: Normalizer
+===================================
+
+This component provides a fallback implementation for the
+[`Normalizer`](https://php.net/Normalizer) class provided
+by the [Intl](https://php.net/intl) extension.
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php b/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php
new file mode 100755
index 0000000..ca18eff
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php
@@ -0,0 +1,17 @@
+<?php
+
+class Normalizer extends Symfony\Polyfill\Intl\Normalizer\Normalizer
+{
+ /**
+ * @deprecated since ICU 56 and removed in PHP 8
+ */
+ const NONE = 1;
+ const FORM_D = 2;
+ const FORM_KD = 3;
+ const FORM_C = 4;
+ const FORM_KC = 5;
+ const NFD = 2;
+ const NFKD = 3;
+ const NFC = 4;
+ const NFKC = 5;
+}
diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php
new file mode 100755
index 0000000..db47644
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php
@@ -0,0 +1,945 @@
+<?php
+
+return array (
+ 'À' => 'À',
+ 'Á' => 'Á',
+ 'Â' => 'Â',
+ 'Ã' => 'Ã',
+ 'Ä' => 'Ä',
+ 'Å' => 'Å',
+ 'Ç' => 'Ç',
+ 'È' => 'È',
+ 'É' => 'É',
+ 'Ê' => 'Ê',
+ 'Ë' => 'Ë',
+ 'Ì' => 'Ì',
+ 'Í' => 'Í',
+ 'Î' => 'Î',
+ 'Ï' => 'Ï',
+ 'Ñ' => 'Ñ',
+ 'Ò' => 'Ò',
+ 'Ó' => 'Ó',
+ 'Ô' => 'Ô',
+ 'Õ' => 'Õ',
+ 'Ö' => 'Ö',
+ 'Ù' => 'Ù',
+ 'Ú' => 'Ú',
+ 'Û' => 'Û',
+ 'Ü' => 'Ü',
+ 'Ý' => 'Ý',
+ 'à' => 'à',
+ 'á' => 'á',
+ 'â' => 'â',
+ 'ã' => 'ã',
+ 'ä' => 'ä',
+ 'å' => 'å',
+ 'ç' => 'ç',
+ 'è' => 'è',
+ 'é' => 'é',
+ 'ê' => 'ê',
+ 'ë' => 'ë',
+ 'ì' => 'ì',
+ 'í' => 'í',
+ 'î' => 'î',
+ 'ï' => 'ï',
+ 'ñ' => 'ñ',
+ 'ò' => 'ò',
+ 'ó' => 'ó',
+ 'ô' => 'ô',
+ 'õ' => 'õ',
+ 'ö' => 'ö',
+ 'ù' => 'ù',
+ 'ú' => 'ú',
+ 'û' => 'û',
+ 'ü' => 'ü',
+ 'ý' => 'ý',
+ 'ÿ' => 'ÿ',
+ 'Ā' => 'Ā',
+ 'ā' => 'ā',
+ 'Ă' => 'Ă',
+ 'ă' => 'ă',
+ 'Ą' => 'Ą',
+ 'ą' => 'ą',
+ 'Ć' => 'Ć',
+ 'ć' => 'ć',
+ 'Ĉ' => 'Ĉ',
+ 'ĉ' => 'ĉ',
+ 'Ċ' => 'Ċ',
+ 'ċ' => 'ċ',
+ 'Č' => 'Č',
+ 'č' => 'č',
+ 'Ď' => 'Ď',
+ 'ď' => 'ď',
+ 'Ē' => 'Ē',
+ 'ē' => 'ē',
+ 'Ĕ' => 'Ĕ',
+ 'ĕ' => 'ĕ',
+ 'Ė' => 'Ė',
+ 'ė' => 'ė',
+ 'Ę' => 'Ę',
+ 'ę' => 'ę',
+ 'Ě' => 'Ě',
+ 'ě' => 'ě',
+ 'Ĝ' => 'Ĝ',
+ 'ĝ' => 'ĝ',
+ 'Ğ' => 'Ğ',
+ 'ğ' => 'ğ',
+ 'Ġ' => 'Ġ',
+ 'ġ' => 'ġ',
+ 'Ģ' => 'Ģ',
+ 'ģ' => 'ģ',
+ 'Ĥ' => 'Ĥ',
+ 'ĥ' => 'ĥ',
+ 'Ĩ' => 'Ĩ',
+ 'ĩ' => 'ĩ',
+ 'Ī' => 'Ī',
+ 'ī' => 'ī',
+ 'Ĭ' => 'Ĭ',
+ 'ĭ' => 'ĭ',
+ 'Į' => 'Į',
+ 'į' => 'į',
+ 'İ' => 'İ',
+ 'Ĵ' => 'Ĵ',
+ 'ĵ' => 'ĵ',
+ 'Ķ' => 'Ķ',
+ 'ķ' => 'ķ',
+ 'Ĺ' => 'Ĺ',
+ 'ĺ' => 'ĺ',
+ 'Ļ' => 'Ļ',
+ 'ļ' => 'ļ',
+ 'Ľ' => 'Ľ',
+ 'ľ' => 'ľ',
+ 'Ń' => 'Ń',
+ 'ń' => 'ń',
+ 'Ņ' => 'Ņ',
+ 'ņ' => 'ņ',
+ 'Ň' => 'Ň',
+ 'ň' => 'ň',
+ 'Ō' => 'Ō',
+ 'ō' => 'ō',
+ 'Ŏ' => 'Ŏ',
+ 'ŏ' => 'ŏ',
+ 'Ő' => 'Ő',
+ 'ő' => 'ő',
+ 'Ŕ' => 'Ŕ',
+ 'ŕ' => 'ŕ',
+ 'Ŗ' => 'Ŗ',
+ 'ŗ' => 'ŗ',
+ 'Ř' => 'Ř',
+ 'ř' => 'ř',
+ 'Ś' => 'Ś',
+ 'ś' => 'ś',
+ 'Ŝ' => 'Ŝ',
+ 'ŝ' => 'ŝ',
+ 'Ş' => 'Ş',
+ 'ş' => 'ş',
+ 'Š' => 'Š',
+ 'š' => 'š',
+ 'Ţ' => 'Ţ',
+ 'ţ' => 'ţ',
+ 'Ť' => 'Ť',
+ 'ť' => 'ť',
+ 'Ũ' => 'Ũ',
+ 'ũ' => 'ũ',
+ 'Ū' => 'Ū',
+ 'ū' => 'ū',
+ 'Ŭ' => 'Ŭ',
+ 'ŭ' => 'ŭ',
+ 'Ů' => 'Ů',
+ 'ů' => 'ů',
+ 'Ű' => 'Ű',
+ 'ű' => 'ű',
+ 'Ų' => 'Ų',
+ 'ų' => 'ų',
+ 'Ŵ' => 'Ŵ',
+ 'ŵ' => 'ŵ',
+ 'Ŷ' => 'Ŷ',
+ 'ŷ' => 'ŷ',
+ 'Ÿ' => 'Ÿ',
+ 'Ź' => 'Ź',
+ 'ź' => 'ź',
+ 'Ż' => 'Ż',
+ 'ż' => 'ż',
+ 'Ž' => 'Ž',
+ 'ž' => 'ž',
+ 'Ơ' => 'Ơ',
+ 'ơ' => 'ơ',
+ 'Ư' => 'Ư',
+ 'ư' => 'ư',
+ 'Ǎ' => 'Ǎ',
+ 'ǎ' => 'ǎ',
+ 'Ǐ' => 'Ǐ',
+ 'ǐ' => 'ǐ',
+ 'Ǒ' => 'Ǒ',
+ 'ǒ' => 'ǒ',
+ 'Ǔ' => 'Ǔ',
+ 'ǔ' => 'ǔ',
+ 'Ǖ' => 'Ǖ',
+ 'ǖ' => 'ǖ',
+ 'Ǘ' => 'Ǘ',
+ 'ǘ' => 'ǘ',
+ 'Ǚ' => 'Ǚ',
+ 'ǚ' => 'ǚ',
+ 'Ǜ' => 'Ǜ',
+ 'ǜ' => 'ǜ',
+ 'Ǟ' => 'Ǟ',
+ 'ǟ' => 'ǟ',
+ 'Ǡ' => 'Ǡ',
+ 'ǡ' => 'ǡ',
+ 'Ǣ' => 'Ǣ',
+ 'ǣ' => 'ǣ',
+ 'Ǧ' => 'Ǧ',
+ 'ǧ' => 'ǧ',
+ 'Ǩ' => 'Ǩ',
+ 'ǩ' => 'ǩ',
+ 'Ǫ' => 'Ǫ',
+ 'ǫ' => 'ǫ',
+ 'Ǭ' => 'Ǭ',
+ 'ǭ' => 'ǭ',
+ 'Ǯ' => 'Ǯ',
+ 'ǯ' => 'ǯ',
+ 'ǰ' => 'ǰ',
+ 'Ǵ' => 'Ǵ',
+ 'ǵ' => 'ǵ',
+ 'Ǹ' => 'Ǹ',
+ 'ǹ' => 'ǹ',
+ 'Ǻ' => 'Ǻ',
+ 'ǻ' => 'ǻ',
+ 'Ǽ' => 'Ǽ',
+ 'ǽ' => 'ǽ',
+ 'Ǿ' => 'Ǿ',
+ 'ǿ' => 'ǿ',
+ 'Ȁ' => 'Ȁ',
+ 'ȁ' => 'ȁ',
+ 'Ȃ' => 'Ȃ',
+ 'ȃ' => 'ȃ',
+ 'Ȅ' => 'Ȅ',
+ 'ȅ' => 'ȅ',
+ 'Ȇ' => 'Ȇ',
+ 'ȇ' => 'ȇ',
+ 'Ȉ' => 'Ȉ',
+ 'ȉ' => 'ȉ',
+ 'Ȋ' => 'Ȋ',
+ 'ȋ' => 'ȋ',
+ 'Ȍ' => 'Ȍ',
+ 'ȍ' => 'ȍ',
+ 'Ȏ' => 'Ȏ',
+ 'ȏ' => 'ȏ',
+ 'Ȑ' => 'Ȑ',
+ 'ȑ' => 'ȑ',
+ 'Ȓ' => 'Ȓ',
+ 'ȓ' => 'ȓ',
+ 'Ȕ' => 'Ȕ',
+ 'ȕ' => 'ȕ',
+ 'Ȗ' => 'Ȗ',
+ 'ȗ' => 'ȗ',
+ 'Ș' => 'Ș',
+ 'ș' => 'ș',
+ 'Ț' => 'Ț',
+ 'ț' => 'ț',
+ 'Ȟ' => 'Ȟ',
+ 'ȟ' => 'ȟ',
+ 'Ȧ' => 'Ȧ',
+ 'ȧ' => 'ȧ',
+ 'Ȩ' => 'Ȩ',
+ 'ȩ' => 'ȩ',
+ 'Ȫ' => 'Ȫ',
+ 'ȫ' => 'ȫ',
+ 'Ȭ' => 'Ȭ',
+ 'ȭ' => 'ȭ',
+ 'Ȯ' => 'Ȯ',
+ 'ȯ' => 'ȯ',
+ 'Ȱ' => 'Ȱ',
+ 'ȱ' => 'ȱ',
+ 'Ȳ' => 'Ȳ',
+ 'ȳ' => 'ȳ',
+ '΅' => '΅',
+ 'Ά' => 'Ά',
+ 'Έ' => 'Έ',
+ 'Ή' => 'Ή',
+ 'Ί' => 'Ί',
+ 'Ό' => 'Ό',
+ 'Ύ' => 'Ύ',
+ 'Ώ' => 'Ώ',
+ 'ΐ' => 'ΐ',
+ 'Ϊ' => 'Ϊ',
+ 'Ϋ' => 'Ϋ',
+ 'ά' => 'ά',
+ 'έ' => 'έ',
+ 'ή' => 'ή',
+ 'ί' => 'ί',
+ 'ΰ' => 'ΰ',
+ 'ϊ' => 'ϊ',
+ 'ϋ' => 'ϋ',
+ 'ό' => 'ό',
+ 'ύ' => 'ύ',
+ 'ώ' => 'ώ',
+ 'ϓ' => 'ϓ',
+ 'ϔ' => 'ϔ',
+ 'Ѐ' => 'Ѐ',
+ 'Ё' => 'Ё',
+ 'Ѓ' => 'Ѓ',
+ 'Ї' => 'Ї',
+ 'Ќ' => 'Ќ',
+ 'Ѝ' => 'Ѝ',
+ 'Ў' => 'Ў',
+ 'Й' => 'Й',
+ 'й' => 'й',
+ 'ѐ' => 'ѐ',
+ 'ё' => 'ё',
+ 'ѓ' => 'ѓ',
+ 'ї' => 'ї',
+ 'ќ' => 'ќ',
+ 'ѝ' => 'ѝ',
+ 'ў' => 'ў',
+ 'Ѷ' => 'Ѷ',
+ 'ѷ' => 'ѷ',
+ 'Ӂ' => 'Ӂ',
+ 'ӂ' => 'ӂ',
+ 'Ӑ' => 'Ӑ',
+ 'ӑ' => 'ӑ',
+ 'Ӓ' => 'Ӓ',
+ 'ӓ' => 'ӓ',
+ 'Ӗ' => 'Ӗ',
+ 'ӗ' => 'ӗ',
+ 'Ӛ' => 'Ӛ',
+ 'ӛ' => 'ӛ',
+ 'Ӝ' => 'Ӝ',
+ 'ӝ' => 'ӝ',
+ 'Ӟ' => 'Ӟ',
+ 'ӟ' => 'ӟ',
+ 'Ӣ' => 'Ӣ',
+ 'ӣ' => 'ӣ',
+ 'Ӥ' => 'Ӥ',
+ 'ӥ' => 'ӥ',
+ 'Ӧ' => 'Ӧ',
+ 'ӧ' => 'ӧ',
+ 'Ӫ' => 'Ӫ',
+ 'ӫ' => 'ӫ',
+ 'Ӭ' => 'Ӭ',
+ 'ӭ' => 'ӭ',
+ 'Ӯ' => 'Ӯ',
+ 'ӯ' => 'ӯ',
+ 'Ӱ' => 'Ӱ',
+ 'ӱ' => 'ӱ',
+ 'Ӳ' => 'Ӳ',
+ 'ӳ' => 'ӳ',
+ 'Ӵ' => 'Ӵ',
+ 'ӵ' => 'ӵ',
+ 'Ӹ' => 'Ӹ',
+ 'ӹ' => 'ӹ',
+ 'آ' => 'آ',
+ 'أ' => 'أ',
+ 'ؤ' => 'ؤ',
+ 'إ' => 'إ',
+ 'ئ' => 'ئ',
+ 'ۀ' => 'ۀ',
+ 'ۂ' => 'ۂ',
+ 'ۓ' => 'ۓ',
+ 'ऩ' => 'ऩ',
+ 'ऱ' => 'ऱ',
+ 'ऴ' => 'ऴ',
+ 'ো' => 'ো',
+ 'ৌ' => 'ৌ',
+ 'ୈ' => 'ୈ',
+ 'ୋ' => 'ୋ',
+ 'ୌ' => 'ୌ',
+ 'ஔ' => 'ஔ',
+ 'ொ' => 'ொ',
+ 'ோ' => 'ோ',
+ 'ௌ' => 'ௌ',
+ 'ై' => 'ై',
+ 'ೀ' => 'ೀ',
+ 'ೇ' => 'ೇ',
+ 'ೈ' => 'ೈ',
+ 'ೊ' => 'ೊ',
+ 'ೋ' => 'ೋ',
+ 'ൊ' => 'ൊ',
+ 'ോ' => 'ോ',
+ 'ൌ' => 'ൌ',
+ 'ේ' => 'ේ',
+ 'ො' => 'ො',
+ 'ෝ' => 'ෝ',
+ 'ෞ' => 'ෞ',
+ 'ဦ' => 'ဦ',
+ 'ᬆ' => 'ᬆ',
+ 'ᬈ' => 'ᬈ',
+ 'ᬊ' => 'ᬊ',
+ 'ᬌ' => 'ᬌ',
+ 'ᬎ' => 'ᬎ',
+ 'ᬒ' => 'ᬒ',
+ 'ᬻ' => 'ᬻ',
+ 'ᬽ' => 'ᬽ',
+ 'ᭀ' => 'ᭀ',
+ 'ᭁ' => 'ᭁ',
+ 'ᭃ' => 'ᭃ',
+ 'Ḁ' => 'Ḁ',
+ 'ḁ' => 'ḁ',
+ 'Ḃ' => 'Ḃ',
+ 'ḃ' => 'ḃ',
+ 'Ḅ' => 'Ḅ',
+ 'ḅ' => 'ḅ',
+ 'Ḇ' => 'Ḇ',
+ 'ḇ' => 'ḇ',
+ 'Ḉ' => 'Ḉ',
+ 'ḉ' => 'ḉ',
+ 'Ḋ' => 'Ḋ',
+ 'ḋ' => 'ḋ',
+ 'Ḍ' => 'Ḍ',
+ 'ḍ' => 'ḍ',
+ 'Ḏ' => 'Ḏ',
+ 'ḏ' => 'ḏ',
+ 'Ḑ' => 'Ḑ',
+ 'ḑ' => 'ḑ',
+ 'Ḓ' => 'Ḓ',
+ 'ḓ' => 'ḓ',
+ 'Ḕ' => 'Ḕ',
+ 'ḕ' => 'ḕ',
+ 'Ḗ' => 'Ḗ',
+ 'ḗ' => 'ḗ',
+ 'Ḙ' => 'Ḙ',
+ 'ḙ' => 'ḙ',
+ 'Ḛ' => 'Ḛ',
+ 'ḛ' => 'ḛ',
+ 'Ḝ' => 'Ḝ',
+ 'ḝ' => 'ḝ',
+ 'Ḟ' => 'Ḟ',
+ 'ḟ' => 'ḟ',
+ 'Ḡ' => 'Ḡ',
+ 'ḡ' => 'ḡ',
+ 'Ḣ' => 'Ḣ',
+ 'ḣ' => 'ḣ',
+ 'Ḥ' => 'Ḥ',
+ 'ḥ' => 'ḥ',
+ 'Ḧ' => 'Ḧ',
+ 'ḧ' => 'ḧ',
+ 'Ḩ' => 'Ḩ',
+ 'ḩ' => 'ḩ',
+ 'Ḫ' => 'Ḫ',
+ 'ḫ' => 'ḫ',
+ 'Ḭ' => 'Ḭ',
+ 'ḭ' => 'ḭ',
+ 'Ḯ' => 'Ḯ',
+ 'ḯ' => 'ḯ',
+ 'Ḱ' => 'Ḱ',
+ 'ḱ' => 'ḱ',
+ 'Ḳ' => 'Ḳ',
+ 'ḳ' => 'ḳ',
+ 'Ḵ' => 'Ḵ',
+ 'ḵ' => 'ḵ',
+ 'Ḷ' => 'Ḷ',
+ 'ḷ' => 'ḷ',
+ 'Ḹ' => 'Ḹ',
+ 'ḹ' => 'ḹ',
+ 'Ḻ' => 'Ḻ',
+ 'ḻ' => 'ḻ',
+ 'Ḽ' => 'Ḽ',
+ 'ḽ' => 'ḽ',
+ 'Ḿ' => 'Ḿ',
+ 'ḿ' => 'ḿ',
+ 'Ṁ' => 'Ṁ',
+ 'ṁ' => 'ṁ',
+ 'Ṃ' => 'Ṃ',
+ 'ṃ' => 'ṃ',
+ 'Ṅ' => 'Ṅ',
+ 'ṅ' => 'ṅ',
+ 'Ṇ' => 'Ṇ',
+ 'ṇ' => 'ṇ',
+ 'Ṉ' => 'Ṉ',
+ 'ṉ' => 'ṉ',
+ 'Ṋ' => 'Ṋ',
+ 'ṋ' => 'ṋ',
+ 'Ṍ' => 'Ṍ',
+ 'ṍ' => 'ṍ',
+ 'Ṏ' => 'Ṏ',
+ 'ṏ' => 'ṏ',
+ 'Ṑ' => 'Ṑ',
+ 'ṑ' => 'ṑ',
+ 'Ṓ' => 'Ṓ',
+ 'ṓ' => 'ṓ',
+ 'Ṕ' => 'Ṕ',
+ 'ṕ' => 'ṕ',
+ 'Ṗ' => 'Ṗ',
+ 'ṗ' => 'ṗ',
+ 'Ṙ' => 'Ṙ',
+ 'ṙ' => 'ṙ',
+ 'Ṛ' => 'Ṛ',
+ 'ṛ' => 'ṛ',
+ 'Ṝ' => 'Ṝ',
+ 'ṝ' => 'ṝ',
+ 'Ṟ' => 'Ṟ',
+ 'ṟ' => 'ṟ',
+ 'Ṡ' => 'Ṡ',
+ 'ṡ' => 'ṡ',
+ 'Ṣ' => 'Ṣ',
+ 'ṣ' => 'ṣ',
+ 'Ṥ' => 'Ṥ',
+ 'ṥ' => 'ṥ',
+ 'Ṧ' => 'Ṧ',
+ 'ṧ' => 'ṧ',
+ 'Ṩ' => 'Ṩ',
+ 'ṩ' => 'ṩ',
+ 'Ṫ' => 'Ṫ',
+ 'ṫ' => 'ṫ',
+ 'Ṭ' => 'Ṭ',
+ 'ṭ' => 'ṭ',
+ 'Ṯ' => 'Ṯ',
+ 'ṯ' => 'ṯ',
+ 'Ṱ' => 'Ṱ',
+ 'ṱ' => 'ṱ',
+ 'Ṳ' => 'Ṳ',
+ 'ṳ' => 'ṳ',
+ 'Ṵ' => 'Ṵ',
+ 'ṵ' => 'ṵ',
+ 'Ṷ' => 'Ṷ',
+ 'ṷ' => 'ṷ',
+ 'Ṹ' => 'Ṹ',
+ 'ṹ' => 'ṹ',
+ 'Ṻ' => 'Ṻ',
+ 'ṻ' => 'ṻ',
+ 'Ṽ' => 'Ṽ',
+ 'ṽ' => 'ṽ',
+ 'Ṿ' => 'Ṿ',
+ 'ṿ' => 'ṿ',
+ 'Ẁ' => 'Ẁ',
+ 'ẁ' => 'ẁ',
+ 'Ẃ' => 'Ẃ',
+ 'ẃ' => 'ẃ',
+ 'Ẅ' => 'Ẅ',
+ 'ẅ' => 'ẅ',
+ 'Ẇ' => 'Ẇ',
+ 'ẇ' => 'ẇ',
+ 'Ẉ' => 'Ẉ',
+ 'ẉ' => 'ẉ',
+ 'Ẋ' => 'Ẋ',
+ 'ẋ' => 'ẋ',
+ 'Ẍ' => 'Ẍ',
+ 'ẍ' => 'ẍ',
+ 'Ẏ' => 'Ẏ',
+ 'ẏ' => 'ẏ',
+ 'Ẑ' => 'Ẑ',
+ 'ẑ' => 'ẑ',
+ 'Ẓ' => 'Ẓ',
+ 'ẓ' => 'ẓ',
+ 'Ẕ' => 'Ẕ',
+ 'ẕ' => 'ẕ',
+ 'ẖ' => 'ẖ',
+ 'ẗ' => 'ẗ',
+ 'ẘ' => 'ẘ',
+ 'ẙ' => 'ẙ',
+ 'ẛ' => 'ẛ',
+ 'Ạ' => 'Ạ',
+ 'ạ' => 'ạ',
+ 'Ả' => 'Ả',
+ 'ả' => 'ả',
+ 'Ấ' => 'Ấ',
+ 'ấ' => 'ấ',
+ 'Ầ' => 'Ầ',
+ 'ầ' => 'ầ',
+ 'Ẩ' => 'Ẩ',
+ 'ẩ' => 'ẩ',
+ 'Ẫ' => 'Ẫ',
+ 'ẫ' => 'ẫ',
+ 'Ậ' => 'Ậ',
+ 'ậ' => 'ậ',
+ 'Ắ' => 'Ắ',
+ 'ắ' => 'ắ',
+ 'Ằ' => 'Ằ',
+ 'ằ' => 'ằ',
+ 'Ẳ' => 'Ẳ',
+ 'ẳ' => 'ẳ',
+ 'Ẵ' => 'Ẵ',
+ 'ẵ' => 'ẵ',
+ 'Ặ' => 'Ặ',
+ 'ặ' => 'ặ',
+ 'Ẹ' => 'Ẹ',
+ 'ẹ' => 'ẹ',
+ 'Ẻ' => 'Ẻ',
+ 'ẻ' => 'ẻ',
+ 'Ẽ' => 'Ẽ',
+ 'ẽ' => 'ẽ',
+ 'Ế' => 'Ế',
+ 'ế' => 'ế',
+ 'Ề' => 'Ề',
+ 'ề' => 'ề',
+ 'Ể' => 'Ể',
+ 'ể' => 'ể',
+ 'Ễ' => 'Ễ',
+ 'ễ' => 'ễ',
+ 'Ệ' => 'Ệ',
+ 'ệ' => 'ệ',
+ 'Ỉ' => 'Ỉ',
+ 'ỉ' => 'ỉ',
+ 'Ị' => 'Ị',
+ 'ị' => 'ị',
+ 'Ọ' => 'Ọ',
+ 'ọ' => 'ọ',
+ 'Ỏ' => 'Ỏ',
+ 'ỏ' => 'ỏ',
+ 'Ố' => 'Ố',
+ 'ố' => 'ố',
+ 'Ồ' => 'Ồ',
+ 'ồ' => 'ồ',
+ 'Ổ' => 'Ổ',
+ 'ổ' => 'ổ',
+ 'Ỗ' => 'Ỗ',
+ 'ỗ' => 'ỗ',
+ 'Ộ' => 'Ộ',
+ 'ộ' => 'ộ',
+ 'Ớ' => 'Ớ',
+ 'ớ' => 'ớ',
+ 'Ờ' => 'Ờ',
+ 'ờ' => 'ờ',
+ 'Ở' => 'Ở',
+ 'ở' => 'ở',
+ 'Ỡ' => 'Ỡ',
+ 'ỡ' => 'ỡ',
+ 'Ợ' => 'Ợ',
+ 'ợ' => 'ợ',
+ 'Ụ' => 'Ụ',
+ 'ụ' => 'ụ',
+ 'Ủ' => 'Ủ',
+ 'ủ' => 'ủ',
+ 'Ứ' => 'Ứ',
+ 'ứ' => 'ứ',
+ 'Ừ' => 'Ừ',
+ 'ừ' => 'ừ',
+ 'Ử' => 'Ử',
+ 'ử' => 'ử',
+ 'Ữ' => 'Ữ',
+ 'ữ' => 'ữ',
+ 'Ự' => 'Ự',
+ 'ự' => 'ự',
+ 'Ỳ' => 'Ỳ',
+ 'ỳ' => 'ỳ',
+ 'Ỵ' => 'Ỵ',
+ 'ỵ' => 'ỵ',
+ 'Ỷ' => 'Ỷ',
+ 'ỷ' => 'ỷ',
+ 'Ỹ' => 'Ỹ',
+ 'ỹ' => 'ỹ',
+ 'ἀ' => 'ἀ',
+ 'ἁ' => 'ἁ',
+ 'ἂ' => 'ἂ',
+ 'ἃ' => 'ἃ',
+ 'ἄ' => 'ἄ',
+ 'ἅ' => 'ἅ',
+ 'ἆ' => 'ἆ',
+ 'ἇ' => 'ἇ',
+ 'Ἀ' => 'Ἀ',
+ 'Ἁ' => 'Ἁ',
+ 'Ἂ' => 'Ἂ',
+ 'Ἃ' => 'Ἃ',
+ 'Ἄ' => 'Ἄ',
+ 'Ἅ' => 'Ἅ',
+ 'Ἆ' => 'Ἆ',
+ 'Ἇ' => 'Ἇ',
+ 'ἐ' => 'ἐ',
+ 'ἑ' => 'ἑ',
+ 'ἒ' => 'ἒ',
+ 'ἓ' => 'ἓ',
+ 'ἔ' => 'ἔ',
+ 'ἕ' => 'ἕ',
+ 'Ἐ' => 'Ἐ',
+ 'Ἑ' => 'Ἑ',
+ 'Ἒ' => 'Ἒ',
+ 'Ἓ' => 'Ἓ',
+ 'Ἔ' => 'Ἔ',
+ 'Ἕ' => 'Ἕ',
+ 'ἠ' => 'ἠ',
+ 'ἡ' => 'ἡ',
+ 'ἢ' => 'ἢ',
+ 'ἣ' => 'ἣ',
+ 'ἤ' => 'ἤ',
+ 'ἥ' => 'ἥ',
+ 'ἦ' => 'ἦ',
+ 'ἧ' => 'ἧ',
+ 'Ἠ' => 'Ἠ',
+ 'Ἡ' => 'Ἡ',
+ 'Ἢ' => 'Ἢ',
+ 'Ἣ' => 'Ἣ',
+ 'Ἤ' => 'Ἤ',
+ 'Ἥ' => 'Ἥ',
+ 'Ἦ' => 'Ἦ',
+ 'Ἧ' => 'Ἧ',
+ 'ἰ' => 'ἰ',
+ 'ἱ' => 'ἱ',
+ 'ἲ' => 'ἲ',
+ 'ἳ' => 'ἳ',
+ 'ἴ' => 'ἴ',
+ 'ἵ' => 'ἵ',
+ 'ἶ' => 'ἶ',
+ 'ἷ' => 'ἷ',
+ 'Ἰ' => 'Ἰ',
+ 'Ἱ' => 'Ἱ',
+ 'Ἲ' => 'Ἲ',
+ 'Ἳ' => 'Ἳ',
+ 'Ἴ' => 'Ἴ',
+ 'Ἵ' => 'Ἵ',
+ 'Ἶ' => 'Ἶ',
+ 'Ἷ' => 'Ἷ',
+ 'ὀ' => 'ὀ',
+ 'ὁ' => 'ὁ',
+ 'ὂ' => 'ὂ',
+ 'ὃ' => 'ὃ',
+ 'ὄ' => 'ὄ',
+ 'ὅ' => 'ὅ',
+ 'Ὀ' => 'Ὀ',
+ 'Ὁ' => 'Ὁ',
+ 'Ὂ' => 'Ὂ',
+ 'Ὃ' => 'Ὃ',
+ 'Ὄ' => 'Ὄ',
+ 'Ὅ' => 'Ὅ',
+ 'ὐ' => 'ὐ',
+ 'ὑ' => 'ὑ',
+ 'ὒ' => 'ὒ',
+ 'ὓ' => 'ὓ',
+ 'ὔ' => 'ὔ',
+ 'ὕ' => 'ὕ',
+ 'ὖ' => 'ὖ',
+ 'ὗ' => 'ὗ',
+ 'Ὑ' => 'Ὑ',
+ 'Ὓ' => 'Ὓ',
+ 'Ὕ' => 'Ὕ',
+ 'Ὗ' => 'Ὗ',
+ 'ὠ' => 'ὠ',
+ 'ὡ' => 'ὡ',
+ 'ὢ' => 'ὢ',
+ 'ὣ' => 'ὣ',
+ 'ὤ' => 'ὤ',
+ 'ὥ' => 'ὥ',
+ 'ὦ' => 'ὦ',
+ 'ὧ' => 'ὧ',
+ 'Ὠ' => 'Ὠ',
+ 'Ὡ' => 'Ὡ',
+ 'Ὢ' => 'Ὢ',
+ 'Ὣ' => 'Ὣ',
+ 'Ὤ' => 'Ὤ',
+ 'Ὥ' => 'Ὥ',
+ 'Ὦ' => 'Ὦ',
+ 'Ὧ' => 'Ὧ',
+ 'ὰ' => 'ὰ',
+ 'ὲ' => 'ὲ',
+ 'ὴ' => 'ὴ',
+ 'ὶ' => 'ὶ',
+ 'ὸ' => 'ὸ',
+ 'ὺ' => 'ὺ',
+ 'ὼ' => 'ὼ',
+ 'ᾀ' => 'ᾀ',
+ 'ᾁ' => 'ᾁ',
+ 'ᾂ' => 'ᾂ',
+ 'ᾃ' => 'ᾃ',
+ 'ᾄ' => 'ᾄ',
+ 'ᾅ' => 'ᾅ',
+ 'ᾆ' => 'ᾆ',
+ 'ᾇ' => 'ᾇ',
+ 'ᾈ' => 'ᾈ',
+ 'ᾉ' => 'ᾉ',
+ 'ᾊ' => 'ᾊ',
+ 'ᾋ' => 'ᾋ',
+ 'ᾌ' => 'ᾌ',
+ 'ᾍ' => 'ᾍ',
+ 'ᾎ' => 'ᾎ',
+ 'ᾏ' => 'ᾏ',
+ 'ᾐ' => 'ᾐ',
+ 'ᾑ' => 'ᾑ',
+ 'ᾒ' => 'ᾒ',
+ 'ᾓ' => 'ᾓ',
+ 'ᾔ' => 'ᾔ',
+ 'ᾕ' => 'ᾕ',
+ 'ᾖ' => 'ᾖ',
+ 'ᾗ' => 'ᾗ',
+ 'ᾘ' => 'ᾘ',
+ 'ᾙ' => 'ᾙ',
+ 'ᾚ' => 'ᾚ',
+ 'ᾛ' => 'ᾛ',
+ 'ᾜ' => 'ᾜ',
+ 'ᾝ' => 'ᾝ',
+ 'ᾞ' => 'ᾞ',
+ 'ᾟ' => 'ᾟ',
+ 'ᾠ' => 'ᾠ',
+ 'ᾡ' => 'ᾡ',
+ 'ᾢ' => 'ᾢ',
+ 'ᾣ' => 'ᾣ',
+ 'ᾤ' => 'ᾤ',
+ 'ᾥ' => 'ᾥ',
+ 'ᾦ' => 'ᾦ',
+ 'ᾧ' => 'ᾧ',
+ 'ᾨ' => 'ᾨ',
+ 'ᾩ' => 'ᾩ',
+ 'ᾪ' => 'ᾪ',
+ 'ᾫ' => 'ᾫ',
+ 'ᾬ' => 'ᾬ',
+ 'ᾭ' => 'ᾭ',
+ 'ᾮ' => 'ᾮ',
+ 'ᾯ' => 'ᾯ',
+ 'ᾰ' => 'ᾰ',
+ 'ᾱ' => 'ᾱ',
+ 'ᾲ' => 'ᾲ',
+ 'ᾳ' => 'ᾳ',
+ 'ᾴ' => 'ᾴ',
+ 'ᾶ' => 'ᾶ',
+ 'ᾷ' => 'ᾷ',
+ 'Ᾰ' => 'Ᾰ',
+ 'Ᾱ' => 'Ᾱ',
+ 'Ὰ' => 'Ὰ',
+ 'ᾼ' => 'ᾼ',
+ '῁' => '῁',
+ 'ῂ' => 'ῂ',
+ 'ῃ' => 'ῃ',
+ 'ῄ' => 'ῄ',
+ 'ῆ' => 'ῆ',
+ 'ῇ' => 'ῇ',
+ 'Ὲ' => 'Ὲ',
+ 'Ὴ' => 'Ὴ',
+ 'ῌ' => 'ῌ',
+ '῍' => '῍',
+ '῎' => '῎',
+ '῏' => '῏',
+ 'ῐ' => 'ῐ',
+ 'ῑ' => 'ῑ',
+ 'ῒ' => 'ῒ',
+ 'ῖ' => 'ῖ',
+ 'ῗ' => 'ῗ',
+ 'Ῐ' => 'Ῐ',
+ 'Ῑ' => 'Ῑ',
+ 'Ὶ' => 'Ὶ',
+ '῝' => '῝',
+ '῞' => '῞',
+ '῟' => '῟',
+ 'ῠ' => 'ῠ',
+ 'ῡ' => 'ῡ',
+ 'ῢ' => 'ῢ',
+ 'ῤ' => 'ῤ',
+ 'ῥ' => 'ῥ',
+ 'ῦ' => 'ῦ',
+ 'ῧ' => 'ῧ',
+ 'Ῠ' => 'Ῠ',
+ 'Ῡ' => 'Ῡ',
+ 'Ὺ' => 'Ὺ',
+ 'Ῥ' => 'Ῥ',
+ '῭' => '῭',
+ 'ῲ' => 'ῲ',
+ 'ῳ' => 'ῳ',
+ 'ῴ' => 'ῴ',
+ 'ῶ' => 'ῶ',
+ 'ῷ' => 'ῷ',
+ 'Ὸ' => 'Ὸ',
+ 'Ὼ' => 'Ὼ',
+ 'ῼ' => 'ῼ',
+ '↚' => '↚',
+ '↛' => '↛',
+ '↮' => '↮',
+ '⇍' => '⇍',
+ '⇎' => '⇎',
+ '⇏' => '⇏',
+ '∄' => '∄',
+ '∉' => '∉',
+ '∌' => '∌',
+ '∤' => '∤',
+ '∦' => '∦',
+ '≁' => '≁',
+ '≄' => '≄',
+ '≇' => '≇',
+ '≉' => '≉',
+ '≠' => '≠',
+ '≢' => '≢',
+ '≭' => '≭',
+ '≮' => '≮',
+ '≯' => '≯',
+ '≰' => '≰',
+ '≱' => '≱',
+ '≴' => '≴',
+ '≵' => '≵',
+ '≸' => '≸',
+ '≹' => '≹',
+ '⊀' => '⊀',
+ '⊁' => '⊁',
+ '⊄' => '⊄',
+ '⊅' => '⊅',
+ '⊈' => '⊈',
+ '⊉' => '⊉',
+ '⊬' => '⊬',
+ '⊭' => '⊭',
+ '⊮' => '⊮',
+ '⊯' => '⊯',
+ '⋠' => '⋠',
+ '⋡' => '⋡',
+ '⋢' => '⋢',
+ '⋣' => '⋣',
+ '⋪' => '⋪',
+ '⋫' => '⋫',
+ '⋬' => '⋬',
+ '⋭' => '⋭',
+ 'が' => 'が',
+ 'ぎ' => 'ぎ',
+ 'ぐ' => 'ぐ',
+ 'げ' => 'げ',
+ 'ご' => 'ご',
+ 'ざ' => 'ざ',
+ 'じ' => 'じ',
+ 'ず' => 'ず',
+ 'ぜ' => 'ぜ',
+ 'ぞ' => 'ぞ',
+ 'だ' => 'だ',
+ 'ぢ' => 'ぢ',
+ 'づ' => 'づ',
+ 'で' => 'で',
+ 'ど' => 'ど',
+ 'ば' => 'ば',
+ 'ぱ' => 'ぱ',
+ 'び' => 'び',
+ 'ぴ' => 'ぴ',
+ 'ぶ' => 'ぶ',
+ 'ぷ' => 'ぷ',
+ 'べ' => 'べ',
+ 'ぺ' => 'ぺ',
+ 'ぼ' => 'ぼ',
+ 'ぽ' => 'ぽ',
+ 'ゔ' => 'ゔ',
+ 'ゞ' => 'ゞ',
+ 'ガ' => 'ガ',
+ 'ギ' => 'ギ',
+ 'グ' => 'グ',
+ 'ゲ' => 'ゲ',
+ 'ゴ' => 'ゴ',
+ 'ザ' => 'ザ',
+ 'ジ' => 'ジ',
+ 'ズ' => 'ズ',
+ 'ゼ' => 'ゼ',
+ 'ゾ' => 'ゾ',
+ 'ダ' => 'ダ',
+ 'ヂ' => 'ヂ',
+ 'ヅ' => 'ヅ',
+ 'デ' => 'デ',
+ 'ド' => 'ド',
+ 'バ' => 'バ',
+ 'パ' => 'パ',
+ 'ビ' => 'ビ',
+ 'ピ' => 'ピ',
+ 'ブ' => 'ブ',
+ 'プ' => 'プ',
+ 'ベ' => 'ベ',
+ 'ペ' => 'ペ',
+ 'ボ' => 'ボ',
+ 'ポ' => 'ポ',
+ 'ヴ' => 'ヴ',
+ 'ヷ' => 'ヷ',
+ 'ヸ' => 'ヸ',
+ 'ヹ' => 'ヹ',
+ 'ヺ' => 'ヺ',
+ 'ヾ' => 'ヾ',
+ '𑂚' => '𑂚',
+ '𑂜' => '𑂜',
+ '𑂫' => '𑂫',
+ '𑄮' => '𑄮',
+ '𑄯' => '𑄯',
+ '𑍋' => '𑍋',
+ '𑍌' => '𑍌',
+ '𑒻' => '𑒻',
+ '𑒼' => '𑒼',
+ '𑒾' => '𑒾',
+ '𑖺' => '𑖺',
+ '𑖻' => '𑖻',
+ '𑤸' => '𑤸',
+);
diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php
new file mode 100755
index 0000000..5a3e8e0
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php
@@ -0,0 +1,2065 @@
+<?php
+
+return array (
+ 'À' => 'À',
+ 'Á' => 'Á',
+ 'Â' => 'Â',
+ 'Ã' => 'Ã',
+ 'Ä' => 'Ä',
+ 'Å' => 'Å',
+ 'Ç' => 'Ç',
+ 'È' => 'È',
+ 'É' => 'É',
+ 'Ê' => 'Ê',
+ 'Ë' => 'Ë',
+ 'Ì' => 'Ì',
+ 'Í' => 'Í',
+ 'Î' => 'Î',
+ 'Ï' => 'Ï',
+ 'Ñ' => 'Ñ',
+ 'Ò' => 'Ò',
+ 'Ó' => 'Ó',
+ 'Ô' => 'Ô',
+ 'Õ' => 'Õ',
+ 'Ö' => 'Ö',
+ 'Ù' => 'Ù',
+ 'Ú' => 'Ú',
+ 'Û' => 'Û',
+ 'Ü' => 'Ü',
+ 'Ý' => 'Ý',
+ 'à' => 'à',
+ 'á' => 'á',
+ 'â' => 'â',
+ 'ã' => 'ã',
+ 'ä' => 'ä',
+ 'å' => 'å',
+ 'ç' => 'ç',
+ 'è' => 'è',
+ 'é' => 'é',
+ 'ê' => 'ê',
+ 'ë' => 'ë',
+ 'ì' => 'ì',
+ 'í' => 'í',
+ 'î' => 'î',
+ 'ï' => 'ï',
+ 'ñ' => 'ñ',
+ 'ò' => 'ò',
+ 'ó' => 'ó',
+ 'ô' => 'ô',
+ 'õ' => 'õ',
+ 'ö' => 'ö',
+ 'ù' => 'ù',
+ 'ú' => 'ú',
+ 'û' => 'û',
+ 'ü' => 'ü',
+ 'ý' => 'ý',
+ 'ÿ' => 'ÿ',
+ 'Ā' => 'Ā',
+ 'ā' => 'ā',
+ 'Ă' => 'Ă',
+ 'ă' => 'ă',
+ 'Ą' => 'Ą',
+ 'ą' => 'ą',
+ 'Ć' => 'Ć',
+ 'ć' => 'ć',
+ 'Ĉ' => 'Ĉ',
+ 'ĉ' => 'ĉ',
+ 'Ċ' => 'Ċ',
+ 'ċ' => 'ċ',
+ 'Č' => 'Č',
+ 'č' => 'č',
+ 'Ď' => 'Ď',
+ 'ď' => 'ď',
+ 'Ē' => 'Ē',
+ 'ē' => 'ē',
+ 'Ĕ' => 'Ĕ',
+ 'ĕ' => 'ĕ',
+ 'Ė' => 'Ė',
+ 'ė' => 'ė',
+ 'Ę' => 'Ę',
+ 'ę' => 'ę',
+ 'Ě' => 'Ě',
+ 'ě' => 'ě',
+ 'Ĝ' => 'Ĝ',
+ 'ĝ' => 'ĝ',
+ 'Ğ' => 'Ğ',
+ 'ğ' => 'ğ',
+ 'Ġ' => 'Ġ',
+ 'ġ' => 'ġ',
+ 'Ģ' => 'Ģ',
+ 'ģ' => 'ģ',
+ 'Ĥ' => 'Ĥ',
+ 'ĥ' => 'ĥ',
+ 'Ĩ' => 'Ĩ',
+ 'ĩ' => 'ĩ',
+ 'Ī' => 'Ī',
+ 'ī' => 'ī',
+ 'Ĭ' => 'Ĭ',
+ 'ĭ' => 'ĭ',
+ 'Į' => 'Į',
+ 'į' => 'į',
+ 'İ' => 'İ',
+ 'Ĵ' => 'Ĵ',
+ 'ĵ' => 'ĵ',
+ 'Ķ' => 'Ķ',
+ 'ķ' => 'ķ',
+ 'Ĺ' => 'Ĺ',
+ 'ĺ' => 'ĺ',
+ 'Ļ' => 'Ļ',
+ 'ļ' => 'ļ',
+ 'Ľ' => 'Ľ',
+ 'ľ' => 'ľ',
+ 'Ń' => 'Ń',
+ 'ń' => 'ń',
+ 'Ņ' => 'Ņ',
+ 'ņ' => 'ņ',
+ 'Ň' => 'Ň',
+ 'ň' => 'ň',
+ 'Ō' => 'Ō',
+ 'ō' => 'ō',
+ 'Ŏ' => 'Ŏ',
+ 'ŏ' => 'ŏ',
+ 'Ő' => 'Ő',
+ 'ő' => 'ő',
+ 'Ŕ' => 'Ŕ',
+ 'ŕ' => 'ŕ',
+ 'Ŗ' => 'Ŗ',
+ 'ŗ' => 'ŗ',
+ 'Ř' => 'Ř',
+ 'ř' => 'ř',
+ 'Ś' => 'Ś',
+ 'ś' => 'ś',
+ 'Ŝ' => 'Ŝ',
+ 'ŝ' => 'ŝ',
+ 'Ş' => 'Ş',
+ 'ş' => 'ş',
+ 'Š' => 'Š',
+ 'š' => 'š',
+ 'Ţ' => 'Ţ',
+ 'ţ' => 'ţ',
+ 'Ť' => 'Ť',
+ 'ť' => 'ť',
+ 'Ũ' => 'Ũ',
+ 'ũ' => 'ũ',
+ 'Ū' => 'Ū',
+ 'ū' => 'ū',
+ 'Ŭ' => 'Ŭ',
+ 'ŭ' => 'ŭ',
+ 'Ů' => 'Ů',
+ 'ů' => 'ů',
+ 'Ű' => 'Ű',
+ 'ű' => 'ű',
+ 'Ų' => 'Ų',
+ 'ų' => 'ų',
+ 'Ŵ' => 'Ŵ',
+ 'ŵ' => 'ŵ',
+ 'Ŷ' => 'Ŷ',
+ 'ŷ' => 'ŷ',
+ 'Ÿ' => 'Ÿ',
+ 'Ź' => 'Ź',
+ 'ź' => 'ź',
+ 'Ż' => 'Ż',
+ 'ż' => 'ż',
+ 'Ž' => 'Ž',
+ 'ž' => 'ž',
+ 'Ơ' => 'Ơ',
+ 'ơ' => 'ơ',
+ 'Ư' => 'Ư',
+ 'ư' => 'ư',
+ 'Ǎ' => 'Ǎ',
+ 'ǎ' => 'ǎ',
+ 'Ǐ' => 'Ǐ',
+ 'ǐ' => 'ǐ',
+ 'Ǒ' => 'Ǒ',
+ 'ǒ' => 'ǒ',
+ 'Ǔ' => 'Ǔ',
+ 'ǔ' => 'ǔ',
+ 'Ǖ' => 'Ǖ',
+ 'ǖ' => 'ǖ',
+ 'Ǘ' => 'Ǘ',
+ 'ǘ' => 'ǘ',
+ 'Ǚ' => 'Ǚ',
+ 'ǚ' => 'ǚ',
+ 'Ǜ' => 'Ǜ',
+ 'ǜ' => 'ǜ',
+ 'Ǟ' => 'Ǟ',
+ 'ǟ' => 'ǟ',
+ 'Ǡ' => 'Ǡ',
+ 'ǡ' => 'ǡ',
+ 'Ǣ' => 'Ǣ',
+ 'ǣ' => 'ǣ',
+ 'Ǧ' => 'Ǧ',
+ 'ǧ' => 'ǧ',
+ 'Ǩ' => 'Ǩ',
+ 'ǩ' => 'ǩ',
+ 'Ǫ' => 'Ǫ',
+ 'ǫ' => 'ǫ',
+ 'Ǭ' => 'Ǭ',
+ 'ǭ' => 'ǭ',
+ 'Ǯ' => 'Ǯ',
+ 'ǯ' => 'ǯ',
+ 'ǰ' => 'ǰ',
+ 'Ǵ' => 'Ǵ',
+ 'ǵ' => 'ǵ',
+ 'Ǹ' => 'Ǹ',
+ 'ǹ' => 'ǹ',
+ 'Ǻ' => 'Ǻ',
+ 'ǻ' => 'ǻ',
+ 'Ǽ' => 'Ǽ',
+ 'ǽ' => 'ǽ',
+ 'Ǿ' => 'Ǿ',
+ 'ǿ' => 'ǿ',
+ 'Ȁ' => 'Ȁ',
+ 'ȁ' => 'ȁ',
+ 'Ȃ' => 'Ȃ',
+ 'ȃ' => 'ȃ',
+ 'Ȅ' => 'Ȅ',
+ 'ȅ' => 'ȅ',
+ 'Ȇ' => 'Ȇ',
+ 'ȇ' => 'ȇ',
+ 'Ȉ' => 'Ȉ',
+ 'ȉ' => 'ȉ',
+ 'Ȋ' => 'Ȋ',
+ 'ȋ' => 'ȋ',
+ 'Ȍ' => 'Ȍ',
+ 'ȍ' => 'ȍ',
+ 'Ȏ' => 'Ȏ',
+ 'ȏ' => 'ȏ',
+ 'Ȑ' => 'Ȑ',
+ 'ȑ' => 'ȑ',
+ 'Ȓ' => 'Ȓ',
+ 'ȓ' => 'ȓ',
+ 'Ȕ' => 'Ȕ',
+ 'ȕ' => 'ȕ',
+ 'Ȗ' => 'Ȗ',
+ 'ȗ' => 'ȗ',
+ 'Ș' => 'Ș',
+ 'ș' => 'ș',
+ 'Ț' => 'Ț',
+ 'ț' => 'ț',
+ 'Ȟ' => 'Ȟ',
+ 'ȟ' => 'ȟ',
+ 'Ȧ' => 'Ȧ',
+ 'ȧ' => 'ȧ',
+ 'Ȩ' => 'Ȩ',
+ 'ȩ' => 'ȩ',
+ 'Ȫ' => 'Ȫ',
+ 'ȫ' => 'ȫ',
+ 'Ȭ' => 'Ȭ',
+ 'ȭ' => 'ȭ',
+ 'Ȯ' => 'Ȯ',
+ 'ȯ' => 'ȯ',
+ 'Ȱ' => 'Ȱ',
+ 'ȱ' => 'ȱ',
+ 'Ȳ' => 'Ȳ',
+ 'ȳ' => 'ȳ',
+ '̀' => '̀',
+ '́' => '́',
+ '̓' => '̓',
+ '̈́' => '̈́',
+ 'ʹ' => 'ʹ',
+ ';' => ';',
+ '΅' => '΅',
+ 'Ά' => 'Ά',
+ '·' => '·',
+ 'Έ' => 'Έ',
+ 'Ή' => 'Ή',
+ 'Ί' => 'Ί',
+ 'Ό' => 'Ό',
+ 'Ύ' => 'Ύ',
+ 'Ώ' => 'Ώ',
+ 'ΐ' => 'ΐ',
+ 'Ϊ' => 'Ϊ',
+ 'Ϋ' => 'Ϋ',
+ 'ά' => 'ά',
+ 'έ' => 'έ',
+ 'ή' => 'ή',
+ 'ί' => 'ί',
+ 'ΰ' => 'ΰ',
+ 'ϊ' => 'ϊ',
+ 'ϋ' => 'ϋ',
+ 'ό' => 'ό',
+ 'ύ' => 'ύ',
+ 'ώ' => 'ώ',
+ 'ϓ' => 'ϓ',
+ 'ϔ' => 'ϔ',
+ 'Ѐ' => 'Ѐ',
+ 'Ё' => 'Ё',
+ 'Ѓ' => 'Ѓ',
+ 'Ї' => 'Ї',
+ 'Ќ' => 'Ќ',
+ 'Ѝ' => 'Ѝ',
+ 'Ў' => 'Ў',
+ 'Й' => 'Й',
+ 'й' => 'й',
+ 'ѐ' => 'ѐ',
+ 'ё' => 'ё',
+ 'ѓ' => 'ѓ',
+ 'ї' => 'ї',
+ 'ќ' => 'ќ',
+ 'ѝ' => 'ѝ',
+ 'ў' => 'ў',
+ 'Ѷ' => 'Ѷ',
+ 'ѷ' => 'ѷ',
+ 'Ӂ' => 'Ӂ',
+ 'ӂ' => 'ӂ',
+ 'Ӑ' => 'Ӑ',
+ 'ӑ' => 'ӑ',
+ 'Ӓ' => 'Ӓ',
+ 'ӓ' => 'ӓ',
+ 'Ӗ' => 'Ӗ',
+ 'ӗ' => 'ӗ',
+ 'Ӛ' => 'Ӛ',
+ 'ӛ' => 'ӛ',
+ 'Ӝ' => 'Ӝ',
+ 'ӝ' => 'ӝ',
+ 'Ӟ' => 'Ӟ',
+ 'ӟ' => 'ӟ',
+ 'Ӣ' => 'Ӣ',
+ 'ӣ' => 'ӣ',
+ 'Ӥ' => 'Ӥ',
+ 'ӥ' => 'ӥ',
+ 'Ӧ' => 'Ӧ',
+ 'ӧ' => 'ӧ',
+ 'Ӫ' => 'Ӫ',
+ 'ӫ' => 'ӫ',
+ 'Ӭ' => 'Ӭ',
+ 'ӭ' => 'ӭ',
+ 'Ӯ' => 'Ӯ',
+ 'ӯ' => 'ӯ',
+ 'Ӱ' => 'Ӱ',
+ 'ӱ' => 'ӱ',
+ 'Ӳ' => 'Ӳ',
+ 'ӳ' => 'ӳ',
+ 'Ӵ' => 'Ӵ',
+ 'ӵ' => 'ӵ',
+ 'Ӹ' => 'Ӹ',
+ 'ӹ' => 'ӹ',
+ 'آ' => 'آ',
+ 'أ' => 'أ',
+ 'ؤ' => 'ؤ',
+ 'إ' => 'إ',
+ 'ئ' => 'ئ',
+ 'ۀ' => 'ۀ',
+ 'ۂ' => 'ۂ',
+ 'ۓ' => 'ۓ',
+ 'ऩ' => 'ऩ',
+ 'ऱ' => 'ऱ',
+ 'ऴ' => 'ऴ',
+ 'क़' => 'क़',
+ 'ख़' => 'ख़',
+ 'ग़' => 'ग़',
+ 'ज़' => 'ज़',
+ 'ड़' => 'ड़',
+ 'ढ़' => 'ढ़',
+ 'फ़' => 'फ़',
+ 'य़' => 'य़',
+ 'ো' => 'ো',
+ 'ৌ' => 'ৌ',
+ 'ড়' => 'ড়',
+ 'ঢ়' => 'ঢ়',
+ 'য়' => 'য়',
+ 'ਲ਼' => 'ਲ਼',
+ 'ਸ਼' => 'ਸ਼',
+ 'ਖ਼' => 'ਖ਼',
+ 'ਗ਼' => 'ਗ਼',
+ 'ਜ਼' => 'ਜ਼',
+ 'ਫ਼' => 'ਫ਼',
+ 'ୈ' => 'ୈ',
+ 'ୋ' => 'ୋ',
+ 'ୌ' => 'ୌ',
+ 'ଡ଼' => 'ଡ଼',
+ 'ଢ଼' => 'ଢ଼',
+ 'ஔ' => 'ஔ',
+ 'ொ' => 'ொ',
+ 'ோ' => 'ோ',
+ 'ௌ' => 'ௌ',
+ 'ై' => 'ై',
+ 'ೀ' => 'ೀ',
+ 'ೇ' => 'ೇ',
+ 'ೈ' => 'ೈ',
+ 'ೊ' => 'ೊ',
+ 'ೋ' => 'ೋ',
+ 'ൊ' => 'ൊ',
+ 'ോ' => 'ോ',
+ 'ൌ' => 'ൌ',
+ 'ේ' => 'ේ',
+ 'ො' => 'ො',
+ 'ෝ' => 'ෝ',
+ 'ෞ' => 'ෞ',
+ 'གྷ' => 'གྷ',
+ 'ཌྷ' => 'ཌྷ',
+ 'དྷ' => 'དྷ',
+ 'བྷ' => 'བྷ',
+ 'ཛྷ' => 'ཛྷ',
+ 'ཀྵ' => 'ཀྵ',
+ 'ཱི' => 'ཱི',
+ 'ཱུ' => 'ཱུ',
+ 'ྲྀ' => 'ྲྀ',
+ 'ླྀ' => 'ླྀ',
+ 'ཱྀ' => 'ཱྀ',
+ 'ྒྷ' => 'ྒྷ',
+ 'ྜྷ' => 'ྜྷ',
+ 'ྡྷ' => 'ྡྷ',
+ 'ྦྷ' => 'ྦྷ',
+ 'ྫྷ' => 'ྫྷ',
+ 'ྐྵ' => 'ྐྵ',
+ 'ဦ' => 'ဦ',
+ 'ᬆ' => 'ᬆ',
+ 'ᬈ' => 'ᬈ',
+ 'ᬊ' => 'ᬊ',
+ 'ᬌ' => 'ᬌ',
+ 'ᬎ' => 'ᬎ',
+ 'ᬒ' => 'ᬒ',
+ 'ᬻ' => 'ᬻ',
+ 'ᬽ' => 'ᬽ',
+ 'ᭀ' => 'ᭀ',
+ 'ᭁ' => 'ᭁ',
+ 'ᭃ' => 'ᭃ',
+ 'Ḁ' => 'Ḁ',
+ 'ḁ' => 'ḁ',
+ 'Ḃ' => 'Ḃ',
+ 'ḃ' => 'ḃ',
+ 'Ḅ' => 'Ḅ',
+ 'ḅ' => 'ḅ',
+ 'Ḇ' => 'Ḇ',
+ 'ḇ' => 'ḇ',
+ 'Ḉ' => 'Ḉ',
+ 'ḉ' => 'ḉ',
+ 'Ḋ' => 'Ḋ',
+ 'ḋ' => 'ḋ',
+ 'Ḍ' => 'Ḍ',
+ 'ḍ' => 'ḍ',
+ 'Ḏ' => 'Ḏ',
+ 'ḏ' => 'ḏ',
+ 'Ḑ' => 'Ḑ',
+ 'ḑ' => 'ḑ',
+ 'Ḓ' => 'Ḓ',
+ 'ḓ' => 'ḓ',
+ 'Ḕ' => 'Ḕ',
+ 'ḕ' => 'ḕ',
+ 'Ḗ' => 'Ḗ',
+ 'ḗ' => 'ḗ',
+ 'Ḙ' => 'Ḙ',
+ 'ḙ' => 'ḙ',
+ 'Ḛ' => 'Ḛ',
+ 'ḛ' => 'ḛ',
+ 'Ḝ' => 'Ḝ',
+ 'ḝ' => 'ḝ',
+ 'Ḟ' => 'Ḟ',
+ 'ḟ' => 'ḟ',
+ 'Ḡ' => 'Ḡ',
+ 'ḡ' => 'ḡ',
+ 'Ḣ' => 'Ḣ',
+ 'ḣ' => 'ḣ',
+ 'Ḥ' => 'Ḥ',
+ 'ḥ' => 'ḥ',
+ 'Ḧ' => 'Ḧ',
+ 'ḧ' => 'ḧ',
+ 'Ḩ' => 'Ḩ',
+ 'ḩ' => 'ḩ',
+ 'Ḫ' => 'Ḫ',
+ 'ḫ' => 'ḫ',
+ 'Ḭ' => 'Ḭ',
+ 'ḭ' => 'ḭ',
+ 'Ḯ' => 'Ḯ',
+ 'ḯ' => 'ḯ',
+ 'Ḱ' => 'Ḱ',
+ 'ḱ' => 'ḱ',
+ 'Ḳ' => 'Ḳ',
+ 'ḳ' => 'ḳ',
+ 'Ḵ' => 'Ḵ',
+ 'ḵ' => 'ḵ',
+ 'Ḷ' => 'Ḷ',
+ 'ḷ' => 'ḷ',
+ 'Ḹ' => 'Ḹ',
+ 'ḹ' => 'ḹ',
+ 'Ḻ' => 'Ḻ',
+ 'ḻ' => 'ḻ',
+ 'Ḽ' => 'Ḽ',
+ 'ḽ' => 'ḽ',
+ 'Ḿ' => 'Ḿ',
+ 'ḿ' => 'ḿ',
+ 'Ṁ' => 'Ṁ',
+ 'ṁ' => 'ṁ',
+ 'Ṃ' => 'Ṃ',
+ 'ṃ' => 'ṃ',
+ 'Ṅ' => 'Ṅ',
+ 'ṅ' => 'ṅ',
+ 'Ṇ' => 'Ṇ',
+ 'ṇ' => 'ṇ',
+ 'Ṉ' => 'Ṉ',
+ 'ṉ' => 'ṉ',
+ 'Ṋ' => 'Ṋ',
+ 'ṋ' => 'ṋ',
+ 'Ṍ' => 'Ṍ',
+ 'ṍ' => 'ṍ',
+ 'Ṏ' => 'Ṏ',
+ 'ṏ' => 'ṏ',
+ 'Ṑ' => 'Ṑ',
+ 'ṑ' => 'ṑ',
+ 'Ṓ' => 'Ṓ',
+ 'ṓ' => 'ṓ',
+ 'Ṕ' => 'Ṕ',
+ 'ṕ' => 'ṕ',
+ 'Ṗ' => 'Ṗ',
+ 'ṗ' => 'ṗ',
+ 'Ṙ' => 'Ṙ',
+ 'ṙ' => 'ṙ',
+ 'Ṛ' => 'Ṛ',
+ 'ṛ' => 'ṛ',
+ 'Ṝ' => 'Ṝ',
+ 'ṝ' => 'ṝ',
+ 'Ṟ' => 'Ṟ',
+ 'ṟ' => 'ṟ',
+ 'Ṡ' => 'Ṡ',
+ 'ṡ' => 'ṡ',
+ 'Ṣ' => 'Ṣ',
+ 'ṣ' => 'ṣ',
+ 'Ṥ' => 'Ṥ',
+ 'ṥ' => 'ṥ',
+ 'Ṧ' => 'Ṧ',
+ 'ṧ' => 'ṧ',
+ 'Ṩ' => 'Ṩ',
+ 'ṩ' => 'ṩ',
+ 'Ṫ' => 'Ṫ',
+ 'ṫ' => 'ṫ',
+ 'Ṭ' => 'Ṭ',
+ 'ṭ' => 'ṭ',
+ 'Ṯ' => 'Ṯ',
+ 'ṯ' => 'ṯ',
+ 'Ṱ' => 'Ṱ',
+ 'ṱ' => 'ṱ',
+ 'Ṳ' => 'Ṳ',
+ 'ṳ' => 'ṳ',
+ 'Ṵ' => 'Ṵ',
+ 'ṵ' => 'ṵ',
+ 'Ṷ' => 'Ṷ',
+ 'ṷ' => 'ṷ',
+ 'Ṹ' => 'Ṹ',
+ 'ṹ' => 'ṹ',
+ 'Ṻ' => 'Ṻ',
+ 'ṻ' => 'ṻ',
+ 'Ṽ' => 'Ṽ',
+ 'ṽ' => 'ṽ',
+ 'Ṿ' => 'Ṿ',
+ 'ṿ' => 'ṿ',
+ 'Ẁ' => 'Ẁ',
+ 'ẁ' => 'ẁ',
+ 'Ẃ' => 'Ẃ',
+ 'ẃ' => 'ẃ',
+ 'Ẅ' => 'Ẅ',
+ 'ẅ' => 'ẅ',
+ 'Ẇ' => 'Ẇ',
+ 'ẇ' => 'ẇ',
+ 'Ẉ' => 'Ẉ',
+ 'ẉ' => 'ẉ',
+ 'Ẋ' => 'Ẋ',
+ 'ẋ' => 'ẋ',
+ 'Ẍ' => 'Ẍ',
+ 'ẍ' => 'ẍ',
+ 'Ẏ' => 'Ẏ',
+ 'ẏ' => 'ẏ',
+ 'Ẑ' => 'Ẑ',
+ 'ẑ' => 'ẑ',
+ 'Ẓ' => 'Ẓ',
+ 'ẓ' => 'ẓ',
+ 'Ẕ' => 'Ẕ',
+ 'ẕ' => 'ẕ',
+ 'ẖ' => 'ẖ',
+ 'ẗ' => 'ẗ',
+ 'ẘ' => 'ẘ',
+ 'ẙ' => 'ẙ',
+ 'ẛ' => 'ẛ',
+ 'Ạ' => 'Ạ',
+ 'ạ' => 'ạ',
+ 'Ả' => 'Ả',
+ 'ả' => 'ả',
+ 'Ấ' => 'Ấ',
+ 'ấ' => 'ấ',
+ 'Ầ' => 'Ầ',
+ 'ầ' => 'ầ',
+ 'Ẩ' => 'Ẩ',
+ 'ẩ' => 'ẩ',
+ 'Ẫ' => 'Ẫ',
+ 'ẫ' => 'ẫ',
+ 'Ậ' => 'Ậ',
+ 'ậ' => 'ậ',
+ 'Ắ' => 'Ắ',
+ 'ắ' => 'ắ',
+ 'Ằ' => 'Ằ',
+ 'ằ' => 'ằ',
+ 'Ẳ' => 'Ẳ',
+ 'ẳ' => 'ẳ',
+ 'Ẵ' => 'Ẵ',
+ 'ẵ' => 'ẵ',
+ 'Ặ' => 'Ặ',
+ 'ặ' => 'ặ',
+ 'Ẹ' => 'Ẹ',
+ 'ẹ' => 'ẹ',
+ 'Ẻ' => 'Ẻ',
+ 'ẻ' => 'ẻ',
+ 'Ẽ' => 'Ẽ',
+ 'ẽ' => 'ẽ',
+ 'Ế' => 'Ế',
+ 'ế' => 'ế',
+ 'Ề' => 'Ề',
+ 'ề' => 'ề',
+ 'Ể' => 'Ể',
+ 'ể' => 'ể',
+ 'Ễ' => 'Ễ',
+ 'ễ' => 'ễ',
+ 'Ệ' => 'Ệ',
+ 'ệ' => 'ệ',
+ 'Ỉ' => 'Ỉ',
+ 'ỉ' => 'ỉ',
+ 'Ị' => 'Ị',
+ 'ị' => 'ị',
+ 'Ọ' => 'Ọ',
+ 'ọ' => 'ọ',
+ 'Ỏ' => 'Ỏ',
+ 'ỏ' => 'ỏ',
+ 'Ố' => 'Ố',
+ 'ố' => 'ố',
+ 'Ồ' => 'Ồ',
+ 'ồ' => 'ồ',
+ 'Ổ' => 'Ổ',
+ 'ổ' => 'ổ',
+ 'Ỗ' => 'Ỗ',
+ 'ỗ' => 'ỗ',
+ 'Ộ' => 'Ộ',
+ 'ộ' => 'ộ',
+ 'Ớ' => 'Ớ',
+ 'ớ' => 'ớ',
+ 'Ờ' => 'Ờ',
+ 'ờ' => 'ờ',
+ 'Ở' => 'Ở',
+ 'ở' => 'ở',
+ 'Ỡ' => 'Ỡ',
+ 'ỡ' => 'ỡ',
+ 'Ợ' => 'Ợ',
+ 'ợ' => 'ợ',
+ 'Ụ' => 'Ụ',
+ 'ụ' => 'ụ',
+ 'Ủ' => 'Ủ',
+ 'ủ' => 'ủ',
+ 'Ứ' => 'Ứ',
+ 'ứ' => 'ứ',
+ 'Ừ' => 'Ừ',
+ 'ừ' => 'ừ',
+ 'Ử' => 'Ử',
+ 'ử' => 'ử',
+ 'Ữ' => 'Ữ',
+ 'ữ' => 'ữ',
+ 'Ự' => 'Ự',
+ 'ự' => 'ự',
+ 'Ỳ' => 'Ỳ',
+ 'ỳ' => 'ỳ',
+ 'Ỵ' => 'Ỵ',
+ 'ỵ' => 'ỵ',
+ 'Ỷ' => 'Ỷ',
+ 'ỷ' => 'ỷ',
+ 'Ỹ' => 'Ỹ',
+ 'ỹ' => 'ỹ',
+ 'ἀ' => 'ἀ',
+ 'ἁ' => 'ἁ',
+ 'ἂ' => 'ἂ',
+ 'ἃ' => 'ἃ',
+ 'ἄ' => 'ἄ',
+ 'ἅ' => 'ἅ',
+ 'ἆ' => 'ἆ',
+ 'ἇ' => 'ἇ',
+ 'Ἀ' => 'Ἀ',
+ 'Ἁ' => 'Ἁ',
+ 'Ἂ' => 'Ἂ',
+ 'Ἃ' => 'Ἃ',
+ 'Ἄ' => 'Ἄ',
+ 'Ἅ' => 'Ἅ',
+ 'Ἆ' => 'Ἆ',
+ 'Ἇ' => 'Ἇ',
+ 'ἐ' => 'ἐ',
+ 'ἑ' => 'ἑ',
+ 'ἒ' => 'ἒ',
+ 'ἓ' => 'ἓ',
+ 'ἔ' => 'ἔ',
+ 'ἕ' => 'ἕ',
+ 'Ἐ' => 'Ἐ',
+ 'Ἑ' => 'Ἑ',
+ 'Ἒ' => 'Ἒ',
+ 'Ἓ' => 'Ἓ',
+ 'Ἔ' => 'Ἔ',
+ 'Ἕ' => 'Ἕ',
+ 'ἠ' => 'ἠ',
+ 'ἡ' => 'ἡ',
+ 'ἢ' => 'ἢ',
+ 'ἣ' => 'ἣ',
+ 'ἤ' => 'ἤ',
+ 'ἥ' => 'ἥ',
+ 'ἦ' => 'ἦ',
+ 'ἧ' => 'ἧ',
+ 'Ἠ' => 'Ἠ',
+ 'Ἡ' => 'Ἡ',
+ 'Ἢ' => 'Ἢ',
+ 'Ἣ' => 'Ἣ',
+ 'Ἤ' => 'Ἤ',
+ 'Ἥ' => 'Ἥ',
+ 'Ἦ' => 'Ἦ',
+ 'Ἧ' => 'Ἧ',
+ 'ἰ' => 'ἰ',
+ 'ἱ' => 'ἱ',
+ 'ἲ' => 'ἲ',
+ 'ἳ' => 'ἳ',
+ 'ἴ' => 'ἴ',
+ 'ἵ' => 'ἵ',
+ 'ἶ' => 'ἶ',
+ 'ἷ' => 'ἷ',
+ 'Ἰ' => 'Ἰ',
+ 'Ἱ' => 'Ἱ',
+ 'Ἲ' => 'Ἲ',
+ 'Ἳ' => 'Ἳ',
+ 'Ἴ' => 'Ἴ',
+ 'Ἵ' => 'Ἵ',
+ 'Ἶ' => 'Ἶ',
+ 'Ἷ' => 'Ἷ',
+ 'ὀ' => 'ὀ',
+ 'ὁ' => 'ὁ',
+ 'ὂ' => 'ὂ',
+ 'ὃ' => 'ὃ',
+ 'ὄ' => 'ὄ',
+ 'ὅ' => 'ὅ',
+ 'Ὀ' => 'Ὀ',
+ 'Ὁ' => 'Ὁ',
+ 'Ὂ' => 'Ὂ',
+ 'Ὃ' => 'Ὃ',
+ 'Ὄ' => 'Ὄ',
+ 'Ὅ' => 'Ὅ',
+ 'ὐ' => 'ὐ',
+ 'ὑ' => 'ὑ',
+ 'ὒ' => 'ὒ',
+ 'ὓ' => 'ὓ',
+ 'ὔ' => 'ὔ',
+ 'ὕ' => 'ὕ',
+ 'ὖ' => 'ὖ',
+ 'ὗ' => 'ὗ',
+ 'Ὑ' => 'Ὑ',
+ 'Ὓ' => 'Ὓ',
+ 'Ὕ' => 'Ὕ',
+ 'Ὗ' => 'Ὗ',
+ 'ὠ' => 'ὠ',
+ 'ὡ' => 'ὡ',
+ 'ὢ' => 'ὢ',
+ 'ὣ' => 'ὣ',
+ 'ὤ' => 'ὤ',
+ 'ὥ' => 'ὥ',
+ 'ὦ' => 'ὦ',
+ 'ὧ' => 'ὧ',
+ 'Ὠ' => 'Ὠ',
+ 'Ὡ' => 'Ὡ',
+ 'Ὢ' => 'Ὢ',
+ 'Ὣ' => 'Ὣ',
+ 'Ὤ' => 'Ὤ',
+ 'Ὥ' => 'Ὥ',
+ 'Ὦ' => 'Ὦ',
+ 'Ὧ' => 'Ὧ',
+ 'ὰ' => 'ὰ',
+ 'ά' => 'ά',
+ 'ὲ' => 'ὲ',
+ 'έ' => 'έ',
+ 'ὴ' => 'ὴ',
+ 'ή' => 'ή',
+ 'ὶ' => 'ὶ',
+ 'ί' => 'ί',
+ 'ὸ' => 'ὸ',
+ 'ό' => 'ό',
+ 'ὺ' => 'ὺ',
+ 'ύ' => 'ύ',
+ 'ὼ' => 'ὼ',
+ 'ώ' => 'ώ',
+ 'ᾀ' => 'ᾀ',
+ 'ᾁ' => 'ᾁ',
+ 'ᾂ' => 'ᾂ',
+ 'ᾃ' => 'ᾃ',
+ 'ᾄ' => 'ᾄ',
+ 'ᾅ' => 'ᾅ',
+ 'ᾆ' => 'ᾆ',
+ 'ᾇ' => 'ᾇ',
+ 'ᾈ' => 'ᾈ',
+ 'ᾉ' => 'ᾉ',
+ 'ᾊ' => 'ᾊ',
+ 'ᾋ' => 'ᾋ',
+ 'ᾌ' => 'ᾌ',
+ 'ᾍ' => 'ᾍ',
+ 'ᾎ' => 'ᾎ',
+ 'ᾏ' => 'ᾏ',
+ 'ᾐ' => 'ᾐ',
+ 'ᾑ' => 'ᾑ',
+ 'ᾒ' => 'ᾒ',
+ 'ᾓ' => 'ᾓ',
+ 'ᾔ' => 'ᾔ',
+ 'ᾕ' => 'ᾕ',
+ 'ᾖ' => 'ᾖ',
+ 'ᾗ' => 'ᾗ',
+ 'ᾘ' => 'ᾘ',
+ 'ᾙ' => 'ᾙ',
+ 'ᾚ' => 'ᾚ',
+ 'ᾛ' => 'ᾛ',
+ 'ᾜ' => 'ᾜ',
+ 'ᾝ' => 'ᾝ',
+ 'ᾞ' => 'ᾞ',
+ 'ᾟ' => 'ᾟ',
+ 'ᾠ' => 'ᾠ',
+ 'ᾡ' => 'ᾡ',
+ 'ᾢ' => 'ᾢ',
+ 'ᾣ' => 'ᾣ',
+ 'ᾤ' => 'ᾤ',
+ 'ᾥ' => 'ᾥ',
+ 'ᾦ' => 'ᾦ',
+ 'ᾧ' => 'ᾧ',
+ 'ᾨ' => 'ᾨ',
+ 'ᾩ' => 'ᾩ',
+ 'ᾪ' => 'ᾪ',
+ 'ᾫ' => 'ᾫ',
+ 'ᾬ' => 'ᾬ',
+ 'ᾭ' => 'ᾭ',
+ 'ᾮ' => 'ᾮ',
+ 'ᾯ' => 'ᾯ',
+ 'ᾰ' => 'ᾰ',
+ 'ᾱ' => 'ᾱ',
+ 'ᾲ' => 'ᾲ',
+ 'ᾳ' => 'ᾳ',
+ 'ᾴ' => 'ᾴ',
+ 'ᾶ' => 'ᾶ',
+ 'ᾷ' => 'ᾷ',
+ 'Ᾰ' => 'Ᾰ',
+ 'Ᾱ' => 'Ᾱ',
+ 'Ὰ' => 'Ὰ',
+ 'Ά' => 'Ά',
+ 'ᾼ' => 'ᾼ',
+ 'ι' => 'ι',
+ '῁' => '῁',
+ 'ῂ' => 'ῂ',
+ 'ῃ' => 'ῃ',
+ 'ῄ' => 'ῄ',
+ 'ῆ' => 'ῆ',
+ 'ῇ' => 'ῇ',
+ 'Ὲ' => 'Ὲ',
+ 'Έ' => 'Έ',
+ 'Ὴ' => 'Ὴ',
+ 'Ή' => 'Ή',
+ 'ῌ' => 'ῌ',
+ '῍' => '῍',
+ '῎' => '῎',
+ '῏' => '῏',
+ 'ῐ' => 'ῐ',
+ 'ῑ' => 'ῑ',
+ 'ῒ' => 'ῒ',
+ 'ΐ' => 'ΐ',
+ 'ῖ' => 'ῖ',
+ 'ῗ' => 'ῗ',
+ 'Ῐ' => 'Ῐ',
+ 'Ῑ' => 'Ῑ',
+ 'Ὶ' => 'Ὶ',
+ 'Ί' => 'Ί',
+ '῝' => '῝',
+ '῞' => '῞',
+ '῟' => '῟',
+ 'ῠ' => 'ῠ',
+ 'ῡ' => 'ῡ',
+ 'ῢ' => 'ῢ',
+ 'ΰ' => 'ΰ',
+ 'ῤ' => 'ῤ',
+ 'ῥ' => 'ῥ',
+ 'ῦ' => 'ῦ',
+ 'ῧ' => 'ῧ',
+ 'Ῠ' => 'Ῠ',
+ 'Ῡ' => 'Ῡ',
+ 'Ὺ' => 'Ὺ',
+ 'Ύ' => 'Ύ',
+ 'Ῥ' => 'Ῥ',
+ '῭' => '῭',
+ '΅' => '΅',
+ '`' => '`',
+ 'ῲ' => 'ῲ',
+ 'ῳ' => 'ῳ',
+ 'ῴ' => 'ῴ',
+ 'ῶ' => 'ῶ',
+ 'ῷ' => 'ῷ',
+ 'Ὸ' => 'Ὸ',
+ 'Ό' => 'Ό',
+ 'Ὼ' => 'Ὼ',
+ 'Ώ' => 'Ώ',
+ 'ῼ' => 'ῼ',
+ '´' => '´',
+ ' ' => ' ',
+ ' ' => ' ',
+ 'Ω' => 'Ω',
+ 'K' => 'K',
+ 'Å' => 'Å',
+ '↚' => '↚',
+ '↛' => '↛',
+ '↮' => '↮',
+ '⇍' => '⇍',
+ '⇎' => '⇎',
+ '⇏' => '⇏',
+ '∄' => '∄',
+ '∉' => '∉',
+ '∌' => '∌',
+ '∤' => '∤',
+ '∦' => '∦',
+ '≁' => '≁',
+ '≄' => '≄',
+ '≇' => '≇',
+ '≉' => '≉',
+ '≠' => '≠',
+ '≢' => '≢',
+ '≭' => '≭',
+ '≮' => '≮',
+ '≯' => '≯',
+ '≰' => '≰',
+ '≱' => '≱',
+ '≴' => '≴',
+ '≵' => '≵',
+ '≸' => '≸',
+ '≹' => '≹',
+ '⊀' => '⊀',
+ '⊁' => '⊁',
+ '⊄' => '⊄',
+ '⊅' => '⊅',
+ '⊈' => '⊈',
+ '⊉' => '⊉',
+ '⊬' => '⊬',
+ '⊭' => '⊭',
+ '⊮' => '⊮',
+ '⊯' => '⊯',
+ '⋠' => '⋠',
+ '⋡' => '⋡',
+ '⋢' => '⋢',
+ '⋣' => '⋣',
+ '⋪' => '⋪',
+ '⋫' => '⋫',
+ '⋬' => '⋬',
+ '⋭' => '⋭',
+ '〈' => '〈',
+ '〉' => '〉',
+ '⫝̸' => '⫝̸',
+ 'が' => 'が',
+ 'ぎ' => 'ぎ',
+ 'ぐ' => 'ぐ',
+ 'げ' => 'げ',
+ 'ご' => 'ご',
+ 'ざ' => 'ざ',
+ 'じ' => 'じ',
+ 'ず' => 'ず',
+ 'ぜ' => 'ぜ',
+ 'ぞ' => 'ぞ',
+ 'だ' => 'だ',
+ 'ぢ' => 'ぢ',
+ 'づ' => 'づ',
+ 'で' => 'で',
+ 'ど' => 'ど',
+ 'ば' => 'ば',
+ 'ぱ' => 'ぱ',
+ 'び' => 'び',
+ 'ぴ' => 'ぴ',
+ 'ぶ' => 'ぶ',
+ 'ぷ' => 'ぷ',
+ 'べ' => 'べ',
+ 'ぺ' => 'ぺ',
+ 'ぼ' => 'ぼ',
+ 'ぽ' => 'ぽ',
+ 'ゔ' => 'ゔ',
+ 'ゞ' => 'ゞ',
+ 'ガ' => 'ガ',
+ 'ギ' => 'ギ',
+ 'グ' => 'グ',
+ 'ゲ' => 'ゲ',
+ 'ゴ' => 'ゴ',
+ 'ザ' => 'ザ',
+ 'ジ' => 'ジ',
+ 'ズ' => 'ズ',
+ 'ゼ' => 'ゼ',
+ 'ゾ' => 'ゾ',
+ 'ダ' => 'ダ',
+ 'ヂ' => 'ヂ',
+ 'ヅ' => 'ヅ',
+ 'デ' => 'デ',
+ 'ド' => 'ド',
+ 'バ' => 'バ',
+ 'パ' => 'パ',
+ 'ビ' => 'ビ',
+ 'ピ' => 'ピ',
+ 'ブ' => 'ブ',
+ 'プ' => 'プ',
+ 'ベ' => 'ベ',
+ 'ペ' => 'ペ',
+ 'ボ' => 'ボ',
+ 'ポ' => 'ポ',
+ 'ヴ' => 'ヴ',
+ 'ヷ' => 'ヷ',
+ 'ヸ' => 'ヸ',
+ 'ヹ' => 'ヹ',
+ 'ヺ' => 'ヺ',
+ 'ヾ' => 'ヾ',
+ '豈' => '豈',
+ '更' => '更',
+ '車' => '車',
+ '賈' => '賈',
+ '滑' => '滑',
+ '串' => '串',
+ '句' => '句',
+ '龜' => '龜',
+ '龜' => '龜',
+ '契' => '契',
+ '金' => '金',
+ '喇' => '喇',
+ '奈' => '奈',
+ '懶' => '懶',
+ '癩' => '癩',
+ '羅' => '羅',
+ '蘿' => '蘿',
+ '螺' => '螺',
+ '裸' => '裸',
+ '邏' => '邏',
+ '樂' => '樂',
+ '洛' => '洛',
+ '烙' => '烙',
+ '珞' => '珞',
+ '落' => '落',
+ '酪' => '酪',
+ '駱' => '駱',
+ '亂' => '亂',
+ '卵' => '卵',
+ '欄' => '欄',
+ '爛' => '爛',
+ '蘭' => '蘭',
+ '鸞' => '鸞',
+ '嵐' => '嵐',
+ '濫' => '濫',
+ '藍' => '藍',
+ '襤' => '襤',
+ '拉' => '拉',
+ '臘' => '臘',
+ '蠟' => '蠟',
+ '廊' => '廊',
+ '朗' => '朗',
+ '浪' => '浪',
+ '狼' => '狼',
+ '郎' => '郎',
+ '來' => '來',
+ '冷' => '冷',
+ '勞' => '勞',
+ '擄' => '擄',
+ '櫓' => '櫓',
+ '爐' => '爐',
+ '盧' => '盧',
+ '老' => '老',
+ '蘆' => '蘆',
+ '虜' => '虜',
+ '路' => '路',
+ '露' => '露',
+ '魯' => '魯',
+ '鷺' => '鷺',
+ '碌' => '碌',
+ '祿' => '祿',
+ '綠' => '綠',
+ '菉' => '菉',
+ '錄' => '錄',
+ '鹿' => '鹿',
+ '論' => '論',
+ '壟' => '壟',
+ '弄' => '弄',
+ '籠' => '籠',
+ '聾' => '聾',
+ '牢' => '牢',
+ '磊' => '磊',
+ '賂' => '賂',
+ '雷' => '雷',
+ '壘' => '壘',
+ '屢' => '屢',
+ '樓' => '樓',
+ '淚' => '淚',
+ '漏' => '漏',
+ '累' => '累',
+ '縷' => '縷',
+ '陋' => '陋',
+ '勒' => '勒',
+ '肋' => '肋',
+ '凜' => '凜',
+ '凌' => '凌',
+ '稜' => '稜',
+ '綾' => '綾',
+ '菱' => '菱',
+ '陵' => '陵',
+ '讀' => '讀',
+ '拏' => '拏',
+ '樂' => '樂',
+ '諾' => '諾',
+ '丹' => '丹',
+ '寧' => '寧',
+ '怒' => '怒',
+ '率' => '率',
+ '異' => '異',
+ '北' => '北',
+ '磻' => '磻',
+ '便' => '便',
+ '復' => '復',
+ '不' => '不',
+ '泌' => '泌',
+ '數' => '數',
+ '索' => '索',
+ '參' => '參',
+ '塞' => '塞',
+ '省' => '省',
+ '葉' => '葉',
+ '說' => '說',
+ '殺' => '殺',
+ '辰' => '辰',
+ '沈' => '沈',
+ '拾' => '拾',
+ '若' => '若',
+ '掠' => '掠',
+ '略' => '略',
+ '亮' => '亮',
+ '兩' => '兩',
+ '凉' => '凉',
+ '梁' => '梁',
+ '糧' => '糧',
+ '良' => '良',
+ '諒' => '諒',
+ '量' => '量',
+ '勵' => '勵',
+ '呂' => '呂',
+ '女' => '女',
+ '廬' => '廬',
+ '旅' => '旅',
+ '濾' => '濾',
+ '礪' => '礪',
+ '閭' => '閭',
+ '驪' => '驪',
+ '麗' => '麗',
+ '黎' => '黎',
+ '力' => '力',
+ '曆' => '曆',
+ '歷' => '歷',
+ '轢' => '轢',
+ '年' => '年',
+ '憐' => '憐',
+ '戀' => '戀',
+ '撚' => '撚',
+ '漣' => '漣',
+ '煉' => '煉',
+ '璉' => '璉',
+ '秊' => '秊',
+ '練' => '練',
+ '聯' => '聯',
+ '輦' => '輦',
+ '蓮' => '蓮',
+ '連' => '連',
+ '鍊' => '鍊',
+ '列' => '列',
+ '劣' => '劣',
+ '咽' => '咽',
+ '烈' => '烈',
+ '裂' => '裂',
+ '說' => '說',
+ '廉' => '廉',
+ '念' => '念',
+ '捻' => '捻',
+ '殮' => '殮',
+ '簾' => '簾',
+ '獵' => '獵',
+ '令' => '令',
+ '囹' => '囹',
+ '寧' => '寧',
+ '嶺' => '嶺',
+ '怜' => '怜',
+ '玲' => '玲',
+ '瑩' => '瑩',
+ '羚' => '羚',
+ '聆' => '聆',
+ '鈴' => '鈴',
+ '零' => '零',
+ '靈' => '靈',
+ '領' => '領',
+ '例' => '例',
+ '禮' => '禮',
+ '醴' => '醴',
+ '隸' => '隸',
+ '惡' => '惡',
+ '了' => '了',
+ '僚' => '僚',
+ '寮' => '寮',
+ '尿' => '尿',
+ '料' => '料',
+ '樂' => '樂',
+ '燎' => '燎',
+ '療' => '療',
+ '蓼' => '蓼',
+ '遼' => '遼',
+ '龍' => '龍',
+ '暈' => '暈',
+ '阮' => '阮',
+ '劉' => '劉',
+ '杻' => '杻',
+ '柳' => '柳',
+ '流' => '流',
+ '溜' => '溜',
+ '琉' => '琉',
+ '留' => '留',
+ '硫' => '硫',
+ '紐' => '紐',
+ '類' => '類',
+ '六' => '六',
+ '戮' => '戮',
+ '陸' => '陸',
+ '倫' => '倫',
+ '崙' => '崙',
+ '淪' => '淪',
+ '輪' => '輪',
+ '律' => '律',
+ '慄' => '慄',
+ '栗' => '栗',
+ '率' => '率',
+ '隆' => '隆',
+ '利' => '利',
+ '吏' => '吏',
+ '履' => '履',
+ '易' => '易',
+ '李' => '李',
+ '梨' => '梨',
+ '泥' => '泥',
+ '理' => '理',
+ '痢' => '痢',
+ '罹' => '罹',
+ '裏' => '裏',
+ '裡' => '裡',
+ '里' => '里',
+ '離' => '離',
+ '匿' => '匿',
+ '溺' => '溺',
+ '吝' => '吝',
+ '燐' => '燐',
+ '璘' => '璘',
+ '藺' => '藺',
+ '隣' => '隣',
+ '鱗' => '鱗',
+ '麟' => '麟',
+ '林' => '林',
+ '淋' => '淋',
+ '臨' => '臨',
+ '立' => '立',
+ '笠' => '笠',
+ '粒' => '粒',
+ '狀' => '狀',
+ '炙' => '炙',
+ '識' => '識',
+ '什' => '什',
+ '茶' => '茶',
+ '刺' => '刺',
+ '切' => '切',
+ '度' => '度',
+ '拓' => '拓',
+ '糖' => '糖',
+ '宅' => '宅',
+ '洞' => '洞',
+ '暴' => '暴',
+ '輻' => '輻',
+ '行' => '行',
+ '降' => '降',
+ '見' => '見',
+ '廓' => '廓',
+ '兀' => '兀',
+ '嗀' => '嗀',
+ '塚' => '塚',
+ '晴' => '晴',
+ '凞' => '凞',
+ '猪' => '猪',
+ '益' => '益',
+ '礼' => '礼',
+ '神' => '神',
+ '祥' => '祥',
+ '福' => '福',
+ '靖' => '靖',
+ '精' => '精',
+ '羽' => '羽',
+ '蘒' => '蘒',
+ '諸' => '諸',
+ '逸' => '逸',
+ '都' => '都',
+ '飯' => '飯',
+ '飼' => '飼',
+ '館' => '館',
+ '鶴' => '鶴',
+ '郞' => '郞',
+ '隷' => '隷',
+ '侮' => '侮',
+ '僧' => '僧',
+ '免' => '免',
+ '勉' => '勉',
+ '勤' => '勤',
+ '卑' => '卑',
+ '喝' => '喝',
+ '嘆' => '嘆',
+ '器' => '器',
+ '塀' => '塀',
+ '墨' => '墨',
+ '層' => '層',
+ '屮' => '屮',
+ '悔' => '悔',
+ '慨' => '慨',
+ '憎' => '憎',
+ '懲' => '懲',
+ '敏' => '敏',
+ '既' => '既',
+ '暑' => '暑',
+ '梅' => '梅',
+ '海' => '海',
+ '渚' => '渚',
+ '漢' => '漢',
+ '煮' => '煮',
+ '爫' => '爫',
+ '琢' => '琢',
+ '碑' => '碑',
+ '社' => '社',
+ '祉' => '祉',
+ '祈' => '祈',
+ '祐' => '祐',
+ '祖' => '祖',
+ '祝' => '祝',
+ '禍' => '禍',
+ '禎' => '禎',
+ '穀' => '穀',
+ '突' => '突',
+ '節' => '節',
+ '練' => '練',
+ '縉' => '縉',
+ '繁' => '繁',
+ '署' => '署',
+ '者' => '者',
+ '臭' => '臭',
+ '艹' => '艹',
+ '艹' => '艹',
+ '著' => '著',
+ '褐' => '褐',
+ '視' => '視',
+ '謁' => '謁',
+ '謹' => '謹',
+ '賓' => '賓',
+ '贈' => '贈',
+ '辶' => '辶',
+ '逸' => '逸',
+ '難' => '難',
+ '響' => '響',
+ '頻' => '頻',
+ '恵' => '恵',
+ '𤋮' => '𤋮',
+ '舘' => '舘',
+ '並' => '並',
+ '况' => '况',
+ '全' => '全',
+ '侀' => '侀',
+ '充' => '充',
+ '冀' => '冀',
+ '勇' => '勇',
+ '勺' => '勺',
+ '喝' => '喝',
+ '啕' => '啕',
+ '喙' => '喙',
+ '嗢' => '嗢',
+ '塚' => '塚',
+ '墳' => '墳',
+ '奄' => '奄',
+ '奔' => '奔',
+ '婢' => '婢',
+ '嬨' => '嬨',
+ '廒' => '廒',
+ '廙' => '廙',
+ '彩' => '彩',
+ '徭' => '徭',
+ '惘' => '惘',
+ '慎' => '慎',
+ '愈' => '愈',
+ '憎' => '憎',
+ '慠' => '慠',
+ '懲' => '懲',
+ '戴' => '戴',
+ '揄' => '揄',
+ '搜' => '搜',
+ '摒' => '摒',
+ '敖' => '敖',
+ '晴' => '晴',
+ '朗' => '朗',
+ '望' => '望',
+ '杖' => '杖',
+ '歹' => '歹',
+ '殺' => '殺',
+ '流' => '流',
+ '滛' => '滛',
+ '滋' => '滋',
+ '漢' => '漢',
+ '瀞' => '瀞',
+ '煮' => '煮',
+ '瞧' => '瞧',
+ '爵' => '爵',
+ '犯' => '犯',
+ '猪' => '猪',
+ '瑱' => '瑱',
+ '甆' => '甆',
+ '画' => '画',
+ '瘝' => '瘝',
+ '瘟' => '瘟',
+ '益' => '益',
+ '盛' => '盛',
+ '直' => '直',
+ '睊' => '睊',
+ '着' => '着',
+ '磌' => '磌',
+ '窱' => '窱',
+ '節' => '節',
+ '类' => '类',
+ '絛' => '絛',
+ '練' => '練',
+ '缾' => '缾',
+ '者' => '者',
+ '荒' => '荒',
+ '華' => '華',
+ '蝹' => '蝹',
+ '襁' => '襁',
+ '覆' => '覆',
+ '視' => '視',
+ '調' => '調',
+ '諸' => '諸',
+ '請' => '請',
+ '謁' => '謁',
+ '諾' => '諾',
+ '諭' => '諭',
+ '謹' => '謹',
+ '變' => '變',
+ '贈' => '贈',
+ '輸' => '輸',
+ '遲' => '遲',
+ '醙' => '醙',
+ '鉶' => '鉶',
+ '陼' => '陼',
+ '難' => '難',
+ '靖' => '靖',
+ '韛' => '韛',
+ '響' => '響',
+ '頋' => '頋',
+ '頻' => '頻',
+ '鬒' => '鬒',
+ '龜' => '龜',
+ '𢡊' => '𢡊',
+ '𢡄' => '𢡄',
+ '𣏕' => '𣏕',
+ '㮝' => '㮝',
+ '䀘' => '䀘',
+ '䀹' => '䀹',
+ '𥉉' => '𥉉',
+ '𥳐' => '𥳐',
+ '𧻓' => '𧻓',
+ '齃' => '齃',
+ '龎' => '龎',
+ 'יִ' => 'יִ',
+ 'ײַ' => 'ײַ',
+ 'שׁ' => 'שׁ',
+ 'שׂ' => 'שׂ',
+ 'שּׁ' => 'שּׁ',
+ 'שּׂ' => 'שּׂ',
+ 'אַ' => 'אַ',
+ 'אָ' => 'אָ',
+ 'אּ' => 'אּ',
+ 'בּ' => 'בּ',
+ 'גּ' => 'גּ',
+ 'דּ' => 'דּ',
+ 'הּ' => 'הּ',
+ 'וּ' => 'וּ',
+ 'זּ' => 'זּ',
+ 'טּ' => 'טּ',
+ 'יּ' => 'יּ',
+ 'ךּ' => 'ךּ',
+ 'כּ' => 'כּ',
+ 'לּ' => 'לּ',
+ 'מּ' => 'מּ',
+ 'נּ' => 'נּ',
+ 'סּ' => 'סּ',
+ 'ףּ' => 'ףּ',
+ 'פּ' => 'פּ',
+ 'צּ' => 'צּ',
+ 'קּ' => 'קּ',
+ 'רּ' => 'רּ',
+ 'שּ' => 'שּ',
+ 'תּ' => 'תּ',
+ 'וֹ' => 'וֹ',
+ 'בֿ' => 'בֿ',
+ 'כֿ' => 'כֿ',
+ 'פֿ' => 'פֿ',
+ '𑂚' => '𑂚',
+ '𑂜' => '𑂜',
+ '𑂫' => '𑂫',
+ '𑄮' => '𑄮',
+ '𑄯' => '𑄯',
+ '𑍋' => '𑍋',
+ '𑍌' => '𑍌',
+ '𑒻' => '𑒻',
+ '𑒼' => '𑒼',
+ '𑒾' => '𑒾',
+ '𑖺' => '𑖺',
+ '𑖻' => '𑖻',
+ '𑤸' => '𑤸',
+ '𝅗𝅥' => '𝅗𝅥',
+ '𝅘𝅥' => '𝅘𝅥',
+ '𝅘𝅥𝅮' => '𝅘𝅥𝅮',
+ '𝅘𝅥𝅯' => '𝅘𝅥𝅯',
+ '𝅘𝅥𝅰' => '𝅘𝅥𝅰',
+ '𝅘𝅥𝅱' => '𝅘𝅥𝅱',
+ '𝅘𝅥𝅲' => '𝅘𝅥𝅲',
+ '𝆹𝅥' => '𝆹𝅥',
+ '𝆺𝅥' => '𝆺𝅥',
+ '𝆹𝅥𝅮' => '𝆹𝅥𝅮',
+ '𝆺𝅥𝅮' => '𝆺𝅥𝅮',
+ '𝆹𝅥𝅯' => '𝆹𝅥𝅯',
+ '𝆺𝅥𝅯' => '𝆺𝅥𝅯',
+ '丽' => '丽',
+ '丸' => '丸',
+ '乁' => '乁',
+ '𠄢' => '𠄢',
+ '你' => '你',
+ '侮' => '侮',
+ '侻' => '侻',
+ '倂' => '倂',
+ '偺' => '偺',
+ '備' => '備',
+ '僧' => '僧',
+ '像' => '像',
+ '㒞' => '㒞',
+ '𠘺' => '𠘺',
+ '免' => '免',
+ '兔' => '兔',
+ '兤' => '兤',
+ '具' => '具',
+ '𠔜' => '𠔜',
+ '㒹' => '㒹',
+ '內' => '內',
+ '再' => '再',
+ '𠕋' => '𠕋',
+ '冗' => '冗',
+ '冤' => '冤',
+ '仌' => '仌',
+ '冬' => '冬',
+ '况' => '况',
+ '𩇟' => '𩇟',
+ '凵' => '凵',
+ '刃' => '刃',
+ '㓟' => '㓟',
+ '刻' => '刻',
+ '剆' => '剆',
+ '割' => '割',
+ '剷' => '剷',
+ '㔕' => '㔕',
+ '勇' => '勇',
+ '勉' => '勉',
+ '勤' => '勤',
+ '勺' => '勺',
+ '包' => '包',
+ '匆' => '匆',
+ '北' => '北',
+ '卉' => '卉',
+ '卑' => '卑',
+ '博' => '博',
+ '即' => '即',
+ '卽' => '卽',
+ '卿' => '卿',
+ '卿' => '卿',
+ '卿' => '卿',
+ '𠨬' => '𠨬',
+ '灰' => '灰',
+ '及' => '及',
+ '叟' => '叟',
+ '𠭣' => '𠭣',
+ '叫' => '叫',
+ '叱' => '叱',
+ '吆' => '吆',
+ '咞' => '咞',
+ '吸' => '吸',
+ '呈' => '呈',
+ '周' => '周',
+ '咢' => '咢',
+ '哶' => '哶',
+ '唐' => '唐',
+ '啓' => '啓',
+ '啣' => '啣',
+ '善' => '善',
+ '善' => '善',
+ '喙' => '喙',
+ '喫' => '喫',
+ '喳' => '喳',
+ '嗂' => '嗂',
+ '圖' => '圖',
+ '嘆' => '嘆',
+ '圗' => '圗',
+ '噑' => '噑',
+ '噴' => '噴',
+ '切' => '切',
+ '壮' => '壮',
+ '城' => '城',
+ '埴' => '埴',
+ '堍' => '堍',
+ '型' => '型',
+ '堲' => '堲',
+ '報' => '報',
+ '墬' => '墬',
+ '𡓤' => '𡓤',
+ '売' => '売',
+ '壷' => '壷',
+ '夆' => '夆',
+ '多' => '多',
+ '夢' => '夢',
+ '奢' => '奢',
+ '𡚨' => '𡚨',
+ '𡛪' => '𡛪',
+ '姬' => '姬',
+ '娛' => '娛',
+ '娧' => '娧',
+ '姘' => '姘',
+ '婦' => '婦',
+ '㛮' => '㛮',
+ '㛼' => '㛼',
+ '嬈' => '嬈',
+ '嬾' => '嬾',
+ '嬾' => '嬾',
+ '𡧈' => '𡧈',
+ '寃' => '寃',
+ '寘' => '寘',
+ '寧' => '寧',
+ '寳' => '寳',
+ '𡬘' => '𡬘',
+ '寿' => '寿',
+ '将' => '将',
+ '当' => '当',
+ '尢' => '尢',
+ '㞁' => '㞁',
+ '屠' => '屠',
+ '屮' => '屮',
+ '峀' => '峀',
+ '岍' => '岍',
+ '𡷤' => '𡷤',
+ '嵃' => '嵃',
+ '𡷦' => '𡷦',
+ '嵮' => '嵮',
+ '嵫' => '嵫',
+ '嵼' => '嵼',
+ '巡' => '巡',
+ '巢' => '巢',
+ '㠯' => '㠯',
+ '巽' => '巽',
+ '帨' => '帨',
+ '帽' => '帽',
+ '幩' => '幩',
+ '㡢' => '㡢',
+ '𢆃' => '𢆃',
+ '㡼' => '㡼',
+ '庰' => '庰',
+ '庳' => '庳',
+ '庶' => '庶',
+ '廊' => '廊',
+ '𪎒' => '𪎒',
+ '廾' => '廾',
+ '𢌱' => '𢌱',
+ '𢌱' => '𢌱',
+ '舁' => '舁',
+ '弢' => '弢',
+ '弢' => '弢',
+ '㣇' => '㣇',
+ '𣊸' => '𣊸',
+ '𦇚' => '𦇚',
+ '形' => '形',
+ '彫' => '彫',
+ '㣣' => '㣣',
+ '徚' => '徚',
+ '忍' => '忍',
+ '志' => '志',
+ '忹' => '忹',
+ '悁' => '悁',
+ '㤺' => '㤺',
+ '㤜' => '㤜',
+ '悔' => '悔',
+ '𢛔' => '𢛔',
+ '惇' => '惇',
+ '慈' => '慈',
+ '慌' => '慌',
+ '慎' => '慎',
+ '慌' => '慌',
+ '慺' => '慺',
+ '憎' => '憎',
+ '憲' => '憲',
+ '憤' => '憤',
+ '憯' => '憯',
+ '懞' => '懞',
+ '懲' => '懲',
+ '懶' => '懶',
+ '成' => '成',
+ '戛' => '戛',
+ '扝' => '扝',
+ '抱' => '抱',
+ '拔' => '拔',
+ '捐' => '捐',
+ '𢬌' => '𢬌',
+ '挽' => '挽',
+ '拼' => '拼',
+ '捨' => '捨',
+ '掃' => '掃',
+ '揤' => '揤',
+ '𢯱' => '𢯱',
+ '搢' => '搢',
+ '揅' => '揅',
+ '掩' => '掩',
+ '㨮' => '㨮',
+ '摩' => '摩',
+ '摾' => '摾',
+ '撝' => '撝',
+ '摷' => '摷',
+ '㩬' => '㩬',
+ '敏' => '敏',
+ '敬' => '敬',
+ '𣀊' => '𣀊',
+ '旣' => '旣',
+ '書' => '書',
+ '晉' => '晉',
+ '㬙' => '㬙',
+ '暑' => '暑',
+ '㬈' => '㬈',
+ '㫤' => '㫤',
+ '冒' => '冒',
+ '冕' => '冕',
+ '最' => '最',
+ '暜' => '暜',
+ '肭' => '肭',
+ '䏙' => '䏙',
+ '朗' => '朗',
+ '望' => '望',
+ '朡' => '朡',
+ '杞' => '杞',
+ '杓' => '杓',
+ '𣏃' => '𣏃',
+ '㭉' => '㭉',
+ '柺' => '柺',
+ '枅' => '枅',
+ '桒' => '桒',
+ '梅' => '梅',
+ '𣑭' => '𣑭',
+ '梎' => '梎',
+ '栟' => '栟',
+ '椔' => '椔',
+ '㮝' => '㮝',
+ '楂' => '楂',
+ '榣' => '榣',
+ '槪' => '槪',
+ '檨' => '檨',
+ '𣚣' => '𣚣',
+ '櫛' => '櫛',
+ '㰘' => '㰘',
+ '次' => '次',
+ '𣢧' => '𣢧',
+ '歔' => '歔',
+ '㱎' => '㱎',
+ '歲' => '歲',
+ '殟' => '殟',
+ '殺' => '殺',
+ '殻' => '殻',
+ '𣪍' => '𣪍',
+ '𡴋' => '𡴋',
+ '𣫺' => '𣫺',
+ '汎' => '汎',
+ '𣲼' => '𣲼',
+ '沿' => '沿',
+ '泍' => '泍',
+ '汧' => '汧',
+ '洖' => '洖',
+ '派' => '派',
+ '海' => '海',
+ '流' => '流',
+ '浩' => '浩',
+ '浸' => '浸',
+ '涅' => '涅',
+ '𣴞' => '𣴞',
+ '洴' => '洴',
+ '港' => '港',
+ '湮' => '湮',
+ '㴳' => '㴳',
+ '滋' => '滋',
+ '滇' => '滇',
+ '𣻑' => '𣻑',
+ '淹' => '淹',
+ '潮' => '潮',
+ '𣽞' => '𣽞',
+ '𣾎' => '𣾎',
+ '濆' => '濆',
+ '瀹' => '瀹',
+ '瀞' => '瀞',
+ '瀛' => '瀛',
+ '㶖' => '㶖',
+ '灊' => '灊',
+ '災' => '災',
+ '灷' => '灷',
+ '炭' => '炭',
+ '𠔥' => '𠔥',
+ '煅' => '煅',
+ '𤉣' => '𤉣',
+ '熜' => '熜',
+ '𤎫' => '𤎫',
+ '爨' => '爨',
+ '爵' => '爵',
+ '牐' => '牐',
+ '𤘈' => '𤘈',
+ '犀' => '犀',
+ '犕' => '犕',
+ '𤜵' => '𤜵',
+ '𤠔' => '𤠔',
+ '獺' => '獺',
+ '王' => '王',
+ '㺬' => '㺬',
+ '玥' => '玥',
+ '㺸' => '㺸',
+ '㺸' => '㺸',
+ '瑇' => '瑇',
+ '瑜' => '瑜',
+ '瑱' => '瑱',
+ '璅' => '璅',
+ '瓊' => '瓊',
+ '㼛' => '㼛',
+ '甤' => '甤',
+ '𤰶' => '𤰶',
+ '甾' => '甾',
+ '𤲒' => '𤲒',
+ '異' => '異',
+ '𢆟' => '𢆟',
+ '瘐' => '瘐',
+ '𤾡' => '𤾡',
+ '𤾸' => '𤾸',
+ '𥁄' => '𥁄',
+ '㿼' => '㿼',
+ '䀈' => '䀈',
+ '直' => '直',
+ '𥃳' => '𥃳',
+ '𥃲' => '𥃲',
+ '𥄙' => '𥄙',
+ '𥄳' => '𥄳',
+ '眞' => '眞',
+ '真' => '真',
+ '真' => '真',
+ '睊' => '睊',
+ '䀹' => '䀹',
+ '瞋' => '瞋',
+ '䁆' => '䁆',
+ '䂖' => '䂖',
+ '𥐝' => '𥐝',
+ '硎' => '硎',
+ '碌' => '碌',
+ '磌' => '磌',
+ '䃣' => '䃣',
+ '𥘦' => '𥘦',
+ '祖' => '祖',
+ '𥚚' => '𥚚',
+ '𥛅' => '𥛅',
+ '福' => '福',
+ '秫' => '秫',
+ '䄯' => '䄯',
+ '穀' => '穀',
+ '穊' => '穊',
+ '穏' => '穏',
+ '𥥼' => '𥥼',
+ '𥪧' => '𥪧',
+ '𥪧' => '𥪧',
+ '竮' => '竮',
+ '䈂' => '䈂',
+ '𥮫' => '𥮫',
+ '篆' => '篆',
+ '築' => '築',
+ '䈧' => '䈧',
+ '𥲀' => '𥲀',
+ '糒' => '糒',
+ '䊠' => '䊠',
+ '糨' => '糨',
+ '糣' => '糣',
+ '紀' => '紀',
+ '𥾆' => '𥾆',
+ '絣' => '絣',
+ '䌁' => '䌁',
+ '緇' => '緇',
+ '縂' => '縂',
+ '繅' => '繅',
+ '䌴' => '䌴',
+ '𦈨' => '𦈨',
+ '𦉇' => '𦉇',
+ '䍙' => '䍙',
+ '𦋙' => '𦋙',
+ '罺' => '罺',
+ '𦌾' => '𦌾',
+ '羕' => '羕',
+ '翺' => '翺',
+ '者' => '者',
+ '𦓚' => '𦓚',
+ '𦔣' => '𦔣',
+ '聠' => '聠',
+ '𦖨' => '𦖨',
+ '聰' => '聰',
+ '𣍟' => '𣍟',
+ '䏕' => '䏕',
+ '育' => '育',
+ '脃' => '脃',
+ '䐋' => '䐋',
+ '脾' => '脾',
+ '媵' => '媵',
+ '𦞧' => '𦞧',
+ '𦞵' => '𦞵',
+ '𣎓' => '𣎓',
+ '𣎜' => '𣎜',
+ '舁' => '舁',
+ '舄' => '舄',
+ '辞' => '辞',
+ '䑫' => '䑫',
+ '芑' => '芑',
+ '芋' => '芋',
+ '芝' => '芝',
+ '劳' => '劳',
+ '花' => '花',
+ '芳' => '芳',
+ '芽' => '芽',
+ '苦' => '苦',
+ '𦬼' => '𦬼',
+ '若' => '若',
+ '茝' => '茝',
+ '荣' => '荣',
+ '莭' => '莭',
+ '茣' => '茣',
+ '莽' => '莽',
+ '菧' => '菧',
+ '著' => '著',
+ '荓' => '荓',
+ '菊' => '菊',
+ '菌' => '菌',
+ '菜' => '菜',
+ '𦰶' => '𦰶',
+ '𦵫' => '𦵫',
+ '𦳕' => '𦳕',
+ '䔫' => '䔫',
+ '蓱' => '蓱',
+ '蓳' => '蓳',
+ '蔖' => '蔖',
+ '𧏊' => '𧏊',
+ '蕤' => '蕤',
+ '𦼬' => '𦼬',
+ '䕝' => '䕝',
+ '䕡' => '䕡',
+ '𦾱' => '𦾱',
+ '𧃒' => '𧃒',
+ '䕫' => '䕫',
+ '虐' => '虐',
+ '虜' => '虜',
+ '虧' => '虧',
+ '虩' => '虩',
+ '蚩' => '蚩',
+ '蚈' => '蚈',
+ '蜎' => '蜎',
+ '蛢' => '蛢',
+ '蝹' => '蝹',
+ '蜨' => '蜨',
+ '蝫' => '蝫',
+ '螆' => '螆',
+ '䗗' => '䗗',
+ '蟡' => '蟡',
+ '蠁' => '蠁',
+ '䗹' => '䗹',
+ '衠' => '衠',
+ '衣' => '衣',
+ '𧙧' => '𧙧',
+ '裗' => '裗',
+ '裞' => '裞',
+ '䘵' => '䘵',
+ '裺' => '裺',
+ '㒻' => '㒻',
+ '𧢮' => '𧢮',
+ '𧥦' => '𧥦',
+ '䚾' => '䚾',
+ '䛇' => '䛇',
+ '誠' => '誠',
+ '諭' => '諭',
+ '變' => '變',
+ '豕' => '豕',
+ '𧲨' => '𧲨',
+ '貫' => '貫',
+ '賁' => '賁',
+ '贛' => '贛',
+ '起' => '起',
+ '𧼯' => '𧼯',
+ '𠠄' => '𠠄',
+ '跋' => '跋',
+ '趼' => '趼',
+ '跰' => '跰',
+ '𠣞' => '𠣞',
+ '軔' => '軔',
+ '輸' => '輸',
+ '𨗒' => '𨗒',
+ '𨗭' => '𨗭',
+ '邔' => '邔',
+ '郱' => '郱',
+ '鄑' => '鄑',
+ '𨜮' => '𨜮',
+ '鄛' => '鄛',
+ '鈸' => '鈸',
+ '鋗' => '鋗',
+ '鋘' => '鋘',
+ '鉼' => '鉼',
+ '鏹' => '鏹',
+ '鐕' => '鐕',
+ '𨯺' => '𨯺',
+ '開' => '開',
+ '䦕' => '䦕',
+ '閷' => '閷',
+ '𨵷' => '𨵷',
+ '䧦' => '䧦',
+ '雃' => '雃',
+ '嶲' => '嶲',
+ '霣' => '霣',
+ '𩅅' => '𩅅',
+ '𩈚' => '𩈚',
+ '䩮' => '䩮',
+ '䩶' => '䩶',
+ '韠' => '韠',
+ '𩐊' => '𩐊',
+ '䪲' => '䪲',
+ '𩒖' => '𩒖',
+ '頋' => '頋',
+ '頋' => '頋',
+ '頩' => '頩',
+ '𩖶' => '𩖶',
+ '飢' => '飢',
+ '䬳' => '䬳',
+ '餩' => '餩',
+ '馧' => '馧',
+ '駂' => '駂',
+ '駾' => '駾',
+ '䯎' => '䯎',
+ '𩬰' => '𩬰',
+ '鬒' => '鬒',
+ '鱀' => '鱀',
+ '鳽' => '鳽',
+ '䳎' => '䳎',
+ '䳭' => '䳭',
+ '鵧' => '鵧',
+ '𪃎' => '𪃎',
+ '䳸' => '䳸',
+ '𪄅' => '𪄅',
+ '𪈎' => '𪈎',
+ '𪊑' => '𪊑',
+ '麻' => '麻',
+ '䵖' => '䵖',
+ '黹' => '黹',
+ '黾' => '黾',
+ '鼅' => '鼅',
+ '鼏' => '鼏',
+ '鼖' => '鼖',
+ '鼻' => '鼻',
+ '𪘀' => '𪘀',
+);
diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php
new file mode 100755
index 0000000..ec90f36
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php
@@ -0,0 +1,876 @@
+<?php
+
+return array (
+ '̀' => 230,
+ '́' => 230,
+ '̂' => 230,
+ '̃' => 230,
+ '̄' => 230,
+ '̅' => 230,
+ '̆' => 230,
+ '̇' => 230,
+ '̈' => 230,
+ '̉' => 230,
+ '̊' => 230,
+ '̋' => 230,
+ '̌' => 230,
+ '̍' => 230,
+ '̎' => 230,
+ '̏' => 230,
+ '̐' => 230,
+ '̑' => 230,
+ '̒' => 230,
+ '̓' => 230,
+ '̔' => 230,
+ '̕' => 232,
+ '̖' => 220,
+ '̗' => 220,
+ '̘' => 220,
+ '̙' => 220,
+ '̚' => 232,
+ '̛' => 216,
+ '̜' => 220,
+ '̝' => 220,
+ '̞' => 220,
+ '̟' => 220,
+ '̠' => 220,
+ '̡' => 202,
+ '̢' => 202,
+ '̣' => 220,
+ '̤' => 220,
+ '̥' => 220,
+ '̦' => 220,
+ '̧' => 202,
+ '̨' => 202,
+ '̩' => 220,
+ '̪' => 220,
+ '̫' => 220,
+ '̬' => 220,
+ '̭' => 220,
+ '̮' => 220,
+ '̯' => 220,
+ '̰' => 220,
+ '̱' => 220,
+ '̲' => 220,
+ '̳' => 220,
+ '̴' => 1,
+ '̵' => 1,
+ '̶' => 1,
+ '̷' => 1,
+ '̸' => 1,
+ '̹' => 220,
+ '̺' => 220,
+ '̻' => 220,
+ '̼' => 220,
+ '̽' => 230,
+ '̾' => 230,
+ '̿' => 230,
+ '̀' => 230,
+ '́' => 230,
+ '͂' => 230,
+ '̓' => 230,
+ '̈́' => 230,
+ 'ͅ' => 240,
+ '͆' => 230,
+ '͇' => 220,
+ '͈' => 220,
+ '͉' => 220,
+ '͊' => 230,
+ '͋' => 230,
+ '͌' => 230,
+ '͍' => 220,
+ '͎' => 220,
+ '͐' => 230,
+ '͑' => 230,
+ '͒' => 230,
+ '͓' => 220,
+ '͔' => 220,
+ '͕' => 220,
+ '͖' => 220,
+ '͗' => 230,
+ '͘' => 232,
+ '͙' => 220,
+ '͚' => 220,
+ '͛' => 230,
+ '͜' => 233,
+ '͝' => 234,
+ '͞' => 234,
+ '͟' => 233,
+ '͠' => 234,
+ '͡' => 234,
+ '͢' => 233,
+ 'ͣ' => 230,
+ 'ͤ' => 230,
+ 'ͥ' => 230,
+ 'ͦ' => 230,
+ 'ͧ' => 230,
+ 'ͨ' => 230,
+ 'ͩ' => 230,
+ 'ͪ' => 230,
+ 'ͫ' => 230,
+ 'ͬ' => 230,
+ 'ͭ' => 230,
+ 'ͮ' => 230,
+ 'ͯ' => 230,
+ '҃' => 230,
+ '҄' => 230,
+ '҅' => 230,
+ '҆' => 230,
+ '҇' => 230,
+ '֑' => 220,
+ '֒' => 230,
+ '֓' => 230,
+ '֔' => 230,
+ '֕' => 230,
+ '֖' => 220,
+ '֗' => 230,
+ '֘' => 230,
+ '֙' => 230,
+ '֚' => 222,
+ '֛' => 220,
+ '֜' => 230,
+ '֝' => 230,
+ '֞' => 230,
+ '֟' => 230,
+ '֠' => 230,
+ '֡' => 230,
+ '֢' => 220,
+ '֣' => 220,
+ '֤' => 220,
+ '֥' => 220,
+ '֦' => 220,
+ '֧' => 220,
+ '֨' => 230,
+ '֩' => 230,
+ '֪' => 220,
+ '֫' => 230,
+ '֬' => 230,
+ '֭' => 222,
+ '֮' => 228,
+ '֯' => 230,
+ 'ְ' => 10,
+ 'ֱ' => 11,
+ 'ֲ' => 12,
+ 'ֳ' => 13,
+ 'ִ' => 14,
+ 'ֵ' => 15,
+ 'ֶ' => 16,
+ 'ַ' => 17,
+ 'ָ' => 18,
+ 'ֹ' => 19,
+ 'ֺ' => 19,
+ 'ֻ' => 20,
+ 'ּ' => 21,
+ 'ֽ' => 22,
+ 'ֿ' => 23,
+ 'ׁ' => 24,
+ 'ׂ' => 25,
+ 'ׄ' => 230,
+ 'ׅ' => 220,
+ 'ׇ' => 18,
+ 'ؐ' => 230,
+ 'ؑ' => 230,
+ 'ؒ' => 230,
+ 'ؓ' => 230,
+ 'ؔ' => 230,
+ 'ؕ' => 230,
+ 'ؖ' => 230,
+ 'ؗ' => 230,
+ 'ؘ' => 30,
+ 'ؙ' => 31,
+ 'ؚ' => 32,
+ 'ً' => 27,
+ 'ٌ' => 28,
+ 'ٍ' => 29,
+ 'َ' => 30,
+ 'ُ' => 31,
+ 'ِ' => 32,
+ 'ّ' => 33,
+ 'ْ' => 34,
+ 'ٓ' => 230,
+ 'ٔ' => 230,
+ 'ٕ' => 220,
+ 'ٖ' => 220,
+ 'ٗ' => 230,
+ '٘' => 230,
+ 'ٙ' => 230,
+ 'ٚ' => 230,
+ 'ٛ' => 230,
+ 'ٜ' => 220,
+ 'ٝ' => 230,
+ 'ٞ' => 230,
+ 'ٟ' => 220,
+ 'ٰ' => 35,
+ 'ۖ' => 230,
+ 'ۗ' => 230,
+ 'ۘ' => 230,
+ 'ۙ' => 230,
+ 'ۚ' => 230,
+ 'ۛ' => 230,
+ 'ۜ' => 230,
+ '۟' => 230,
+ '۠' => 230,
+ 'ۡ' => 230,
+ 'ۢ' => 230,
+ 'ۣ' => 220,
+ 'ۤ' => 230,
+ 'ۧ' => 230,
+ 'ۨ' => 230,
+ '۪' => 220,
+ '۫' => 230,
+ '۬' => 230,
+ 'ۭ' => 220,
+ 'ܑ' => 36,
+ 'ܰ' => 230,
+ 'ܱ' => 220,
+ 'ܲ' => 230,
+ 'ܳ' => 230,
+ 'ܴ' => 220,
+ 'ܵ' => 230,
+ 'ܶ' => 230,
+ 'ܷ' => 220,
+ 'ܸ' => 220,
+ 'ܹ' => 220,
+ 'ܺ' => 230,
+ 'ܻ' => 220,
+ 'ܼ' => 220,
+ 'ܽ' => 230,
+ 'ܾ' => 220,
+ 'ܿ' => 230,
+ '݀' => 230,
+ '݁' => 230,
+ '݂' => 220,
+ '݃' => 230,
+ '݄' => 220,
+ '݅' => 230,
+ '݆' => 220,
+ '݇' => 230,
+ '݈' => 220,
+ '݉' => 230,
+ '݊' => 230,
+ '߫' => 230,
+ '߬' => 230,
+ '߭' => 230,
+ '߮' => 230,
+ '߯' => 230,
+ '߰' => 230,
+ '߱' => 230,
+ '߲' => 220,
+ '߳' => 230,
+ '߽' => 220,
+ 'ࠖ' => 230,
+ 'ࠗ' => 230,
+ '࠘' => 230,
+ '࠙' => 230,
+ 'ࠛ' => 230,
+ 'ࠜ' => 230,
+ 'ࠝ' => 230,
+ 'ࠞ' => 230,
+ 'ࠟ' => 230,
+ 'ࠠ' => 230,
+ 'ࠡ' => 230,
+ 'ࠢ' => 230,
+ 'ࠣ' => 230,
+ 'ࠥ' => 230,
+ 'ࠦ' => 230,
+ 'ࠧ' => 230,
+ 'ࠩ' => 230,
+ 'ࠪ' => 230,
+ 'ࠫ' => 230,
+ 'ࠬ' => 230,
+ '࠭' => 230,
+ '࡙' => 220,
+ '࡚' => 220,
+ '࡛' => 220,
+ '࣓' => 220,
+ 'ࣔ' => 230,
+ 'ࣕ' => 230,
+ 'ࣖ' => 230,
+ 'ࣗ' => 230,
+ 'ࣘ' => 230,
+ 'ࣙ' => 230,
+ 'ࣚ' => 230,
+ 'ࣛ' => 230,
+ 'ࣜ' => 230,
+ 'ࣝ' => 230,
+ 'ࣞ' => 230,
+ 'ࣟ' => 230,
+ '࣠' => 230,
+ '࣡' => 230,
+ 'ࣣ' => 220,
+ 'ࣤ' => 230,
+ 'ࣥ' => 230,
+ 'ࣦ' => 220,
+ 'ࣧ' => 230,
+ 'ࣨ' => 230,
+ 'ࣩ' => 220,
+ '࣪' => 230,
+ '࣫' => 230,
+ '࣬' => 230,
+ '࣭' => 220,
+ '࣮' => 220,
+ '࣯' => 220,
+ 'ࣰ' => 27,
+ 'ࣱ' => 28,
+ 'ࣲ' => 29,
+ 'ࣳ' => 230,
+ 'ࣴ' => 230,
+ 'ࣵ' => 230,
+ 'ࣶ' => 220,
+ 'ࣷ' => 230,
+ 'ࣸ' => 230,
+ 'ࣹ' => 220,
+ 'ࣺ' => 220,
+ 'ࣻ' => 230,
+ 'ࣼ' => 230,
+ 'ࣽ' => 230,
+ 'ࣾ' => 230,
+ 'ࣿ' => 230,
+ '़' => 7,
+ '्' => 9,
+ '॑' => 230,
+ '॒' => 220,
+ '॓' => 230,
+ '॔' => 230,
+ '়' => 7,
+ '্' => 9,
+ '৾' => 230,
+ '਼' => 7,
+ '੍' => 9,
+ '઼' => 7,
+ '્' => 9,
+ '଼' => 7,
+ '୍' => 9,
+ '்' => 9,
+ '్' => 9,
+ 'ౕ' => 84,
+ 'ౖ' => 91,
+ '಼' => 7,
+ '್' => 9,
+ '഻' => 9,
+ '഼' => 9,
+ '്' => 9,
+ '්' => 9,
+ 'ุ' => 103,
+ 'ู' => 103,
+ 'ฺ' => 9,
+ '่' => 107,
+ '้' => 107,
+ '๊' => 107,
+ '๋' => 107,
+ 'ຸ' => 118,
+ 'ູ' => 118,
+ '຺' => 9,
+ '່' => 122,
+ '້' => 122,
+ '໊' => 122,
+ '໋' => 122,
+ '༘' => 220,
+ '༙' => 220,
+ '༵' => 220,
+ '༷' => 220,
+ '༹' => 216,
+ 'ཱ' => 129,
+ 'ི' => 130,
+ 'ུ' => 132,
+ 'ེ' => 130,
+ 'ཻ' => 130,
+ 'ོ' => 130,
+ 'ཽ' => 130,
+ 'ྀ' => 130,
+ 'ྂ' => 230,
+ 'ྃ' => 230,
+ '྄' => 9,
+ '྆' => 230,
+ '྇' => 230,
+ '࿆' => 220,
+ '့' => 7,
+ '္' => 9,
+ '်' => 9,
+ 'ႍ' => 220,
+ '፝' => 230,
+ '፞' => 230,
+ '፟' => 230,
+ '᜔' => 9,
+ '᜴' => 9,
+ '្' => 9,
+ '៝' => 230,
+ 'ᢩ' => 228,
+ '᤹' => 222,
+ '᤺' => 230,
+ '᤻' => 220,
+ 'ᨗ' => 230,
+ 'ᨘ' => 220,
+ '᩠' => 9,
+ '᩵' => 230,
+ '᩶' => 230,
+ '᩷' => 230,
+ '᩸' => 230,
+ '᩹' => 230,
+ '᩺' => 230,
+ '᩻' => 230,
+ '᩼' => 230,
+ '᩿' => 220,
+ '᪰' => 230,
+ '᪱' => 230,
+ '᪲' => 230,
+ '᪳' => 230,
+ '᪴' => 230,
+ '᪵' => 220,
+ '᪶' => 220,
+ '᪷' => 220,
+ '᪸' => 220,
+ '᪹' => 220,
+ '᪺' => 220,
+ '᪻' => 230,
+ '᪼' => 230,
+ '᪽' => 220,
+ 'ᪿ' => 220,
+ 'ᫀ' => 220,
+ '᬴' => 7,
+ '᭄' => 9,
+ '᭫' => 230,
+ '᭬' => 220,
+ '᭭' => 230,
+ '᭮' => 230,
+ '᭯' => 230,
+ '᭰' => 230,
+ '᭱' => 230,
+ '᭲' => 230,
+ '᭳' => 230,
+ '᮪' => 9,
+ '᮫' => 9,
+ '᯦' => 7,
+ '᯲' => 9,
+ '᯳' => 9,
+ '᰷' => 7,
+ '᳐' => 230,
+ '᳑' => 230,
+ '᳒' => 230,
+ '᳔' => 1,
+ '᳕' => 220,
+ '᳖' => 220,
+ '᳗' => 220,
+ '᳘' => 220,
+ '᳙' => 220,
+ '᳚' => 230,
+ '᳛' => 230,
+ '᳜' => 220,
+ '᳝' => 220,
+ '᳞' => 220,
+ '᳟' => 220,
+ '᳠' => 230,
+ '᳢' => 1,
+ '᳣' => 1,
+ '᳤' => 1,
+ '᳥' => 1,
+ '᳦' => 1,
+ '᳧' => 1,
+ '᳨' => 1,
+ '᳭' => 220,
+ '᳴' => 230,
+ '᳸' => 230,
+ '᳹' => 230,
+ '᷀' => 230,
+ '᷁' => 230,
+ '᷂' => 220,
+ '᷃' => 230,
+ '᷄' => 230,
+ '᷅' => 230,
+ '᷆' => 230,
+ '᷇' => 230,
+ '᷈' => 230,
+ '᷉' => 230,
+ '᷊' => 220,
+ '᷋' => 230,
+ '᷌' => 230,
+ '᷍' => 234,
+ '᷎' => 214,
+ '᷏' => 220,
+ '᷐' => 202,
+ '᷑' => 230,
+ '᷒' => 230,
+ 'ᷓ' => 230,
+ 'ᷔ' => 230,
+ 'ᷕ' => 230,
+ 'ᷖ' => 230,
+ 'ᷗ' => 230,
+ 'ᷘ' => 230,
+ 'ᷙ' => 230,
+ 'ᷚ' => 230,
+ 'ᷛ' => 230,
+ 'ᷜ' => 230,
+ 'ᷝ' => 230,
+ 'ᷞ' => 230,
+ 'ᷟ' => 230,
+ 'ᷠ' => 230,
+ 'ᷡ' => 230,
+ 'ᷢ' => 230,
+ 'ᷣ' => 230,
+ 'ᷤ' => 230,
+ 'ᷥ' => 230,
+ 'ᷦ' => 230,
+ 'ᷧ' => 230,
+ 'ᷨ' => 230,
+ 'ᷩ' => 230,
+ 'ᷪ' => 230,
+ 'ᷫ' => 230,
+ 'ᷬ' => 230,
+ 'ᷭ' => 230,
+ 'ᷮ' => 230,
+ 'ᷯ' => 230,
+ 'ᷰ' => 230,
+ 'ᷱ' => 230,
+ 'ᷲ' => 230,
+ 'ᷳ' => 230,
+ 'ᷴ' => 230,
+ '᷵' => 230,
+ '᷶' => 232,
+ '᷷' => 228,
+ '᷸' => 228,
+ '᷹' => 220,
+ '᷻' => 230,
+ '᷼' => 233,
+ '᷽' => 220,
+ '᷾' => 230,
+ '᷿' => 220,
+ '⃐' => 230,
+ '⃑' => 230,
+ '⃒' => 1,
+ '⃓' => 1,
+ '⃔' => 230,
+ '⃕' => 230,
+ '⃖' => 230,
+ '⃗' => 230,
+ '⃘' => 1,
+ '⃙' => 1,
+ '⃚' => 1,
+ '⃛' => 230,
+ '⃜' => 230,
+ '⃡' => 230,
+ '⃥' => 1,
+ '⃦' => 1,
+ '⃧' => 230,
+ '⃨' => 220,
+ '⃩' => 230,
+ '⃪' => 1,
+ '⃫' => 1,
+ '⃬' => 220,
+ '⃭' => 220,
+ '⃮' => 220,
+ '⃯' => 220,
+ '⃰' => 230,
+ '⳯' => 230,
+ '⳰' => 230,
+ '⳱' => 230,
+ '⵿' => 9,
+ 'ⷠ' => 230,
+ 'ⷡ' => 230,
+ 'ⷢ' => 230,
+ 'ⷣ' => 230,
+ 'ⷤ' => 230,
+ 'ⷥ' => 230,
+ 'ⷦ' => 230,
+ 'ⷧ' => 230,
+ 'ⷨ' => 230,
+ 'ⷩ' => 230,
+ 'ⷪ' => 230,
+ 'ⷫ' => 230,
+ 'ⷬ' => 230,
+ 'ⷭ' => 230,
+ 'ⷮ' => 230,
+ 'ⷯ' => 230,
+ 'ⷰ' => 230,
+ 'ⷱ' => 230,
+ 'ⷲ' => 230,
+ 'ⷳ' => 230,
+ 'ⷴ' => 230,
+ 'ⷵ' => 230,
+ 'ⷶ' => 230,
+ 'ⷷ' => 230,
+ 'ⷸ' => 230,
+ 'ⷹ' => 230,
+ 'ⷺ' => 230,
+ 'ⷻ' => 230,
+ 'ⷼ' => 230,
+ 'ⷽ' => 230,
+ 'ⷾ' => 230,
+ 'ⷿ' => 230,
+ '〪' => 218,
+ '〫' => 228,
+ '〬' => 232,
+ '〭' => 222,
+ '〮' => 224,
+ '〯' => 224,
+ '゙' => 8,
+ '゚' => 8,
+ '꙯' => 230,
+ 'ꙴ' => 230,
+ 'ꙵ' => 230,
+ 'ꙶ' => 230,
+ 'ꙷ' => 230,
+ 'ꙸ' => 230,
+ 'ꙹ' => 230,
+ 'ꙺ' => 230,
+ 'ꙻ' => 230,
+ '꙼' => 230,
+ '꙽' => 230,
+ 'ꚞ' => 230,
+ 'ꚟ' => 230,
+ '꛰' => 230,
+ '꛱' => 230,
+ '꠆' => 9,
+ '꠬' => 9,
+ '꣄' => 9,
+ '꣠' => 230,
+ '꣡' => 230,
+ '꣢' => 230,
+ '꣣' => 230,
+ '꣤' => 230,
+ '꣥' => 230,
+ '꣦' => 230,
+ '꣧' => 230,
+ '꣨' => 230,
+ '꣩' => 230,
+ '꣪' => 230,
+ '꣫' => 230,
+ '꣬' => 230,
+ '꣭' => 230,
+ '꣮' => 230,
+ '꣯' => 230,
+ '꣰' => 230,
+ '꣱' => 230,
+ '꤫' => 220,
+ '꤬' => 220,
+ '꤭' => 220,
+ '꥓' => 9,
+ '꦳' => 7,
+ '꧀' => 9,
+ 'ꪰ' => 230,
+ 'ꪲ' => 230,
+ 'ꪳ' => 230,
+ 'ꪴ' => 220,
+ 'ꪷ' => 230,
+ 'ꪸ' => 230,
+ 'ꪾ' => 230,
+ '꪿' => 230,
+ '꫁' => 230,
+ '꫶' => 9,
+ '꯭' => 9,
+ 'ﬞ' => 26,
+ '︠' => 230,
+ '︡' => 230,
+ '︢' => 230,
+ '︣' => 230,
+ '︤' => 230,
+ '︥' => 230,
+ '︦' => 230,
+ '︧' => 220,
+ '︨' => 220,
+ '︩' => 220,
+ '︪' => 220,
+ '︫' => 220,
+ '︬' => 220,
+ '︭' => 220,
+ '︮' => 230,
+ '︯' => 230,
+ '𐇽' => 220,
+ '𐋠' => 220,
+ '𐍶' => 230,
+ '𐍷' => 230,
+ '𐍸' => 230,
+ '𐍹' => 230,
+ '𐍺' => 230,
+ '𐨍' => 220,
+ '𐨏' => 230,
+ '𐨸' => 230,
+ '𐨹' => 1,
+ '𐨺' => 220,
+ '𐨿' => 9,
+ '𐫥' => 230,
+ '𐫦' => 220,
+ '𐴤' => 230,
+ '𐴥' => 230,
+ '𐴦' => 230,
+ '𐴧' => 230,
+ '𐺫' => 230,
+ '𐺬' => 230,
+ '𐽆' => 220,
+ '𐽇' => 220,
+ '𐽈' => 230,
+ '𐽉' => 230,
+ '𐽊' => 230,
+ '𐽋' => 220,
+ '𐽌' => 230,
+ '𐽍' => 220,
+ '𐽎' => 220,
+ '𐽏' => 220,
+ '𐽐' => 220,
+ '𑁆' => 9,
+ '𑁿' => 9,
+ '𑂹' => 9,
+ '𑂺' => 7,
+ '𑄀' => 230,
+ '𑄁' => 230,
+ '𑄂' => 230,
+ '𑄳' => 9,
+ '𑄴' => 9,
+ '𑅳' => 7,
+ '𑇀' => 9,
+ '𑇊' => 7,
+ '𑈵' => 9,
+ '𑈶' => 7,
+ '𑋩' => 7,
+ '𑋪' => 9,
+ '𑌻' => 7,
+ '𑌼' => 7,
+ '𑍍' => 9,
+ '𑍦' => 230,
+ '𑍧' => 230,
+ '𑍨' => 230,
+ '𑍩' => 230,
+ '𑍪' => 230,
+ '𑍫' => 230,
+ '𑍬' => 230,
+ '𑍰' => 230,
+ '𑍱' => 230,
+ '𑍲' => 230,
+ '𑍳' => 230,
+ '𑍴' => 230,
+ '𑑂' => 9,
+ '𑑆' => 7,
+ '𑑞' => 230,
+ '𑓂' => 9,
+ '𑓃' => 7,
+ '𑖿' => 9,
+ '𑗀' => 7,
+ '𑘿' => 9,
+ '𑚶' => 9,
+ '𑚷' => 7,
+ '𑜫' => 9,
+ '𑠹' => 9,
+ '𑠺' => 7,
+ '𑤽' => 9,
+ '𑤾' => 9,
+ '𑥃' => 7,
+ '𑧠' => 9,
+ '𑨴' => 9,
+ '𑩇' => 9,
+ '𑪙' => 9,
+ '𑰿' => 9,
+ '𑵂' => 7,
+ '𑵄' => 9,
+ '𑵅' => 9,
+ '𑶗' => 9,
+ '𖫰' => 1,
+ '𖫱' => 1,
+ '𖫲' => 1,
+ '𖫳' => 1,
+ '𖫴' => 1,
+ '𖬰' => 230,
+ '𖬱' => 230,
+ '𖬲' => 230,
+ '𖬳' => 230,
+ '𖬴' => 230,
+ '𖬵' => 230,
+ '𖬶' => 230,
+ '𖿰' => 6,
+ '𖿱' => 6,
+ '𛲞' => 1,
+ '𝅥' => 216,
+ '𝅦' => 216,
+ '𝅧' => 1,
+ '𝅨' => 1,
+ '𝅩' => 1,
+ '𝅭' => 226,
+ '𝅮' => 216,
+ '𝅯' => 216,
+ '𝅰' => 216,
+ '𝅱' => 216,
+ '𝅲' => 216,
+ '𝅻' => 220,
+ '𝅼' => 220,
+ '𝅽' => 220,
+ '𝅾' => 220,
+ '𝅿' => 220,
+ '𝆀' => 220,
+ '𝆁' => 220,
+ '𝆂' => 220,
+ '𝆅' => 230,
+ '𝆆' => 230,
+ '𝆇' => 230,
+ '𝆈' => 230,
+ '𝆉' => 230,
+ '𝆊' => 220,
+ '𝆋' => 220,
+ '𝆪' => 230,
+ '𝆫' => 230,
+ '𝆬' => 230,
+ '𝆭' => 230,
+ '𝉂' => 230,
+ '𝉃' => 230,
+ '𝉄' => 230,
+ '𞀀' => 230,
+ '𞀁' => 230,
+ '𞀂' => 230,
+ '𞀃' => 230,
+ '𞀄' => 230,
+ '𞀅' => 230,
+ '𞀆' => 230,
+ '𞀈' => 230,
+ '𞀉' => 230,
+ '𞀊' => 230,
+ '𞀋' => 230,
+ '𞀌' => 230,
+ '𞀍' => 230,
+ '𞀎' => 230,
+ '𞀏' => 230,
+ '𞀐' => 230,
+ '𞀑' => 230,
+ '𞀒' => 230,
+ '𞀓' => 230,
+ '𞀔' => 230,
+ '𞀕' => 230,
+ '𞀖' => 230,
+ '𞀗' => 230,
+ '𞀘' => 230,
+ '𞀛' => 230,
+ '𞀜' => 230,
+ '𞀝' => 230,
+ '𞀞' => 230,
+ '𞀟' => 230,
+ '𞀠' => 230,
+ '𞀡' => 230,
+ '𞀣' => 230,
+ '𞀤' => 230,
+ '𞀦' => 230,
+ '𞀧' => 230,
+ '𞀨' => 230,
+ '𞀩' => 230,
+ '𞀪' => 230,
+ '𞄰' => 230,
+ '𞄱' => 230,
+ '𞄲' => 230,
+ '𞄳' => 230,
+ '𞄴' => 230,
+ '𞄵' => 230,
+ '𞄶' => 230,
+ '𞋬' => 230,
+ '𞋭' => 230,
+ '𞋮' => 230,
+ '𞋯' => 230,
+ '𞣐' => 220,
+ '𞣑' => 220,
+ '𞣒' => 220,
+ '𞣓' => 220,
+ '𞣔' => 220,
+ '𞣕' => 220,
+ '𞣖' => 220,
+ '𞥄' => 230,
+ '𞥅' => 230,
+ '𞥆' => 230,
+ '𞥇' => 230,
+ '𞥈' => 230,
+ '𞥉' => 230,
+ '𞥊' => 7,
+);
diff --git a/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php
new file mode 100755
index 0000000..1574902
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php
@@ -0,0 +1,3695 @@
+<?php
+
+return array (
+ ' ' => ' ',
+ '¨' => ' ̈',
+ 'ª' => 'a',
+ '¯' => ' ̄',
+ '²' => '2',
+ '³' => '3',
+ '´' => ' ́',
+ 'µ' => 'μ',
+ '¸' => ' ̧',
+ '¹' => '1',
+ 'º' => 'o',
+ '¼' => '1⁄4',
+ '½' => '1⁄2',
+ '¾' => '3⁄4',
+ 'IJ' => 'IJ',
+ 'ij' => 'ij',
+ 'Ŀ' => 'L·',
+ 'ŀ' => 'l·',
+ 'ʼn' => 'ʼn',
+ 'ſ' => 's',
+ 'DŽ' => 'DŽ',
+ 'Dž' => 'Dž',
+ 'dž' => 'dž',
+ 'LJ' => 'LJ',
+ 'Lj' => 'Lj',
+ 'lj' => 'lj',
+ 'NJ' => 'NJ',
+ 'Nj' => 'Nj',
+ 'nj' => 'nj',
+ 'DZ' => 'DZ',
+ 'Dz' => 'Dz',
+ 'dz' => 'dz',
+ 'ʰ' => 'h',
+ 'ʱ' => 'ɦ',
+ 'ʲ' => 'j',
+ 'ʳ' => 'r',
+ 'ʴ' => 'ɹ',
+ 'ʵ' => 'ɻ',
+ 'ʶ' => 'ʁ',
+ 'ʷ' => 'w',
+ 'ʸ' => 'y',
+ '˘' => ' ̆',
+ '˙' => ' ̇',
+ '˚' => ' ̊',
+ '˛' => ' ̨',
+ '˜' => ' ̃',
+ '˝' => ' ̋',
+ 'ˠ' => 'ɣ',
+ 'ˡ' => 'l',
+ 'ˢ' => 's',
+ 'ˣ' => 'x',
+ 'ˤ' => 'ʕ',
+ 'ͺ' => ' ͅ',
+ '΄' => ' ́',
+ '΅' => ' ̈́',
+ 'ϐ' => 'β',
+ 'ϑ' => 'θ',
+ 'ϒ' => 'Υ',
+ 'ϓ' => 'Ύ',
+ 'ϔ' => 'Ϋ',
+ 'ϕ' => 'φ',
+ 'ϖ' => 'π',
+ 'ϰ' => 'κ',
+ 'ϱ' => 'ρ',
+ 'ϲ' => 'ς',
+ 'ϴ' => 'Θ',
+ 'ϵ' => 'ε',
+ 'Ϲ' => 'Σ',
+ 'և' => 'եւ',
+ 'ٵ' => 'اٴ',
+ 'ٶ' => 'وٴ',
+ 'ٷ' => 'ۇٴ',
+ 'ٸ' => 'يٴ',
+ 'ำ' => 'ํา',
+ 'ຳ' => 'ໍາ',
+ 'ໜ' => 'ຫນ',
+ 'ໝ' => 'ຫມ',
+ '༌' => '་',
+ 'ཷ' => 'ྲཱྀ',
+ 'ཹ' => 'ླཱྀ',
+ 'ჼ' => 'ნ',
+ 'ᴬ' => 'A',
+ 'ᴭ' => 'Æ',
+ 'ᴮ' => 'B',
+ 'ᴰ' => 'D',
+ 'ᴱ' => 'E',
+ 'ᴲ' => 'Ǝ',
+ 'ᴳ' => 'G',
+ 'ᴴ' => 'H',
+ 'ᴵ' => 'I',
+ 'ᴶ' => 'J',
+ 'ᴷ' => 'K',
+ 'ᴸ' => 'L',
+ 'ᴹ' => 'M',
+ 'ᴺ' => 'N',
+ 'ᴼ' => 'O',
+ 'ᴽ' => 'Ȣ',
+ 'ᴾ' => 'P',
+ 'ᴿ' => 'R',
+ 'ᵀ' => 'T',
+ 'ᵁ' => 'U',
+ 'ᵂ' => 'W',
+ 'ᵃ' => 'a',
+ 'ᵄ' => 'ɐ',
+ 'ᵅ' => 'ɑ',
+ 'ᵆ' => 'ᴂ',
+ 'ᵇ' => 'b',
+ 'ᵈ' => 'd',
+ 'ᵉ' => 'e',
+ 'ᵊ' => 'ə',
+ 'ᵋ' => 'ɛ',
+ 'ᵌ' => 'ɜ',
+ 'ᵍ' => 'g',
+ 'ᵏ' => 'k',
+ 'ᵐ' => 'm',
+ 'ᵑ' => 'ŋ',
+ 'ᵒ' => 'o',
+ 'ᵓ' => 'ɔ',
+ 'ᵔ' => 'ᴖ',
+ 'ᵕ' => 'ᴗ',
+ 'ᵖ' => 'p',
+ 'ᵗ' => 't',
+ 'ᵘ' => 'u',
+ 'ᵙ' => 'ᴝ',
+ 'ᵚ' => 'ɯ',
+ 'ᵛ' => 'v',
+ 'ᵜ' => 'ᴥ',
+ 'ᵝ' => 'β',
+ 'ᵞ' => 'γ',
+ 'ᵟ' => 'δ',
+ 'ᵠ' => 'φ',
+ 'ᵡ' => 'χ',
+ 'ᵢ' => 'i',
+ 'ᵣ' => 'r',
+ 'ᵤ' => 'u',
+ 'ᵥ' => 'v',
+ 'ᵦ' => 'β',
+ 'ᵧ' => 'γ',
+ 'ᵨ' => 'ρ',
+ 'ᵩ' => 'φ',
+ 'ᵪ' => 'χ',
+ 'ᵸ' => 'н',
+ 'ᶛ' => 'ɒ',
+ 'ᶜ' => 'c',
+ 'ᶝ' => 'ɕ',
+ 'ᶞ' => 'ð',
+ 'ᶟ' => 'ɜ',
+ 'ᶠ' => 'f',
+ 'ᶡ' => 'ɟ',
+ 'ᶢ' => 'ɡ',
+ 'ᶣ' => 'ɥ',
+ 'ᶤ' => 'ɨ',
+ 'ᶥ' => 'ɩ',
+ 'ᶦ' => 'ɪ',
+ 'ᶧ' => 'ᵻ',
+ 'ᶨ' => 'ʝ',
+ 'ᶩ' => 'ɭ',
+ 'ᶪ' => 'ᶅ',
+ 'ᶫ' => 'ʟ',
+ 'ᶬ' => 'ɱ',
+ 'ᶭ' => 'ɰ',
+ 'ᶮ' => 'ɲ',
+ 'ᶯ' => 'ɳ',
+ 'ᶰ' => 'ɴ',
+ 'ᶱ' => 'ɵ',
+ 'ᶲ' => 'ɸ',
+ 'ᶳ' => 'ʂ',
+ 'ᶴ' => 'ʃ',
+ 'ᶵ' => 'ƫ',
+ 'ᶶ' => 'ʉ',
+ 'ᶷ' => 'ʊ',
+ 'ᶸ' => 'ᴜ',
+ 'ᶹ' => 'ʋ',
+ 'ᶺ' => 'ʌ',
+ 'ᶻ' => 'z',
+ 'ᶼ' => 'ʐ',
+ 'ᶽ' => 'ʑ',
+ 'ᶾ' => 'ʒ',
+ 'ᶿ' => 'θ',
+ 'ẚ' => 'aʾ',
+ 'ẛ' => 'ṡ',
+ '᾽' => ' ̓',
+ '᾿' => ' ̓',
+ '῀' => ' ͂',
+ '῁' => ' ̈͂',
+ '῍' => ' ̓̀',
+ '῎' => ' ̓́',
+ '῏' => ' ̓͂',
+ '῝' => ' ̔̀',
+ '῞' => ' ̔́',
+ '῟' => ' ̔͂',
+ '῭' => ' ̈̀',
+ '΅' => ' ̈́',
+ '´' => ' ́',
+ '῾' => ' ̔',
+ ' ' => ' ',
+ ' ' => ' ',
+ ' ' => ' ',
+ ' ' => ' ',
+ ' ' => ' ',
+ ' ' => ' ',
+ ' ' => ' ',
+ ' ' => ' ',
+ ' ' => ' ',
+ ' ' => ' ',
+ ' ' => ' ',
+ '‑' => '‐',
+ '‗' => ' ̳',
+ '․' => '.',
+ '‥' => '..',
+ '…' => '...',
+ ' ' => ' ',
+ '″' => '′′',
+ '‴' => '′′′',
+ '‶' => '‵‵',
+ '‷' => '‵‵‵',
+ '‼' => '!!',
+ '‾' => ' ̅',
+ '⁇' => '??',
+ '⁈' => '?!',
+ '⁉' => '!?',
+ '⁗' => '′′′′',
+ ' ' => ' ',
+ '⁰' => '0',
+ 'ⁱ' => 'i',
+ '⁴' => '4',
+ '⁵' => '5',
+ '⁶' => '6',
+ '⁷' => '7',
+ '⁸' => '8',
+ '⁹' => '9',
+ '⁺' => '+',
+ '⁻' => '−',
+ '⁼' => '=',
+ '⁽' => '(',
+ '⁾' => ')',
+ 'ⁿ' => 'n',
+ '₀' => '0',
+ '₁' => '1',
+ '₂' => '2',
+ '₃' => '3',
+ '₄' => '4',
+ '₅' => '5',
+ '₆' => '6',
+ '₇' => '7',
+ '₈' => '8',
+ '₉' => '9',
+ '₊' => '+',
+ '₋' => '−',
+ '₌' => '=',
+ '₍' => '(',
+ '₎' => ')',
+ 'ₐ' => 'a',
+ 'ₑ' => 'e',
+ 'ₒ' => 'o',
+ 'ₓ' => 'x',
+ 'ₔ' => 'ə',
+ 'ₕ' => 'h',
+ 'ₖ' => 'k',
+ 'ₗ' => 'l',
+ 'ₘ' => 'm',
+ 'ₙ' => 'n',
+ 'ₚ' => 'p',
+ 'ₛ' => 's',
+ 'ₜ' => 't',
+ '₨' => 'Rs',
+ '℀' => 'a/c',
+ '℁' => 'a/s',
+ 'ℂ' => 'C',
+ '℃' => '°C',
+ '℅' => 'c/o',
+ '℆' => 'c/u',
+ 'ℇ' => 'Ɛ',
+ '℉' => '°F',
+ 'ℊ' => 'g',
+ 'ℋ' => 'H',
+ 'ℌ' => 'H',
+ 'ℍ' => 'H',
+ 'ℎ' => 'h',
+ 'ℏ' => 'ħ',
+ 'ℐ' => 'I',
+ 'ℑ' => 'I',
+ 'ℒ' => 'L',
+ 'ℓ' => 'l',
+ 'ℕ' => 'N',
+ '№' => 'No',
+ 'ℙ' => 'P',
+ 'ℚ' => 'Q',
+ 'ℛ' => 'R',
+ 'ℜ' => 'R',
+ 'ℝ' => 'R',
+ '℠' => 'SM',
+ '℡' => 'TEL',
+ '™' => 'TM',
+ 'ℤ' => 'Z',
+ 'ℨ' => 'Z',
+ 'ℬ' => 'B',
+ 'ℭ' => 'C',
+ 'ℯ' => 'e',
+ 'ℰ' => 'E',
+ 'ℱ' => 'F',
+ 'ℳ' => 'M',
+ 'ℴ' => 'o',
+ 'ℵ' => 'א',
+ 'ℶ' => 'ב',
+ 'ℷ' => 'ג',
+ 'ℸ' => 'ד',
+ 'ℹ' => 'i',
+ '℻' => 'FAX',
+ 'ℼ' => 'π',
+ 'ℽ' => 'γ',
+ 'ℾ' => 'Γ',
+ 'ℿ' => 'Π',
+ '⅀' => '∑',
+ 'ⅅ' => 'D',
+ 'ⅆ' => 'd',
+ 'ⅇ' => 'e',
+ 'ⅈ' => 'i',
+ 'ⅉ' => 'j',
+ '⅐' => '1⁄7',
+ '⅑' => '1⁄9',
+ '⅒' => '1⁄10',
+ '⅓' => '1⁄3',
+ '⅔' => '2⁄3',
+ '⅕' => '1⁄5',
+ '⅖' => '2⁄5',
+ '⅗' => '3⁄5',
+ '⅘' => '4⁄5',
+ '⅙' => '1⁄6',
+ '⅚' => '5⁄6',
+ '⅛' => '1⁄8',
+ '⅜' => '3⁄8',
+ '⅝' => '5⁄8',
+ '⅞' => '7⁄8',
+ '⅟' => '1⁄',
+ 'Ⅰ' => 'I',
+ 'Ⅱ' => 'II',
+ 'Ⅲ' => 'III',
+ 'Ⅳ' => 'IV',
+ 'Ⅴ' => 'V',
+ 'Ⅵ' => 'VI',
+ 'Ⅶ' => 'VII',
+ 'Ⅷ' => 'VIII',
+ 'Ⅸ' => 'IX',
+ 'Ⅹ' => 'X',
+ 'Ⅺ' => 'XI',
+ 'Ⅻ' => 'XII',
+ 'Ⅼ' => 'L',
+ 'Ⅽ' => 'C',
+ 'Ⅾ' => 'D',
+ 'Ⅿ' => 'M',
+ 'ⅰ' => 'i',
+ 'ⅱ' => 'ii',
+ 'ⅲ' => 'iii',
+ 'ⅳ' => 'iv',
+ 'ⅴ' => 'v',
+ 'ⅵ' => 'vi',
+ 'ⅶ' => 'vii',
+ 'ⅷ' => 'viii',
+ 'ⅸ' => 'ix',
+ 'ⅹ' => 'x',
+ 'ⅺ' => 'xi',
+ 'ⅻ' => 'xii',
+ 'ⅼ' => 'l',
+ 'ⅽ' => 'c',
+ 'ⅾ' => 'd',
+ 'ⅿ' => 'm',
+ '↉' => '0⁄3',
+ '∬' => '∫∫',
+ '∭' => '∫∫∫',
+ '∯' => '∮∮',
+ '∰' => '∮∮∮',
+ '①' => '1',
+ '②' => '2',
+ '③' => '3',
+ '④' => '4',
+ '⑤' => '5',
+ '⑥' => '6',
+ '⑦' => '7',
+ '⑧' => '8',
+ '⑨' => '9',
+ '⑩' => '10',
+ '⑪' => '11',
+ '⑫' => '12',
+ '⑬' => '13',
+ '⑭' => '14',
+ '⑮' => '15',
+ '⑯' => '16',
+ '⑰' => '17',
+ '⑱' => '18',
+ '⑲' => '19',
+ '⑳' => '20',
+ '⑴' => '(1)',
+ '⑵' => '(2)',
+ '⑶' => '(3)',
+ '⑷' => '(4)',
+ '⑸' => '(5)',
+ '⑹' => '(6)',
+ '⑺' => '(7)',
+ '⑻' => '(8)',
+ '⑼' => '(9)',
+ '⑽' => '(10)',
+ '⑾' => '(11)',
+ '⑿' => '(12)',
+ '⒀' => '(13)',
+ '⒁' => '(14)',
+ '⒂' => '(15)',
+ '⒃' => '(16)',
+ '⒄' => '(17)',
+ '⒅' => '(18)',
+ '⒆' => '(19)',
+ '⒇' => '(20)',
+ '⒈' => '1.',
+ '⒉' => '2.',
+ '⒊' => '3.',
+ '⒋' => '4.',
+ '⒌' => '5.',
+ '⒍' => '6.',
+ '⒎' => '7.',
+ '⒏' => '8.',
+ '⒐' => '9.',
+ '⒑' => '10.',
+ '⒒' => '11.',
+ '⒓' => '12.',
+ '⒔' => '13.',
+ '⒕' => '14.',
+ '⒖' => '15.',
+ '⒗' => '16.',
+ '⒘' => '17.',
+ '⒙' => '18.',
+ '⒚' => '19.',
+ '⒛' => '20.',
+ '⒜' => '(a)',
+ '⒝' => '(b)',
+ '⒞' => '(c)',
+ '⒟' => '(d)',
+ '⒠' => '(e)',
+ '⒡' => '(f)',
+ '⒢' => '(g)',
+ '⒣' => '(h)',
+ '⒤' => '(i)',
+ '⒥' => '(j)',
+ '⒦' => '(k)',
+ '⒧' => '(l)',
+ '⒨' => '(m)',
+ '⒩' => '(n)',
+ '⒪' => '(o)',
+ '⒫' => '(p)',
+ '⒬' => '(q)',
+ '⒭' => '(r)',
+ '⒮' => '(s)',
+ '⒯' => '(t)',
+ '⒰' => '(u)',
+ '⒱' => '(v)',
+ '⒲' => '(w)',
+ '⒳' => '(x)',
+ '⒴' => '(y)',
+ '⒵' => '(z)',
+ 'Ⓐ' => 'A',
+ 'Ⓑ' => 'B',
+ 'Ⓒ' => 'C',
+ 'Ⓓ' => 'D',
+ 'Ⓔ' => 'E',
+ 'Ⓕ' => 'F',
+ 'Ⓖ' => 'G',
+ 'Ⓗ' => 'H',
+ 'Ⓘ' => 'I',
+ 'Ⓙ' => 'J',
+ 'Ⓚ' => 'K',
+ 'Ⓛ' => 'L',
+ 'Ⓜ' => 'M',
+ 'Ⓝ' => 'N',
+ 'Ⓞ' => 'O',
+ 'Ⓟ' => 'P',
+ 'Ⓠ' => 'Q',
+ 'Ⓡ' => 'R',
+ 'Ⓢ' => 'S',
+ 'Ⓣ' => 'T',
+ 'Ⓤ' => 'U',
+ 'Ⓥ' => 'V',
+ 'Ⓦ' => 'W',
+ 'Ⓧ' => 'X',
+ 'Ⓨ' => 'Y',
+ 'Ⓩ' => 'Z',
+ 'ⓐ' => 'a',
+ 'ⓑ' => 'b',
+ 'ⓒ' => 'c',
+ 'ⓓ' => 'd',
+ 'ⓔ' => 'e',
+ 'ⓕ' => 'f',
+ 'ⓖ' => 'g',
+ 'ⓗ' => 'h',
+ 'ⓘ' => 'i',
+ 'ⓙ' => 'j',
+ 'ⓚ' => 'k',
+ 'ⓛ' => 'l',
+ 'ⓜ' => 'm',
+ 'ⓝ' => 'n',
+ 'ⓞ' => 'o',
+ 'ⓟ' => 'p',
+ 'ⓠ' => 'q',
+ 'ⓡ' => 'r',
+ 'ⓢ' => 's',
+ 'ⓣ' => 't',
+ 'ⓤ' => 'u',
+ 'ⓥ' => 'v',
+ 'ⓦ' => 'w',
+ 'ⓧ' => 'x',
+ 'ⓨ' => 'y',
+ 'ⓩ' => 'z',
+ '⓪' => '0',
+ '⨌' => '∫∫∫∫',
+ '⩴' => '::=',
+ '⩵' => '==',
+ '⩶' => '===',
+ 'ⱼ' => 'j',
+ 'ⱽ' => 'V',
+ 'ⵯ' => 'ⵡ',
+ '⺟' => '母',
+ '⻳' => '龟',
+ '⼀' => '一',
+ '⼁' => '丨',
+ '⼂' => '丶',
+ '⼃' => '丿',
+ '⼄' => '乙',
+ '⼅' => '亅',
+ '⼆' => '二',
+ '⼇' => '亠',
+ '⼈' => '人',
+ '⼉' => '儿',
+ '⼊' => '入',
+ '⼋' => '八',
+ '⼌' => '冂',
+ '⼍' => '冖',
+ '⼎' => '冫',
+ '⼏' => '几',
+ '⼐' => '凵',
+ '⼑' => '刀',
+ '⼒' => '力',
+ '⼓' => '勹',
+ '⼔' => '匕',
+ '⼕' => '匚',
+ '⼖' => '匸',
+ '⼗' => '十',
+ '⼘' => '卜',
+ '⼙' => '卩',
+ '⼚' => '厂',
+ '⼛' => '厶',
+ '⼜' => '又',
+ '⼝' => '口',
+ '⼞' => '囗',
+ '⼟' => '土',
+ '⼠' => '士',
+ '⼡' => '夂',
+ '⼢' => '夊',
+ '⼣' => '夕',
+ '⼤' => '大',
+ '⼥' => '女',
+ '⼦' => '子',
+ '⼧' => '宀',
+ '⼨' => '寸',
+ '⼩' => '小',
+ '⼪' => '尢',
+ '⼫' => '尸',
+ '⼬' => '屮',
+ '⼭' => '山',
+ '⼮' => '巛',
+ '⼯' => '工',
+ '⼰' => '己',
+ '⼱' => '巾',
+ '⼲' => '干',
+ '⼳' => '幺',
+ '⼴' => '广',
+ '⼵' => '廴',
+ '⼶' => '廾',
+ '⼷' => '弋',
+ '⼸' => '弓',
+ '⼹' => '彐',
+ '⼺' => '彡',
+ '⼻' => '彳',
+ '⼼' => '心',
+ '⼽' => '戈',
+ '⼾' => '戶',
+ '⼿' => '手',
+ '⽀' => '支',
+ '⽁' => '攴',
+ '⽂' => '文',
+ '⽃' => '斗',
+ '⽄' => '斤',
+ '⽅' => '方',
+ '⽆' => '无',
+ '⽇' => '日',
+ '⽈' => '曰',
+ '⽉' => '月',
+ '⽊' => '木',
+ '⽋' => '欠',
+ '⽌' => '止',
+ '⽍' => '歹',
+ '⽎' => '殳',
+ '⽏' => '毋',
+ '⽐' => '比',
+ '⽑' => '毛',
+ '⽒' => '氏',
+ '⽓' => '气',
+ '⽔' => '水',
+ '⽕' => '火',
+ '⽖' => '爪',
+ '⽗' => '父',
+ '⽘' => '爻',
+ '⽙' => '爿',
+ '⽚' => '片',
+ '⽛' => '牙',
+ '⽜' => '牛',
+ '⽝' => '犬',
+ '⽞' => '玄',
+ '⽟' => '玉',
+ '⽠' => '瓜',
+ '⽡' => '瓦',
+ '⽢' => '甘',
+ '⽣' => '生',
+ '⽤' => '用',
+ '⽥' => '田',
+ '⽦' => '疋',
+ '⽧' => '疒',
+ '⽨' => '癶',
+ '⽩' => '白',
+ '⽪' => '皮',
+ '⽫' => '皿',
+ '⽬' => '目',
+ '⽭' => '矛',
+ '⽮' => '矢',
+ '⽯' => '石',
+ '⽰' => '示',
+ '⽱' => '禸',
+ '⽲' => '禾',
+ '⽳' => '穴',
+ '⽴' => '立',
+ '⽵' => '竹',
+ '⽶' => '米',
+ '⽷' => '糸',
+ '⽸' => '缶',
+ '⽹' => '网',
+ '⽺' => '羊',
+ '⽻' => '羽',
+ '⽼' => '老',
+ '⽽' => '而',
+ '⽾' => '耒',
+ '⽿' => '耳',
+ '⾀' => '聿',
+ '⾁' => '肉',
+ '⾂' => '臣',
+ '⾃' => '自',
+ '⾄' => '至',
+ '⾅' => '臼',
+ '⾆' => '舌',
+ '⾇' => '舛',
+ '⾈' => '舟',
+ '⾉' => '艮',
+ '⾊' => '色',
+ '⾋' => '艸',
+ '⾌' => '虍',
+ '⾍' => '虫',
+ '⾎' => '血',
+ '⾏' => '行',
+ '⾐' => '衣',
+ '⾑' => '襾',
+ '⾒' => '見',
+ '⾓' => '角',
+ '⾔' => '言',
+ '⾕' => '谷',
+ '⾖' => '豆',
+ '⾗' => '豕',
+ '⾘' => '豸',
+ '⾙' => '貝',
+ '⾚' => '赤',
+ '⾛' => '走',
+ '⾜' => '足',
+ '⾝' => '身',
+ '⾞' => '車',
+ '⾟' => '辛',
+ '⾠' => '辰',
+ '⾡' => '辵',
+ '⾢' => '邑',
+ '⾣' => '酉',
+ '⾤' => '釆',
+ '⾥' => '里',
+ '⾦' => '金',
+ '⾧' => '長',
+ '⾨' => '門',
+ '⾩' => '阜',
+ '⾪' => '隶',
+ '⾫' => '隹',
+ '⾬' => '雨',
+ '⾭' => '靑',
+ '⾮' => '非',
+ '⾯' => '面',
+ '⾰' => '革',
+ '⾱' => '韋',
+ '⾲' => '韭',
+ '⾳' => '音',
+ '⾴' => '頁',
+ '⾵' => '風',
+ '⾶' => '飛',
+ '⾷' => '食',
+ '⾸' => '首',
+ '⾹' => '香',
+ '⾺' => '馬',
+ '⾻' => '骨',
+ '⾼' => '高',
+ '⾽' => '髟',
+ '⾾' => '鬥',
+ '⾿' => '鬯',
+ '⿀' => '鬲',
+ '⿁' => '鬼',
+ '⿂' => '魚',
+ '⿃' => '鳥',
+ '⿄' => '鹵',
+ '⿅' => '鹿',
+ '⿆' => '麥',
+ '⿇' => '麻',
+ '⿈' => '黃',
+ '⿉' => '黍',
+ '⿊' => '黑',
+ '⿋' => '黹',
+ '⿌' => '黽',
+ '⿍' => '鼎',
+ '⿎' => '鼓',
+ '⿏' => '鼠',
+ '⿐' => '鼻',
+ '⿑' => '齊',
+ '⿒' => '齒',
+ '⿓' => '龍',
+ '⿔' => '龜',
+ '⿕' => '龠',
+ ' ' => ' ',
+ '〶' => '〒',
+ '〸' => '十',
+ '〹' => '卄',
+ '〺' => '卅',
+ '゛' => ' ゙',
+ '゜' => ' ゚',
+ 'ゟ' => 'より',
+ 'ヿ' => 'コト',
+ 'ㄱ' => 'ᄀ',
+ 'ㄲ' => 'ᄁ',
+ 'ㄳ' => 'ᆪ',
+ 'ㄴ' => 'ᄂ',
+ 'ㄵ' => 'ᆬ',
+ 'ㄶ' => 'ᆭ',
+ 'ㄷ' => 'ᄃ',
+ 'ㄸ' => 'ᄄ',
+ 'ㄹ' => 'ᄅ',
+ 'ㄺ' => 'ᆰ',
+ 'ㄻ' => 'ᆱ',
+ 'ㄼ' => 'ᆲ',
+ 'ㄽ' => 'ᆳ',
+ 'ㄾ' => 'ᆴ',
+ 'ㄿ' => 'ᆵ',
+ 'ㅀ' => 'ᄚ',
+ 'ㅁ' => 'ᄆ',
+ 'ㅂ' => 'ᄇ',
+ 'ㅃ' => 'ᄈ',
+ 'ㅄ' => 'ᄡ',
+ 'ㅅ' => 'ᄉ',
+ 'ㅆ' => 'ᄊ',
+ 'ㅇ' => 'ᄋ',
+ 'ㅈ' => 'ᄌ',
+ 'ㅉ' => 'ᄍ',
+ 'ㅊ' => 'ᄎ',
+ 'ㅋ' => 'ᄏ',
+ 'ㅌ' => 'ᄐ',
+ 'ㅍ' => 'ᄑ',
+ 'ㅎ' => 'ᄒ',
+ 'ㅏ' => 'ᅡ',
+ 'ㅐ' => 'ᅢ',
+ 'ㅑ' => 'ᅣ',
+ 'ㅒ' => 'ᅤ',
+ 'ㅓ' => 'ᅥ',
+ 'ㅔ' => 'ᅦ',
+ 'ㅕ' => 'ᅧ',
+ 'ㅖ' => 'ᅨ',
+ 'ㅗ' => 'ᅩ',
+ 'ㅘ' => 'ᅪ',
+ 'ㅙ' => 'ᅫ',
+ 'ㅚ' => 'ᅬ',
+ 'ㅛ' => 'ᅭ',
+ 'ㅜ' => 'ᅮ',
+ 'ㅝ' => 'ᅯ',
+ 'ㅞ' => 'ᅰ',
+ 'ㅟ' => 'ᅱ',
+ 'ㅠ' => 'ᅲ',
+ 'ㅡ' => 'ᅳ',
+ 'ㅢ' => 'ᅴ',
+ 'ㅣ' => 'ᅵ',
+ 'ㅤ' => 'ᅠ',
+ 'ㅥ' => 'ᄔ',
+ 'ㅦ' => 'ᄕ',
+ 'ㅧ' => 'ᇇ',
+ 'ㅨ' => 'ᇈ',
+ 'ㅩ' => 'ᇌ',
+ 'ㅪ' => 'ᇎ',
+ 'ㅫ' => 'ᇓ',
+ 'ㅬ' => 'ᇗ',
+ 'ㅭ' => 'ᇙ',
+ 'ㅮ' => 'ᄜ',
+ 'ㅯ' => 'ᇝ',
+ 'ㅰ' => 'ᇟ',
+ 'ㅱ' => 'ᄝ',
+ 'ㅲ' => 'ᄞ',
+ 'ㅳ' => 'ᄠ',
+ 'ㅴ' => 'ᄢ',
+ 'ㅵ' => 'ᄣ',
+ 'ㅶ' => 'ᄧ',
+ 'ㅷ' => 'ᄩ',
+ 'ㅸ' => 'ᄫ',
+ 'ㅹ' => 'ᄬ',
+ 'ㅺ' => 'ᄭ',
+ 'ㅻ' => 'ᄮ',
+ 'ㅼ' => 'ᄯ',
+ 'ㅽ' => 'ᄲ',
+ 'ㅾ' => 'ᄶ',
+ 'ㅿ' => 'ᅀ',
+ 'ㆀ' => 'ᅇ',
+ 'ㆁ' => 'ᅌ',
+ 'ㆂ' => 'ᇱ',
+ 'ㆃ' => 'ᇲ',
+ 'ㆄ' => 'ᅗ',
+ 'ㆅ' => 'ᅘ',
+ 'ㆆ' => 'ᅙ',
+ 'ㆇ' => 'ᆄ',
+ 'ㆈ' => 'ᆅ',
+ 'ㆉ' => 'ᆈ',
+ 'ㆊ' => 'ᆑ',
+ 'ㆋ' => 'ᆒ',
+ 'ㆌ' => 'ᆔ',
+ 'ㆍ' => 'ᆞ',
+ 'ㆎ' => 'ᆡ',
+ '㆒' => '一',
+ '㆓' => '二',
+ '㆔' => '三',
+ '㆕' => '四',
+ '㆖' => '上',
+ '㆗' => '中',
+ '㆘' => '下',
+ '㆙' => '甲',
+ '㆚' => '乙',
+ '㆛' => '丙',
+ '㆜' => '丁',
+ '㆝' => '天',
+ '㆞' => '地',
+ '㆟' => '人',
+ '㈀' => '(ᄀ)',
+ '㈁' => '(ᄂ)',
+ '㈂' => '(ᄃ)',
+ '㈃' => '(ᄅ)',
+ '㈄' => '(ᄆ)',
+ '㈅' => '(ᄇ)',
+ '㈆' => '(ᄉ)',
+ '㈇' => '(ᄋ)',
+ '㈈' => '(ᄌ)',
+ '㈉' => '(ᄎ)',
+ '㈊' => '(ᄏ)',
+ '㈋' => '(ᄐ)',
+ '㈌' => '(ᄑ)',
+ '㈍' => '(ᄒ)',
+ '㈎' => '(가)',
+ '㈏' => '(나)',
+ '㈐' => '(다)',
+ '㈑' => '(라)',
+ '㈒' => '(마)',
+ '㈓' => '(바)',
+ '㈔' => '(사)',
+ '㈕' => '(아)',
+ '㈖' => '(자)',
+ '㈗' => '(차)',
+ '㈘' => '(카)',
+ '㈙' => '(타)',
+ '㈚' => '(파)',
+ '㈛' => '(하)',
+ '㈜' => '(주)',
+ '㈝' => '(오전)',
+ '㈞' => '(오후)',
+ '㈠' => '(一)',
+ '㈡' => '(二)',
+ '㈢' => '(三)',
+ '㈣' => '(四)',
+ '㈤' => '(五)',
+ '㈥' => '(六)',
+ '㈦' => '(七)',
+ '㈧' => '(八)',
+ '㈨' => '(九)',
+ '㈩' => '(十)',
+ '㈪' => '(月)',
+ '㈫' => '(火)',
+ '㈬' => '(水)',
+ '㈭' => '(木)',
+ '㈮' => '(金)',
+ '㈯' => '(土)',
+ '㈰' => '(日)',
+ '㈱' => '(株)',
+ '㈲' => '(有)',
+ '㈳' => '(社)',
+ '㈴' => '(名)',
+ '㈵' => '(特)',
+ '㈶' => '(財)',
+ '㈷' => '(祝)',
+ '㈸' => '(労)',
+ '㈹' => '(代)',
+ '㈺' => '(呼)',
+ '㈻' => '(学)',
+ '㈼' => '(監)',
+ '㈽' => '(企)',
+ '㈾' => '(資)',
+ '㈿' => '(協)',
+ '㉀' => '(祭)',
+ '㉁' => '(休)',
+ '㉂' => '(自)',
+ '㉃' => '(至)',
+ '㉄' => '問',
+ '㉅' => '幼',
+ '㉆' => '文',
+ '㉇' => '箏',
+ '㉐' => 'PTE',
+ '㉑' => '21',
+ '㉒' => '22',
+ '㉓' => '23',
+ '㉔' => '24',
+ '㉕' => '25',
+ '㉖' => '26',
+ '㉗' => '27',
+ '㉘' => '28',
+ '㉙' => '29',
+ '㉚' => '30',
+ '㉛' => '31',
+ '㉜' => '32',
+ '㉝' => '33',
+ '㉞' => '34',
+ '㉟' => '35',
+ '㉠' => 'ᄀ',
+ '㉡' => 'ᄂ',
+ '㉢' => 'ᄃ',
+ '㉣' => 'ᄅ',
+ '㉤' => 'ᄆ',
+ '㉥' => 'ᄇ',
+ '㉦' => 'ᄉ',
+ '㉧' => 'ᄋ',
+ '㉨' => 'ᄌ',
+ '㉩' => 'ᄎ',
+ '㉪' => 'ᄏ',
+ '㉫' => 'ᄐ',
+ '㉬' => 'ᄑ',
+ '㉭' => 'ᄒ',
+ '㉮' => '가',
+ '㉯' => '나',
+ '㉰' => '다',
+ '㉱' => '라',
+ '㉲' => '마',
+ '㉳' => '바',
+ '㉴' => '사',
+ '㉵' => '아',
+ '㉶' => '자',
+ '㉷' => '차',
+ '㉸' => '카',
+ '㉹' => '타',
+ '㉺' => '파',
+ '㉻' => '하',
+ '㉼' => '참고',
+ '㉽' => '주의',
+ '㉾' => '우',
+ '㊀' => '一',
+ '㊁' => '二',
+ '㊂' => '三',
+ '㊃' => '四',
+ '㊄' => '五',
+ '㊅' => '六',
+ '㊆' => '七',
+ '㊇' => '八',
+ '㊈' => '九',
+ '㊉' => '十',
+ '㊊' => '月',
+ '㊋' => '火',
+ '㊌' => '水',
+ '㊍' => '木',
+ '㊎' => '金',
+ '㊏' => '土',
+ '㊐' => '日',
+ '㊑' => '株',
+ '㊒' => '有',
+ '㊓' => '社',
+ '㊔' => '名',
+ '㊕' => '特',
+ '㊖' => '財',
+ '㊗' => '祝',
+ '㊘' => '労',
+ '㊙' => '秘',
+ '㊚' => '男',
+ '㊛' => '女',
+ '㊜' => '適',
+ '㊝' => '優',
+ '㊞' => '印',
+ '㊟' => '注',
+ '㊠' => '項',
+ '㊡' => '休',
+ '㊢' => '写',
+ '㊣' => '正',
+ '㊤' => '上',
+ '㊥' => '中',
+ '㊦' => '下',
+ '㊧' => '左',
+ '㊨' => '右',
+ '㊩' => '医',
+ '㊪' => '宗',
+ '㊫' => '学',
+ '㊬' => '監',
+ '㊭' => '企',
+ '㊮' => '資',
+ '㊯' => '協',
+ '㊰' => '夜',
+ '㊱' => '36',
+ '㊲' => '37',
+ '㊳' => '38',
+ '㊴' => '39',
+ '㊵' => '40',
+ '㊶' => '41',
+ '㊷' => '42',
+ '㊸' => '43',
+ '㊹' => '44',
+ '㊺' => '45',
+ '㊻' => '46',
+ '㊼' => '47',
+ '㊽' => '48',
+ '㊾' => '49',
+ '㊿' => '50',
+ '㋀' => '1月',
+ '㋁' => '2月',
+ '㋂' => '3月',
+ '㋃' => '4月',
+ '㋄' => '5月',
+ '㋅' => '6月',
+ '㋆' => '7月',
+ '㋇' => '8月',
+ '㋈' => '9月',
+ '㋉' => '10月',
+ '㋊' => '11月',
+ '㋋' => '12月',
+ '㋌' => 'Hg',
+ '㋍' => 'erg',
+ '㋎' => 'eV',
+ '㋏' => 'LTD',
+ '㋐' => 'ア',
+ '㋑' => 'イ',
+ '㋒' => 'ウ',
+ '㋓' => 'エ',
+ '㋔' => 'オ',
+ '㋕' => 'カ',
+ '㋖' => 'キ',
+ '㋗' => 'ク',
+ '㋘' => 'ケ',
+ '㋙' => 'コ',
+ '㋚' => 'サ',
+ '㋛' => 'シ',
+ '㋜' => 'ス',
+ '㋝' => 'セ',
+ '㋞' => 'ソ',
+ '㋟' => 'タ',
+ '㋠' => 'チ',
+ '㋡' => 'ツ',
+ '㋢' => 'テ',
+ '㋣' => 'ト',
+ '㋤' => 'ナ',
+ '㋥' => 'ニ',
+ '㋦' => 'ヌ',
+ '㋧' => 'ネ',
+ '㋨' => 'ノ',
+ '㋩' => 'ハ',
+ '㋪' => 'ヒ',
+ '㋫' => 'フ',
+ '㋬' => 'ヘ',
+ '㋭' => 'ホ',
+ '㋮' => 'マ',
+ '㋯' => 'ミ',
+ '㋰' => 'ム',
+ '㋱' => 'メ',
+ '㋲' => 'モ',
+ '㋳' => 'ヤ',
+ '㋴' => 'ユ',
+ '㋵' => 'ヨ',
+ '㋶' => 'ラ',
+ '㋷' => 'リ',
+ '㋸' => 'ル',
+ '㋹' => 'レ',
+ '㋺' => 'ロ',
+ '㋻' => 'ワ',
+ '㋼' => 'ヰ',
+ '㋽' => 'ヱ',
+ '㋾' => 'ヲ',
+ '㋿' => '令和',
+ '㌀' => 'アパート',
+ '㌁' => 'アルファ',
+ '㌂' => 'アンペア',
+ '㌃' => 'アール',
+ '㌄' => 'イニング',
+ '㌅' => 'インチ',
+ '㌆' => 'ウォン',
+ '㌇' => 'エスクード',
+ '㌈' => 'エーカー',
+ '㌉' => 'オンス',
+ '㌊' => 'オーム',
+ '㌋' => 'カイリ',
+ '㌌' => 'カラット',
+ '㌍' => 'カロリー',
+ '㌎' => 'ガロン',
+ '㌏' => 'ガンマ',
+ '㌐' => 'ギガ',
+ '㌑' => 'ギニー',
+ '㌒' => 'キュリー',
+ '㌓' => 'ギルダー',
+ '㌔' => 'キロ',
+ '㌕' => 'キログラム',
+ '㌖' => 'キロメートル',
+ '㌗' => 'キロワット',
+ '㌘' => 'グラム',
+ '㌙' => 'グラムトン',
+ '㌚' => 'クルゼイロ',
+ '㌛' => 'クローネ',
+ '㌜' => 'ケース',
+ '㌝' => 'コルナ',
+ '㌞' => 'コーポ',
+ '㌟' => 'サイクル',
+ '㌠' => 'サンチーム',
+ '㌡' => 'シリング',
+ '㌢' => 'センチ',
+ '㌣' => 'セント',
+ '㌤' => 'ダース',
+ '㌥' => 'デシ',
+ '㌦' => 'ドル',
+ '㌧' => 'トン',
+ '㌨' => 'ナノ',
+ '㌩' => 'ノット',
+ '㌪' => 'ハイツ',
+ '㌫' => 'パーセント',
+ '㌬' => 'パーツ',
+ '㌭' => 'バーレル',
+ '㌮' => 'ピアストル',
+ '㌯' => 'ピクル',
+ '㌰' => 'ピコ',
+ '㌱' => 'ビル',
+ '㌲' => 'ファラッド',
+ '㌳' => 'フィート',
+ '㌴' => 'ブッシェル',
+ '㌵' => 'フラン',
+ '㌶' => 'ヘクタール',
+ '㌷' => 'ペソ',
+ '㌸' => 'ペニヒ',
+ '㌹' => 'ヘルツ',
+ '㌺' => 'ペンス',
+ '㌻' => 'ページ',
+ '㌼' => 'ベータ',
+ '㌽' => 'ポイント',
+ '㌾' => 'ボルト',
+ '㌿' => 'ホン',
+ '㍀' => 'ポンド',
+ '㍁' => 'ホール',
+ '㍂' => 'ホーン',
+ '㍃' => 'マイクロ',
+ '㍄' => 'マイル',
+ '㍅' => 'マッハ',
+ '㍆' => 'マルク',
+ '㍇' => 'マンション',
+ '㍈' => 'ミクロン',
+ '㍉' => 'ミリ',
+ '㍊' => 'ミリバール',
+ '㍋' => 'メガ',
+ '㍌' => 'メガトン',
+ '㍍' => 'メートル',
+ '㍎' => 'ヤード',
+ '㍏' => 'ヤール',
+ '㍐' => 'ユアン',
+ '㍑' => 'リットル',
+ '㍒' => 'リラ',
+ '㍓' => 'ルピー',
+ '㍔' => 'ルーブル',
+ '㍕' => 'レム',
+ '㍖' => 'レントゲン',
+ '㍗' => 'ワット',
+ '㍘' => '0点',
+ '㍙' => '1点',
+ '㍚' => '2点',
+ '㍛' => '3点',
+ '㍜' => '4点',
+ '㍝' => '5点',
+ '㍞' => '6点',
+ '㍟' => '7点',
+ '㍠' => '8点',
+ '㍡' => '9点',
+ '㍢' => '10点',
+ '㍣' => '11点',
+ '㍤' => '12点',
+ '㍥' => '13点',
+ '㍦' => '14点',
+ '㍧' => '15点',
+ '㍨' => '16点',
+ '㍩' => '17点',
+ '㍪' => '18点',
+ '㍫' => '19点',
+ '㍬' => '20点',
+ '㍭' => '21点',
+ '㍮' => '22点',
+ '㍯' => '23点',
+ '㍰' => '24点',
+ '㍱' => 'hPa',
+ '㍲' => 'da',
+ '㍳' => 'AU',
+ '㍴' => 'bar',
+ '㍵' => 'oV',
+ '㍶' => 'pc',
+ '㍷' => 'dm',
+ '㍸' => 'dm2',
+ '㍹' => 'dm3',
+ '㍺' => 'IU',
+ '㍻' => '平成',
+ '㍼' => '昭和',
+ '㍽' => '大正',
+ '㍾' => '明治',
+ '㍿' => '株式会社',
+ '㎀' => 'pA',
+ '㎁' => 'nA',
+ '㎂' => 'μA',
+ '㎃' => 'mA',
+ '㎄' => 'kA',
+ '㎅' => 'KB',
+ '㎆' => 'MB',
+ '㎇' => 'GB',
+ '㎈' => 'cal',
+ '㎉' => 'kcal',
+ '㎊' => 'pF',
+ '㎋' => 'nF',
+ '㎌' => 'μF',
+ '㎍' => 'μg',
+ '㎎' => 'mg',
+ '㎏' => 'kg',
+ '㎐' => 'Hz',
+ '㎑' => 'kHz',
+ '㎒' => 'MHz',
+ '㎓' => 'GHz',
+ '㎔' => 'THz',
+ '㎕' => 'μl',
+ '㎖' => 'ml',
+ '㎗' => 'dl',
+ '㎘' => 'kl',
+ '㎙' => 'fm',
+ '㎚' => 'nm',
+ '㎛' => 'μm',
+ '㎜' => 'mm',
+ '㎝' => 'cm',
+ '㎞' => 'km',
+ '㎟' => 'mm2',
+ '㎠' => 'cm2',
+ '㎡' => 'm2',
+ '㎢' => 'km2',
+ '㎣' => 'mm3',
+ '㎤' => 'cm3',
+ '㎥' => 'm3',
+ '㎦' => 'km3',
+ '㎧' => 'm∕s',
+ '㎨' => 'm∕s2',
+ '㎩' => 'Pa',
+ '㎪' => 'kPa',
+ '㎫' => 'MPa',
+ '㎬' => 'GPa',
+ '㎭' => 'rad',
+ '㎮' => 'rad∕s',
+ '㎯' => 'rad∕s2',
+ '㎰' => 'ps',
+ '㎱' => 'ns',
+ '㎲' => 'μs',
+ '㎳' => 'ms',
+ '㎴' => 'pV',
+ '㎵' => 'nV',
+ '㎶' => 'μV',
+ '㎷' => 'mV',
+ '㎸' => 'kV',
+ '㎹' => 'MV',
+ '㎺' => 'pW',
+ '㎻' => 'nW',
+ '㎼' => 'μW',
+ '㎽' => 'mW',
+ '㎾' => 'kW',
+ '㎿' => 'MW',
+ '㏀' => 'kΩ',
+ '㏁' => 'MΩ',
+ '㏂' => 'a.m.',
+ '㏃' => 'Bq',
+ '㏄' => 'cc',
+ '㏅' => 'cd',
+ '㏆' => 'C∕kg',
+ '㏇' => 'Co.',
+ '㏈' => 'dB',
+ '㏉' => 'Gy',
+ '㏊' => 'ha',
+ '㏋' => 'HP',
+ '㏌' => 'in',
+ '㏍' => 'KK',
+ '㏎' => 'KM',
+ '㏏' => 'kt',
+ '㏐' => 'lm',
+ '㏑' => 'ln',
+ '㏒' => 'log',
+ '㏓' => 'lx',
+ '㏔' => 'mb',
+ '㏕' => 'mil',
+ '㏖' => 'mol',
+ '㏗' => 'PH',
+ '㏘' => 'p.m.',
+ '㏙' => 'PPM',
+ '㏚' => 'PR',
+ '㏛' => 'sr',
+ '㏜' => 'Sv',
+ '㏝' => 'Wb',
+ '㏞' => 'V∕m',
+ '㏟' => 'A∕m',
+ '㏠' => '1日',
+ '㏡' => '2日',
+ '㏢' => '3日',
+ '㏣' => '4日',
+ '㏤' => '5日',
+ '㏥' => '6日',
+ '㏦' => '7日',
+ '㏧' => '8日',
+ '㏨' => '9日',
+ '㏩' => '10日',
+ '㏪' => '11日',
+ '㏫' => '12日',
+ '㏬' => '13日',
+ '㏭' => '14日',
+ '㏮' => '15日',
+ '㏯' => '16日',
+ '㏰' => '17日',
+ '㏱' => '18日',
+ '㏲' => '19日',
+ '㏳' => '20日',
+ '㏴' => '21日',
+ '㏵' => '22日',
+ '㏶' => '23日',
+ '㏷' => '24日',
+ '㏸' => '25日',
+ '㏹' => '26日',
+ '㏺' => '27日',
+ '㏻' => '28日',
+ '㏼' => '29日',
+ '㏽' => '30日',
+ '㏾' => '31日',
+ '㏿' => 'gal',
+ 'ꚜ' => 'ъ',
+ 'ꚝ' => 'ь',
+ 'ꝰ' => 'ꝯ',
+ 'ꟸ' => 'Ħ',
+ 'ꟹ' => 'œ',
+ 'ꭜ' => 'ꜧ',
+ 'ꭝ' => 'ꬷ',
+ 'ꭞ' => 'ɫ',
+ 'ꭟ' => 'ꭒ',
+ 'ꭩ' => 'ʍ',
+ 'ff' => 'ff',
+ 'fi' => 'fi',
+ 'fl' => 'fl',
+ 'ffi' => 'ffi',
+ 'ffl' => 'ffl',
+ 'ſt' => 'st',
+ 'st' => 'st',
+ 'ﬓ' => 'մն',
+ 'ﬔ' => 'մե',
+ 'ﬕ' => 'մի',
+ 'ﬖ' => 'վն',
+ 'ﬗ' => 'մխ',
+ 'ﬠ' => 'ע',
+ 'ﬡ' => 'א',
+ 'ﬢ' => 'ד',
+ 'ﬣ' => 'ה',
+ 'ﬤ' => 'כ',
+ 'ﬥ' => 'ל',
+ 'ﬦ' => 'ם',
+ 'ﬧ' => 'ר',
+ 'ﬨ' => 'ת',
+ '﬩' => '+',
+ 'ﭏ' => 'אל',
+ 'ﭐ' => 'ٱ',
+ 'ﭑ' => 'ٱ',
+ 'ﭒ' => 'ٻ',
+ 'ﭓ' => 'ٻ',
+ 'ﭔ' => 'ٻ',
+ 'ﭕ' => 'ٻ',
+ 'ﭖ' => 'پ',
+ 'ﭗ' => 'پ',
+ 'ﭘ' => 'پ',
+ 'ﭙ' => 'پ',
+ 'ﭚ' => 'ڀ',
+ 'ﭛ' => 'ڀ',
+ 'ﭜ' => 'ڀ',
+ 'ﭝ' => 'ڀ',
+ 'ﭞ' => 'ٺ',
+ 'ﭟ' => 'ٺ',
+ 'ﭠ' => 'ٺ',
+ 'ﭡ' => 'ٺ',
+ 'ﭢ' => 'ٿ',
+ 'ﭣ' => 'ٿ',
+ 'ﭤ' => 'ٿ',
+ 'ﭥ' => 'ٿ',
+ 'ﭦ' => 'ٹ',
+ 'ﭧ' => 'ٹ',
+ 'ﭨ' => 'ٹ',
+ 'ﭩ' => 'ٹ',
+ 'ﭪ' => 'ڤ',
+ 'ﭫ' => 'ڤ',
+ 'ﭬ' => 'ڤ',
+ 'ﭭ' => 'ڤ',
+ 'ﭮ' => 'ڦ',
+ 'ﭯ' => 'ڦ',
+ 'ﭰ' => 'ڦ',
+ 'ﭱ' => 'ڦ',
+ 'ﭲ' => 'ڄ',
+ 'ﭳ' => 'ڄ',
+ 'ﭴ' => 'ڄ',
+ 'ﭵ' => 'ڄ',
+ 'ﭶ' => 'ڃ',
+ 'ﭷ' => 'ڃ',
+ 'ﭸ' => 'ڃ',
+ 'ﭹ' => 'ڃ',
+ 'ﭺ' => 'چ',
+ 'ﭻ' => 'چ',
+ 'ﭼ' => 'چ',
+ 'ﭽ' => 'چ',
+ 'ﭾ' => 'ڇ',
+ 'ﭿ' => 'ڇ',
+ 'ﮀ' => 'ڇ',
+ 'ﮁ' => 'ڇ',
+ 'ﮂ' => 'ڍ',
+ 'ﮃ' => 'ڍ',
+ 'ﮄ' => 'ڌ',
+ 'ﮅ' => 'ڌ',
+ 'ﮆ' => 'ڎ',
+ 'ﮇ' => 'ڎ',
+ 'ﮈ' => 'ڈ',
+ 'ﮉ' => 'ڈ',
+ 'ﮊ' => 'ژ',
+ 'ﮋ' => 'ژ',
+ 'ﮌ' => 'ڑ',
+ 'ﮍ' => 'ڑ',
+ 'ﮎ' => 'ک',
+ 'ﮏ' => 'ک',
+ 'ﮐ' => 'ک',
+ 'ﮑ' => 'ک',
+ 'ﮒ' => 'گ',
+ 'ﮓ' => 'گ',
+ 'ﮔ' => 'گ',
+ 'ﮕ' => 'گ',
+ 'ﮖ' => 'ڳ',
+ 'ﮗ' => 'ڳ',
+ 'ﮘ' => 'ڳ',
+ 'ﮙ' => 'ڳ',
+ 'ﮚ' => 'ڱ',
+ 'ﮛ' => 'ڱ',
+ 'ﮜ' => 'ڱ',
+ 'ﮝ' => 'ڱ',
+ 'ﮞ' => 'ں',
+ 'ﮟ' => 'ں',
+ 'ﮠ' => 'ڻ',
+ 'ﮡ' => 'ڻ',
+ 'ﮢ' => 'ڻ',
+ 'ﮣ' => 'ڻ',
+ 'ﮤ' => 'ۀ',
+ 'ﮥ' => 'ۀ',
+ 'ﮦ' => 'ہ',
+ 'ﮧ' => 'ہ',
+ 'ﮨ' => 'ہ',
+ 'ﮩ' => 'ہ',
+ 'ﮪ' => 'ھ',
+ 'ﮫ' => 'ھ',
+ 'ﮬ' => 'ھ',
+ 'ﮭ' => 'ھ',
+ 'ﮮ' => 'ے',
+ 'ﮯ' => 'ے',
+ 'ﮰ' => 'ۓ',
+ 'ﮱ' => 'ۓ',
+ 'ﯓ' => 'ڭ',
+ 'ﯔ' => 'ڭ',
+ 'ﯕ' => 'ڭ',
+ 'ﯖ' => 'ڭ',
+ 'ﯗ' => 'ۇ',
+ 'ﯘ' => 'ۇ',
+ 'ﯙ' => 'ۆ',
+ 'ﯚ' => 'ۆ',
+ 'ﯛ' => 'ۈ',
+ 'ﯜ' => 'ۈ',
+ 'ﯝ' => 'ۇٴ',
+ 'ﯞ' => 'ۋ',
+ 'ﯟ' => 'ۋ',
+ 'ﯠ' => 'ۅ',
+ 'ﯡ' => 'ۅ',
+ 'ﯢ' => 'ۉ',
+ 'ﯣ' => 'ۉ',
+ 'ﯤ' => 'ې',
+ 'ﯥ' => 'ې',
+ 'ﯦ' => 'ې',
+ 'ﯧ' => 'ې',
+ 'ﯨ' => 'ى',
+ 'ﯩ' => 'ى',
+ 'ﯪ' => 'ئا',
+ 'ﯫ' => 'ئا',
+ 'ﯬ' => 'ئە',
+ 'ﯭ' => 'ئە',
+ 'ﯮ' => 'ئو',
+ 'ﯯ' => 'ئو',
+ 'ﯰ' => 'ئۇ',
+ 'ﯱ' => 'ئۇ',
+ 'ﯲ' => 'ئۆ',
+ 'ﯳ' => 'ئۆ',
+ 'ﯴ' => 'ئۈ',
+ 'ﯵ' => 'ئۈ',
+ 'ﯶ' => 'ئې',
+ 'ﯷ' => 'ئې',
+ 'ﯸ' => 'ئې',
+ 'ﯹ' => 'ئى',
+ 'ﯺ' => 'ئى',
+ 'ﯻ' => 'ئى',
+ 'ﯼ' => 'ی',
+ 'ﯽ' => 'ی',
+ 'ﯾ' => 'ی',
+ 'ﯿ' => 'ی',
+ 'ﰀ' => 'ئج',
+ 'ﰁ' => 'ئح',
+ 'ﰂ' => 'ئم',
+ 'ﰃ' => 'ئى',
+ 'ﰄ' => 'ئي',
+ 'ﰅ' => 'بج',
+ 'ﰆ' => 'بح',
+ 'ﰇ' => 'بخ',
+ 'ﰈ' => 'بم',
+ 'ﰉ' => 'بى',
+ 'ﰊ' => 'بي',
+ 'ﰋ' => 'تج',
+ 'ﰌ' => 'تح',
+ 'ﰍ' => 'تخ',
+ 'ﰎ' => 'تم',
+ 'ﰏ' => 'تى',
+ 'ﰐ' => 'تي',
+ 'ﰑ' => 'ثج',
+ 'ﰒ' => 'ثم',
+ 'ﰓ' => 'ثى',
+ 'ﰔ' => 'ثي',
+ 'ﰕ' => 'جح',
+ 'ﰖ' => 'جم',
+ 'ﰗ' => 'حج',
+ 'ﰘ' => 'حم',
+ 'ﰙ' => 'خج',
+ 'ﰚ' => 'خح',
+ 'ﰛ' => 'خم',
+ 'ﰜ' => 'سج',
+ 'ﰝ' => 'سح',
+ 'ﰞ' => 'سخ',
+ 'ﰟ' => 'سم',
+ 'ﰠ' => 'صح',
+ 'ﰡ' => 'صم',
+ 'ﰢ' => 'ضج',
+ 'ﰣ' => 'ضح',
+ 'ﰤ' => 'ضخ',
+ 'ﰥ' => 'ضم',
+ 'ﰦ' => 'طح',
+ 'ﰧ' => 'طم',
+ 'ﰨ' => 'ظم',
+ 'ﰩ' => 'عج',
+ 'ﰪ' => 'عم',
+ 'ﰫ' => 'غج',
+ 'ﰬ' => 'غم',
+ 'ﰭ' => 'فج',
+ 'ﰮ' => 'فح',
+ 'ﰯ' => 'فخ',
+ 'ﰰ' => 'فم',
+ 'ﰱ' => 'فى',
+ 'ﰲ' => 'في',
+ 'ﰳ' => 'قح',
+ 'ﰴ' => 'قم',
+ 'ﰵ' => 'قى',
+ 'ﰶ' => 'قي',
+ 'ﰷ' => 'كا',
+ 'ﰸ' => 'كج',
+ 'ﰹ' => 'كح',
+ 'ﰺ' => 'كخ',
+ 'ﰻ' => 'كل',
+ 'ﰼ' => 'كم',
+ 'ﰽ' => 'كى',
+ 'ﰾ' => 'كي',
+ 'ﰿ' => 'لج',
+ 'ﱀ' => 'لح',
+ 'ﱁ' => 'لخ',
+ 'ﱂ' => 'لم',
+ 'ﱃ' => 'لى',
+ 'ﱄ' => 'لي',
+ 'ﱅ' => 'مج',
+ 'ﱆ' => 'مح',
+ 'ﱇ' => 'مخ',
+ 'ﱈ' => 'مم',
+ 'ﱉ' => 'مى',
+ 'ﱊ' => 'مي',
+ 'ﱋ' => 'نج',
+ 'ﱌ' => 'نح',
+ 'ﱍ' => 'نخ',
+ 'ﱎ' => 'نم',
+ 'ﱏ' => 'نى',
+ 'ﱐ' => 'ني',
+ 'ﱑ' => 'هج',
+ 'ﱒ' => 'هم',
+ 'ﱓ' => 'هى',
+ 'ﱔ' => 'هي',
+ 'ﱕ' => 'يج',
+ 'ﱖ' => 'يح',
+ 'ﱗ' => 'يخ',
+ 'ﱘ' => 'يم',
+ 'ﱙ' => 'يى',
+ 'ﱚ' => 'يي',
+ 'ﱛ' => 'ذٰ',
+ 'ﱜ' => 'رٰ',
+ 'ﱝ' => 'ىٰ',
+ 'ﱞ' => ' ٌّ',
+ 'ﱟ' => ' ٍّ',
+ 'ﱠ' => ' َّ',
+ 'ﱡ' => ' ُّ',
+ 'ﱢ' => ' ِّ',
+ 'ﱣ' => ' ّٰ',
+ 'ﱤ' => 'ئر',
+ 'ﱥ' => 'ئز',
+ 'ﱦ' => 'ئم',
+ 'ﱧ' => 'ئن',
+ 'ﱨ' => 'ئى',
+ 'ﱩ' => 'ئي',
+ 'ﱪ' => 'بر',
+ 'ﱫ' => 'بز',
+ 'ﱬ' => 'بم',
+ 'ﱭ' => 'بن',
+ 'ﱮ' => 'بى',
+ 'ﱯ' => 'بي',
+ 'ﱰ' => 'تر',
+ 'ﱱ' => 'تز',
+ 'ﱲ' => 'تم',
+ 'ﱳ' => 'تن',
+ 'ﱴ' => 'تى',
+ 'ﱵ' => 'تي',
+ 'ﱶ' => 'ثر',
+ 'ﱷ' => 'ثز',
+ 'ﱸ' => 'ثم',
+ 'ﱹ' => 'ثن',
+ 'ﱺ' => 'ثى',
+ 'ﱻ' => 'ثي',
+ 'ﱼ' => 'فى',
+ 'ﱽ' => 'في',
+ 'ﱾ' => 'قى',
+ 'ﱿ' => 'قي',
+ 'ﲀ' => 'كا',
+ 'ﲁ' => 'كل',
+ 'ﲂ' => 'كم',
+ 'ﲃ' => 'كى',
+ 'ﲄ' => 'كي',
+ 'ﲅ' => 'لم',
+ 'ﲆ' => 'لى',
+ 'ﲇ' => 'لي',
+ 'ﲈ' => 'ما',
+ 'ﲉ' => 'مم',
+ 'ﲊ' => 'نر',
+ 'ﲋ' => 'نز',
+ 'ﲌ' => 'نم',
+ 'ﲍ' => 'نن',
+ 'ﲎ' => 'نى',
+ 'ﲏ' => 'ني',
+ 'ﲐ' => 'ىٰ',
+ 'ﲑ' => 'ير',
+ 'ﲒ' => 'يز',
+ 'ﲓ' => 'يم',
+ 'ﲔ' => 'ين',
+ 'ﲕ' => 'يى',
+ 'ﲖ' => 'يي',
+ 'ﲗ' => 'ئج',
+ 'ﲘ' => 'ئح',
+ 'ﲙ' => 'ئخ',
+ 'ﲚ' => 'ئم',
+ 'ﲛ' => 'ئه',
+ 'ﲜ' => 'بج',
+ 'ﲝ' => 'بح',
+ 'ﲞ' => 'بخ',
+ 'ﲟ' => 'بم',
+ 'ﲠ' => 'به',
+ 'ﲡ' => 'تج',
+ 'ﲢ' => 'تح',
+ 'ﲣ' => 'تخ',
+ 'ﲤ' => 'تم',
+ 'ﲥ' => 'ته',
+ 'ﲦ' => 'ثم',
+ 'ﲧ' => 'جح',
+ 'ﲨ' => 'جم',
+ 'ﲩ' => 'حج',
+ 'ﲪ' => 'حم',
+ 'ﲫ' => 'خج',
+ 'ﲬ' => 'خم',
+ 'ﲭ' => 'سج',
+ 'ﲮ' => 'سح',
+ 'ﲯ' => 'سخ',
+ 'ﲰ' => 'سم',
+ 'ﲱ' => 'صح',
+ 'ﲲ' => 'صخ',
+ 'ﲳ' => 'صم',
+ 'ﲴ' => 'ضج',
+ 'ﲵ' => 'ضح',
+ 'ﲶ' => 'ضخ',
+ 'ﲷ' => 'ضم',
+ 'ﲸ' => 'طح',
+ 'ﲹ' => 'ظم',
+ 'ﲺ' => 'عج',
+ 'ﲻ' => 'عم',
+ 'ﲼ' => 'غج',
+ 'ﲽ' => 'غم',
+ 'ﲾ' => 'فج',
+ 'ﲿ' => 'فح',
+ 'ﳀ' => 'فخ',
+ 'ﳁ' => 'فم',
+ 'ﳂ' => 'قح',
+ 'ﳃ' => 'قم',
+ 'ﳄ' => 'كج',
+ 'ﳅ' => 'كح',
+ 'ﳆ' => 'كخ',
+ 'ﳇ' => 'كل',
+ 'ﳈ' => 'كم',
+ 'ﳉ' => 'لج',
+ 'ﳊ' => 'لح',
+ 'ﳋ' => 'لخ',
+ 'ﳌ' => 'لم',
+ 'ﳍ' => 'له',
+ 'ﳎ' => 'مج',
+ 'ﳏ' => 'مح',
+ 'ﳐ' => 'مخ',
+ 'ﳑ' => 'مم',
+ 'ﳒ' => 'نج',
+ 'ﳓ' => 'نح',
+ 'ﳔ' => 'نخ',
+ 'ﳕ' => 'نم',
+ 'ﳖ' => 'نه',
+ 'ﳗ' => 'هج',
+ 'ﳘ' => 'هم',
+ 'ﳙ' => 'هٰ',
+ 'ﳚ' => 'يج',
+ 'ﳛ' => 'يح',
+ 'ﳜ' => 'يخ',
+ 'ﳝ' => 'يم',
+ 'ﳞ' => 'يه',
+ 'ﳟ' => 'ئم',
+ 'ﳠ' => 'ئه',
+ 'ﳡ' => 'بم',
+ 'ﳢ' => 'به',
+ 'ﳣ' => 'تم',
+ 'ﳤ' => 'ته',
+ 'ﳥ' => 'ثم',
+ 'ﳦ' => 'ثه',
+ 'ﳧ' => 'سم',
+ 'ﳨ' => 'سه',
+ 'ﳩ' => 'شم',
+ 'ﳪ' => 'شه',
+ 'ﳫ' => 'كل',
+ 'ﳬ' => 'كم',
+ 'ﳭ' => 'لم',
+ 'ﳮ' => 'نم',
+ 'ﳯ' => 'نه',
+ 'ﳰ' => 'يم',
+ 'ﳱ' => 'يه',
+ 'ﳲ' => 'ـَّ',
+ 'ﳳ' => 'ـُّ',
+ 'ﳴ' => 'ـِّ',
+ 'ﳵ' => 'طى',
+ 'ﳶ' => 'طي',
+ 'ﳷ' => 'عى',
+ 'ﳸ' => 'عي',
+ 'ﳹ' => 'غى',
+ 'ﳺ' => 'غي',
+ 'ﳻ' => 'سى',
+ 'ﳼ' => 'سي',
+ 'ﳽ' => 'شى',
+ 'ﳾ' => 'شي',
+ 'ﳿ' => 'حى',
+ 'ﴀ' => 'حي',
+ 'ﴁ' => 'جى',
+ 'ﴂ' => 'جي',
+ 'ﴃ' => 'خى',
+ 'ﴄ' => 'خي',
+ 'ﴅ' => 'صى',
+ 'ﴆ' => 'صي',
+ 'ﴇ' => 'ضى',
+ 'ﴈ' => 'ضي',
+ 'ﴉ' => 'شج',
+ 'ﴊ' => 'شح',
+ 'ﴋ' => 'شخ',
+ 'ﴌ' => 'شم',
+ 'ﴍ' => 'شر',
+ 'ﴎ' => 'سر',
+ 'ﴏ' => 'صر',
+ 'ﴐ' => 'ضر',
+ 'ﴑ' => 'طى',
+ 'ﴒ' => 'طي',
+ 'ﴓ' => 'عى',
+ 'ﴔ' => 'عي',
+ 'ﴕ' => 'غى',
+ 'ﴖ' => 'غي',
+ 'ﴗ' => 'سى',
+ 'ﴘ' => 'سي',
+ 'ﴙ' => 'شى',
+ 'ﴚ' => 'شي',
+ 'ﴛ' => 'حى',
+ 'ﴜ' => 'حي',
+ 'ﴝ' => 'جى',
+ 'ﴞ' => 'جي',
+ 'ﴟ' => 'خى',
+ 'ﴠ' => 'خي',
+ 'ﴡ' => 'صى',
+ 'ﴢ' => 'صي',
+ 'ﴣ' => 'ضى',
+ 'ﴤ' => 'ضي',
+ 'ﴥ' => 'شج',
+ 'ﴦ' => 'شح',
+ 'ﴧ' => 'شخ',
+ 'ﴨ' => 'شم',
+ 'ﴩ' => 'شر',
+ 'ﴪ' => 'سر',
+ 'ﴫ' => 'صر',
+ 'ﴬ' => 'ضر',
+ 'ﴭ' => 'شج',
+ 'ﴮ' => 'شح',
+ 'ﴯ' => 'شخ',
+ 'ﴰ' => 'شم',
+ 'ﴱ' => 'سه',
+ 'ﴲ' => 'شه',
+ 'ﴳ' => 'طم',
+ 'ﴴ' => 'سج',
+ 'ﴵ' => 'سح',
+ 'ﴶ' => 'سخ',
+ 'ﴷ' => 'شج',
+ 'ﴸ' => 'شح',
+ 'ﴹ' => 'شخ',
+ 'ﴺ' => 'طم',
+ 'ﴻ' => 'ظم',
+ 'ﴼ' => 'اً',
+ 'ﴽ' => 'اً',
+ 'ﵐ' => 'تجم',
+ 'ﵑ' => 'تحج',
+ 'ﵒ' => 'تحج',
+ 'ﵓ' => 'تحم',
+ 'ﵔ' => 'تخم',
+ 'ﵕ' => 'تمج',
+ 'ﵖ' => 'تمح',
+ 'ﵗ' => 'تمخ',
+ 'ﵘ' => 'جمح',
+ 'ﵙ' => 'جمح',
+ 'ﵚ' => 'حمي',
+ 'ﵛ' => 'حمى',
+ 'ﵜ' => 'سحج',
+ 'ﵝ' => 'سجح',
+ 'ﵞ' => 'سجى',
+ 'ﵟ' => 'سمح',
+ 'ﵠ' => 'سمح',
+ 'ﵡ' => 'سمج',
+ 'ﵢ' => 'سمم',
+ 'ﵣ' => 'سمم',
+ 'ﵤ' => 'صحح',
+ 'ﵥ' => 'صحح',
+ 'ﵦ' => 'صمم',
+ 'ﵧ' => 'شحم',
+ 'ﵨ' => 'شحم',
+ 'ﵩ' => 'شجي',
+ 'ﵪ' => 'شمخ',
+ 'ﵫ' => 'شمخ',
+ 'ﵬ' => 'شمم',
+ 'ﵭ' => 'شمم',
+ 'ﵮ' => 'ضحى',
+ 'ﵯ' => 'ضخم',
+ 'ﵰ' => 'ضخم',
+ 'ﵱ' => 'طمح',
+ 'ﵲ' => 'طمح',
+ 'ﵳ' => 'طمم',
+ 'ﵴ' => 'طمي',
+ 'ﵵ' => 'عجم',
+ 'ﵶ' => 'عمم',
+ 'ﵷ' => 'عمم',
+ 'ﵸ' => 'عمى',
+ 'ﵹ' => 'غمم',
+ 'ﵺ' => 'غمي',
+ 'ﵻ' => 'غمى',
+ 'ﵼ' => 'فخم',
+ 'ﵽ' => 'فخم',
+ 'ﵾ' => 'قمح',
+ 'ﵿ' => 'قمم',
+ 'ﶀ' => 'لحم',
+ 'ﶁ' => 'لحي',
+ 'ﶂ' => 'لحى',
+ 'ﶃ' => 'لجج',
+ 'ﶄ' => 'لجج',
+ 'ﶅ' => 'لخم',
+ 'ﶆ' => 'لخم',
+ 'ﶇ' => 'لمح',
+ 'ﶈ' => 'لمح',
+ 'ﶉ' => 'محج',
+ 'ﶊ' => 'محم',
+ 'ﶋ' => 'محي',
+ 'ﶌ' => 'مجح',
+ 'ﶍ' => 'مجم',
+ 'ﶎ' => 'مخج',
+ 'ﶏ' => 'مخم',
+ 'ﶒ' => 'مجخ',
+ 'ﶓ' => 'همج',
+ 'ﶔ' => 'همم',
+ 'ﶕ' => 'نحم',
+ 'ﶖ' => 'نحى',
+ 'ﶗ' => 'نجم',
+ 'ﶘ' => 'نجم',
+ 'ﶙ' => 'نجى',
+ 'ﶚ' => 'نمي',
+ 'ﶛ' => 'نمى',
+ 'ﶜ' => 'يمم',
+ 'ﶝ' => 'يمم',
+ 'ﶞ' => 'بخي',
+ 'ﶟ' => 'تجي',
+ 'ﶠ' => 'تجى',
+ 'ﶡ' => 'تخي',
+ 'ﶢ' => 'تخى',
+ 'ﶣ' => 'تمي',
+ 'ﶤ' => 'تمى',
+ 'ﶥ' => 'جمي',
+ 'ﶦ' => 'جحى',
+ 'ﶧ' => 'جمى',
+ 'ﶨ' => 'سخى',
+ 'ﶩ' => 'صحي',
+ 'ﶪ' => 'شحي',
+ 'ﶫ' => 'ضحي',
+ 'ﶬ' => 'لجي',
+ 'ﶭ' => 'لمي',
+ 'ﶮ' => 'يحي',
+ 'ﶯ' => 'يجي',
+ 'ﶰ' => 'يمي',
+ 'ﶱ' => 'ممي',
+ 'ﶲ' => 'قمي',
+ 'ﶳ' => 'نحي',
+ 'ﶴ' => 'قمح',
+ 'ﶵ' => 'لحم',
+ 'ﶶ' => 'عمي',
+ 'ﶷ' => 'كمي',
+ 'ﶸ' => 'نجح',
+ 'ﶹ' => 'مخي',
+ 'ﶺ' => 'لجم',
+ 'ﶻ' => 'كمم',
+ 'ﶼ' => 'لجم',
+ 'ﶽ' => 'نجح',
+ 'ﶾ' => 'جحي',
+ 'ﶿ' => 'حجي',
+ 'ﷀ' => 'مجي',
+ 'ﷁ' => 'فمي',
+ 'ﷂ' => 'بحي',
+ 'ﷃ' => 'كمم',
+ 'ﷄ' => 'عجم',
+ 'ﷅ' => 'صمم',
+ 'ﷆ' => 'سخي',
+ 'ﷇ' => 'نجي',
+ 'ﷰ' => 'صلے',
+ 'ﷱ' => 'قلے',
+ 'ﷲ' => 'الله',
+ 'ﷳ' => 'اكبر',
+ 'ﷴ' => 'محمد',
+ 'ﷵ' => 'صلعم',
+ 'ﷶ' => 'رسول',
+ 'ﷷ' => 'عليه',
+ 'ﷸ' => 'وسلم',
+ 'ﷹ' => 'صلى',
+ 'ﷺ' => 'صلى الله عليه وسلم',
+ 'ﷻ' => 'جل جلاله',
+ '﷼' => 'ریال',
+ '︐' => ',',
+ '︑' => '、',
+ '︒' => '。',
+ '︓' => ':',
+ '︔' => ';',
+ '︕' => '!',
+ '︖' => '?',
+ '︗' => '〖',
+ '︘' => '〗',
+ '︙' => '...',
+ '︰' => '..',
+ '︱' => '—',
+ '︲' => '–',
+ '︳' => '_',
+ '︴' => '_',
+ '︵' => '(',
+ '︶' => ')',
+ '︷' => '{',
+ '︸' => '}',
+ '︹' => '〔',
+ '︺' => '〕',
+ '︻' => '【',
+ '︼' => '】',
+ '︽' => '《',
+ '︾' => '》',
+ '︿' => '〈',
+ '﹀' => '〉',
+ '﹁' => '「',
+ '﹂' => '」',
+ '﹃' => '『',
+ '﹄' => '』',
+ '﹇' => '[',
+ '﹈' => ']',
+ '﹉' => ' ̅',
+ '﹊' => ' ̅',
+ '﹋' => ' ̅',
+ '﹌' => ' ̅',
+ '﹍' => '_',
+ '﹎' => '_',
+ '﹏' => '_',
+ '﹐' => ',',
+ '﹑' => '、',
+ '﹒' => '.',
+ '﹔' => ';',
+ '﹕' => ':',
+ '﹖' => '?',
+ '﹗' => '!',
+ '﹘' => '—',
+ '﹙' => '(',
+ '﹚' => ')',
+ '﹛' => '{',
+ '﹜' => '}',
+ '﹝' => '〔',
+ '﹞' => '〕',
+ '﹟' => '#',
+ '﹠' => '&',
+ '﹡' => '*',
+ '﹢' => '+',
+ '﹣' => '-',
+ '﹤' => '<',
+ '﹥' => '>',
+ '﹦' => '=',
+ '﹨' => '\\',
+ '﹩' => '$',
+ '﹪' => '%',
+ '﹫' => '@',
+ 'ﹰ' => ' ً',
+ 'ﹱ' => 'ـً',
+ 'ﹲ' => ' ٌ',
+ 'ﹴ' => ' ٍ',
+ 'ﹶ' => ' َ',
+ 'ﹷ' => 'ـَ',
+ 'ﹸ' => ' ُ',
+ 'ﹹ' => 'ـُ',
+ 'ﹺ' => ' ِ',
+ 'ﹻ' => 'ـِ',
+ 'ﹼ' => ' ّ',
+ 'ﹽ' => 'ـّ',
+ 'ﹾ' => ' ْ',
+ 'ﹿ' => 'ـْ',
+ 'ﺀ' => 'ء',
+ 'ﺁ' => 'آ',
+ 'ﺂ' => 'آ',
+ 'ﺃ' => 'أ',
+ 'ﺄ' => 'أ',
+ 'ﺅ' => 'ؤ',
+ 'ﺆ' => 'ؤ',
+ 'ﺇ' => 'إ',
+ 'ﺈ' => 'إ',
+ 'ﺉ' => 'ئ',
+ 'ﺊ' => 'ئ',
+ 'ﺋ' => 'ئ',
+ 'ﺌ' => 'ئ',
+ 'ﺍ' => 'ا',
+ 'ﺎ' => 'ا',
+ 'ﺏ' => 'ب',
+ 'ﺐ' => 'ب',
+ 'ﺑ' => 'ب',
+ 'ﺒ' => 'ب',
+ 'ﺓ' => 'ة',
+ 'ﺔ' => 'ة',
+ 'ﺕ' => 'ت',
+ 'ﺖ' => 'ت',
+ 'ﺗ' => 'ت',
+ 'ﺘ' => 'ت',
+ 'ﺙ' => 'ث',
+ 'ﺚ' => 'ث',
+ 'ﺛ' => 'ث',
+ 'ﺜ' => 'ث',
+ 'ﺝ' => 'ج',
+ 'ﺞ' => 'ج',
+ 'ﺟ' => 'ج',
+ 'ﺠ' => 'ج',
+ 'ﺡ' => 'ح',
+ 'ﺢ' => 'ح',
+ 'ﺣ' => 'ح',
+ 'ﺤ' => 'ح',
+ 'ﺥ' => 'خ',
+ 'ﺦ' => 'خ',
+ 'ﺧ' => 'خ',
+ 'ﺨ' => 'خ',
+ 'ﺩ' => 'د',
+ 'ﺪ' => 'د',
+ 'ﺫ' => 'ذ',
+ 'ﺬ' => 'ذ',
+ 'ﺭ' => 'ر',
+ 'ﺮ' => 'ر',
+ 'ﺯ' => 'ز',
+ 'ﺰ' => 'ز',
+ 'ﺱ' => 'س',
+ 'ﺲ' => 'س',
+ 'ﺳ' => 'س',
+ 'ﺴ' => 'س',
+ 'ﺵ' => 'ش',
+ 'ﺶ' => 'ش',
+ 'ﺷ' => 'ش',
+ 'ﺸ' => 'ش',
+ 'ﺹ' => 'ص',
+ 'ﺺ' => 'ص',
+ 'ﺻ' => 'ص',
+ 'ﺼ' => 'ص',
+ 'ﺽ' => 'ض',
+ 'ﺾ' => 'ض',
+ 'ﺿ' => 'ض',
+ 'ﻀ' => 'ض',
+ 'ﻁ' => 'ط',
+ 'ﻂ' => 'ط',
+ 'ﻃ' => 'ط',
+ 'ﻄ' => 'ط',
+ 'ﻅ' => 'ظ',
+ 'ﻆ' => 'ظ',
+ 'ﻇ' => 'ظ',
+ 'ﻈ' => 'ظ',
+ 'ﻉ' => 'ع',
+ 'ﻊ' => 'ع',
+ 'ﻋ' => 'ع',
+ 'ﻌ' => 'ع',
+ 'ﻍ' => 'غ',
+ 'ﻎ' => 'غ',
+ 'ﻏ' => 'غ',
+ 'ﻐ' => 'غ',
+ 'ﻑ' => 'ف',
+ 'ﻒ' => 'ف',
+ 'ﻓ' => 'ف',
+ 'ﻔ' => 'ف',
+ 'ﻕ' => 'ق',
+ 'ﻖ' => 'ق',
+ 'ﻗ' => 'ق',
+ 'ﻘ' => 'ق',
+ 'ﻙ' => 'ك',
+ 'ﻚ' => 'ك',
+ 'ﻛ' => 'ك',
+ 'ﻜ' => 'ك',
+ 'ﻝ' => 'ل',
+ 'ﻞ' => 'ل',
+ 'ﻟ' => 'ل',
+ 'ﻠ' => 'ل',
+ 'ﻡ' => 'م',
+ 'ﻢ' => 'م',
+ 'ﻣ' => 'م',
+ 'ﻤ' => 'م',
+ 'ﻥ' => 'ن',
+ 'ﻦ' => 'ن',
+ 'ﻧ' => 'ن',
+ 'ﻨ' => 'ن',
+ 'ﻩ' => 'ه',
+ 'ﻪ' => 'ه',
+ 'ﻫ' => 'ه',
+ 'ﻬ' => 'ه',
+ 'ﻭ' => 'و',
+ 'ﻮ' => 'و',
+ 'ﻯ' => 'ى',
+ 'ﻰ' => 'ى',
+ 'ﻱ' => 'ي',
+ 'ﻲ' => 'ي',
+ 'ﻳ' => 'ي',
+ 'ﻴ' => 'ي',
+ 'ﻵ' => 'لآ',
+ 'ﻶ' => 'لآ',
+ 'ﻷ' => 'لأ',
+ 'ﻸ' => 'لأ',
+ 'ﻹ' => 'لإ',
+ 'ﻺ' => 'لإ',
+ 'ﻻ' => 'لا',
+ 'ﻼ' => 'لا',
+ '!' => '!',
+ '"' => '"',
+ '#' => '#',
+ '$' => '$',
+ '%' => '%',
+ '&' => '&',
+ ''' => '\'',
+ '(' => '(',
+ ')' => ')',
+ '*' => '*',
+ '+' => '+',
+ ',' => ',',
+ '-' => '-',
+ '.' => '.',
+ '/' => '/',
+ '0' => '0',
+ '1' => '1',
+ '2' => '2',
+ '3' => '3',
+ '4' => '4',
+ '5' => '5',
+ '6' => '6',
+ '7' => '7',
+ '8' => '8',
+ '9' => '9',
+ ':' => ':',
+ ';' => ';',
+ '<' => '<',
+ '=' => '=',
+ '>' => '>',
+ '?' => '?',
+ '@' => '@',
+ 'A' => 'A',
+ 'B' => 'B',
+ 'C' => 'C',
+ 'D' => 'D',
+ 'E' => 'E',
+ 'F' => 'F',
+ 'G' => 'G',
+ 'H' => 'H',
+ 'I' => 'I',
+ 'J' => 'J',
+ 'K' => 'K',
+ 'L' => 'L',
+ 'M' => 'M',
+ 'N' => 'N',
+ 'O' => 'O',
+ 'P' => 'P',
+ 'Q' => 'Q',
+ 'R' => 'R',
+ 'S' => 'S',
+ 'T' => 'T',
+ 'U' => 'U',
+ 'V' => 'V',
+ 'W' => 'W',
+ 'X' => 'X',
+ 'Y' => 'Y',
+ 'Z' => 'Z',
+ '[' => '[',
+ '\' => '\\',
+ ']' => ']',
+ '^' => '^',
+ '_' => '_',
+ '`' => '`',
+ 'a' => 'a',
+ 'b' => 'b',
+ 'c' => 'c',
+ 'd' => 'd',
+ 'e' => 'e',
+ 'f' => 'f',
+ 'g' => 'g',
+ 'h' => 'h',
+ 'i' => 'i',
+ 'j' => 'j',
+ 'k' => 'k',
+ 'l' => 'l',
+ 'm' => 'm',
+ 'n' => 'n',
+ 'o' => 'o',
+ 'p' => 'p',
+ 'q' => 'q',
+ 'r' => 'r',
+ 's' => 's',
+ 't' => 't',
+ 'u' => 'u',
+ 'v' => 'v',
+ 'w' => 'w',
+ 'x' => 'x',
+ 'y' => 'y',
+ 'z' => 'z',
+ '{' => '{',
+ '|' => '|',
+ '}' => '}',
+ '~' => '~',
+ '⦅' => '⦅',
+ '⦆' => '⦆',
+ '。' => '。',
+ '「' => '「',
+ '」' => '」',
+ '、' => '、',
+ '・' => '・',
+ 'ヲ' => 'ヲ',
+ 'ァ' => 'ァ',
+ 'ィ' => 'ィ',
+ 'ゥ' => 'ゥ',
+ 'ェ' => 'ェ',
+ 'ォ' => 'ォ',
+ 'ャ' => 'ャ',
+ 'ュ' => 'ュ',
+ 'ョ' => 'ョ',
+ 'ッ' => 'ッ',
+ 'ー' => 'ー',
+ 'ア' => 'ア',
+ 'イ' => 'イ',
+ 'ウ' => 'ウ',
+ 'エ' => 'エ',
+ 'オ' => 'オ',
+ 'カ' => 'カ',
+ 'キ' => 'キ',
+ 'ク' => 'ク',
+ 'ケ' => 'ケ',
+ 'コ' => 'コ',
+ 'サ' => 'サ',
+ 'シ' => 'シ',
+ 'ス' => 'ス',
+ 'セ' => 'セ',
+ 'ソ' => 'ソ',
+ 'タ' => 'タ',
+ 'チ' => 'チ',
+ 'ツ' => 'ツ',
+ 'テ' => 'テ',
+ 'ト' => 'ト',
+ 'ナ' => 'ナ',
+ 'ニ' => 'ニ',
+ 'ヌ' => 'ヌ',
+ 'ネ' => 'ネ',
+ 'ノ' => 'ノ',
+ 'ハ' => 'ハ',
+ 'ヒ' => 'ヒ',
+ 'フ' => 'フ',
+ 'ヘ' => 'ヘ',
+ 'ホ' => 'ホ',
+ 'マ' => 'マ',
+ 'ミ' => 'ミ',
+ 'ム' => 'ム',
+ 'メ' => 'メ',
+ 'モ' => 'モ',
+ 'ヤ' => 'ヤ',
+ 'ユ' => 'ユ',
+ 'ヨ' => 'ヨ',
+ 'ラ' => 'ラ',
+ 'リ' => 'リ',
+ 'ル' => 'ル',
+ 'レ' => 'レ',
+ 'ロ' => 'ロ',
+ 'ワ' => 'ワ',
+ 'ン' => 'ン',
+ '゙' => '゙',
+ '゚' => '゚',
+ 'ᅠ' => 'ᅠ',
+ 'ᄀ' => 'ᄀ',
+ 'ᄁ' => 'ᄁ',
+ 'ᆪ' => 'ᆪ',
+ 'ᄂ' => 'ᄂ',
+ 'ᆬ' => 'ᆬ',
+ 'ᆭ' => 'ᆭ',
+ 'ᄃ' => 'ᄃ',
+ 'ᄄ' => 'ᄄ',
+ 'ᄅ' => 'ᄅ',
+ 'ᆰ' => 'ᆰ',
+ 'ᆱ' => 'ᆱ',
+ 'ᆲ' => 'ᆲ',
+ 'ᆳ' => 'ᆳ',
+ 'ᆴ' => 'ᆴ',
+ 'ᆵ' => 'ᆵ',
+ 'ᄚ' => 'ᄚ',
+ 'ᄆ' => 'ᄆ',
+ 'ᄇ' => 'ᄇ',
+ 'ᄈ' => 'ᄈ',
+ 'ᄡ' => 'ᄡ',
+ 'ᄉ' => 'ᄉ',
+ 'ᄊ' => 'ᄊ',
+ 'ᄋ' => 'ᄋ',
+ 'ᄌ' => 'ᄌ',
+ 'ᄍ' => 'ᄍ',
+ 'ᄎ' => 'ᄎ',
+ 'ᄏ' => 'ᄏ',
+ 'ᄐ' => 'ᄐ',
+ 'ᄑ' => 'ᄑ',
+ 'ᄒ' => 'ᄒ',
+ 'ᅡ' => 'ᅡ',
+ 'ᅢ' => 'ᅢ',
+ 'ᅣ' => 'ᅣ',
+ 'ᅤ' => 'ᅤ',
+ 'ᅥ' => 'ᅥ',
+ 'ᅦ' => 'ᅦ',
+ 'ᅧ' => 'ᅧ',
+ 'ᅨ' => 'ᅨ',
+ 'ᅩ' => 'ᅩ',
+ 'ᅪ' => 'ᅪ',
+ 'ᅫ' => 'ᅫ',
+ 'ᅬ' => 'ᅬ',
+ 'ᅭ' => 'ᅭ',
+ 'ᅮ' => 'ᅮ',
+ 'ᅯ' => 'ᅯ',
+ 'ᅰ' => 'ᅰ',
+ 'ᅱ' => 'ᅱ',
+ 'ᅲ' => 'ᅲ',
+ 'ᅳ' => 'ᅳ',
+ 'ᅴ' => 'ᅴ',
+ 'ᅵ' => 'ᅵ',
+ '¢' => '¢',
+ '£' => '£',
+ '¬' => '¬',
+ ' ̄' => ' ̄',
+ '¦' => '¦',
+ '¥' => '¥',
+ '₩' => '₩',
+ '│' => '│',
+ '←' => '←',
+ '↑' => '↑',
+ '→' => '→',
+ '↓' => '↓',
+ '■' => '■',
+ '○' => '○',
+ '𝐀' => 'A',
+ '𝐁' => 'B',
+ '𝐂' => 'C',
+ '𝐃' => 'D',
+ '𝐄' => 'E',
+ '𝐅' => 'F',
+ '𝐆' => 'G',
+ '𝐇' => 'H',
+ '𝐈' => 'I',
+ '𝐉' => 'J',
+ '𝐊' => 'K',
+ '𝐋' => 'L',
+ '𝐌' => 'M',
+ '𝐍' => 'N',
+ '𝐎' => 'O',
+ '𝐏' => 'P',
+ '𝐐' => 'Q',
+ '𝐑' => 'R',
+ '𝐒' => 'S',
+ '𝐓' => 'T',
+ '𝐔' => 'U',
+ '𝐕' => 'V',
+ '𝐖' => 'W',
+ '𝐗' => 'X',
+ '𝐘' => 'Y',
+ '𝐙' => 'Z',
+ '𝐚' => 'a',
+ '𝐛' => 'b',
+ '𝐜' => 'c',
+ '𝐝' => 'd',
+ '𝐞' => 'e',
+ '𝐟' => 'f',
+ '𝐠' => 'g',
+ '𝐡' => 'h',
+ '𝐢' => 'i',
+ '𝐣' => 'j',
+ '𝐤' => 'k',
+ '𝐥' => 'l',
+ '𝐦' => 'm',
+ '𝐧' => 'n',
+ '𝐨' => 'o',
+ '𝐩' => 'p',
+ '𝐪' => 'q',
+ '𝐫' => 'r',
+ '𝐬' => 's',
+ '𝐭' => 't',
+ '𝐮' => 'u',
+ '𝐯' => 'v',
+ '𝐰' => 'w',
+ '𝐱' => 'x',
+ '𝐲' => 'y',
+ '𝐳' => 'z',
+ '𝐴' => 'A',
+ '𝐵' => 'B',
+ '𝐶' => 'C',
+ '𝐷' => 'D',
+ '𝐸' => 'E',
+ '𝐹' => 'F',
+ '𝐺' => 'G',
+ '𝐻' => 'H',
+ '𝐼' => 'I',
+ '𝐽' => 'J',
+ '𝐾' => 'K',
+ '𝐿' => 'L',
+ '𝑀' => 'M',
+ '𝑁' => 'N',
+ '𝑂' => 'O',
+ '𝑃' => 'P',
+ '𝑄' => 'Q',
+ '𝑅' => 'R',
+ '𝑆' => 'S',
+ '𝑇' => 'T',
+ '𝑈' => 'U',
+ '𝑉' => 'V',
+ '𝑊' => 'W',
+ '𝑋' => 'X',
+ '𝑌' => 'Y',
+ '𝑍' => 'Z',
+ '𝑎' => 'a',
+ '𝑏' => 'b',
+ '𝑐' => 'c',
+ '𝑑' => 'd',
+ '𝑒' => 'e',
+ '𝑓' => 'f',
+ '𝑔' => 'g',
+ '𝑖' => 'i',
+ '𝑗' => 'j',
+ '𝑘' => 'k',
+ '𝑙' => 'l',
+ '𝑚' => 'm',
+ '𝑛' => 'n',
+ '𝑜' => 'o',
+ '𝑝' => 'p',
+ '𝑞' => 'q',
+ '𝑟' => 'r',
+ '𝑠' => 's',
+ '𝑡' => 't',
+ '𝑢' => 'u',
+ '𝑣' => 'v',
+ '𝑤' => 'w',
+ '𝑥' => 'x',
+ '𝑦' => 'y',
+ '𝑧' => 'z',
+ '𝑨' => 'A',
+ '𝑩' => 'B',
+ '𝑪' => 'C',
+ '𝑫' => 'D',
+ '𝑬' => 'E',
+ '𝑭' => 'F',
+ '𝑮' => 'G',
+ '𝑯' => 'H',
+ '𝑰' => 'I',
+ '𝑱' => 'J',
+ '𝑲' => 'K',
+ '𝑳' => 'L',
+ '𝑴' => 'M',
+ '𝑵' => 'N',
+ '𝑶' => 'O',
+ '𝑷' => 'P',
+ '𝑸' => 'Q',
+ '𝑹' => 'R',
+ '𝑺' => 'S',
+ '𝑻' => 'T',
+ '𝑼' => 'U',
+ '𝑽' => 'V',
+ '𝑾' => 'W',
+ '𝑿' => 'X',
+ '𝒀' => 'Y',
+ '𝒁' => 'Z',
+ '𝒂' => 'a',
+ '𝒃' => 'b',
+ '𝒄' => 'c',
+ '𝒅' => 'd',
+ '𝒆' => 'e',
+ '𝒇' => 'f',
+ '𝒈' => 'g',
+ '𝒉' => 'h',
+ '𝒊' => 'i',
+ '𝒋' => 'j',
+ '𝒌' => 'k',
+ '𝒍' => 'l',
+ '𝒎' => 'm',
+ '𝒏' => 'n',
+ '𝒐' => 'o',
+ '𝒑' => 'p',
+ '𝒒' => 'q',
+ '𝒓' => 'r',
+ '𝒔' => 's',
+ '𝒕' => 't',
+ '𝒖' => 'u',
+ '𝒗' => 'v',
+ '𝒘' => 'w',
+ '𝒙' => 'x',
+ '𝒚' => 'y',
+ '𝒛' => 'z',
+ '𝒜' => 'A',
+ '𝒞' => 'C',
+ '𝒟' => 'D',
+ '𝒢' => 'G',
+ '𝒥' => 'J',
+ '𝒦' => 'K',
+ '𝒩' => 'N',
+ '𝒪' => 'O',
+ '𝒫' => 'P',
+ '𝒬' => 'Q',
+ '𝒮' => 'S',
+ '𝒯' => 'T',
+ '𝒰' => 'U',
+ '𝒱' => 'V',
+ '𝒲' => 'W',
+ '𝒳' => 'X',
+ '𝒴' => 'Y',
+ '𝒵' => 'Z',
+ '𝒶' => 'a',
+ '𝒷' => 'b',
+ '𝒸' => 'c',
+ '𝒹' => 'd',
+ '𝒻' => 'f',
+ '𝒽' => 'h',
+ '𝒾' => 'i',
+ '𝒿' => 'j',
+ '𝓀' => 'k',
+ '𝓁' => 'l',
+ '𝓂' => 'm',
+ '𝓃' => 'n',
+ '𝓅' => 'p',
+ '𝓆' => 'q',
+ '𝓇' => 'r',
+ '𝓈' => 's',
+ '𝓉' => 't',
+ '𝓊' => 'u',
+ '𝓋' => 'v',
+ '𝓌' => 'w',
+ '𝓍' => 'x',
+ '𝓎' => 'y',
+ '𝓏' => 'z',
+ '𝓐' => 'A',
+ '𝓑' => 'B',
+ '𝓒' => 'C',
+ '𝓓' => 'D',
+ '𝓔' => 'E',
+ '𝓕' => 'F',
+ '𝓖' => 'G',
+ '𝓗' => 'H',
+ '𝓘' => 'I',
+ '𝓙' => 'J',
+ '𝓚' => 'K',
+ '𝓛' => 'L',
+ '𝓜' => 'M',
+ '𝓝' => 'N',
+ '𝓞' => 'O',
+ '𝓟' => 'P',
+ '𝓠' => 'Q',
+ '𝓡' => 'R',
+ '𝓢' => 'S',
+ '𝓣' => 'T',
+ '𝓤' => 'U',
+ '𝓥' => 'V',
+ '𝓦' => 'W',
+ '𝓧' => 'X',
+ '𝓨' => 'Y',
+ '𝓩' => 'Z',
+ '𝓪' => 'a',
+ '𝓫' => 'b',
+ '𝓬' => 'c',
+ '𝓭' => 'd',
+ '𝓮' => 'e',
+ '𝓯' => 'f',
+ '𝓰' => 'g',
+ '𝓱' => 'h',
+ '𝓲' => 'i',
+ '𝓳' => 'j',
+ '𝓴' => 'k',
+ '𝓵' => 'l',
+ '𝓶' => 'm',
+ '𝓷' => 'n',
+ '𝓸' => 'o',
+ '𝓹' => 'p',
+ '𝓺' => 'q',
+ '𝓻' => 'r',
+ '𝓼' => 's',
+ '𝓽' => 't',
+ '𝓾' => 'u',
+ '𝓿' => 'v',
+ '𝔀' => 'w',
+ '𝔁' => 'x',
+ '𝔂' => 'y',
+ '𝔃' => 'z',
+ '𝔄' => 'A',
+ '𝔅' => 'B',
+ '𝔇' => 'D',
+ '𝔈' => 'E',
+ '𝔉' => 'F',
+ '𝔊' => 'G',
+ '𝔍' => 'J',
+ '𝔎' => 'K',
+ '𝔏' => 'L',
+ '𝔐' => 'M',
+ '𝔑' => 'N',
+ '𝔒' => 'O',
+ '𝔓' => 'P',
+ '𝔔' => 'Q',
+ '𝔖' => 'S',
+ '𝔗' => 'T',
+ '𝔘' => 'U',
+ '𝔙' => 'V',
+ '𝔚' => 'W',
+ '𝔛' => 'X',
+ '𝔜' => 'Y',
+ '𝔞' => 'a',
+ '𝔟' => 'b',
+ '𝔠' => 'c',
+ '𝔡' => 'd',
+ '𝔢' => 'e',
+ '𝔣' => 'f',
+ '𝔤' => 'g',
+ '𝔥' => 'h',
+ '𝔦' => 'i',
+ '𝔧' => 'j',
+ '𝔨' => 'k',
+ '𝔩' => 'l',
+ '𝔪' => 'm',
+ '𝔫' => 'n',
+ '𝔬' => 'o',
+ '𝔭' => 'p',
+ '𝔮' => 'q',
+ '𝔯' => 'r',
+ '𝔰' => 's',
+ '𝔱' => 't',
+ '𝔲' => 'u',
+ '𝔳' => 'v',
+ '𝔴' => 'w',
+ '𝔵' => 'x',
+ '𝔶' => 'y',
+ '𝔷' => 'z',
+ '𝔸' => 'A',
+ '𝔹' => 'B',
+ '𝔻' => 'D',
+ '𝔼' => 'E',
+ '𝔽' => 'F',
+ '𝔾' => 'G',
+ '𝕀' => 'I',
+ '𝕁' => 'J',
+ '𝕂' => 'K',
+ '𝕃' => 'L',
+ '𝕄' => 'M',
+ '𝕆' => 'O',
+ '𝕊' => 'S',
+ '𝕋' => 'T',
+ '𝕌' => 'U',
+ '𝕍' => 'V',
+ '𝕎' => 'W',
+ '𝕏' => 'X',
+ '𝕐' => 'Y',
+ '𝕒' => 'a',
+ '𝕓' => 'b',
+ '𝕔' => 'c',
+ '𝕕' => 'd',
+ '𝕖' => 'e',
+ '𝕗' => 'f',
+ '𝕘' => 'g',
+ '𝕙' => 'h',
+ '𝕚' => 'i',
+ '𝕛' => 'j',
+ '𝕜' => 'k',
+ '𝕝' => 'l',
+ '𝕞' => 'm',
+ '𝕟' => 'n',
+ '𝕠' => 'o',
+ '𝕡' => 'p',
+ '𝕢' => 'q',
+ '𝕣' => 'r',
+ '𝕤' => 's',
+ '𝕥' => 't',
+ '𝕦' => 'u',
+ '𝕧' => 'v',
+ '𝕨' => 'w',
+ '𝕩' => 'x',
+ '𝕪' => 'y',
+ '𝕫' => 'z',
+ '𝕬' => 'A',
+ '𝕭' => 'B',
+ '𝕮' => 'C',
+ '𝕯' => 'D',
+ '𝕰' => 'E',
+ '𝕱' => 'F',
+ '𝕲' => 'G',
+ '𝕳' => 'H',
+ '𝕴' => 'I',
+ '𝕵' => 'J',
+ '𝕶' => 'K',
+ '𝕷' => 'L',
+ '𝕸' => 'M',
+ '𝕹' => 'N',
+ '𝕺' => 'O',
+ '𝕻' => 'P',
+ '𝕼' => 'Q',
+ '𝕽' => 'R',
+ '𝕾' => 'S',
+ '𝕿' => 'T',
+ '𝖀' => 'U',
+ '𝖁' => 'V',
+ '𝖂' => 'W',
+ '𝖃' => 'X',
+ '𝖄' => 'Y',
+ '𝖅' => 'Z',
+ '𝖆' => 'a',
+ '𝖇' => 'b',
+ '𝖈' => 'c',
+ '𝖉' => 'd',
+ '𝖊' => 'e',
+ '𝖋' => 'f',
+ '𝖌' => 'g',
+ '𝖍' => 'h',
+ '𝖎' => 'i',
+ '𝖏' => 'j',
+ '𝖐' => 'k',
+ '𝖑' => 'l',
+ '𝖒' => 'm',
+ '𝖓' => 'n',
+ '𝖔' => 'o',
+ '𝖕' => 'p',
+ '𝖖' => 'q',
+ '𝖗' => 'r',
+ '𝖘' => 's',
+ '𝖙' => 't',
+ '𝖚' => 'u',
+ '𝖛' => 'v',
+ '𝖜' => 'w',
+ '𝖝' => 'x',
+ '𝖞' => 'y',
+ '𝖟' => 'z',
+ '𝖠' => 'A',
+ '𝖡' => 'B',
+ '𝖢' => 'C',
+ '𝖣' => 'D',
+ '𝖤' => 'E',
+ '𝖥' => 'F',
+ '𝖦' => 'G',
+ '𝖧' => 'H',
+ '𝖨' => 'I',
+ '𝖩' => 'J',
+ '𝖪' => 'K',
+ '𝖫' => 'L',
+ '𝖬' => 'M',
+ '𝖭' => 'N',
+ '𝖮' => 'O',
+ '𝖯' => 'P',
+ '𝖰' => 'Q',
+ '𝖱' => 'R',
+ '𝖲' => 'S',
+ '𝖳' => 'T',
+ '𝖴' => 'U',
+ '𝖵' => 'V',
+ '𝖶' => 'W',
+ '𝖷' => 'X',
+ '𝖸' => 'Y',
+ '𝖹' => 'Z',
+ '𝖺' => 'a',
+ '𝖻' => 'b',
+ '𝖼' => 'c',
+ '𝖽' => 'd',
+ '𝖾' => 'e',
+ '𝖿' => 'f',
+ '𝗀' => 'g',
+ '𝗁' => 'h',
+ '𝗂' => 'i',
+ '𝗃' => 'j',
+ '𝗄' => 'k',
+ '𝗅' => 'l',
+ '𝗆' => 'm',
+ '𝗇' => 'n',
+ '𝗈' => 'o',
+ '𝗉' => 'p',
+ '𝗊' => 'q',
+ '𝗋' => 'r',
+ '𝗌' => 's',
+ '𝗍' => 't',
+ '𝗎' => 'u',
+ '𝗏' => 'v',
+ '𝗐' => 'w',
+ '𝗑' => 'x',
+ '𝗒' => 'y',
+ '𝗓' => 'z',
+ '𝗔' => 'A',
+ '𝗕' => 'B',
+ '𝗖' => 'C',
+ '𝗗' => 'D',
+ '𝗘' => 'E',
+ '𝗙' => 'F',
+ '𝗚' => 'G',
+ '𝗛' => 'H',
+ '𝗜' => 'I',
+ '𝗝' => 'J',
+ '𝗞' => 'K',
+ '𝗟' => 'L',
+ '𝗠' => 'M',
+ '𝗡' => 'N',
+ '𝗢' => 'O',
+ '𝗣' => 'P',
+ '𝗤' => 'Q',
+ '𝗥' => 'R',
+ '𝗦' => 'S',
+ '𝗧' => 'T',
+ '𝗨' => 'U',
+ '𝗩' => 'V',
+ '𝗪' => 'W',
+ '𝗫' => 'X',
+ '𝗬' => 'Y',
+ '𝗭' => 'Z',
+ '𝗮' => 'a',
+ '𝗯' => 'b',
+ '𝗰' => 'c',
+ '𝗱' => 'd',
+ '𝗲' => 'e',
+ '𝗳' => 'f',
+ '𝗴' => 'g',
+ '𝗵' => 'h',
+ '𝗶' => 'i',
+ '𝗷' => 'j',
+ '𝗸' => 'k',
+ '𝗹' => 'l',
+ '𝗺' => 'm',
+ '𝗻' => 'n',
+ '𝗼' => 'o',
+ '𝗽' => 'p',
+ '𝗾' => 'q',
+ '𝗿' => 'r',
+ '𝘀' => 's',
+ '𝘁' => 't',
+ '𝘂' => 'u',
+ '𝘃' => 'v',
+ '𝘄' => 'w',
+ '𝘅' => 'x',
+ '𝘆' => 'y',
+ '𝘇' => 'z',
+ '𝘈' => 'A',
+ '𝘉' => 'B',
+ '𝘊' => 'C',
+ '𝘋' => 'D',
+ '𝘌' => 'E',
+ '𝘍' => 'F',
+ '𝘎' => 'G',
+ '𝘏' => 'H',
+ '𝘐' => 'I',
+ '𝘑' => 'J',
+ '𝘒' => 'K',
+ '𝘓' => 'L',
+ '𝘔' => 'M',
+ '𝘕' => 'N',
+ '𝘖' => 'O',
+ '𝘗' => 'P',
+ '𝘘' => 'Q',
+ '𝘙' => 'R',
+ '𝘚' => 'S',
+ '𝘛' => 'T',
+ '𝘜' => 'U',
+ '𝘝' => 'V',
+ '𝘞' => 'W',
+ '𝘟' => 'X',
+ '𝘠' => 'Y',
+ '𝘡' => 'Z',
+ '𝘢' => 'a',
+ '𝘣' => 'b',
+ '𝘤' => 'c',
+ '𝘥' => 'd',
+ '𝘦' => 'e',
+ '𝘧' => 'f',
+ '𝘨' => 'g',
+ '𝘩' => 'h',
+ '𝘪' => 'i',
+ '𝘫' => 'j',
+ '𝘬' => 'k',
+ '𝘭' => 'l',
+ '𝘮' => 'm',
+ '𝘯' => 'n',
+ '𝘰' => 'o',
+ '𝘱' => 'p',
+ '𝘲' => 'q',
+ '𝘳' => 'r',
+ '𝘴' => 's',
+ '𝘵' => 't',
+ '𝘶' => 'u',
+ '𝘷' => 'v',
+ '𝘸' => 'w',
+ '𝘹' => 'x',
+ '𝘺' => 'y',
+ '𝘻' => 'z',
+ '𝘼' => 'A',
+ '𝘽' => 'B',
+ '𝘾' => 'C',
+ '𝘿' => 'D',
+ '𝙀' => 'E',
+ '𝙁' => 'F',
+ '𝙂' => 'G',
+ '𝙃' => 'H',
+ '𝙄' => 'I',
+ '𝙅' => 'J',
+ '𝙆' => 'K',
+ '𝙇' => 'L',
+ '𝙈' => 'M',
+ '𝙉' => 'N',
+ '𝙊' => 'O',
+ '𝙋' => 'P',
+ '𝙌' => 'Q',
+ '𝙍' => 'R',
+ '𝙎' => 'S',
+ '𝙏' => 'T',
+ '𝙐' => 'U',
+ '𝙑' => 'V',
+ '𝙒' => 'W',
+ '𝙓' => 'X',
+ '𝙔' => 'Y',
+ '𝙕' => 'Z',
+ '𝙖' => 'a',
+ '𝙗' => 'b',
+ '𝙘' => 'c',
+ '𝙙' => 'd',
+ '𝙚' => 'e',
+ '𝙛' => 'f',
+ '𝙜' => 'g',
+ '𝙝' => 'h',
+ '𝙞' => 'i',
+ '𝙟' => 'j',
+ '𝙠' => 'k',
+ '𝙡' => 'l',
+ '𝙢' => 'm',
+ '𝙣' => 'n',
+ '𝙤' => 'o',
+ '𝙥' => 'p',
+ '𝙦' => 'q',
+ '𝙧' => 'r',
+ '𝙨' => 's',
+ '𝙩' => 't',
+ '𝙪' => 'u',
+ '𝙫' => 'v',
+ '𝙬' => 'w',
+ '𝙭' => 'x',
+ '𝙮' => 'y',
+ '𝙯' => 'z',
+ '𝙰' => 'A',
+ '𝙱' => 'B',
+ '𝙲' => 'C',
+ '𝙳' => 'D',
+ '𝙴' => 'E',
+ '𝙵' => 'F',
+ '𝙶' => 'G',
+ '𝙷' => 'H',
+ '𝙸' => 'I',
+ '𝙹' => 'J',
+ '𝙺' => 'K',
+ '𝙻' => 'L',
+ '𝙼' => 'M',
+ '𝙽' => 'N',
+ '𝙾' => 'O',
+ '𝙿' => 'P',
+ '𝚀' => 'Q',
+ '𝚁' => 'R',
+ '𝚂' => 'S',
+ '𝚃' => 'T',
+ '𝚄' => 'U',
+ '𝚅' => 'V',
+ '𝚆' => 'W',
+ '𝚇' => 'X',
+ '𝚈' => 'Y',
+ '𝚉' => 'Z',
+ '𝚊' => 'a',
+ '𝚋' => 'b',
+ '𝚌' => 'c',
+ '𝚍' => 'd',
+ '𝚎' => 'e',
+ '𝚏' => 'f',
+ '𝚐' => 'g',
+ '𝚑' => 'h',
+ '𝚒' => 'i',
+ '𝚓' => 'j',
+ '𝚔' => 'k',
+ '𝚕' => 'l',
+ '𝚖' => 'm',
+ '𝚗' => 'n',
+ '𝚘' => 'o',
+ '𝚙' => 'p',
+ '𝚚' => 'q',
+ '𝚛' => 'r',
+ '𝚜' => 's',
+ '𝚝' => 't',
+ '𝚞' => 'u',
+ '𝚟' => 'v',
+ '𝚠' => 'w',
+ '𝚡' => 'x',
+ '𝚢' => 'y',
+ '𝚣' => 'z',
+ '𝚤' => 'ı',
+ '𝚥' => 'ȷ',
+ '𝚨' => 'Α',
+ '𝚩' => 'Β',
+ '𝚪' => 'Γ',
+ '𝚫' => 'Δ',
+ '𝚬' => 'Ε',
+ '𝚭' => 'Ζ',
+ '𝚮' => 'Η',
+ '𝚯' => 'Θ',
+ '𝚰' => 'Ι',
+ '𝚱' => 'Κ',
+ '𝚲' => 'Λ',
+ '𝚳' => 'Μ',
+ '𝚴' => 'Ν',
+ '𝚵' => 'Ξ',
+ '𝚶' => 'Ο',
+ '𝚷' => 'Π',
+ '𝚸' => 'Ρ',
+ '𝚹' => 'Θ',
+ '𝚺' => 'Σ',
+ '𝚻' => 'Τ',
+ '𝚼' => 'Υ',
+ '𝚽' => 'Φ',
+ '𝚾' => 'Χ',
+ '𝚿' => 'Ψ',
+ '𝛀' => 'Ω',
+ '𝛁' => '∇',
+ '𝛂' => 'α',
+ '𝛃' => 'β',
+ '𝛄' => 'γ',
+ '𝛅' => 'δ',
+ '𝛆' => 'ε',
+ '𝛇' => 'ζ',
+ '𝛈' => 'η',
+ '𝛉' => 'θ',
+ '𝛊' => 'ι',
+ '𝛋' => 'κ',
+ '𝛌' => 'λ',
+ '𝛍' => 'μ',
+ '𝛎' => 'ν',
+ '𝛏' => 'ξ',
+ '𝛐' => 'ο',
+ '𝛑' => 'π',
+ '𝛒' => 'ρ',
+ '𝛓' => 'ς',
+ '𝛔' => 'σ',
+ '𝛕' => 'τ',
+ '𝛖' => 'υ',
+ '𝛗' => 'φ',
+ '𝛘' => 'χ',
+ '𝛙' => 'ψ',
+ '𝛚' => 'ω',
+ '𝛛' => '∂',
+ '𝛜' => 'ε',
+ '𝛝' => 'θ',
+ '𝛞' => 'κ',
+ '𝛟' => 'φ',
+ '𝛠' => 'ρ',
+ '𝛡' => 'π',
+ '𝛢' => 'Α',
+ '𝛣' => 'Β',
+ '𝛤' => 'Γ',
+ '𝛥' => 'Δ',
+ '𝛦' => 'Ε',
+ '𝛧' => 'Ζ',
+ '𝛨' => 'Η',
+ '𝛩' => 'Θ',
+ '𝛪' => 'Ι',
+ '𝛫' => 'Κ',
+ '𝛬' => 'Λ',
+ '𝛭' => 'Μ',
+ '𝛮' => 'Ν',
+ '𝛯' => 'Ξ',
+ '𝛰' => 'Ο',
+ '𝛱' => 'Π',
+ '𝛲' => 'Ρ',
+ '𝛳' => 'Θ',
+ '𝛴' => 'Σ',
+ '𝛵' => 'Τ',
+ '𝛶' => 'Υ',
+ '𝛷' => 'Φ',
+ '𝛸' => 'Χ',
+ '𝛹' => 'Ψ',
+ '𝛺' => 'Ω',
+ '𝛻' => '∇',
+ '𝛼' => 'α',
+ '𝛽' => 'β',
+ '𝛾' => 'γ',
+ '𝛿' => 'δ',
+ '𝜀' => 'ε',
+ '𝜁' => 'ζ',
+ '𝜂' => 'η',
+ '𝜃' => 'θ',
+ '𝜄' => 'ι',
+ '𝜅' => 'κ',
+ '𝜆' => 'λ',
+ '𝜇' => 'μ',
+ '𝜈' => 'ν',
+ '𝜉' => 'ξ',
+ '𝜊' => 'ο',
+ '𝜋' => 'π',
+ '𝜌' => 'ρ',
+ '𝜍' => 'ς',
+ '𝜎' => 'σ',
+ '𝜏' => 'τ',
+ '𝜐' => 'υ',
+ '𝜑' => 'φ',
+ '𝜒' => 'χ',
+ '𝜓' => 'ψ',
+ '𝜔' => 'ω',
+ '𝜕' => '∂',
+ '𝜖' => 'ε',
+ '𝜗' => 'θ',
+ '𝜘' => 'κ',
+ '𝜙' => 'φ',
+ '𝜚' => 'ρ',
+ '𝜛' => 'π',
+ '𝜜' => 'Α',
+ '𝜝' => 'Β',
+ '𝜞' => 'Γ',
+ '𝜟' => 'Δ',
+ '𝜠' => 'Ε',
+ '𝜡' => 'Ζ',
+ '𝜢' => 'Η',
+ '𝜣' => 'Θ',
+ '𝜤' => 'Ι',
+ '𝜥' => 'Κ',
+ '𝜦' => 'Λ',
+ '𝜧' => 'Μ',
+ '𝜨' => 'Ν',
+ '𝜩' => 'Ξ',
+ '𝜪' => 'Ο',
+ '𝜫' => 'Π',
+ '𝜬' => 'Ρ',
+ '𝜭' => 'Θ',
+ '𝜮' => 'Σ',
+ '𝜯' => 'Τ',
+ '𝜰' => 'Υ',
+ '𝜱' => 'Φ',
+ '𝜲' => 'Χ',
+ '𝜳' => 'Ψ',
+ '𝜴' => 'Ω',
+ '𝜵' => '∇',
+ '𝜶' => 'α',
+ '𝜷' => 'β',
+ '𝜸' => 'γ',
+ '𝜹' => 'δ',
+ '𝜺' => 'ε',
+ '𝜻' => 'ζ',
+ '𝜼' => 'η',
+ '𝜽' => 'θ',
+ '𝜾' => 'ι',
+ '𝜿' => 'κ',
+ '𝝀' => 'λ',
+ '𝝁' => 'μ',
+ '𝝂' => 'ν',
+ '𝝃' => 'ξ',
+ '𝝄' => 'ο',
+ '𝝅' => 'π',
+ '𝝆' => 'ρ',
+ '𝝇' => 'ς',
+ '𝝈' => 'σ',
+ '𝝉' => 'τ',
+ '𝝊' => 'υ',
+ '𝝋' => 'φ',
+ '𝝌' => 'χ',
+ '𝝍' => 'ψ',
+ '𝝎' => 'ω',
+ '𝝏' => '∂',
+ '𝝐' => 'ε',
+ '𝝑' => 'θ',
+ '𝝒' => 'κ',
+ '𝝓' => 'φ',
+ '𝝔' => 'ρ',
+ '𝝕' => 'π',
+ '𝝖' => 'Α',
+ '𝝗' => 'Β',
+ '𝝘' => 'Γ',
+ '𝝙' => 'Δ',
+ '𝝚' => 'Ε',
+ '𝝛' => 'Ζ',
+ '𝝜' => 'Η',
+ '𝝝' => 'Θ',
+ '𝝞' => 'Ι',
+ '𝝟' => 'Κ',
+ '𝝠' => 'Λ',
+ '𝝡' => 'Μ',
+ '𝝢' => 'Ν',
+ '𝝣' => 'Ξ',
+ '𝝤' => 'Ο',
+ '𝝥' => 'Π',
+ '𝝦' => 'Ρ',
+ '𝝧' => 'Θ',
+ '𝝨' => 'Σ',
+ '𝝩' => 'Τ',
+ '𝝪' => 'Υ',
+ '𝝫' => 'Φ',
+ '𝝬' => 'Χ',
+ '𝝭' => 'Ψ',
+ '𝝮' => 'Ω',
+ '𝝯' => '∇',
+ '𝝰' => 'α',
+ '𝝱' => 'β',
+ '𝝲' => 'γ',
+ '𝝳' => 'δ',
+ '𝝴' => 'ε',
+ '𝝵' => 'ζ',
+ '𝝶' => 'η',
+ '𝝷' => 'θ',
+ '𝝸' => 'ι',
+ '𝝹' => 'κ',
+ '𝝺' => 'λ',
+ '𝝻' => 'μ',
+ '𝝼' => 'ν',
+ '𝝽' => 'ξ',
+ '𝝾' => 'ο',
+ '𝝿' => 'π',
+ '𝞀' => 'ρ',
+ '𝞁' => 'ς',
+ '𝞂' => 'σ',
+ '𝞃' => 'τ',
+ '𝞄' => 'υ',
+ '𝞅' => 'φ',
+ '𝞆' => 'χ',
+ '𝞇' => 'ψ',
+ '𝞈' => 'ω',
+ '𝞉' => '∂',
+ '𝞊' => 'ε',
+ '𝞋' => 'θ',
+ '𝞌' => 'κ',
+ '𝞍' => 'φ',
+ '𝞎' => 'ρ',
+ '𝞏' => 'π',
+ '𝞐' => 'Α',
+ '𝞑' => 'Β',
+ '𝞒' => 'Γ',
+ '𝞓' => 'Δ',
+ '𝞔' => 'Ε',
+ '𝞕' => 'Ζ',
+ '𝞖' => 'Η',
+ '𝞗' => 'Θ',
+ '𝞘' => 'Ι',
+ '𝞙' => 'Κ',
+ '𝞚' => 'Λ',
+ '𝞛' => 'Μ',
+ '𝞜' => 'Ν',
+ '𝞝' => 'Ξ',
+ '𝞞' => 'Ο',
+ '𝞟' => 'Π',
+ '𝞠' => 'Ρ',
+ '𝞡' => 'Θ',
+ '𝞢' => 'Σ',
+ '𝞣' => 'Τ',
+ '𝞤' => 'Υ',
+ '𝞥' => 'Φ',
+ '𝞦' => 'Χ',
+ '𝞧' => 'Ψ',
+ '𝞨' => 'Ω',
+ '𝞩' => '∇',
+ '𝞪' => 'α',
+ '𝞫' => 'β',
+ '𝞬' => 'γ',
+ '𝞭' => 'δ',
+ '𝞮' => 'ε',
+ '𝞯' => 'ζ',
+ '𝞰' => 'η',
+ '𝞱' => 'θ',
+ '𝞲' => 'ι',
+ '𝞳' => 'κ',
+ '𝞴' => 'λ',
+ '𝞵' => 'μ',
+ '𝞶' => 'ν',
+ '𝞷' => 'ξ',
+ '𝞸' => 'ο',
+ '𝞹' => 'π',
+ '𝞺' => 'ρ',
+ '𝞻' => 'ς',
+ '𝞼' => 'σ',
+ '𝞽' => 'τ',
+ '𝞾' => 'υ',
+ '𝞿' => 'φ',
+ '𝟀' => 'χ',
+ '𝟁' => 'ψ',
+ '𝟂' => 'ω',
+ '𝟃' => '∂',
+ '𝟄' => 'ε',
+ '𝟅' => 'θ',
+ '𝟆' => 'κ',
+ '𝟇' => 'φ',
+ '𝟈' => 'ρ',
+ '𝟉' => 'π',
+ '𝟊' => 'Ϝ',
+ '𝟋' => 'ϝ',
+ '𝟎' => '0',
+ '𝟏' => '1',
+ '𝟐' => '2',
+ '𝟑' => '3',
+ '𝟒' => '4',
+ '𝟓' => '5',
+ '𝟔' => '6',
+ '𝟕' => '7',
+ '𝟖' => '8',
+ '𝟗' => '9',
+ '𝟘' => '0',
+ '𝟙' => '1',
+ '𝟚' => '2',
+ '𝟛' => '3',
+ '𝟜' => '4',
+ '𝟝' => '5',
+ '𝟞' => '6',
+ '𝟟' => '7',
+ '𝟠' => '8',
+ '𝟡' => '9',
+ '𝟢' => '0',
+ '𝟣' => '1',
+ '𝟤' => '2',
+ '𝟥' => '3',
+ '𝟦' => '4',
+ '𝟧' => '5',
+ '𝟨' => '6',
+ '𝟩' => '7',
+ '𝟪' => '8',
+ '𝟫' => '9',
+ '𝟬' => '0',
+ '𝟭' => '1',
+ '𝟮' => '2',
+ '𝟯' => '3',
+ '𝟰' => '4',
+ '𝟱' => '5',
+ '𝟲' => '6',
+ '𝟳' => '7',
+ '𝟴' => '8',
+ '𝟵' => '9',
+ '𝟶' => '0',
+ '𝟷' => '1',
+ '𝟸' => '2',
+ '𝟹' => '3',
+ '𝟺' => '4',
+ '𝟻' => '5',
+ '𝟼' => '6',
+ '𝟽' => '7',
+ '𝟾' => '8',
+ '𝟿' => '9',
+ '𞸀' => 'ا',
+ '𞸁' => 'ب',
+ '𞸂' => 'ج',
+ '𞸃' => 'د',
+ '𞸅' => 'و',
+ '𞸆' => 'ز',
+ '𞸇' => 'ح',
+ '𞸈' => 'ط',
+ '𞸉' => 'ي',
+ '𞸊' => 'ك',
+ '𞸋' => 'ل',
+ '𞸌' => 'م',
+ '𞸍' => 'ن',
+ '𞸎' => 'س',
+ '𞸏' => 'ع',
+ '𞸐' => 'ف',
+ '𞸑' => 'ص',
+ '𞸒' => 'ق',
+ '𞸓' => 'ر',
+ '𞸔' => 'ش',
+ '𞸕' => 'ت',
+ '𞸖' => 'ث',
+ '𞸗' => 'خ',
+ '𞸘' => 'ذ',
+ '𞸙' => 'ض',
+ '𞸚' => 'ظ',
+ '𞸛' => 'غ',
+ '𞸜' => 'ٮ',
+ '𞸝' => 'ں',
+ '𞸞' => 'ڡ',
+ '𞸟' => 'ٯ',
+ '𞸡' => 'ب',
+ '𞸢' => 'ج',
+ '𞸤' => 'ه',
+ '𞸧' => 'ح',
+ '𞸩' => 'ي',
+ '𞸪' => 'ك',
+ '𞸫' => 'ل',
+ '𞸬' => 'م',
+ '𞸭' => 'ن',
+ '𞸮' => 'س',
+ '𞸯' => 'ع',
+ '𞸰' => 'ف',
+ '𞸱' => 'ص',
+ '𞸲' => 'ق',
+ '𞸴' => 'ش',
+ '𞸵' => 'ت',
+ '𞸶' => 'ث',
+ '𞸷' => 'خ',
+ '𞸹' => 'ض',
+ '𞸻' => 'غ',
+ '𞹂' => 'ج',
+ '𞹇' => 'ح',
+ '𞹉' => 'ي',
+ '𞹋' => 'ل',
+ '𞹍' => 'ن',
+ '𞹎' => 'س',
+ '𞹏' => 'ع',
+ '𞹑' => 'ص',
+ '𞹒' => 'ق',
+ '𞹔' => 'ش',
+ '𞹗' => 'خ',
+ '𞹙' => 'ض',
+ '𞹛' => 'غ',
+ '𞹝' => 'ں',
+ '𞹟' => 'ٯ',
+ '𞹡' => 'ب',
+ '𞹢' => 'ج',
+ '𞹤' => 'ه',
+ '𞹧' => 'ح',
+ '𞹨' => 'ط',
+ '𞹩' => 'ي',
+ '𞹪' => 'ك',
+ '𞹬' => 'م',
+ '𞹭' => 'ن',
+ '𞹮' => 'س',
+ '𞹯' => 'ع',
+ '𞹰' => 'ف',
+ '𞹱' => 'ص',
+ '𞹲' => 'ق',
+ '𞹴' => 'ش',
+ '𞹵' => 'ت',
+ '𞹶' => 'ث',
+ '𞹷' => 'خ',
+ '𞹹' => 'ض',
+ '𞹺' => 'ظ',
+ '𞹻' => 'غ',
+ '𞹼' => 'ٮ',
+ '𞹾' => 'ڡ',
+ '𞺀' => 'ا',
+ '𞺁' => 'ب',
+ '𞺂' => 'ج',
+ '𞺃' => 'د',
+ '𞺄' => 'ه',
+ '𞺅' => 'و',
+ '𞺆' => 'ز',
+ '𞺇' => 'ح',
+ '𞺈' => 'ط',
+ '𞺉' => 'ي',
+ '𞺋' => 'ل',
+ '𞺌' => 'م',
+ '𞺍' => 'ن',
+ '𞺎' => 'س',
+ '𞺏' => 'ع',
+ '𞺐' => 'ف',
+ '𞺑' => 'ص',
+ '𞺒' => 'ق',
+ '𞺓' => 'ر',
+ '𞺔' => 'ش',
+ '𞺕' => 'ت',
+ '𞺖' => 'ث',
+ '𞺗' => 'خ',
+ '𞺘' => 'ذ',
+ '𞺙' => 'ض',
+ '𞺚' => 'ظ',
+ '𞺛' => 'غ',
+ '𞺡' => 'ب',
+ '𞺢' => 'ج',
+ '𞺣' => 'د',
+ '𞺥' => 'و',
+ '𞺦' => 'ز',
+ '𞺧' => 'ح',
+ '𞺨' => 'ط',
+ '𞺩' => 'ي',
+ '𞺫' => 'ل',
+ '𞺬' => 'م',
+ '𞺭' => 'ن',
+ '𞺮' => 'س',
+ '𞺯' => 'ع',
+ '𞺰' => 'ف',
+ '𞺱' => 'ص',
+ '𞺲' => 'ق',
+ '𞺳' => 'ر',
+ '𞺴' => 'ش',
+ '𞺵' => 'ت',
+ '𞺶' => 'ث',
+ '𞺷' => 'خ',
+ '𞺸' => 'ذ',
+ '𞺹' => 'ض',
+ '𞺺' => 'ظ',
+ '𞺻' => 'غ',
+ '🄀' => '0.',
+ '🄁' => '0,',
+ '🄂' => '1,',
+ '🄃' => '2,',
+ '🄄' => '3,',
+ '🄅' => '4,',
+ '🄆' => '5,',
+ '🄇' => '6,',
+ '🄈' => '7,',
+ '🄉' => '8,',
+ '🄊' => '9,',
+ '🄐' => '(A)',
+ '🄑' => '(B)',
+ '🄒' => '(C)',
+ '🄓' => '(D)',
+ '🄔' => '(E)',
+ '🄕' => '(F)',
+ '🄖' => '(G)',
+ '🄗' => '(H)',
+ '🄘' => '(I)',
+ '🄙' => '(J)',
+ '🄚' => '(K)',
+ '🄛' => '(L)',
+ '🄜' => '(M)',
+ '🄝' => '(N)',
+ '🄞' => '(O)',
+ '🄟' => '(P)',
+ '🄠' => '(Q)',
+ '🄡' => '(R)',
+ '🄢' => '(S)',
+ '🄣' => '(T)',
+ '🄤' => '(U)',
+ '🄥' => '(V)',
+ '🄦' => '(W)',
+ '🄧' => '(X)',
+ '🄨' => '(Y)',
+ '🄩' => '(Z)',
+ '🄪' => '〔S〕',
+ '🄫' => 'C',
+ '🄬' => 'R',
+ '🄭' => 'CD',
+ '🄮' => 'WZ',
+ '🄰' => 'A',
+ '🄱' => 'B',
+ '🄲' => 'C',
+ '🄳' => 'D',
+ '🄴' => 'E',
+ '🄵' => 'F',
+ '🄶' => 'G',
+ '🄷' => 'H',
+ '🄸' => 'I',
+ '🄹' => 'J',
+ '🄺' => 'K',
+ '🄻' => 'L',
+ '🄼' => 'M',
+ '🄽' => 'N',
+ '🄾' => 'O',
+ '🄿' => 'P',
+ '🅀' => 'Q',
+ '🅁' => 'R',
+ '🅂' => 'S',
+ '🅃' => 'T',
+ '🅄' => 'U',
+ '🅅' => 'V',
+ '🅆' => 'W',
+ '🅇' => 'X',
+ '🅈' => 'Y',
+ '🅉' => 'Z',
+ '🅊' => 'HV',
+ '🅋' => 'MV',
+ '🅌' => 'SD',
+ '🅍' => 'SS',
+ '🅎' => 'PPV',
+ '🅏' => 'WC',
+ '🅪' => 'MC',
+ '🅫' => 'MD',
+ '🅬' => 'MR',
+ '🆐' => 'DJ',
+ '🈀' => 'ほか',
+ '🈁' => 'ココ',
+ '🈂' => 'サ',
+ '🈐' => '手',
+ '🈑' => '字',
+ '🈒' => '双',
+ '🈓' => 'デ',
+ '🈔' => '二',
+ '🈕' => '多',
+ '🈖' => '解',
+ '🈗' => '天',
+ '🈘' => '交',
+ '🈙' => '映',
+ '🈚' => '無',
+ '🈛' => '料',
+ '🈜' => '前',
+ '🈝' => '後',
+ '🈞' => '再',
+ '🈟' => '新',
+ '🈠' => '初',
+ '🈡' => '終',
+ '🈢' => '生',
+ '🈣' => '販',
+ '🈤' => '声',
+ '🈥' => '吹',
+ '🈦' => '演',
+ '🈧' => '投',
+ '🈨' => '捕',
+ '🈩' => '一',
+ '🈪' => '三',
+ '🈫' => '遊',
+ '🈬' => '左',
+ '🈭' => '中',
+ '🈮' => '右',
+ '🈯' => '指',
+ '🈰' => '走',
+ '🈱' => '打',
+ '🈲' => '禁',
+ '🈳' => '空',
+ '🈴' => '合',
+ '🈵' => '満',
+ '🈶' => '有',
+ '🈷' => '月',
+ '🈸' => '申',
+ '🈹' => '割',
+ '🈺' => '営',
+ '🈻' => '配',
+ '🉀' => '〔本〕',
+ '🉁' => '〔三〕',
+ '🉂' => '〔二〕',
+ '🉃' => '〔安〕',
+ '🉄' => '〔点〕',
+ '🉅' => '〔打〕',
+ '🉆' => '〔盗〕',
+ '🉇' => '〔勝〕',
+ '🉈' => '〔敗〕',
+ '🉐' => '得',
+ '🉑' => '可',
+ '🯰' => '0',
+ '🯱' => '1',
+ '🯲' => '2',
+ '🯳' => '3',
+ '🯴' => '4',
+ '🯵' => '5',
+ '🯶' => '6',
+ '🯷' => '7',
+ '🯸' => '8',
+ '🯹' => '9',
+);
diff --git a/vendor/symfony/polyfill-intl-normalizer/bootstrap.php b/vendor/symfony/polyfill-intl-normalizer/bootstrap.php
new file mode 100755
index 0000000..ba62006
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-normalizer/bootstrap.php
@@ -0,0 +1,19 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Intl\Normalizer as p;
+
+if (!function_exists('normalizer_is_normalized')) {
+ function normalizer_is_normalized($s, $form = p\Normalizer::NFC) { return p\Normalizer::isNormalized($s, $form); }
+}
+if (!function_exists('normalizer_normalize')) {
+ function normalizer_normalize($s, $form = p\Normalizer::NFC) { return p\Normalizer::normalize($s, $form); }
+}
diff --git a/vendor/symfony/polyfill-intl-normalizer/composer.json b/vendor/symfony/polyfill-intl-normalizer/composer.json
new file mode 100755
index 0000000..b0986a8
--- /dev/null
+++ b/vendor/symfony/polyfill-intl-normalizer/composer.json
@@ -0,0 +1,39 @@
+{
+ "name": "symfony/polyfill-intl-normalizer",
+ "type": "library",
+ "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "normalizer"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Intl\\Normalizer\\": "" },
+ "files": [ "bootstrap.php" ],
+ "classmap": [ "Resources/stubs" ]
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.18-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ }
+}
diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE
new file mode 100755
index 0000000..4cd8bdd
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php
new file mode 100755
index 0000000..a5e4a8f
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/Mbstring.php
@@ -0,0 +1,800 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Mbstring;
+
+/**
+ * Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
+ *
+ * Implemented:
+ * - mb_chr - Returns a specific character from its Unicode code point
+ * - mb_convert_encoding - Convert character encoding
+ * - mb_convert_variables - Convert character code in variable(s)
+ * - mb_decode_mimeheader - Decode string in MIME header field
+ * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
+ * - mb_decode_numericentity - Decode HTML numeric string reference to character
+ * - mb_encode_numericentity - Encode character to HTML numeric string reference
+ * - mb_convert_case - Perform case folding on a string
+ * - mb_detect_encoding - Detect character encoding
+ * - mb_get_info - Get internal settings of mbstring
+ * - mb_http_input - Detect HTTP input character encoding
+ * - mb_http_output - Set/Get HTTP output character encoding
+ * - mb_internal_encoding - Set/Get internal character encoding
+ * - mb_list_encodings - Returns an array of all supported encodings
+ * - mb_ord - Returns the Unicode code point of a character
+ * - mb_output_handler - Callback function converts character encoding in output buffer
+ * - mb_scrub - Replaces ill-formed byte sequences with substitute characters
+ * - mb_strlen - Get string length
+ * - mb_strpos - Find position of first occurrence of string in a string
+ * - mb_strrpos - Find position of last occurrence of a string in a string
+ * - mb_strtolower - Make a string lowercase
+ * - mb_strtoupper - Make a string uppercase
+ * - mb_substitute_character - Set/Get substitution character
+ * - mb_substr - Get part of string
+ * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive
+ * - mb_stristr - Finds first occurrence of a string within another, case insensitive
+ * - mb_strrchr - Finds the last occurrence of a character in a string within another
+ * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive
+ * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive
+ * - mb_strstr - Finds first occurrence of a string within another
+ * - mb_strwidth - Return width of string
+ * - mb_substr_count - Count the number of substring occurrences
+ *
+ * Not implemented:
+ * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
+ * - mb_ereg_* - Regular expression with multibyte support
+ * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable
+ * - mb_preferred_mime_name - Get MIME charset string
+ * - mb_regex_encoding - Returns current encoding for multibyte regex as string
+ * - mb_regex_set_options - Set/Get the default options for mbregex functions
+ * - mb_send_mail - Send encoded mail
+ * - mb_split - Split multibyte string using regular expression
+ * - mb_strcut - Get part of string
+ * - mb_strimwidth - Get truncated string with specified width
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+final class Mbstring
+{
+ const MB_CASE_FOLD = PHP_INT_MAX;
+
+ private static $encodingList = array('ASCII', 'UTF-8');
+ private static $language = 'neutral';
+ private static $internalEncoding = 'UTF-8';
+ private static $caseFold = array(
+ array('µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"),
+ array('μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'),
+ );
+
+ public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
+ {
+ if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) {
+ $fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
+ } else {
+ $fromEncoding = self::getEncoding($fromEncoding);
+ }
+
+ $toEncoding = self::getEncoding($toEncoding);
+
+ if ('BASE64' === $fromEncoding) {
+ $s = base64_decode($s);
+ $fromEncoding = $toEncoding;
+ }
+
+ if ('BASE64' === $toEncoding) {
+ return base64_encode($s);
+ }
+
+ if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
+ if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
+ $fromEncoding = 'Windows-1252';
+ }
+ if ('UTF-8' !== $fromEncoding) {
+ $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
+ }
+
+ return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s);
+ }
+
+ if ('HTML-ENTITIES' === $fromEncoding) {
+ $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8');
+ $fromEncoding = 'UTF-8';
+ }
+
+ return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
+ }
+
+ public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null)
+ {
+ $vars = array(&$a, &$b, &$c, &$d, &$e, &$f);
+
+ $ok = true;
+ array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
+ if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
+ $ok = false;
+ }
+ });
+
+ return $ok ? $fromEncoding : false;
+ }
+
+ public static function mb_decode_mimeheader($s)
+ {
+ return iconv_mime_decode($s, 2, self::$internalEncoding);
+ }
+
+ public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
+ {
+ trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING);
+ }
+
+ public static function mb_decode_numericentity($s, $convmap, $encoding = null)
+ {
+ if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) {
+ trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING);
+
+ return null;
+ }
+
+ if (!\is_array($convmap) || !$convmap) {
+ return false;
+ }
+
+ if (null !== $encoding && !\is_scalar($encoding)) {
+ trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING);
+
+ return ''; // Instead of null (cf. mb_encode_numericentity).
+ }
+
+ $s = (string) $s;
+ if ('' === $s) {
+ return '';
+ }
+
+ $encoding = self::getEncoding($encoding);
+
+ if ('UTF-8' === $encoding) {
+ $encoding = null;
+ if (!preg_match('//u', $s)) {
+ $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+ }
+ } else {
+ $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+ }
+
+ $cnt = floor(\count($convmap) / 4) * 4;
+
+ for ($i = 0; $i < $cnt; $i += 4) {
+ // collector_decode_htmlnumericentity ignores $convmap[$i + 3]
+ $convmap[$i] += $convmap[$i + 2];
+ $convmap[$i + 1] += $convmap[$i + 2];
+ }
+
+ $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
+ $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
+ for ($i = 0; $i < $cnt; $i += 4) {
+ if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
+ return Mbstring::mb_chr($c - $convmap[$i + 2]);
+ }
+ }
+
+ return $m[0];
+ }, $s);
+
+ if (null === $encoding) {
+ return $s;
+ }
+
+ return iconv('UTF-8', $encoding.'//IGNORE', $s);
+ }
+
+ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
+ {
+ if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) {
+ trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING);
+
+ return null;
+ }
+
+ if (!\is_array($convmap) || !$convmap) {
+ return false;
+ }
+
+ if (null !== $encoding && !\is_scalar($encoding)) {
+ trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING);
+
+ return null; // Instead of '' (cf. mb_decode_numericentity).
+ }
+
+ if (null !== $is_hex && !\is_scalar($is_hex)) {
+ trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', E_USER_WARNING);
+
+ return null;
+ }
+
+ $s = (string) $s;
+ if ('' === $s) {
+ return '';
+ }
+
+ $encoding = self::getEncoding($encoding);
+
+ if ('UTF-8' === $encoding) {
+ $encoding = null;
+ if (!preg_match('//u', $s)) {
+ $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+ }
+ } else {
+ $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+ }
+
+ static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);
+
+ $cnt = floor(\count($convmap) / 4) * 4;
+ $i = 0;
+ $len = \strlen($s);
+ $result = '';
+
+ while ($i < $len) {
+ $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
+ $uchr = substr($s, $i, $ulen);
+ $i += $ulen;
+ $c = self::mb_ord($uchr);
+
+ for ($j = 0; $j < $cnt; $j += 4) {
+ if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
+ $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
+ $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
+ continue 2;
+ }
+ }
+ $result .= $uchr;
+ }
+
+ if (null === $encoding) {
+ return $result;
+ }
+
+ return iconv('UTF-8', $encoding.'//IGNORE', $result);
+ }
+
+ public static function mb_convert_case($s, $mode, $encoding = null)
+ {
+ $s = (string) $s;
+ if ('' === $s) {
+ return '';
+ }
+
+ $encoding = self::getEncoding($encoding);
+
+ if ('UTF-8' === $encoding) {
+ $encoding = null;
+ if (!preg_match('//u', $s)) {
+ $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
+ }
+ } else {
+ $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+ }
+
+ if (MB_CASE_TITLE == $mode) {
+ static $titleRegexp = null;
+ if (null === $titleRegexp) {
+ $titleRegexp = self::getData('titleCaseRegexp');
+ }
+ $s = preg_replace_callback($titleRegexp, array(__CLASS__, 'title_case'), $s);
+ } else {
+ if (MB_CASE_UPPER == $mode) {
+ static $upper = null;
+ if (null === $upper) {
+ $upper = self::getData('upperCase');
+ }
+ $map = $upper;
+ } else {
+ if (self::MB_CASE_FOLD === $mode) {
+ $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s);
+ }
+
+ static $lower = null;
+ if (null === $lower) {
+ $lower = self::getData('lowerCase');
+ }
+ $map = $lower;
+ }
+
+ static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);
+
+ $i = 0;
+ $len = \strlen($s);
+
+ while ($i < $len) {
+ $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
+ $uchr = substr($s, $i, $ulen);
+ $i += $ulen;
+
+ if (isset($map[$uchr])) {
+ $uchr = $map[$uchr];
+ $nlen = \strlen($uchr);
+
+ if ($nlen == $ulen) {
+ $nlen = $i;
+ do {
+ $s[--$nlen] = $uchr[--$ulen];
+ } while ($ulen);
+ } else {
+ $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
+ $len += $nlen - $ulen;
+ $i += $nlen - $ulen;
+ }
+ }
+ }
+ }
+
+ if (null === $encoding) {
+ return $s;
+ }
+
+ return iconv('UTF-8', $encoding.'//IGNORE', $s);
+ }
+
+ public static function mb_internal_encoding($encoding = null)
+ {
+ if (null === $encoding) {
+ return self::$internalEncoding;
+ }
+
+ $encoding = self::getEncoding($encoding);
+
+ if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) {
+ self::$internalEncoding = $encoding;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public static function mb_language($lang = null)
+ {
+ if (null === $lang) {
+ return self::$language;
+ }
+
+ switch ($lang = strtolower($lang)) {
+ case 'uni':
+ case 'neutral':
+ self::$language = $lang;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public static function mb_list_encodings()
+ {
+ return array('UTF-8');
+ }
+
+ public static function mb_encoding_aliases($encoding)
+ {
+ switch (strtoupper($encoding)) {
+ case 'UTF8':
+ case 'UTF-8':
+ return array('utf8');
+ }
+
+ return false;
+ }
+
+ public static function mb_check_encoding($var = null, $encoding = null)
+ {
+ if (null === $encoding) {
+ if (null === $var) {
+ return false;
+ }
+ $encoding = self::$internalEncoding;
+ }
+
+ return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var);
+ }
+
+ public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
+ {
+ if (null === $encodingList) {
+ $encodingList = self::$encodingList;
+ } else {
+ if (!\is_array($encodingList)) {
+ $encodingList = array_map('trim', explode(',', $encodingList));
+ }
+ $encodingList = array_map('strtoupper', $encodingList);
+ }
+
+ foreach ($encodingList as $enc) {
+ switch ($enc) {
+ case 'ASCII':
+ if (!preg_match('/[\x80-\xFF]/', $str)) {
+ return $enc;
+ }
+ break;
+
+ case 'UTF8':
+ case 'UTF-8':
+ if (preg_match('//u', $str)) {
+ return 'UTF-8';
+ }
+ break;
+
+ default:
+ if (0 === strncmp($enc, 'ISO-8859-', 9)) {
+ return $enc;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public static function mb_detect_order($encodingList = null)
+ {
+ if (null === $encodingList) {
+ return self::$encodingList;
+ }
+
+ if (!\is_array($encodingList)) {
+ $encodingList = array_map('trim', explode(',', $encodingList));
+ }
+ $encodingList = array_map('strtoupper', $encodingList);
+
+ foreach ($encodingList as $enc) {
+ switch ($enc) {
+ default:
+ if (strncmp($enc, 'ISO-8859-', 9)) {
+ return false;
+ }
+ // no break
+ case 'ASCII':
+ case 'UTF8':
+ case 'UTF-8':
+ }
+ }
+
+ self::$encodingList = $encodingList;
+
+ return true;
+ }
+
+ public static function mb_strlen($s, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ return \strlen($s);
+ }
+
+ return @iconv_strlen($s, $encoding);
+ }
+
+ public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ return strpos($haystack, $needle, $offset);
+ }
+
+ $needle = (string) $needle;
+ if ('' === $needle) {
+ trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING);
+
+ return false;
+ }
+
+ return iconv_strpos($haystack, $needle, $offset, $encoding);
+ }
+
+ public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ return strrpos($haystack, $needle, $offset);
+ }
+
+ if ($offset != (int) $offset) {
+ $offset = 0;
+ } elseif ($offset = (int) $offset) {
+ if ($offset < 0) {
+ $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
+ $offset = 0;
+ } else {
+ $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
+ }
+ }
+
+ $pos = iconv_strrpos($haystack, $needle, $encoding);
+
+ return false !== $pos ? $offset + $pos : false;
+ }
+
+ public static function mb_strtolower($s, $encoding = null)
+ {
+ return self::mb_convert_case($s, MB_CASE_LOWER, $encoding);
+ }
+
+ public static function mb_strtoupper($s, $encoding = null)
+ {
+ return self::mb_convert_case($s, MB_CASE_UPPER, $encoding);
+ }
+
+ public static function mb_substitute_character($c = null)
+ {
+ if (0 === strcasecmp($c, 'none')) {
+ return true;
+ }
+
+ return null !== $c ? false : 'none';
+ }
+
+ public static function mb_substr($s, $start, $length = null, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ return substr($s, $start, null === $length ? 2147483647 : $length);
+ }
+
+ if ($start < 0) {
+ $start = iconv_strlen($s, $encoding) + $start;
+ if ($start < 0) {
+ $start = 0;
+ }
+ }
+
+ if (null === $length) {
+ $length = 2147483647;
+ } elseif ($length < 0) {
+ $length = iconv_strlen($s, $encoding) + $length - $start;
+ if ($length < 0) {
+ return '';
+ }
+ }
+
+ return (string) iconv_substr($s, $start, $length, $encoding);
+ }
+
+ public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
+ {
+ $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
+ $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
+
+ return self::mb_strpos($haystack, $needle, $offset, $encoding);
+ }
+
+ public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
+ {
+ $pos = self::mb_stripos($haystack, $needle, 0, $encoding);
+
+ return self::getSubpart($pos, $part, $haystack, $encoding);
+ }
+
+ public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+ if ('CP850' === $encoding || 'ASCII' === $encoding) {
+ return strrchr($haystack, $needle, $part);
+ }
+ $needle = self::mb_substr($needle, 0, 1, $encoding);
+ $pos = iconv_strrpos($haystack, $needle, $encoding);
+
+ return self::getSubpart($pos, $part, $haystack, $encoding);
+ }
+
+ public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
+ {
+ $needle = self::mb_substr($needle, 0, 1, $encoding);
+ $pos = self::mb_strripos($haystack, $needle, $encoding);
+
+ return self::getSubpart($pos, $part, $haystack, $encoding);
+ }
+
+ public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
+ {
+ $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
+ $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
+
+ return self::mb_strrpos($haystack, $needle, $offset, $encoding);
+ }
+
+ public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
+ {
+ $pos = strpos($haystack, $needle);
+ if (false === $pos) {
+ return false;
+ }
+ if ($part) {
+ return substr($haystack, 0, $pos);
+ }
+
+ return substr($haystack, $pos);
+ }
+
+ public static function mb_get_info($type = 'all')
+ {
+ $info = array(
+ 'internal_encoding' => self::$internalEncoding,
+ 'http_output' => 'pass',
+ 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
+ 'func_overload' => 0,
+ 'func_overload_list' => 'no overload',
+ 'mail_charset' => 'UTF-8',
+ 'mail_header_encoding' => 'BASE64',
+ 'mail_body_encoding' => 'BASE64',
+ 'illegal_chars' => 0,
+ 'encoding_translation' => 'Off',
+ 'language' => self::$language,
+ 'detect_order' => self::$encodingList,
+ 'substitute_character' => 'none',
+ 'strict_detection' => 'Off',
+ );
+
+ if ('all' === $type) {
+ return $info;
+ }
+ if (isset($info[$type])) {
+ return $info[$type];
+ }
+
+ return false;
+ }
+
+ public static function mb_http_input($type = '')
+ {
+ return false;
+ }
+
+ public static function mb_http_output($encoding = null)
+ {
+ return null !== $encoding ? 'pass' === $encoding : 'pass';
+ }
+
+ public static function mb_strwidth($s, $encoding = null)
+ {
+ $encoding = self::getEncoding($encoding);
+
+ if ('UTF-8' !== $encoding) {
+ $s = iconv($encoding, 'UTF-8//IGNORE', $s);
+ }
+
+ $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide);
+
+ return ($wide << 1) + iconv_strlen($s, 'UTF-8');
+ }
+
+ public static function mb_substr_count($haystack, $needle, $encoding = null)
+ {
+ return substr_count($haystack, $needle);
+ }
+
+ public static function mb_output_handler($contents, $status)
+ {
+ return $contents;
+ }
+
+ public static function mb_chr($code, $encoding = null)
+ {
+ if (0x80 > $code %= 0x200000) {
+ $s = \chr($code);
+ } elseif (0x800 > $code) {
+ $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
+ } elseif (0x10000 > $code) {
+ $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+ } else {
+ $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+ }
+
+ if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
+ $s = mb_convert_encoding($s, $encoding, 'UTF-8');
+ }
+
+ return $s;
+ }
+
+ public static function mb_ord($s, $encoding = null)
+ {
+ if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
+ $s = mb_convert_encoding($s, 'UTF-8', $encoding);
+ }
+
+ if (1 === \strlen($s)) {
+ return \ord($s);
+ }
+
+ $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
+ if (0xF0 <= $code) {
+ return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
+ }
+ if (0xE0 <= $code) {
+ return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
+ }
+ if (0xC0 <= $code) {
+ return (($code - 0xC0) << 6) + $s[2] - 0x80;
+ }
+
+ return $code;
+ }
+
+ private static function getSubpart($pos, $part, $haystack, $encoding)
+ {
+ if (false === $pos) {
+ return false;
+ }
+ if ($part) {
+ return self::mb_substr($haystack, 0, $pos, $encoding);
+ }
+
+ return self::mb_substr($haystack, $pos, null, $encoding);
+ }
+
+ private static function html_encoding_callback(array $m)
+ {
+ $i = 1;
+ $entities = '';
+ $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8'));
+
+ while (isset($m[$i])) {
+ if (0x80 > $m[$i]) {
+ $entities .= \chr($m[$i++]);
+ continue;
+ }
+ if (0xF0 <= $m[$i]) {
+ $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
+ } elseif (0xE0 <= $m[$i]) {
+ $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
+ } else {
+ $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
+ }
+
+ $entities .= '&#'.$c.';';
+ }
+
+ return $entities;
+ }
+
+ private static function title_case(array $s)
+ {
+ return self::mb_convert_case($s[1], MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], MB_CASE_LOWER, 'UTF-8');
+ }
+
+ private static function getData($file)
+ {
+ if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
+ return require $file;
+ }
+
+ return false;
+ }
+
+ private static function getEncoding($encoding)
+ {
+ if (null === $encoding) {
+ return self::$internalEncoding;
+ }
+
+ $encoding = strtoupper($encoding);
+
+ if ('8BIT' === $encoding || 'BINARY' === $encoding) {
+ return 'CP850';
+ }
+ if ('UTF8' === $encoding) {
+ return 'UTF-8';
+ }
+
+ return $encoding;
+ }
+}
diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md
new file mode 100755
index 0000000..342e828
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/README.md
@@ -0,0 +1,13 @@
+Symfony Polyfill / Mbstring
+===========================
+
+This component provides a partial, native PHP implementation for the
+[Mbstring](http://php.net/mbstring) extension.
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php
new file mode 100755
index 0000000..e6fbfa6
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php
@@ -0,0 +1,1096 @@
+<?php
+
+return array(
+ 'A' => 'a',
+ 'B' => 'b',
+ 'C' => 'c',
+ 'D' => 'd',
+ 'E' => 'e',
+ 'F' => 'f',
+ 'G' => 'g',
+ 'H' => 'h',
+ 'I' => 'i',
+ 'J' => 'j',
+ 'K' => 'k',
+ 'L' => 'l',
+ 'M' => 'm',
+ 'N' => 'n',
+ 'O' => 'o',
+ 'P' => 'p',
+ 'Q' => 'q',
+ 'R' => 'r',
+ 'S' => 's',
+ 'T' => 't',
+ 'U' => 'u',
+ 'V' => 'v',
+ 'W' => 'w',
+ 'X' => 'x',
+ 'Y' => 'y',
+ 'Z' => 'z',
+ 'À' => 'à',
+ 'Á' => 'á',
+ 'Â' => 'â',
+ 'Ã' => 'ã',
+ 'Ä' => 'ä',
+ 'Å' => 'å',
+ 'Æ' => 'æ',
+ 'Ç' => 'ç',
+ 'È' => 'è',
+ 'É' => 'é',
+ 'Ê' => 'ê',
+ 'Ë' => 'ë',
+ 'Ì' => 'ì',
+ 'Í' => 'í',
+ 'Î' => 'î',
+ 'Ï' => 'ï',
+ 'Ð' => 'ð',
+ 'Ñ' => 'ñ',
+ 'Ò' => 'ò',
+ 'Ó' => 'ó',
+ 'Ô' => 'ô',
+ 'Õ' => 'õ',
+ 'Ö' => 'ö',
+ 'Ø' => 'ø',
+ 'Ù' => 'ù',
+ 'Ú' => 'ú',
+ 'Û' => 'û',
+ 'Ü' => 'ü',
+ 'Ý' => 'ý',
+ 'Þ' => 'þ',
+ 'Ā' => 'ā',
+ 'Ă' => 'ă',
+ 'Ą' => 'ą',
+ 'Ć' => 'ć',
+ 'Ĉ' => 'ĉ',
+ 'Ċ' => 'ċ',
+ 'Č' => 'č',
+ 'Ď' => 'ď',
+ 'Đ' => 'đ',
+ 'Ē' => 'ē',
+ 'Ĕ' => 'ĕ',
+ 'Ė' => 'ė',
+ 'Ę' => 'ę',
+ 'Ě' => 'ě',
+ 'Ĝ' => 'ĝ',
+ 'Ğ' => 'ğ',
+ 'Ġ' => 'ġ',
+ 'Ģ' => 'ģ',
+ 'Ĥ' => 'ĥ',
+ 'Ħ' => 'ħ',
+ 'Ĩ' => 'ĩ',
+ 'Ī' => 'ī',
+ 'Ĭ' => 'ĭ',
+ 'Į' => 'į',
+ 'İ' => 'i',
+ 'IJ' => 'ij',
+ 'Ĵ' => 'ĵ',
+ 'Ķ' => 'ķ',
+ 'Ĺ' => 'ĺ',
+ 'Ļ' => 'ļ',
+ 'Ľ' => 'ľ',
+ 'Ŀ' => 'ŀ',
+ 'Ł' => 'ł',
+ 'Ń' => 'ń',
+ 'Ņ' => 'ņ',
+ 'Ň' => 'ň',
+ 'Ŋ' => 'ŋ',
+ 'Ō' => 'ō',
+ 'Ŏ' => 'ŏ',
+ 'Ő' => 'ő',
+ 'Œ' => 'œ',
+ 'Ŕ' => 'ŕ',
+ 'Ŗ' => 'ŗ',
+ 'Ř' => 'ř',
+ 'Ś' => 'ś',
+ 'Ŝ' => 'ŝ',
+ 'Ş' => 'ş',
+ 'Š' => 'š',
+ 'Ţ' => 'ţ',
+ 'Ť' => 'ť',
+ 'Ŧ' => 'ŧ',
+ 'Ũ' => 'ũ',
+ 'Ū' => 'ū',
+ 'Ŭ' => 'ŭ',
+ 'Ů' => 'ů',
+ 'Ű' => 'ű',
+ 'Ų' => 'ų',
+ 'Ŵ' => 'ŵ',
+ 'Ŷ' => 'ŷ',
+ 'Ÿ' => 'ÿ',
+ 'Ź' => 'ź',
+ 'Ż' => 'ż',
+ 'Ž' => 'ž',
+ 'Ɓ' => 'ɓ',
+ 'Ƃ' => 'ƃ',
+ 'Ƅ' => 'ƅ',
+ 'Ɔ' => 'ɔ',
+ 'Ƈ' => 'ƈ',
+ 'Ɖ' => 'ɖ',
+ 'Ɗ' => 'ɗ',
+ 'Ƌ' => 'ƌ',
+ 'Ǝ' => 'ǝ',
+ 'Ə' => 'ə',
+ 'Ɛ' => 'ɛ',
+ 'Ƒ' => 'ƒ',
+ 'Ɠ' => 'ɠ',
+ 'Ɣ' => 'ɣ',
+ 'Ɩ' => 'ɩ',
+ 'Ɨ' => 'ɨ',
+ 'Ƙ' => 'ƙ',
+ 'Ɯ' => 'ɯ',
+ 'Ɲ' => 'ɲ',
+ 'Ɵ' => 'ɵ',
+ 'Ơ' => 'ơ',
+ 'Ƣ' => 'ƣ',
+ 'Ƥ' => 'ƥ',
+ 'Ʀ' => 'ʀ',
+ 'Ƨ' => 'ƨ',
+ 'Ʃ' => 'ʃ',
+ 'Ƭ' => 'ƭ',
+ 'Ʈ' => 'ʈ',
+ 'Ư' => 'ư',
+ 'Ʊ' => 'ʊ',
+ 'Ʋ' => 'ʋ',
+ 'Ƴ' => 'ƴ',
+ 'Ƶ' => 'ƶ',
+ 'Ʒ' => 'ʒ',
+ 'Ƹ' => 'ƹ',
+ 'Ƽ' => 'ƽ',
+ 'DŽ' => 'dž',
+ 'Dž' => 'dž',
+ 'LJ' => 'lj',
+ 'Lj' => 'lj',
+ 'NJ' => 'nj',
+ 'Nj' => 'nj',
+ 'Ǎ' => 'ǎ',
+ 'Ǐ' => 'ǐ',
+ 'Ǒ' => 'ǒ',
+ 'Ǔ' => 'ǔ',
+ 'Ǖ' => 'ǖ',
+ 'Ǘ' => 'ǘ',
+ 'Ǚ' => 'ǚ',
+ 'Ǜ' => 'ǜ',
+ 'Ǟ' => 'ǟ',
+ 'Ǡ' => 'ǡ',
+ 'Ǣ' => 'ǣ',
+ 'Ǥ' => 'ǥ',
+ 'Ǧ' => 'ǧ',
+ 'Ǩ' => 'ǩ',
+ 'Ǫ' => 'ǫ',
+ 'Ǭ' => 'ǭ',
+ 'Ǯ' => 'ǯ',
+ 'DZ' => 'dz',
+ 'Dz' => 'dz',
+ 'Ǵ' => 'ǵ',
+ 'Ƕ' => 'ƕ',
+ 'Ƿ' => 'ƿ',
+ 'Ǹ' => 'ǹ',
+ 'Ǻ' => 'ǻ',
+ 'Ǽ' => 'ǽ',
+ 'Ǿ' => 'ǿ',
+ 'Ȁ' => 'ȁ',
+ 'Ȃ' => 'ȃ',
+ 'Ȅ' => 'ȅ',
+ 'Ȇ' => 'ȇ',
+ 'Ȉ' => 'ȉ',
+ 'Ȋ' => 'ȋ',
+ 'Ȍ' => 'ȍ',
+ 'Ȏ' => 'ȏ',
+ 'Ȑ' => 'ȑ',
+ 'Ȓ' => 'ȓ',
+ 'Ȕ' => 'ȕ',
+ 'Ȗ' => 'ȗ',
+ 'Ș' => 'ș',
+ 'Ț' => 'ț',
+ 'Ȝ' => 'ȝ',
+ 'Ȟ' => 'ȟ',
+ 'Ƞ' => 'ƞ',
+ 'Ȣ' => 'ȣ',
+ 'Ȥ' => 'ȥ',
+ 'Ȧ' => 'ȧ',
+ 'Ȩ' => 'ȩ',
+ 'Ȫ' => 'ȫ',
+ 'Ȭ' => 'ȭ',
+ 'Ȯ' => 'ȯ',
+ 'Ȱ' => 'ȱ',
+ 'Ȳ' => 'ȳ',
+ 'Ⱥ' => 'ⱥ',
+ 'Ȼ' => 'ȼ',
+ 'Ƚ' => 'ƚ',
+ 'Ⱦ' => 'ⱦ',
+ 'Ɂ' => 'ɂ',
+ 'Ƀ' => 'ƀ',
+ 'Ʉ' => 'ʉ',
+ 'Ʌ' => 'ʌ',
+ 'Ɇ' => 'ɇ',
+ 'Ɉ' => 'ɉ',
+ 'Ɋ' => 'ɋ',
+ 'Ɍ' => 'ɍ',
+ 'Ɏ' => 'ɏ',
+ 'Ͱ' => 'ͱ',
+ 'Ͳ' => 'ͳ',
+ 'Ͷ' => 'ͷ',
+ 'Ϳ' => 'ϳ',
+ 'Ά' => 'ά',
+ 'Έ' => 'έ',
+ 'Ή' => 'ή',
+ 'Ί' => 'ί',
+ 'Ό' => 'ό',
+ 'Ύ' => 'ύ',
+ 'Ώ' => 'ώ',
+ 'Α' => 'α',
+ 'Β' => 'β',
+ 'Γ' => 'γ',
+ 'Δ' => 'δ',
+ 'Ε' => 'ε',
+ 'Ζ' => 'ζ',
+ 'Η' => 'η',
+ 'Θ' => 'θ',
+ 'Ι' => 'ι',
+ 'Κ' => 'κ',
+ 'Λ' => 'λ',
+ 'Μ' => 'μ',
+ 'Ν' => 'ν',
+ 'Ξ' => 'ξ',
+ 'Ο' => 'ο',
+ 'Π' => 'π',
+ 'Ρ' => 'ρ',
+ 'Σ' => 'σ',
+ 'Τ' => 'τ',
+ 'Υ' => 'υ',
+ 'Φ' => 'φ',
+ 'Χ' => 'χ',
+ 'Ψ' => 'ψ',
+ 'Ω' => 'ω',
+ 'Ϊ' => 'ϊ',
+ 'Ϋ' => 'ϋ',
+ 'Ϗ' => 'ϗ',
+ 'Ϙ' => 'ϙ',
+ 'Ϛ' => 'ϛ',
+ 'Ϝ' => 'ϝ',
+ 'Ϟ' => 'ϟ',
+ 'Ϡ' => 'ϡ',
+ 'Ϣ' => 'ϣ',
+ 'Ϥ' => 'ϥ',
+ 'Ϧ' => 'ϧ',
+ 'Ϩ' => 'ϩ',
+ 'Ϫ' => 'ϫ',
+ 'Ϭ' => 'ϭ',
+ 'Ϯ' => 'ϯ',
+ 'ϴ' => 'θ',
+ 'Ϸ' => 'ϸ',
+ 'Ϲ' => 'ϲ',
+ 'Ϻ' => 'ϻ',
+ 'Ͻ' => 'ͻ',
+ 'Ͼ' => 'ͼ',
+ 'Ͽ' => 'ͽ',
+ 'Ѐ' => 'ѐ',
+ 'Ё' => 'ё',
+ 'Ђ' => 'ђ',
+ 'Ѓ' => 'ѓ',
+ 'Є' => 'є',
+ 'Ѕ' => 'ѕ',
+ 'І' => 'і',
+ 'Ї' => 'ї',
+ 'Ј' => 'ј',
+ 'Љ' => 'љ',
+ 'Њ' => 'њ',
+ 'Ћ' => 'ћ',
+ 'Ќ' => 'ќ',
+ 'Ѝ' => 'ѝ',
+ 'Ў' => 'ў',
+ 'Џ' => 'џ',
+ 'А' => 'а',
+ 'Б' => 'б',
+ 'В' => 'в',
+ 'Г' => 'г',
+ 'Д' => 'д',
+ 'Е' => 'е',
+ 'Ж' => 'ж',
+ 'З' => 'з',
+ 'И' => 'и',
+ 'Й' => 'й',
+ 'К' => 'к',
+ 'Л' => 'л',
+ 'М' => 'м',
+ 'Н' => 'н',
+ 'О' => 'о',
+ 'П' => 'п',
+ 'Р' => 'р',
+ 'С' => 'с',
+ 'Т' => 'т',
+ 'У' => 'у',
+ 'Ф' => 'ф',
+ 'Х' => 'х',
+ 'Ц' => 'ц',
+ 'Ч' => 'ч',
+ 'Ш' => 'ш',
+ 'Щ' => 'щ',
+ 'Ъ' => 'ъ',
+ 'Ы' => 'ы',
+ 'Ь' => 'ь',
+ 'Э' => 'э',
+ 'Ю' => 'ю',
+ 'Я' => 'я',
+ 'Ѡ' => 'ѡ',
+ 'Ѣ' => 'ѣ',
+ 'Ѥ' => 'ѥ',
+ 'Ѧ' => 'ѧ',
+ 'Ѩ' => 'ѩ',
+ 'Ѫ' => 'ѫ',
+ 'Ѭ' => 'ѭ',
+ 'Ѯ' => 'ѯ',
+ 'Ѱ' => 'ѱ',
+ 'Ѳ' => 'ѳ',
+ 'Ѵ' => 'ѵ',
+ 'Ѷ' => 'ѷ',
+ 'Ѹ' => 'ѹ',
+ 'Ѻ' => 'ѻ',
+ 'Ѽ' => 'ѽ',
+ 'Ѿ' => 'ѿ',
+ 'Ҁ' => 'ҁ',
+ 'Ҋ' => 'ҋ',
+ 'Ҍ' => 'ҍ',
+ 'Ҏ' => 'ҏ',
+ 'Ґ' => 'ґ',
+ 'Ғ' => 'ғ',
+ 'Ҕ' => 'ҕ',
+ 'Җ' => 'җ',
+ 'Ҙ' => 'ҙ',
+ 'Қ' => 'қ',
+ 'Ҝ' => 'ҝ',
+ 'Ҟ' => 'ҟ',
+ 'Ҡ' => 'ҡ',
+ 'Ң' => 'ң',
+ 'Ҥ' => 'ҥ',
+ 'Ҧ' => 'ҧ',
+ 'Ҩ' => 'ҩ',
+ 'Ҫ' => 'ҫ',
+ 'Ҭ' => 'ҭ',
+ 'Ү' => 'ү',
+ 'Ұ' => 'ұ',
+ 'Ҳ' => 'ҳ',
+ 'Ҵ' => 'ҵ',
+ 'Ҷ' => 'ҷ',
+ 'Ҹ' => 'ҹ',
+ 'Һ' => 'һ',
+ 'Ҽ' => 'ҽ',
+ 'Ҿ' => 'ҿ',
+ 'Ӏ' => 'ӏ',
+ 'Ӂ' => 'ӂ',
+ 'Ӄ' => 'ӄ',
+ 'Ӆ' => 'ӆ',
+ 'Ӈ' => 'ӈ',
+ 'Ӊ' => 'ӊ',
+ 'Ӌ' => 'ӌ',
+ 'Ӎ' => 'ӎ',
+ 'Ӑ' => 'ӑ',
+ 'Ӓ' => 'ӓ',
+ 'Ӕ' => 'ӕ',
+ 'Ӗ' => 'ӗ',
+ 'Ә' => 'ә',
+ 'Ӛ' => 'ӛ',
+ 'Ӝ' => 'ӝ',
+ 'Ӟ' => 'ӟ',
+ 'Ӡ' => 'ӡ',
+ 'Ӣ' => 'ӣ',
+ 'Ӥ' => 'ӥ',
+ 'Ӧ' => 'ӧ',
+ 'Ө' => 'ө',
+ 'Ӫ' => 'ӫ',
+ 'Ӭ' => 'ӭ',
+ 'Ӯ' => 'ӯ',
+ 'Ӱ' => 'ӱ',
+ 'Ӳ' => 'ӳ',
+ 'Ӵ' => 'ӵ',
+ 'Ӷ' => 'ӷ',
+ 'Ӹ' => 'ӹ',
+ 'Ӻ' => 'ӻ',
+ 'Ӽ' => 'ӽ',
+ 'Ӿ' => 'ӿ',
+ 'Ԁ' => 'ԁ',
+ 'Ԃ' => 'ԃ',
+ 'Ԅ' => 'ԅ',
+ 'Ԇ' => 'ԇ',
+ 'Ԉ' => 'ԉ',
+ 'Ԋ' => 'ԋ',
+ 'Ԍ' => 'ԍ',
+ 'Ԏ' => 'ԏ',
+ 'Ԑ' => 'ԑ',
+ 'Ԓ' => 'ԓ',
+ 'Ԕ' => 'ԕ',
+ 'Ԗ' => 'ԗ',
+ 'Ԙ' => 'ԙ',
+ 'Ԛ' => 'ԛ',
+ 'Ԝ' => 'ԝ',
+ 'Ԟ' => 'ԟ',
+ 'Ԡ' => 'ԡ',
+ 'Ԣ' => 'ԣ',
+ 'Ԥ' => 'ԥ',
+ 'Ԧ' => 'ԧ',
+ 'Ԩ' => 'ԩ',
+ 'Ԫ' => 'ԫ',
+ 'Ԭ' => 'ԭ',
+ 'Ԯ' => 'ԯ',
+ 'Ա' => 'ա',
+ 'Բ' => 'բ',
+ 'Գ' => 'գ',
+ 'Դ' => 'դ',
+ 'Ե' => 'ե',
+ 'Զ' => 'զ',
+ 'Է' => 'է',
+ 'Ը' => 'ը',
+ 'Թ' => 'թ',
+ 'Ժ' => 'ժ',
+ 'Ի' => 'ի',
+ 'Լ' => 'լ',
+ 'Խ' => 'խ',
+ 'Ծ' => 'ծ',
+ 'Կ' => 'կ',
+ 'Հ' => 'հ',
+ 'Ձ' => 'ձ',
+ 'Ղ' => 'ղ',
+ 'Ճ' => 'ճ',
+ 'Մ' => 'մ',
+ 'Յ' => 'յ',
+ 'Ն' => 'ն',
+ 'Շ' => 'շ',
+ 'Ո' => 'ո',
+ 'Չ' => 'չ',
+ 'Պ' => 'պ',
+ 'Ջ' => 'ջ',
+ 'Ռ' => 'ռ',
+ 'Ս' => 'ս',
+ 'Վ' => 'վ',
+ 'Տ' => 'տ',
+ 'Ր' => 'ր',
+ 'Ց' => 'ց',
+ 'Ւ' => 'ւ',
+ 'Փ' => 'փ',
+ 'Ք' => 'ք',
+ 'Օ' => 'օ',
+ 'Ֆ' => 'ֆ',
+ 'Ⴀ' => 'ⴀ',
+ 'Ⴁ' => 'ⴁ',
+ 'Ⴂ' => 'ⴂ',
+ 'Ⴃ' => 'ⴃ',
+ 'Ⴄ' => 'ⴄ',
+ 'Ⴅ' => 'ⴅ',
+ 'Ⴆ' => 'ⴆ',
+ 'Ⴇ' => 'ⴇ',
+ 'Ⴈ' => 'ⴈ',
+ 'Ⴉ' => 'ⴉ',
+ 'Ⴊ' => 'ⴊ',
+ 'Ⴋ' => 'ⴋ',
+ 'Ⴌ' => 'ⴌ',
+ 'Ⴍ' => 'ⴍ',
+ 'Ⴎ' => 'ⴎ',
+ 'Ⴏ' => 'ⴏ',
+ 'Ⴐ' => 'ⴐ',
+ 'Ⴑ' => 'ⴑ',
+ 'Ⴒ' => 'ⴒ',
+ 'Ⴓ' => 'ⴓ',
+ 'Ⴔ' => 'ⴔ',
+ 'Ⴕ' => 'ⴕ',
+ 'Ⴖ' => 'ⴖ',
+ 'Ⴗ' => 'ⴗ',
+ 'Ⴘ' => 'ⴘ',
+ 'Ⴙ' => 'ⴙ',
+ 'Ⴚ' => 'ⴚ',
+ 'Ⴛ' => 'ⴛ',
+ 'Ⴜ' => 'ⴜ',
+ 'Ⴝ' => 'ⴝ',
+ 'Ⴞ' => 'ⴞ',
+ 'Ⴟ' => 'ⴟ',
+ 'Ⴠ' => 'ⴠ',
+ 'Ⴡ' => 'ⴡ',
+ 'Ⴢ' => 'ⴢ',
+ 'Ⴣ' => 'ⴣ',
+ 'Ⴤ' => 'ⴤ',
+ 'Ⴥ' => 'ⴥ',
+ 'Ⴧ' => 'ⴧ',
+ 'Ⴭ' => 'ⴭ',
+ 'Ḁ' => 'ḁ',
+ 'Ḃ' => 'ḃ',
+ 'Ḅ' => 'ḅ',
+ 'Ḇ' => 'ḇ',
+ 'Ḉ' => 'ḉ',
+ 'Ḋ' => 'ḋ',
+ 'Ḍ' => 'ḍ',
+ 'Ḏ' => 'ḏ',
+ 'Ḑ' => 'ḑ',
+ 'Ḓ' => 'ḓ',
+ 'Ḕ' => 'ḕ',
+ 'Ḗ' => 'ḗ',
+ 'Ḙ' => 'ḙ',
+ 'Ḛ' => 'ḛ',
+ 'Ḝ' => 'ḝ',
+ 'Ḟ' => 'ḟ',
+ 'Ḡ' => 'ḡ',
+ 'Ḣ' => 'ḣ',
+ 'Ḥ' => 'ḥ',
+ 'Ḧ' => 'ḧ',
+ 'Ḩ' => 'ḩ',
+ 'Ḫ' => 'ḫ',
+ 'Ḭ' => 'ḭ',
+ 'Ḯ' => 'ḯ',
+ 'Ḱ' => 'ḱ',
+ 'Ḳ' => 'ḳ',
+ 'Ḵ' => 'ḵ',
+ 'Ḷ' => 'ḷ',
+ 'Ḹ' => 'ḹ',
+ 'Ḻ' => 'ḻ',
+ 'Ḽ' => 'ḽ',
+ 'Ḿ' => 'ḿ',
+ 'Ṁ' => 'ṁ',
+ 'Ṃ' => 'ṃ',
+ 'Ṅ' => 'ṅ',
+ 'Ṇ' => 'ṇ',
+ 'Ṉ' => 'ṉ',
+ 'Ṋ' => 'ṋ',
+ 'Ṍ' => 'ṍ',
+ 'Ṏ' => 'ṏ',
+ 'Ṑ' => 'ṑ',
+ 'Ṓ' => 'ṓ',
+ 'Ṕ' => 'ṕ',
+ 'Ṗ' => 'ṗ',
+ 'Ṙ' => 'ṙ',
+ 'Ṛ' => 'ṛ',
+ 'Ṝ' => 'ṝ',
+ 'Ṟ' => 'ṟ',
+ 'Ṡ' => 'ṡ',
+ 'Ṣ' => 'ṣ',
+ 'Ṥ' => 'ṥ',
+ 'Ṧ' => 'ṧ',
+ 'Ṩ' => 'ṩ',
+ 'Ṫ' => 'ṫ',
+ 'Ṭ' => 'ṭ',
+ 'Ṯ' => 'ṯ',
+ 'Ṱ' => 'ṱ',
+ 'Ṳ' => 'ṳ',
+ 'Ṵ' => 'ṵ',
+ 'Ṷ' => 'ṷ',
+ 'Ṹ' => 'ṹ',
+ 'Ṻ' => 'ṻ',
+ 'Ṽ' => 'ṽ',
+ 'Ṿ' => 'ṿ',
+ 'Ẁ' => 'ẁ',
+ 'Ẃ' => 'ẃ',
+ 'Ẅ' => 'ẅ',
+ 'Ẇ' => 'ẇ',
+ 'Ẉ' => 'ẉ',
+ 'Ẋ' => 'ẋ',
+ 'Ẍ' => 'ẍ',
+ 'Ẏ' => 'ẏ',
+ 'Ẑ' => 'ẑ',
+ 'Ẓ' => 'ẓ',
+ 'Ẕ' => 'ẕ',
+ 'ẞ' => 'ß',
+ 'Ạ' => 'ạ',
+ 'Ả' => 'ả',
+ 'Ấ' => 'ấ',
+ 'Ầ' => 'ầ',
+ 'Ẩ' => 'ẩ',
+ 'Ẫ' => 'ẫ',
+ 'Ậ' => 'ậ',
+ 'Ắ' => 'ắ',
+ 'Ằ' => 'ằ',
+ 'Ẳ' => 'ẳ',
+ 'Ẵ' => 'ẵ',
+ 'Ặ' => 'ặ',
+ 'Ẹ' => 'ẹ',
+ 'Ẻ' => 'ẻ',
+ 'Ẽ' => 'ẽ',
+ 'Ế' => 'ế',
+ 'Ề' => 'ề',
+ 'Ể' => 'ể',
+ 'Ễ' => 'ễ',
+ 'Ệ' => 'ệ',
+ 'Ỉ' => 'ỉ',
+ 'Ị' => 'ị',
+ 'Ọ' => 'ọ',
+ 'Ỏ' => 'ỏ',
+ 'Ố' => 'ố',
+ 'Ồ' => 'ồ',
+ 'Ổ' => 'ổ',
+ 'Ỗ' => 'ỗ',
+ 'Ộ' => 'ộ',
+ 'Ớ' => 'ớ',
+ 'Ờ' => 'ờ',
+ 'Ở' => 'ở',
+ 'Ỡ' => 'ỡ',
+ 'Ợ' => 'ợ',
+ 'Ụ' => 'ụ',
+ 'Ủ' => 'ủ',
+ 'Ứ' => 'ứ',
+ 'Ừ' => 'ừ',
+ 'Ử' => 'ử',
+ 'Ữ' => 'ữ',
+ 'Ự' => 'ự',
+ 'Ỳ' => 'ỳ',
+ 'Ỵ' => 'ỵ',
+ 'Ỷ' => 'ỷ',
+ 'Ỹ' => 'ỹ',
+ 'Ỻ' => 'ỻ',
+ 'Ỽ' => 'ỽ',
+ 'Ỿ' => 'ỿ',
+ 'Ἀ' => 'ἀ',
+ 'Ἁ' => 'ἁ',
+ 'Ἂ' => 'ἂ',
+ 'Ἃ' => 'ἃ',
+ 'Ἄ' => 'ἄ',
+ 'Ἅ' => 'ἅ',
+ 'Ἆ' => 'ἆ',
+ 'Ἇ' => 'ἇ',
+ 'Ἐ' => 'ἐ',
+ 'Ἑ' => 'ἑ',
+ 'Ἒ' => 'ἒ',
+ 'Ἓ' => 'ἓ',
+ 'Ἔ' => 'ἔ',
+ 'Ἕ' => 'ἕ',
+ 'Ἠ' => 'ἠ',
+ 'Ἡ' => 'ἡ',
+ 'Ἢ' => 'ἢ',
+ 'Ἣ' => 'ἣ',
+ 'Ἤ' => 'ἤ',
+ 'Ἥ' => 'ἥ',
+ 'Ἦ' => 'ἦ',
+ 'Ἧ' => 'ἧ',
+ 'Ἰ' => 'ἰ',
+ 'Ἱ' => 'ἱ',
+ 'Ἲ' => 'ἲ',
+ 'Ἳ' => 'ἳ',
+ 'Ἴ' => 'ἴ',
+ 'Ἵ' => 'ἵ',
+ 'Ἶ' => 'ἶ',
+ 'Ἷ' => 'ἷ',
+ 'Ὀ' => 'ὀ',
+ 'Ὁ' => 'ὁ',
+ 'Ὂ' => 'ὂ',
+ 'Ὃ' => 'ὃ',
+ 'Ὄ' => 'ὄ',
+ 'Ὅ' => 'ὅ',
+ 'Ὑ' => 'ὑ',
+ 'Ὓ' => 'ὓ',
+ 'Ὕ' => 'ὕ',
+ 'Ὗ' => 'ὗ',
+ 'Ὠ' => 'ὠ',
+ 'Ὡ' => 'ὡ',
+ 'Ὢ' => 'ὢ',
+ 'Ὣ' => 'ὣ',
+ 'Ὤ' => 'ὤ',
+ 'Ὥ' => 'ὥ',
+ 'Ὦ' => 'ὦ',
+ 'Ὧ' => 'ὧ',
+ 'ᾈ' => 'ᾀ',
+ 'ᾉ' => 'ᾁ',
+ 'ᾊ' => 'ᾂ',
+ 'ᾋ' => 'ᾃ',
+ 'ᾌ' => 'ᾄ',
+ 'ᾍ' => 'ᾅ',
+ 'ᾎ' => 'ᾆ',
+ 'ᾏ' => 'ᾇ',
+ 'ᾘ' => 'ᾐ',
+ 'ᾙ' => 'ᾑ',
+ 'ᾚ' => 'ᾒ',
+ 'ᾛ' => 'ᾓ',
+ 'ᾜ' => 'ᾔ',
+ 'ᾝ' => 'ᾕ',
+ 'ᾞ' => 'ᾖ',
+ 'ᾟ' => 'ᾗ',
+ 'ᾨ' => 'ᾠ',
+ 'ᾩ' => 'ᾡ',
+ 'ᾪ' => 'ᾢ',
+ 'ᾫ' => 'ᾣ',
+ 'ᾬ' => 'ᾤ',
+ 'ᾭ' => 'ᾥ',
+ 'ᾮ' => 'ᾦ',
+ 'ᾯ' => 'ᾧ',
+ 'Ᾰ' => 'ᾰ',
+ 'Ᾱ' => 'ᾱ',
+ 'Ὰ' => 'ὰ',
+ 'Ά' => 'ά',
+ 'ᾼ' => 'ᾳ',
+ 'Ὲ' => 'ὲ',
+ 'Έ' => 'έ',
+ 'Ὴ' => 'ὴ',
+ 'Ή' => 'ή',
+ 'ῌ' => 'ῃ',
+ 'Ῐ' => 'ῐ',
+ 'Ῑ' => 'ῑ',
+ 'Ὶ' => 'ὶ',
+ 'Ί' => 'ί',
+ 'Ῠ' => 'ῠ',
+ 'Ῡ' => 'ῡ',
+ 'Ὺ' => 'ὺ',
+ 'Ύ' => 'ύ',
+ 'Ῥ' => 'ῥ',
+ 'Ὸ' => 'ὸ',
+ 'Ό' => 'ό',
+ 'Ὼ' => 'ὼ',
+ 'Ώ' => 'ώ',
+ 'ῼ' => 'ῳ',
+ 'Ω' => 'ω',
+ 'K' => 'k',
+ 'Å' => 'å',
+ 'Ⅎ' => 'ⅎ',
+ 'Ⅰ' => 'ⅰ',
+ 'Ⅱ' => 'ⅱ',
+ 'Ⅲ' => 'ⅲ',
+ 'Ⅳ' => 'ⅳ',
+ 'Ⅴ' => 'ⅴ',
+ 'Ⅵ' => 'ⅵ',
+ 'Ⅶ' => 'ⅶ',
+ 'Ⅷ' => 'ⅷ',
+ 'Ⅸ' => 'ⅸ',
+ 'Ⅹ' => 'ⅹ',
+ 'Ⅺ' => 'ⅺ',
+ 'Ⅻ' => 'ⅻ',
+ 'Ⅼ' => 'ⅼ',
+ 'Ⅽ' => 'ⅽ',
+ 'Ⅾ' => 'ⅾ',
+ 'Ⅿ' => 'ⅿ',
+ 'Ↄ' => 'ↄ',
+ 'Ⓐ' => 'ⓐ',
+ 'Ⓑ' => 'ⓑ',
+ 'Ⓒ' => 'ⓒ',
+ 'Ⓓ' => 'ⓓ',
+ 'Ⓔ' => 'ⓔ',
+ 'Ⓕ' => 'ⓕ',
+ 'Ⓖ' => 'ⓖ',
+ 'Ⓗ' => 'ⓗ',
+ 'Ⓘ' => 'ⓘ',
+ 'Ⓙ' => 'ⓙ',
+ 'Ⓚ' => 'ⓚ',
+ 'Ⓛ' => 'ⓛ',
+ 'Ⓜ' => 'ⓜ',
+ 'Ⓝ' => 'ⓝ',
+ 'Ⓞ' => 'ⓞ',
+ 'Ⓟ' => 'ⓟ',
+ 'Ⓠ' => 'ⓠ',
+ 'Ⓡ' => 'ⓡ',
+ 'Ⓢ' => 'ⓢ',
+ 'Ⓣ' => 'ⓣ',
+ 'Ⓤ' => 'ⓤ',
+ 'Ⓥ' => 'ⓥ',
+ 'Ⓦ' => 'ⓦ',
+ 'Ⓧ' => 'ⓧ',
+ 'Ⓨ' => 'ⓨ',
+ 'Ⓩ' => 'ⓩ',
+ 'Ⰰ' => 'ⰰ',
+ 'Ⰱ' => 'ⰱ',
+ 'Ⰲ' => 'ⰲ',
+ 'Ⰳ' => 'ⰳ',
+ 'Ⰴ' => 'ⰴ',
+ 'Ⰵ' => 'ⰵ',
+ 'Ⰶ' => 'ⰶ',
+ 'Ⰷ' => 'ⰷ',
+ 'Ⰸ' => 'ⰸ',
+ 'Ⰹ' => 'ⰹ',
+ 'Ⰺ' => 'ⰺ',
+ 'Ⰻ' => 'ⰻ',
+ 'Ⰼ' => 'ⰼ',
+ 'Ⰽ' => 'ⰽ',
+ 'Ⰾ' => 'ⰾ',
+ 'Ⰿ' => 'ⰿ',
+ 'Ⱀ' => 'ⱀ',
+ 'Ⱁ' => 'ⱁ',
+ 'Ⱂ' => 'ⱂ',
+ 'Ⱃ' => 'ⱃ',
+ 'Ⱄ' => 'ⱄ',
+ 'Ⱅ' => 'ⱅ',
+ 'Ⱆ' => 'ⱆ',
+ 'Ⱇ' => 'ⱇ',
+ 'Ⱈ' => 'ⱈ',
+ 'Ⱉ' => 'ⱉ',
+ 'Ⱊ' => 'ⱊ',
+ 'Ⱋ' => 'ⱋ',
+ 'Ⱌ' => 'ⱌ',
+ 'Ⱍ' => 'ⱍ',
+ 'Ⱎ' => 'ⱎ',
+ 'Ⱏ' => 'ⱏ',
+ 'Ⱐ' => 'ⱐ',
+ 'Ⱑ' => 'ⱑ',
+ 'Ⱒ' => 'ⱒ',
+ 'Ⱓ' => 'ⱓ',
+ 'Ⱔ' => 'ⱔ',
+ 'Ⱕ' => 'ⱕ',
+ 'Ⱖ' => 'ⱖ',
+ 'Ⱗ' => 'ⱗ',
+ 'Ⱘ' => 'ⱘ',
+ 'Ⱙ' => 'ⱙ',
+ 'Ⱚ' => 'ⱚ',
+ 'Ⱛ' => 'ⱛ',
+ 'Ⱜ' => 'ⱜ',
+ 'Ⱝ' => 'ⱝ',
+ 'Ⱞ' => 'ⱞ',
+ 'Ⱡ' => 'ⱡ',
+ 'Ɫ' => 'ɫ',
+ 'Ᵽ' => 'ᵽ',
+ 'Ɽ' => 'ɽ',
+ 'Ⱨ' => 'ⱨ',
+ 'Ⱪ' => 'ⱪ',
+ 'Ⱬ' => 'ⱬ',
+ 'Ɑ' => 'ɑ',
+ 'Ɱ' => 'ɱ',
+ 'Ɐ' => 'ɐ',
+ 'Ɒ' => 'ɒ',
+ 'Ⱳ' => 'ⱳ',
+ 'Ⱶ' => 'ⱶ',
+ 'Ȿ' => 'ȿ',
+ 'Ɀ' => 'ɀ',
+ 'Ⲁ' => 'ⲁ',
+ 'Ⲃ' => 'ⲃ',
+ 'Ⲅ' => 'ⲅ',
+ 'Ⲇ' => 'ⲇ',
+ 'Ⲉ' => 'ⲉ',
+ 'Ⲋ' => 'ⲋ',
+ 'Ⲍ' => 'ⲍ',
+ 'Ⲏ' => 'ⲏ',
+ 'Ⲑ' => 'ⲑ',
+ 'Ⲓ' => 'ⲓ',
+ 'Ⲕ' => 'ⲕ',
+ 'Ⲗ' => 'ⲗ',
+ 'Ⲙ' => 'ⲙ',
+ 'Ⲛ' => 'ⲛ',
+ 'Ⲝ' => 'ⲝ',
+ 'Ⲟ' => 'ⲟ',
+ 'Ⲡ' => 'ⲡ',
+ 'Ⲣ' => 'ⲣ',
+ 'Ⲥ' => 'ⲥ',
+ 'Ⲧ' => 'ⲧ',
+ 'Ⲩ' => 'ⲩ',
+ 'Ⲫ' => 'ⲫ',
+ 'Ⲭ' => 'ⲭ',
+ 'Ⲯ' => 'ⲯ',
+ 'Ⲱ' => 'ⲱ',
+ 'Ⲳ' => 'ⲳ',
+ 'Ⲵ' => 'ⲵ',
+ 'Ⲷ' => 'ⲷ',
+ 'Ⲹ' => 'ⲹ',
+ 'Ⲻ' => 'ⲻ',
+ 'Ⲽ' => 'ⲽ',
+ 'Ⲿ' => 'ⲿ',
+ 'Ⳁ' => 'ⳁ',
+ 'Ⳃ' => 'ⳃ',
+ 'Ⳅ' => 'ⳅ',
+ 'Ⳇ' => 'ⳇ',
+ 'Ⳉ' => 'ⳉ',
+ 'Ⳋ' => 'ⳋ',
+ 'Ⳍ' => 'ⳍ',
+ 'Ⳏ' => 'ⳏ',
+ 'Ⳑ' => 'ⳑ',
+ 'Ⳓ' => 'ⳓ',
+ 'Ⳕ' => 'ⳕ',
+ 'Ⳗ' => 'ⳗ',
+ 'Ⳙ' => 'ⳙ',
+ 'Ⳛ' => 'ⳛ',
+ 'Ⳝ' => 'ⳝ',
+ 'Ⳟ' => 'ⳟ',
+ 'Ⳡ' => 'ⳡ',
+ 'Ⳣ' => 'ⳣ',
+ 'Ⳬ' => 'ⳬ',
+ 'Ⳮ' => 'ⳮ',
+ 'Ⳳ' => 'ⳳ',
+ 'Ꙁ' => 'ꙁ',
+ 'Ꙃ' => 'ꙃ',
+ 'Ꙅ' => 'ꙅ',
+ 'Ꙇ' => 'ꙇ',
+ 'Ꙉ' => 'ꙉ',
+ 'Ꙋ' => 'ꙋ',
+ 'Ꙍ' => 'ꙍ',
+ 'Ꙏ' => 'ꙏ',
+ 'Ꙑ' => 'ꙑ',
+ 'Ꙓ' => 'ꙓ',
+ 'Ꙕ' => 'ꙕ',
+ 'Ꙗ' => 'ꙗ',
+ 'Ꙙ' => 'ꙙ',
+ 'Ꙛ' => 'ꙛ',
+ 'Ꙝ' => 'ꙝ',
+ 'Ꙟ' => 'ꙟ',
+ 'Ꙡ' => 'ꙡ',
+ 'Ꙣ' => 'ꙣ',
+ 'Ꙥ' => 'ꙥ',
+ 'Ꙧ' => 'ꙧ',
+ 'Ꙩ' => 'ꙩ',
+ 'Ꙫ' => 'ꙫ',
+ 'Ꙭ' => 'ꙭ',
+ 'Ꚁ' => 'ꚁ',
+ 'Ꚃ' => 'ꚃ',
+ 'Ꚅ' => 'ꚅ',
+ 'Ꚇ' => 'ꚇ',
+ 'Ꚉ' => 'ꚉ',
+ 'Ꚋ' => 'ꚋ',
+ 'Ꚍ' => 'ꚍ',
+ 'Ꚏ' => 'ꚏ',
+ 'Ꚑ' => 'ꚑ',
+ 'Ꚓ' => 'ꚓ',
+ 'Ꚕ' => 'ꚕ',
+ 'Ꚗ' => 'ꚗ',
+ 'Ꚙ' => 'ꚙ',
+ 'Ꚛ' => 'ꚛ',
+ 'Ꜣ' => 'ꜣ',
+ 'Ꜥ' => 'ꜥ',
+ 'Ꜧ' => 'ꜧ',
+ 'Ꜩ' => 'ꜩ',
+ 'Ꜫ' => 'ꜫ',
+ 'Ꜭ' => 'ꜭ',
+ 'Ꜯ' => 'ꜯ',
+ 'Ꜳ' => 'ꜳ',
+ 'Ꜵ' => 'ꜵ',
+ 'Ꜷ' => 'ꜷ',
+ 'Ꜹ' => 'ꜹ',
+ 'Ꜻ' => 'ꜻ',
+ 'Ꜽ' => 'ꜽ',
+ 'Ꜿ' => 'ꜿ',
+ 'Ꝁ' => 'ꝁ',
+ 'Ꝃ' => 'ꝃ',
+ 'Ꝅ' => 'ꝅ',
+ 'Ꝇ' => 'ꝇ',
+ 'Ꝉ' => 'ꝉ',
+ 'Ꝋ' => 'ꝋ',
+ 'Ꝍ' => 'ꝍ',
+ 'Ꝏ' => 'ꝏ',
+ 'Ꝑ' => 'ꝑ',
+ 'Ꝓ' => 'ꝓ',
+ 'Ꝕ' => 'ꝕ',
+ 'Ꝗ' => 'ꝗ',
+ 'Ꝙ' => 'ꝙ',
+ 'Ꝛ' => 'ꝛ',
+ 'Ꝝ' => 'ꝝ',
+ 'Ꝟ' => 'ꝟ',
+ 'Ꝡ' => 'ꝡ',
+ 'Ꝣ' => 'ꝣ',
+ 'Ꝥ' => 'ꝥ',
+ 'Ꝧ' => 'ꝧ',
+ 'Ꝩ' => 'ꝩ',
+ 'Ꝫ' => 'ꝫ',
+ 'Ꝭ' => 'ꝭ',
+ 'Ꝯ' => 'ꝯ',
+ 'Ꝺ' => 'ꝺ',
+ 'Ꝼ' => 'ꝼ',
+ 'Ᵹ' => 'ᵹ',
+ 'Ꝿ' => 'ꝿ',
+ 'Ꞁ' => 'ꞁ',
+ 'Ꞃ' => 'ꞃ',
+ 'Ꞅ' => 'ꞅ',
+ 'Ꞇ' => 'ꞇ',
+ 'Ꞌ' => 'ꞌ',
+ 'Ɥ' => 'ɥ',
+ 'Ꞑ' => 'ꞑ',
+ 'Ꞓ' => 'ꞓ',
+ 'Ꞗ' => 'ꞗ',
+ 'Ꞙ' => 'ꞙ',
+ 'Ꞛ' => 'ꞛ',
+ 'Ꞝ' => 'ꞝ',
+ 'Ꞟ' => 'ꞟ',
+ 'Ꞡ' => 'ꞡ',
+ 'Ꞣ' => 'ꞣ',
+ 'Ꞥ' => 'ꞥ',
+ 'Ꞧ' => 'ꞧ',
+ 'Ꞩ' => 'ꞩ',
+ 'Ɦ' => 'ɦ',
+ 'Ɜ' => 'ɜ',
+ 'Ɡ' => 'ɡ',
+ 'Ɬ' => 'ɬ',
+ 'Ʞ' => 'ʞ',
+ 'Ʇ' => 'ʇ',
+ 'A' => 'a',
+ 'B' => 'b',
+ 'C' => 'c',
+ 'D' => 'd',
+ 'E' => 'e',
+ 'F' => 'f',
+ 'G' => 'g',
+ 'H' => 'h',
+ 'I' => 'i',
+ 'J' => 'j',
+ 'K' => 'k',
+ 'L' => 'l',
+ 'M' => 'm',
+ 'N' => 'n',
+ 'O' => 'o',
+ 'P' => 'p',
+ 'Q' => 'q',
+ 'R' => 'r',
+ 'S' => 's',
+ 'T' => 't',
+ 'U' => 'u',
+ 'V' => 'v',
+ 'W' => 'w',
+ 'X' => 'x',
+ 'Y' => 'y',
+ 'Z' => 'z',
+ '𐐀' => '𐐨',
+ '𐐁' => '𐐩',
+ '𐐂' => '𐐪',
+ '𐐃' => '𐐫',
+ '𐐄' => '𐐬',
+ '𐐅' => '𐐭',
+ '𐐆' => '𐐮',
+ '𐐇' => '𐐯',
+ '𐐈' => '𐐰',
+ '𐐉' => '𐐱',
+ '𐐊' => '𐐲',
+ '𐐋' => '𐐳',
+ '𐐌' => '𐐴',
+ '𐐍' => '𐐵',
+ '𐐎' => '𐐶',
+ '𐐏' => '𐐷',
+ '𐐐' => '𐐸',
+ '𐐑' => '𐐹',
+ '𐐒' => '𐐺',
+ '𐐓' => '𐐻',
+ '𐐔' => '𐐼',
+ '𐐕' => '𐐽',
+ '𐐖' => '𐐾',
+ '𐐗' => '𐐿',
+ '𐐘' => '𐑀',
+ '𐐙' => '𐑁',
+ '𐐚' => '𐑂',
+ '𐐛' => '𐑃',
+ '𐐜' => '𐑄',
+ '𐐝' => '𐑅',
+ '𐐞' => '𐑆',
+ '𐐟' => '𐑇',
+ '𐐠' => '𐑈',
+ '𐐡' => '𐑉',
+ '𐐢' => '𐑊',
+ '𐐣' => '𐑋',
+ '𐐤' => '𐑌',
+ '𐐥' => '𐑍',
+ '𐐦' => '𐑎',
+ '𐐧' => '𐑏',
+ '𑢠' => '𑣀',
+ '𑢡' => '𑣁',
+ '𑢢' => '𑣂',
+ '𑢣' => '𑣃',
+ '𑢤' => '𑣄',
+ '𑢥' => '𑣅',
+ '𑢦' => '𑣆',
+ '𑢧' => '𑣇',
+ '𑢨' => '𑣈',
+ '𑢩' => '𑣉',
+ '𑢪' => '𑣊',
+ '𑢫' => '𑣋',
+ '𑢬' => '𑣌',
+ '𑢭' => '𑣍',
+ '𑢮' => '𑣎',
+ '𑢯' => '𑣏',
+ '𑢰' => '𑣐',
+ '𑢱' => '𑣑',
+ '𑢲' => '𑣒',
+ '𑢳' => '𑣓',
+ '𑢴' => '𑣔',
+ '𑢵' => '𑣕',
+ '𑢶' => '𑣖',
+ '𑢷' => '𑣗',
+ '𑢸' => '𑣘',
+ '𑢹' => '𑣙',
+ '𑢺' => '𑣚',
+ '𑢻' => '𑣛',
+ '𑢼' => '𑣜',
+ '𑢽' => '𑣝',
+ '𑢾' => '𑣞',
+ '𑢿' => '𑣟',
+);
diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php
new file mode 100755
index 0000000..2a8f6e7
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php
@@ -0,0 +1,5 @@
+<?php
+
+// from Case_Ignorable in https://unicode.org/Public/UNIDATA/DerivedCoreProperties.txt
+
+return '/(?<![\x{0027}\x{002E}\x{003A}\x{005E}\x{0060}\x{00A8}\x{00AD}\x{00AF}\x{00B4}\x{00B7}\x{00B8}\x{02B0}-\x{02C1}\x{02C2}-\x{02C5}\x{02C6}-\x{02D1}\x{02D2}-\x{02DF}\x{02E0}-\x{02E4}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EE}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037A}\x{0384}-\x{0385}\x{0387}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0559}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{05F4}\x{0600}-\x{0605}\x{0610}-\x{061A}\x{061C}\x{0640}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DD}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07FA}\x{07FD}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0971}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E46}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EB9}\x{0EBB}-\x{0EBC}\x{0EC6}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{10FC}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17D7}\x{17DD}\x{180B}-\x{180D}\x{180E}\x{1843}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AA7}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1C78}-\x{1C7D}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1D2C}-\x{1D6A}\x{1D78}\x{1D9B}-\x{1DBF}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200F}\x{2018}\x{2019}\x{2024}\x{2027}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{206F}\x{2071}\x{207F}\x{2090}-\x{209C}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2C7C}-\x{2C7D}\x{2CEF}-\x{2CF1}\x{2D6F}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E2F}\x{3005}\x{302A}-\x{302D}\x{3031}-\x{3035}\x{303B}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{309D}-\x{309E}\x{30FC}-\x{30FE}\x{A015}\x{A4F8}-\x{A4FD}\x{A60C}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A67F}\x{A69C}-\x{A69D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A770}\x{A788}\x{A789}-\x{A78A}\x{A7F8}-\x{A7F9}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}\x{A9CF}\x{A9E5}\x{A9E6}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA70}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AADD}\x{AAEC}-\x{AAED}\x{AAF3}-\x{AAF4}\x{AAF6}\x{AB5B}\x{AB5C}-\x{AB5F}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FBB2}-\x{FBC1}\x{FE00}-\x{FE0F}\x{FE13}\x{FE20}-\x{FE2F}\x{FE52}\x{FE55}\x{FEFF}\x{FF07}\x{FF0E}\x{FF1A}\x{FF3E}\x{FF40}\x{FF70}\x{FF9E}-\x{FF9F}\x{FFE3}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110BD}\x{110CD}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16B40}-\x{16B43}\x{16F8F}-\x{16F92}\x{16F93}-\x{16F9F}\x{16FE0}-\x{16FE1}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1F3FB}-\x{1F3FF}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}])(\pL)(\pL*+)/u';
diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php
new file mode 100755
index 0000000..b8103b2
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php
@@ -0,0 +1,1104 @@
+<?php
+
+return array(
+ 'a' => 'A',
+ 'b' => 'B',
+ 'c' => 'C',
+ 'd' => 'D',
+ 'e' => 'E',
+ 'f' => 'F',
+ 'g' => 'G',
+ 'h' => 'H',
+ 'i' => 'I',
+ 'j' => 'J',
+ 'k' => 'K',
+ 'l' => 'L',
+ 'm' => 'M',
+ 'n' => 'N',
+ 'o' => 'O',
+ 'p' => 'P',
+ 'q' => 'Q',
+ 'r' => 'R',
+ 's' => 'S',
+ 't' => 'T',
+ 'u' => 'U',
+ 'v' => 'V',
+ 'w' => 'W',
+ 'x' => 'X',
+ 'y' => 'Y',
+ 'z' => 'Z',
+ 'µ' => 'Μ',
+ 'à' => 'À',
+ 'á' => 'Á',
+ 'â' => 'Â',
+ 'ã' => 'Ã',
+ 'ä' => 'Ä',
+ 'å' => 'Å',
+ 'æ' => 'Æ',
+ 'ç' => 'Ç',
+ 'è' => 'È',
+ 'é' => 'É',
+ 'ê' => 'Ê',
+ 'ë' => 'Ë',
+ 'ì' => 'Ì',
+ 'í' => 'Í',
+ 'î' => 'Î',
+ 'ï' => 'Ï',
+ 'ð' => 'Ð',
+ 'ñ' => 'Ñ',
+ 'ò' => 'Ò',
+ 'ó' => 'Ó',
+ 'ô' => 'Ô',
+ 'õ' => 'Õ',
+ 'ö' => 'Ö',
+ 'ø' => 'Ø',
+ 'ù' => 'Ù',
+ 'ú' => 'Ú',
+ 'û' => 'Û',
+ 'ü' => 'Ü',
+ 'ý' => 'Ý',
+ 'þ' => 'Þ',
+ 'ÿ' => 'Ÿ',
+ 'ā' => 'Ā',
+ 'ă' => 'Ă',
+ 'ą' => 'Ą',
+ 'ć' => 'Ć',
+ 'ĉ' => 'Ĉ',
+ 'ċ' => 'Ċ',
+ 'č' => 'Č',
+ 'ď' => 'Ď',
+ 'đ' => 'Đ',
+ 'ē' => 'Ē',
+ 'ĕ' => 'Ĕ',
+ 'ė' => 'Ė',
+ 'ę' => 'Ę',
+ 'ě' => 'Ě',
+ 'ĝ' => 'Ĝ',
+ 'ğ' => 'Ğ',
+ 'ġ' => 'Ġ',
+ 'ģ' => 'Ģ',
+ 'ĥ' => 'Ĥ',
+ 'ħ' => 'Ħ',
+ 'ĩ' => 'Ĩ',
+ 'ī' => 'Ī',
+ 'ĭ' => 'Ĭ',
+ 'į' => 'Į',
+ 'ı' => 'I',
+ 'ij' => 'IJ',
+ 'ĵ' => 'Ĵ',
+ 'ķ' => 'Ķ',
+ 'ĺ' => 'Ĺ',
+ 'ļ' => 'Ļ',
+ 'ľ' => 'Ľ',
+ 'ŀ' => 'Ŀ',
+ 'ł' => 'Ł',
+ 'ń' => 'Ń',
+ 'ņ' => 'Ņ',
+ 'ň' => 'Ň',
+ 'ŋ' => 'Ŋ',
+ 'ō' => 'Ō',
+ 'ŏ' => 'Ŏ',
+ 'ő' => 'Ő',
+ 'œ' => 'Œ',
+ 'ŕ' => 'Ŕ',
+ 'ŗ' => 'Ŗ',
+ 'ř' => 'Ř',
+ 'ś' => 'Ś',
+ 'ŝ' => 'Ŝ',
+ 'ş' => 'Ş',
+ 'š' => 'Š',
+ 'ţ' => 'Ţ',
+ 'ť' => 'Ť',
+ 'ŧ' => 'Ŧ',
+ 'ũ' => 'Ũ',
+ 'ū' => 'Ū',
+ 'ŭ' => 'Ŭ',
+ 'ů' => 'Ů',
+ 'ű' => 'Ű',
+ 'ų' => 'Ų',
+ 'ŵ' => 'Ŵ',
+ 'ŷ' => 'Ŷ',
+ 'ź' => 'Ź',
+ 'ż' => 'Ż',
+ 'ž' => 'Ž',
+ 'ſ' => 'S',
+ 'ƀ' => 'Ƀ',
+ 'ƃ' => 'Ƃ',
+ 'ƅ' => 'Ƅ',
+ 'ƈ' => 'Ƈ',
+ 'ƌ' => 'Ƌ',
+ 'ƒ' => 'Ƒ',
+ 'ƕ' => 'Ƕ',
+ 'ƙ' => 'Ƙ',
+ 'ƚ' => 'Ƚ',
+ 'ƞ' => 'Ƞ',
+ 'ơ' => 'Ơ',
+ 'ƣ' => 'Ƣ',
+ 'ƥ' => 'Ƥ',
+ 'ƨ' => 'Ƨ',
+ 'ƭ' => 'Ƭ',
+ 'ư' => 'Ư',
+ 'ƴ' => 'Ƴ',
+ 'ƶ' => 'Ƶ',
+ 'ƹ' => 'Ƹ',
+ 'ƽ' => 'Ƽ',
+ 'ƿ' => 'Ƿ',
+ 'Dž' => 'DŽ',
+ 'dž' => 'DŽ',
+ 'Lj' => 'LJ',
+ 'lj' => 'LJ',
+ 'Nj' => 'NJ',
+ 'nj' => 'NJ',
+ 'ǎ' => 'Ǎ',
+ 'ǐ' => 'Ǐ',
+ 'ǒ' => 'Ǒ',
+ 'ǔ' => 'Ǔ',
+ 'ǖ' => 'Ǖ',
+ 'ǘ' => 'Ǘ',
+ 'ǚ' => 'Ǚ',
+ 'ǜ' => 'Ǜ',
+ 'ǝ' => 'Ǝ',
+ 'ǟ' => 'Ǟ',
+ 'ǡ' => 'Ǡ',
+ 'ǣ' => 'Ǣ',
+ 'ǥ' => 'Ǥ',
+ 'ǧ' => 'Ǧ',
+ 'ǩ' => 'Ǩ',
+ 'ǫ' => 'Ǫ',
+ 'ǭ' => 'Ǭ',
+ 'ǯ' => 'Ǯ',
+ 'Dz' => 'DZ',
+ 'dz' => 'DZ',
+ 'ǵ' => 'Ǵ',
+ 'ǹ' => 'Ǹ',
+ 'ǻ' => 'Ǻ',
+ 'ǽ' => 'Ǽ',
+ 'ǿ' => 'Ǿ',
+ 'ȁ' => 'Ȁ',
+ 'ȃ' => 'Ȃ',
+ 'ȅ' => 'Ȅ',
+ 'ȇ' => 'Ȇ',
+ 'ȉ' => 'Ȉ',
+ 'ȋ' => 'Ȋ',
+ 'ȍ' => 'Ȍ',
+ 'ȏ' => 'Ȏ',
+ 'ȑ' => 'Ȑ',
+ 'ȓ' => 'Ȓ',
+ 'ȕ' => 'Ȕ',
+ 'ȗ' => 'Ȗ',
+ 'ș' => 'Ș',
+ 'ț' => 'Ț',
+ 'ȝ' => 'Ȝ',
+ 'ȟ' => 'Ȟ',
+ 'ȣ' => 'Ȣ',
+ 'ȥ' => 'Ȥ',
+ 'ȧ' => 'Ȧ',
+ 'ȩ' => 'Ȩ',
+ 'ȫ' => 'Ȫ',
+ 'ȭ' => 'Ȭ',
+ 'ȯ' => 'Ȯ',
+ 'ȱ' => 'Ȱ',
+ 'ȳ' => 'Ȳ',
+ 'ȼ' => 'Ȼ',
+ 'ȿ' => 'Ȿ',
+ 'ɀ' => 'Ɀ',
+ 'ɂ' => 'Ɂ',
+ 'ɇ' => 'Ɇ',
+ 'ɉ' => 'Ɉ',
+ 'ɋ' => 'Ɋ',
+ 'ɍ' => 'Ɍ',
+ 'ɏ' => 'Ɏ',
+ 'ɐ' => 'Ɐ',
+ 'ɑ' => 'Ɑ',
+ 'ɒ' => 'Ɒ',
+ 'ɓ' => 'Ɓ',
+ 'ɔ' => 'Ɔ',
+ 'ɖ' => 'Ɖ',
+ 'ɗ' => 'Ɗ',
+ 'ə' => 'Ə',
+ 'ɛ' => 'Ɛ',
+ 'ɜ' => 'Ɜ',
+ 'ɠ' => 'Ɠ',
+ 'ɡ' => 'Ɡ',
+ 'ɣ' => 'Ɣ',
+ 'ɥ' => 'Ɥ',
+ 'ɦ' => 'Ɦ',
+ 'ɨ' => 'Ɨ',
+ 'ɩ' => 'Ɩ',
+ 'ɫ' => 'Ɫ',
+ 'ɬ' => 'Ɬ',
+ 'ɯ' => 'Ɯ',
+ 'ɱ' => 'Ɱ',
+ 'ɲ' => 'Ɲ',
+ 'ɵ' => 'Ɵ',
+ 'ɽ' => 'Ɽ',
+ 'ʀ' => 'Ʀ',
+ 'ʃ' => 'Ʃ',
+ 'ʇ' => 'Ʇ',
+ 'ʈ' => 'Ʈ',
+ 'ʉ' => 'Ʉ',
+ 'ʊ' => 'Ʊ',
+ 'ʋ' => 'Ʋ',
+ 'ʌ' => 'Ʌ',
+ 'ʒ' => 'Ʒ',
+ 'ʞ' => 'Ʞ',
+ 'ͅ' => 'Ι',
+ 'ͱ' => 'Ͱ',
+ 'ͳ' => 'Ͳ',
+ 'ͷ' => 'Ͷ',
+ 'ͻ' => 'Ͻ',
+ 'ͼ' => 'Ͼ',
+ 'ͽ' => 'Ͽ',
+ 'ά' => 'Ά',
+ 'έ' => 'Έ',
+ 'ή' => 'Ή',
+ 'ί' => 'Ί',
+ 'α' => 'Α',
+ 'β' => 'Β',
+ 'γ' => 'Γ',
+ 'δ' => 'Δ',
+ 'ε' => 'Ε',
+ 'ζ' => 'Ζ',
+ 'η' => 'Η',
+ 'θ' => 'Θ',
+ 'ι' => 'Ι',
+ 'κ' => 'Κ',
+ 'λ' => 'Λ',
+ 'μ' => 'Μ',
+ 'ν' => 'Ν',
+ 'ξ' => 'Ξ',
+ 'ο' => 'Ο',
+ 'π' => 'Π',
+ 'ρ' => 'Ρ',
+ 'ς' => 'Σ',
+ 'σ' => 'Σ',
+ 'τ' => 'Τ',
+ 'υ' => 'Υ',
+ 'φ' => 'Φ',
+ 'χ' => 'Χ',
+ 'ψ' => 'Ψ',
+ 'ω' => 'Ω',
+ 'ϊ' => 'Ϊ',
+ 'ϋ' => 'Ϋ',
+ 'ό' => 'Ό',
+ 'ύ' => 'Ύ',
+ 'ώ' => 'Ώ',
+ 'ϐ' => 'Β',
+ 'ϑ' => 'Θ',
+ 'ϕ' => 'Φ',
+ 'ϖ' => 'Π',
+ 'ϗ' => 'Ϗ',
+ 'ϙ' => 'Ϙ',
+ 'ϛ' => 'Ϛ',
+ 'ϝ' => 'Ϝ',
+ 'ϟ' => 'Ϟ',
+ 'ϡ' => 'Ϡ',
+ 'ϣ' => 'Ϣ',
+ 'ϥ' => 'Ϥ',
+ 'ϧ' => 'Ϧ',
+ 'ϩ' => 'Ϩ',
+ 'ϫ' => 'Ϫ',
+ 'ϭ' => 'Ϭ',
+ 'ϯ' => 'Ϯ',
+ 'ϰ' => 'Κ',
+ 'ϱ' => 'Ρ',
+ 'ϲ' => 'Ϲ',
+ 'ϳ' => 'Ϳ',
+ 'ϵ' => 'Ε',
+ 'ϸ' => 'Ϸ',
+ 'ϻ' => 'Ϻ',
+ 'а' => 'А',
+ 'б' => 'Б',
+ 'в' => 'В',
+ 'г' => 'Г',
+ 'д' => 'Д',
+ 'е' => 'Е',
+ 'ж' => 'Ж',
+ 'з' => 'З',
+ 'и' => 'И',
+ 'й' => 'Й',
+ 'к' => 'К',
+ 'л' => 'Л',
+ 'м' => 'М',
+ 'н' => 'Н',
+ 'о' => 'О',
+ 'п' => 'П',
+ 'р' => 'Р',
+ 'с' => 'С',
+ 'т' => 'Т',
+ 'у' => 'У',
+ 'ф' => 'Ф',
+ 'х' => 'Х',
+ 'ц' => 'Ц',
+ 'ч' => 'Ч',
+ 'ш' => 'Ш',
+ 'щ' => 'Щ',
+ 'ъ' => 'Ъ',
+ 'ы' => 'Ы',
+ 'ь' => 'Ь',
+ 'э' => 'Э',
+ 'ю' => 'Ю',
+ 'я' => 'Я',
+ 'ѐ' => 'Ѐ',
+ 'ё' => 'Ё',
+ 'ђ' => 'Ђ',
+ 'ѓ' => 'Ѓ',
+ 'є' => 'Є',
+ 'ѕ' => 'Ѕ',
+ 'і' => 'І',
+ 'ї' => 'Ї',
+ 'ј' => 'Ј',
+ 'љ' => 'Љ',
+ 'њ' => 'Њ',
+ 'ћ' => 'Ћ',
+ 'ќ' => 'Ќ',
+ 'ѝ' => 'Ѝ',
+ 'ў' => 'Ў',
+ 'џ' => 'Џ',
+ 'ѡ' => 'Ѡ',
+ 'ѣ' => 'Ѣ',
+ 'ѥ' => 'Ѥ',
+ 'ѧ' => 'Ѧ',
+ 'ѩ' => 'Ѩ',
+ 'ѫ' => 'Ѫ',
+ 'ѭ' => 'Ѭ',
+ 'ѯ' => 'Ѯ',
+ 'ѱ' => 'Ѱ',
+ 'ѳ' => 'Ѳ',
+ 'ѵ' => 'Ѵ',
+ 'ѷ' => 'Ѷ',
+ 'ѹ' => 'Ѹ',
+ 'ѻ' => 'Ѻ',
+ 'ѽ' => 'Ѽ',
+ 'ѿ' => 'Ѿ',
+ 'ҁ' => 'Ҁ',
+ 'ҋ' => 'Ҋ',
+ 'ҍ' => 'Ҍ',
+ 'ҏ' => 'Ҏ',
+ 'ґ' => 'Ґ',
+ 'ғ' => 'Ғ',
+ 'ҕ' => 'Ҕ',
+ 'җ' => 'Җ',
+ 'ҙ' => 'Ҙ',
+ 'қ' => 'Қ',
+ 'ҝ' => 'Ҝ',
+ 'ҟ' => 'Ҟ',
+ 'ҡ' => 'Ҡ',
+ 'ң' => 'Ң',
+ 'ҥ' => 'Ҥ',
+ 'ҧ' => 'Ҧ',
+ 'ҩ' => 'Ҩ',
+ 'ҫ' => 'Ҫ',
+ 'ҭ' => 'Ҭ',
+ 'ү' => 'Ү',
+ 'ұ' => 'Ұ',
+ 'ҳ' => 'Ҳ',
+ 'ҵ' => 'Ҵ',
+ 'ҷ' => 'Ҷ',
+ 'ҹ' => 'Ҹ',
+ 'һ' => 'Һ',
+ 'ҽ' => 'Ҽ',
+ 'ҿ' => 'Ҿ',
+ 'ӂ' => 'Ӂ',
+ 'ӄ' => 'Ӄ',
+ 'ӆ' => 'Ӆ',
+ 'ӈ' => 'Ӈ',
+ 'ӊ' => 'Ӊ',
+ 'ӌ' => 'Ӌ',
+ 'ӎ' => 'Ӎ',
+ 'ӏ' => 'Ӏ',
+ 'ӑ' => 'Ӑ',
+ 'ӓ' => 'Ӓ',
+ 'ӕ' => 'Ӕ',
+ 'ӗ' => 'Ӗ',
+ 'ә' => 'Ә',
+ 'ӛ' => 'Ӛ',
+ 'ӝ' => 'Ӝ',
+ 'ӟ' => 'Ӟ',
+ 'ӡ' => 'Ӡ',
+ 'ӣ' => 'Ӣ',
+ 'ӥ' => 'Ӥ',
+ 'ӧ' => 'Ӧ',
+ 'ө' => 'Ө',
+ 'ӫ' => 'Ӫ',
+ 'ӭ' => 'Ӭ',
+ 'ӯ' => 'Ӯ',
+ 'ӱ' => 'Ӱ',
+ 'ӳ' => 'Ӳ',
+ 'ӵ' => 'Ӵ',
+ 'ӷ' => 'Ӷ',
+ 'ӹ' => 'Ӹ',
+ 'ӻ' => 'Ӻ',
+ 'ӽ' => 'Ӽ',
+ 'ӿ' => 'Ӿ',
+ 'ԁ' => 'Ԁ',
+ 'ԃ' => 'Ԃ',
+ 'ԅ' => 'Ԅ',
+ 'ԇ' => 'Ԇ',
+ 'ԉ' => 'Ԉ',
+ 'ԋ' => 'Ԋ',
+ 'ԍ' => 'Ԍ',
+ 'ԏ' => 'Ԏ',
+ 'ԑ' => 'Ԑ',
+ 'ԓ' => 'Ԓ',
+ 'ԕ' => 'Ԕ',
+ 'ԗ' => 'Ԗ',
+ 'ԙ' => 'Ԙ',
+ 'ԛ' => 'Ԛ',
+ 'ԝ' => 'Ԝ',
+ 'ԟ' => 'Ԟ',
+ 'ԡ' => 'Ԡ',
+ 'ԣ' => 'Ԣ',
+ 'ԥ' => 'Ԥ',
+ 'ԧ' => 'Ԧ',
+ 'ԩ' => 'Ԩ',
+ 'ԫ' => 'Ԫ',
+ 'ԭ' => 'Ԭ',
+ 'ԯ' => 'Ԯ',
+ 'ա' => 'Ա',
+ 'բ' => 'Բ',
+ 'գ' => 'Գ',
+ 'դ' => 'Դ',
+ 'ե' => 'Ե',
+ 'զ' => 'Զ',
+ 'է' => 'Է',
+ 'ը' => 'Ը',
+ 'թ' => 'Թ',
+ 'ժ' => 'Ժ',
+ 'ի' => 'Ի',
+ 'լ' => 'Լ',
+ 'խ' => 'Խ',
+ 'ծ' => 'Ծ',
+ 'կ' => 'Կ',
+ 'հ' => 'Հ',
+ 'ձ' => 'Ձ',
+ 'ղ' => 'Ղ',
+ 'ճ' => 'Ճ',
+ 'մ' => 'Մ',
+ 'յ' => 'Յ',
+ 'ն' => 'Ն',
+ 'շ' => 'Շ',
+ 'ո' => 'Ո',
+ 'չ' => 'Չ',
+ 'պ' => 'Պ',
+ 'ջ' => 'Ջ',
+ 'ռ' => 'Ռ',
+ 'ս' => 'Ս',
+ 'վ' => 'Վ',
+ 'տ' => 'Տ',
+ 'ր' => 'Ր',
+ 'ց' => 'Ց',
+ 'ւ' => 'Ւ',
+ 'փ' => 'Փ',
+ 'ք' => 'Ք',
+ 'օ' => 'Օ',
+ 'ֆ' => 'Ֆ',
+ 'ᵹ' => 'Ᵹ',
+ 'ᵽ' => 'Ᵽ',
+ 'ḁ' => 'Ḁ',
+ 'ḃ' => 'Ḃ',
+ 'ḅ' => 'Ḅ',
+ 'ḇ' => 'Ḇ',
+ 'ḉ' => 'Ḉ',
+ 'ḋ' => 'Ḋ',
+ 'ḍ' => 'Ḍ',
+ 'ḏ' => 'Ḏ',
+ 'ḑ' => 'Ḑ',
+ 'ḓ' => 'Ḓ',
+ 'ḕ' => 'Ḕ',
+ 'ḗ' => 'Ḗ',
+ 'ḙ' => 'Ḙ',
+ 'ḛ' => 'Ḛ',
+ 'ḝ' => 'Ḝ',
+ 'ḟ' => 'Ḟ',
+ 'ḡ' => 'Ḡ',
+ 'ḣ' => 'Ḣ',
+ 'ḥ' => 'Ḥ',
+ 'ḧ' => 'Ḧ',
+ 'ḩ' => 'Ḩ',
+ 'ḫ' => 'Ḫ',
+ 'ḭ' => 'Ḭ',
+ 'ḯ' => 'Ḯ',
+ 'ḱ' => 'Ḱ',
+ 'ḳ' => 'Ḳ',
+ 'ḵ' => 'Ḵ',
+ 'ḷ' => 'Ḷ',
+ 'ḹ' => 'Ḹ',
+ 'ḻ' => 'Ḻ',
+ 'ḽ' => 'Ḽ',
+ 'ḿ' => 'Ḿ',
+ 'ṁ' => 'Ṁ',
+ 'ṃ' => 'Ṃ',
+ 'ṅ' => 'Ṅ',
+ 'ṇ' => 'Ṇ',
+ 'ṉ' => 'Ṉ',
+ 'ṋ' => 'Ṋ',
+ 'ṍ' => 'Ṍ',
+ 'ṏ' => 'Ṏ',
+ 'ṑ' => 'Ṑ',
+ 'ṓ' => 'Ṓ',
+ 'ṕ' => 'Ṕ',
+ 'ṗ' => 'Ṗ',
+ 'ṙ' => 'Ṙ',
+ 'ṛ' => 'Ṛ',
+ 'ṝ' => 'Ṝ',
+ 'ṟ' => 'Ṟ',
+ 'ṡ' => 'Ṡ',
+ 'ṣ' => 'Ṣ',
+ 'ṥ' => 'Ṥ',
+ 'ṧ' => 'Ṧ',
+ 'ṩ' => 'Ṩ',
+ 'ṫ' => 'Ṫ',
+ 'ṭ' => 'Ṭ',
+ 'ṯ' => 'Ṯ',
+ 'ṱ' => 'Ṱ',
+ 'ṳ' => 'Ṳ',
+ 'ṵ' => 'Ṵ',
+ 'ṷ' => 'Ṷ',
+ 'ṹ' => 'Ṹ',
+ 'ṻ' => 'Ṻ',
+ 'ṽ' => 'Ṽ',
+ 'ṿ' => 'Ṿ',
+ 'ẁ' => 'Ẁ',
+ 'ẃ' => 'Ẃ',
+ 'ẅ' => 'Ẅ',
+ 'ẇ' => 'Ẇ',
+ 'ẉ' => 'Ẉ',
+ 'ẋ' => 'Ẋ',
+ 'ẍ' => 'Ẍ',
+ 'ẏ' => 'Ẏ',
+ 'ẑ' => 'Ẑ',
+ 'ẓ' => 'Ẓ',
+ 'ẕ' => 'Ẕ',
+ 'ẛ' => 'Ṡ',
+ 'ạ' => 'Ạ',
+ 'ả' => 'Ả',
+ 'ấ' => 'Ấ',
+ 'ầ' => 'Ầ',
+ 'ẩ' => 'Ẩ',
+ 'ẫ' => 'Ẫ',
+ 'ậ' => 'Ậ',
+ 'ắ' => 'Ắ',
+ 'ằ' => 'Ằ',
+ 'ẳ' => 'Ẳ',
+ 'ẵ' => 'Ẵ',
+ 'ặ' => 'Ặ',
+ 'ẹ' => 'Ẹ',
+ 'ẻ' => 'Ẻ',
+ 'ẽ' => 'Ẽ',
+ 'ế' => 'Ế',
+ 'ề' => 'Ề',
+ 'ể' => 'Ể',
+ 'ễ' => 'Ễ',
+ 'ệ' => 'Ệ',
+ 'ỉ' => 'Ỉ',
+ 'ị' => 'Ị',
+ 'ọ' => 'Ọ',
+ 'ỏ' => 'Ỏ',
+ 'ố' => 'Ố',
+ 'ồ' => 'Ồ',
+ 'ổ' => 'Ổ',
+ 'ỗ' => 'Ỗ',
+ 'ộ' => 'Ộ',
+ 'ớ' => 'Ớ',
+ 'ờ' => 'Ờ',
+ 'ở' => 'Ở',
+ 'ỡ' => 'Ỡ',
+ 'ợ' => 'Ợ',
+ 'ụ' => 'Ụ',
+ 'ủ' => 'Ủ',
+ 'ứ' => 'Ứ',
+ 'ừ' => 'Ừ',
+ 'ử' => 'Ử',
+ 'ữ' => 'Ữ',
+ 'ự' => 'Ự',
+ 'ỳ' => 'Ỳ',
+ 'ỵ' => 'Ỵ',
+ 'ỷ' => 'Ỷ',
+ 'ỹ' => 'Ỹ',
+ 'ỻ' => 'Ỻ',
+ 'ỽ' => 'Ỽ',
+ 'ỿ' => 'Ỿ',
+ 'ἀ' => 'Ἀ',
+ 'ἁ' => 'Ἁ',
+ 'ἂ' => 'Ἂ',
+ 'ἃ' => 'Ἃ',
+ 'ἄ' => 'Ἄ',
+ 'ἅ' => 'Ἅ',
+ 'ἆ' => 'Ἆ',
+ 'ἇ' => 'Ἇ',
+ 'ἐ' => 'Ἐ',
+ 'ἑ' => 'Ἑ',
+ 'ἒ' => 'Ἒ',
+ 'ἓ' => 'Ἓ',
+ 'ἔ' => 'Ἔ',
+ 'ἕ' => 'Ἕ',
+ 'ἠ' => 'Ἠ',
+ 'ἡ' => 'Ἡ',
+ 'ἢ' => 'Ἢ',
+ 'ἣ' => 'Ἣ',
+ 'ἤ' => 'Ἤ',
+ 'ἥ' => 'Ἥ',
+ 'ἦ' => 'Ἦ',
+ 'ἧ' => 'Ἧ',
+ 'ἰ' => 'Ἰ',
+ 'ἱ' => 'Ἱ',
+ 'ἲ' => 'Ἲ',
+ 'ἳ' => 'Ἳ',
+ 'ἴ' => 'Ἴ',
+ 'ἵ' => 'Ἵ',
+ 'ἶ' => 'Ἶ',
+ 'ἷ' => 'Ἷ',
+ 'ὀ' => 'Ὀ',
+ 'ὁ' => 'Ὁ',
+ 'ὂ' => 'Ὂ',
+ 'ὃ' => 'Ὃ',
+ 'ὄ' => 'Ὄ',
+ 'ὅ' => 'Ὅ',
+ 'ὑ' => 'Ὑ',
+ 'ὓ' => 'Ὓ',
+ 'ὕ' => 'Ὕ',
+ 'ὗ' => 'Ὗ',
+ 'ὠ' => 'Ὠ',
+ 'ὡ' => 'Ὡ',
+ 'ὢ' => 'Ὢ',
+ 'ὣ' => 'Ὣ',
+ 'ὤ' => 'Ὤ',
+ 'ὥ' => 'Ὥ',
+ 'ὦ' => 'Ὦ',
+ 'ὧ' => 'Ὧ',
+ 'ὰ' => 'Ὰ',
+ 'ά' => 'Ά',
+ 'ὲ' => 'Ὲ',
+ 'έ' => 'Έ',
+ 'ὴ' => 'Ὴ',
+ 'ή' => 'Ή',
+ 'ὶ' => 'Ὶ',
+ 'ί' => 'Ί',
+ 'ὸ' => 'Ὸ',
+ 'ό' => 'Ό',
+ 'ὺ' => 'Ὺ',
+ 'ύ' => 'Ύ',
+ 'ὼ' => 'Ὼ',
+ 'ώ' => 'Ώ',
+ 'ᾀ' => 'ᾈ',
+ 'ᾁ' => 'ᾉ',
+ 'ᾂ' => 'ᾊ',
+ 'ᾃ' => 'ᾋ',
+ 'ᾄ' => 'ᾌ',
+ 'ᾅ' => 'ᾍ',
+ 'ᾆ' => 'ᾎ',
+ 'ᾇ' => 'ᾏ',
+ 'ᾐ' => 'ᾘ',
+ 'ᾑ' => 'ᾙ',
+ 'ᾒ' => 'ᾚ',
+ 'ᾓ' => 'ᾛ',
+ 'ᾔ' => 'ᾜ',
+ 'ᾕ' => 'ᾝ',
+ 'ᾖ' => 'ᾞ',
+ 'ᾗ' => 'ᾟ',
+ 'ᾠ' => 'ᾨ',
+ 'ᾡ' => 'ᾩ',
+ 'ᾢ' => 'ᾪ',
+ 'ᾣ' => 'ᾫ',
+ 'ᾤ' => 'ᾬ',
+ 'ᾥ' => 'ᾭ',
+ 'ᾦ' => 'ᾮ',
+ 'ᾧ' => 'ᾯ',
+ 'ᾰ' => 'Ᾰ',
+ 'ᾱ' => 'Ᾱ',
+ 'ᾳ' => 'ᾼ',
+ 'ι' => 'Ι',
+ 'ῃ' => 'ῌ',
+ 'ῐ' => 'Ῐ',
+ 'ῑ' => 'Ῑ',
+ 'ῠ' => 'Ῠ',
+ 'ῡ' => 'Ῡ',
+ 'ῥ' => 'Ῥ',
+ 'ῳ' => 'ῼ',
+ 'ⅎ' => 'Ⅎ',
+ 'ⅰ' => 'Ⅰ',
+ 'ⅱ' => 'Ⅱ',
+ 'ⅲ' => 'Ⅲ',
+ 'ⅳ' => 'Ⅳ',
+ 'ⅴ' => 'Ⅴ',
+ 'ⅵ' => 'Ⅵ',
+ 'ⅶ' => 'Ⅶ',
+ 'ⅷ' => 'Ⅷ',
+ 'ⅸ' => 'Ⅸ',
+ 'ⅹ' => 'Ⅹ',
+ 'ⅺ' => 'Ⅺ',
+ 'ⅻ' => 'Ⅻ',
+ 'ⅼ' => 'Ⅼ',
+ 'ⅽ' => 'Ⅽ',
+ 'ⅾ' => 'Ⅾ',
+ 'ⅿ' => 'Ⅿ',
+ 'ↄ' => 'Ↄ',
+ 'ⓐ' => 'Ⓐ',
+ 'ⓑ' => 'Ⓑ',
+ 'ⓒ' => 'Ⓒ',
+ 'ⓓ' => 'Ⓓ',
+ 'ⓔ' => 'Ⓔ',
+ 'ⓕ' => 'Ⓕ',
+ 'ⓖ' => 'Ⓖ',
+ 'ⓗ' => 'Ⓗ',
+ 'ⓘ' => 'Ⓘ',
+ 'ⓙ' => 'Ⓙ',
+ 'ⓚ' => 'Ⓚ',
+ 'ⓛ' => 'Ⓛ',
+ 'ⓜ' => 'Ⓜ',
+ 'ⓝ' => 'Ⓝ',
+ 'ⓞ' => 'Ⓞ',
+ 'ⓟ' => 'Ⓟ',
+ 'ⓠ' => 'Ⓠ',
+ 'ⓡ' => 'Ⓡ',
+ 'ⓢ' => 'Ⓢ',
+ 'ⓣ' => 'Ⓣ',
+ 'ⓤ' => 'Ⓤ',
+ 'ⓥ' => 'Ⓥ',
+ 'ⓦ' => 'Ⓦ',
+ 'ⓧ' => 'Ⓧ',
+ 'ⓨ' => 'Ⓨ',
+ 'ⓩ' => 'Ⓩ',
+ 'ⰰ' => 'Ⰰ',
+ 'ⰱ' => 'Ⰱ',
+ 'ⰲ' => 'Ⰲ',
+ 'ⰳ' => 'Ⰳ',
+ 'ⰴ' => 'Ⰴ',
+ 'ⰵ' => 'Ⰵ',
+ 'ⰶ' => 'Ⰶ',
+ 'ⰷ' => 'Ⰷ',
+ 'ⰸ' => 'Ⰸ',
+ 'ⰹ' => 'Ⰹ',
+ 'ⰺ' => 'Ⰺ',
+ 'ⰻ' => 'Ⰻ',
+ 'ⰼ' => 'Ⰼ',
+ 'ⰽ' => 'Ⰽ',
+ 'ⰾ' => 'Ⰾ',
+ 'ⰿ' => 'Ⰿ',
+ 'ⱀ' => 'Ⱀ',
+ 'ⱁ' => 'Ⱁ',
+ 'ⱂ' => 'Ⱂ',
+ 'ⱃ' => 'Ⱃ',
+ 'ⱄ' => 'Ⱄ',
+ 'ⱅ' => 'Ⱅ',
+ 'ⱆ' => 'Ⱆ',
+ 'ⱇ' => 'Ⱇ',
+ 'ⱈ' => 'Ⱈ',
+ 'ⱉ' => 'Ⱉ',
+ 'ⱊ' => 'Ⱊ',
+ 'ⱋ' => 'Ⱋ',
+ 'ⱌ' => 'Ⱌ',
+ 'ⱍ' => 'Ⱍ',
+ 'ⱎ' => 'Ⱎ',
+ 'ⱏ' => 'Ⱏ',
+ 'ⱐ' => 'Ⱐ',
+ 'ⱑ' => 'Ⱑ',
+ 'ⱒ' => 'Ⱒ',
+ 'ⱓ' => 'Ⱓ',
+ 'ⱔ' => 'Ⱔ',
+ 'ⱕ' => 'Ⱕ',
+ 'ⱖ' => 'Ⱖ',
+ 'ⱗ' => 'Ⱗ',
+ 'ⱘ' => 'Ⱘ',
+ 'ⱙ' => 'Ⱙ',
+ 'ⱚ' => 'Ⱚ',
+ 'ⱛ' => 'Ⱛ',
+ 'ⱜ' => 'Ⱜ',
+ 'ⱝ' => 'Ⱝ',
+ 'ⱞ' => 'Ⱞ',
+ 'ⱡ' => 'Ⱡ',
+ 'ⱥ' => 'Ⱥ',
+ 'ⱦ' => 'Ⱦ',
+ 'ⱨ' => 'Ⱨ',
+ 'ⱪ' => 'Ⱪ',
+ 'ⱬ' => 'Ⱬ',
+ 'ⱳ' => 'Ⱳ',
+ 'ⱶ' => 'Ⱶ',
+ 'ⲁ' => 'Ⲁ',
+ 'ⲃ' => 'Ⲃ',
+ 'ⲅ' => 'Ⲅ',
+ 'ⲇ' => 'Ⲇ',
+ 'ⲉ' => 'Ⲉ',
+ 'ⲋ' => 'Ⲋ',
+ 'ⲍ' => 'Ⲍ',
+ 'ⲏ' => 'Ⲏ',
+ 'ⲑ' => 'Ⲑ',
+ 'ⲓ' => 'Ⲓ',
+ 'ⲕ' => 'Ⲕ',
+ 'ⲗ' => 'Ⲗ',
+ 'ⲙ' => 'Ⲙ',
+ 'ⲛ' => 'Ⲛ',
+ 'ⲝ' => 'Ⲝ',
+ 'ⲟ' => 'Ⲟ',
+ 'ⲡ' => 'Ⲡ',
+ 'ⲣ' => 'Ⲣ',
+ 'ⲥ' => 'Ⲥ',
+ 'ⲧ' => 'Ⲧ',
+ 'ⲩ' => 'Ⲩ',
+ 'ⲫ' => 'Ⲫ',
+ 'ⲭ' => 'Ⲭ',
+ 'ⲯ' => 'Ⲯ',
+ 'ⲱ' => 'Ⲱ',
+ 'ⲳ' => 'Ⲳ',
+ 'ⲵ' => 'Ⲵ',
+ 'ⲷ' => 'Ⲷ',
+ 'ⲹ' => 'Ⲹ',
+ 'ⲻ' => 'Ⲻ',
+ 'ⲽ' => 'Ⲽ',
+ 'ⲿ' => 'Ⲿ',
+ 'ⳁ' => 'Ⳁ',
+ 'ⳃ' => 'Ⳃ',
+ 'ⳅ' => 'Ⳅ',
+ 'ⳇ' => 'Ⳇ',
+ 'ⳉ' => 'Ⳉ',
+ 'ⳋ' => 'Ⳋ',
+ 'ⳍ' => 'Ⳍ',
+ 'ⳏ' => 'Ⳏ',
+ 'ⳑ' => 'Ⳑ',
+ 'ⳓ' => 'Ⳓ',
+ 'ⳕ' => 'Ⳕ',
+ 'ⳗ' => 'Ⳗ',
+ 'ⳙ' => 'Ⳙ',
+ 'ⳛ' => 'Ⳛ',
+ 'ⳝ' => 'Ⳝ',
+ 'ⳟ' => 'Ⳟ',
+ 'ⳡ' => 'Ⳡ',
+ 'ⳣ' => 'Ⳣ',
+ 'ⳬ' => 'Ⳬ',
+ 'ⳮ' => 'Ⳮ',
+ 'ⳳ' => 'Ⳳ',
+ 'ⴀ' => 'Ⴀ',
+ 'ⴁ' => 'Ⴁ',
+ 'ⴂ' => 'Ⴂ',
+ 'ⴃ' => 'Ⴃ',
+ 'ⴄ' => 'Ⴄ',
+ 'ⴅ' => 'Ⴅ',
+ 'ⴆ' => 'Ⴆ',
+ 'ⴇ' => 'Ⴇ',
+ 'ⴈ' => 'Ⴈ',
+ 'ⴉ' => 'Ⴉ',
+ 'ⴊ' => 'Ⴊ',
+ 'ⴋ' => 'Ⴋ',
+ 'ⴌ' => 'Ⴌ',
+ 'ⴍ' => 'Ⴍ',
+ 'ⴎ' => 'Ⴎ',
+ 'ⴏ' => 'Ⴏ',
+ 'ⴐ' => 'Ⴐ',
+ 'ⴑ' => 'Ⴑ',
+ 'ⴒ' => 'Ⴒ',
+ 'ⴓ' => 'Ⴓ',
+ 'ⴔ' => 'Ⴔ',
+ 'ⴕ' => 'Ⴕ',
+ 'ⴖ' => 'Ⴖ',
+ 'ⴗ' => 'Ⴗ',
+ 'ⴘ' => 'Ⴘ',
+ 'ⴙ' => 'Ⴙ',
+ 'ⴚ' => 'Ⴚ',
+ 'ⴛ' => 'Ⴛ',
+ 'ⴜ' => 'Ⴜ',
+ 'ⴝ' => 'Ⴝ',
+ 'ⴞ' => 'Ⴞ',
+ 'ⴟ' => 'Ⴟ',
+ 'ⴠ' => 'Ⴠ',
+ 'ⴡ' => 'Ⴡ',
+ 'ⴢ' => 'Ⴢ',
+ 'ⴣ' => 'Ⴣ',
+ 'ⴤ' => 'Ⴤ',
+ 'ⴥ' => 'Ⴥ',
+ 'ⴧ' => 'Ⴧ',
+ 'ⴭ' => 'Ⴭ',
+ 'ꙁ' => 'Ꙁ',
+ 'ꙃ' => 'Ꙃ',
+ 'ꙅ' => 'Ꙅ',
+ 'ꙇ' => 'Ꙇ',
+ 'ꙉ' => 'Ꙉ',
+ 'ꙋ' => 'Ꙋ',
+ 'ꙍ' => 'Ꙍ',
+ 'ꙏ' => 'Ꙏ',
+ 'ꙑ' => 'Ꙑ',
+ 'ꙓ' => 'Ꙓ',
+ 'ꙕ' => 'Ꙕ',
+ 'ꙗ' => 'Ꙗ',
+ 'ꙙ' => 'Ꙙ',
+ 'ꙛ' => 'Ꙛ',
+ 'ꙝ' => 'Ꙝ',
+ 'ꙟ' => 'Ꙟ',
+ 'ꙡ' => 'Ꙡ',
+ 'ꙣ' => 'Ꙣ',
+ 'ꙥ' => 'Ꙥ',
+ 'ꙧ' => 'Ꙧ',
+ 'ꙩ' => 'Ꙩ',
+ 'ꙫ' => 'Ꙫ',
+ 'ꙭ' => 'Ꙭ',
+ 'ꚁ' => 'Ꚁ',
+ 'ꚃ' => 'Ꚃ',
+ 'ꚅ' => 'Ꚅ',
+ 'ꚇ' => 'Ꚇ',
+ 'ꚉ' => 'Ꚉ',
+ 'ꚋ' => 'Ꚋ',
+ 'ꚍ' => 'Ꚍ',
+ 'ꚏ' => 'Ꚏ',
+ 'ꚑ' => 'Ꚑ',
+ 'ꚓ' => 'Ꚓ',
+ 'ꚕ' => 'Ꚕ',
+ 'ꚗ' => 'Ꚗ',
+ 'ꚙ' => 'Ꚙ',
+ 'ꚛ' => 'Ꚛ',
+ 'ꜣ' => 'Ꜣ',
+ 'ꜥ' => 'Ꜥ',
+ 'ꜧ' => 'Ꜧ',
+ 'ꜩ' => 'Ꜩ',
+ 'ꜫ' => 'Ꜫ',
+ 'ꜭ' => 'Ꜭ',
+ 'ꜯ' => 'Ꜯ',
+ 'ꜳ' => 'Ꜳ',
+ 'ꜵ' => 'Ꜵ',
+ 'ꜷ' => 'Ꜷ',
+ 'ꜹ' => 'Ꜹ',
+ 'ꜻ' => 'Ꜻ',
+ 'ꜽ' => 'Ꜽ',
+ 'ꜿ' => 'Ꜿ',
+ 'ꝁ' => 'Ꝁ',
+ 'ꝃ' => 'Ꝃ',
+ 'ꝅ' => 'Ꝅ',
+ 'ꝇ' => 'Ꝇ',
+ 'ꝉ' => 'Ꝉ',
+ 'ꝋ' => 'Ꝋ',
+ 'ꝍ' => 'Ꝍ',
+ 'ꝏ' => 'Ꝏ',
+ 'ꝑ' => 'Ꝑ',
+ 'ꝓ' => 'Ꝓ',
+ 'ꝕ' => 'Ꝕ',
+ 'ꝗ' => 'Ꝗ',
+ 'ꝙ' => 'Ꝙ',
+ 'ꝛ' => 'Ꝛ',
+ 'ꝝ' => 'Ꝝ',
+ 'ꝟ' => 'Ꝟ',
+ 'ꝡ' => 'Ꝡ',
+ 'ꝣ' => 'Ꝣ',
+ 'ꝥ' => 'Ꝥ',
+ 'ꝧ' => 'Ꝧ',
+ 'ꝩ' => 'Ꝩ',
+ 'ꝫ' => 'Ꝫ',
+ 'ꝭ' => 'Ꝭ',
+ 'ꝯ' => 'Ꝯ',
+ 'ꝺ' => 'Ꝺ',
+ 'ꝼ' => 'Ꝼ',
+ 'ꝿ' => 'Ꝿ',
+ 'ꞁ' => 'Ꞁ',
+ 'ꞃ' => 'Ꞃ',
+ 'ꞅ' => 'Ꞅ',
+ 'ꞇ' => 'Ꞇ',
+ 'ꞌ' => 'Ꞌ',
+ 'ꞑ' => 'Ꞑ',
+ 'ꞓ' => 'Ꞓ',
+ 'ꞗ' => 'Ꞗ',
+ 'ꞙ' => 'Ꞙ',
+ 'ꞛ' => 'Ꞛ',
+ 'ꞝ' => 'Ꞝ',
+ 'ꞟ' => 'Ꞟ',
+ 'ꞡ' => 'Ꞡ',
+ 'ꞣ' => 'Ꞣ',
+ 'ꞥ' => 'Ꞥ',
+ 'ꞧ' => 'Ꞧ',
+ 'ꞩ' => 'Ꞩ',
+ 'a' => 'A',
+ 'b' => 'B',
+ 'c' => 'C',
+ 'd' => 'D',
+ 'e' => 'E',
+ 'f' => 'F',
+ 'g' => 'G',
+ 'h' => 'H',
+ 'i' => 'I',
+ 'j' => 'J',
+ 'k' => 'K',
+ 'l' => 'L',
+ 'm' => 'M',
+ 'n' => 'N',
+ 'o' => 'O',
+ 'p' => 'P',
+ 'q' => 'Q',
+ 'r' => 'R',
+ 's' => 'S',
+ 't' => 'T',
+ 'u' => 'U',
+ 'v' => 'V',
+ 'w' => 'W',
+ 'x' => 'X',
+ 'y' => 'Y',
+ 'z' => 'Z',
+ '𐐨' => '𐐀',
+ '𐐩' => '𐐁',
+ '𐐪' => '𐐂',
+ '𐐫' => '𐐃',
+ '𐐬' => '𐐄',
+ '𐐭' => '𐐅',
+ '𐐮' => '𐐆',
+ '𐐯' => '𐐇',
+ '𐐰' => '𐐈',
+ '𐐱' => '𐐉',
+ '𐐲' => '𐐊',
+ '𐐳' => '𐐋',
+ '𐐴' => '𐐌',
+ '𐐵' => '𐐍',
+ '𐐶' => '𐐎',
+ '𐐷' => '𐐏',
+ '𐐸' => '𐐐',
+ '𐐹' => '𐐑',
+ '𐐺' => '𐐒',
+ '𐐻' => '𐐓',
+ '𐐼' => '𐐔',
+ '𐐽' => '𐐕',
+ '𐐾' => '𐐖',
+ '𐐿' => '𐐗',
+ '𐑀' => '𐐘',
+ '𐑁' => '𐐙',
+ '𐑂' => '𐐚',
+ '𐑃' => '𐐛',
+ '𐑄' => '𐐜',
+ '𐑅' => '𐐝',
+ '𐑆' => '𐐞',
+ '𐑇' => '𐐟',
+ '𐑈' => '𐐠',
+ '𐑉' => '𐐡',
+ '𐑊' => '𐐢',
+ '𐑋' => '𐐣',
+ '𐑌' => '𐐤',
+ '𐑍' => '𐐥',
+ '𐑎' => '𐐦',
+ '𐑏' => '𐐧',
+ '𑣀' => '𑢠',
+ '𑣁' => '𑢡',
+ '𑣂' => '𑢢',
+ '𑣃' => '𑢣',
+ '𑣄' => '𑢤',
+ '𑣅' => '𑢥',
+ '𑣆' => '𑢦',
+ '𑣇' => '𑢧',
+ '𑣈' => '𑢨',
+ '𑣉' => '𑢩',
+ '𑣊' => '𑢪',
+ '𑣋' => '𑢫',
+ '𑣌' => '𑢬',
+ '𑣍' => '𑢭',
+ '𑣎' => '𑢮',
+ '𑣏' => '𑢯',
+ '𑣐' => '𑢰',
+ '𑣑' => '𑢱',
+ '𑣒' => '𑢲',
+ '𑣓' => '𑢳',
+ '𑣔' => '𑢴',
+ '𑣕' => '𑢵',
+ '𑣖' => '𑢶',
+ '𑣗' => '𑢷',
+ '𑣘' => '𑢸',
+ '𑣙' => '𑢹',
+ '𑣚' => '𑢺',
+ '𑣛' => '𑢻',
+ '𑣜' => '𑢼',
+ '𑣝' => '𑢽',
+ '𑣞' => '𑢾',
+ '𑣟' => '𑢿',
+);
diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php
new file mode 100755
index 0000000..2fdcc5a
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/bootstrap.php
@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Mbstring as p;
+
+if (!function_exists('mb_strlen')) {
+ define('MB_CASE_UPPER', 0);
+ define('MB_CASE_LOWER', 1);
+ define('MB_CASE_TITLE', 2);
+
+ function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); }
+ function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); }
+ function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); }
+ function mb_decode_numericentity($s, $convmap, $enc = null) { return p\Mbstring::mb_decode_numericentity($s, $convmap, $enc); }
+ function mb_encode_numericentity($s, $convmap, $enc = null, $is_hex = false) { return p\Mbstring::mb_encode_numericentity($s, $convmap, $enc, $is_hex); }
+ function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); }
+ function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); }
+ function mb_language($lang = null) { return p\Mbstring::mb_language($lang); }
+ function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
+ function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
+ function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); }
+ function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); }
+ function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); }
+ function mb_parse_str($s, &$result = array()) { parse_str($s, $result); }
+ function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); }
+ function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); }
+ function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); }
+ function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); }
+ function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); }
+ function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); }
+ function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); }
+ function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); }
+ function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); }
+ function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); }
+ function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); }
+ function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); }
+ function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); }
+ function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
+ function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); }
+ function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); }
+ function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); }
+ function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); }
+ function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); }
+ function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); }
+}
+if (!function_exists('mb_chr')) {
+ function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); }
+ function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); }
+ function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); }
+}
diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json
new file mode 100755
index 0000000..0bce087
--- /dev/null
+++ b/vendor/symfony/polyfill-mbstring/composer.json
@@ -0,0 +1,34 @@
+{
+ "name": "symfony/polyfill-mbstring",
+ "type": "library",
+ "description": "Symfony polyfill for the Mbstring extension",
+ "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" },
+ "files": [ "bootstrap.php" ]
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.11-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/polyfill-php70/LICENSE b/vendor/symfony/polyfill-php70/LICENSE
new file mode 100755
index 0000000..4cd8bdd
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/polyfill-php70/Php70.php b/vendor/symfony/polyfill-php70/Php70.php
new file mode 100755
index 0000000..7f1ad08
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Php70.php
@@ -0,0 +1,74 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Php70;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @internal
+ */
+final class Php70
+{
+ public static function intdiv($dividend, $divisor)
+ {
+ $dividend = self::intArg($dividend, __FUNCTION__, 1);
+ $divisor = self::intArg($divisor, __FUNCTION__, 2);
+
+ if (0 === $divisor) {
+ throw new \DivisionByZeroError('Division by zero');
+ }
+ if (-1 === $divisor && ~PHP_INT_MAX === $dividend) {
+ throw new \ArithmeticError('Division of PHP_INT_MIN by -1 is not an integer');
+ }
+
+ return ($dividend - ($dividend % $divisor)) / $divisor;
+ }
+
+ public static function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0)
+ {
+ $count = 0;
+ $result = (string) $subject;
+ if (0 === $limit = self::intArg($limit, __FUNCTION__, 3)) {
+ return $result;
+ }
+
+ foreach ($patterns as $pattern => $callback) {
+ $result = preg_replace_callback($pattern, $callback, $result, $limit, $c);
+ $count += $c;
+ }
+
+ return $result;
+ }
+
+ public static function error_clear_last()
+ {
+ static $handler;
+ if (!$handler) {
+ $handler = function () { return false; };
+ }
+ set_error_handler($handler);
+ @trigger_error('');
+ restore_error_handler();
+ }
+
+ private static function intArg($value, $caller, $pos)
+ {
+ if (\is_int($value)) {
+ return $value;
+ }
+ if (!\is_numeric($value) || PHP_INT_MAX <= ($value += 0) || ~PHP_INT_MAX >= $value) {
+ throw new \TypeError(sprintf('%s() expects parameter %d to be integer, %s given', $caller, $pos, \gettype($value)));
+ }
+
+ return (int) $value;
+ }
+}
diff --git a/vendor/symfony/polyfill-php70/README.md b/vendor/symfony/polyfill-php70/README.md
new file mode 100755
index 0000000..04988c6
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/README.md
@@ -0,0 +1,28 @@
+Symfony Polyfill / Php70
+========================
+
+This component provides features unavailable in releases prior to PHP 7.0:
+
+- [`intdiv`](http://php.net/intdiv)
+- [`preg_replace_callback_array`](http://php.net/preg_replace_callback_array)
+- [`error_clear_last`](http://php.net/error_clear_last)
+- `random_bytes` and `random_int` (from [paragonie/random_compat](https://github.com/paragonie/random_compat))
+- [`*Error` throwable classes](http://php.net/Error)
+- [`PHP_INT_MIN`](http://php.net/manual/en/reserved.constants.php#constant.php-int-min)
+- `SessionUpdateTimestampHandlerInterface`
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+Compatibility notes
+===================
+
+To write portable code between PHP5 and PHP7, some care must be taken:
+- `\*Error` exceptions must be caught before `\Exception`;
+- after calling `error_clear_last()`, the result of `$e = error_get_last()` must be
+ verified using `isset($e['message'][0])` instead of `null !== $e`.
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php b/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php
new file mode 100755
index 0000000..6819124
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php
@@ -0,0 +1,5 @@
+<?php
+
+class ArithmeticError extends Error
+{
+}
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/AssertionError.php b/vendor/symfony/polyfill-php70/Resources/stubs/AssertionError.php
new file mode 100755
index 0000000..acb1250
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/AssertionError.php
@@ -0,0 +1,5 @@
+<?php
+
+class AssertionError extends Error
+{
+}
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php b/vendor/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php
new file mode 100755
index 0000000..c99278b
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php
@@ -0,0 +1,5 @@
+<?php
+
+class DivisionByZeroError extends Error
+{
+}
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/Error.php b/vendor/symfony/polyfill-php70/Resources/stubs/Error.php
new file mode 100755
index 0000000..405847f
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/Error.php
@@ -0,0 +1,5 @@
+<?php
+
+class Error extends Exception
+{
+}
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/ParseError.php b/vendor/symfony/polyfill-php70/Resources/stubs/ParseError.php
new file mode 100755
index 0000000..2dd447d
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/ParseError.php
@@ -0,0 +1,5 @@
+<?php
+
+class ParseError extends Error
+{
+}
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php b/vendor/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php
new file mode 100755
index 0000000..0cc02c8
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php
@@ -0,0 +1,23 @@
+<?php
+
+interface SessionUpdateTimestampHandlerInterface
+{
+ /**
+ * Checks if a session identifier already exists or not.
+ *
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function validateId($key);
+
+ /**
+ * Updates the timestamp of a session when its data didn't change.
+ *
+ * @param string $key
+ * @param string $val
+ *
+ * @return bool
+ */
+ public function updateTimestamp($key, $val);
+}
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/TypeError.php b/vendor/symfony/polyfill-php70/Resources/stubs/TypeError.php
new file mode 100755
index 0000000..2bed1b4
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/TypeError.php
@@ -0,0 +1,5 @@
+<?php
+
+class TypeError extends Error
+{
+}
diff --git a/vendor/symfony/polyfill-php70/bootstrap.php b/vendor/symfony/polyfill-php70/bootstrap.php
new file mode 100755
index 0000000..445c398
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/bootstrap.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Php70 as p;
+
+if (PHP_VERSION_ID < 70000) {
+ if (!defined('PHP_INT_MIN')) {
+ define('PHP_INT_MIN', ~PHP_INT_MAX);
+ }
+ if (!function_exists('intdiv')) {
+ function intdiv($dividend, $divisor) { return p\Php70::intdiv($dividend, $divisor); }
+ }
+ if (!function_exists('preg_replace_callback_array')) {
+ function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0) { return p\Php70::preg_replace_callback_array($patterns, $subject, $limit, $count); }
+ }
+ if (!function_exists('error_clear_last')) {
+ function error_clear_last() { return p\Php70::error_clear_last(); }
+ }
+}
diff --git a/vendor/symfony/polyfill-php70/composer.json b/vendor/symfony/polyfill-php70/composer.json
new file mode 100755
index 0000000..923f309
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "symfony/polyfill-php70",
+ "type": "library",
+ "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
+ "keywords": ["polyfill", "shim", "compatibility", "portable"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3",
+ "paragonie/random_compat": "~1.0|~2.0|~9.99"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Php70\\": "" },
+ "files": [ "bootstrap.php" ],
+ "classmap": [ "Resources/stubs" ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.11-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/polyfill-php72/LICENSE b/vendor/symfony/polyfill-php72/LICENSE
new file mode 100755
index 0000000..4cd8bdd
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/polyfill-php72/Php72.php b/vendor/symfony/polyfill-php72/Php72.php
new file mode 100755
index 0000000..d531e84
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/Php72.php
@@ -0,0 +1,216 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Php72;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
+ *
+ * @internal
+ */
+final class Php72
+{
+ private static $hashMask;
+
+ public static function utf8_encode($s)
+ {
+ $s .= $s;
+ $len = \strlen($s);
+
+ for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) {
+ switch (true) {
+ case $s[$i] < "\x80": $s[$j] = $s[$i]; break;
+ case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break;
+ default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break;
+ }
+ }
+
+ return substr($s, 0, $j);
+ }
+
+ public static function utf8_decode($s)
+ {
+ $s = (string) $s;
+ $len = \strlen($s);
+
+ for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) {
+ switch ($s[$i] & "\xF0") {
+ case "\xC0":
+ case "\xD0":
+ $c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F");
+ $s[$j] = $c < 256 ? \chr($c) : '?';
+ break;
+
+ case "\xF0":
+ ++$i;
+ // no break
+
+ case "\xE0":
+ $s[$j] = '?';
+ $i += 2;
+ break;
+
+ default:
+ $s[$j] = $s[$i];
+ }
+ }
+
+ return substr($s, 0, $j);
+ }
+
+ public static function php_os_family()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ return 'Windows';
+ }
+
+ $map = array(
+ 'Darwin' => 'Darwin',
+ 'DragonFly' => 'BSD',
+ 'FreeBSD' => 'BSD',
+ 'NetBSD' => 'BSD',
+ 'OpenBSD' => 'BSD',
+ 'Linux' => 'Linux',
+ 'SunOS' => 'Solaris',
+ );
+
+ return isset($map[PHP_OS]) ? $map[PHP_OS] : 'Unknown';
+ }
+
+ public static function spl_object_id($object)
+ {
+ if (null === self::$hashMask) {
+ self::initHashMask();
+ }
+ if (null === $hash = spl_object_hash($object)) {
+ return;
+ }
+
+ return self::$hashMask ^ hexdec(substr($hash, 16 - \PHP_INT_SIZE, \PHP_INT_SIZE));
+ }
+
+ public static function sapi_windows_vt100_support($stream, $enable = null)
+ {
+ if (!\is_resource($stream)) {
+ trigger_error('sapi_windows_vt100_support() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING);
+
+ return false;
+ }
+
+ $meta = stream_get_meta_data($stream);
+
+ if ('STDIO' !== $meta['stream_type']) {
+ trigger_error('sapi_windows_vt100_support() was not able to analyze the specified stream', E_USER_WARNING);
+
+ return false;
+ }
+
+ // We cannot actually disable vt100 support if it is set
+ if (false === $enable || !self::stream_isatty($stream)) {
+ return false;
+ }
+
+ // The native function does not apply to stdin
+ $meta = array_map('strtolower', $meta);
+ $stdin = 'php://stdin' === $meta['uri'] || 'php://fd/0' === $meta['uri'];
+
+ return !$stdin
+ && (false !== getenv('ANSICON')
+ || 'ON' === getenv('ConEmuANSI')
+ || 'xterm' === getenv('TERM')
+ || 'Hyper' === getenv('TERM_PROGRAM'));
+ }
+
+ public static function stream_isatty($stream)
+ {
+ if (!\is_resource($stream)) {
+ trigger_error('stream_isatty() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING);
+
+ return false;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $stat = @fstat($stream);
+ // Check if formatted mode is S_IFCHR
+ return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
+ }
+
+ return \function_exists('posix_isatty') && @posix_isatty($stream);
+ }
+
+ private static function initHashMask()
+ {
+ $obj = (object) array();
+ self::$hashMask = -1;
+
+ // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below
+ $obFuncs = array('ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush');
+ foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) {
+ if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) {
+ $frame['line'] = 0;
+ break;
+ }
+ }
+ if (!empty($frame['line'])) {
+ ob_start();
+ debug_zval_dump($obj);
+ self::$hashMask = (int) substr(ob_get_clean(), 17);
+ }
+
+ self::$hashMask ^= hexdec(substr(spl_object_hash($obj), 16 - \PHP_INT_SIZE, \PHP_INT_SIZE));
+ }
+
+ public static function mb_chr($code, $encoding = null)
+ {
+ if (0x80 > $code %= 0x200000) {
+ $s = \chr($code);
+ } elseif (0x800 > $code) {
+ $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
+ } elseif (0x10000 > $code) {
+ $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+ } else {
+ $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
+ }
+
+ if ('UTF-8' !== $encoding) {
+ $s = mb_convert_encoding($s, $encoding, 'UTF-8');
+ }
+
+ return $s;
+ }
+
+ public static function mb_ord($s, $encoding = null)
+ {
+ if (null == $encoding) {
+ $s = mb_convert_encoding($s, 'UTF-8');
+ } elseif ('UTF-8' !== $encoding) {
+ $s = mb_convert_encoding($s, 'UTF-8', $encoding);
+ }
+
+ if (1 === \strlen($s)) {
+ return \ord($s);
+ }
+
+ $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
+ if (0xF0 <= $code) {
+ return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
+ }
+ if (0xE0 <= $code) {
+ return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
+ }
+ if (0xC0 <= $code) {
+ return (($code - 0xC0) << 6) + $s[2] - 0x80;
+ }
+
+ return $code;
+ }
+}
diff --git a/vendor/symfony/polyfill-php72/README.md b/vendor/symfony/polyfill-php72/README.md
new file mode 100755
index 0000000..82c45f7
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/README.md
@@ -0,0 +1,27 @@
+Symfony Polyfill / Php72
+========================
+
+This component provides functions added to PHP 7.2 core:
+
+- [`spl_object_id`](https://php.net/spl_object_id)
+- [`stream_isatty`](https://php.net/stream_isatty)
+
+On Windows only:
+
+- [`sapi_windows_vt100_support`](https://php.net/sapi_windows_vt100_support)
+
+Moved to core since 7.2 (was in the optional XML extension earlier):
+
+- [`utf8_encode`](https://php.net/utf8_encode)
+- [`utf8_decode`](https://php.net/utf8_decode)
+
+Also, it provides a constant added to PHP 7.2:
+- [`PHP_OS_FAMILY`](http://php.net/manual/en/reserved.constants.php#constant.php-os-family)
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-php72/bootstrap.php b/vendor/symfony/polyfill-php72/bootstrap.php
new file mode 100755
index 0000000..519056d
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/bootstrap.php
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Php72 as p;
+
+if (PHP_VERSION_ID < 70200) {
+ if ('\\' === DIRECTORY_SEPARATOR && !function_exists('sapi_windows_vt100_support')) {
+ function sapi_windows_vt100_support($stream, $enable = null) { return p\Php72::sapi_windows_vt100_support($stream, $enable); }
+ }
+ if (!function_exists('stream_isatty')) {
+ function stream_isatty($stream) { return p\Php72::stream_isatty($stream); }
+ }
+ if (!function_exists('utf8_encode')) {
+ function utf8_encode($s) { return p\Php72::utf8_encode($s); }
+ function utf8_decode($s) { return p\Php72::utf8_decode($s); }
+ }
+ if (!function_exists('spl_object_id')) {
+ function spl_object_id($s) { return p\Php72::spl_object_id($s); }
+ }
+ if (!defined('PHP_OS_FAMILY')) {
+ define('PHP_OS_FAMILY', p\Php72::php_os_family());
+ }
+ if (!function_exists('mb_chr')) {
+ function mb_ord($s, $enc = null) { return p\Php72::mb_ord($s, $enc); }
+ function mb_chr($code, $enc = null) { return p\Php72::mb_chr($code, $enc); }
+ function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); }
+ }
+}
diff --git a/vendor/symfony/polyfill-php72/composer.json b/vendor/symfony/polyfill-php72/composer.json
new file mode 100755
index 0000000..8282555
--- /dev/null
+++ b/vendor/symfony/polyfill-php72/composer.json
@@ -0,0 +1,31 @@
+{
+ "name": "symfony/polyfill-php72",
+ "type": "library",
+ "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+ "keywords": ["polyfill", "shim", "compatibility", "portable"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Php72\\": "" },
+ "files": [ "bootstrap.php" ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.11-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/process/.gitignore b/vendor/symfony/process/.gitignore
new file mode 100755
index 0000000..c49a5d8
--- /dev/null
+++ b/vendor/symfony/process/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/vendor/symfony/process/CHANGELOG.md b/vendor/symfony/process/CHANGELOG.md
new file mode 100755
index 0000000..31d0638
--- /dev/null
+++ b/vendor/symfony/process/CHANGELOG.md
@@ -0,0 +1,90 @@
+CHANGELOG
+=========
+
+4.2.0
+-----
+
+ * added the `Process::fromShellCommandline()` to run commands in a shell wrapper
+ * deprecated passing a command as string when creating a `Process` instance
+ * deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods
+ * added the `Process::waitUntil()` method to wait for the process only for a
+ specific output, then continue the normal execution of your application
+
+4.1.0
+-----
+
+ * added the `Process::isTtySupported()` method that allows to check for TTY support
+ * made `PhpExecutableFinder` look for the `PHP_BINARY` env var when searching the php binary
+ * added the `ProcessSignaledException` class to properly catch signaled process errors
+
+4.0.0
+-----
+
+ * environment variables will always be inherited
+ * added a second `array $env = []` argument to the `start()`, `run()`,
+ `mustRun()`, and `restart()` methods of the `Process` class
+ * added a second `array $env = []` argument to the `start()` method of the
+ `PhpProcess` class
+ * the `ProcessUtils::escapeArgument()` method has been removed
+ * the `areEnvironmentVariablesInherited()`, `getOptions()`, and `setOptions()`
+ methods of the `Process` class have been removed
+ * support for passing `proc_open()` options has been removed
+ * removed the `ProcessBuilder` class, use the `Process` class instead
+ * removed the `getEnhanceWindowsCompatibility()` and `setEnhanceWindowsCompatibility()` methods of the `Process` class
+ * passing a not existing working directory to the constructor of the `Symfony\Component\Process\Process` class is not
+ supported anymore
+
+3.4.0
+-----
+
+ * deprecated the ProcessBuilder class
+ * deprecated calling `Process::start()` without setting a valid working directory beforehand (via `setWorkingDirectory()` or constructor)
+
+3.3.0
+-----
+
+ * added command line arrays in the `Process` class
+ * added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods
+ * deprecated the `ProcessUtils::escapeArgument()` method
+ * deprecated not inheriting environment variables
+ * deprecated configuring `proc_open()` options
+ * deprecated configuring enhanced Windows compatibility
+ * deprecated configuring enhanced sigchild compatibility
+
+2.5.0
+-----
+
+ * added support for PTY mode
+ * added the convenience method "mustRun"
+ * deprecation: Process::setStdin() is deprecated in favor of Process::setInput()
+ * deprecation: Process::getStdin() is deprecated in favor of Process::getInput()
+ * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types
+
+2.4.0
+-----
+
+ * added the ability to define an idle timeout
+
+2.3.0
+-----
+
+ * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows
+ * added Process::signal()
+ * added Process::getPid()
+ * added support for a TTY mode
+
+2.2.0
+-----
+
+ * added ProcessBuilder::setArguments() to reset the arguments on a builder
+ * added a way to retrieve the standard and error output incrementally
+ * added Process:restart()
+
+2.1.0
+-----
+
+ * added support for non-blocking processes (start(), wait(), isRunning(), stop())
+ * enhanced Windows compatibility
+ * added Process::getExitCodeText() that returns a string representation for
+ the exit code returned by the process
+ * added ProcessBuilder
diff --git a/vendor/symfony/process/Exception/ExceptionInterface.php b/vendor/symfony/process/Exception/ExceptionInterface.php
new file mode 100755
index 0000000..bd4a604
--- /dev/null
+++ b/vendor/symfony/process/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * Marker Interface for the Process Component.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/vendor/symfony/process/Exception/InvalidArgumentException.php b/vendor/symfony/process/Exception/InvalidArgumentException.php
new file mode 100755
index 0000000..926ee21
--- /dev/null
+++ b/vendor/symfony/process/Exception/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * InvalidArgumentException for the Process Component.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/process/Exception/LogicException.php b/vendor/symfony/process/Exception/LogicException.php
new file mode 100755
index 0000000..be3d490
--- /dev/null
+++ b/vendor/symfony/process/Exception/LogicException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * LogicException for the Process Component.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/process/Exception/ProcessFailedException.php b/vendor/symfony/process/Exception/ProcessFailedException.php
new file mode 100755
index 0000000..328acfd
--- /dev/null
+++ b/vendor/symfony/process/Exception/ProcessFailedException.php
@@ -0,0 +1,54 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception for failed processes.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ProcessFailedException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ if ($process->isSuccessful()) {
+ throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
+ }
+
+ $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
+ $process->getCommandLine(),
+ $process->getExitCode(),
+ $process->getExitCodeText(),
+ $process->getWorkingDirectory()
+ );
+
+ if (!$process->isOutputDisabled()) {
+ $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
+ $process->getOutput(),
+ $process->getErrorOutput()
+ );
+ }
+
+ parent::__construct($error);
+
+ $this->process = $process;
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+}
diff --git a/vendor/symfony/process/Exception/ProcessSignaledException.php b/vendor/symfony/process/Exception/ProcessSignaledException.php
new file mode 100755
index 0000000..d4d3227
--- /dev/null
+++ b/vendor/symfony/process/Exception/ProcessSignaledException.php
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process has been signaled.
+ *
+ * @author Sullivan Senechal <soullivaneuh@gmail.com>
+ */
+final class ProcessSignaledException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ $this->process = $process;
+
+ parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
+ }
+
+ public function getProcess(): Process
+ {
+ return $this->process;
+ }
+
+ public function getSignal(): int
+ {
+ return $this->getProcess()->getTermSignal();
+ }
+}
diff --git a/vendor/symfony/process/Exception/ProcessTimedOutException.php b/vendor/symfony/process/Exception/ProcessTimedOutException.php
new file mode 100755
index 0000000..e1f6445
--- /dev/null
+++ b/vendor/symfony/process/Exception/ProcessTimedOutException.php
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process times out.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ProcessTimedOutException extends RuntimeException
+{
+ const TYPE_GENERAL = 1;
+ const TYPE_IDLE = 2;
+
+ private $process;
+ private $timeoutType;
+
+ public function __construct(Process $process, int $timeoutType)
+ {
+ $this->process = $process;
+ $this->timeoutType = $timeoutType;
+
+ parent::__construct(sprintf(
+ 'The process "%s" exceeded the timeout of %s seconds.',
+ $process->getCommandLine(),
+ $this->getExceededTimeout()
+ ));
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+
+ public function isGeneralTimeout()
+ {
+ return self::TYPE_GENERAL === $this->timeoutType;
+ }
+
+ public function isIdleTimeout()
+ {
+ return self::TYPE_IDLE === $this->timeoutType;
+ }
+
+ public function getExceededTimeout()
+ {
+ switch ($this->timeoutType) {
+ case self::TYPE_GENERAL:
+ return $this->process->getTimeout();
+
+ case self::TYPE_IDLE:
+ return $this->process->getIdleTimeout();
+
+ default:
+ throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
+ }
+ }
+}
diff --git a/vendor/symfony/process/Exception/RuntimeException.php b/vendor/symfony/process/Exception/RuntimeException.php
new file mode 100755
index 0000000..adead25
--- /dev/null
+++ b/vendor/symfony/process/Exception/RuntimeException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * RuntimeException for the Process Component.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/process/ExecutableFinder.php b/vendor/symfony/process/ExecutableFinder.php
new file mode 100755
index 0000000..cb4345e
--- /dev/null
+++ b/vendor/symfony/process/ExecutableFinder.php
@@ -0,0 +1,88 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * Generic executable finder.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ExecutableFinder
+{
+ private $suffixes = ['.exe', '.bat', '.cmd', '.com'];
+
+ /**
+ * Replaces default suffixes of executable.
+ */
+ public function setSuffixes(array $suffixes)
+ {
+ $this->suffixes = $suffixes;
+ }
+
+ /**
+ * Adds new possible suffix to check for executable.
+ *
+ * @param string $suffix
+ */
+ public function addSuffix($suffix)
+ {
+ $this->suffixes[] = $suffix;
+ }
+
+ /**
+ * Finds an executable by name.
+ *
+ * @param string $name The executable name (without the extension)
+ * @param string|null $default The default to return if no executable is found
+ * @param array $extraDirs Additional dirs to check into
+ *
+ * @return string|null The executable path or default value
+ */
+ public function find($name, $default = null, array $extraDirs = [])
+ {
+ if (ini_get('open_basedir')) {
+ $searchPath = array_merge(explode(PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
+ $dirs = [];
+ foreach ($searchPath as $path) {
+ // Silencing against https://bugs.php.net/69240
+ if (@is_dir($path)) {
+ $dirs[] = $path;
+ } else {
+ if (basename($path) == $name && @is_executable($path)) {
+ return $path;
+ }
+ }
+ }
+ } else {
+ $dirs = array_merge(
+ explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
+ $extraDirs
+ );
+ }
+
+ $suffixes = [''];
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $pathExt = getenv('PATHEXT');
+ $suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
+ }
+ foreach ($suffixes as $suffix) {
+ foreach ($dirs as $dir) {
+ if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
+ return $file;
+ }
+ }
+ }
+
+ return $default;
+ }
+}
diff --git a/vendor/symfony/process/InputStream.php b/vendor/symfony/process/InputStream.php
new file mode 100755
index 0000000..426ffa3
--- /dev/null
+++ b/vendor/symfony/process/InputStream.php
@@ -0,0 +1,90 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * Provides a way to continuously write to the input of a Process until the InputStream is closed.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class InputStream implements \IteratorAggregate
+{
+ /** @var callable|null */
+ private $onEmpty = null;
+ private $input = [];
+ private $open = true;
+
+ /**
+ * Sets a callback that is called when the write buffer becomes empty.
+ */
+ public function onEmpty(callable $onEmpty = null)
+ {
+ $this->onEmpty = $onEmpty;
+ }
+
+ /**
+ * Appends an input to the write buffer.
+ *
+ * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar,
+ * stream resource or \Traversable
+ */
+ public function write($input)
+ {
+ if (null === $input) {
+ return;
+ }
+ if ($this->isClosed()) {
+ throw new RuntimeException(sprintf('%s is closed', static::class));
+ }
+ $this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
+ }
+
+ /**
+ * Closes the write buffer.
+ */
+ public function close()
+ {
+ $this->open = false;
+ }
+
+ /**
+ * Tells whether the write buffer is closed or not.
+ */
+ public function isClosed()
+ {
+ return !$this->open;
+ }
+
+ public function getIterator()
+ {
+ $this->open = true;
+
+ while ($this->open || $this->input) {
+ if (!$this->input) {
+ yield '';
+ continue;
+ }
+ $current = array_shift($this->input);
+
+ if ($current instanceof \Iterator) {
+ yield from $current;
+ } else {
+ yield $current;
+ }
+ if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
+ $this->write($onEmpty($this));
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/process/LICENSE b/vendor/symfony/process/LICENSE
new file mode 100755
index 0000000..a677f43
--- /dev/null
+++ b/vendor/symfony/process/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/process/PhpExecutableFinder.php b/vendor/symfony/process/PhpExecutableFinder.php
new file mode 100755
index 0000000..461ea13
--- /dev/null
+++ b/vendor/symfony/process/PhpExecutableFinder.php
@@ -0,0 +1,101 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * An executable finder specifically designed for the PHP executable.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class PhpExecutableFinder
+{
+ private $executableFinder;
+
+ public function __construct()
+ {
+ $this->executableFinder = new ExecutableFinder();
+ }
+
+ /**
+ * Finds The PHP executable.
+ *
+ * @param bool $includeArgs Whether or not include command arguments
+ *
+ * @return string|false The PHP executable path or false if it cannot be found
+ */
+ public function find($includeArgs = true)
+ {
+ if ($php = getenv('PHP_BINARY')) {
+ if (!is_executable($php)) {
+ $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v';
+ if ($php = strtok(exec($command.' '.escapeshellarg($php)), PHP_EOL)) {
+ if (!is_executable($php)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ return $php;
+ }
+
+ $args = $this->findArguments();
+ $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
+
+ // PHP_BINARY return the current sapi executable
+ if (PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) {
+ return PHP_BINARY.$args;
+ }
+
+ if ($php = getenv('PHP_PATH')) {
+ if (!@is_executable($php)) {
+ return false;
+ }
+
+ return $php;
+ }
+
+ if ($php = getenv('PHP_PEAR_PHP_BIN')) {
+ if (@is_executable($php)) {
+ return $php;
+ }
+ }
+
+ if (@is_executable($php = PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) {
+ return $php;
+ }
+
+ $dirs = [PHP_BINDIR];
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $dirs[] = 'C:\xampp\php\\';
+ }
+
+ return $this->executableFinder->find('php', false, $dirs);
+ }
+
+ /**
+ * Finds the PHP executable arguments.
+ *
+ * @return array The PHP executable arguments
+ */
+ public function findArguments()
+ {
+ $arguments = [];
+ if ('phpdbg' === \PHP_SAPI) {
+ $arguments[] = '-qrr';
+ }
+
+ return $arguments;
+ }
+}
diff --git a/vendor/symfony/process/PhpProcess.php b/vendor/symfony/process/PhpProcess.php
new file mode 100755
index 0000000..126d9b7
--- /dev/null
+++ b/vendor/symfony/process/PhpProcess.php
@@ -0,0 +1,75 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * PhpProcess runs a PHP script in an independent process.
+ *
+ * $p = new PhpProcess('<?php echo "foo"; ?>');
+ * $p->run();
+ * print $p->getOutput()."\n";
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class PhpProcess extends Process
+{
+ /**
+ * @param string $script The PHP script to run (as a string)
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param int $timeout The timeout in seconds
+ * @param array|null $php Path to the PHP binary to use with any additional arguments
+ */
+ public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60, array $php = null)
+ {
+ if (null === $php) {
+ $executableFinder = new PhpExecutableFinder();
+ $php = $executableFinder->find(false);
+ $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
+ }
+ if ('phpdbg' === \PHP_SAPI) {
+ $file = tempnam(sys_get_temp_dir(), 'dbg');
+ file_put_contents($file, $script);
+ register_shutdown_function('unlink', $file);
+ $php[] = $file;
+ $script = null;
+ }
+
+ parent::__construct($php, $cwd, $env, $script, $timeout);
+ }
+
+ /**
+ * Sets the path to the PHP binary to use.
+ *
+ * @deprecated since Symfony 4.2, use the $php argument of the constructor instead.
+ */
+ public function setPhpBinary($php)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the $php argument of the constructor instead.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->setCommandLine($php);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start(callable $callback = null, array $env = [])
+ {
+ if (null === $this->getCommandLine()) {
+ throw new RuntimeException('Unable to find the PHP executable.');
+ }
+
+ parent::start($callback, $env);
+ }
+}
diff --git a/vendor/symfony/process/Pipes/AbstractPipes.php b/vendor/symfony/process/Pipes/AbstractPipes.php
new file mode 100755
index 0000000..23886b6
--- /dev/null
+++ b/vendor/symfony/process/Pipes/AbstractPipes.php
@@ -0,0 +1,178 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+abstract class AbstractPipes implements PipesInterface
+{
+ public $pipes = [];
+
+ private $inputBuffer = '';
+ private $input;
+ private $blocked = true;
+ private $lastError;
+
+ /**
+ * @param resource|string|int|float|bool|\Iterator|null $input
+ */
+ public function __construct($input)
+ {
+ if (\is_resource($input) || $input instanceof \Iterator) {
+ $this->input = $input;
+ } elseif (\is_string($input)) {
+ $this->inputBuffer = $input;
+ } else {
+ $this->inputBuffer = (string) $input;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ foreach ($this->pipes as $pipe) {
+ fclose($pipe);
+ }
+ $this->pipes = [];
+ }
+
+ /**
+ * Returns true if a system call has been interrupted.
+ *
+ * @return bool
+ */
+ protected function hasSystemCallBeenInterrupted()
+ {
+ $lastError = $this->lastError;
+ $this->lastError = null;
+
+ // stream_select returns false when the `select` system call is interrupted by an incoming signal
+ return null !== $lastError && false !== stripos($lastError, 'interrupted system call');
+ }
+
+ /**
+ * Unblocks streams.
+ */
+ protected function unblock()
+ {
+ if (!$this->blocked) {
+ return;
+ }
+
+ foreach ($this->pipes as $pipe) {
+ stream_set_blocking($pipe, 0);
+ }
+ if (\is_resource($this->input)) {
+ stream_set_blocking($this->input, 0);
+ }
+
+ $this->blocked = false;
+ }
+
+ /**
+ * Writes input to stdin.
+ *
+ * @throws InvalidArgumentException When an input iterator yields a non supported value
+ */
+ protected function write()
+ {
+ if (!isset($this->pipes[0])) {
+ return;
+ }
+ $input = $this->input;
+
+ if ($input instanceof \Iterator) {
+ if (!$input->valid()) {
+ $input = null;
+ } elseif (\is_resource($input = $input->current())) {
+ stream_set_blocking($input, 0);
+ } elseif (!isset($this->inputBuffer[0])) {
+ if (!\is_string($input)) {
+ if (!is_scalar($input)) {
+ throw new InvalidArgumentException(sprintf('%s yielded a value of type "%s", but only scalars and stream resources are supported', \get_class($this->input), \gettype($input)));
+ }
+ $input = (string) $input;
+ }
+ $this->inputBuffer = $input;
+ $this->input->next();
+ $input = null;
+ } else {
+ $input = null;
+ }
+ }
+
+ $r = $e = [];
+ $w = [$this->pipes[0]];
+
+ // let's have a look if something changed in streams
+ if (false === @stream_select($r, $w, $e, 0, 0)) {
+ return;
+ }
+
+ foreach ($w as $stdin) {
+ if (isset($this->inputBuffer[0])) {
+ $written = fwrite($stdin, $this->inputBuffer);
+ $this->inputBuffer = substr($this->inputBuffer, $written);
+ if (isset($this->inputBuffer[0])) {
+ return [$this->pipes[0]];
+ }
+ }
+
+ if ($input) {
+ for (;;) {
+ $data = fread($input, self::CHUNK_SIZE);
+ if (!isset($data[0])) {
+ break;
+ }
+ $written = fwrite($stdin, $data);
+ $data = substr($data, $written);
+ if (isset($data[0])) {
+ $this->inputBuffer = $data;
+
+ return [$this->pipes[0]];
+ }
+ }
+ if (feof($input)) {
+ if ($this->input instanceof \Iterator) {
+ $this->input->next();
+ } else {
+ $this->input = null;
+ }
+ }
+ }
+ }
+
+ // no input to read on resource, buffer is empty
+ if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
+ $this->input = null;
+ fclose($this->pipes[0]);
+ unset($this->pipes[0]);
+ } elseif (!$w) {
+ return [$this->pipes[0]];
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public function handleError($type, $msg)
+ {
+ $this->lastError = $msg;
+ }
+}
diff --git a/vendor/symfony/process/Pipes/PipesInterface.php b/vendor/symfony/process/Pipes/PipesInterface.php
new file mode 100755
index 0000000..52bbe76
--- /dev/null
+++ b/vendor/symfony/process/Pipes/PipesInterface.php
@@ -0,0 +1,67 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+/**
+ * PipesInterface manages descriptors and pipes for the use of proc_open.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+interface PipesInterface
+{
+ const CHUNK_SIZE = 16384;
+
+ /**
+ * Returns an array of descriptors for the use of proc_open.
+ *
+ * @return array
+ */
+ public function getDescriptors();
+
+ /**
+ * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
+ *
+ * @return string[]
+ */
+ public function getFiles();
+
+ /**
+ * Reads data in file handles and pipes.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close pipes if they've reached EOF
+ *
+ * @return string[] An array of read data indexed by their fd
+ */
+ public function readAndWrite($blocking, $close = false);
+
+ /**
+ * Returns if the current state has open file handles or pipes.
+ *
+ * @return bool
+ */
+ public function areOpen();
+
+ /**
+ * Returns if pipes are able to read output.
+ *
+ * @return bool
+ */
+ public function haveReadSupport();
+
+ /**
+ * Closes file handles and pipes.
+ */
+ public function close();
+}
diff --git a/vendor/symfony/process/Pipes/UnixPipes.php b/vendor/symfony/process/Pipes/UnixPipes.php
new file mode 100755
index 0000000..875ee6a
--- /dev/null
+++ b/vendor/symfony/process/Pipes/UnixPipes.php
@@ -0,0 +1,153 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * UnixPipes implementation uses unix pipes as handles.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+class UnixPipes extends AbstractPipes
+{
+ private $ttyMode;
+ private $ptyMode;
+ private $haveReadSupport;
+
+ public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport)
+ {
+ $this->ttyMode = $ttyMode;
+ $this->ptyMode = $ptyMode;
+ $this->haveReadSupport = $haveReadSupport;
+
+ parent::__construct($input);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors()
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('/dev/null', 'c');
+
+ return [
+ ['pipe', 'r'],
+ $nullstream,
+ $nullstream,
+ ];
+ }
+
+ if ($this->ttyMode) {
+ return [
+ ['file', '/dev/tty', 'r'],
+ ['file', '/dev/tty', 'w'],
+ ['file', '/dev/tty', 'w'],
+ ];
+ }
+
+ if ($this->ptyMode && Process::isPtySupported()) {
+ return [
+ ['pty'],
+ ['pty'],
+ ['pty'],
+ ];
+ }
+
+ return [
+ ['pipe', 'r'],
+ ['pipe', 'w'], // stdout
+ ['pipe', 'w'], // stderr
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles()
+ {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite($blocking, $close = false)
+ {
+ $this->unblock();
+ $w = $this->write();
+
+ $read = $e = [];
+ $r = $this->pipes;
+ unset($r[0]);
+
+ // let's have a look if something changed in streams
+ set_error_handler([$this, 'handleError']);
+ if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
+ restore_error_handler();
+ // if a system call has been interrupted, forget about it, let's try again
+ // otherwise, an error occurred, let's reset pipes
+ if (!$this->hasSystemCallBeenInterrupted()) {
+ $this->pipes = [];
+ }
+
+ return $read;
+ }
+ restore_error_handler();
+
+ foreach ($r as $pipe) {
+ // prior PHP 5.4 the array passed to stream_select is modified and
+ // lose key association, we have to find back the key
+ $read[$type = array_search($pipe, $this->pipes, true)] = '';
+
+ do {
+ $data = fread($pipe, self::CHUNK_SIZE);
+ $read[$type] .= $data;
+ } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));
+
+ if (!isset($read[$type][0])) {
+ unset($read[$type]);
+ }
+
+ if ($close && feof($pipe)) {
+ fclose($pipe);
+ unset($this->pipes[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport()
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen()
+ {
+ return (bool) $this->pipes;
+ }
+}
diff --git a/vendor/symfony/process/Pipes/WindowsPipes.php b/vendor/symfony/process/Pipes/WindowsPipes.php
new file mode 100755
index 0000000..f44f33b
--- /dev/null
+++ b/vendor/symfony/process/Pipes/WindowsPipes.php
@@ -0,0 +1,191 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Process;
+
+/**
+ * WindowsPipes implementation uses temporary files as handles.
+ *
+ * @see https://bugs.php.net/bug.php?id=51800
+ * @see https://bugs.php.net/bug.php?id=65650
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+class WindowsPipes extends AbstractPipes
+{
+ private $files = [];
+ private $fileHandles = [];
+ private $lockHandles = [];
+ private $readBytes = [
+ Process::STDOUT => 0,
+ Process::STDERR => 0,
+ ];
+ private $haveReadSupport;
+
+ public function __construct($input, bool $haveReadSupport)
+ {
+ $this->haveReadSupport = $haveReadSupport;
+
+ if ($this->haveReadSupport) {
+ // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
+ // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
+ //
+ // @see https://bugs.php.net/bug.php?id=51800
+ $pipes = [
+ Process::STDOUT => Process::OUT,
+ Process::STDERR => Process::ERR,
+ ];
+ $tmpDir = sys_get_temp_dir();
+ $lastError = 'unknown reason';
+ set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
+ for ($i = 0;; ++$i) {
+ foreach ($pipes as $pipe => $name) {
+ $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
+
+ if (!$h = fopen($file.'.lock', 'w')) {
+ restore_error_handler();
+ throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $lastError));
+ }
+ if (!flock($h, LOCK_EX | LOCK_NB)) {
+ continue 2;
+ }
+ if (isset($this->lockHandles[$pipe])) {
+ flock($this->lockHandles[$pipe], LOCK_UN);
+ fclose($this->lockHandles[$pipe]);
+ }
+ $this->lockHandles[$pipe] = $h;
+
+ if (!fclose(fopen($file, 'w')) || !$h = fopen($file, 'r')) {
+ flock($this->lockHandles[$pipe], LOCK_UN);
+ fclose($this->lockHandles[$pipe]);
+ unset($this->lockHandles[$pipe]);
+ continue 2;
+ }
+ $this->fileHandles[$pipe] = $h;
+ $this->files[$pipe] = $file;
+ }
+ break;
+ }
+ restore_error_handler();
+ }
+
+ parent::__construct($input);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors()
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('NUL', 'c');
+
+ return [
+ ['pipe', 'r'],
+ $nullstream,
+ $nullstream,
+ ];
+ }
+
+ // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800)
+ // We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650
+ // So we redirect output within the commandline and pass the nul device to the process
+ return [
+ ['pipe', 'r'],
+ ['file', 'NUL', 'w'],
+ ['file', 'NUL', 'w'],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles()
+ {
+ return $this->files;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite($blocking, $close = false)
+ {
+ $this->unblock();
+ $w = $this->write();
+ $read = $r = $e = [];
+
+ if ($blocking) {
+ if ($w) {
+ @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
+ } elseif ($this->fileHandles) {
+ usleep(Process::TIMEOUT_PRECISION * 1E6);
+ }
+ }
+ foreach ($this->fileHandles as $type => $fileHandle) {
+ $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
+
+ if (isset($data[0])) {
+ $this->readBytes[$type] += \strlen($data);
+ $read[$type] = $data;
+ }
+ if ($close) {
+ ftruncate($fileHandle, 0);
+ fclose($fileHandle);
+ flock($this->lockHandles[$type], LOCK_UN);
+ fclose($this->lockHandles[$type]);
+ unset($this->fileHandles[$type], $this->lockHandles[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport()
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen()
+ {
+ return $this->pipes && $this->fileHandles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ parent::close();
+ foreach ($this->fileHandles as $type => $handle) {
+ ftruncate($handle, 0);
+ fclose($handle);
+ flock($this->lockHandles[$type], LOCK_UN);
+ fclose($this->lockHandles[$type]);
+ }
+ $this->fileHandles = $this->lockHandles = [];
+ }
+}
diff --git a/vendor/symfony/process/Process.php b/vendor/symfony/process/Process.php
new file mode 100755
index 0000000..5e3993d
--- /dev/null
+++ b/vendor/symfony/process/Process.php
@@ -0,0 +1,1653 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Exception\ProcessSignaledException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Pipes\UnixPipes;
+use Symfony\Component\Process\Pipes\WindowsPipes;
+
+/**
+ * Process is a thin wrapper around proc_* functions to easily
+ * start independent PHP processes.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Romain Neutron <imprec@gmail.com>
+ */
+class Process implements \IteratorAggregate
+{
+ const ERR = 'err';
+ const OUT = 'out';
+
+ const STATUS_READY = 'ready';
+ const STATUS_STARTED = 'started';
+ const STATUS_TERMINATED = 'terminated';
+
+ const STDIN = 0;
+ const STDOUT = 1;
+ const STDERR = 2;
+
+ // Timeout Precision in seconds.
+ const TIMEOUT_PRECISION = 0.2;
+
+ const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
+ const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
+ const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
+ const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
+
+ private $callback;
+ private $hasCallback = false;
+ private $commandline;
+ private $cwd;
+ private $env;
+ private $input;
+ private $starttime;
+ private $lastOutputTime;
+ private $timeout;
+ private $idleTimeout;
+ private $exitcode;
+ private $fallbackStatus = [];
+ private $processInformation;
+ private $outputDisabled = false;
+ private $stdout;
+ private $stderr;
+ private $process;
+ private $status = self::STATUS_READY;
+ private $incrementalOutputOffset = 0;
+ private $incrementalErrorOutputOffset = 0;
+ private $tty;
+ private $pty;
+
+ private $useFileHandles = false;
+ /** @var PipesInterface */
+ private $processPipes;
+
+ private $latestSignal;
+
+ private static $sigchild;
+
+ /**
+ * Exit codes translation table.
+ *
+ * User-defined errors must use exit codes in the 64-113 range.
+ */
+ public static $exitCodes = [
+ 0 => 'OK',
+ 1 => 'General error',
+ 2 => 'Misuse of shell builtins',
+
+ 126 => 'Invoked command cannot execute',
+ 127 => 'Command not found',
+ 128 => 'Invalid exit argument',
+
+ // signals
+ 129 => 'Hangup',
+ 130 => 'Interrupt',
+ 131 => 'Quit and dump core',
+ 132 => 'Illegal instruction',
+ 133 => 'Trace/breakpoint trap',
+ 134 => 'Process aborted',
+ 135 => 'Bus error: "access to undefined portion of memory object"',
+ 136 => 'Floating point exception: "erroneous arithmetic operation"',
+ 137 => 'Kill (terminate immediately)',
+ 138 => 'User-defined 1',
+ 139 => 'Segmentation violation',
+ 140 => 'User-defined 2',
+ 141 => 'Write to pipe with no one reading',
+ 142 => 'Signal raised by alarm',
+ 143 => 'Termination (request to terminate)',
+ // 144 - not defined
+ 145 => 'Child process terminated, stopped (or continued*)',
+ 146 => 'Continue if stopped',
+ 147 => 'Stop executing temporarily',
+ 148 => 'Terminal stop signal',
+ 149 => 'Background process attempting to read from tty ("in")',
+ 150 => 'Background process attempting to write to tty ("out")',
+ 151 => 'Urgent data available on socket',
+ 152 => 'CPU time limit exceeded',
+ 153 => 'File size limit exceeded',
+ 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
+ 155 => 'Profiling timer expired',
+ // 156 - not defined
+ 157 => 'Pollable event',
+ // 158 - not defined
+ 159 => 'Bad syscall',
+ ];
+
+ /**
+ * @param array $command The command to run and its arguments listed as separate entries
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
+ * @param int|float|null $timeout The timeout in seconds or null to disable
+ *
+ * @throws RuntimeException When proc_open is not installed
+ */
+ public function __construct($command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+ {
+ if (!\function_exists('proc_open')) {
+ throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
+ }
+
+ if (!\is_array($command)) {
+ @trigger_error(sprintf('Passing a command as string when creating a "%s" instance is deprecated since Symfony 4.2, pass it as an array of its arguments instead, or use the "Process::fromShellCommandline()" constructor if you need features provided by the shell.', __CLASS__), E_USER_DEPRECATED);
+ }
+
+ $this->commandline = $command;
+ $this->cwd = $cwd;
+
+ // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
+ // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
+ // @see : https://bugs.php.net/bug.php?id=51800
+ // @see : https://bugs.php.net/bug.php?id=50524
+ if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
+ $this->cwd = getcwd();
+ }
+ if (null !== $env) {
+ $this->setEnv($env);
+ }
+
+ $this->setInput($input);
+ $this->setTimeout($timeout);
+ $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
+ $this->pty = false;
+ }
+
+ /**
+ * Creates a Process instance as a command-line to be run in a shell wrapper.
+ *
+ * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)
+ * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the
+ * shell wrapper and not to your commands.
+ *
+ * In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
+ * This will save escaping values, which is not portable nor secure anyway:
+ *
+ * $process = Process::fromShellCommandline('my_command "$MY_VAR"');
+ * $process->run(null, ['MY_VAR' => $theValue]);
+ *
+ * @param string $command The command line to pass to the shell of the OS
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
+ * @param int|float|null $timeout The timeout in seconds or null to disable
+ *
+ * @throws RuntimeException When proc_open is not installed
+ */
+ public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+ {
+ $process = new static([], $cwd, $env, $input, $timeout);
+ $process->commandline = $command;
+
+ return $process;
+ }
+
+ public function __destruct()
+ {
+ $this->stop(0);
+ }
+
+ public function __clone()
+ {
+ $this->resetProcessData();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * The callback receives the type of output (out or err) and
+ * some bytes from the output in real-time. It allows to have feedback
+ * from the independent process during execution.
+ *
+ * The STDOUT and STDERR are also available after the process is finished
+ * via the getOutput() and getErrorOutput() methods.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ * @param array $env An array of additional env vars to set when running the process
+ *
+ * @return int The exit status code
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process stopped after receiving signal
+ * @throws LogicException In case a callback is provided and output has been disabled
+ *
+ * @final
+ */
+ public function run(callable $callback = null, array $env = []): int
+ {
+ $this->start($callback, $env);
+
+ return $this->wait();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * This is identical to run() except that an exception is thrown if the process
+ * exits with a non-zero exit code.
+ *
+ * @param callable|null $callback
+ * @param array $env An array of additional env vars to set when running the process
+ *
+ * @return self
+ *
+ * @throws ProcessFailedException if the process didn't terminate successfully
+ *
+ * @final
+ */
+ public function mustRun(callable $callback = null, array $env = [])
+ {
+ if (0 !== $this->run($callback, $env)) {
+ throw new ProcessFailedException($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Starts the process and returns after writing the input to STDIN.
+ *
+ * This method blocks until all STDIN data is sent to the process then it
+ * returns while the process runs in the background.
+ *
+ * The termination of the process can be awaited with wait().
+ *
+ * The callback receives the type of output (out or err) and some bytes from
+ * the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ * @param array $env An array of additional env vars to set when running the process
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ * @throws LogicException In case a callback is provided and output has been disabled
+ */
+ public function start(callable $callback = null, array $env = [])
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running');
+ }
+
+ $this->resetProcessData();
+ $this->starttime = $this->lastOutputTime = microtime(true);
+ $this->callback = $this->buildCallback($callback);
+ $this->hasCallback = null !== $callback;
+ $descriptors = $this->getDescriptors();
+
+ if (\is_array($commandline = $this->commandline)) {
+ $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));
+
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ // exec is mandatory to deal with sending a signal to the process
+ $commandline = 'exec '.$commandline;
+ }
+ }
+
+ if ($this->env) {
+ $env += $this->env;
+ }
+ $env += $this->getDefaultEnv();
+
+ $options = ['suppress_errors' => true];
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $options['bypass_shell'] = true;
+ $commandline = $this->prepareWindowsCommandLine($commandline, $env);
+ } elseif (!$this->useFileHandles && $this->isSigchildEnabled()) {
+ // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
+ $descriptors[3] = ['pipe', 'w'];
+
+ // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
+ $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
+ $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
+
+ // Workaround for the bug, when PTS functionality is enabled.
+ // @see : https://bugs.php.net/69442
+ $ptsWorkaround = fopen(__FILE__, 'r');
+ }
+
+ $envPairs = [];
+ foreach ($env as $k => $v) {
+ if (false !== $v) {
+ $envPairs[] = $k.'='.$v;
+ }
+ }
+
+ if (!is_dir($this->cwd)) {
+ throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd));
+ }
+
+ $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $options);
+
+ if (!\is_resource($this->process)) {
+ throw new RuntimeException('Unable to launch a new process.');
+ }
+ $this->status = self::STATUS_STARTED;
+
+ if (isset($descriptors[3])) {
+ $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
+ }
+
+ if ($this->tty) {
+ return;
+ }
+
+ $this->updateStatus(false);
+ $this->checkTimeout();
+ }
+
+ /**
+ * Restarts the process.
+ *
+ * Be warned that the process is cloned before being started.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ * @param array $env An array of additional env vars to set when running the process
+ *
+ * @return $this
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ *
+ * @see start()
+ *
+ * @final
+ */
+ public function restart(callable $callback = null, array $env = [])
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running');
+ }
+
+ $process = clone $this;
+ $process->start($callback, $env);
+
+ return $process;
+ }
+
+ /**
+ * Waits for the process to terminate.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * from the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @param callable|null $callback A valid PHP callback
+ *
+ * @return int The exitcode of the process
+ *
+ * @throws RuntimeException When process timed out
+ * @throws RuntimeException When process stopped after receiving signal
+ * @throws LogicException When process is not yet started
+ */
+ public function wait(callable $callback = null)
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+
+ $this->updateStatus(false);
+
+ if (null !== $callback) {
+ if (!$this->processPipes->haveReadSupport()) {
+ $this->stop(0);
+ throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait"');
+ }
+ $this->callback = $this->buildCallback($callback);
+ }
+
+ do {
+ $this->checkTimeout();
+ $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+ $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+ } while ($running);
+
+ while ($this->isRunning()) {
+ $this->checkTimeout();
+ usleep(1000);
+ }
+
+ if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
+ throw new ProcessSignaledException($this);
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Waits until the callback returns true.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * from the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @throws RuntimeException When process timed out
+ * @throws LogicException When process is not yet started
+ */
+ public function waitUntil(callable $callback): bool
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+ $this->updateStatus(false);
+
+ if (!$this->processPipes->haveReadSupport()) {
+ $this->stop(0);
+ throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
+ }
+ $callback = $this->buildCallback($callback);
+
+ $ready = false;
+ while (true) {
+ $this->checkTimeout();
+ $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+ $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+ foreach ($output as $type => $data) {
+ if (3 !== $type) {
+ $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;
+ } elseif (!isset($this->fallbackStatus['signaled'])) {
+ $this->fallbackStatus['exitcode'] = (int) $data;
+ }
+ }
+ if ($ready) {
+ return true;
+ }
+ if (!$running) {
+ return false;
+ }
+
+ usleep(1000);
+ }
+ }
+
+ /**
+ * Returns the Pid (process identifier), if applicable.
+ *
+ * @return int|null The process id if running, null otherwise
+ */
+ public function getPid()
+ {
+ return $this->isRunning() ? $this->processInformation['pid'] : null;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
+ *
+ * @return $this
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+ * @throws RuntimeException In case of failure
+ */
+ public function signal($signal)
+ {
+ $this->doSignal($signal, true);
+
+ return $this;
+ }
+
+ /**
+ * Disables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ * @throws LogicException if an idle timeout is set
+ */
+ public function disableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Disabling output while the process is running is not possible.');
+ }
+ if (null !== $this->idleTimeout) {
+ throw new LogicException('Output can not be disabled while an idle timeout is set.');
+ }
+
+ $this->outputDisabled = true;
+
+ return $this;
+ }
+
+ /**
+ * Enables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ */
+ public function enableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Enabling output while the process is running is not possible.');
+ }
+
+ $this->outputDisabled = false;
+
+ return $this;
+ }
+
+ /**
+ * Returns true in case the output is disabled, false otherwise.
+ *
+ * @return bool
+ */
+ public function isOutputDisabled()
+ {
+ return $this->outputDisabled;
+ }
+
+ /**
+ * Returns the current output of the process (STDOUT).
+ *
+ * @return string The process output
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the output incrementally.
+ *
+ * In comparison with the getOutput method which always return the whole
+ * output, this one returns the new output since the last call.
+ *
+ * @return string The process output since the last call
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+ $this->incrementalOutputOffset = ftell($this->stdout);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
+ *
+ * @param int $flags A bit field of Process::ITER_* flags
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ *
+ * @return \Generator
+ */
+ public function getIterator($flags = 0)
+ {
+ $this->readPipesForOutput(__FUNCTION__, false);
+
+ $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
+ $blocking = !(self::ITER_NON_BLOCKING & $flags);
+ $yieldOut = !(self::ITER_SKIP_OUT & $flags);
+ $yieldErr = !(self::ITER_SKIP_ERR & $flags);
+
+ while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
+ if ($yieldOut) {
+ $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+
+ if (isset($out[0])) {
+ if ($clearOutput) {
+ $this->clearOutput();
+ } else {
+ $this->incrementalOutputOffset = ftell($this->stdout);
+ }
+
+ yield self::OUT => $out;
+ }
+ }
+
+ if ($yieldErr) {
+ $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+
+ if (isset($err[0])) {
+ if ($clearOutput) {
+ $this->clearErrorOutput();
+ } else {
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+ }
+
+ yield self::ERR => $err;
+ }
+ }
+
+ if (!$blocking && !isset($out[0]) && !isset($err[0])) {
+ yield self::OUT => '';
+ }
+
+ $this->checkTimeout();
+ $this->readPipesForOutput(__FUNCTION__, $blocking);
+ }
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearOutput()
+ {
+ ftruncate($this->stdout, 0);
+ fseek($this->stdout, 0);
+ $this->incrementalOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the current error output of the process (STDERR).
+ *
+ * @return string The process error output
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the errorOutput incrementally.
+ *
+ * In comparison with the getErrorOutput method which always return the
+ * whole error output, this one returns the new error output since the last
+ * call.
+ *
+ * @return string The process error output since the last call
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearErrorOutput()
+ {
+ ftruncate($this->stderr, 0);
+ fseek($this->stderr, 0);
+ $this->incrementalErrorOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the exit code returned by the process.
+ *
+ * @return int|null The exit status code, null if the Process is not terminated
+ */
+ public function getExitCode()
+ {
+ $this->updateStatus(false);
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Returns a string representation for the exit code returned by the process.
+ *
+ * This method relies on the Unix exit code status standardization
+ * and might not be relevant for other operating systems.
+ *
+ * @return string|null A string representation for the exit status code, null if the Process is not terminated
+ *
+ * @see http://tldp.org/LDP/abs/html/exitcodes.html
+ * @see http://en.wikipedia.org/wiki/Unix_signal
+ */
+ public function getExitCodeText()
+ {
+ if (null === $exitcode = $this->getExitCode()) {
+ return;
+ }
+
+ return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
+ }
+
+ /**
+ * Checks if the process ended successfully.
+ *
+ * @return bool true if the process ended successfully, false otherwise
+ */
+ public function isSuccessful()
+ {
+ return 0 === $this->getExitCode();
+ }
+
+ /**
+ * Returns true if the child process has been terminated by an uncaught signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenSignaled()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['signaled'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to terminate its execution.
+ *
+ * It is only meaningful if hasBeenSignaled() returns true.
+ *
+ * @return int
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getTermSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
+ }
+
+ return $this->processInformation['termsig'];
+ }
+
+ /**
+ * Returns true if the child process has been stopped by a signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenStopped()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopped'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to stop its execution.
+ *
+ * It is only meaningful if hasBeenStopped() returns true.
+ *
+ * @return int
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getStopSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopsig'];
+ }
+
+ /**
+ * Checks if the process is currently running.
+ *
+ * @return bool true if the process is currently running, false otherwise
+ */
+ public function isRunning()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return false;
+ }
+
+ $this->updateStatus(false);
+
+ return $this->processInformation['running'];
+ }
+
+ /**
+ * Checks if the process has been started with no regard to the current state.
+ *
+ * @return bool true if status is ready, false otherwise
+ */
+ public function isStarted()
+ {
+ return self::STATUS_READY != $this->status;
+ }
+
+ /**
+ * Checks if the process is terminated.
+ *
+ * @return bool true if process is terminated, false otherwise
+ */
+ public function isTerminated()
+ {
+ $this->updateStatus(false);
+
+ return self::STATUS_TERMINATED == $this->status;
+ }
+
+ /**
+ * Gets the process status.
+ *
+ * The status is one of: ready, started, terminated.
+ *
+ * @return string The current process status
+ */
+ public function getStatus()
+ {
+ $this->updateStatus(false);
+
+ return $this->status;
+ }
+
+ /**
+ * Stops the process.
+ *
+ * @param int|float $timeout The timeout in seconds
+ * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
+ *
+ * @return int The exit-code of the process
+ */
+ public function stop($timeout = 10, $signal = null)
+ {
+ $timeoutMicro = microtime(true) + $timeout;
+ if ($this->isRunning()) {
+ // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here
+ $this->doSignal(15, false);
+ do {
+ usleep(1000);
+ } while ($this->isRunning() && microtime(true) < $timeoutMicro);
+
+ if ($this->isRunning()) {
+ // Avoid exception here: process is supposed to be running, but it might have stopped just
+ // after this line. In any case, let's silently discard the error, we cannot do anything.
+ $this->doSignal($signal ?: 9, false);
+ }
+ }
+
+ if ($this->isRunning()) {
+ if (isset($this->fallbackStatus['pid'])) {
+ unset($this->fallbackStatus['pid']);
+
+ return $this->stop(0, $signal);
+ }
+ $this->close();
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Adds a line to the STDOUT stream.
+ *
+ * @internal
+ */
+ public function addOutput(string $line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stdout, 0, SEEK_END);
+ fwrite($this->stdout, $line);
+ fseek($this->stdout, $this->incrementalOutputOffset);
+ }
+
+ /**
+ * Adds a line to the STDERR stream.
+ *
+ * @internal
+ */
+ public function addErrorOutput(string $line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stderr, 0, SEEK_END);
+ fwrite($this->stderr, $line);
+ fseek($this->stderr, $this->incrementalErrorOutputOffset);
+ }
+
+ /**
+ * Gets the command line to be executed.
+ *
+ * @return string The command to execute
+ */
+ public function getCommandLine()
+ {
+ return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline;
+ }
+
+ /**
+ * Sets the command line to be executed.
+ *
+ * @param string|array $commandline The command to execute
+ *
+ * @return self The current Process instance
+ *
+ * @deprecated since Symfony 4.2.
+ */
+ public function setCommandLine($commandline)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->commandline = $commandline;
+
+ return $this;
+ }
+
+ /**
+ * Gets the process timeout (max. runtime).
+ *
+ * @return float|null The timeout in seconds or null if it's disabled
+ */
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * Gets the process idle timeout (max. time since last output).
+ *
+ * @return float|null The timeout in seconds or null if it's disabled
+ */
+ public function getIdleTimeout()
+ {
+ return $this->idleTimeout;
+ }
+
+ /**
+ * Sets the process timeout (max. runtime).
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param int|float|null $timeout The timeout in seconds
+ *
+ * @return self The current Process instance
+ *
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setTimeout($timeout)
+ {
+ $this->timeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Sets the process idle timeout (max. time since last output).
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param int|float|null $timeout The timeout in seconds
+ *
+ * @return self The current Process instance
+ *
+ * @throws LogicException if the output is disabled
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setIdleTimeout($timeout)
+ {
+ if (null !== $timeout && $this->outputDisabled) {
+ throw new LogicException('Idle timeout can not be set while the output is disabled.');
+ }
+
+ $this->idleTimeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Enables or disables the TTY mode.
+ *
+ * @param bool $tty True to enabled and false to disable
+ *
+ * @return self The current Process instance
+ *
+ * @throws RuntimeException In case the TTY mode is not supported
+ */
+ public function setTty($tty)
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
+ throw new RuntimeException('TTY mode is not supported on Windows platform.');
+ }
+
+ if ($tty && !self::isTtySupported()) {
+ throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
+ }
+
+ $this->tty = (bool) $tty;
+
+ return $this;
+ }
+
+ /**
+ * Checks if the TTY mode is enabled.
+ *
+ * @return bool true if the TTY mode is enabled, false otherwise
+ */
+ public function isTty()
+ {
+ return $this->tty;
+ }
+
+ /**
+ * Sets PTY mode.
+ *
+ * @param bool $bool
+ *
+ * @return self
+ */
+ public function setPty($bool)
+ {
+ $this->pty = (bool) $bool;
+
+ return $this;
+ }
+
+ /**
+ * Returns PTY state.
+ *
+ * @return bool
+ */
+ public function isPty()
+ {
+ return $this->pty;
+ }
+
+ /**
+ * Gets the working directory.
+ *
+ * @return string|null The current working directory or null on failure
+ */
+ public function getWorkingDirectory()
+ {
+ if (null === $this->cwd) {
+ // getcwd() will return false if any one of the parent directories does not have
+ // the readable or search mode set, even if the current directory does
+ return getcwd() ?: null;
+ }
+
+ return $this->cwd;
+ }
+
+ /**
+ * Sets the current working directory.
+ *
+ * @param string $cwd The new working directory
+ *
+ * @return self The current Process instance
+ */
+ public function setWorkingDirectory($cwd)
+ {
+ $this->cwd = $cwd;
+
+ return $this;
+ }
+
+ /**
+ * Gets the environment variables.
+ *
+ * @return array The current environment variables
+ */
+ public function getEnv()
+ {
+ return $this->env;
+ }
+
+ /**
+ * Sets the environment variables.
+ *
+ * Each environment variable value should be a string.
+ * If it is an array, the variable is ignored.
+ * If it is false or null, it will be removed when
+ * env vars are otherwise inherited.
+ *
+ * That happens in PHP when 'argv' is registered into
+ * the $_ENV array for instance.
+ *
+ * @param array $env The new environment variables
+ *
+ * @return self The current Process instance
+ */
+ public function setEnv(array $env)
+ {
+ // Process can not handle env values that are arrays
+ $env = array_filter($env, function ($value) {
+ return !\is_array($value);
+ });
+
+ $this->env = $env;
+
+ return $this;
+ }
+
+ /**
+ * Gets the Process input.
+ *
+ * @return resource|string|\Iterator|null The Process input
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * Sets the input.
+ *
+ * This content will be passed to the underlying process standard input.
+ *
+ * @param string|int|float|bool|resource|\Traversable|null $input The content
+ *
+ * @return self The current Process instance
+ *
+ * @throws LogicException In case the process is running
+ */
+ public function setInput($input)
+ {
+ if ($this->isRunning()) {
+ throw new LogicException('Input can not be set while the process is running.');
+ }
+
+ $this->input = ProcessUtils::validateInput(__METHOD__, $input);
+
+ return $this;
+ }
+
+ /**
+ * Sets whether environment variables will be inherited or not.
+ *
+ * @param bool $inheritEnv
+ *
+ * @return self The current Process instance
+ */
+ public function inheritEnvironmentVariables($inheritEnv = true)
+ {
+ if (!$inheritEnv) {
+ throw new InvalidArgumentException('Not inheriting environment variables is not supported.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Performs a check between the timeout definition and the time the process started.
+ *
+ * In case you run a background process (with the start method), you should
+ * trigger this method regularly to ensure the process timeout
+ *
+ * @throws ProcessTimedOutException In case the timeout was reached
+ */
+ public function checkTimeout()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
+ }
+
+ if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
+ }
+ }
+
+ /**
+ * Returns whether TTY is supported on the current operating system.
+ */
+ public static function isTtySupported(): bool
+ {
+ static $isTtySupported;
+
+ if (null === $isTtySupported) {
+ $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
+ }
+
+ return $isTtySupported;
+ }
+
+ /**
+ * Returns whether PTY is supported on the current operating system.
+ *
+ * @return bool
+ */
+ public static function isPtySupported()
+ {
+ static $result;
+
+ if (null !== $result) {
+ return $result;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ return $result = false;
+ }
+
+ return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);
+ }
+
+ /**
+ * Creates the descriptors needed by the proc_open.
+ */
+ private function getDescriptors(): array
+ {
+ if ($this->input instanceof \Iterator) {
+ $this->input->rewind();
+ }
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
+ } else {
+ $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
+ }
+
+ return $this->processPipes->getDescriptors();
+ }
+
+ /**
+ * Builds up the callback used by wait().
+ *
+ * The callbacks adds all occurred output to the specific buffer and calls
+ * the user callback (if present) with the received output.
+ *
+ * @param callable|null $callback The user defined PHP callback
+ *
+ * @return \Closure A PHP closure
+ */
+ protected function buildCallback(callable $callback = null)
+ {
+ if ($this->outputDisabled) {
+ return function ($type, $data) use ($callback) {
+ if (null !== $callback) {
+ return $callback($type, $data);
+ }
+ };
+ }
+
+ $out = self::OUT;
+
+ return function ($type, $data) use ($callback, $out) {
+ if ($out == $type) {
+ $this->addOutput($data);
+ } else {
+ $this->addErrorOutput($data);
+ }
+
+ if (null !== $callback) {
+ return $callback($type, $data);
+ }
+ };
+ }
+
+ /**
+ * Updates the status of the process, reads pipes.
+ *
+ * @param bool $blocking Whether to use a blocking read call
+ */
+ protected function updateStatus($blocking)
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ $this->processInformation = proc_get_status($this->process);
+ $running = $this->processInformation['running'];
+
+ $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+ if ($this->fallbackStatus && $this->isSigchildEnabled()) {
+ $this->processInformation = $this->fallbackStatus + $this->processInformation;
+ }
+
+ if (!$running) {
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
+ *
+ * @return bool
+ */
+ protected function isSigchildEnabled()
+ {
+ if (null !== self::$sigchild) {
+ return self::$sigchild;
+ }
+
+ if (!\function_exists('phpinfo')) {
+ return self::$sigchild = false;
+ }
+
+ ob_start();
+ phpinfo(INFO_GENERAL);
+
+ return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+ }
+
+ /**
+ * Reads pipes for the freshest output.
+ *
+ * @param string $caller The name of the method that needs fresh outputs
+ * @param bool $blocking Whether to use blocking calls or not
+ *
+ * @throws LogicException in case output has been disabled or process is not started
+ */
+ private function readPipesForOutput(string $caller, bool $blocking = false)
+ {
+ if ($this->outputDisabled) {
+ throw new LogicException('Output has been disabled.');
+ }
+
+ $this->requireProcessIsStarted($caller);
+
+ $this->updateStatus($blocking);
+ }
+
+ /**
+ * Validates and returns the filtered timeout.
+ *
+ * @throws InvalidArgumentException if the given timeout is a negative number
+ */
+ private function validateTimeout(?float $timeout): ?float
+ {
+ $timeout = (float) $timeout;
+
+ if (0.0 === $timeout) {
+ $timeout = null;
+ } elseif ($timeout < 0) {
+ throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
+ }
+
+ return $timeout;
+ }
+
+ /**
+ * Reads pipes, executes callback.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close file handles or not
+ */
+ private function readPipes(bool $blocking, bool $close)
+ {
+ $result = $this->processPipes->readAndWrite($blocking, $close);
+
+ $callback = $this->callback;
+ foreach ($result as $type => $data) {
+ if (3 !== $type) {
+ $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
+ } elseif (!isset($this->fallbackStatus['signaled'])) {
+ $this->fallbackStatus['exitcode'] = (int) $data;
+ }
+ }
+ }
+
+ /**
+ * Closes process resource, closes file handles, sets the exitcode.
+ *
+ * @return int The exitcode
+ */
+ private function close(): int
+ {
+ $this->processPipes->close();
+ if (\is_resource($this->process)) {
+ proc_close($this->process);
+ }
+ $this->exitcode = $this->processInformation['exitcode'];
+ $this->status = self::STATUS_TERMINATED;
+
+ if (-1 === $this->exitcode) {
+ if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
+ // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
+ $this->exitcode = 128 + $this->processInformation['termsig'];
+ } elseif ($this->isSigchildEnabled()) {
+ $this->processInformation['signaled'] = true;
+ $this->processInformation['termsig'] = -1;
+ }
+ }
+
+ // Free memory from self-reference callback created by buildCallback
+ // Doing so in other contexts like __destruct or by garbage collector is ineffective
+ // Now pipes are closed, so the callback is no longer necessary
+ $this->callback = null;
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Resets data related to the latest run of the process.
+ */
+ private function resetProcessData()
+ {
+ $this->starttime = null;
+ $this->callback = null;
+ $this->exitcode = null;
+ $this->fallbackStatus = [];
+ $this->processInformation = null;
+ $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
+ $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
+ $this->process = null;
+ $this->latestSignal = null;
+ $this->status = self::STATUS_READY;
+ $this->incrementalOutputOffset = 0;
+ $this->incrementalErrorOutputOffset = 0;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
+ * @param bool $throwException Whether to throw exception in case signal failed
+ *
+ * @return bool True if the signal was sent successfully, false otherwise
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+ * @throws RuntimeException In case of failure
+ */
+ private function doSignal(int $signal, bool $throwException): bool
+ {
+ if (null === $pid = $this->getPid()) {
+ if ($throwException) {
+ throw new LogicException('Can not send signal on a non running process.');
+ }
+
+ return false;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
+ if ($exitCode && $this->isRunning()) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
+ }
+
+ return false;
+ }
+ } else {
+ if (!$this->isSigchildEnabled()) {
+ $ok = @proc_terminate($this->process, $signal);
+ } elseif (\function_exists('posix_kill')) {
+ $ok = @posix_kill($pid, $signal);
+ } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
+ $ok = false === fgets($pipes[2]);
+ }
+ if (!$ok) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal));
+ }
+
+ return false;
+ }
+ }
+
+ $this->latestSignal = $signal;
+ $this->fallbackStatus['signaled'] = true;
+ $this->fallbackStatus['exitcode'] = -1;
+ $this->fallbackStatus['termsig'] = $this->latestSignal;
+
+ return true;
+ }
+
+ private function prepareWindowsCommandLine(string $cmd, array &$env)
+ {
+ $uid = uniqid('', true);
+ $varCount = 0;
+ $varCache = [];
+ $cmd = preg_replace_callback(
+ '/"(?:(
+ [^"%!^]*+
+ (?:
+ (?: !LF! | "(?:\^[%!^])?+" )
+ [^"%!^]*+
+ )++
+ ) | [^"]*+ )"/x',
+ function ($m) use (&$env, &$varCache, &$varCount, $uid) {
+ if (!isset($m[1])) {
+ return $m[0];
+ }
+ if (isset($varCache[$m[0]])) {
+ return $varCache[$m[0]];
+ }
+ if (false !== strpos($value = $m[1], "\0")) {
+ $value = str_replace("\0", '?', $value);
+ }
+ if (false === strpbrk($value, "\"%!\n")) {
+ return '"'.$value.'"';
+ }
+
+ $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
+ $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
+ $var = $uid.++$varCount;
+
+ $env[$var] = $value;
+
+ return $varCache[$m[0]] = '!'.$var.'!';
+ },
+ $cmd
+ );
+
+ $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
+ foreach ($this->processPipes->getFiles() as $offset => $filename) {
+ $cmd .= ' '.$offset.'>"'.$filename.'"';
+ }
+
+ return $cmd;
+ }
+
+ /**
+ * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
+ *
+ * @throws LogicException if the process has not run
+ */
+ private function requireProcessIsStarted(string $functionName)
+ {
+ if (!$this->isStarted()) {
+ throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
+ }
+ }
+
+ /**
+ * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated".
+ *
+ * @throws LogicException if the process is not yet terminated
+ */
+ private function requireProcessIsTerminated(string $functionName)
+ {
+ if (!$this->isTerminated()) {
+ throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
+ }
+ }
+
+ /**
+ * Escapes a string to be used as a shell argument.
+ */
+ private function escapeArgument(?string $argument): string
+ {
+ if ('' === $argument || null === $argument) {
+ return '""';
+ }
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ return "'".str_replace("'", "'\\''", $argument)."'";
+ }
+ if (false !== strpos($argument, "\0")) {
+ $argument = str_replace("\0", '?', $argument);
+ }
+ if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
+ return $argument;
+ }
+ $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
+
+ return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
+ }
+
+ private function getDefaultEnv()
+ {
+ $env = [];
+
+ foreach ($_SERVER as $k => $v) {
+ if (\is_string($v) && false !== $v = getenv($k)) {
+ $env[$k] = $v;
+ }
+ }
+
+ foreach ($_ENV as $k => $v) {
+ if (\is_string($v)) {
+ $env[$k] = $v;
+ }
+ }
+
+ return $env;
+ }
+}
diff --git a/vendor/symfony/process/ProcessUtils.php b/vendor/symfony/process/ProcessUtils.php
new file mode 100755
index 0000000..2f9c4be
--- /dev/null
+++ b/vendor/symfony/process/ProcessUtils.php
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * ProcessUtils is a bunch of utility methods.
+ *
+ * This class contains static methods only and is not meant to be instantiated.
+ *
+ * @author Martin Hasoň <martin.hason@gmail.com>
+ */
+class ProcessUtils
+{
+ /**
+ * This class should not be instantiated.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Validates and normalizes a Process input.
+ *
+ * @param string $caller The name of method call that validates the input
+ * @param mixed $input The input to validate
+ *
+ * @return mixed The validated input
+ *
+ * @throws InvalidArgumentException In case the input is not valid
+ */
+ public static function validateInput($caller, $input)
+ {
+ if (null !== $input) {
+ if (\is_resource($input)) {
+ return $input;
+ }
+ if (\is_string($input)) {
+ return $input;
+ }
+ if (is_scalar($input)) {
+ return (string) $input;
+ }
+ if ($input instanceof Process) {
+ return $input->getIterator($input::ITER_SKIP_ERR);
+ }
+ if ($input instanceof \Iterator) {
+ return $input;
+ }
+ if ($input instanceof \Traversable) {
+ return new \IteratorIterator($input);
+ }
+
+ throw new InvalidArgumentException(sprintf('%s only accepts strings, Traversable objects or stream resources.', $caller));
+ }
+
+ return $input;
+ }
+}
diff --git a/vendor/symfony/process/README.md b/vendor/symfony/process/README.md
new file mode 100755
index 0000000..b7ca5b4
--- /dev/null
+++ b/vendor/symfony/process/README.md
@@ -0,0 +1,13 @@
+Process Component
+=================
+
+The Process component executes commands in sub-processes.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/process.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/vendor/symfony/process/Tests/ErrorProcessInitiator.php b/vendor/symfony/process/Tests/ErrorProcessInitiator.php
new file mode 100755
index 0000000..c37aeb5
--- /dev/null
+++ b/vendor/symfony/process/Tests/ErrorProcessInitiator.php
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Process;
+
+require \dirname(__DIR__).'/vendor/autoload.php';
+
+list('e' => $php) = getopt('e:') + ['e' => 'php'];
+
+try {
+ $process = new Process("exec $php -r \"echo 'ready'; trigger_error('error', E_USER_ERROR);\"");
+ $process->start();
+ $process->setTimeout(0.5);
+ while (false === strpos($process->getOutput(), 'ready')) {
+ usleep(1000);
+ }
+ $process->signal(SIGSTOP);
+ $process->wait();
+
+ return $process->getExitCode();
+} catch (ProcessTimedOutException $t) {
+ echo $t->getMessage().PHP_EOL;
+
+ return 1;
+}
diff --git a/vendor/symfony/process/Tests/ExecutableFinderTest.php b/vendor/symfony/process/Tests/ExecutableFinderTest.php
new file mode 100755
index 0000000..6d69a77
--- /dev/null
+++ b/vendor/symfony/process/Tests/ExecutableFinderTest.php
@@ -0,0 +1,178 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\ExecutableFinder;
+
+/**
+ * @author Chris Smith <chris@cs278.org>
+ */
+class ExecutableFinderTest extends TestCase
+{
+ private $path;
+
+ protected function tearDown()
+ {
+ if ($this->path) {
+ // Restore path if it was changed.
+ putenv('PATH='.$this->path);
+ }
+ }
+
+ private function setPath($path)
+ {
+ $this->path = getenv('PATH');
+ putenv('PATH='.$path);
+ }
+
+ public function testFind()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath(\dirname(PHP_BINARY));
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName());
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindWithDefault()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $expected = 'defaultValue';
+
+ $this->setPath('');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find('foo', $expected);
+
+ $this->assertEquals($expected, $result);
+ }
+
+ public function testFindWithNullAsDefault()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath('');
+
+ $finder = new ExecutableFinder();
+
+ $result = $finder->find('foo');
+
+ $this->assertNull($result);
+ }
+
+ public function testFindWithExtraDirs()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath('');
+
+ $extraDirs = [\dirname(PHP_BINARY)];
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName(), null, $extraDirs);
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindWithOpenBaseDir()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Cannot run test on windows');
+ }
+
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->iniSet('open_basedir', \dirname(PHP_BINARY).PATH_SEPARATOR.'/');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName());
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindProcessInOpenBasedir()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Cannot run test on windows');
+ }
+
+ $this->setPath('');
+ $this->iniSet('open_basedir', PHP_BINARY.PATH_SEPARATOR.'/');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName(), false);
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ /**
+ * @requires PHP 5.4
+ */
+ public function testFindBatchExecutableOnWindows()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Can be only tested on windows');
+ }
+
+ $target = tempnam(sys_get_temp_dir(), 'example-windows-executable');
+
+ touch($target);
+ touch($target.'.BAT');
+
+ $this->assertFalse(is_executable($target));
+
+ $this->setPath(sys_get_temp_dir());
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find(basename($target), false);
+
+ unlink($target);
+ unlink($target.'.BAT');
+
+ $this->assertSamePath($target.'.BAT', $result);
+ }
+
+ private function assertSamePath($expected, $tested)
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->assertEquals(strtolower($expected), strtolower($tested));
+ } else {
+ $this->assertEquals($expected, $tested);
+ }
+ }
+
+ private function getPhpBinaryName()
+ {
+ return basename(PHP_BINARY, '\\' === \DIRECTORY_SEPARATOR ? '.exe' : '');
+ }
+}
diff --git a/vendor/symfony/process/Tests/KillableProcessWithOutput.php b/vendor/symfony/process/Tests/KillableProcessWithOutput.php
new file mode 100755
index 0000000..28a6a27
--- /dev/null
+++ b/vendor/symfony/process/Tests/KillableProcessWithOutput.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+$outputs = [
+ 'First iteration output',
+ 'Second iteration output',
+ 'One more iteration output',
+ 'This took more time',
+];
+
+$iterationTime = 10000;
+
+foreach ($outputs as $output) {
+ usleep($iterationTime);
+ $iterationTime *= 10;
+ echo $output."\n";
+}
diff --git a/vendor/symfony/process/Tests/NonStopableProcess.php b/vendor/symfony/process/Tests/NonStopableProcess.php
new file mode 100755
index 0000000..5643259
--- /dev/null
+++ b/vendor/symfony/process/Tests/NonStopableProcess.php
@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Runs a PHP script that can be stopped only with a SIGKILL (9) signal for 3 seconds.
+ *
+ * @args duration Run this script with a custom duration
+ *
+ * @example `php NonStopableProcess.php 42` will run the script for 42 seconds
+ */
+function handleSignal($signal)
+{
+ switch ($signal) {
+ case SIGTERM:
+ $name = 'SIGTERM';
+ break;
+ case SIGINT:
+ $name = 'SIGINT';
+ break;
+ default:
+ $name = $signal.' (unknown)';
+ break;
+ }
+
+ echo "signal $name\n";
+}
+
+pcntl_signal(SIGTERM, 'handleSignal');
+pcntl_signal(SIGINT, 'handleSignal');
+
+echo 'received ';
+
+$duration = isset($argv[1]) ? (int) $argv[1] : 3;
+$start = microtime(true);
+
+while ($duration > (microtime(true) - $start)) {
+ usleep(10000);
+ pcntl_signal_dispatch();
+}
diff --git a/vendor/symfony/process/Tests/PhpExecutableFinderTest.php b/vendor/symfony/process/Tests/PhpExecutableFinderTest.php
new file mode 100755
index 0000000..338bb4e
--- /dev/null
+++ b/vendor/symfony/process/Tests/PhpExecutableFinderTest.php
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\PhpExecutableFinder;
+
+/**
+ * @author Robert Schönthal <seroscho@googlemail.com>
+ */
+class PhpExecutableFinderTest extends TestCase
+{
+ /**
+ * tests find() with the constant PHP_BINARY.
+ */
+ public function testFind()
+ {
+ $f = new PhpExecutableFinder();
+
+ $current = PHP_BINARY;
+ $args = 'phpdbg' === \PHP_SAPI ? ' -qrr' : '';
+
+ $this->assertEquals($current.$args, $f->find(), '::find() returns the executable PHP');
+ $this->assertEquals($current, $f->find(false), '::find() returns the executable PHP');
+ }
+
+ /**
+ * tests find() with the env var PHP_PATH.
+ */
+ public function testFindArguments()
+ {
+ $f = new PhpExecutableFinder();
+
+ if ('phpdbg' === \PHP_SAPI) {
+ $this->assertEquals($f->findArguments(), ['-qrr'], '::findArguments() returns phpdbg arguments');
+ } else {
+ $this->assertEquals($f->findArguments(), [], '::findArguments() returns no arguments');
+ }
+ }
+}
diff --git a/vendor/symfony/process/Tests/PhpProcessTest.php b/vendor/symfony/process/Tests/PhpProcessTest.php
new file mode 100755
index 0000000..0355c85
--- /dev/null
+++ b/vendor/symfony/process/Tests/PhpProcessTest.php
@@ -0,0 +1,63 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\PhpProcess;
+
+class PhpProcessTest extends TestCase
+{
+ public function testNonBlockingWorks()
+ {
+ $expected = 'hello world!';
+ $process = new PhpProcess(<<<PHP
+<?php echo '$expected';
+PHP
+ );
+ $process->start();
+ $process->wait();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+
+ public function testCommandLine()
+ {
+ $process = new PhpProcess(<<<'PHP'
+<?php echo phpversion().PHP_SAPI;
+PHP
+ );
+
+ $commandLine = $process->getCommandLine();
+
+ $process->start();
+ $this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start');
+
+ $process->wait();
+ $this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait');
+
+ $this->assertSame(PHP_VERSION.\PHP_SAPI, $process->getOutput());
+ }
+
+ public function testPassingPhpExplicitly()
+ {
+ $finder = new PhpExecutableFinder();
+ $php = array_merge([$finder->find(false)], $finder->findArguments());
+
+ $expected = 'hello world!';
+ $script = <<<PHP
+<?php echo '$expected';
+PHP;
+ $process = new PhpProcess($script, null, null, 60, $php);
+ $process->run();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+}
diff --git a/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php b/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
new file mode 100755
index 0000000..9ea8981
--- /dev/null
+++ b/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+define('ERR_SELECT_FAILED', 1);
+define('ERR_TIMEOUT', 2);
+define('ERR_READ_FAILED', 3);
+define('ERR_WRITE_FAILED', 4);
+
+$read = [STDIN];
+$write = [STDOUT, STDERR];
+
+stream_set_blocking(STDIN, 0);
+stream_set_blocking(STDOUT, 0);
+stream_set_blocking(STDERR, 0);
+
+$out = $err = '';
+while ($read || $write) {
+ $r = $read;
+ $w = $write;
+ $e = null;
+ $n = stream_select($r, $w, $e, 5);
+
+ if (false === $n) {
+ die(ERR_SELECT_FAILED);
+ } elseif ($n < 1) {
+ die(ERR_TIMEOUT);
+ }
+
+ if (in_array(STDOUT, $w) && strlen($out) > 0) {
+ $written = fwrite(STDOUT, (string) $out, 32768);
+ if (false === $written) {
+ die(ERR_WRITE_FAILED);
+ }
+ $out = (string) substr($out, $written);
+ }
+ if (null === $read && '' === $out) {
+ $write = array_diff($write, [STDOUT]);
+ }
+
+ if (in_array(STDERR, $w) && strlen($err) > 0) {
+ $written = fwrite(STDERR, (string) $err, 32768);
+ if (false === $written) {
+ die(ERR_WRITE_FAILED);
+ }
+ $err = (string) substr($err, $written);
+ }
+ if (null === $read && '' === $err) {
+ $write = array_diff($write, [STDERR]);
+ }
+
+ if ($r) {
+ $str = fread(STDIN, 32768);
+ if (false !== $str) {
+ $out .= $str;
+ $err .= $str;
+ }
+ if (false === $str || feof(STDIN)) {
+ $read = null;
+ if (!feof(STDIN)) {
+ die(ERR_READ_FAILED);
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php b/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php
new file mode 100755
index 0000000..ba085bd
--- /dev/null
+++ b/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php
@@ -0,0 +1,137 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+
+/**
+ * @author Sebastian Marek <proofek@gmail.com>
+ */
+class ProcessFailedExceptionTest extends TestCase
+{
+ /**
+ * tests ProcessFailedException throws exception if the process was successful.
+ */
+ public function testProcessFailedExceptionThrowsException()
+ {
+ $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful'])->setConstructorArgs([['php']])->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->willReturn(true);
+
+ if (method_exists($this, 'expectException')) {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Expected a failed process, but the given process was successful.');
+ } else {
+ $this->setExpectedException(\InvalidArgumentException::class, 'Expected a failed process, but the given process was successful.');
+ }
+
+ new ProcessFailedException($process);
+ }
+
+ /**
+ * tests ProcessFailedException uses information from process output
+ * to generate exception message.
+ */
+ public function testProcessFailedExceptionPopulatesInformationFromProcessOutput()
+ {
+ $cmd = 'php';
+ $exitCode = 1;
+ $exitText = 'General error';
+ $output = 'Command output';
+ $errorOutput = 'FATAL: Unexpected error';
+ $workingDirectory = getcwd();
+
+ $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText', 'isOutputDisabled', 'getWorkingDirectory'])->setConstructorArgs([[$cmd]])->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->willReturn(false);
+
+ $process->expects($this->once())
+ ->method('getOutput')
+ ->willReturn($output);
+
+ $process->expects($this->once())
+ ->method('getErrorOutput')
+ ->willReturn($errorOutput);
+
+ $process->expects($this->once())
+ ->method('getExitCode')
+ ->willReturn($exitCode);
+
+ $process->expects($this->once())
+ ->method('getExitCodeText')
+ ->willReturn($exitText);
+
+ $process->expects($this->once())
+ ->method('isOutputDisabled')
+ ->willReturn(false);
+
+ $process->expects($this->once())
+ ->method('getWorkingDirectory')
+ ->willReturn($workingDirectory);
+
+ $exception = new ProcessFailedException($process);
+
+ $this->assertEquals(
+ "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}\n\nOutput:\n================\n{$output}\n\nError Output:\n================\n{$errorOutput}",
+ str_replace("'php'", 'php', $exception->getMessage())
+ );
+ }
+
+ /**
+ * Tests that ProcessFailedException does not extract information from
+ * process output if it was previously disabled.
+ */
+ public function testDisabledOutputInFailedExceptionDoesNotPopulateOutput()
+ {
+ $cmd = 'php';
+ $exitCode = 1;
+ $exitText = 'General error';
+ $workingDirectory = getcwd();
+
+ $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful', 'isOutputDisabled', 'getExitCode', 'getExitCodeText', 'getOutput', 'getErrorOutput', 'getWorkingDirectory'])->setConstructorArgs([[$cmd]])->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->willReturn(false);
+
+ $process->expects($this->never())
+ ->method('getOutput');
+
+ $process->expects($this->never())
+ ->method('getErrorOutput');
+
+ $process->expects($this->once())
+ ->method('getExitCode')
+ ->willReturn($exitCode);
+
+ $process->expects($this->once())
+ ->method('getExitCodeText')
+ ->willReturn($exitText);
+
+ $process->expects($this->once())
+ ->method('isOutputDisabled')
+ ->willReturn(true);
+
+ $process->expects($this->once())
+ ->method('getWorkingDirectory')
+ ->willReturn($workingDirectory);
+
+ $exception = new ProcessFailedException($process);
+
+ $this->assertEquals(
+ "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}",
+ str_replace("'php'", 'php', $exception->getMessage())
+ );
+ }
+}
diff --git a/vendor/symfony/process/Tests/ProcessTest.php b/vendor/symfony/process/Tests/ProcessTest.php
new file mode 100755
index 0000000..8ae8d4c
--- /dev/null
+++ b/vendor/symfony/process/Tests/ProcessTest.php
@@ -0,0 +1,1557 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\InputStream;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Process;
+
+/**
+ * @author Robert Schönthal <seroscho@googlemail.com>
+ */
+class ProcessTest extends TestCase
+{
+ private static $phpBin;
+ private static $process;
+ private static $sigchild;
+
+ public static function setUpBeforeClass()
+ {
+ $phpBin = new PhpExecutableFinder();
+ self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === \PHP_SAPI ? 'php' : $phpBin->find());
+
+ ob_start();
+ phpinfo(INFO_GENERAL);
+ self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+ }
+
+ protected function tearDown()
+ {
+ if (self::$process) {
+ self::$process->stop(0);
+ self::$process = null;
+ }
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage The provided cwd "
+ * @expectedExceptionMessage "does not exist.
+ */
+ public function testInvalidCwd()
+ {
+ try {
+ // Check that it works fine if the CWD exists
+ $cmd = new Process(['echo', 'test'], __DIR__);
+ $cmd->run();
+ } catch (\Exception $e) {
+ $this->fail($e);
+ }
+
+ $cmd = new Process(['echo', 'test'], __DIR__.'/notfound/');
+ $cmd->run();
+ }
+
+ public function testThatProcessDoesNotThrowWarningDuringRun()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test is transient on Windows');
+ }
+ @trigger_error('Test Error', E_USER_NOTICE);
+ $process = $this->getProcessForCode('sleep(3)');
+ $process->run();
+ $actualError = error_get_last();
+ $this->assertEquals('Test Error', $actualError['message']);
+ $this->assertEquals(E_USER_NOTICE, $actualError['type']);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
+ */
+ public function testNegativeTimeoutFromConstructor()
+ {
+ $this->getProcess('', null, null, null, -1);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
+ */
+ public function testNegativeTimeoutFromSetter()
+ {
+ $p = $this->getProcess('');
+ $p->setTimeout(-1);
+ }
+
+ public function testFloatAndNullTimeout()
+ {
+ $p = $this->getProcess('');
+
+ $p->setTimeout(10);
+ $this->assertSame(10.0, $p->getTimeout());
+
+ $p->setTimeout(null);
+ $this->assertNull($p->getTimeout());
+
+ $p->setTimeout(0.0);
+ $this->assertNull($p->getTimeout());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testStopWithTimeoutIsActuallyWorking()
+ {
+ $p = $this->getProcess([self::$phpBin, __DIR__.'/NonStopableProcess.php', 30]);
+ $p->start();
+
+ while ($p->isRunning() && false === strpos($p->getOutput(), 'received')) {
+ usleep(1000);
+ }
+
+ if (!$p->isRunning()) {
+ throw new \LogicException('Process is not running: '.$p->getErrorOutput());
+ }
+
+ $start = microtime(true);
+ $p->stop(0.1);
+
+ $p->wait();
+
+ $this->assertLessThan(15, microtime(true) - $start);
+ }
+
+ public function testWaitUntilSpecificOutput()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestIncomplete('This test is too transient on Windows, help wanted to improve it');
+ }
+
+ $p = $this->getProcess([self::$phpBin, __DIR__.'/KillableProcessWithOutput.php']);
+ $p->start();
+
+ $start = microtime(true);
+
+ $completeOutput = '';
+ $result = $p->waitUntil(function ($type, $output) use (&$completeOutput) {
+ return false !== strpos($completeOutput .= $output, 'One more');
+ });
+ $this->assertTrue($result);
+ $this->assertLessThan(20, microtime(true) - $start);
+ $this->assertStringStartsWith("First iteration output\nSecond iteration output\nOne more", $completeOutput);
+ $p->stop();
+ }
+
+ public function testWaitUntilCanReturnFalse()
+ {
+ $p = $this->getProcess('echo foo');
+ $p->start();
+ $this->assertFalse($p->waitUntil(function () { return false; }));
+ }
+
+ public function testAllOutputIsActuallyReadOnTermination()
+ {
+ // this code will result in a maximum of 2 reads of 8192 bytes by calling
+ // start() and isRunning(). by the time getOutput() is called the process
+ // has terminated so the internal pipes array is already empty. normally
+ // the call to start() will not read any data as the process will not have
+ // generated output, but this is non-deterministic so we must count it as
+ // a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus
+ // another byte which will never be read.
+ $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
+
+ $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
+ $p = $this->getProcessForCode($code);
+
+ $p->start();
+
+ // Don't call Process::run nor Process::wait to avoid any read of pipes
+ $h = new \ReflectionProperty($p, 'process');
+ $h->setAccessible(true);
+ $h = $h->getValue($p);
+ $s = @proc_get_status($h);
+
+ while (!empty($s['running'])) {
+ usleep(1000);
+ $s = proc_get_status($h);
+ }
+
+ $o = $p->getOutput();
+
+ $this->assertEquals($expectedOutputSize, \strlen($o));
+ }
+
+ public function testCallbacksAreExecutedWithStart()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->start(function ($type, $buffer) use (&$data) {
+ $data .= $buffer;
+ });
+
+ $process->wait();
+
+ $this->assertSame('foo'.PHP_EOL, $data);
+ }
+
+ /**
+ * tests results from sub processes.
+ *
+ * @dataProvider responsesCodeProvider
+ */
+ public function testProcessResponses($expected, $getter, $code)
+ {
+ $p = $this->getProcessForCode($code);
+ $p->run();
+
+ $this->assertSame($expected, $p->$getter());
+ }
+
+ /**
+ * tests results from sub processes.
+ *
+ * @dataProvider pipesCodeProvider
+ */
+ public function testProcessPipes($code, $size)
+ {
+ $expected = str_repeat(str_repeat('*', 1024), $size).'!';
+ $expectedLength = (1024 * $size) + 1;
+
+ $p = $this->getProcessForCode($code);
+ $p->setInput($expected);
+ $p->run();
+
+ $this->assertEquals($expectedLength, \strlen($p->getOutput()));
+ $this->assertEquals($expectedLength, \strlen($p->getErrorOutput()));
+ }
+
+ /**
+ * @dataProvider pipesCodeProvider
+ */
+ public function testSetStreamAsInput($code, $size)
+ {
+ $expected = str_repeat(str_repeat('*', 1024), $size).'!';
+ $expectedLength = (1024 * $size) + 1;
+
+ $stream = fopen('php://temporary', 'w+');
+ fwrite($stream, $expected);
+ rewind($stream);
+
+ $p = $this->getProcessForCode($code);
+ $p->setInput($stream);
+ $p->run();
+
+ fclose($stream);
+
+ $this->assertEquals($expectedLength, \strlen($p->getOutput()));
+ $this->assertEquals($expectedLength, \strlen($p->getErrorOutput()));
+ }
+
+ public function testLiveStreamAsInput()
+ {
+ $stream = fopen('php://memory', 'r+');
+ fwrite($stream, 'hello');
+ rewind($stream);
+
+ $p = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $p->setInput($stream);
+ $p->start(function ($type, $data) use ($stream) {
+ if ('hello' === $data) {
+ fclose($stream);
+ }
+ });
+ $p->wait();
+
+ $this->assertSame('hello', $p->getOutput());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ * @expectedExceptionMessage Input can not be set while the process is running.
+ */
+ public function testSetInputWhileRunningThrowsAnException()
+ {
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->start();
+ try {
+ $process->setInput('foobar');
+ $process->stop();
+ $this->fail('A LogicException should have been raised.');
+ } catch (LogicException $e) {
+ }
+ $process->stop();
+
+ throw $e;
+ }
+
+ /**
+ * @dataProvider provideInvalidInputValues
+ * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
+ * @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings, Traversable objects or stream resources.
+ */
+ public function testInvalidInput($value)
+ {
+ $process = $this->getProcess('foo');
+ $process->setInput($value);
+ }
+
+ public function provideInvalidInputValues()
+ {
+ return [
+ [[]],
+ [new NonStringifiable()],
+ ];
+ }
+
+ /**
+ * @dataProvider provideInputValues
+ */
+ public function testValidInput($expected, $value)
+ {
+ $process = $this->getProcess('foo');
+ $process->setInput($value);
+ $this->assertSame($expected, $process->getInput());
+ }
+
+ public function provideInputValues()
+ {
+ return [
+ [null, null],
+ ['24.5', 24.5],
+ ['input data', 'input data'],
+ ];
+ }
+
+ public function chainedCommandsOutputProvider()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ return [
+ ["2 \r\n2\r\n", '&&', '2'],
+ ];
+ }
+
+ return [
+ ["1\n1\n", ';', '1'],
+ ["2\n2\n", '&&', '2'],
+ ];
+ }
+
+ /**
+ * @dataProvider chainedCommandsOutputProvider
+ */
+ public function testChainedCommandsOutput($expected, $operator, $input)
+ {
+ $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
+ $process->run();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+
+ public function testCallbackIsExecutedForOutput()
+ {
+ $p = $this->getProcessForCode('echo \'foo\';');
+
+ $called = false;
+ $p->run(function ($type, $buffer) use (&$called) {
+ $called = 'foo' === $buffer;
+ });
+
+ $this->assertTrue($called, 'The callback should be executed with the output');
+ }
+
+ public function testCallbackIsExecutedForOutputWheneverOutputIsDisabled()
+ {
+ $p = $this->getProcessForCode('echo \'foo\';');
+ $p->disableOutput();
+
+ $called = false;
+ $p->run(function ($type, $buffer) use (&$called) {
+ $called = 'foo' === $buffer;
+ });
+
+ $this->assertTrue($called, 'The callback should be executed with the output');
+ }
+
+ public function testGetErrorOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
+
+ $p->run();
+ $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
+ }
+
+ public function testFlushErrorOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
+
+ $p->run();
+ $p->clearErrorOutput();
+ $this->assertEmpty($p->getErrorOutput());
+ }
+
+ /**
+ * @dataProvider provideIncrementalOutput
+ */
+ public function testIncrementalOutput($getOutput, $getIncrementalOutput, $uri)
+ {
+ $lock = tempnam(sys_get_temp_dir(), __FUNCTION__);
+
+ $p = $this->getProcessForCode('file_put_contents($s = \''.$uri.'\', \'foo\'); flock(fopen('.var_export($lock, true).', \'r\'), LOCK_EX); file_put_contents($s, \'bar\');');
+
+ $h = fopen($lock, 'w');
+ flock($h, LOCK_EX);
+
+ $p->start();
+
+ foreach (['foo', 'bar'] as $s) {
+ while (false === strpos($p->$getOutput(), $s)) {
+ usleep(1000);
+ }
+
+ $this->assertSame($s, $p->$getIncrementalOutput());
+ $this->assertSame('', $p->$getIncrementalOutput());
+
+ flock($h, LOCK_UN);
+ }
+
+ fclose($h);
+ }
+
+ public function provideIncrementalOutput()
+ {
+ return [
+ ['getOutput', 'getIncrementalOutput', 'php://stdout'],
+ ['getErrorOutput', 'getIncrementalErrorOutput', 'php://stderr'],
+ ];
+ }
+
+ public function testGetOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }');
+
+ $p->run();
+ $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
+ }
+
+ public function testFlushOutput()
+ {
+ $p = $this->getProcessForCode('$n=0;while ($n<3) {echo \' foo \';$n++;}');
+
+ $p->run();
+ $p->clearOutput();
+ $this->assertEmpty($p->getOutput());
+ }
+
+ public function testZeroAsOutput()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line
+ $p = $this->getProcess('echo | set /p dummyName=0');
+ } else {
+ $p = $this->getProcess('printf 0');
+ }
+
+ $p->run();
+ $this->assertSame('0', $p->getOutput());
+ }
+
+ public function testExitCodeCommandFailed()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX exit code');
+ }
+
+ // such command run in bash return an exitcode 127
+ $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
+ $process->run();
+
+ $this->assertGreaterThan(0, $process->getExitCode());
+ }
+
+ public function testTTYCommand()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not have /dev/tty support');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null && '.$this->getProcessForCode('usleep(100000);')->getCommandLine());
+ $process->setTty(true);
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->wait();
+
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ }
+
+ public function testTTYCommandExitCode()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does have /dev/tty support');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null');
+ $process->setTty(true);
+ $process->run();
+
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage TTY mode is not supported on Windows platform.
+ */
+ public function testTTYInWindowsEnvironment()
+ {
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test is for Windows platform only');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null');
+ $process->setTty(false);
+ $process->setTty(true);
+ }
+
+ public function testExitCodeTextIsNullWhenExitCodeIsNull()
+ {
+ $process = $this->getProcess('');
+ $this->assertNull($process->getExitCodeText());
+ }
+
+ public function testPTYCommand()
+ {
+ if (!Process::isPtySupported()) {
+ $this->markTestSkipped('PTY is not supported on this operating system.');
+ }
+
+ $process = $this->getProcess('echo "foo"');
+ $process->setPty(true);
+ $process->run();
+
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ $this->assertEquals("foo\r\n", $process->getOutput());
+ }
+
+ public function testMustRun()
+ {
+ $process = $this->getProcess('echo foo');
+
+ $this->assertSame($process, $process->mustRun());
+ $this->assertEquals('foo'.PHP_EOL, $process->getOutput());
+ }
+
+ public function testSuccessfulMustRunHasCorrectExitCode()
+ {
+ $process = $this->getProcess('echo foo')->mustRun();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\ProcessFailedException
+ */
+ public function testMustRunThrowsException()
+ {
+ $process = $this->getProcess('exit 1');
+ $process->mustRun();
+ }
+
+ public function testExitCodeText()
+ {
+ $process = $this->getProcess('');
+ $r = new \ReflectionObject($process);
+ $p = $r->getProperty('exitcode');
+ $p->setAccessible(true);
+
+ $p->setValue($process, 2);
+ $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
+ }
+
+ public function testStartIsNonBlocking()
+ {
+ $process = $this->getProcessForCode('usleep(500000);');
+ $start = microtime(true);
+ $process->start();
+ $end = microtime(true);
+ $this->assertLessThan(0.4, $end - $start);
+ $process->stop();
+ }
+
+ public function testUpdateStatus()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertGreaterThan(0, \strlen($process->getOutput()));
+ }
+
+ public function testGetExitCodeIsNullOnStart()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $this->assertNull($process->getExitCode());
+ $process->start();
+ $this->assertNull($process->getExitCode());
+ $process->wait();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testGetExitCodeIsNullOnWhenStartingAgain()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $process->run();
+ $this->assertEquals(0, $process->getExitCode());
+ $process->start();
+ $this->assertNull($process->getExitCode());
+ $process->wait();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testGetExitCode()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertSame(0, $process->getExitCode());
+ }
+
+ public function testStatus()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $this->assertFalse($process->isRunning());
+ $this->assertFalse($process->isStarted());
+ $this->assertFalse($process->isTerminated());
+ $this->assertSame(Process::STATUS_READY, $process->getStatus());
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $this->assertTrue($process->isStarted());
+ $this->assertFalse($process->isTerminated());
+ $this->assertSame(Process::STATUS_STARTED, $process->getStatus());
+ $process->wait();
+ $this->assertFalse($process->isRunning());
+ $this->assertTrue($process->isStarted());
+ $this->assertTrue($process->isTerminated());
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ }
+
+ public function testStop()
+ {
+ $process = $this->getProcessForCode('sleep(31);');
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->stop();
+ $this->assertFalse($process->isRunning());
+ }
+
+ public function testIsSuccessful()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testIsSuccessfulOnlyAfterTerminated()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $process->start();
+
+ $this->assertFalse($process->isSuccessful());
+
+ $process->wait();
+
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testIsNotSuccessful()
+ {
+ $process = $this->getProcessForCode('throw new \Exception(\'BOUM\');');
+ $process->run();
+ $this->assertFalse($process->isSuccessful());
+ }
+
+ public function testProcessIsNotSignaled()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertFalse($process->hasBeenSignaled());
+ }
+
+ public function testProcessWithoutTermSignal()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertEquals(0, $process->getTermSignal());
+ }
+
+ public function testProcessIsSignaledIfStopped()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcessForCode('sleep(32);');
+ $process->start();
+ $process->stop();
+ $this->assertTrue($process->hasBeenSignaled());
+ $this->assertEquals(15, $process->getTermSignal()); // SIGTERM
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\ProcessSignaledException
+ * @expectedExceptionMessage The process has been signaled with signal "9".
+ */
+ public function testProcessThrowsExceptionWhenExternallySignaled()
+ {
+ if (!\function_exists('posix_kill')) {
+ $this->markTestSkipped('Function posix_kill is required.');
+ }
+
+ if (self::$sigchild) {
+ $this->markTestSkipped('PHP is compiled with --enable-sigchild.');
+ }
+
+ $process = $this->getProcessForCode('sleep(32.1);');
+ $process->start();
+ posix_kill($process->getPid(), 9); // SIGKILL
+
+ $process->wait();
+ }
+
+ public function testRestart()
+ {
+ $process1 = $this->getProcessForCode('echo getmypid();');
+ $process1->run();
+ $process2 = $process1->restart();
+
+ $process2->wait(); // wait for output
+
+ // Ensure that both processed finished and the output is numeric
+ $this->assertFalse($process1->isRunning());
+ $this->assertFalse($process2->isRunning());
+ $this->assertInternalType('numeric', $process1->getOutput());
+ $this->assertInternalType('numeric', $process2->getOutput());
+
+ // Ensure that restart returned a new process by check that the output is different
+ $this->assertNotEquals($process1->getOutput(), $process2->getOutput());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
+ * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
+ */
+ public function testRunProcessWithTimeout()
+ {
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->setTimeout(0.1);
+ $start = microtime(true);
+ try {
+ $process->run();
+ $this->fail('A RuntimeException should have been raised');
+ } catch (RuntimeException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
+ * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
+ */
+ public function testIterateOverProcessWithTimeout()
+ {
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->setTimeout(0.1);
+ $start = microtime(true);
+ try {
+ $process->start();
+ foreach ($process as $buffer);
+ $this->fail('A RuntimeException should have been raised');
+ } catch (RuntimeException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ public function testCheckTimeoutOnNonStartedProcess()
+ {
+ $process = $this->getProcess('echo foo');
+ $this->assertNull($process->checkTimeout());
+ }
+
+ public function testCheckTimeoutOnTerminatedProcess()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertNull($process->checkTimeout());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
+ * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
+ */
+ public function testCheckTimeoutOnStartedProcess()
+ {
+ $process = $this->getProcessForCode('sleep(33);');
+ $process->setTimeout(0.1);
+
+ $process->start();
+ $start = microtime(true);
+
+ try {
+ while ($process->isRunning()) {
+ $process->checkTimeout();
+ usleep(100000);
+ }
+ $this->fail('A ProcessTimedOutException should have been raised');
+ } catch (ProcessTimedOutException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ public function testIdleTimeout()
+ {
+ $process = $this->getProcessForCode('sleep(34);');
+ $process->setTimeout(60);
+ $process->setIdleTimeout(0.1);
+
+ try {
+ $process->run();
+
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $e) {
+ $this->assertTrue($e->isIdleTimeout());
+ $this->assertFalse($e->isGeneralTimeout());
+ $this->assertEquals(0.1, $e->getExceededTimeout());
+ }
+ }
+
+ public function testIdleTimeoutNotExceededWhenOutputIsSent()
+ {
+ $process = $this->getProcessForCode('while (true) {echo \'foo \'; usleep(1000);}');
+ $process->setTimeout(1);
+ $process->start();
+
+ while (false === strpos($process->getOutput(), 'foo')) {
+ usleep(1000);
+ }
+
+ $process->setIdleTimeout(0.5);
+
+ try {
+ $process->wait();
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $e) {
+ $this->assertTrue($e->isGeneralTimeout(), 'A general timeout is expected.');
+ $this->assertFalse($e->isIdleTimeout(), 'No idle timeout is expected.');
+ $this->assertEquals(1, $e->getExceededTimeout());
+ }
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
+ * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
+ */
+ public function testStartAfterATimeout()
+ {
+ $process = $this->getProcessForCode('sleep(35);');
+ $process->setTimeout(0.1);
+
+ try {
+ $process->run();
+ $this->fail('A ProcessTimedOutException should have been raised.');
+ } catch (ProcessTimedOutException $e) {
+ }
+ $this->assertFalse($process->isRunning());
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->stop(0);
+
+ throw $e;
+ }
+
+ public function testGetPid()
+ {
+ $process = $this->getProcessForCode('sleep(36);');
+ $process->start();
+ $this->assertGreaterThan(0, $process->getPid());
+ $process->stop(0);
+ }
+
+ public function testGetPidIsNullBeforeStart()
+ {
+ $process = $this->getProcess('foo');
+ $this->assertNull($process->getPid());
+ }
+
+ public function testGetPidIsNullAfterRun()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertNull($process->getPid());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testSignal()
+ {
+ $process = $this->getProcess([self::$phpBin, __DIR__.'/SignalListener.php']);
+ $process->start();
+
+ while (false === strpos($process->getOutput(), 'Caught')) {
+ usleep(1000);
+ }
+ $process->signal(SIGUSR1);
+ $process->wait();
+
+ $this->assertEquals('Caught SIGUSR1', $process->getOutput());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testExitCodeIsAvailableAfterSignal()
+ {
+ $process = $this->getProcess('sleep 4');
+ $process->start();
+ $process->signal(SIGKILL);
+
+ while ($process->isRunning()) {
+ usleep(10000);
+ }
+
+ $this->assertFalse($process->isRunning());
+ $this->assertTrue($process->hasBeenSignaled());
+ $this->assertFalse($process->isSuccessful());
+ $this->assertEquals(137, $process->getExitCode());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ * @expectedExceptionMessage Can not send signal on a non running process.
+ */
+ public function testSignalProcessNotRunning()
+ {
+ $process = $this->getProcess('foo');
+ $process->signal(1); // SIGHUP
+ }
+
+ /**
+ * @dataProvider provideMethodsThatNeedARunningProcess
+ */
+ public function testMethodsThatNeedARunningProcess($method)
+ {
+ $process = $this->getProcess('foo');
+
+ if (method_exists($this, 'expectException')) {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage(sprintf('Process must be started before calling %s.', $method));
+ } else {
+ $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method));
+ }
+
+ $process->{$method}();
+ }
+
+ public function provideMethodsThatNeedARunningProcess()
+ {
+ return [
+ ['getOutput'],
+ ['getIncrementalOutput'],
+ ['getErrorOutput'],
+ ['getIncrementalErrorOutput'],
+ ['wait'],
+ ];
+ }
+
+ /**
+ * @dataProvider provideMethodsThatNeedATerminatedProcess
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ * @expectedExceptionMessage Process must be terminated before calling
+ */
+ public function testMethodsThatNeedATerminatedProcess($method)
+ {
+ $process = $this->getProcessForCode('sleep(37);');
+ $process->start();
+ try {
+ $process->{$method}();
+ $process->stop(0);
+ $this->fail('A LogicException must have been thrown');
+ } catch (\Exception $e) {
+ }
+ $process->stop(0);
+
+ throw $e;
+ }
+
+ public function provideMethodsThatNeedATerminatedProcess()
+ {
+ return [
+ ['hasBeenSignaled'],
+ ['getTermSignal'],
+ ['hasBeenStopped'],
+ ['getStopSignal'],
+ ];
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ */
+ public function testWrongSignal()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('POSIX signals do not work on Windows');
+ }
+
+ $process = $this->getProcessForCode('sleep(38);');
+ $process->start();
+ try {
+ $process->signal(-4);
+ $this->fail('A RuntimeException must have been thrown');
+ } catch (RuntimeException $e) {
+ $process->stop(0);
+ }
+
+ throw $e;
+ }
+
+ public function testDisableOutputDisablesTheOutput()
+ {
+ $p = $this->getProcess('foo');
+ $this->assertFalse($p->isOutputDisabled());
+ $p->disableOutput();
+ $this->assertTrue($p->isOutputDisabled());
+ $p->enableOutput();
+ $this->assertFalse($p->isOutputDisabled());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage Disabling output while the process is running is not possible.
+ */
+ public function testDisableOutputWhileRunningThrowsException()
+ {
+ $p = $this->getProcessForCode('sleep(39);');
+ $p->start();
+ $p->disableOutput();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage Enabling output while the process is running is not possible.
+ */
+ public function testEnableOutputWhileRunningThrowsException()
+ {
+ $p = $this->getProcessForCode('sleep(40);');
+ $p->disableOutput();
+ $p->start();
+ $p->enableOutput();
+ }
+
+ public function testEnableOrDisableOutputAfterRunDoesNotThrowException()
+ {
+ $p = $this->getProcess('echo foo');
+ $p->disableOutput();
+ $p->run();
+ $p->enableOutput();
+ $p->disableOutput();
+ $this->assertTrue($p->isOutputDisabled());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ * @expectedExceptionMessage Output can not be disabled while an idle timeout is set.
+ */
+ public function testDisableOutputWhileIdleTimeoutIsSet()
+ {
+ $process = $this->getProcess('foo');
+ $process->setIdleTimeout(1);
+ $process->disableOutput();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ * @expectedExceptionMessage timeout can not be set while the output is disabled.
+ */
+ public function testSetIdleTimeoutWhileOutputIsDisabled()
+ {
+ $process = $this->getProcess('foo');
+ $process->disableOutput();
+ $process->setIdleTimeout(1);
+ }
+
+ public function testSetNullIdleTimeoutWhileOutputIsDisabled()
+ {
+ $process = $this->getProcess('foo');
+ $process->disableOutput();
+ $this->assertSame($process, $process->setIdleTimeout(null));
+ }
+
+ /**
+ * @dataProvider provideOutputFetchingMethods
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ * @expectedExceptionMessage Output has been disabled.
+ */
+ public function testGetOutputWhileDisabled($fetchMethod)
+ {
+ $p = $this->getProcessForCode('sleep(41);');
+ $p->disableOutput();
+ $p->start();
+ $p->{$fetchMethod}();
+ }
+
+ public function provideOutputFetchingMethods()
+ {
+ return [
+ ['getOutput'],
+ ['getIncrementalOutput'],
+ ['getErrorOutput'],
+ ['getIncrementalErrorOutput'],
+ ];
+ }
+
+ public function testStopTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(42);');
+ $process->run(function () use ($process) {
+ $process->stop();
+ });
+ $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function testKillSignalTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(43);');
+ $process->run(function () use ($process) {
+ $process->signal(9); // SIGKILL
+ });
+ $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function testTermSignalTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(44);');
+ $process->run(function () use ($process) {
+ $process->signal(15); // SIGTERM
+ });
+ $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function responsesCodeProvider()
+ {
+ return [
+ //expected output / getter / code to execute
+ // [1,'getExitCode','exit(1);'],
+ // [true,'isSuccessful','exit();'],
+ ['output', 'getOutput', 'echo \'output\';'],
+ ];
+ }
+
+ public function pipesCodeProvider()
+ {
+ $variations = [
+ 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
+ 'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
+ ];
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ // Avoid XL buffers on Windows because of https://bugs.php.net/bug.php?id=65650
+ $sizes = [1, 2, 4, 8];
+ } else {
+ $sizes = [1, 16, 64, 1024, 4096];
+ }
+
+ $codes = [];
+ foreach ($sizes as $size) {
+ foreach ($variations as $code) {
+ $codes[] = [$code, $size];
+ }
+ }
+
+ return $codes;
+ }
+
+ /**
+ * @dataProvider provideVariousIncrementals
+ */
+ public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
+ {
+ $process = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\''.$stream.'\', $n, 1); $n++; usleep(1000); }', null, null, null, null);
+ $process->start();
+ $result = '';
+ $limit = microtime(true) + 3;
+ $expected = '012';
+
+ while ($result !== $expected && microtime(true) < $limit) {
+ $result .= $process->$method();
+ }
+
+ $this->assertSame($expected, $result);
+ $process->stop();
+ }
+
+ public function provideVariousIncrementals()
+ {
+ return [
+ ['php://stdout', 'getIncrementalOutput'],
+ ['php://stderr', 'getIncrementalErrorOutput'],
+ ];
+ }
+
+ public function testIteratorInput()
+ {
+ $input = function () {
+ yield 'ping';
+ yield 'pong';
+ };
+
+ $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);', null, null, $input());
+ $process->run();
+ $this->assertSame('pingpong', $process->getOutput());
+ }
+
+ public function testSimpleInputStream()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('echo \'ping\'; echo fread(STDIN, 4); echo fread(STDIN, 4);');
+ $process->setInput($input);
+
+ $process->start(function ($type, $data) use ($input) {
+ if ('ping' === $data) {
+ $input->write('pang');
+ } elseif (!$input->isClosed()) {
+ $input->write('pong');
+ $input->close();
+ }
+ });
+
+ $process->wait();
+ $this->assertSame('pingpangpong', $process->getOutput());
+ }
+
+ public function testInputStreamWithCallable()
+ {
+ $i = 0;
+ $stream = fopen('php://memory', 'w+');
+ $stream = function () use ($stream, &$i) {
+ if ($i < 3) {
+ rewind($stream);
+ fwrite($stream, ++$i);
+ rewind($stream);
+
+ return $stream;
+ }
+ };
+
+ $input = new InputStream();
+ $input->onEmpty($stream);
+ $input->write($stream());
+
+ $process = $this->getProcessForCode('echo fread(STDIN, 3);');
+ $process->setInput($input);
+ $process->start(function ($type, $data) use ($input) {
+ $input->close();
+ });
+
+ $process->wait();
+ $this->assertSame('123', $process->getOutput());
+ }
+
+ public function testInputStreamWithGenerator()
+ {
+ $input = new InputStream();
+ $input->onEmpty(function ($input) {
+ yield 'pong';
+ $input->close();
+ });
+
+ $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $process->setInput($input);
+ $process->start();
+ $input->write('ping');
+ $process->wait();
+ $this->assertSame('pingpong', $process->getOutput());
+ }
+
+ public function testInputStreamOnEmpty()
+ {
+ $i = 0;
+ $input = new InputStream();
+ $input->onEmpty(function () use (&$i) { ++$i; });
+
+ $process = $this->getProcessForCode('echo 123; echo fread(STDIN, 1); echo 456;');
+ $process->setInput($input);
+ $process->start(function ($type, $data) use ($input) {
+ if ('123' === $data) {
+ $input->close();
+ }
+ });
+ $process->wait();
+
+ $this->assertSame(0, $i, 'InputStream->onEmpty callback should be called only when the input *becomes* empty');
+ $this->assertSame('123456', $process->getOutput());
+ }
+
+ public function testIteratorOutput()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);');
+ $process->setInput($input);
+ $process->start();
+ $output = [];
+
+ foreach ($process as $type => $data) {
+ $output[] = [$type, $data];
+ break;
+ }
+ $expectedOutput = [
+ [$process::OUT, '123'],
+ ];
+ $this->assertSame($expectedOutput, $output);
+
+ $input->write(345);
+
+ foreach ($process as $type => $data) {
+ $output[] = [$type, $data];
+ }
+
+ $this->assertSame('', $process->getOutput());
+ $this->assertFalse($process->isRunning());
+
+ $expectedOutput = [
+ [$process::OUT, '123'],
+ [$process::ERR, '234'],
+ [$process::OUT, '345'],
+ [$process::ERR, '456'],
+ ];
+ $this->assertSame($expectedOutput, $output);
+ }
+
+ public function testNonBlockingNorClearingIteratorOutput()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('fwrite(STDOUT, fread(STDIN, 3));');
+ $process->setInput($input);
+ $process->start();
+ $output = [];
+
+ foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
+ $output[] = [$type, $data];
+ break;
+ }
+ $expectedOutput = [
+ [$process::OUT, ''],
+ ];
+ $this->assertSame($expectedOutput, $output);
+
+ $input->write(123);
+
+ foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
+ if ('' !== $data) {
+ $output[] = [$type, $data];
+ }
+ }
+
+ $this->assertSame('123', $process->getOutput());
+ $this->assertFalse($process->isRunning());
+
+ $expectedOutput = [
+ [$process::OUT, ''],
+ [$process::OUT, '123'],
+ ];
+ $this->assertSame($expectedOutput, $output);
+ }
+
+ public function testChainedProcesses()
+ {
+ $p1 = $this->getProcessForCode('fwrite(STDERR, 123); fwrite(STDOUT, 456);');
+ $p2 = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $p2->setInput($p1);
+
+ $p1->start();
+ $p2->run();
+
+ $this->assertSame('123', $p1->getErrorOutput());
+ $this->assertSame('', $p1->getOutput());
+ $this->assertSame('', $p2->getErrorOutput());
+ $this->assertSame('456', $p2->getOutput());
+ }
+
+ public function testSetBadEnv()
+ {
+ $process = $this->getProcess('echo hello');
+ $process->setEnv(['bad%%' => '123']);
+ $process->inheritEnvironmentVariables(true);
+
+ $process->run();
+
+ $this->assertSame('hello'.PHP_EOL, $process->getOutput());
+ $this->assertSame('', $process->getErrorOutput());
+ }
+
+ public function testEnvBackupDoesNotDeleteExistingVars()
+ {
+ putenv('existing_var=foo');
+ $_ENV['existing_var'] = 'foo';
+ $process = $this->getProcess('php -r "echo getenv(\'new_test_var\');"');
+ $process->setEnv(['existing_var' => 'bar', 'new_test_var' => 'foo']);
+ $process->inheritEnvironmentVariables();
+
+ $process->run();
+
+ $this->assertSame('foo', $process->getOutput());
+ $this->assertSame('foo', getenv('existing_var'));
+ $this->assertFalse(getenv('new_test_var'));
+
+ putenv('existing_var');
+ unset($_ENV['existing_var']);
+ }
+
+ public function testEnvIsInherited()
+ {
+ $process = $this->getProcessForCode('echo serialize($_SERVER);', null, ['BAR' => 'BAZ', 'EMPTY' => '']);
+
+ putenv('FOO=BAR');
+ $_ENV['FOO'] = 'BAR';
+
+ $process->run();
+
+ $expected = ['BAR' => 'BAZ', 'EMPTY' => '', 'FOO' => 'BAR'];
+ $env = array_intersect_key(unserialize($process->getOutput()), $expected);
+
+ $this->assertEquals($expected, $env);
+
+ putenv('FOO');
+ unset($_ENV['FOO']);
+ }
+
+ public function testGetCommandLine()
+ {
+ $p = new Process(['/usr/bin/php']);
+
+ $expected = '\\' === \DIRECTORY_SEPARATOR ? '"/usr/bin/php"' : "'/usr/bin/php'";
+ $this->assertSame($expected, $p->getCommandLine());
+ }
+
+ /**
+ * @dataProvider provideEscapeArgument
+ */
+ public function testEscapeArgument($arg)
+ {
+ $p = new Process([self::$phpBin, '-r', 'echo $argv[1];', $arg]);
+ $p->run();
+
+ $this->assertSame((string) $arg, $p->getOutput());
+ }
+
+ public function testRawCommandLine()
+ {
+ $p = Process::fromShellCommandline(sprintf('"%s" -r %s "a" "" "b"', self::$phpBin, escapeshellarg('print_r($argv);')));
+ $p->run();
+
+ $expected = <<<EOTXT
+Array
+(
+ [0] => -
+ [1] => a
+ [2] =>
+ [3] => b
+)
+
+EOTXT;
+ $this->assertSame($expected, str_replace('Standard input code', '-', $p->getOutput()));
+ }
+
+ public function provideEscapeArgument()
+ {
+ yield ['a"b%c%'];
+ yield ['a"b^c^'];
+ yield ["a\nb'c"];
+ yield ['a^b c!'];
+ yield ["a!b\tc"];
+ yield ['a\\\\"\\"'];
+ yield ['éÉèÈàÀöä'];
+ yield [null];
+ yield [1];
+ yield [1.1];
+ }
+
+ public function testEnvArgument()
+ {
+ $env = ['FOO' => 'Foo', 'BAR' => 'Bar'];
+ $cmd = '\\' === \DIRECTORY_SEPARATOR ? 'echo !FOO! !BAR! !BAZ!' : 'echo $FOO $BAR $BAZ';
+ $p = Process::fromShellCommandline($cmd, null, $env);
+ $p->run(null, ['BAR' => 'baR', 'BAZ' => 'baZ']);
+
+ $this->assertSame('Foo baR baZ', rtrim($p->getOutput()));
+ $this->assertSame($env, $p->getEnv());
+ }
+
+ public function testWaitStoppedDeadProcess()
+ {
+ $process = $this->getProcess(self::$phpBin.' '.__DIR__.'/ErrorProcessInitiator.php -e '.self::$phpBin);
+ $process->start();
+ $process->setTimeout(2);
+ $process->wait();
+ $this->assertFalse($process->isRunning());
+ }
+
+ /**
+ * @param string $commandline
+ * @param string|null $cwd
+ * @param array|null $env
+ * @param string|null $input
+ * @param int $timeout
+ * @param array $options
+ *
+ * @return Process
+ */
+ private function getProcess($commandline, string $cwd = null, array $env = null, $input = null, ?int $timeout = 60): Process
+ {
+ if (\is_string($commandline)) {
+ $process = Process::fromShellCommandline($commandline, $cwd, $env, $input, $timeout);
+ } else {
+ $process = new Process($commandline, $cwd, $env, $input, $timeout);
+ }
+ $process->inheritEnvironmentVariables();
+
+ if (self::$process) {
+ self::$process->stop(0);
+ }
+
+ return self::$process = $process;
+ }
+
+ private function getProcessForCode(string $code, string $cwd = null, array $env = null, $input = null, ?int $timeout = 60): Process
+ {
+ return $this->getProcess([self::$phpBin, '-r', $code], $cwd, $env, $input, $timeout);
+ }
+}
+
+class NonStringifiable
+{
+}
diff --git a/vendor/symfony/process/Tests/SignalListener.php b/vendor/symfony/process/Tests/SignalListener.php
new file mode 100755
index 0000000..9e30ce3
--- /dev/null
+++ b/vendor/symfony/process/Tests/SignalListener.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+pcntl_signal(SIGUSR1, function () { echo 'SIGUSR1'; exit; });
+
+echo 'Caught ';
+
+$n = 0;
+
+while ($n++ < 400) {
+ usleep(10000);
+ pcntl_signal_dispatch();
+}
diff --git a/vendor/symfony/process/composer.json b/vendor/symfony/process/composer.json
new file mode 100755
index 0000000..d3efd02
--- /dev/null
+++ b/vendor/symfony/process/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "symfony/process",
+ "type": "library",
+ "description": "Symfony Process Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^7.1.3"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Process\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.3-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/process/phpunit.xml.dist b/vendor/symfony/process/phpunit.xml.dist
new file mode 100755
index 0000000..c32f251
--- /dev/null
+++ b/vendor/symfony/process/phpunit.xml.dist
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
+ backupGlobals="false"
+ colors="true"
+ bootstrap="vendor/autoload.php"
+ failOnRisky="true"
+ failOnWarning="true"
+>
+ <php>
+ <ini name="error_reporting" value="-1" />
+ </php>
+
+ <testsuites>
+ <testsuite name="Symfony Process Component Test Suite">
+ <directory>./Tests/</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>./</directory>
+ <exclude>
+ <directory>./Tests</directory>
+ <directory>./vendor</directory>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/vendor/symfony/psr-http-message-bridge/.gitignore b/vendor/symfony/psr-http-message-bridge/.gitignore
new file mode 100755
index 0000000..027924f
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/.gitignore
@@ -0,0 +1,4 @@
+vendor/
+composer.lock
+phpunit.xml
+.php_cs.cache
diff --git a/vendor/symfony/psr-http-message-bridge/.php_cs.dist b/vendor/symfony/psr-http-message-bridge/.php_cs.dist
new file mode 100755
index 0000000..d741d39
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/.php_cs.dist
@@ -0,0 +1,24 @@
+<?php
+
+return PhpCsFixer\Config::create()
+ ->setRules([
+ '@Symfony' => true,
+ '@Symfony:risky' => true,
+ '@PHPUnit48Migration:risky' => true,
+ 'php_unit_no_expectation_annotation' => false, // part of `PHPUnitXYMigration:risky` ruleset, to be enabled when PHPUnit 4.x support will be dropped, as we don't want to rewrite exceptions handling twice
+ 'array_syntax' => ['syntax' => 'short'],
+ 'fopen_flags' => false,
+ 'ordered_imports' => true,
+ 'protected_to_private' => false,
+ // Part of @Symfony:risky in PHP-CS-Fixer 2.13.0. To be removed from the config file once upgrading
+ 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'namespaced'],
+ // Part of future @Symfony ruleset in PHP-CS-Fixer To be removed from the config file once upgrading
+ 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
+ ])
+ ->setRiskyAllowed(true)
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__)
+ ->name('*.php')
+ )
+;
diff --git a/vendor/symfony/psr-http-message-bridge/.travis.yml b/vendor/symfony/psr-http-message-bridge/.travis.yml
new file mode 100755
index 0000000..a247c61
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/.travis.yml
@@ -0,0 +1,47 @@
+language: php
+sudo: false
+cache:
+ directories:
+ - $HOME/.composer/cache/files
+ - $HOME/symfony-bridge/.phpunit
+
+env:
+ global:
+ - PHPUNIT_FLAGS="-v"
+ - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit"
+
+matrix:
+ fast_finish: true
+ include:
+ # Minimum supported dependencies with the latest and oldest PHP version
+ - php: 7.2
+ env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors"
+
+ - php: 7.1
+ - php: 7.2
+ env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text"
+
+ # Latest commit to master
+ - php: 7.2
+ env: STABILITY="dev"
+
+ allow_failures:
+ # Dev-master is allowed to fail.
+ - env: STABILITY="dev"
+
+before_install:
+ - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi
+ - if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi;
+ - if ! [ -v "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi;
+
+install:
+ # To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355
+ - if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi
+ - composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction
+ - ./vendor/bin/simple-phpunit install
+
+script:
+ - composer validate --strict --no-check-lock
+ # simple-phpunit is the PHPUnit wrapper provided by the PHPUnit Bridge component and
+ # it helps with testing legacy code and deprecations (composer require symfony/phpunit-bridge)
+ - ./vendor/bin/simple-phpunit $PHPUNIT_FLAGS
diff --git a/vendor/symfony/psr-http-message-bridge/CHANGELOG.md b/vendor/symfony/psr-http-message-bridge/CHANGELOG.md
new file mode 100755
index 0000000..70f3b82
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/CHANGELOG.md
@@ -0,0 +1,18 @@
+CHANGELOG
+=========
+
+* 1.1.0 (2018-08-30)
+
+ * Added support for creating PSR-7 messages using PSR-17 factories
+
+* 1.0.2 (2017-12-19)
+
+ * Fixed request target in PSR7 Request (mtibben)
+
+* 1.0.1 (2017-12-04)
+
+ * Added support for Symfony 4 (dunglas)
+
+* 1.0.0 (2016-09-14)
+
+ * Initial release
diff --git a/vendor/symfony/psr-http-message-bridge/Factory/DiactorosFactory.php b/vendor/symfony/psr-http-message-bridge/Factory/DiactorosFactory.php
new file mode 100755
index 0000000..d3e70b5
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Factory/DiactorosFactory.php
@@ -0,0 +1,175 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Factory;
+
+@trigger_error(sprintf('The "%s" class is deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead.', DiactorosFactory::class), E_USER_DEPRECATED);
+
+use Psr\Http\Message\UploadedFileInterface;
+use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+use Zend\Diactoros\Response as DiactorosResponse;
+use Zend\Diactoros\ServerRequest;
+use Zend\Diactoros\ServerRequestFactory as DiactorosRequestFactory;
+use Zend\Diactoros\Stream as DiactorosStream;
+use Zend\Diactoros\UploadedFile as DiactorosUploadedFile;
+
+/**
+ * Builds Psr\HttpMessage instances using the Zend Diactoros implementation.
+ *
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ *
+ * @deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead
+ */
+class DiactorosFactory implements HttpMessageFactoryInterface
+{
+ public function __construct()
+ {
+ if (!class_exists('Zend\Diactoros\ServerRequestFactory')) {
+ throw new \RuntimeException('Zend Diactoros must be installed to use the DiactorosFactory.');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createRequest(Request $symfonyRequest)
+ {
+ $server = method_exists('Zend\Diactoros\ServerRequestFactory', 'normalizeServer')
+ ? DiactorosRequestFactory::normalizeServer($symfonyRequest->server->all())
+ : \Zend\Diactoros\normalizeServer($symfonyRequest->server->all());
+ $headers = $symfonyRequest->headers->all();
+
+ $body = new DiactorosStream($symfonyRequest->getContent(true));
+
+ $files = method_exists('Zend\Diactoros\ServerRequestFactory', 'normalizeFiles')
+ ? DiactorosRequestFactory::normalizeFiles($this->getFiles($symfonyRequest->files->all()))
+ : \Zend\Diactoros\normalizeUploadedFiles($this->getFiles($symfonyRequest->files->all()));
+
+ $request = new ServerRequest(
+ $server,
+ $files,
+ $symfonyRequest->getSchemeAndHttpHost().$symfonyRequest->getRequestUri(),
+ $symfonyRequest->getMethod(),
+ $body,
+ $headers
+ );
+
+ $request = $request
+ ->withCookieParams($symfonyRequest->cookies->all())
+ ->withQueryParams($symfonyRequest->query->all())
+ ->withParsedBody($symfonyRequest->request->all())
+ ->withRequestTarget($symfonyRequest->getRequestUri())
+ ;
+
+ foreach ($symfonyRequest->attributes->all() as $key => $value) {
+ $request = $request->withAttribute($key, $value);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Converts Symfony uploaded files array to the PSR one.
+ *
+ * @param array $uploadedFiles
+ *
+ * @return array
+ */
+ private function getFiles(array $uploadedFiles)
+ {
+ $files = [];
+
+ foreach ($uploadedFiles as $key => $value) {
+ if (null === $value) {
+ $files[$key] = new DiactorosUploadedFile(null, 0, UPLOAD_ERR_NO_FILE, null, null);
+ continue;
+ }
+ if ($value instanceof UploadedFile) {
+ $files[$key] = $this->createUploadedFile($value);
+ } else {
+ $files[$key] = $this->getFiles($value);
+ }
+ }
+
+ return $files;
+ }
+
+ /**
+ * Creates a PSR-7 UploadedFile instance from a Symfony one.
+ *
+ * @param UploadedFile $symfonyUploadedFile
+ *
+ * @return UploadedFileInterface
+ */
+ private function createUploadedFile(UploadedFile $symfonyUploadedFile)
+ {
+ return new DiactorosUploadedFile(
+ $symfonyUploadedFile->getRealPath(),
+ (int) $symfonyUploadedFile->getSize(),
+ $symfonyUploadedFile->getError(),
+ $symfonyUploadedFile->getClientOriginalName(),
+ $symfonyUploadedFile->getClientMimeType()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createResponse(Response $symfonyResponse)
+ {
+ if ($symfonyResponse instanceof BinaryFileResponse) {
+ $stream = new DiactorosStream($symfonyResponse->getFile()->getPathname(), 'r');
+ } else {
+ $stream = new DiactorosStream('php://temp', 'wb+');
+ if ($symfonyResponse instanceof StreamedResponse) {
+ ob_start(function ($buffer) use ($stream) {
+ $stream->write($buffer);
+
+ return '';
+ });
+
+ $symfonyResponse->sendContent();
+ ob_end_clean();
+ } else {
+ $stream->write($symfonyResponse->getContent());
+ }
+ }
+
+ $headers = $symfonyResponse->headers->all();
+ if (!isset($headers['Set-Cookie']) && !isset($headers['set-cookie'])) {
+ $cookies = $symfonyResponse->headers->getCookies();
+ if (!empty($cookies)) {
+ $headers['Set-Cookie'] = [];
+ foreach ($cookies as $cookie) {
+ $headers['Set-Cookie'][] = $cookie->__toString();
+ }
+ }
+ }
+
+ $response = new DiactorosResponse(
+ $stream,
+ $symfonyResponse->getStatusCode(),
+ $headers
+ );
+
+ $protocolVersion = $symfonyResponse->getProtocolVersion();
+ if ('1.1' !== $protocolVersion) {
+ $response = $response->withProtocolVersion($protocolVersion);
+ }
+
+ return $response;
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php b/vendor/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php
new file mode 100755
index 0000000..4be0aff
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php
@@ -0,0 +1,280 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Factory;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\UploadedFileInterface;
+use Psr\Http\Message\UriInterface;
+use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+
+/**
+ * {@inheritdoc}
+ *
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+class HttpFoundationFactory implements HttpFoundationFactoryInterface
+{
+ /**
+ * @var int The maximum output buffering size for each iteration when sending the response
+ */
+ private $responseBufferMaxLength;
+
+ public function __construct(int $responseBufferMaxLength = 16372)
+ {
+ $this->responseBufferMaxLength = $responseBufferMaxLength;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createRequest(ServerRequestInterface $psrRequest)
+ {
+ $server = [];
+ $uri = $psrRequest->getUri();
+
+ if ($uri instanceof UriInterface) {
+ $server['SERVER_NAME'] = $uri->getHost();
+ $server['SERVER_PORT'] = $uri->getPort();
+ $server['REQUEST_URI'] = $uri->getPath();
+ $server['QUERY_STRING'] = $uri->getQuery();
+ }
+
+ $server['REQUEST_METHOD'] = $psrRequest->getMethod();
+
+ $server = array_replace($server, $psrRequest->getServerParams());
+
+ $parsedBody = $psrRequest->getParsedBody();
+ $parsedBody = \is_array($parsedBody) ? $parsedBody : [];
+
+ $request = new Request(
+ $psrRequest->getQueryParams(),
+ $parsedBody,
+ $psrRequest->getAttributes(),
+ $psrRequest->getCookieParams(),
+ $this->getFiles($psrRequest->getUploadedFiles()),
+ $server,
+ $psrRequest->getBody()->__toString()
+ );
+ $request->headers->replace($psrRequest->getHeaders());
+
+ return $request;
+ }
+
+ /**
+ * Converts to the input array to $_FILES structure.
+ *
+ * @param array $uploadedFiles
+ *
+ * @return array
+ */
+ private function getFiles(array $uploadedFiles)
+ {
+ $files = [];
+
+ foreach ($uploadedFiles as $key => $value) {
+ if ($value instanceof UploadedFileInterface) {
+ $files[$key] = $this->createUploadedFile($value);
+ } else {
+ $files[$key] = $this->getFiles($value);
+ }
+ }
+
+ return $files;
+ }
+
+ /**
+ * Creates Symfony UploadedFile instance from PSR-7 ones.
+ *
+ * @param UploadedFileInterface $psrUploadedFile
+ *
+ * @return UploadedFile
+ */
+ private function createUploadedFile(UploadedFileInterface $psrUploadedFile)
+ {
+ $temporaryPath = '';
+ $clientFileName = '';
+ if (UPLOAD_ERR_NO_FILE !== $psrUploadedFile->getError()) {
+ $temporaryPath = $this->getTemporaryPath();
+ $psrUploadedFile->moveTo($temporaryPath);
+
+ $clientFileName = $psrUploadedFile->getClientFilename();
+ }
+
+ if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) {
+ // Symfony 4.1+
+ return new UploadedFile(
+ $temporaryPath,
+ null === $clientFileName ? '' : $clientFileName,
+ $psrUploadedFile->getClientMediaType(),
+ $psrUploadedFile->getError(),
+ true
+ );
+ }
+
+ return new UploadedFile(
+ $temporaryPath,
+ null === $clientFileName ? '' : $clientFileName,
+ $psrUploadedFile->getClientMediaType(),
+ $psrUploadedFile->getSize(),
+ $psrUploadedFile->getError(),
+ true
+ );
+ }
+
+ /**
+ * Gets a temporary file path.
+ *
+ * @return string
+ */
+ protected function getTemporaryPath()
+ {
+ return tempnam(sys_get_temp_dir(), uniqid('symfony', true));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createResponse(ResponseInterface $psrResponse, bool $streamed = false)
+ {
+ $cookies = $psrResponse->getHeader('Set-Cookie');
+ $psrResponse = $psrResponse->withoutHeader('Set-Cookie');
+
+ if ($streamed) {
+ $response = new StreamedResponse(
+ $this->createStreamedResponseCallback($psrResponse->getBody()),
+ $psrResponse->getStatusCode(),
+ $psrResponse->getHeaders()
+ );
+ } else {
+ $response = new Response(
+ $psrResponse->getBody()->__toString(),
+ $psrResponse->getStatusCode(),
+ $psrResponse->getHeaders()
+ );
+ }
+
+ $response->setProtocolVersion($psrResponse->getProtocolVersion());
+
+ foreach ($cookies as $cookie) {
+ $response->headers->setCookie($this->createCookie($cookie));
+ }
+
+ return $response;
+ }
+
+ /**
+ * Creates a Cookie instance from a cookie string.
+ *
+ * Some snippets have been taken from the Guzzle project: https://github.com/guzzle/guzzle/blob/5.3/src/Cookie/SetCookie.php#L34
+ *
+ * @param string $cookie
+ *
+ * @return Cookie
+ *
+ * @throws \InvalidArgumentException
+ */
+ private function createCookie($cookie)
+ {
+ foreach (explode(';', $cookie) as $part) {
+ $part = trim($part);
+
+ $data = explode('=', $part, 2);
+ $name = $data[0];
+ $value = isset($data[1]) ? trim($data[1], " \n\r\t\0\x0B\"") : null;
+
+ if (!isset($cookieName)) {
+ $cookieName = $name;
+ $cookieValue = $value;
+
+ continue;
+ }
+
+ if ('expires' === strtolower($name) && null !== $value) {
+ $cookieExpire = new \DateTime($value);
+
+ continue;
+ }
+
+ if ('path' === strtolower($name) && null !== $value) {
+ $cookiePath = $value;
+
+ continue;
+ }
+
+ if ('domain' === strtolower($name) && null !== $value) {
+ $cookieDomain = $value;
+
+ continue;
+ }
+
+ if ('secure' === strtolower($name)) {
+ $cookieSecure = true;
+
+ continue;
+ }
+
+ if ('httponly' === strtolower($name)) {
+ $cookieHttpOnly = true;
+
+ continue;
+ }
+
+ if ('samesite' === strtolower($name) && null !== $value) {
+ $samesite = $value;
+
+ continue;
+ }
+ }
+
+ if (!isset($cookieName)) {
+ throw new \InvalidArgumentException('The value of the Set-Cookie header is malformed.');
+ }
+
+ return new Cookie(
+ $cookieName,
+ $cookieValue,
+ isset($cookieExpire) ? $cookieExpire : 0,
+ isset($cookiePath) ? $cookiePath : '/',
+ isset($cookieDomain) ? $cookieDomain : null,
+ isset($cookieSecure),
+ isset($cookieHttpOnly),
+ false,
+ isset($samesite) ? $samesite : null
+ );
+ }
+
+ private function createStreamedResponseCallback(StreamInterface $body): callable
+ {
+ return function () use ($body) {
+ if ($body->isSeekable()) {
+ $body->rewind();
+ }
+
+ if (!$body->isReadable()) {
+ echo $body;
+
+ return;
+ }
+
+ while (!$body->eof()) {
+ echo $body->read($this->responseBufferMaxLength);
+ }
+ };
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php b/vendor/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php
new file mode 100755
index 0000000..a37e1e9
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php
@@ -0,0 +1,172 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Factory;
+
+use Psr\Http\Message\ResponseFactoryInterface;
+use Psr\Http\Message\ServerRequestFactoryInterface;
+use Psr\Http\Message\StreamFactoryInterface;
+use Psr\Http\Message\UploadedFileFactoryInterface;
+use Psr\Http\Message\UploadedFileInterface;
+use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+
+/**
+ * Builds Psr\HttpMessage instances using a PSR-17 implementation.
+ *
+ * @author Antonio J. García Lagar <aj@garcialagar.es>
+ */
+class PsrHttpFactory implements HttpMessageFactoryInterface
+{
+ private $serverRequestFactory;
+ private $streamFactory;
+ private $uploadedFileFactory;
+ private $responseFactory;
+
+ public function __construct(ServerRequestFactoryInterface $serverRequestFactory, StreamFactoryInterface $streamFactory, UploadedFileFactoryInterface $uploadedFileFactory, ResponseFactoryInterface $responseFactory)
+ {
+ $this->serverRequestFactory = $serverRequestFactory;
+ $this->streamFactory = $streamFactory;
+ $this->uploadedFileFactory = $uploadedFileFactory;
+ $this->responseFactory = $responseFactory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createRequest(Request $symfonyRequest)
+ {
+ $request = $this->serverRequestFactory->createServerRequest(
+ $symfonyRequest->getMethod(),
+ $symfonyRequest->getUri(),
+ $symfonyRequest->server->all()
+ );
+
+ foreach ($symfonyRequest->headers->all() as $name => $value) {
+ $request = $request->withHeader($name, $value);
+ }
+
+ $body = $this->streamFactory->createStreamFromResource($symfonyRequest->getContent(true));
+
+ $request = $request
+ ->withBody($body)
+ ->withUploadedFiles($this->getFiles($symfonyRequest->files->all()))
+ ->withCookieParams($symfonyRequest->cookies->all())
+ ->withQueryParams($symfonyRequest->query->all())
+ ->withParsedBody($symfonyRequest->request->all())
+ ;
+
+ foreach ($symfonyRequest->attributes->all() as $key => $value) {
+ $request = $request->withAttribute($key, $value);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Converts Symfony uploaded files array to the PSR one.
+ *
+ * @param array $uploadedFiles
+ *
+ * @return array
+ */
+ private function getFiles(array $uploadedFiles)
+ {
+ $files = [];
+
+ foreach ($uploadedFiles as $key => $value) {
+ if (null === $value) {
+ $files[$key] = $this->uploadedFileFactory->createUploadedFile($this->streamFactory->createStream(), 0, UPLOAD_ERR_NO_FILE);
+ continue;
+ }
+ if ($value instanceof UploadedFile) {
+ $files[$key] = $this->createUploadedFile($value);
+ } else {
+ $files[$key] = $this->getFiles($value);
+ }
+ }
+
+ return $files;
+ }
+
+ /**
+ * Creates a PSR-7 UploadedFile instance from a Symfony one.
+ *
+ * @param UploadedFile $symfonyUploadedFile
+ *
+ * @return UploadedFileInterface
+ */
+ private function createUploadedFile(UploadedFile $symfonyUploadedFile)
+ {
+ return $this->uploadedFileFactory->createUploadedFile(
+ $this->streamFactory->createStreamFromFile(
+ $symfonyUploadedFile->getRealPath()
+ ),
+ (int) $symfonyUploadedFile->getSize(),
+ $symfonyUploadedFile->getError(),
+ $symfonyUploadedFile->getClientOriginalName(),
+ $symfonyUploadedFile->getClientMimeType()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createResponse(Response $symfonyResponse)
+ {
+ $response = $this->responseFactory->createResponse($symfonyResponse->getStatusCode(), Response::$statusTexts[$symfonyResponse->getStatusCode()] ?? '');
+
+ if ($symfonyResponse instanceof BinaryFileResponse) {
+ $stream = $this->streamFactory->createStreamFromFile(
+ $symfonyResponse->getFile()->getPathname()
+ );
+ } else {
+ $stream = $this->streamFactory->createStreamFromFile('php://temp', 'wb+');
+ if ($symfonyResponse instanceof StreamedResponse) {
+ ob_start(function ($buffer) use ($stream) {
+ $stream->write($buffer);
+
+ return '';
+ });
+
+ $symfonyResponse->sendContent();
+ ob_end_clean();
+ } else {
+ $stream->write($symfonyResponse->getContent());
+ }
+ }
+
+ $response = $response->withBody($stream);
+
+ $headers = $symfonyResponse->headers->all();
+ $cookies = $symfonyResponse->headers->getCookies();
+ if (!empty($cookies)) {
+ $headers['Set-Cookie'] = [];
+
+ foreach ($cookies as $cookie) {
+ $headers['Set-Cookie'][] = $cookie->__toString();
+ }
+ }
+
+ foreach ($headers as $name => $value) {
+ $response = $response->withHeader($name, $value);
+ }
+
+ $protocolVersion = $symfonyResponse->getProtocolVersion();
+ $response = $response->withProtocolVersion($protocolVersion);
+
+ return $response;
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php b/vendor/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php
new file mode 100755
index 0000000..79ec40f
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Creates Symfony Request and Response instances from PSR-7 ones.
+ *
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+interface HttpFoundationFactoryInterface
+{
+ /**
+ * Creates a Symfony Request instance from a PSR-7 one.
+ *
+ * @param ServerRequestInterface $psrRequest
+ *
+ * @return Request
+ */
+ public function createRequest(ServerRequestInterface $psrRequest);
+
+ /**
+ * Creates a Symfony Response instance from a PSR-7 one.
+ *
+ * @param ResponseInterface $psrResponse
+ *
+ * @return Response
+ */
+ public function createResponse(ResponseInterface $psrResponse);
+}
diff --git a/vendor/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php b/vendor/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php
new file mode 100755
index 0000000..b7960e8
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Creates PSR HTTP Request and Response instances from Symfony ones.
+ *
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+interface HttpMessageFactoryInterface
+{
+ /**
+ * Creates a PSR-7 Request instance from a Symfony one.
+ *
+ * @param Request $symfonyRequest
+ *
+ * @return ServerRequestInterface
+ */
+ public function createRequest(Request $symfonyRequest);
+
+ /**
+ * Creates a PSR-7 Response instance from a Symfony one.
+ *
+ * @param Response $symfonyResponse
+ *
+ * @return ResponseInterface
+ */
+ public function createResponse(Response $symfonyResponse);
+}
diff --git a/vendor/symfony/psr-http-message-bridge/LICENSE b/vendor/symfony/psr-http-message-bridge/LICENSE
new file mode 100755
index 0000000..12a7453
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2016 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/psr-http-message-bridge/README.md b/vendor/symfony/psr-http-message-bridge/README.md
new file mode 100755
index 0000000..87fbd43
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/README.md
@@ -0,0 +1,20 @@
+PSR-7 Bridge
+============
+
+Provides integration for PSR7.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/psr7.html)
+ * [SensioFrameworkExtraBundle](https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html#psr-7-support)
+
+Running the tests
+-----------------
+
+If you want to run the unit tests, install dev dependencies before
+running PHPUnit:
+
+ $ cd path/to/Symfony/Bridge/PsrHttpMessage/
+ $ composer.phar install
+ $ phpunit
diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Factory/AbstractHttpMessageFactoryTest.php b/vendor/symfony/psr-http-message-bridge/Tests/Factory/AbstractHttpMessageFactoryTest.php
new file mode 100755
index 0000000..4b8bb97
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Tests/Factory/AbstractHttpMessageFactoryTest.php
@@ -0,0 +1,229 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+
+/**
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ * @author Antonio J. García Lagar <aj@garcialagar.es>
+ */
+abstract class AbstractHttpMessageFactoryTest extends TestCase
+{
+ private $factory;
+ private $tmpDir;
+
+ /**
+ * @return HttpMessageFactoryInterface
+ */
+ abstract protected function buildHttpMessageFactory();
+
+ public function setup()
+ {
+ $this->factory = $this->buildHttpMessageFactory();
+ $this->tmpDir = sys_get_temp_dir();
+ }
+
+ public function testCreateRequest()
+ {
+ $stdClass = new \stdClass();
+ $request = new Request(
+ [
+ 'bar' => ['baz' => '42'],
+ 'foo' => '1',
+ ],
+ [
+ 'twitter' => [
+ '@dunglas' => 'Kévin Dunglas',
+ '@coopTilleuls' => 'Les-Tilleuls.coop',
+ ],
+ 'baz' => '2',
+ ],
+ [
+ 'a1' => $stdClass,
+ 'a2' => ['foo' => 'bar'],
+ ],
+ [
+ 'c1' => 'foo',
+ 'c2' => ['c3' => 'bar'],
+ ],
+ [
+ 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', UPLOAD_ERR_OK),
+ 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', UPLOAD_ERR_OK)],
+ ],
+ [
+ 'REQUEST_METHOD' => 'POST',
+ 'HTTP_HOST' => 'dunglas.fr',
+ 'HTTP_X_SYMFONY' => '2.8',
+ 'REQUEST_URI' => '/testCreateRequest?bar[baz]=42&foo=1',
+ 'QUERY_STRING' => 'bar[baz]=42&foo=1',
+ ],
+ 'Content'
+ );
+
+ $psrRequest = $this->factory->createRequest($request);
+
+ $this->assertEquals('Content', $psrRequest->getBody()->__toString());
+
+ $queryParams = $psrRequest->getQueryParams();
+ $this->assertEquals('1', $queryParams['foo']);
+ $this->assertEquals('42', $queryParams['bar']['baz']);
+
+ $requestTarget = $psrRequest->getRequestTarget();
+ $this->assertEquals('/testCreateRequest?bar[baz]=42&foo=1', urldecode($requestTarget));
+
+ $parsedBody = $psrRequest->getParsedBody();
+ $this->assertEquals('Kévin Dunglas', $parsedBody['twitter']['@dunglas']);
+ $this->assertEquals('Les-Tilleuls.coop', $parsedBody['twitter']['@coopTilleuls']);
+ $this->assertEquals('2', $parsedBody['baz']);
+
+ $attributes = $psrRequest->getAttributes();
+ $this->assertEquals($stdClass, $attributes['a1']);
+ $this->assertEquals('bar', $attributes['a2']['foo']);
+
+ $cookies = $psrRequest->getCookieParams();
+ $this->assertEquals('foo', $cookies['c1']);
+ $this->assertEquals('bar', $cookies['c2']['c3']);
+
+ $uploadedFiles = $psrRequest->getUploadedFiles();
+ $this->assertEquals('F1', $uploadedFiles['f1']->getStream()->__toString());
+ $this->assertEquals('f1.txt', $uploadedFiles['f1']->getClientFilename());
+ $this->assertEquals('text/plain', $uploadedFiles['f1']->getClientMediaType());
+ $this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['f1']->getError());
+
+ $this->assertEquals('F2', $uploadedFiles['foo']['f2']->getStream()->__toString());
+ $this->assertEquals('f2.txt', $uploadedFiles['foo']['f2']->getClientFilename());
+ $this->assertEquals('text/plain', $uploadedFiles['foo']['f2']->getClientMediaType());
+ $this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['foo']['f2']->getError());
+
+ $serverParams = $psrRequest->getServerParams();
+ $this->assertEquals('POST', $serverParams['REQUEST_METHOD']);
+ $this->assertEquals('2.8', $serverParams['HTTP_X_SYMFONY']);
+ $this->assertEquals('POST', $psrRequest->getMethod());
+ $this->assertEquals(['2.8'], $psrRequest->getHeader('X-Symfony'));
+ }
+
+ public function testGetContentCanBeCalledAfterRequestCreation()
+ {
+ $header = ['HTTP_HOST' => 'dunglas.fr'];
+ $request = new Request([], [], [], [], [], $header, 'Content');
+
+ $psrRequest = $this->factory->createRequest($request);
+
+ $this->assertEquals('Content', $psrRequest->getBody()->__toString());
+ $this->assertEquals('Content', $request->getContent());
+ }
+
+ private function createUploadedFile($content, $originalName, $mimeType, $error)
+ {
+ $path = tempnam($this->tmpDir, uniqid());
+ file_put_contents($path, $content);
+
+ if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) {
+ // Symfony 4.1+
+ return new UploadedFile($path, $originalName, $mimeType, $error, true);
+ }
+
+ return new UploadedFile($path, $originalName, $mimeType, filesize($path), $error, true);
+ }
+
+ public function testCreateResponse()
+ {
+ $response = new Response(
+ 'Response content.',
+ 202,
+ ['X-Symfony' => ['3.4']]
+ );
+ $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'), '/', null, false, true, false, 'lax'));
+
+ $psrResponse = $this->factory->createResponse($response);
+ $this->assertEquals('Response content.', $psrResponse->getBody()->__toString());
+ $this->assertEquals(202, $psrResponse->getStatusCode());
+ $this->assertEquals(['3.4'], $psrResponse->getHeader('X-Symfony'));
+
+ $cookieHeader = $psrResponse->getHeader('Set-Cookie');
+ $this->assertInternalType('array', $cookieHeader);
+ $this->assertCount(1, $cookieHeader);
+ $this->assertRegExp('{city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}i', $cookieHeader[0]);
+ }
+
+ public function testCreateResponseFromStreamed()
+ {
+ $response = new StreamedResponse(function () {
+ echo "Line 1\n";
+ flush();
+
+ echo "Line 2\n";
+ flush();
+ });
+
+ $psrResponse = $this->factory->createResponse($response);
+
+ $this->assertEquals("Line 1\nLine 2\n", $psrResponse->getBody()->__toString());
+ }
+
+ public function testCreateResponseFromBinaryFile()
+ {
+ $path = tempnam($this->tmpDir, uniqid());
+ file_put_contents($path, 'Binary');
+
+ $response = new BinaryFileResponse($path);
+
+ $psrResponse = $this->factory->createResponse($response);
+
+ $this->assertEquals('Binary', $psrResponse->getBody()->__toString());
+ }
+
+ public function testUploadErrNoFile()
+ {
+ if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) {
+ // Symfony 4.1+
+ $file = new UploadedFile('', '', null, UPLOAD_ERR_NO_FILE, true);
+ } else {
+ $file = new UploadedFile('', '', null, 0, UPLOAD_ERR_NO_FILE, true);
+ }
+ $this->assertEquals(0, $file->getSize());
+ $this->assertEquals(UPLOAD_ERR_NO_FILE, $file->getError());
+ $this->assertFalse($file->getSize(), 'SplFile::getSize() returns false on error');
+
+ $request = new Request(
+ [],
+ [],
+ [],
+ [],
+ [
+ 'f1' => $file,
+ 'f2' => ['name' => null, 'type' => null, 'tmp_name' => null, 'error' => UPLOAD_ERR_NO_FILE, 'size' => 0],
+ ],
+ [
+ 'REQUEST_METHOD' => 'POST',
+ 'HTTP_HOST' => 'dunglas.fr',
+ 'HTTP_X_SYMFONY' => '2.8',
+ ],
+ 'Content'
+ );
+
+ $psrRequest = $this->factory->createRequest($request);
+
+ $uploadedFiles = $psrRequest->getUploadedFiles();
+
+ $this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f1']->getError());
+ $this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f2']->getError());
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Factory/DiactorosFactoryTest.php b/vendor/symfony/psr-http-message-bridge/Tests/Factory/DiactorosFactoryTest.php
new file mode 100755
index 0000000..5ebe7a9
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Tests/Factory/DiactorosFactoryTest.php
@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory;
+
+use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
+
+/**
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ * @author Antonio J. García Lagar <aj@garcialagar.es>
+ *
+ * @group legacy
+ */
+class DiactorosFactoryTest extends AbstractHttpMessageFactoryTest
+{
+ protected function buildHttpMessageFactory()
+ {
+ if (!class_exists('Zend\Diactoros\ServerRequestFactory')) {
+ $this->markTestSkipped('Zend Diactoros is not installed.');
+ }
+
+ return new DiactorosFactory();
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Factory/HttpFoundationFactoryTest.php b/vendor/symfony/psr-http-message-bridge/Tests/Factory/HttpFoundationFactoryTest.php
new file mode 100755
index 0000000..27b84c0
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Tests/Factory/HttpFoundationFactoryTest.php
@@ -0,0 +1,250 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory;
+
+use PHPUnit\Framework\TestCase;
+use Psr\Http\Message\UploadedFileInterface;
+use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
+use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Response;
+use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\ServerRequest;
+use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Stream;
+use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\UploadedFile;
+use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Uri;
+use Symfony\Component\HttpFoundation\Cookie;
+
+/**
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+class HttpFoundationFactoryTest extends TestCase
+{
+ /** @var HttpFoundationFactory */
+ private $factory;
+
+ /** @var string */
+ private $tmpDir;
+
+ public function setup()
+ {
+ $this->factory = new HttpFoundationFactory();
+ $this->tmpDir = sys_get_temp_dir();
+ }
+
+ public function testCreateRequest()
+ {
+ $stdClass = new \stdClass();
+ $serverRequest = new ServerRequest(
+ '1.1',
+ [
+ 'X-Dunglas-API-Platform' => '1.0',
+ 'X-data' => ['a', 'b'],
+ ],
+ new Stream('The body'),
+ '/about/kevin',
+ 'GET',
+ 'http://les-tilleuls.coop/about/kevin',
+ ['country' => 'France'],
+ ['city' => 'Lille'],
+ ['url' => 'http://les-tilleuls.coop'],
+ [
+ 'doc1' => $this->createUploadedFile('Doc 1', UPLOAD_ERR_OK, 'doc1.txt', 'text/plain'),
+ 'nested' => [
+ 'docs' => [
+ $this->createUploadedFile('Doc 2', UPLOAD_ERR_OK, 'doc2.txt', 'text/plain'),
+ $this->createUploadedFile('Doc 3', UPLOAD_ERR_OK, 'doc3.txt', 'text/plain'),
+ ],
+ ],
+ ],
+ ['url' => 'http://dunglas.fr'],
+ ['custom' => $stdClass]
+ );
+
+ $symfonyRequest = $this->factory->createRequest($serverRequest);
+ $files = $symfonyRequest->files->all();
+
+ $this->assertEquals('http://les-tilleuls.coop', $symfonyRequest->query->get('url'));
+ $this->assertEquals('doc1.txt', $files['doc1']->getClientOriginalName());
+ $this->assertEquals('doc2.txt', $files['nested']['docs'][0]->getClientOriginalName());
+ $this->assertEquals('doc3.txt', $files['nested']['docs'][1]->getClientOriginalName());
+ $this->assertEquals('http://dunglas.fr', $symfonyRequest->request->get('url'));
+ $this->assertEquals($stdClass, $symfonyRequest->attributes->get('custom'));
+ $this->assertEquals('Lille', $symfonyRequest->cookies->get('city'));
+ $this->assertEquals('France', $symfonyRequest->server->get('country'));
+ $this->assertEquals('The body', $symfonyRequest->getContent());
+ $this->assertEquals('1.0', $symfonyRequest->headers->get('X-Dunglas-API-Platform'));
+ $this->assertEquals(['a', 'b'], $symfonyRequest->headers->get('X-data', null, false));
+ }
+
+ public function testCreateRequestWithNullParsedBody()
+ {
+ $serverRequest = new ServerRequest(
+ '1.1',
+ [],
+ new Stream(),
+ '/',
+ 'GET',
+ null,
+ [],
+ [],
+ [],
+ [],
+ null,
+ []
+ );
+
+ $this->assertCount(0, $this->factory->createRequest($serverRequest)->request);
+ }
+
+ public function testCreateRequestWithObjectParsedBody()
+ {
+ $serverRequest = new ServerRequest(
+ '1.1',
+ [],
+ new Stream(),
+ '/',
+ 'GET',
+ null,
+ [],
+ [],
+ [],
+ [],
+ new \stdClass(),
+ []
+ );
+
+ $this->assertCount(0, $this->factory->createRequest($serverRequest)->request);
+ }
+
+ public function testCreateRequestWithUri()
+ {
+ $serverRequest = new ServerRequest(
+ '1.1',
+ [],
+ new Stream(),
+ '/',
+ 'GET',
+ new Uri('http://les-tilleuls.coop/about/kevin'),
+ [],
+ [],
+ [],
+ [],
+ null,
+ []
+ );
+
+ $this->assertEquals('/about/kevin', $this->factory->createRequest($serverRequest)->getPathInfo());
+ }
+
+ public function testCreateUploadedFile()
+ {
+ $uploadedFile = $this->createUploadedFile('An uploaded file.', UPLOAD_ERR_OK, 'myfile.txt', 'text/plain');
+ $symfonyUploadedFile = $this->callCreateUploadedFile($uploadedFile);
+ $size = $symfonyUploadedFile->getSize();
+
+ $uniqid = uniqid();
+ $symfonyUploadedFile->move($this->tmpDir, $uniqid);
+
+ $this->assertEquals($uploadedFile->getSize(), $size);
+ $this->assertEquals(UPLOAD_ERR_OK, $symfonyUploadedFile->getError());
+ $this->assertEquals('myfile.txt', $symfonyUploadedFile->getClientOriginalName());
+ $this->assertEquals('txt', $symfonyUploadedFile->getClientOriginalExtension());
+ $this->assertEquals('text/plain', $symfonyUploadedFile->getClientMimeType());
+ $this->assertEquals('An uploaded file.', file_get_contents($this->tmpDir.'/'.$uniqid));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\HttpFoundation\File\Exception\FileException
+ * @expectedExceptionMessage The file "e" could not be written on disk.
+ */
+ public function testCreateUploadedFileWithError()
+ {
+ $uploadedFile = $this->createUploadedFile('Error.', UPLOAD_ERR_CANT_WRITE, 'e', 'text/plain');
+ $symfonyUploadedFile = $this->callCreateUploadedFile($uploadedFile);
+
+ $this->assertEquals(UPLOAD_ERR_CANT_WRITE, $symfonyUploadedFile->getError());
+
+ $symfonyUploadedFile->move($this->tmpDir, 'shouldFail.txt');
+ }
+
+ private function createUploadedFile($content, $error, $clientFileName, $clientMediaType)
+ {
+ $filePath = tempnam($this->tmpDir, uniqid());
+ file_put_contents($filePath, $content);
+
+ return new UploadedFile($filePath, filesize($filePath), $error, $clientFileName, $clientMediaType);
+ }
+
+ private function callCreateUploadedFile(UploadedFileInterface $uploadedFile)
+ {
+ $reflection = new \ReflectionClass($this->factory);
+ $createUploadedFile = $reflection->getMethod('createUploadedFile');
+ $createUploadedFile->setAccessible(true);
+
+ return $createUploadedFile->invokeArgs($this->factory, [$uploadedFile]);
+ }
+
+ public function testCreateResponse()
+ {
+ $response = new Response(
+ '1.0',
+ [
+ 'X-Symfony' => ['2.8'],
+ 'Set-Cookie' => [
+ 'theme=light',
+ 'test',
+ 'ABC=AeD; Domain=dunglas.fr; Path=/kevin; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly; SameSite=Strict',
+ ],
+ ],
+ new Stream('The response body'),
+ 200
+ );
+
+ $symfonyResponse = $this->factory->createResponse($response);
+
+ $this->assertEquals('1.0', $symfonyResponse->getProtocolVersion());
+ $this->assertEquals('2.8', $symfonyResponse->headers->get('X-Symfony'));
+
+ $cookies = $symfonyResponse->headers->getCookies();
+ $this->assertEquals('theme', $cookies[0]->getName());
+ $this->assertEquals('light', $cookies[0]->getValue());
+ $this->assertEquals(0, $cookies[0]->getExpiresTime());
+ $this->assertNull($cookies[0]->getDomain());
+ $this->assertEquals('/', $cookies[0]->getPath());
+ $this->assertFalse($cookies[0]->isSecure());
+ $this->assertFalse($cookies[0]->isHttpOnly());
+
+ $this->assertEquals('test', $cookies[1]->getName());
+ $this->assertNull($cookies[1]->getValue());
+
+ $this->assertEquals('ABC', $cookies[2]->getName());
+ $this->assertEquals('AeD', $cookies[2]->getValue());
+ $this->assertEquals(strtotime('Wed, 13 Jan 2021 22:23:01 GMT'), $cookies[2]->getExpiresTime());
+ $this->assertEquals('dunglas.fr', $cookies[2]->getDomain());
+ $this->assertEquals('/kevin', $cookies[2]->getPath());
+ $this->assertTrue($cookies[2]->isSecure());
+ $this->assertTrue($cookies[2]->isHttpOnly());
+ if (\defined('Symfony\Component\HttpFoundation\Cookie::SAMESITE_STRICT')) {
+ $this->assertEquals(Cookie::SAMESITE_STRICT, $cookies[2]->getSameSite());
+ }
+
+ $this->assertEquals('The response body', $symfonyResponse->getContent());
+ $this->assertEquals(200, $symfonyResponse->getStatusCode());
+
+ $symfonyResponse = $this->factory->createResponse($response, true);
+
+ ob_start();
+ $symfonyResponse->sendContent();
+ $sentContent = ob_get_clean();
+
+ $this->assertEquals('The response body', $sentContent);
+ $this->assertEquals(200, $symfonyResponse->getStatusCode());
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Factory/PsrHttpFactoryTest.php b/vendor/symfony/psr-http-message-bridge/Tests/Factory/PsrHttpFactoryTest.php
new file mode 100755
index 0000000..da12ea7
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Tests/Factory/PsrHttpFactoryTest.php
@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory;
+
+use Nyholm\Psr7\Factory\Psr17Factory;
+use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
+
+/**
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ * @author Antonio J. García Lagar <aj@garcialagar.es>
+ */
+class PsrHttpFactoryTest extends AbstractHttpMessageFactoryTest
+{
+ protected function buildHttpMessageFactory()
+ {
+ $factory = new Psr17Factory();
+
+ return new PsrHttpFactory($factory, $factory, $factory, $factory);
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Message.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Message.php
new file mode 100755
index 0000000..0cda6fc
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Message.php
@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures;
+
+use Psr\Http\Message\MessageInterface;
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Message.
+ *
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+class Message implements MessageInterface
+{
+ private $version = '1.1';
+ private $headers = [];
+ private $body;
+
+ public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null)
+ {
+ $this->version = $version;
+ $this->headers = $headers;
+ $this->body = null === $body ? new Stream() : $body;
+ }
+
+ public function getProtocolVersion()
+ {
+ return $this->version;
+ }
+
+ public function withProtocolVersion($version)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ public function hasHeader($name)
+ {
+ return isset($this->headers[$name]);
+ }
+
+ public function getHeader($name)
+ {
+ return $this->hasHeader($name) ? $this->headers[$name] : [];
+ }
+
+ public function getHeaderLine($name)
+ {
+ return $this->hasHeader($name) ? implode(',', $this->headers[$name]) : '';
+ }
+
+ public function withHeader($name, $value)
+ {
+ $this->headers[$name] = (array) $value;
+
+ return $this;
+ }
+
+ public function withAddedHeader($name, $value)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function withoutHeader($name)
+ {
+ unset($this->headers[$name]);
+
+ return $this;
+ }
+
+ public function getBody()
+ {
+ return $this->body;
+ }
+
+ public function withBody(StreamInterface $body)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Response.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Response.php
new file mode 100755
index 0000000..a890792
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Response.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+class Response extends Message implements ResponseInterface
+{
+ private $statusCode;
+
+ public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null, $statusCode = 200)
+ {
+ parent::__construct($version, $headers, $body);
+
+ $this->statusCode = $statusCode;
+ }
+
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ public function withStatus($code, $reasonPhrase = '')
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function getReasonPhrase()
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/ServerRequest.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/ServerRequest.php
new file mode 100755
index 0000000..88ec984
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/ServerRequest.php
@@ -0,0 +1,141 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures;
+
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\UriInterface;
+
+/**
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+class ServerRequest extends Message implements ServerRequestInterface
+{
+ private $requestTarget;
+ private $method;
+ private $uri;
+ private $server;
+ private $cookies;
+ private $query;
+ private $uploadedFiles;
+ private $data;
+ private $attributes;
+
+ public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null, $requestTarget = '/', $method = 'GET', $uri = null, array $server = [], array $cookies = [], array $query = [], array $uploadedFiles = [], $data = null, array $attributes = [])
+ {
+ parent::__construct($version, $headers, $body);
+
+ $this->requestTarget = $requestTarget;
+ $this->method = $method;
+ $this->uri = $uri;
+ $this->server = $server;
+ $this->cookies = $cookies;
+ $this->query = $query;
+ $this->uploadedFiles = $uploadedFiles;
+ $this->data = $data;
+ $this->attributes = $attributes;
+ }
+
+ public function getRequestTarget()
+ {
+ return $this->requestTarget;
+ }
+
+ public function withRequestTarget($requestTarget)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ public function withMethod($method)
+ {
+ }
+
+ public function getUri()
+ {
+ return $this->uri;
+ }
+
+ public function withUri(UriInterface $uri, $preserveHost = false)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function getServerParams()
+ {
+ return $this->server;
+ }
+
+ public function getCookieParams()
+ {
+ return $this->cookies;
+ }
+
+ public function withCookieParams(array $cookies)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function getQueryParams()
+ {
+ return $this->query;
+ }
+
+ public function withQueryParams(array $query)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function getUploadedFiles()
+ {
+ return $this->uploadedFiles;
+ }
+
+ public function withUploadedFiles(array $uploadedFiles)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function getParsedBody()
+ {
+ return $this->data;
+ }
+
+ public function withParsedBody($data)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ public function getAttribute($name, $default = null)
+ {
+ return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
+ }
+
+ public function withAttribute($name, $value)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function withoutAttribute($name)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Stream.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Stream.php
new file mode 100755
index 0000000..aa0ba24
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Stream.php
@@ -0,0 +1,99 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+class Stream implements StreamInterface
+{
+ private $stringContent;
+ private $eof = true;
+
+ public function __construct($stringContent = '')
+ {
+ $this->stringContent = $stringContent;
+ }
+
+ public function __toString()
+ {
+ return $this->stringContent;
+ }
+
+ public function close()
+ {
+ }
+
+ public function detach()
+ {
+ }
+
+ public function getSize()
+ {
+ }
+
+ public function tell()
+ {
+ return 0;
+ }
+
+ public function eof()
+ {
+ return $this->eof;
+ }
+
+ public function isSeekable()
+ {
+ return true;
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ }
+
+ public function rewind()
+ {
+ $this->eof = false;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function write($string)
+ {
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function read($length)
+ {
+ $this->eof = true;
+
+ return $this->stringContent;
+ }
+
+ public function getContents()
+ {
+ return $this->stringContent;
+ }
+
+ public function getMetadata($key = null)
+ {
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/UploadedFile.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/UploadedFile.php
new file mode 100755
index 0000000..4cfa98b
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/UploadedFile.php
@@ -0,0 +1,65 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures;
+
+use Psr\Http\Message\UploadedFileInterface;
+
+/**
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+class UploadedFile implements UploadedFileInterface
+{
+ private $filePath;
+ private $size;
+ private $error;
+ private $clientFileName;
+ private $clientMediaType;
+
+ public function __construct($filePath, $size = null, $error = UPLOAD_ERR_OK, $clientFileName = null, $clientMediaType = null)
+ {
+ $this->filePath = $filePath;
+ $this->size = $size;
+ $this->error = $error;
+ $this->clientFileName = $clientFileName;
+ $this->clientMediaType = $clientMediaType;
+ }
+
+ public function getStream()
+ {
+ throw new \RuntimeException('No stream is available.');
+ }
+
+ public function moveTo($targetPath)
+ {
+ rename($this->filePath, $targetPath);
+ }
+
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ public function getClientFilename()
+ {
+ return $this->clientFileName;
+ }
+
+ public function getClientMediaType()
+ {
+ return $this->clientMediaType;
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Uri.php b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Uri.php
new file mode 100755
index 0000000..f11c7e5
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Tests/Fixtures/Uri.php
@@ -0,0 +1,135 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures;
+
+use Psr\Http\Message\UriInterface;
+
+/**
+ * @author Rougin Royce Gutib <rougingutib@gmail.com>
+ */
+class Uri implements UriInterface
+{
+ private $scheme = '';
+ private $userInfo = '';
+ private $host = '';
+ private $port;
+ private $path = '';
+ private $query = '';
+ private $fragment = '';
+ private $uriString;
+
+ public function __construct($uri = '')
+ {
+ $parts = parse_url($uri);
+
+ $this->scheme = isset($parts['scheme']) ? $parts['scheme'] : '';
+ $this->userInfo = isset($parts['user']) ? $parts['user'] : '';
+ $this->host = isset($parts['host']) ? $parts['host'] : '';
+ $this->port = isset($parts['port']) ? $parts['port'] : null;
+ $this->path = isset($parts['path']) ? $parts['path'] : '';
+ $this->query = isset($parts['query']) ? $parts['query'] : '';
+ $this->fragment = isset($parts['fragment']) ? $parts['fragment'] : '';
+ $this->uriString = $uri;
+ }
+
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ public function getAuthority()
+ {
+ if (empty($this->host)) {
+ return '';
+ }
+
+ $authority = $this->host;
+
+ if (!empty($this->userInfo)) {
+ $authority = $this->userInfo.'@'.$authority;
+ }
+
+ $authority .= ':'.$this->port;
+
+ return $authority;
+ }
+
+ public function getUserInfo()
+ {
+ return $this->userInfo;
+ }
+
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ public function getFragment()
+ {
+ return $this->fragment;
+ }
+
+ public function withScheme($scheme)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function withUserInfo($user, $password = null)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function withHost($host)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function withPort($port)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function withPath($path)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function withQuery($query)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function withFragment($fragment)
+ {
+ throw new \BadMethodCallException('Not implemented.');
+ }
+
+ public function __toString()
+ {
+ return $this->uriString;
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/Tests/Functional/CovertTest.php b/vendor/symfony/psr-http-message-bridge/Tests/Functional/CovertTest.php
new file mode 100755
index 0000000..33ec34f
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/Tests/Functional/CovertTest.php
@@ -0,0 +1,239 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\Tests\Functional;
+
+use Nyholm\Psr7\Factory\Psr17Factory;
+use Nyholm\Psr7\Response as Psr7Response;
+use Nyholm\Psr7\ServerRequest as Psr7Request;
+use Nyholm\Psr7\Stream as Psr7Stream;
+use PHPUnit\Framework\TestCase;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
+use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
+use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
+use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Test to convert a request/response back and forth to make sure we do not loose data.
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+class CovertTest extends TestCase
+{
+ private $tmpDir;
+
+ public function setup()
+ {
+ if (!class_exists('Nyholm\Psr7\ServerRequest')) {
+ $this->markTestSkipped('nyholm/psr7 is not installed.');
+ }
+
+ $this->tmpDir = sys_get_temp_dir();
+ }
+
+ /**
+ * @dataProvider requestProvider
+ *
+ * @param Request|ServerRequestInterface $request
+ * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $firstFactory
+ * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $secondFactory
+ */
+ public function testConvertRequestMultipleTimes($request, $firstFactory, $secondFactory)
+ {
+ $temporaryRequest = $firstFactory->createRequest($request);
+ $finalRequest = $secondFactory->createRequest($temporaryRequest);
+
+ if ($finalRequest instanceof Request) {
+ $this->assertEquals($request->getBasePath(), $finalRequest->getBasePath());
+ $this->assertEquals($request->getBaseUrl(), $finalRequest->getBaseUrl());
+ $this->assertEquals($request->getContent(), $finalRequest->getContent());
+ $this->assertEquals($request->getEncodings(), $finalRequest->getEncodings());
+ $this->assertEquals($request->getETags(), $finalRequest->getETags());
+ $this->assertEquals($request->getHost(), $finalRequest->getHost());
+ $this->assertEquals($request->getHttpHost(), $finalRequest->getHttpHost());
+ $this->assertEquals($request->getMethod(), $finalRequest->getMethod());
+ $this->assertEquals($request->getPassword(), $finalRequest->getPassword());
+ $this->assertEquals($request->getPathInfo(), $finalRequest->getPathInfo());
+ $this->assertEquals($request->getPort(), $finalRequest->getPort());
+ $this->assertEquals($request->getProtocolVersion(), $finalRequest->getProtocolVersion());
+ $this->assertEquals($request->getQueryString(), $finalRequest->getQueryString());
+ $this->assertEquals($request->getRequestUri(), $finalRequest->getRequestUri());
+ $this->assertEquals($request->getScheme(), $finalRequest->getScheme());
+ $this->assertEquals($request->getSchemeAndHttpHost(), $finalRequest->getSchemeAndHttpHost());
+ $this->assertEquals($request->getScriptName(), $finalRequest->getScriptName());
+ $this->assertEquals($request->getUri(), $finalRequest->getUri());
+ $this->assertEquals($request->getUser(), $finalRequest->getUser());
+ $this->assertEquals($request->getUserInfo(), $finalRequest->getUserInfo());
+ } elseif ($finalRequest instanceof ServerRequestInterface) {
+ $strToLower = function ($arr) {
+ foreach ($arr as $key => $value) {
+ yield strtolower($key) => $value;
+ }
+ };
+ $this->assertEquals($request->getAttributes(), $finalRequest->getAttributes());
+ $this->assertEquals($request->getCookieParams(), $finalRequest->getCookieParams());
+ $this->assertEquals((array) $request->getParsedBody(), (array) $finalRequest->getParsedBody());
+ $this->assertEquals($request->getQueryParams(), $finalRequest->getQueryParams());
+ // PSR7 does not define a "withServerParams" so this is impossible to implement without knowing the PSR7 implementation.
+ //$this->assertEquals($request->getServerParams(), $finalRequest->getServerParams());
+ $this->assertEquals($request->getUploadedFiles(), $finalRequest->getUploadedFiles());
+ $this->assertEquals($request->getMethod(), $finalRequest->getMethod());
+ $this->assertEquals($request->getRequestTarget(), $finalRequest->getRequestTarget());
+ $this->assertEquals((string) $request->getUri(), (string) $finalRequest->getUri());
+ $this->assertEquals((string) $request->getBody(), (string) $finalRequest->getBody());
+ $this->assertEquals($strToLower($request->getHeaders()), $strToLower($finalRequest->getHeaders()));
+ $this->assertEquals($request->getProtocolVersion(), $finalRequest->getProtocolVersion());
+ } else {
+ $this->fail('$finalRequest must be an instance of PSR7 or a HTTPFoundation request');
+ }
+ }
+
+ public function requestProvider()
+ {
+ $sfRequest = new Request(
+ [
+ 'foo' => '1',
+ 'bar' => ['baz' => '42'],
+ ],
+ [
+ 'twitter' => [
+ '@dunglas' => 'Kévin Dunglas',
+ '@coopTilleuls' => 'Les-Tilleuls.coop',
+ ],
+ 'baz' => '2',
+ ],
+ [
+ 'a2' => ['foo' => 'bar'],
+ ],
+ [
+ 'c1' => 'foo',
+ 'c2' => ['c3' => 'bar'],
+ ],
+ [
+ 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', UPLOAD_ERR_OK),
+ 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', UPLOAD_ERR_OK)],
+ ],
+ [
+ 'REQUEST_METHOD' => 'POST',
+ 'HTTP_HOST' => 'dunglas.fr',
+ 'SERVER_NAME' => 'dunglas.fr',
+ 'SERVER_PORT' => null,
+ 'HTTP_X_SYMFONY' => '2.8',
+ 'REQUEST_URI' => '/testCreateRequest?bar[baz]=42&foo=1',
+ 'QUERY_STRING' => 'foo=1&bar[baz]=42',
+ ],
+ 'Content'
+ );
+
+ $psr7Request = (new Psr7Request('POST', 'http://tnyholm.se/foo/?bar=biz'))
+ ->withQueryParams(['bar' => 'biz']);
+
+ $nyholmFactory = new Psr17Factory();
+ $psr17Factory = new PsrHttpFactory($nyholmFactory, $nyholmFactory, $nyholmFactory, $nyholmFactory);
+ $symfonyFactory = new HttpFoundationFactory();
+
+ return [
+ [$sfRequest, $psr17Factory, $symfonyFactory],
+ [$psr7Request, $symfonyFactory, $psr17Factory],
+ ];
+ }
+
+ /**
+ * @dataProvider responseProvider
+ *
+ * @param Response|ResponseInterface $response
+ * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $firstFactory
+ * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $secondFactory
+ */
+ public function testConvertResponseMultipleTimes($response, $firstFactory, $secondFactory)
+ {
+ $temporaryResponse = $firstFactory->createResponse($response);
+ $finalResponse = $secondFactory->createResponse($temporaryResponse);
+
+ if ($finalResponse instanceof Response) {
+ $this->assertEquals($response->getAge(), $finalResponse->getAge());
+ $this->assertEquals($response->getCharset(), $finalResponse->getCharset());
+ $this->assertEquals($response->getContent(), $finalResponse->getContent());
+ $this->assertEquals($response->getDate(), $finalResponse->getDate());
+ $this->assertEquals($response->getEtag(), $finalResponse->getEtag());
+ $this->assertEquals($response->getExpires(), $finalResponse->getExpires());
+ $this->assertEquals($response->getLastModified(), $finalResponse->getLastModified());
+ $this->assertEquals($response->getMaxAge(), $finalResponse->getMaxAge());
+ $this->assertEquals($response->getProtocolVersion(), $finalResponse->getProtocolVersion());
+ $this->assertEquals($response->getStatusCode(), $finalResponse->getStatusCode());
+ $this->assertEquals($response->getTtl(), $finalResponse->getTtl());
+ } elseif ($finalResponse instanceof ResponseInterface) {
+ $strToLower = function ($arr) {
+ foreach ($arr as $key => $value) {
+ yield strtolower($key) => $value;
+ }
+ };
+ $this->assertEquals($response->getStatusCode(), $finalResponse->getStatusCode());
+ $this->assertEquals($response->getReasonPhrase(), $finalResponse->getReasonPhrase());
+ $this->assertEquals((string) $response->getBody(), (string) $finalResponse->getBody());
+ $this->assertEquals($strToLower($response->getHeaders()), $strToLower($finalResponse->getHeaders()));
+ $this->assertEquals($response->getProtocolVersion(), $finalResponse->getProtocolVersion());
+ } else {
+ $this->fail('$finalResponse must be an instance of PSR7 or a HTTPFoundation response');
+ }
+ }
+
+ public function responseProvider()
+ {
+ $sfResponse = new Response(
+ 'Response content.',
+ 202,
+ ['x-symfony' => ['3.4']]
+ );
+
+ if (method_exists(Cookie::class, 'create')) {
+ $cookie = Cookie::create('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'));
+ } else {
+ $cookie = new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'));
+ }
+
+ $sfResponse->headers->setCookie($cookie);
+ $body = Psr7Stream::create();
+ $status = 302;
+ $headers = [
+ 'location' => ['http://example.com/'],
+ ];
+ $zendResponse = new Psr7Response($status, $headers, $body);
+
+ $nyholmFactory = new Psr17Factory();
+ $psr17Factory = new PsrHttpFactory($nyholmFactory, $nyholmFactory, $nyholmFactory, $nyholmFactory);
+ $symfonyFactory = new HttpFoundationFactory();
+
+ return [
+ [$sfResponse, $psr17Factory, $symfonyFactory],
+ [$zendResponse, $symfonyFactory, $psr17Factory],
+ ];
+ }
+
+ private function createUploadedFile($content, $originalName, $mimeType, $error)
+ {
+ $path = tempnam($this->tmpDir, uniqid());
+ file_put_contents($path, $content);
+
+ if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) {
+ // Symfony 4.1+
+ return new UploadedFile($path, $originalName, $mimeType, $error, true);
+ }
+
+ return new UploadedFile($path, $originalName, $mimeType, filesize($path), $error, true);
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/composer.json b/vendor/symfony/psr-http-message-bridge/composer.json
new file mode 100755
index 0000000..51bb655
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/composer.json
@@ -0,0 +1,42 @@
+{
+ "name": "symfony/psr-http-message-bridge",
+ "type": "symfony-bridge",
+ "description": "PSR HTTP message bridge",
+ "keywords": ["http", "psr-7", "psr-17", "http-message"],
+ "homepage": "http://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^7.1",
+ "psr/http-message": "^1.0",
+ "symfony/http-foundation": "^3.4 || ^4.0"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "^3.4.20 || ^4.0",
+ "nyholm/psr7": "^1.1",
+ "zendframework/zend-diactoros": "^1.4.1 || ^2.0"
+ },
+ "suggest": {
+ "nyholm/psr7": "For a super lightweight PSR-7/17 implementation"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/phpunit.xml.dist b/vendor/symfony/psr-http-message-bridge/phpunit.xml.dist
new file mode 100755
index 0000000..9a6e477
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/phpunit.xml.dist
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ syntaxCheck="false"
+ bootstrap="vendor/autoload.php"
+>
+ <testsuites>
+ <testsuite name="Symfony PSR-7 HTTP message Bridge Test Suite">
+ <directory>./Tests/</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>./</directory>
+ <exclude>
+ <directory>./Resources</directory>
+ <directory>./Tests</directory>
+ <directory>./vendor</directory>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/vendor/symfony/translation-contracts/LICENSE b/vendor/symfony/translation-contracts/LICENSE
new file mode 100755
index 0000000..3f853aa
--- /dev/null
+++ b/vendor/symfony/translation-contracts/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/translation-contracts/LocaleAwareInterface.php b/vendor/symfony/translation-contracts/LocaleAwareInterface.php
new file mode 100755
index 0000000..dbd8894
--- /dev/null
+++ b/vendor/symfony/translation-contracts/LocaleAwareInterface.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Translation;
+
+interface LocaleAwareInterface
+{
+ /**
+ * Sets the current locale.
+ *
+ * @param string $locale The locale
+ *
+ * @throws \InvalidArgumentException If the locale contains invalid characters
+ */
+ public function setLocale($locale);
+
+ /**
+ * Returns the current locale.
+ *
+ * @return string The locale
+ */
+ public function getLocale();
+}
diff --git a/vendor/symfony/translation-contracts/README.md b/vendor/symfony/translation-contracts/README.md
new file mode 100755
index 0000000..6c693ce
--- /dev/null
+++ b/vendor/symfony/translation-contracts/README.md
@@ -0,0 +1,9 @@
+Symfony Translation Contracts
+=============================
+
+A set of abstractions extracted out of the Symfony components.
+
+Can be used to build on semantics that the Symfony components proved useful - and
+that already have battle tested implementations.
+
+See https://github.com/symfony/contracts/blob/master/README.md for more information.
diff --git a/vendor/symfony/translation-contracts/Test/TranslatorTest.php b/vendor/symfony/translation-contracts/Test/TranslatorTest.php
new file mode 100755
index 0000000..4846630
--- /dev/null
+++ b/vendor/symfony/translation-contracts/Test/TranslatorTest.php
@@ -0,0 +1,353 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Translation\Test;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Contracts\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorTrait;
+
+/**
+ * Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms
+ * and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms.
+ *
+ * See also https://developer.mozilla.org/en/Localization_and_Plurals which mentions 15 rules having a maximum of 6 forms.
+ * The mozilla code is also interesting to check for.
+ *
+ * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199
+ *
+ * The goal to cover all languages is to far fetched so this test case is smaller.
+ *
+ * @author Clemens Tolboom clemens@build2be.nl
+ */
+class TranslatorTest extends TestCase
+{
+ public function getTranslator()
+ {
+ return new class() implements TranslatorInterface {
+ use TranslatorTrait;
+ };
+ }
+
+ /**
+ * @dataProvider getTransTests
+ */
+ public function testTrans($expected, $id, $parameters)
+ {
+ $translator = $this->getTranslator();
+
+ $this->assertEquals($expected, $translator->trans($id, $parameters));
+ }
+
+ /**
+ * @dataProvider getTransChoiceTests
+ */
+ public function testTransChoiceWithExplicitLocale($expected, $id, $number)
+ {
+ $translator = $this->getTranslator();
+ $translator->setLocale('en');
+
+ $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number]));
+ }
+
+ /**
+ * @dataProvider getTransChoiceTests
+ */
+ public function testTransChoiceWithDefaultLocale($expected, $id, $number)
+ {
+ \Locale::setDefault('en');
+
+ $translator = $this->getTranslator();
+
+ $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number]));
+ }
+
+ public function testGetSetLocale()
+ {
+ $translator = $this->getTranslator();
+ $translator->setLocale('en');
+
+ $this->assertEquals('en', $translator->getLocale());
+ }
+
+ /**
+ * @requires extension intl
+ */
+ public function testGetLocaleReturnsDefaultLocaleIfNotSet()
+ {
+ $translator = $this->getTranslator();
+
+ \Locale::setDefault('pt_BR');
+ $this->assertEquals('pt_BR', $translator->getLocale());
+
+ \Locale::setDefault('en');
+ $this->assertEquals('en', $translator->getLocale());
+ }
+
+ public function getTransTests()
+ {
+ return [
+ ['Symfony is great!', 'Symfony is great!', []],
+ ['Symfony is awesome!', 'Symfony is %what%!', ['%what%' => 'awesome']],
+ ];
+ }
+
+ public function getTransChoiceTests()
+ {
+ return [
+ ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1],
+ ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10],
+ ['There are 0 apples', 'There is 1 apple|There are %count% apples', 0],
+ ['There is 1 apple', 'There is 1 apple|There are %count% apples', 1],
+ ['There are 10 apples', 'There is 1 apple|There are %count% apples', 10],
+ // custom validation messages may be coded with a fixed value
+ ['There are 2 apples', 'There are 2 apples', 2],
+ ];
+ }
+
+ /**
+ * @dataProvider getInternal
+ */
+ public function testInterval($expected, $number, $interval)
+ {
+ $translator = $this->getTranslator();
+
+ $this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number]));
+ }
+
+ public function getInternal()
+ {
+ return [
+ ['foo', 3, '{1,2, 3 ,4}'],
+ ['bar', 10, '{1,2, 3 ,4}'],
+ ['bar', 3, '[1,2]'],
+ ['foo', 1, '[1,2]'],
+ ['foo', 2, '[1,2]'],
+ ['bar', 1, ']1,2['],
+ ['bar', 2, ']1,2['],
+ ['foo', log(0), '[-Inf,2['],
+ ['foo', -log(0), '[-2,+Inf]'],
+ ];
+ }
+
+ /**
+ * @dataProvider getChooseTests
+ */
+ public function testChoose($expected, $id, $number)
+ {
+ $translator = $this->getTranslator();
+
+ $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number]));
+ }
+
+ public function testReturnMessageIfExactlyOneStandardRuleIsGiven()
+ {
+ $translator = $this->getTranslator();
+
+ $this->assertEquals('There are two apples', $translator->trans('There are two apples', ['%count%' => 2]));
+ }
+
+ /**
+ * @dataProvider getNonMatchingMessages
+ * @expectedException \InvalidArgumentException
+ */
+ public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number)
+ {
+ $translator = $this->getTranslator();
+
+ $translator->trans($id, ['%count%' => $number]);
+ }
+
+ public function getNonMatchingMessages()
+ {
+ return [
+ ['{0} There are no apples|{1} There is one apple', 2],
+ ['{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['{1} There is one apple|]2,Inf] There are %count% apples', 2],
+ ['{0} There are no apples|There is one apple', 2],
+ ];
+ }
+
+ public function getChooseTests()
+ {
+ return [
+ ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+
+ ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1],
+
+ ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10],
+ ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10],
+ ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10],
+
+ ['There are 0 apples', 'There is one apple|There are %count% apples', 0],
+ ['There is one apple', 'There is one apple|There are %count% apples', 1],
+ ['There are 10 apples', 'There is one apple|There are %count% apples', 10],
+
+ ['There are 0 apples', 'one: There is one apple|more: There are %count% apples', 0],
+ ['There is one apple', 'one: There is one apple|more: There are %count% apples', 1],
+ ['There are 10 apples', 'one: There is one apple|more: There are %count% apples', 10],
+
+ ['There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0],
+ ['There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1],
+ ['There are 10 apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10],
+
+ ['', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1],
+
+ // Indexed only tests which are Gettext PoFile* compatible strings.
+ ['There are 0 apples', 'There is one apple|There are %count% apples', 0],
+ ['There is one apple', 'There is one apple|There are %count% apples', 1],
+ ['There are 2 apples', 'There is one apple|There are %count% apples', 2],
+
+ // Tests for float numbers
+ ['There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7],
+ ['There is one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1],
+ ['There is more than one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7],
+ ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0],
+ ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0],
+ ['There are no apples', '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0],
+
+ // Test texts with new-lines
+ // with double-quotes and \n in id & double-quotes and actual newlines in text
+ ["This is a text with a\n new-line in it. Selector = 0.", '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 0],
+ // with double-quotes and \n in id and single-quotes and actual newlines in text
+ ["This is a text with a\n new-line in it. Selector = 1.", '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 1],
+ ["This is a text with a\n new-line in it. Selector > 1.", '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 5],
+ // with double-quotes and id split accros lines
+ ['This is a text with a
+ new-line in it. Selector = 1.', '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 1],
+ // with single-quotes and id split accros lines
+ ['This is a text with a
+ new-line in it. Selector > 1.', '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 5],
+ // with single-quotes and \n in text
+ ['This is a text with a\nnew-line in it. Selector = 0.', '{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.', 0],
+ // with double-quotes and id split accros lines
+ ["This is a text with a\nnew-line in it. Selector = 1.", "{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.", 1],
+ // esacape pipe
+ ['This is a text with | in it. Selector = 0.', '{0}This is a text with || in it. Selector = 0.|{1}This is a text with || in it. Selector = 1.', 0],
+ // Empty plural set (2 plural forms) from a .PO file
+ ['', '|', 1],
+ // Empty plural set (3 plural forms) from a .PO file
+ ['', '||', 1],
+ ];
+ }
+
+ /**
+ * @dataProvider failingLangcodes
+ */
+ public function testFailedLangcodes($nplural, $langCodes)
+ {
+ $matrix = $this->generateTestData($langCodes);
+ $this->validateMatrix($nplural, $matrix, false);
+ }
+
+ /**
+ * @dataProvider successLangcodes
+ */
+ public function testLangcodes($nplural, $langCodes)
+ {
+ $matrix = $this->generateTestData($langCodes);
+ $this->validateMatrix($nplural, $matrix);
+ }
+
+ /**
+ * This array should contain all currently known langcodes.
+ *
+ * As it is impossible to have this ever complete we should try as hard as possible to have it almost complete.
+ *
+ * @return array
+ */
+ public function successLangcodes()
+ {
+ return [
+ ['1', ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky']],
+ ['2', ['nl', 'fr', 'en', 'de', 'de_GE', 'hy', 'hy_AM']],
+ ['3', ['be', 'bs', 'cs', 'hr']],
+ ['4', ['cy', 'mt', 'sl']],
+ ['6', ['ar']],
+ ];
+ }
+
+ /**
+ * This array should be at least empty within the near future.
+ *
+ * This both depends on a complete list trying to add above as understanding
+ * the plural rules of the current failing languages.
+ *
+ * @return array with nplural together with langcodes
+ */
+ public function failingLangcodes()
+ {
+ return [
+ ['1', ['fa']],
+ ['2', ['jbo']],
+ ['3', ['cbs']],
+ ['4', ['gd', 'kw']],
+ ['5', ['ga']],
+ ];
+ }
+
+ /**
+ * We validate only on the plural coverage. Thus the real rules is not tested.
+ *
+ * @param string $nplural Plural expected
+ * @param array $matrix Containing langcodes and their plural index values
+ * @param bool $expectSuccess
+ */
+ protected function validateMatrix($nplural, $matrix, $expectSuccess = true)
+ {
+ foreach ($matrix as $langCode => $data) {
+ $indexes = array_flip($data);
+ if ($expectSuccess) {
+ $this->assertEquals($nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms.");
+ } else {
+ $this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms.");
+ }
+ }
+ }
+
+ protected function generateTestData($langCodes)
+ {
+ $translator = new class() {
+ use TranslatorTrait {
+ getPluralizationRule as public;
+ }
+ };
+
+ $matrix = [];
+ foreach ($langCodes as $langCode) {
+ for ($count = 0; $count < 200; ++$count) {
+ $plural = $translator->getPluralizationRule($count, $langCode);
+ $matrix[$langCode][$count] = $plural;
+ }
+ }
+
+ return $matrix;
+ }
+}
diff --git a/vendor/symfony/translation-contracts/TranslatorInterface.php b/vendor/symfony/translation-contracts/TranslatorInterface.php
new file mode 100755
index 0000000..d867637
--- /dev/null
+++ b/vendor/symfony/translation-contracts/TranslatorInterface.php
@@ -0,0 +1,65 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Translation;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface TranslatorInterface
+{
+ /**
+ * Translates the given message.
+ *
+ * When a number is provided as a parameter named "%count%", the message is parsed for plural
+ * forms and a translation is chosen according to this number using the following rules:
+ *
+ * Given a message with different plural translations separated by a
+ * pipe (|), this method returns the correct portion of the message based
+ * on the given number, locale and the pluralization rules in the message
+ * itself.
+ *
+ * The message supports two different types of pluralization rules:
+ *
+ * interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples
+ * indexed: There is one apple|There are %count% apples
+ *
+ * The indexed solution can also contain labels (e.g. one: There is one apple).
+ * This is purely for making the translations more clear - it does not
+ * affect the functionality.
+ *
+ * The two methods can also be mixed:
+ * {0} There are no apples|one: There is one apple|more: There are %count% apples
+ *
+ * An interval can represent a finite set of numbers:
+ * {1,2,3,4}
+ *
+ * An interval can represent numbers between two numbers:
+ * [1, +Inf]
+ * ]-1,2[
+ *
+ * The left delimiter can be [ (inclusive) or ] (exclusive).
+ * The right delimiter can be [ (exclusive) or ] (inclusive).
+ * Beside numbers, you can use -Inf and +Inf for the infinite.
+ *
+ * @see https://en.wikipedia.org/wiki/ISO_31-11
+ *
+ * @param string $id The message id (may also be an object that can be cast to string)
+ * @param array $parameters An array of parameters for the message
+ * @param string|null $domain The domain for the message or null to use the default
+ * @param string|null $locale The locale or null to use the default
+ *
+ * @return string The translated string
+ *
+ * @throws \InvalidArgumentException If the locale contains invalid characters
+ */
+ public function trans($id, array $parameters = [], $domain = null, $locale = null);
+}
diff --git a/vendor/symfony/translation-contracts/TranslatorTrait.php b/vendor/symfony/translation-contracts/TranslatorTrait.php
new file mode 100755
index 0000000..488b4f4
--- /dev/null
+++ b/vendor/symfony/translation-contracts/TranslatorTrait.php
@@ -0,0 +1,255 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Translation;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+
+/**
+ * A trait to help implement TranslatorInterface and LocaleAwareInterface.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+trait TranslatorTrait
+{
+ private $locale;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setLocale($locale)
+ {
+ $this->locale = (string) $locale;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLocale()
+ {
+ return $this->locale ?: \Locale::getDefault();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function trans($id, array $parameters = [], $domain = null, $locale = null)
+ {
+ $id = (string) $id;
+
+ if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) {
+ return strtr($id, $parameters);
+ }
+
+ $number = (float) $parameters['%count%'];
+ $locale = (string) $locale ?: $this->getLocale();
+
+ $parts = [];
+ if (preg_match('/^\|++$/', $id)) {
+ $parts = explode('|', $id);
+ } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) {
+ $parts = $matches[0];
+ }
+
+ $intervalRegexp = <<<'EOF'
+/^(?P<interval>
+ ({\s*
+ (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)
+ \s*})
+
+ |
+
+ (?P<left_delimiter>[\[\]])
+ \s*
+ (?P<left>-Inf|\-?\d+(\.\d+)?)
+ \s*,\s*
+ (?P<right>\+?Inf|\-?\d+(\.\d+)?)
+ \s*
+ (?P<right_delimiter>[\[\]])
+)\s*(?P<message>.*?)$/xs
+EOF;
+
+ $standardRules = [];
+ foreach ($parts as $part) {
+ $part = trim(str_replace('||', '|', $part));
+
+ // try to match an explicit rule, then fallback to the standard ones
+ if (preg_match($intervalRegexp, $part, $matches)) {
+ if ($matches[2]) {
+ foreach (explode(',', $matches[3]) as $n) {
+ if ($number == $n) {
+ return strtr($matches['message'], $parameters);
+ }
+ }
+ } else {
+ $leftNumber = '-Inf' === $matches['left'] ? -INF : (float) $matches['left'];
+ $rightNumber = is_numeric($matches['right']) ? (float) $matches['right'] : INF;
+
+ if (('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber)
+ && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber)
+ ) {
+ return strtr($matches['message'], $parameters);
+ }
+ }
+ } elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) {
+ $standardRules[] = $matches[1];
+ } else {
+ $standardRules[] = $part;
+ }
+ }
+
+ $position = $this->getPluralizationRule($number, $locale);
+
+ if (!isset($standardRules[$position])) {
+ // when there's exactly one rule given, and that rule is a standard
+ // rule, use this rule
+ if (1 === \count($parts) && isset($standardRules[0])) {
+ return strtr($standardRules[0], $parameters);
+ }
+
+ $message = sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $id, $locale, $number);
+
+ if (class_exists(InvalidArgumentException::class)) {
+ throw new InvalidArgumentException($message);
+ }
+
+ throw new \InvalidArgumentException($message);
+ }
+
+ return strtr($standardRules[$position], $parameters);
+ }
+
+ /**
+ * Returns the plural position to use for the given locale and number.
+ *
+ * The plural rules are derived from code of the Zend Framework (2010-09-25),
+ * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd).
+ * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ */
+ private function getPluralizationRule(int $number, string $locale): int
+ {
+ switch ('pt_BR' !== $locale && \strlen($locale) > 3 ? substr($locale, 0, strrpos($locale, '_')) : $locale) {
+ case 'af':
+ case 'bn':
+ case 'bg':
+ case 'ca':
+ case 'da':
+ case 'de':
+ case 'el':
+ case 'en':
+ case 'eo':
+ case 'es':
+ case 'et':
+ case 'eu':
+ case 'fa':
+ case 'fi':
+ case 'fo':
+ case 'fur':
+ case 'fy':
+ case 'gl':
+ case 'gu':
+ case 'ha':
+ case 'he':
+ case 'hu':
+ case 'is':
+ case 'it':
+ case 'ku':
+ case 'lb':
+ case 'ml':
+ case 'mn':
+ case 'mr':
+ case 'nah':
+ case 'nb':
+ case 'ne':
+ case 'nl':
+ case 'nn':
+ case 'no':
+ case 'oc':
+ case 'om':
+ case 'or':
+ case 'pa':
+ case 'pap':
+ case 'ps':
+ case 'pt':
+ case 'so':
+ case 'sq':
+ case 'sv':
+ case 'sw':
+ case 'ta':
+ case 'te':
+ case 'tk':
+ case 'ur':
+ case 'zu':
+ return (1 == $number) ? 0 : 1;
+
+ case 'am':
+ case 'bh':
+ case 'fil':
+ case 'fr':
+ case 'gun':
+ case 'hi':
+ case 'hy':
+ case 'ln':
+ case 'mg':
+ case 'nso':
+ case 'pt_BR':
+ case 'ti':
+ case 'wa':
+ return ((0 == $number) || (1 == $number)) ? 0 : 1;
+
+ case 'be':
+ case 'bs':
+ case 'hr':
+ case 'ru':
+ case 'sh':
+ case 'sr':
+ case 'uk':
+ return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
+
+ case 'cs':
+ case 'sk':
+ return (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2);
+
+ case 'ga':
+ return (1 == $number) ? 0 : ((2 == $number) ? 1 : 2);
+
+ case 'lt':
+ return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
+
+ case 'sl':
+ return (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3));
+
+ case 'mk':
+ return (1 == $number % 10) ? 0 : 1;
+
+ case 'mt':
+ return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3));
+
+ case 'lv':
+ return (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2);
+
+ case 'pl':
+ return (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2);
+
+ case 'cy':
+ return (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3));
+
+ case 'ro':
+ return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2);
+
+ case 'ar':
+ return (0 == $number) ? 0 : ((1 == $number) ? 1 : ((2 == $number) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5))));
+
+ default:
+ return 0;
+ }
+ }
+}
diff --git a/vendor/symfony/translation-contracts/composer.json b/vendor/symfony/translation-contracts/composer.json
new file mode 100755
index 0000000..09749d3
--- /dev/null
+++ b/vendor/symfony/translation-contracts/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "symfony/translation-contracts",
+ "type": "library",
+ "description": "Generic abstractions related to translation",
+ "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^7.1.3"
+ },
+ "suggest": {
+ "symfony/translation-implementation": ""
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Contracts\\Translation\\": "" }
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/translation/.gitignore b/vendor/symfony/translation/.gitignore
new file mode 100755
index 0000000..c49a5d8
--- /dev/null
+++ b/vendor/symfony/translation/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/vendor/symfony/translation/CHANGELOG.md b/vendor/symfony/translation/CHANGELOG.md
new file mode 100755
index 0000000..c807168
--- /dev/null
+++ b/vendor/symfony/translation/CHANGELOG.md
@@ -0,0 +1,126 @@
+CHANGELOG
+=========
+
+4.3.0
+-----
+
+ * Improved Xliff 1.2 loader to load the original file's metadata
+ * Added `TranslatorPathsPass`
+
+4.2.0
+-----
+
+ * Started using ICU parent locales as fallback locales.
+ * allow using the ICU message format using domains with the "+intl-icu" suffix
+ * deprecated `Translator::transChoice()` in favor of using `Translator::trans()` with a `%count%` parameter
+ * deprecated `TranslatorInterface` in favor of `Symfony\Contracts\Translation\TranslatorInterface`
+ * deprecated `MessageSelector`, `Interval` and `PluralizationRules`; use `IdentityTranslator` instead
+ * Added `IntlFormatter` and `IntlFormatterInterface`
+ * added support for multiple files and directories in `XliffLintCommand`
+ * Marked `Translator::getFallbackLocales()` and `TranslationDataCollector::getFallbackLocales()` as internal
+
+4.1.0
+-----
+
+ * The `FileDumper::setBackup()` method is deprecated.
+ * The `TranslationWriter::disableBackup()` method is deprecated.
+ * The `XliffFileDumper` will write "name" on the "unit" node when dumping XLIFF 2.0.
+
+4.0.0
+-----
+
+ * removed the backup feature of the `FileDumper` class
+ * removed `TranslationWriter::writeTranslations()` method
+ * removed support for passing `MessageSelector` instances to the constructor of the `Translator` class
+
+3.4.0
+-----
+
+ * Added `TranslationDumperPass`
+ * Added `TranslationExtractorPass`
+ * Added `TranslatorPass`
+ * Added `TranslationReader` and `TranslationReaderInterface`
+ * Added `<notes>` section to the Xliff 2.0 dumper.
+ * Improved Xliff 2.0 loader to load `<notes>` section.
+ * Added `TranslationWriterInterface`
+ * Deprecated `TranslationWriter::writeTranslations` in favor of `TranslationWriter::write`
+ * added support for adding custom message formatter and decoupling the default one.
+ * Added `PhpExtractor`
+ * Added `PhpStringTokenParser`
+
+3.2.0
+-----
+
+ * Added support for escaping `|` in plural translations with double pipe.
+
+3.1.0
+-----
+
+ * Deprecated the backup feature of the file dumper classes.
+
+3.0.0
+-----
+
+ * removed `FileDumper::format()` method.
+ * Changed the visibility of the locale property in `Translator` from protected to private.
+
+2.8.0
+-----
+
+ * deprecated FileDumper::format(), overwrite FileDumper::formatCatalogue() instead.
+ * deprecated Translator::getMessages(), rely on TranslatorBagInterface::getCatalogue() instead.
+ * added `FileDumper::formatCatalogue` which allows format the catalogue without dumping it into file.
+ * added option `json_encoding` to JsonFileDumper
+ * added options `as_tree`, `inline` to YamlFileDumper
+ * added support for XLIFF 2.0.
+ * added support for XLIFF target and tool attributes.
+ * added message parameters to DataCollectorTranslator.
+ * [DEPRECATION] The `DiffOperation` class has been deprecated and
+ will be removed in Symfony 3.0, since its operation has nothing to do with 'diff',
+ so the class name is misleading. The `TargetOperation` class should be used for
+ this use-case instead.
+
+2.7.0
+-----
+
+ * added DataCollectorTranslator for collecting the translated messages.
+
+2.6.0
+-----
+
+ * added possibility to cache catalogues
+ * added TranslatorBagInterface
+ * added LoggingTranslator
+ * added Translator::getMessages() for retrieving the message catalogue as an array
+
+2.5.0
+-----
+
+ * added relative file path template to the file dumpers
+ * added optional backup to the file dumpers
+ * changed IcuResFileDumper to extend FileDumper
+
+2.3.0
+-----
+
+ * added classes to make operations on catalogues (like making a diff or a merge on 2 catalogues)
+ * added Translator::getFallbackLocales()
+ * deprecated Translator::setFallbackLocale() in favor of the new Translator::setFallbackLocales() method
+
+2.2.0
+-----
+
+ * QtTranslationsLoader class renamed to QtFileLoader. QtTranslationsLoader is deprecated and will be removed in 2.3.
+ * [BC BREAK] uniformized the exception thrown by the load() method when an error occurs. The load() method now
+ throws Symfony\Component\Translation\Exception\NotFoundResourceException when a resource cannot be found
+ and Symfony\Component\Translation\Exception\InvalidResourceException when a resource is invalid.
+ * changed the exception class thrown by some load() methods from \RuntimeException to \InvalidArgumentException
+ (IcuDatFileLoader, IcuResFileLoader and QtFileLoader)
+
+2.1.0
+-----
+
+ * added support for more than one fallback locale
+ * added support for extracting translation messages from templates (Twig and PHP)
+ * added dumpers for translation catalogs
+ * added support for QT, gettext, and ResourceBundles
diff --git a/vendor/symfony/translation/Catalogue/AbstractOperation.php b/vendor/symfony/translation/Catalogue/AbstractOperation.php
new file mode 100755
index 0000000..919bab8
--- /dev/null
+++ b/vendor/symfony/translation/Catalogue/AbstractOperation.php
@@ -0,0 +1,157 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Catalogue;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\LogicException;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\MessageCatalogueInterface;
+
+/**
+ * Base catalogues binary operation class.
+ *
+ * A catalogue binary operation performs operation on
+ * source (the left argument) and target (the right argument) catalogues.
+ *
+ * @author Jean-François Simon <contact@jfsimon.fr>
+ */
+abstract class AbstractOperation implements OperationInterface
+{
+ protected $source;
+ protected $target;
+ protected $result;
+
+ /**
+ * @var array|null The domains affected by this operation
+ */
+ private $domains;
+
+ /**
+ * This array stores 'all', 'new' and 'obsolete' messages for all valid domains.
+ *
+ * The data structure of this array is as follows:
+ *
+ * [
+ * 'domain 1' => [
+ * 'all' => [...],
+ * 'new' => [...],
+ * 'obsolete' => [...]
+ * ],
+ * 'domain 2' => [
+ * 'all' => [...],
+ * 'new' => [...],
+ * 'obsolete' => [...]
+ * ],
+ * ...
+ * ]
+ *
+ * @var array The array that stores 'all', 'new' and 'obsolete' messages
+ */
+ protected $messages;
+
+ /**
+ * @throws LogicException
+ */
+ public function __construct(MessageCatalogueInterface $source, MessageCatalogueInterface $target)
+ {
+ if ($source->getLocale() !== $target->getLocale()) {
+ throw new LogicException('Operated catalogues must belong to the same locale.');
+ }
+
+ $this->source = $source;
+ $this->target = $target;
+ $this->result = new MessageCatalogue($source->getLocale());
+ $this->messages = [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDomains()
+ {
+ if (null === $this->domains) {
+ $this->domains = array_values(array_unique(array_merge($this->source->getDomains(), $this->target->getDomains())));
+ }
+
+ return $this->domains;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMessages($domain)
+ {
+ if (!\in_array($domain, $this->getDomains())) {
+ throw new InvalidArgumentException(sprintf('Invalid domain: %s.', $domain));
+ }
+
+ if (!isset($this->messages[$domain]['all'])) {
+ $this->processDomain($domain);
+ }
+
+ return $this->messages[$domain]['all'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getNewMessages($domain)
+ {
+ if (!\in_array($domain, $this->getDomains())) {
+ throw new InvalidArgumentException(sprintf('Invalid domain: %s.', $domain));
+ }
+
+ if (!isset($this->messages[$domain]['new'])) {
+ $this->processDomain($domain);
+ }
+
+ return $this->messages[$domain]['new'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getObsoleteMessages($domain)
+ {
+ if (!\in_array($domain, $this->getDomains())) {
+ throw new InvalidArgumentException(sprintf('Invalid domain: %s.', $domain));
+ }
+
+ if (!isset($this->messages[$domain]['obsolete'])) {
+ $this->processDomain($domain);
+ }
+
+ return $this->messages[$domain]['obsolete'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getResult()
+ {
+ foreach ($this->getDomains() as $domain) {
+ if (!isset($this->messages[$domain])) {
+ $this->processDomain($domain);
+ }
+ }
+
+ return $this->result;
+ }
+
+ /**
+ * Performs operation on source and target catalogues for the given domain and
+ * stores the results.
+ *
+ * @param string $domain The domain which the operation will be performed for
+ */
+ abstract protected function processDomain($domain);
+}
diff --git a/vendor/symfony/translation/Catalogue/MergeOperation.php b/vendor/symfony/translation/Catalogue/MergeOperation.php
new file mode 100755
index 0000000..d2f4abd
--- /dev/null
+++ b/vendor/symfony/translation/Catalogue/MergeOperation.php
@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Catalogue;
+
+use Symfony\Component\Translation\MessageCatalogueInterface;
+
+/**
+ * Merge operation between two catalogues as follows:
+ * all = source ∪ target = {x: x ∈ source ∨ x ∈ target}
+ * new = all ∖ source = {x: x ∈ target ∧ x ∉ source}
+ * obsolete = source ∖ all = {x: x ∈ source ∧ x ∉ source ∧ x ∉ target} = ∅
+ * Basically, the result contains messages from both catalogues.
+ *
+ * @author Jean-François Simon <contact@jfsimon.fr>
+ */
+class MergeOperation extends AbstractOperation
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function processDomain($domain)
+ {
+ $this->messages[$domain] = [
+ 'all' => [],
+ 'new' => [],
+ 'obsolete' => [],
+ ];
+ $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
+
+ foreach ($this->source->all($domain) as $id => $message) {
+ $this->messages[$domain]['all'][$id] = $message;
+ $this->result->add([$id => $message], $this->source->defines($id, $intlDomain) ? $intlDomain : $domain);
+ if (null !== $keyMetadata = $this->source->getMetadata($id, $domain)) {
+ $this->result->setMetadata($id, $keyMetadata, $domain);
+ }
+ }
+
+ foreach ($this->target->all($domain) as $id => $message) {
+ if (!$this->source->has($id, $domain)) {
+ $this->messages[$domain]['all'][$id] = $message;
+ $this->messages[$domain]['new'][$id] = $message;
+ $this->result->add([$id => $message], $this->target->defines($id, $intlDomain) ? $intlDomain : $domain);
+ if (null !== $keyMetadata = $this->target->getMetadata($id, $domain)) {
+ $this->result->setMetadata($id, $keyMetadata, $domain);
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Catalogue/OperationInterface.php b/vendor/symfony/translation/Catalogue/OperationInterface.php
new file mode 100755
index 0000000..87d888e
--- /dev/null
+++ b/vendor/symfony/translation/Catalogue/OperationInterface.php
@@ -0,0 +1,77 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Catalogue;
+
+use Symfony\Component\Translation\MessageCatalogueInterface;
+
+/**
+ * Represents an operation on catalogue(s).
+ *
+ * An instance of this interface performs an operation on one or more catalogues and
+ * stores intermediate and final results of the operation.
+ *
+ * The first catalogue in its argument(s) is called the 'source catalogue' or 'source' and
+ * the following results are stored:
+ *
+ * Messages: also called 'all', are valid messages for the given domain after the operation is performed.
+ *
+ * New Messages: also called 'new' (new = all ∖ source = {x: x ∈ all ∧ x ∉ source}).
+ *
+ * Obsolete Messages: also called 'obsolete' (obsolete = source ∖ all = {x: x ∈ source ∧ x ∉ all}).
+ *
+ * Result: also called 'result', is the resulting catalogue for the given domain that holds the same messages as 'all'.
+ *
+ * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
+ */
+interface OperationInterface
+{
+ /**
+ * Returns domains affected by operation.
+ *
+ * @return array
+ */
+ public function getDomains();
+
+ /**
+ * Returns all valid messages ('all') after operation.
+ *
+ * @param string $domain
+ *
+ * @return array
+ */
+ public function getMessages($domain);
+
+ /**
+ * Returns new messages ('new') after operation.
+ *
+ * @param string $domain
+ *
+ * @return array
+ */
+ public function getNewMessages($domain);
+
+ /**
+ * Returns obsolete messages ('obsolete') after operation.
+ *
+ * @param string $domain
+ *
+ * @return array
+ */
+ public function getObsoleteMessages($domain);
+
+ /**
+ * Returns resulting catalogue ('result').
+ *
+ * @return MessageCatalogueInterface
+ */
+ public function getResult();
+}
diff --git a/vendor/symfony/translation/Catalogue/TargetOperation.php b/vendor/symfony/translation/Catalogue/TargetOperation.php
new file mode 100755
index 0000000..22aa9a3
--- /dev/null
+++ b/vendor/symfony/translation/Catalogue/TargetOperation.php
@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Catalogue;
+
+use Symfony\Component\Translation\MessageCatalogueInterface;
+
+/**
+ * Target operation between two catalogues:
+ * intersection = source ∩ target = {x: x ∈ source ∧ x ∈ target}
+ * all = intersection ∪ (target ∖ intersection) = target
+ * new = all ∖ source = {x: x ∈ target ∧ x ∉ source}
+ * obsolete = source ∖ all = source ∖ target = {x: x ∈ source ∧ x ∉ target}
+ * Basically, the result contains messages from the target catalogue.
+ *
+ * @author Michael Lee <michael.lee@zerustech.com>
+ */
+class TargetOperation extends AbstractOperation
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function processDomain($domain)
+ {
+ $this->messages[$domain] = [
+ 'all' => [],
+ 'new' => [],
+ 'obsolete' => [],
+ ];
+ $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
+
+ // For 'all' messages, the code can't be simplified as ``$this->messages[$domain]['all'] = $target->all($domain);``,
+ // because doing so will drop messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback}
+ //
+ // For 'new' messages, the code can't be simplified as ``array_diff_assoc($this->target->all($domain), $this->source->all($domain));``
+ // because doing so will not exclude messages like {x: x ∈ target ∧ x ∉ source.all ∧ x ∈ source.fallback}
+ //
+ // For 'obsolete' messages, the code can't be simplified as ``array_diff_assoc($this->source->all($domain), $this->target->all($domain))``
+ // because doing so will not exclude messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback}
+
+ foreach ($this->source->all($domain) as $id => $message) {
+ if ($this->target->has($id, $domain)) {
+ $this->messages[$domain]['all'][$id] = $message;
+ $this->result->add([$id => $message], $this->target->defines($id, $intlDomain) ? $intlDomain : $domain);
+ if (null !== $keyMetadata = $this->source->getMetadata($id, $domain)) {
+ $this->result->setMetadata($id, $keyMetadata, $domain);
+ }
+ } else {
+ $this->messages[$domain]['obsolete'][$id] = $message;
+ }
+ }
+
+ foreach ($this->target->all($domain) as $id => $message) {
+ if (!$this->source->has($id, $domain)) {
+ $this->messages[$domain]['all'][$id] = $message;
+ $this->messages[$domain]['new'][$id] = $message;
+ $this->result->add([$id => $message], $this->target->defines($id, $intlDomain) ? $intlDomain : $domain);
+ if (null !== $keyMetadata = $this->target->getMetadata($id, $domain)) {
+ $this->result->setMetadata($id, $keyMetadata, $domain);
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Command/XliffLintCommand.php b/vendor/symfony/translation/Command/XliffLintCommand.php
new file mode 100755
index 0000000..3c2cc9e
--- /dev/null
+++ b/vendor/symfony/translation/Command/XliffLintCommand.php
@@ -0,0 +1,279 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Command;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\Translation\Util\XliffUtils;
+
+/**
+ * Validates XLIFF files syntax and outputs encountered errors.
+ *
+ * @author Grégoire Pineau <lyrixx@lyrixx.info>
+ * @author Robin Chalas <robin.chalas@gmail.com>
+ * @author Javier Eguiluz <javier.eguiluz@gmail.com>
+ */
+class XliffLintCommand extends Command
+{
+ protected static $defaultName = 'lint:xliff';
+
+ private $format;
+ private $displayCorrectFiles;
+ private $directoryIteratorProvider;
+ private $isReadableProvider;
+ private $requireStrictFileNames;
+
+ public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null, bool $requireStrictFileNames = true)
+ {
+ parent::__construct($name);
+
+ $this->directoryIteratorProvider = $directoryIteratorProvider;
+ $this->isReadableProvider = $isReadableProvider;
+ $this->requireStrictFileNames = $requireStrictFileNames;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this
+ ->setDescription('Lints a XLIFF file and outputs encountered errors')
+ ->addArgument('filename', InputArgument::IS_ARRAY, 'A file or a directory or STDIN')
+ ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
+ ->setHelp(<<<EOF
+The <info>%command.name%</info> command lints a XLIFF file and outputs to STDOUT
+the first encountered syntax error.
+
+You can validates XLIFF contents passed from STDIN:
+
+ <info>cat filename | php %command.full_name%</info>
+
+You can also validate the syntax of a file:
+
+ <info>php %command.full_name% filename</info>
+
+Or of a whole directory:
+
+ <info>php %command.full_name% dirname</info>
+ <info>php %command.full_name% dirname --format=json</info>
+
+EOF
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $io = new SymfonyStyle($input, $output);
+ $filenames = (array) $input->getArgument('filename');
+ $this->format = $input->getOption('format');
+ $this->displayCorrectFiles = $output->isVerbose();
+
+ if (0 === \count($filenames)) {
+ if (!$stdin = $this->getStdin()) {
+ throw new RuntimeException('Please provide a filename or pipe file content to STDIN.');
+ }
+
+ return $this->display($io, [$this->validate($stdin)]);
+ }
+
+ $filesInfo = [];
+ foreach ($filenames as $filename) {
+ if (!$this->isReadable($filename)) {
+ throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename));
+ }
+
+ foreach ($this->getFiles($filename) as $file) {
+ $filesInfo[] = $this->validate(file_get_contents($file), $file);
+ }
+ }
+
+ return $this->display($io, $filesInfo);
+ }
+
+ private function validate($content, $file = null)
+ {
+ $errors = [];
+
+ // Avoid: Warning DOMDocument::loadXML(): Empty string supplied as input
+ if ('' === trim($content)) {
+ return ['file' => $file, 'valid' => true];
+ }
+
+ $internal = libxml_use_internal_errors(true);
+
+ $document = new \DOMDocument();
+ $document->loadXML($content);
+
+ if (null !== $targetLanguage = $this->getTargetLanguageFromFile($document)) {
+ $normalizedLocale = preg_quote(str_replace('-', '_', $targetLanguage), '/');
+ // strict file names require translation files to be named '____.locale.xlf'
+ // otherwise, both '____.locale.xlf' and 'locale.____.xlf' are allowed
+ // also, the regexp matching must be case-insensitive, as defined for 'target-language' values
+ // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#target-language
+ $expectedFilenamePattern = $this->requireStrictFileNames ? sprintf('/^.*\.(?i:%s)\.xlf/', $normalizedLocale) : sprintf('/^(.*\.(?i:%s)\.xlf|(?i:%s)\..*\.xlf)/', $normalizedLocale, $normalizedLocale);
+
+ if (0 === preg_match($expectedFilenamePattern, basename($file))) {
+ $errors[] = [
+ 'line' => -1,
+ 'column' => -1,
+ 'message' => sprintf('There is a mismatch between the language included in the file name ("%s") and the "%s" value used in the "target-language" attribute of the file.', basename($file), $targetLanguage),
+ ];
+ }
+ }
+
+ foreach (XliffUtils::validateSchema($document) as $xmlError) {
+ $errors[] = [
+ 'line' => $xmlError['line'],
+ 'column' => $xmlError['column'],
+ 'message' => $xmlError['message'],
+ ];
+ }
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internal);
+
+ return ['file' => $file, 'valid' => 0 === \count($errors), 'messages' => $errors];
+ }
+
+ private function display(SymfonyStyle $io, array $files)
+ {
+ switch ($this->format) {
+ case 'txt':
+ return $this->displayTxt($io, $files);
+ case 'json':
+ return $this->displayJson($io, $files);
+ default:
+ throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format));
+ }
+ }
+
+ private function displayTxt(SymfonyStyle $io, array $filesInfo)
+ {
+ $countFiles = \count($filesInfo);
+ $erroredFiles = 0;
+
+ foreach ($filesInfo as $info) {
+ if ($info['valid'] && $this->displayCorrectFiles) {
+ $io->comment('<info>OK</info>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
+ } elseif (!$info['valid']) {
+ ++$erroredFiles;
+ $io->text('<error> ERROR </error>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
+ $io->listing(array_map(function ($error) {
+ // general document errors have a '-1' line number
+ return -1 === $error['line'] ? $error['message'] : sprintf('Line %d, Column %d: %s', $error['line'], $error['column'], $error['message']);
+ }, $info['messages']));
+ }
+ }
+
+ if (0 === $erroredFiles) {
+ $io->success(sprintf('All %d XLIFF files contain valid syntax.', $countFiles));
+ } else {
+ $io->warning(sprintf('%d XLIFF files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles));
+ }
+
+ return min($erroredFiles, 1);
+ }
+
+ private function displayJson(SymfonyStyle $io, array $filesInfo)
+ {
+ $errors = 0;
+
+ array_walk($filesInfo, function (&$v) use (&$errors) {
+ $v['file'] = (string) $v['file'];
+ if (!$v['valid']) {
+ ++$errors;
+ }
+ });
+
+ $io->writeln(json_encode($filesInfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
+
+ return min($errors, 1);
+ }
+
+ private function getFiles($fileOrDirectory)
+ {
+ if (is_file($fileOrDirectory)) {
+ yield new \SplFileInfo($fileOrDirectory);
+
+ return;
+ }
+
+ foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) {
+ if (!\in_array($file->getExtension(), ['xlf', 'xliff'])) {
+ continue;
+ }
+
+ yield $file;
+ }
+ }
+
+ private function getStdin()
+ {
+ if (0 !== ftell(STDIN)) {
+ return;
+ }
+
+ $inputs = '';
+ while (!feof(STDIN)) {
+ $inputs .= fread(STDIN, 1024);
+ }
+
+ return $inputs;
+ }
+
+ private function getDirectoryIterator($directory)
+ {
+ $default = function ($directory) {
+ return new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ );
+ };
+
+ if (null !== $this->directoryIteratorProvider) {
+ return ($this->directoryIteratorProvider)($directory, $default);
+ }
+
+ return $default($directory);
+ }
+
+ private function isReadable($fileOrDirectory)
+ {
+ $default = function ($fileOrDirectory) {
+ return is_readable($fileOrDirectory);
+ };
+
+ if (null !== $this->isReadableProvider) {
+ return ($this->isReadableProvider)($fileOrDirectory, $default);
+ }
+
+ return $default($fileOrDirectory);
+ }
+
+ private function getTargetLanguageFromFile(\DOMDocument $xliffContents): ?string
+ {
+ foreach ($xliffContents->getElementsByTagName('file')[0]->attributes ?? [] as $attribute) {
+ if ('target-language' === $attribute->nodeName) {
+ return $attribute->nodeValue;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/symfony/translation/DataCollector/TranslationDataCollector.php b/vendor/symfony/translation/DataCollector/TranslationDataCollector.php
new file mode 100755
index 0000000..35dfc0e
--- /dev/null
+++ b/vendor/symfony/translation/DataCollector/TranslationDataCollector.php
@@ -0,0 +1,169 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\DataCollector;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\DataCollector\DataCollector;
+use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
+use Symfony\Component\Translation\DataCollectorTranslator;
+
+/**
+ * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
+ */
+class TranslationDataCollector extends DataCollector implements LateDataCollectorInterface
+{
+ private $translator;
+
+ public function __construct(DataCollectorTranslator $translator)
+ {
+ $this->translator = $translator;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function lateCollect()
+ {
+ $messages = $this->sanitizeCollectedMessages($this->translator->getCollectedMessages());
+
+ $this->data += $this->computeCount($messages);
+ $this->data['messages'] = $messages;
+
+ $this->data = $this->cloneVar($this->data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function collect(Request $request, Response $response, \Exception $exception = null)
+ {
+ $this->data['locale'] = $this->translator->getLocale();
+ $this->data['fallback_locales'] = $this->translator->getFallbackLocales();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ $this->data = [];
+ }
+
+ /**
+ * @return array
+ */
+ public function getMessages()
+ {
+ return isset($this->data['messages']) ? $this->data['messages'] : [];
+ }
+
+ /**
+ * @return int
+ */
+ public function getCountMissings()
+ {
+ return isset($this->data[DataCollectorTranslator::MESSAGE_MISSING]) ? $this->data[DataCollectorTranslator::MESSAGE_MISSING] : 0;
+ }
+
+ /**
+ * @return int
+ */
+ public function getCountFallbacks()
+ {
+ return isset($this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK]) ? $this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK] : 0;
+ }
+
+ /**
+ * @return int
+ */
+ public function getCountDefines()
+ {
+ return isset($this->data[DataCollectorTranslator::MESSAGE_DEFINED]) ? $this->data[DataCollectorTranslator::MESSAGE_DEFINED] : 0;
+ }
+
+ public function getLocale()
+ {
+ return !empty($this->data['locale']) ? $this->data['locale'] : null;
+ }
+
+ /**
+ * @internal since Symfony 4.2
+ */
+ public function getFallbackLocales()
+ {
+ return (isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'translation';
+ }
+
+ private function sanitizeCollectedMessages($messages)
+ {
+ $result = [];
+ foreach ($messages as $key => $message) {
+ $messageId = $message['locale'].$message['domain'].$message['id'];
+
+ if (!isset($result[$messageId])) {
+ $message['count'] = 1;
+ $message['parameters'] = !empty($message['parameters']) ? [$message['parameters']] : [];
+ $messages[$key]['translation'] = $this->sanitizeString($message['translation']);
+ $result[$messageId] = $message;
+ } else {
+ if (!empty($message['parameters'])) {
+ $result[$messageId]['parameters'][] = $message['parameters'];
+ }
+
+ ++$result[$messageId]['count'];
+ }
+
+ unset($messages[$key]);
+ }
+
+ return $result;
+ }
+
+ private function computeCount($messages)
+ {
+ $count = [
+ DataCollectorTranslator::MESSAGE_DEFINED => 0,
+ DataCollectorTranslator::MESSAGE_MISSING => 0,
+ DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK => 0,
+ ];
+
+ foreach ($messages as $message) {
+ ++$count[$message['state']];
+ }
+
+ return $count;
+ }
+
+ private function sanitizeString($string, $length = 80)
+ {
+ $string = trim(preg_replace('/\s+/', ' ', $string));
+
+ if (false !== $encoding = mb_detect_encoding($string, null, true)) {
+ if (mb_strlen($string, $encoding) > $length) {
+ return mb_substr($string, 0, $length - 3, $encoding).'...';
+ }
+ } elseif (\strlen($string) > $length) {
+ return substr($string, 0, $length - 3).'...';
+ }
+
+ return $string;
+ }
+}
diff --git a/vendor/symfony/translation/DataCollectorTranslator.php b/vendor/symfony/translation/DataCollectorTranslator.php
new file mode 100755
index 0000000..0284b77
--- /dev/null
+++ b/vendor/symfony/translation/DataCollectorTranslator.php
@@ -0,0 +1,188 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
+use Symfony\Contracts\Translation\LocaleAwareInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+/**
+ * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
+ */
+class DataCollectorTranslator implements LegacyTranslatorInterface, TranslatorInterface, TranslatorBagInterface, WarmableInterface
+{
+ const MESSAGE_DEFINED = 0;
+ const MESSAGE_MISSING = 1;
+ const MESSAGE_EQUALS_FALLBACK = 2;
+
+ /**
+ * @var TranslatorInterface|TranslatorBagInterface
+ */
+ private $translator;
+
+ private $messages = [];
+
+ /**
+ * @param TranslatorInterface $translator The translator must implement TranslatorBagInterface
+ */
+ public function __construct($translator)
+ {
+ if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
+ throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
+ }
+ if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) {
+ throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', \get_class($translator)));
+ }
+
+ $this->translator = $translator;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function trans($id, array $parameters = [], $domain = null, $locale = null)
+ {
+ $trans = $this->translator->trans($id, $parameters, $domain, $locale);
+ $this->collectMessage($locale, $domain, $id, $trans, $parameters);
+
+ return $trans;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter
+ */
+ public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null)
+ {
+ if ($this->translator instanceof TranslatorInterface) {
+ $trans = $this->translator->trans($id, ['%count%' => $number] + $parameters, $domain, $locale);
+ } else {
+ $trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
+ }
+
+ $this->collectMessage($locale, $domain, $id, $trans, ['%count%' => $number] + $parameters);
+
+ return $trans;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setLocale($locale)
+ {
+ $this->translator->setLocale($locale);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLocale()
+ {
+ return $this->translator->getLocale();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCatalogue($locale = null)
+ {
+ return $this->translator->getCatalogue($locale);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function warmUp($cacheDir)
+ {
+ if ($this->translator instanceof WarmableInterface) {
+ $this->translator->warmUp($cacheDir);
+ }
+ }
+
+ /**
+ * Gets the fallback locales.
+ *
+ * @return array The fallback locales
+ */
+ public function getFallbackLocales()
+ {
+ if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) {
+ return $this->translator->getFallbackLocales();
+ }
+
+ return [];
+ }
+
+ /**
+ * Passes through all unknown calls onto the translator object.
+ */
+ public function __call($method, $args)
+ {
+ return $this->translator->{$method}(...$args);
+ }
+
+ /**
+ * @return array
+ */
+ public function getCollectedMessages()
+ {
+ return $this->messages;
+ }
+
+ /**
+ * @param string|null $locale
+ * @param string|null $domain
+ * @param string $id
+ * @param string $translation
+ * @param array|null $parameters
+ */
+ private function collectMessage($locale, $domain, $id, $translation, $parameters = [])
+ {
+ if (null === $domain) {
+ $domain = 'messages';
+ }
+
+ $id = (string) $id;
+ $catalogue = $this->translator->getCatalogue($locale);
+ $locale = $catalogue->getLocale();
+ if ($catalogue->defines($id, $domain)) {
+ $state = self::MESSAGE_DEFINED;
+ } elseif ($catalogue->has($id, $domain)) {
+ $state = self::MESSAGE_EQUALS_FALLBACK;
+
+ $fallbackCatalogue = $catalogue->getFallbackCatalogue();
+ while ($fallbackCatalogue) {
+ if ($fallbackCatalogue->defines($id, $domain)) {
+ $locale = $fallbackCatalogue->getLocale();
+ break;
+ }
+
+ $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
+ }
+ } else {
+ $state = self::MESSAGE_MISSING;
+ }
+
+ $this->messages[] = [
+ 'locale' => $locale,
+ 'domain' => $domain,
+ 'id' => $id,
+ 'translation' => $translation,
+ 'parameters' => $parameters,
+ 'state' => $state,
+ 'transChoiceNumber' => isset($parameters['%count%']) && is_numeric($parameters['%count%']) ? $parameters['%count%'] : null,
+ ];
+ }
+}
diff --git a/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php b/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php
new file mode 100755
index 0000000..930f36d
--- /dev/null
+++ b/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Adds tagged translation.formatter services to translation writer.
+ */
+class TranslationDumperPass implements CompilerPassInterface
+{
+ private $writerServiceId;
+ private $dumperTag;
+
+ public function __construct(string $writerServiceId = 'translation.writer', string $dumperTag = 'translation.dumper')
+ {
+ $this->writerServiceId = $writerServiceId;
+ $this->dumperTag = $dumperTag;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->writerServiceId)) {
+ return;
+ }
+
+ $definition = $container->getDefinition($this->writerServiceId);
+
+ foreach ($container->findTaggedServiceIds($this->dumperTag, true) as $id => $attributes) {
+ $definition->addMethodCall('addDumper', [$attributes[0]['alias'], new Reference($id)]);
+ }
+ }
+}
diff --git a/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php b/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php
new file mode 100755
index 0000000..d08b2ba
--- /dev/null
+++ b/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\RuntimeException;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Adds tagged translation.extractor services to translation extractor.
+ */
+class TranslationExtractorPass implements CompilerPassInterface
+{
+ private $extractorServiceId;
+ private $extractorTag;
+
+ public function __construct(string $extractorServiceId = 'translation.extractor', string $extractorTag = 'translation.extractor')
+ {
+ $this->extractorServiceId = $extractorServiceId;
+ $this->extractorTag = $extractorTag;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->extractorServiceId)) {
+ return;
+ }
+
+ $definition = $container->getDefinition($this->extractorServiceId);
+
+ foreach ($container->findTaggedServiceIds($this->extractorTag, true) as $id => $attributes) {
+ if (!isset($attributes[0]['alias'])) {
+ throw new RuntimeException(sprintf('The alias for the tag "translation.extractor" of service "%s" must be set.', $id));
+ }
+
+ $definition->addMethodCall('addExtractor', [$attributes[0]['alias'], new Reference($id)]);
+ }
+ }
+}
diff --git a/vendor/symfony/translation/DependencyInjection/TranslatorPass.php b/vendor/symfony/translation/DependencyInjection/TranslatorPass.php
new file mode 100755
index 0000000..ed4a840
--- /dev/null
+++ b/vendor/symfony/translation/DependencyInjection/TranslatorPass.php
@@ -0,0 +1,89 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+class TranslatorPass implements CompilerPassInterface
+{
+ private $translatorServiceId;
+ private $readerServiceId;
+ private $loaderTag;
+ private $debugCommandServiceId;
+ private $updateCommandServiceId;
+
+ public function __construct(string $translatorServiceId = 'translator.default', string $readerServiceId = 'translation.reader', string $loaderTag = 'translation.loader', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_update')
+ {
+ $this->translatorServiceId = $translatorServiceId;
+ $this->readerServiceId = $readerServiceId;
+ $this->loaderTag = $loaderTag;
+ $this->debugCommandServiceId = $debugCommandServiceId;
+ $this->updateCommandServiceId = $updateCommandServiceId;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->translatorServiceId)) {
+ return;
+ }
+
+ $loaders = [];
+ $loaderRefs = [];
+ foreach ($container->findTaggedServiceIds($this->loaderTag, true) as $id => $attributes) {
+ $loaderRefs[$id] = new Reference($id);
+ $loaders[$id][] = $attributes[0]['alias'];
+ if (isset($attributes[0]['legacy-alias'])) {
+ $loaders[$id][] = $attributes[0]['legacy-alias'];
+ }
+ }
+
+ if ($container->hasDefinition($this->readerServiceId)) {
+ $definition = $container->getDefinition($this->readerServiceId);
+ foreach ($loaders as $id => $formats) {
+ foreach ($formats as $format) {
+ $definition->addMethodCall('addLoader', [$format, $loaderRefs[$id]]);
+ }
+ }
+ }
+
+ $container
+ ->findDefinition($this->translatorServiceId)
+ ->replaceArgument(0, ServiceLocatorTagPass::register($container, $loaderRefs))
+ ->replaceArgument(3, $loaders)
+ ;
+
+ if (!$container->hasParameter('twig.default_path')) {
+ return;
+ }
+
+ $paths = array_keys($container->getDefinition('twig.template_iterator')->getArgument(2));
+ if ($container->hasDefinition($this->debugCommandServiceId)) {
+ $definition = $container->getDefinition($this->debugCommandServiceId);
+ $definition->replaceArgument(4, $container->getParameter('twig.default_path'));
+
+ if (\count($definition->getArguments()) > 6) {
+ $definition->replaceArgument(6, $paths);
+ }
+ }
+ if ($container->hasDefinition($this->updateCommandServiceId)) {
+ $definition = $container->getDefinition($this->updateCommandServiceId);
+ $definition->replaceArgument(5, $container->getParameter('twig.default_path'));
+
+ if (\count($definition->getArguments()) > 7) {
+ $definition->replaceArgument(7, $paths);
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php b/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php
new file mode 100755
index 0000000..8ff2587
--- /dev/null
+++ b/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php
@@ -0,0 +1,144 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ServiceLocator;
+
+/**
+ * @author Yonel Ceruto <yonelceruto@gmail.com>
+ */
+class TranslatorPathsPass extends AbstractRecursivePass
+{
+ private $translatorServiceId;
+ private $debugCommandServiceId;
+ private $updateCommandServiceId;
+ private $resolverServiceId;
+ private $level = 0;
+ private $paths = [];
+ private $definitions = [];
+ private $controllers = [];
+
+ public function __construct(string $translatorServiceId = 'translator', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_update', string $resolverServiceId = 'argument_resolver.service')
+ {
+ $this->translatorServiceId = $translatorServiceId;
+ $this->debugCommandServiceId = $debugCommandServiceId;
+ $this->updateCommandServiceId = $updateCommandServiceId;
+ $this->resolverServiceId = $resolverServiceId;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->translatorServiceId)) {
+ return;
+ }
+
+ foreach ($this->findControllerArguments($container) as $controller => $argument) {
+ $id = substr($controller, 0, strpos($controller, ':') ?: \strlen($controller));
+ if ($container->hasDefinition($id)) {
+ list($locatorRef) = $argument->getValues();
+ $this->controllers[(string) $locatorRef][$container->getDefinition($id)->getClass()] = true;
+ }
+ }
+
+ try {
+ parent::process($container);
+
+ $paths = [];
+ foreach ($this->paths as $class => $_) {
+ if (($r = $container->getReflectionClass($class)) && !$r->isInterface()) {
+ $paths[] = $r->getFileName();
+ }
+ }
+ if ($paths) {
+ if ($container->hasDefinition($this->debugCommandServiceId)) {
+ $definition = $container->getDefinition($this->debugCommandServiceId);
+ $definition->replaceArgument(6, array_merge($definition->getArgument(6), $paths));
+ }
+ if ($container->hasDefinition($this->updateCommandServiceId)) {
+ $definition = $container->getDefinition($this->updateCommandServiceId);
+ $definition->replaceArgument(7, array_merge($definition->getArgument(7), $paths));
+ }
+ }
+ } finally {
+ $this->level = 0;
+ $this->paths = [];
+ $this->definitions = [];
+ }
+ }
+
+ protected function processValue($value, $isRoot = false)
+ {
+ if ($value instanceof Reference) {
+ if ((string) $value === $this->translatorServiceId) {
+ for ($i = $this->level - 1; $i >= 0; --$i) {
+ $class = $this->definitions[$i]->getClass();
+
+ if (ServiceLocator::class === $class) {
+ if (!isset($this->controllers[$this->currentId])) {
+ continue;
+ }
+ foreach ($this->controllers[$this->currentId] as $class => $_) {
+ $this->paths[$class] = true;
+ }
+ } else {
+ $this->paths[$class] = true;
+ }
+
+ break;
+ }
+ }
+
+ return $value;
+ }
+
+ if ($value instanceof Definition) {
+ $this->definitions[$this->level++] = $value;
+ $value = parent::processValue($value, $isRoot);
+ unset($this->definitions[--$this->level]);
+
+ return $value;
+ }
+
+ return parent::processValue($value, $isRoot);
+ }
+
+ private function findControllerArguments(ContainerBuilder $container): array
+ {
+ if ($container->hasDefinition($this->resolverServiceId)) {
+ $argument = $container->getDefinition($this->resolverServiceId)->getArgument(0);
+ if ($argument instanceof Reference) {
+ $argument = $container->getDefinition($argument);
+ }
+
+ return $argument->getArgument(0);
+ }
+
+ if ($container->hasDefinition('debug.'.$this->resolverServiceId)) {
+ $argument = $container->getDefinition('debug.'.$this->resolverServiceId)->getArgument(0);
+ if ($argument instanceof Reference) {
+ $argument = $container->getDefinition($argument);
+ }
+ $argument = $argument->getArgument(0);
+ if ($argument instanceof Reference) {
+ $argument = $container->getDefinition($argument);
+ }
+
+ return $argument->getArgument(0);
+ }
+
+ return [];
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/CsvFileDumper.php b/vendor/symfony/translation/Dumper/CsvFileDumper.php
new file mode 100755
index 0000000..bfa8db6
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/CsvFileDumper.php
@@ -0,0 +1,63 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * CsvFileDumper generates a csv formatted string representation of a message catalogue.
+ *
+ * @author Stealth35
+ */
+class CsvFileDumper extends FileDumper
+{
+ private $delimiter = ';';
+ private $enclosure = '"';
+
+ /**
+ * {@inheritdoc}
+ */
+ public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = [])
+ {
+ $handle = fopen('php://memory', 'r+b');
+
+ foreach ($messages->all($domain) as $source => $target) {
+ fputcsv($handle, [$source, $target], $this->delimiter, $this->enclosure);
+ }
+
+ rewind($handle);
+ $output = stream_get_contents($handle);
+ fclose($handle);
+
+ return $output;
+ }
+
+ /**
+ * Sets the delimiter and escape character for CSV.
+ *
+ * @param string $delimiter Delimiter character
+ * @param string $enclosure Enclosure character
+ */
+ public function setCsvControl($delimiter = ';', $enclosure = '"')
+ {
+ $this->delimiter = $delimiter;
+ $this->enclosure = $enclosure;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getExtension()
+ {
+ return 'csv';
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/DumperInterface.php b/vendor/symfony/translation/Dumper/DumperInterface.php
new file mode 100755
index 0000000..9965e80
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/DumperInterface.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * DumperInterface is the interface implemented by all translation dumpers.
+ * There is no common option.
+ *
+ * @author Michel Salib <michelsalib@hotmail.com>
+ */
+interface DumperInterface
+{
+ /**
+ * Dumps the message catalogue.
+ *
+ * @param MessageCatalogue $messages The message catalogue
+ * @param array $options Options that are used by the dumper
+ */
+ public function dump(MessageCatalogue $messages, $options = []);
+}
diff --git a/vendor/symfony/translation/Dumper/FileDumper.php b/vendor/symfony/translation/Dumper/FileDumper.php
new file mode 100755
index 0000000..8457ecc
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/FileDumper.php
@@ -0,0 +1,132 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\RuntimeException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * FileDumper is an implementation of DumperInterface that dump a message catalogue to file(s).
+ *
+ * Options:
+ * - path (mandatory): the directory where the files should be saved
+ *
+ * @author Michel Salib <michelsalib@hotmail.com>
+ */
+abstract class FileDumper implements DumperInterface
+{
+ /**
+ * A template for the relative paths to files.
+ *
+ * @var string
+ */
+ protected $relativePathTemplate = '%domain%.%locale%.%extension%';
+
+ /**
+ * Sets the template for the relative paths to files.
+ *
+ * @param string $relativePathTemplate A template for the relative paths to files
+ */
+ public function setRelativePathTemplate($relativePathTemplate)
+ {
+ $this->relativePathTemplate = $relativePathTemplate;
+ }
+
+ /**
+ * Sets backup flag.
+ *
+ * @param bool $backup
+ *
+ * @deprecated since Symfony 4.1
+ */
+ public function setBackup($backup)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1.', __METHOD__), E_USER_DEPRECATED);
+
+ if (false !== $backup) {
+ throw new \LogicException('The backup feature is no longer supported.');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dump(MessageCatalogue $messages, $options = [])
+ {
+ if (!\array_key_exists('path', $options)) {
+ throw new InvalidArgumentException('The file dumper needs a path option.');
+ }
+
+ // save a file for each domain
+ foreach ($messages->getDomains() as $domain) {
+ $fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale());
+ if (!file_exists($fullpath)) {
+ $directory = \dirname($fullpath);
+ if (!file_exists($directory) && !@mkdir($directory, 0777, true)) {
+ throw new RuntimeException(sprintf('Unable to create directory "%s".', $directory));
+ }
+ }
+
+ $intlDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX;
+ $intlMessages = $messages->all($intlDomain);
+
+ if ($intlMessages) {
+ $intlPath = $options['path'].'/'.$this->getRelativePath($intlDomain, $messages->getLocale());
+ file_put_contents($intlPath, $this->formatCatalogue($messages, $intlDomain, $options));
+
+ $messages->replace([], $intlDomain);
+
+ try {
+ if ($messages->all($domain)) {
+ file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options));
+ }
+ continue;
+ } finally {
+ $messages->replace($intlMessages, $intlDomain);
+ }
+ }
+
+ file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options));
+ }
+ }
+
+ /**
+ * Transforms a domain of a message catalogue to its string representation.
+ *
+ * @param MessageCatalogue $messages
+ * @param string $domain
+ * @param array $options
+ *
+ * @return string representation
+ */
+ abstract public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = []);
+
+ /**
+ * Gets the file extension of the dumper.
+ *
+ * @return string file extension
+ */
+ abstract protected function getExtension();
+
+ /**
+ * Gets the relative file path using the template.
+ */
+ private function getRelativePath(string $domain, string $locale): string
+ {
+ return strtr($this->relativePathTemplate, [
+ '%domain%' => $domain,
+ '%locale%' => $locale,
+ '%extension%' => $this->getExtension(),
+ ]);
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/IcuResFileDumper.php b/vendor/symfony/translation/Dumper/IcuResFileDumper.php
new file mode 100755
index 0000000..48d0bef
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/IcuResFileDumper.php
@@ -0,0 +1,106 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * IcuResDumper generates an ICU ResourceBundle formatted string representation of a message catalogue.
+ *
+ * @author Stealth35
+ */
+class IcuResFileDumper extends FileDumper
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $relativePathTemplate = '%domain%/%locale%.%extension%';
+
+ /**
+ * {@inheritdoc}
+ */
+ public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = [])
+ {
+ $data = $indexes = $resources = '';
+
+ foreach ($messages->all($domain) as $source => $target) {
+ $indexes .= pack('v', \strlen($data) + 28);
+ $data .= $source."\0";
+ }
+
+ $data .= $this->writePadding($data);
+
+ $keyTop = $this->getPosition($data);
+
+ foreach ($messages->all($domain) as $source => $target) {
+ $resources .= pack('V', $this->getPosition($data));
+
+ $data .= pack('V', \strlen($target))
+ .mb_convert_encoding($target."\0", 'UTF-16LE', 'UTF-8')
+ .$this->writePadding($data)
+ ;
+ }
+
+ $resOffset = $this->getPosition($data);
+
+ $data .= pack('v', \count($messages->all($domain)))
+ .$indexes
+ .$this->writePadding($data)
+ .$resources
+ ;
+
+ $bundleTop = $this->getPosition($data);
+
+ $root = pack('V7',
+ $resOffset + (2 << 28), // Resource Offset + Resource Type
+ 6, // Index length
+ $keyTop, // Index keys top
+ $bundleTop, // Index resources top
+ $bundleTop, // Index bundle top
+ \count($messages->all($domain)), // Index max table length
+ 0 // Index attributes
+ );
+
+ $header = pack('vC2v4C12@32',
+ 32, // Header size
+ 0xDA, 0x27, // Magic number 1 and 2
+ 20, 0, 0, 2, // Rest of the header, ..., Size of a char
+ 0x52, 0x65, 0x73, 0x42, // Data format identifier
+ 1, 2, 0, 0, // Data version
+ 1, 4, 0, 0 // Unicode version
+ );
+
+ return $header.$root.$data;
+ }
+
+ private function writePadding($data)
+ {
+ $padding = \strlen($data) % 4;
+
+ if ($padding) {
+ return str_repeat("\xAA", 4 - $padding);
+ }
+ }
+
+ private function getPosition($data)
+ {
+ return (\strlen($data) + 28) / 4;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getExtension()
+ {
+ return 'res';
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/IniFileDumper.php b/vendor/symfony/translation/Dumper/IniFileDumper.php
new file mode 100755
index 0000000..45ff961
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/IniFileDumper.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * IniFileDumper generates an ini formatted string representation of a message catalogue.
+ *
+ * @author Stealth35
+ */
+class IniFileDumper extends FileDumper
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = [])
+ {
+ $output = '';
+
+ foreach ($messages->all($domain) as $source => $target) {
+ $escapeTarget = str_replace('"', '\"', $target);
+ $output .= $source.'="'.$escapeTarget."\"\n";
+ }
+
+ return $output;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getExtension()
+ {
+ return 'ini';
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/JsonFileDumper.php b/vendor/symfony/translation/Dumper/JsonFileDumper.php
new file mode 100755
index 0000000..2af8231
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/JsonFileDumper.php
@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * JsonFileDumper generates an json formatted string representation of a message catalogue.
+ *
+ * @author singles
+ */
+class JsonFileDumper extends FileDumper
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = [])
+ {
+ $flags = $options['json_encoding'] ?? JSON_PRETTY_PRINT;
+
+ return json_encode($messages->all($domain), $flags);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getExtension()
+ {
+ return 'json';
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/MoFileDumper.php b/vendor/symfony/translation/Dumper/MoFileDumper.php
new file mode 100755
index 0000000..27be16d
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/MoFileDumper.php
@@ -0,0 +1,82 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\Loader\MoFileLoader;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * MoFileDumper generates a gettext formatted string representation of a message catalogue.
+ *
+ * @author Stealth35
+ */
+class MoFileDumper extends FileDumper
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = [])
+ {
+ $sources = $targets = $sourceOffsets = $targetOffsets = '';
+ $offsets = [];
+ $size = 0;
+
+ foreach ($messages->all($domain) as $source => $target) {
+ $offsets[] = array_map('strlen', [$sources, $source, $targets, $target]);
+ $sources .= "\0".$source;
+ $targets .= "\0".$target;
+ ++$size;
+ }
+
+ $header = [
+ 'magicNumber' => MoFileLoader::MO_LITTLE_ENDIAN_MAGIC,
+ 'formatRevision' => 0,
+ 'count' => $size,
+ 'offsetId' => MoFileLoader::MO_HEADER_SIZE,
+ 'offsetTranslated' => MoFileLoader::MO_HEADER_SIZE + (8 * $size),
+ 'sizeHashes' => 0,
+ 'offsetHashes' => MoFileLoader::MO_HEADER_SIZE + (16 * $size),
+ ];
+
+ $sourcesSize = \strlen($sources);
+ $sourcesStart = $header['offsetHashes'] + 1;
+
+ foreach ($offsets as $offset) {
+ $sourceOffsets .= $this->writeLong($offset[1])
+ .$this->writeLong($offset[0] + $sourcesStart);
+ $targetOffsets .= $this->writeLong($offset[3])
+ .$this->writeLong($offset[2] + $sourcesStart + $sourcesSize);
+ }
+
+ $output = implode('', array_map([$this, 'writeLong'], $header))
+ .$sourceOffsets
+ .$targetOffsets
+ .$sources
+ .$targets
+ ;
+
+ return $output;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getExtension()
+ {
+ return 'mo';
+ }
+
+ private function writeLong($str)
+ {
+ return pack('V*', $str);
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/PhpFileDumper.php b/vendor/symfony/translation/Dumper/PhpFileDumper.php
new file mode 100755
index 0000000..e77afc2
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/PhpFileDumper.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * PhpFileDumper generates PHP files from a message catalogue.
+ *
+ * @author Michel Salib <michelsalib@hotmail.com>
+ */
+class PhpFileDumper extends FileDumper
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = [])
+ {
+ return "<?php\n\nreturn ".var_export($messages->all($domain), true).";\n";
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getExtension()
+ {
+ return 'php';
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/PoFileDumper.php b/vendor/symfony/translation/Dumper/PoFileDumper.php
new file mode 100755
index 0000000..5f60086
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/PoFileDumper.php
@@ -0,0 +1,84 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * PoFileDumper generates a gettext formatted string representation of a message catalogue.
+ *
+ * @author Stealth35
+ */
+class PoFileDumper extends FileDumper
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = [])
+ {
+ $output = 'msgid ""'."\n";
+ $output .= 'msgstr ""'."\n";
+ $output .= '"Content-Type: text/plain; charset=UTF-8\n"'."\n";
+ $output .= '"Content-Transfer-Encoding: 8bit\n"'."\n";
+ $output .= '"Language: '.$messages->getLocale().'\n"'."\n";
+ $output .= "\n";
+
+ $newLine = false;
+ foreach ($messages->all($domain) as $source => $target) {
+ if ($newLine) {
+ $output .= "\n";
+ } else {
+ $newLine = true;
+ }
+ $metadata = $messages->getMetadata($source, $domain);
+
+ if (isset($metadata['comments'])) {
+ $output .= $this->formatComments($metadata['comments']);
+ }
+ if (isset($metadata['flags'])) {
+ $output .= $this->formatComments(implode(',', (array) $metadata['flags']), ',');
+ }
+ if (isset($metadata['sources'])) {
+ $output .= $this->formatComments(implode(' ', (array) $metadata['sources']), ':');
+ }
+
+ $output .= sprintf('msgid "%s"'."\n", $this->escape($source));
+ $output .= sprintf('msgstr "%s"'."\n", $this->escape($target));
+ }
+
+ return $output;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getExtension()
+ {
+ return 'po';
+ }
+
+ private function escape($str)
+ {
+ return addcslashes($str, "\0..\37\42\134");
+ }
+
+ private function formatComments($comments, string $prefix = ''): ?string
+ {
+ $output = null;
+
+ foreach ((array) $comments as $comment) {
+ $output .= sprintf('#%s %s'."\n", $prefix, $comment);
+ }
+
+ return $output;
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/QtFileDumper.php b/vendor/symfony/translation/Dumper/QtFileDumper.php
new file mode 100755
index 0000000..79a64b2
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/QtFileDumper.php
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * QtFileDumper generates ts files from a message catalogue.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class QtFileDumper extends FileDumper
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = [])
+ {
+ $dom = new \DOMDocument('1.0', 'utf-8');
+ $dom->formatOutput = true;
+ $ts = $dom->appendChild($dom->createElement('TS'));
+ $context = $ts->appendChild($dom->createElement('context'));
+ $context->appendChild($dom->createElement('name', $domain));
+
+ foreach ($messages->all($domain) as $source => $target) {
+ $message = $context->appendChild($dom->createElement('message'));
+ $metadata = $messages->getMetadata($source, $domain);
+ if (isset($metadata['sources'])) {
+ foreach ((array) $metadata['sources'] as $location) {
+ $loc = explode(':', $location, 2);
+ $location = $message->appendChild($dom->createElement('location'));
+ $location->setAttribute('filename', $loc[0]);
+ if (isset($loc[1])) {
+ $location->setAttribute('line', $loc[1]);
+ }
+ }
+ }
+ $message->appendChild($dom->createElement('source', $source));
+ $message->appendChild($dom->createElement('translation', $target));
+ }
+
+ return $dom->saveXML();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getExtension()
+ {
+ return 'ts';
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/XliffFileDumper.php b/vendor/symfony/translation/Dumper/XliffFileDumper.php
new file mode 100755
index 0000000..8d2694f
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/XliffFileDumper.php
@@ -0,0 +1,209 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * XliffFileDumper generates xliff files from a message catalogue.
+ *
+ * @author Michel Salib <michelsalib@hotmail.com>
+ */
+class XliffFileDumper extends FileDumper
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = [])
+ {
+ $xliffVersion = '1.2';
+ if (\array_key_exists('xliff_version', $options)) {
+ $xliffVersion = $options['xliff_version'];
+ }
+
+ if (\array_key_exists('default_locale', $options)) {
+ $defaultLocale = $options['default_locale'];
+ } else {
+ $defaultLocale = \Locale::getDefault();
+ }
+
+ if ('1.2' === $xliffVersion) {
+ return $this->dumpXliff1($defaultLocale, $messages, $domain, $options);
+ }
+ if ('2.0' === $xliffVersion) {
+ return $this->dumpXliff2($defaultLocale, $messages, $domain, $options);
+ }
+
+ throw new InvalidArgumentException(sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getExtension()
+ {
+ return 'xlf';
+ }
+
+ private function dumpXliff1($defaultLocale, MessageCatalogue $messages, $domain, array $options = [])
+ {
+ $toolInfo = ['tool-id' => 'symfony', 'tool-name' => 'Symfony'];
+ if (\array_key_exists('tool_info', $options)) {
+ $toolInfo = array_merge($toolInfo, $options['tool_info']);
+ }
+
+ $dom = new \DOMDocument('1.0', 'utf-8');
+ $dom->formatOutput = true;
+
+ $xliff = $dom->appendChild($dom->createElement('xliff'));
+ $xliff->setAttribute('version', '1.2');
+ $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:1.2');
+
+ $xliffFile = $xliff->appendChild($dom->createElement('file'));
+ $xliffFile->setAttribute('source-language', str_replace('_', '-', $defaultLocale));
+ $xliffFile->setAttribute('target-language', str_replace('_', '-', $messages->getLocale()));
+ $xliffFile->setAttribute('datatype', 'plaintext');
+ $xliffFile->setAttribute('original', 'file.ext');
+
+ $xliffHead = $xliffFile->appendChild($dom->createElement('header'));
+ $xliffTool = $xliffHead->appendChild($dom->createElement('tool'));
+ foreach ($toolInfo as $id => $value) {
+ $xliffTool->setAttribute($id, $value);
+ }
+
+ $xliffBody = $xliffFile->appendChild($dom->createElement('body'));
+ foreach ($messages->all($domain) as $source => $target) {
+ $translation = $dom->createElement('trans-unit');
+
+ $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._'));
+ $translation->setAttribute('resname', $source);
+
+ $s = $translation->appendChild($dom->createElement('source'));
+ $s->appendChild($dom->createTextNode($source));
+
+ // Does the target contain characters requiring a CDATA section?
+ $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target);
+
+ $targetElement = $dom->createElement('target');
+ $metadata = $messages->getMetadata($source, $domain);
+ if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) {
+ foreach ($metadata['target-attributes'] as $name => $value) {
+ $targetElement->setAttribute($name, $value);
+ }
+ }
+ $t = $translation->appendChild($targetElement);
+ $t->appendChild($text);
+
+ if ($this->hasMetadataArrayInfo('notes', $metadata)) {
+ foreach ($metadata['notes'] as $note) {
+ if (!isset($note['content'])) {
+ continue;
+ }
+
+ $n = $translation->appendChild($dom->createElement('note'));
+ $n->appendChild($dom->createTextNode($note['content']));
+
+ if (isset($note['priority'])) {
+ $n->setAttribute('priority', $note['priority']);
+ }
+
+ if (isset($note['from'])) {
+ $n->setAttribute('from', $note['from']);
+ }
+ }
+ }
+
+ $xliffBody->appendChild($translation);
+ }
+
+ return $dom->saveXML();
+ }
+
+ private function dumpXliff2($defaultLocale, MessageCatalogue $messages, $domain, array $options = [])
+ {
+ $dom = new \DOMDocument('1.0', 'utf-8');
+ $dom->formatOutput = true;
+
+ $xliff = $dom->appendChild($dom->createElement('xliff'));
+ $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0');
+ $xliff->setAttribute('version', '2.0');
+ $xliff->setAttribute('srcLang', str_replace('_', '-', $defaultLocale));
+ $xliff->setAttribute('trgLang', str_replace('_', '-', $messages->getLocale()));
+
+ $xliffFile = $xliff->appendChild($dom->createElement('file'));
+ if (MessageCatalogue::INTL_DOMAIN_SUFFIX === substr($domain, -($suffixLength = \strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX)))) {
+ $xliffFile->setAttribute('id', substr($domain, 0, -$suffixLength).'.'.$messages->getLocale());
+ } else {
+ $xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale());
+ }
+
+ foreach ($messages->all($domain) as $source => $target) {
+ $translation = $dom->createElement('unit');
+ $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._'));
+ $name = $source;
+ if (\strlen($source) > 80) {
+ $name = substr(md5($source), -7);
+ }
+ $translation->setAttribute('name', $name);
+ $metadata = $messages->getMetadata($source, $domain);
+
+ // Add notes section
+ if ($this->hasMetadataArrayInfo('notes', $metadata)) {
+ $notesElement = $dom->createElement('notes');
+ foreach ($metadata['notes'] as $note) {
+ $n = $dom->createElement('note');
+ $n->appendChild($dom->createTextNode(isset($note['content']) ? $note['content'] : ''));
+ unset($note['content']);
+
+ foreach ($note as $name => $value) {
+ $n->setAttribute($name, $value);
+ }
+ $notesElement->appendChild($n);
+ }
+ $translation->appendChild($notesElement);
+ }
+
+ $segment = $translation->appendChild($dom->createElement('segment'));
+
+ $s = $segment->appendChild($dom->createElement('source'));
+ $s->appendChild($dom->createTextNode($source));
+
+ // Does the target contain characters requiring a CDATA section?
+ $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target);
+
+ $targetElement = $dom->createElement('target');
+ if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) {
+ foreach ($metadata['target-attributes'] as $name => $value) {
+ $targetElement->setAttribute($name, $value);
+ }
+ }
+ $t = $segment->appendChild($targetElement);
+ $t->appendChild($text);
+
+ $xliffFile->appendChild($translation);
+ }
+
+ return $dom->saveXML();
+ }
+
+ /**
+ * @param string $key
+ * @param array|null $metadata
+ *
+ * @return bool
+ */
+ private function hasMetadataArrayInfo($key, $metadata = null)
+ {
+ return null !== $metadata && \array_key_exists($key, $metadata) && ($metadata[$key] instanceof \Traversable || \is_array($metadata[$key]));
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/YamlFileDumper.php b/vendor/symfony/translation/Dumper/YamlFileDumper.php
new file mode 100755
index 0000000..d6e4af8
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/YamlFileDumper.php
@@ -0,0 +1,62 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\Exception\LogicException;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\Util\ArrayConverter;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * YamlFileDumper generates yaml files from a message catalogue.
+ *
+ * @author Michel Salib <michelsalib@hotmail.com>
+ */
+class YamlFileDumper extends FileDumper
+{
+ private $extension;
+
+ public function __construct(string $extension = 'yml')
+ {
+ $this->extension = $extension;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = [])
+ {
+ if (!class_exists('Symfony\Component\Yaml\Yaml')) {
+ throw new LogicException('Dumping translations in the YAML format requires the Symfony Yaml component.');
+ }
+
+ $data = $messages->all($domain);
+
+ if (isset($options['as_tree']) && $options['as_tree']) {
+ $data = ArrayConverter::expandToTree($data);
+ }
+
+ if (isset($options['inline']) && ($inline = (int) $options['inline']) > 0) {
+ return Yaml::dump($data, $inline);
+ }
+
+ return Yaml::dump($data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getExtension()
+ {
+ return $this->extension;
+ }
+}
diff --git a/vendor/symfony/translation/Exception/ExceptionInterface.php b/vendor/symfony/translation/Exception/ExceptionInterface.php
new file mode 100755
index 0000000..8f9c54e
--- /dev/null
+++ b/vendor/symfony/translation/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * Exception interface for all exceptions thrown by the component.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/vendor/symfony/translation/Exception/InvalidArgumentException.php b/vendor/symfony/translation/Exception/InvalidArgumentException.php
new file mode 100755
index 0000000..90d0669
--- /dev/null
+++ b/vendor/symfony/translation/Exception/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * Base InvalidArgumentException for the Translation component.
+ *
+ * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/translation/Exception/InvalidResourceException.php b/vendor/symfony/translation/Exception/InvalidResourceException.php
new file mode 100755
index 0000000..cf07943
--- /dev/null
+++ b/vendor/symfony/translation/Exception/InvalidResourceException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * Thrown when a resource cannot be loaded.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class InvalidResourceException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/translation/Exception/LogicException.php b/vendor/symfony/translation/Exception/LogicException.php
new file mode 100755
index 0000000..9019c7e
--- /dev/null
+++ b/vendor/symfony/translation/Exception/LogicException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * Base LogicException for Translation component.
+ *
+ * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/translation/Exception/NotFoundResourceException.php b/vendor/symfony/translation/Exception/NotFoundResourceException.php
new file mode 100755
index 0000000..cff73ae
--- /dev/null
+++ b/vendor/symfony/translation/Exception/NotFoundResourceException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * Thrown when a resource does not exist.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class NotFoundResourceException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/translation/Exception/RuntimeException.php b/vendor/symfony/translation/Exception/RuntimeException.php
new file mode 100755
index 0000000..dcd7940
--- /dev/null
+++ b/vendor/symfony/translation/Exception/RuntimeException.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * Base RuntimeException for the Translation component.
+ *
+ * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/translation/Extractor/AbstractFileExtractor.php b/vendor/symfony/translation/Extractor/AbstractFileExtractor.php
new file mode 100755
index 0000000..8d722da
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/AbstractFileExtractor.php
@@ -0,0 +1,80 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+
+/**
+ * Base class used by classes that extract translation messages from files.
+ *
+ * @author Marcos D. Sánchez <marcosdsanchez@gmail.com>
+ */
+abstract class AbstractFileExtractor
+{
+ /**
+ * @param string|array $resource Files, a file or a directory
+ *
+ * @return array
+ */
+ protected function extractFiles($resource)
+ {
+ if (\is_array($resource) || $resource instanceof \Traversable) {
+ $files = [];
+ foreach ($resource as $file) {
+ if ($this->canBeExtracted($file)) {
+ $files[] = $this->toSplFileInfo($file);
+ }
+ }
+ } elseif (is_file($resource)) {
+ $files = $this->canBeExtracted($resource) ? [$this->toSplFileInfo($resource)] : [];
+ } else {
+ $files = $this->extractFromDirectory($resource);
+ }
+
+ return $files;
+ }
+
+ private function toSplFileInfo(string $file): \SplFileInfo
+ {
+ return new \SplFileInfo($file);
+ }
+
+ /**
+ * @param string $file
+ *
+ * @return bool
+ *
+ * @throws InvalidArgumentException
+ */
+ protected function isFile($file)
+ {
+ if (!is_file($file)) {
+ throw new InvalidArgumentException(sprintf('The "%s" file does not exist.', $file));
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $file
+ *
+ * @return bool
+ */
+ abstract protected function canBeExtracted($file);
+
+ /**
+ * @param string|array $resource Files, a file or a directory
+ *
+ * @return array files to be extracted
+ */
+ abstract protected function extractFromDirectory($resource);
+}
diff --git a/vendor/symfony/translation/Extractor/ChainExtractor.php b/vendor/symfony/translation/Extractor/ChainExtractor.php
new file mode 100755
index 0000000..69ee2df
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/ChainExtractor.php
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * ChainExtractor extracts translation messages from template files.
+ *
+ * @author Michel Salib <michelsalib@hotmail.com>
+ */
+class ChainExtractor implements ExtractorInterface
+{
+ /**
+ * The extractors.
+ *
+ * @var ExtractorInterface[]
+ */
+ private $extractors = [];
+
+ /**
+ * Adds a loader to the translation extractor.
+ *
+ * @param string $format The format of the loader
+ * @param ExtractorInterface $extractor The loader
+ */
+ public function addExtractor($format, ExtractorInterface $extractor)
+ {
+ $this->extractors[$format] = $extractor;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setPrefix($prefix)
+ {
+ foreach ($this->extractors as $extractor) {
+ $extractor->setPrefix($prefix);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function extract($directory, MessageCatalogue $catalogue)
+ {
+ foreach ($this->extractors as $extractor) {
+ $extractor->extract($directory, $catalogue);
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Extractor/ExtractorInterface.php b/vendor/symfony/translation/Extractor/ExtractorInterface.php
new file mode 100755
index 0000000..b4534fa
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/ExtractorInterface.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * Extracts translation messages from a directory or files to the catalogue.
+ * New found messages are injected to the catalogue using the prefix.
+ *
+ * @author Michel Salib <michelsalib@hotmail.com>
+ */
+interface ExtractorInterface
+{
+ /**
+ * Extracts translation messages from files, a file or a directory to the catalogue.
+ *
+ * @param string|array $resource Files, a file or a directory
+ * @param MessageCatalogue $catalogue The catalogue
+ */
+ public function extract($resource, MessageCatalogue $catalogue);
+
+ /**
+ * Sets the prefix that should be used for new found messages.
+ *
+ * @param string $prefix The prefix
+ */
+ public function setPrefix($prefix);
+}
diff --git a/vendor/symfony/translation/Extractor/PhpExtractor.php b/vendor/symfony/translation/Extractor/PhpExtractor.php
new file mode 100755
index 0000000..3e1a152
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/PhpExtractor.php
@@ -0,0 +1,278 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor;
+
+use Symfony\Component\Finder\Finder;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * PhpExtractor extracts translation messages from a PHP template.
+ *
+ * @author Michel Salib <michelsalib@hotmail.com>
+ */
+class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
+{
+ const MESSAGE_TOKEN = 300;
+ const METHOD_ARGUMENTS_TOKEN = 1000;
+ const DOMAIN_TOKEN = 1001;
+
+ /**
+ * Prefix for new found message.
+ *
+ * @var string
+ */
+ private $prefix = '';
+
+ /**
+ * The sequence that captures translation messages.
+ *
+ * @var array
+ */
+ protected $sequences = [
+ [
+ '->',
+ 'trans',
+ '(',
+ self::MESSAGE_TOKEN,
+ ',',
+ self::METHOD_ARGUMENTS_TOKEN,
+ ',',
+ self::DOMAIN_TOKEN,
+ ],
+ [
+ '->',
+ 'transChoice',
+ '(',
+ self::MESSAGE_TOKEN,
+ ',',
+ self::METHOD_ARGUMENTS_TOKEN,
+ ',',
+ self::METHOD_ARGUMENTS_TOKEN,
+ ',',
+ self::DOMAIN_TOKEN,
+ ],
+ [
+ '->',
+ 'trans',
+ '(',
+ self::MESSAGE_TOKEN,
+ ],
+ [
+ '->',
+ 'transChoice',
+ '(',
+ self::MESSAGE_TOKEN,
+ ],
+ ];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function extract($resource, MessageCatalogue $catalog)
+ {
+ $files = $this->extractFiles($resource);
+ foreach ($files as $file) {
+ $this->parseTokens(token_get_all(file_get_contents($file)), $catalog, $file);
+
+ gc_mem_caches();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setPrefix($prefix)
+ {
+ $this->prefix = $prefix;
+ }
+
+ /**
+ * Normalizes a token.
+ *
+ * @param mixed $token
+ *
+ * @return string
+ */
+ protected function normalizeToken($token)
+ {
+ if (isset($token[1]) && 'b"' !== $token) {
+ return $token[1];
+ }
+
+ return $token;
+ }
+
+ /**
+ * Seeks to a non-whitespace token.
+ */
+ private function seekToNextRelevantToken(\Iterator $tokenIterator)
+ {
+ for (; $tokenIterator->valid(); $tokenIterator->next()) {
+ $t = $tokenIterator->current();
+ if (T_WHITESPACE !== $t[0]) {
+ break;
+ }
+ }
+ }
+
+ private function skipMethodArgument(\Iterator $tokenIterator)
+ {
+ $openBraces = 0;
+
+ for (; $tokenIterator->valid(); $tokenIterator->next()) {
+ $t = $tokenIterator->current();
+
+ if ('[' === $t[0] || '(' === $t[0]) {
+ ++$openBraces;
+ }
+
+ if (']' === $t[0] || ')' === $t[0]) {
+ --$openBraces;
+ }
+
+ if ((0 === $openBraces && ',' === $t[0]) || (-1 === $openBraces && ')' === $t[0])) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Extracts the message from the iterator while the tokens
+ * match allowed message tokens.
+ */
+ private function getValue(\Iterator $tokenIterator)
+ {
+ $message = '';
+ $docToken = '';
+ $docPart = '';
+
+ for (; $tokenIterator->valid(); $tokenIterator->next()) {
+ $t = $tokenIterator->current();
+ if ('.' === $t) {
+ // Concatenate with next token
+ continue;
+ }
+ if (!isset($t[1])) {
+ break;
+ }
+
+ switch ($t[0]) {
+ case T_START_HEREDOC:
+ $docToken = $t[1];
+ break;
+ case T_ENCAPSED_AND_WHITESPACE:
+ case T_CONSTANT_ENCAPSED_STRING:
+ if ('' === $docToken) {
+ $message .= PhpStringTokenParser::parse($t[1]);
+ } else {
+ $docPart = $t[1];
+ }
+ break;
+ case T_END_HEREDOC:
+ $message .= PhpStringTokenParser::parseDocString($docToken, $docPart);
+ $docToken = '';
+ $docPart = '';
+ break;
+ case T_WHITESPACE:
+ break;
+ default:
+ break 2;
+ }
+ }
+
+ return $message;
+ }
+
+ /**
+ * Extracts trans message from PHP tokens.
+ *
+ * @param array $tokens
+ * @param MessageCatalogue $catalog
+ * @param string $filename
+ */
+ protected function parseTokens($tokens, MessageCatalogue $catalog/*, string $filename*/)
+ {
+ if (\func_num_args() < 3 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface) {
+ @trigger_error(sprintf('The "%s()" method will have a new "string $filename" argument in version 5.0, not defining it is deprecated since Symfony 4.3.', __METHOD__), E_USER_DEPRECATED);
+ }
+ $filename = 2 < \func_num_args() ? func_get_arg(2) : '';
+
+ $tokenIterator = new \ArrayIterator($tokens);
+
+ for ($key = 0; $key < $tokenIterator->count(); ++$key) {
+ foreach ($this->sequences as $sequence) {
+ $message = '';
+ $domain = 'messages';
+ $tokenIterator->seek($key);
+
+ foreach ($sequence as $sequenceKey => $item) {
+ $this->seekToNextRelevantToken($tokenIterator);
+
+ if ($this->normalizeToken($tokenIterator->current()) === $item) {
+ $tokenIterator->next();
+ continue;
+ } elseif (self::MESSAGE_TOKEN === $item) {
+ $message = $this->getValue($tokenIterator);
+
+ if (\count($sequence) === ($sequenceKey + 1)) {
+ break;
+ }
+ } elseif (self::METHOD_ARGUMENTS_TOKEN === $item) {
+ $this->skipMethodArgument($tokenIterator);
+ } elseif (self::DOMAIN_TOKEN === $item) {
+ $domainToken = $this->getValue($tokenIterator);
+ if ('' !== $domainToken) {
+ $domain = $domainToken;
+ }
+
+ break;
+ } else {
+ break;
+ }
+ }
+
+ if ($message) {
+ $catalog->set($message, $this->prefix.$message, $domain);
+ $metadata = $catalog->getMetadata($message, $domain) ?? [];
+ $normalizedFilename = preg_replace('{[\\\\/]+}', '/', $filename);
+ $metadata['sources'][] = $normalizedFilename.':'.$tokens[$key][2];
+ $catalog->setMetadata($message, $metadata, $domain);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param string $file
+ *
+ * @return bool
+ *
+ * @throws \InvalidArgumentException
+ */
+ protected function canBeExtracted($file)
+ {
+ return $this->isFile($file) && 'php' === pathinfo($file, PATHINFO_EXTENSION);
+ }
+
+ /**
+ * @param string|array $directory
+ *
+ * @return array
+ */
+ protected function extractFromDirectory($directory)
+ {
+ $finder = new Finder();
+
+ return $finder->files()->name('*.php')->in($directory);
+ }
+}
diff --git a/vendor/symfony/translation/Extractor/PhpStringTokenParser.php b/vendor/symfony/translation/Extractor/PhpStringTokenParser.php
new file mode 100755
index 0000000..8a8ccb1
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/PhpStringTokenParser.php
@@ -0,0 +1,142 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor;
+
+/*
+ * The following is derived from code at http://github.com/nikic/PHP-Parser
+ *
+ * Copyright (c) 2011 by Nikita Popov
+ *
+ * Some rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * * The names of the contributors may not be used to endorse or
+ * promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+class PhpStringTokenParser
+{
+ protected static $replacements = [
+ '\\' => '\\',
+ '$' => '$',
+ 'n' => "\n",
+ 'r' => "\r",
+ 't' => "\t",
+ 'f' => "\f",
+ 'v' => "\v",
+ 'e' => "\x1B",
+ ];
+
+ /**
+ * Parses a string token.
+ *
+ * @param string $str String token content
+ *
+ * @return string The parsed string
+ */
+ public static function parse($str)
+ {
+ $bLength = 0;
+ if ('b' === $str[0]) {
+ $bLength = 1;
+ }
+
+ if ('\'' === $str[$bLength]) {
+ return str_replace(
+ ['\\\\', '\\\''],
+ ['\\', '\''],
+ substr($str, $bLength + 1, -1)
+ );
+ } else {
+ return self::parseEscapeSequences(substr($str, $bLength + 1, -1), '"');
+ }
+ }
+
+ /**
+ * Parses escape sequences in strings (all string types apart from single quoted).
+ *
+ * @param string $str String without quotes
+ * @param string|null $quote Quote type
+ *
+ * @return string String with escape sequences parsed
+ */
+ public static function parseEscapeSequences($str, $quote)
+ {
+ if (null !== $quote) {
+ $str = str_replace('\\'.$quote, $quote, $str);
+ }
+
+ return preg_replace_callback(
+ '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~',
+ [__CLASS__, 'parseCallback'],
+ $str
+ );
+ }
+
+ private static function parseCallback($matches)
+ {
+ $str = $matches[1];
+
+ if (isset(self::$replacements[$str])) {
+ return self::$replacements[$str];
+ } elseif ('x' === $str[0] || 'X' === $str[0]) {
+ return \chr(hexdec($str));
+ } else {
+ return \chr(octdec($str));
+ }
+ }
+
+ /**
+ * Parses a constant doc string.
+ *
+ * @param string $startToken Doc string start token content (<<<SMTHG)
+ * @param string $str String token content
+ *
+ * @return string Parsed string
+ */
+ public static function parseDocString($startToken, $str)
+ {
+ // strip last newline (thanks tokenizer for sticking it into the string!)
+ $str = preg_replace('~(\r\n|\n|\r)$~', '', $str);
+
+ // nowdoc string
+ if (false !== strpos($startToken, '\'')) {
+ return $str;
+ }
+
+ return self::parseEscapeSequences($str, null);
+ }
+}
diff --git a/vendor/symfony/translation/Formatter/ChoiceMessageFormatterInterface.php b/vendor/symfony/translation/Formatter/ChoiceMessageFormatterInterface.php
new file mode 100755
index 0000000..621dbb2
--- /dev/null
+++ b/vendor/symfony/translation/Formatter/ChoiceMessageFormatterInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Formatter;
+
+/**
+ * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
+ *
+ * @deprecated since Symfony 4.2, use MessageFormatterInterface::format() with a %count% parameter instead
+ */
+interface ChoiceMessageFormatterInterface
+{
+ /**
+ * Formats a localized message pattern with given arguments.
+ *
+ * @param string $message The message (may also be an object that can be cast to string)
+ * @param int $number The number to use to find the indice of the message
+ * @param string $locale The message locale
+ * @param array $parameters An array of parameters for the message
+ *
+ * @return string
+ */
+ public function choiceFormat($message, $number, $locale, array $parameters = []);
+}
diff --git a/vendor/symfony/translation/Formatter/IntlFormatter.php b/vendor/symfony/translation/Formatter/IntlFormatter.php
new file mode 100755
index 0000000..1d5f468
--- /dev/null
+++ b/vendor/symfony/translation/Formatter/IntlFormatter.php
@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Formatter;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\LogicException;
+
+/**
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
+ */
+class IntlFormatter implements IntlFormatterInterface
+{
+ private $hasMessageFormatter;
+ private $cache = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function formatIntl(string $message, string $locale, array $parameters = []): string
+ {
+ if (!$formatter = $this->cache[$locale][$message] ?? null) {
+ if (!($this->hasMessageFormatter ?? $this->hasMessageFormatter = class_exists(\MessageFormatter::class))) {
+ throw new LogicException('Cannot parse message translation: please install the "intl" PHP extension or the "symfony/polyfill-intl-messageformatter" package.');
+ }
+ try {
+ $this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message);
+ } catch (\IntlException $e) {
+ throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): %s.', intl_get_error_code(), intl_get_error_message()), 0, $e);
+ }
+ }
+
+ foreach ($parameters as $key => $value) {
+ if (\in_array($key[0] ?? null, ['%', '{'], true)) {
+ unset($parameters[$key]);
+ $parameters[trim($key, '%{ }')] = $value;
+ }
+ }
+
+ if (false === $message = $formatter->format($parameters)) {
+ throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): %s.', $formatter->getErrorCode(), $formatter->getErrorMessage()));
+ }
+
+ return $message;
+ }
+}
diff --git a/vendor/symfony/translation/Formatter/IntlFormatterInterface.php b/vendor/symfony/translation/Formatter/IntlFormatterInterface.php
new file mode 100755
index 0000000..02fc6ac
--- /dev/null
+++ b/vendor/symfony/translation/Formatter/IntlFormatterInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Formatter;
+
+/**
+ * Formats ICU message patterns.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+interface IntlFormatterInterface
+{
+ /**
+ * Formats a localized message using rules defined by ICU MessageFormat.
+ *
+ * @see http://icu-project.org/apiref/icu4c/classMessageFormat.html#details
+ */
+ public function formatIntl(string $message, string $locale, array $parameters = []): string;
+}
diff --git a/vendor/symfony/translation/Formatter/MessageFormatter.php b/vendor/symfony/translation/Formatter/MessageFormatter.php
new file mode 100755
index 0000000..030d7a5
--- /dev/null
+++ b/vendor/symfony/translation/Formatter/MessageFormatter.php
@@ -0,0 +1,79 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Formatter;
+
+use Symfony\Component\Translation\IdentityTranslator;
+use Symfony\Component\Translation\MessageSelector;
+use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+/**
+ * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
+ */
+class MessageFormatter implements MessageFormatterInterface, IntlFormatterInterface, ChoiceMessageFormatterInterface
+{
+ private $translator;
+ private $intlFormatter;
+
+ /**
+ * @param TranslatorInterface|null $translator An identity translator to use as selector for pluralization
+ */
+ public function __construct($translator = null, IntlFormatterInterface $intlFormatter = null)
+ {
+ if ($translator instanceof MessageSelector) {
+ $translator = new IdentityTranslator($translator);
+ } elseif (null !== $translator && !$translator instanceof TranslatorInterface && !$translator instanceof LegacyTranslatorInterface) {
+ throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
+ }
+
+ $this->translator = $translator ?? new IdentityTranslator();
+ $this->intlFormatter = $intlFormatter ?? new IntlFormatter();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function format($message, $locale, array $parameters = [])
+ {
+ if ($this->translator instanceof TranslatorInterface) {
+ return $this->translator->trans($message, $parameters, null, $locale);
+ }
+
+ return strtr($message, $parameters);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function formatIntl(string $message, string $locale, array $parameters = []): string
+ {
+ return $this->intlFormatter->formatIntl($message, $locale, $parameters);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @deprecated since Symfony 4.2, use format() with a %count% parameter instead
+ */
+ public function choiceFormat($message, $number, $locale, array $parameters = [])
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the format() one instead with a %%count%% parameter.', __METHOD__), E_USER_DEPRECATED);
+
+ $parameters = ['%count%' => $number] + $parameters;
+
+ if ($this->translator instanceof TranslatorInterface) {
+ return $this->format($message, $locale, $parameters);
+ }
+
+ return $this->format($this->translator->transChoice($message, $number, [], null, $locale), $locale, $parameters);
+ }
+}
diff --git a/vendor/symfony/translation/Formatter/MessageFormatterInterface.php b/vendor/symfony/translation/Formatter/MessageFormatterInterface.php
new file mode 100755
index 0000000..370c055
--- /dev/null
+++ b/vendor/symfony/translation/Formatter/MessageFormatterInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Formatter;
+
+/**
+ * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
+ */
+interface MessageFormatterInterface
+{
+ /**
+ * Formats a localized message pattern with given arguments.
+ *
+ * @param string $message The message (may also be an object that can be cast to string)
+ * @param string $locale The message locale
+ * @param array $parameters An array of parameters for the message
+ *
+ * @return string
+ */
+ public function format($message, $locale, array $parameters = []);
+}
diff --git a/vendor/symfony/translation/IdentityTranslator.php b/vendor/symfony/translation/IdentityTranslator.php
new file mode 100755
index 0000000..b15792a
--- /dev/null
+++ b/vendor/symfony/translation/IdentityTranslator.php
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorTrait;
+
+/**
+ * IdentityTranslator does not translate anything.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class IdentityTranslator implements LegacyTranslatorInterface, TranslatorInterface
+{
+ use TranslatorTrait;
+
+ private $selector;
+
+ /**
+ * @param MessageSelector|null $selector The message selector for pluralization
+ */
+ public function __construct(MessageSelector $selector = null)
+ {
+ $this->selector = $selector;
+
+ if (__CLASS__ !== \get_class($this)) {
+ @trigger_error(sprintf('Calling "%s()" is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter
+ */
+ public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the trans() one instead with a "%%count%%" parameter.', __METHOD__), E_USER_DEPRECATED);
+
+ if ($this->selector) {
+ return strtr($this->selector->choose((string) $id, $number, $locale ?: $this->getLocale()), $parameters);
+ }
+
+ return $this->trans($id, ['%count%' => $number] + $parameters, $domain, $locale);
+ }
+
+ private function getPluralizationRule(int $number, string $locale): int
+ {
+ return PluralizationRules::get($number, $locale, false);
+ }
+}
diff --git a/vendor/symfony/translation/Interval.php b/vendor/symfony/translation/Interval.php
new file mode 100755
index 0000000..a0e484d
--- /dev/null
+++ b/vendor/symfony/translation/Interval.php
@@ -0,0 +1,112 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.2, use IdentityTranslator instead.', Interval::class), E_USER_DEPRECATED);
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+
+/**
+ * Tests if a given number belongs to a given math interval.
+ *
+ * An interval can represent a finite set of numbers:
+ *
+ * {1,2,3,4}
+ *
+ * An interval can represent numbers between two numbers:
+ *
+ * [1, +Inf]
+ * ]-1,2[
+ *
+ * The left delimiter can be [ (inclusive) or ] (exclusive).
+ * The right delimiter can be [ (exclusive) or ] (inclusive).
+ * Beside numbers, you can use -Inf and +Inf for the infinite.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @see http://en.wikipedia.org/wiki/Interval_%28mathematics%29#The_ISO_notation
+ * @deprecated since Symfony 4.2, use IdentityTranslator instead
+ */
+class Interval
+{
+ /**
+ * Tests if the given number is in the math interval.
+ *
+ * @param int $number A number
+ * @param string $interval An interval
+ *
+ * @return bool
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function test($number, $interval)
+ {
+ $interval = trim($interval);
+
+ if (!preg_match('/^'.self::getIntervalRegexp().'$/x', $interval, $matches)) {
+ throw new InvalidArgumentException(sprintf('"%s" is not a valid interval.', $interval));
+ }
+
+ if ($matches[1]) {
+ foreach (explode(',', $matches[2]) as $n) {
+ if ($number == $n) {
+ return true;
+ }
+ }
+ } else {
+ $leftNumber = self::convertNumber($matches['left']);
+ $rightNumber = self::convertNumber($matches['right']);
+
+ return
+ ('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber)
+ && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber)
+ ;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a Regexp that matches valid intervals.
+ *
+ * @return string A Regexp (without the delimiters)
+ */
+ public static function getIntervalRegexp()
+ {
+ return <<<EOF
+ ({\s*
+ (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)
+ \s*})
+
+ |
+
+ (?P<left_delimiter>[\[\]])
+ \s*
+ (?P<left>-Inf|\-?\d+(\.\d+)?)
+ \s*,\s*
+ (?P<right>\+?Inf|\-?\d+(\.\d+)?)
+ \s*
+ (?P<right_delimiter>[\[\]])
+EOF;
+ }
+
+ private static function convertNumber($number)
+ {
+ if ('-Inf' === $number) {
+ return log(0);
+ } elseif ('+Inf' === $number || 'Inf' === $number) {
+ return -log(0);
+ }
+
+ return (float) $number;
+ }
+}
diff --git a/vendor/symfony/translation/LICENSE b/vendor/symfony/translation/LICENSE
new file mode 100755
index 0000000..a677f43
--- /dev/null
+++ b/vendor/symfony/translation/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/translation/Loader/ArrayLoader.php b/vendor/symfony/translation/Loader/ArrayLoader.php
new file mode 100755
index 0000000..0a6f9f0
--- /dev/null
+++ b/vendor/symfony/translation/Loader/ArrayLoader.php
@@ -0,0 +1,66 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * ArrayLoader loads translations from a PHP array.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class ArrayLoader implements LoaderInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function load($resource, $locale, $domain = 'messages')
+ {
+ $this->flatten($resource);
+ $catalogue = new MessageCatalogue($locale);
+ $catalogue->add($resource, $domain);
+
+ return $catalogue;
+ }
+
+ /**
+ * Flattens an nested array of translations.
+ *
+ * The scheme used is:
+ * 'key' => ['key2' => ['key3' => 'value']]
+ * Becomes:
+ * 'key.key2.key3' => 'value'
+ *
+ * This function takes an array by reference and will modify it
+ *
+ * @param array &$messages The array that will be flattened
+ * @param array $subnode Current subnode being parsed, used internally for recursive calls
+ * @param string $path Current path being parsed, used internally for recursive calls
+ */
+ private function flatten(array &$messages, array $subnode = null, $path = null)
+ {
+ if (null === $subnode) {
+ $subnode = &$messages;
+ }
+ foreach ($subnode as $key => $value) {
+ if (\is_array($value)) {
+ $nodePath = $path ? $path.'.'.$key : $key;
+ $this->flatten($messages, $value, $nodePath);
+ if (null === $path) {
+ unset($messages[$key]);
+ }
+ } elseif (null !== $path) {
+ $messages[$path.'.'.$key] = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Loader/CsvFileLoader.php b/vendor/symfony/translation/Loader/CsvFileLoader.php
new file mode 100755
index 0000000..18cc83e
--- /dev/null
+++ b/vendor/symfony/translation/Loader/CsvFileLoader.php
@@ -0,0 +1,65 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+
+/**
+ * CsvFileLoader loads translations from CSV files.
+ *
+ * @author Saša Stamenković <umpirsky@gmail.com>
+ */
+class CsvFileLoader extends FileLoader
+{
+ private $delimiter = ';';
+ private $enclosure = '"';
+ private $escape = '\\';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function loadResource($resource)
+ {
+ $messages = [];
+
+ try {
+ $file = new \SplFileObject($resource, 'rb');
+ } catch (\RuntimeException $e) {
+ throw new NotFoundResourceException(sprintf('Error opening file "%s".', $resource), 0, $e);
+ }
+
+ $file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY);
+ $file->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
+
+ foreach ($file as $data) {
+ if ('#' !== substr($data[0], 0, 1) && isset($data[1]) && 2 === \count($data)) {
+ $messages[$data[0]] = $data[1];
+ }
+ }
+
+ return $messages;
+ }
+
+ /**
+ * Sets the delimiter, enclosure, and escape character for CSV.
+ *
+ * @param string $delimiter Delimiter character
+ * @param string $enclosure Enclosure character
+ * @param string $escape Escape character
+ */
+ public function setCsvControl($delimiter = ';', $enclosure = '"', $escape = '\\')
+ {
+ $this->delimiter = $delimiter;
+ $this->enclosure = $enclosure;
+ $this->escape = $escape;
+ }
+}
diff --git a/vendor/symfony/translation/Loader/FileLoader.php b/vendor/symfony/translation/Loader/FileLoader.php
new file mode 100755
index 0000000..7ec54a3
--- /dev/null
+++ b/vendor/symfony/translation/Loader/FileLoader.php
@@ -0,0 +1,65 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+
+/**
+ * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
+ */
+abstract class FileLoader extends ArrayLoader
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function load($resource, $locale, $domain = 'messages')
+ {
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
+ $messages = $this->loadResource($resource);
+
+ // empty resource
+ if (null === $messages) {
+ $messages = [];
+ }
+
+ // not an array
+ if (!\is_array($messages)) {
+ throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource));
+ }
+
+ $catalogue = parent::load($messages, $locale, $domain);
+
+ if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
+ $catalogue->addResource(new FileResource($resource));
+ }
+
+ return $catalogue;
+ }
+
+ /**
+ * @param string $resource
+ *
+ * @return array
+ *
+ * @throws InvalidResourceException if stream content has an invalid format
+ */
+ abstract protected function loadResource($resource);
+}
diff --git a/vendor/symfony/translation/Loader/IcuDatFileLoader.php b/vendor/symfony/translation/Loader/IcuDatFileLoader.php
new file mode 100755
index 0000000..7bbf4c2
--- /dev/null
+++ b/vendor/symfony/translation/Loader/IcuDatFileLoader.php
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * IcuResFileLoader loads translations from a resource bundle.
+ *
+ * @author stealth35
+ */
+class IcuDatFileLoader extends IcuResFileLoader
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function load($resource, $locale, $domain = 'messages')
+ {
+ if (!stream_is_local($resource.'.dat')) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource.'.dat')) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
+ try {
+ $rb = new \ResourceBundle($locale, $resource);
+ } catch (\Exception $e) {
+ $rb = null;
+ }
+
+ if (!$rb) {
+ throw new InvalidResourceException(sprintf('Cannot load resource "%s"', $resource));
+ } elseif (intl_is_failure($rb->getErrorCode())) {
+ throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode());
+ }
+
+ $messages = $this->flatten($rb);
+ $catalogue = new MessageCatalogue($locale);
+ $catalogue->add($messages, $domain);
+
+ if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
+ $catalogue->addResource(new FileResource($resource.'.dat'));
+ }
+
+ return $catalogue;
+ }
+}
diff --git a/vendor/symfony/translation/Loader/IcuResFileLoader.php b/vendor/symfony/translation/Loader/IcuResFileLoader.php
new file mode 100755
index 0000000..005f260
--- /dev/null
+++ b/vendor/symfony/translation/Loader/IcuResFileLoader.php
@@ -0,0 +1,91 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Config\Resource\DirectoryResource;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * IcuResFileLoader loads translations from a resource bundle.
+ *
+ * @author stealth35
+ */
+class IcuResFileLoader implements LoaderInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function load($resource, $locale, $domain = 'messages')
+ {
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!is_dir($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
+ try {
+ $rb = new \ResourceBundle($locale, $resource);
+ } catch (\Exception $e) {
+ $rb = null;
+ }
+
+ if (!$rb) {
+ throw new InvalidResourceException(sprintf('Cannot load resource "%s"', $resource));
+ } elseif (intl_is_failure($rb->getErrorCode())) {
+ throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode());
+ }
+
+ $messages = $this->flatten($rb);
+ $catalogue = new MessageCatalogue($locale);
+ $catalogue->add($messages, $domain);
+
+ if (class_exists('Symfony\Component\Config\Resource\DirectoryResource')) {
+ $catalogue->addResource(new DirectoryResource($resource));
+ }
+
+ return $catalogue;
+ }
+
+ /**
+ * Flattens an ResourceBundle.
+ *
+ * The scheme used is:
+ * key { key2 { key3 { "value" } } }
+ * Becomes:
+ * 'key.key2.key3' => 'value'
+ *
+ * This function takes an array by reference and will modify it
+ *
+ * @param \ResourceBundle $rb The ResourceBundle that will be flattened
+ * @param array $messages Used internally for recursive calls
+ * @param string $path Current path being parsed, used internally for recursive calls
+ *
+ * @return array the flattened ResourceBundle
+ */
+ protected function flatten(\ResourceBundle $rb, array &$messages = [], $path = null)
+ {
+ foreach ($rb as $key => $value) {
+ $nodePath = $path ? $path.'.'.$key : $key;
+ if ($value instanceof \ResourceBundle) {
+ $this->flatten($value, $messages, $nodePath);
+ } else {
+ $messages[$nodePath] = $value;
+ }
+ }
+
+ return $messages;
+ }
+}
diff --git a/vendor/symfony/translation/Loader/IniFileLoader.php b/vendor/symfony/translation/Loader/IniFileLoader.php
new file mode 100755
index 0000000..11d9b27
--- /dev/null
+++ b/vendor/symfony/translation/Loader/IniFileLoader.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+/**
+ * IniFileLoader loads translations from an ini file.
+ *
+ * @author stealth35
+ */
+class IniFileLoader extends FileLoader
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function loadResource($resource)
+ {
+ return parse_ini_file($resource, true);
+ }
+}
diff --git a/vendor/symfony/translation/Loader/JsonFileLoader.php b/vendor/symfony/translation/Loader/JsonFileLoader.php
new file mode 100755
index 0000000..5267212
--- /dev/null
+++ b/vendor/symfony/translation/Loader/JsonFileLoader.php
@@ -0,0 +1,64 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+
+/**
+ * JsonFileLoader loads translations from an json file.
+ *
+ * @author singles
+ */
+class JsonFileLoader extends FileLoader
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function loadResource($resource)
+ {
+ $messages = [];
+ if ($data = file_get_contents($resource)) {
+ $messages = json_decode($data, true);
+
+ if (0 < $errorCode = json_last_error()) {
+ throw new InvalidResourceException(sprintf('Error parsing JSON - %s', $this->getJSONErrorMessage($errorCode)));
+ }
+ }
+
+ return $messages;
+ }
+
+ /**
+ * Translates JSON_ERROR_* constant into meaningful message.
+ *
+ * @param int $errorCode Error code returned by json_last_error() call
+ *
+ * @return string Message string
+ */
+ private function getJSONErrorMessage($errorCode)
+ {
+ switch ($errorCode) {
+ case JSON_ERROR_DEPTH:
+ return 'Maximum stack depth exceeded';
+ case JSON_ERROR_STATE_MISMATCH:
+ return 'Underflow or the modes mismatch';
+ case JSON_ERROR_CTRL_CHAR:
+ return 'Unexpected control character found';
+ case JSON_ERROR_SYNTAX:
+ return 'Syntax error, malformed JSON';
+ case JSON_ERROR_UTF8:
+ return 'Malformed UTF-8 characters, possibly incorrectly encoded';
+ default:
+ return 'Unknown error';
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Loader/LoaderInterface.php b/vendor/symfony/translation/Loader/LoaderInterface.php
new file mode 100755
index 0000000..1785402
--- /dev/null
+++ b/vendor/symfony/translation/Loader/LoaderInterface.php
@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * LoaderInterface is the interface implemented by all translation loaders.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface LoaderInterface
+{
+ /**
+ * Loads a locale.
+ *
+ * @param mixed $resource A resource
+ * @param string $locale A locale
+ * @param string $domain The domain
+ *
+ * @return MessageCatalogue A MessageCatalogue instance
+ *
+ * @throws NotFoundResourceException when the resource cannot be found
+ * @throws InvalidResourceException when the resource cannot be loaded
+ */
+ public function load($resource, $locale, $domain = 'messages');
+}
diff --git a/vendor/symfony/translation/Loader/MoFileLoader.php b/vendor/symfony/translation/Loader/MoFileLoader.php
new file mode 100755
index 0000000..16a3dfc
--- /dev/null
+++ b/vendor/symfony/translation/Loader/MoFileLoader.php
@@ -0,0 +1,145 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+
+/**
+ * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/)
+ */
+class MoFileLoader extends FileLoader
+{
+ /**
+ * Magic used for validating the format of a MO file as well as
+ * detecting if the machine used to create that file was little endian.
+ */
+ const MO_LITTLE_ENDIAN_MAGIC = 0x950412de;
+
+ /**
+ * Magic used for validating the format of a MO file as well as
+ * detecting if the machine used to create that file was big endian.
+ */
+ const MO_BIG_ENDIAN_MAGIC = 0xde120495;
+
+ /**
+ * The size of the header of a MO file in bytes.
+ */
+ const MO_HEADER_SIZE = 28;
+
+ /**
+ * Parses machine object (MO) format, independent of the machine's endian it
+ * was created on. Both 32bit and 64bit systems are supported.
+ *
+ * {@inheritdoc}
+ */
+ protected function loadResource($resource)
+ {
+ $stream = fopen($resource, 'r');
+
+ $stat = fstat($stream);
+
+ if ($stat['size'] < self::MO_HEADER_SIZE) {
+ throw new InvalidResourceException('MO stream content has an invalid format.');
+ }
+ $magic = unpack('V1', fread($stream, 4));
+ $magic = hexdec(substr(dechex(current($magic)), -8));
+
+ if (self::MO_LITTLE_ENDIAN_MAGIC == $magic) {
+ $isBigEndian = false;
+ } elseif (self::MO_BIG_ENDIAN_MAGIC == $magic) {
+ $isBigEndian = true;
+ } else {
+ throw new InvalidResourceException('MO stream content has an invalid format.');
+ }
+
+ // formatRevision
+ $this->readLong($stream, $isBigEndian);
+ $count = $this->readLong($stream, $isBigEndian);
+ $offsetId = $this->readLong($stream, $isBigEndian);
+ $offsetTranslated = $this->readLong($stream, $isBigEndian);
+ // sizeHashes
+ $this->readLong($stream, $isBigEndian);
+ // offsetHashes
+ $this->readLong($stream, $isBigEndian);
+
+ $messages = [];
+
+ for ($i = 0; $i < $count; ++$i) {
+ $pluralId = null;
+ $translated = null;
+
+ fseek($stream, $offsetId + $i * 8);
+
+ $length = $this->readLong($stream, $isBigEndian);
+ $offset = $this->readLong($stream, $isBigEndian);
+
+ if ($length < 1) {
+ continue;
+ }
+
+ fseek($stream, $offset);
+ $singularId = fread($stream, $length);
+
+ if (false !== strpos($singularId, "\000")) {
+ list($singularId, $pluralId) = explode("\000", $singularId);
+ }
+
+ fseek($stream, $offsetTranslated + $i * 8);
+ $length = $this->readLong($stream, $isBigEndian);
+ $offset = $this->readLong($stream, $isBigEndian);
+
+ if ($length < 1) {
+ continue;
+ }
+
+ fseek($stream, $offset);
+ $translated = fread($stream, $length);
+
+ if (false !== strpos($translated, "\000")) {
+ $translated = explode("\000", $translated);
+ }
+
+ $ids = ['singular' => $singularId, 'plural' => $pluralId];
+ $item = compact('ids', 'translated');
+
+ if (\is_array($item['translated'])) {
+ $messages[$item['ids']['singular']] = stripcslashes($item['translated'][0]);
+ if (isset($item['ids']['plural'])) {
+ $plurals = [];
+ foreach ($item['translated'] as $plural => $translated) {
+ $plurals[] = sprintf('{%d} %s', $plural, $translated);
+ }
+ $messages[$item['ids']['plural']] = stripcslashes(implode('|', $plurals));
+ }
+ } elseif (!empty($item['ids']['singular'])) {
+ $messages[$item['ids']['singular']] = stripcslashes($item['translated']);
+ }
+ }
+
+ fclose($stream);
+
+ return array_filter($messages);
+ }
+
+ /**
+ * Reads an unsigned long from stream respecting endianness.
+ *
+ * @param resource $stream
+ */
+ private function readLong($stream, bool $isBigEndian): int
+ {
+ $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4));
+ $result = current($result);
+
+ return (int) substr($result, -8);
+ }
+}
diff --git a/vendor/symfony/translation/Loader/PhpFileLoader.php b/vendor/symfony/translation/Loader/PhpFileLoader.php
new file mode 100755
index 0000000..a0050e8
--- /dev/null
+++ b/vendor/symfony/translation/Loader/PhpFileLoader.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+/**
+ * PhpFileLoader loads translations from PHP files returning an array of translations.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class PhpFileLoader extends FileLoader
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function loadResource($resource)
+ {
+ return require $resource;
+ }
+}
diff --git a/vendor/symfony/translation/Loader/PoFileLoader.php b/vendor/symfony/translation/Loader/PoFileLoader.php
new file mode 100755
index 0000000..1412a78
--- /dev/null
+++ b/vendor/symfony/translation/Loader/PoFileLoader.php
@@ -0,0 +1,148 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+/**
+ * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/)
+ * @copyright Copyright (c) 2012, Clemens Tolboom
+ */
+class PoFileLoader extends FileLoader
+{
+ /**
+ * Parses portable object (PO) format.
+ *
+ * From http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files
+ * we should be able to parse files having:
+ *
+ * white-space
+ * # translator-comments
+ * #. extracted-comments
+ * #: reference...
+ * #, flag...
+ * #| msgid previous-untranslated-string
+ * msgid untranslated-string
+ * msgstr translated-string
+ *
+ * extra or different lines are:
+ *
+ * #| msgctxt previous-context
+ * #| msgid previous-untranslated-string
+ * msgctxt context
+ *
+ * #| msgid previous-untranslated-string-singular
+ * #| msgid_plural previous-untranslated-string-plural
+ * msgid untranslated-string-singular
+ * msgid_plural untranslated-string-plural
+ * msgstr[0] translated-string-case-0
+ * ...
+ * msgstr[N] translated-string-case-n
+ *
+ * The definition states:
+ * - white-space and comments are optional.
+ * - msgid "" that an empty singleline defines a header.
+ *
+ * This parser sacrifices some features of the reference implementation the
+ * differences to that implementation are as follows.
+ * - No support for comments spanning multiple lines.
+ * - Translator and extracted comments are treated as being the same type.
+ * - Message IDs are allowed to have other encodings as just US-ASCII.
+ *
+ * Items with an empty id are ignored.
+ *
+ * {@inheritdoc}
+ */
+ protected function loadResource($resource)
+ {
+ $stream = fopen($resource, 'r');
+
+ $defaults = [
+ 'ids' => [],
+ 'translated' => null,
+ ];
+
+ $messages = [];
+ $item = $defaults;
+ $flags = [];
+
+ while ($line = fgets($stream)) {
+ $line = trim($line);
+
+ if ('' === $line) {
+ // Whitespace indicated current item is done
+ if (!\in_array('fuzzy', $flags)) {
+ $this->addMessage($messages, $item);
+ }
+ $item = $defaults;
+ $flags = [];
+ } elseif ('#,' === substr($line, 0, 2)) {
+ $flags = array_map('trim', explode(',', substr($line, 2)));
+ } elseif ('msgid "' === substr($line, 0, 7)) {
+ // We start a new msg so save previous
+ // TODO: this fails when comments or contexts are added
+ $this->addMessage($messages, $item);
+ $item = $defaults;
+ $item['ids']['singular'] = substr($line, 7, -1);
+ } elseif ('msgstr "' === substr($line, 0, 8)) {
+ $item['translated'] = substr($line, 8, -1);
+ } elseif ('"' === $line[0]) {
+ $continues = isset($item['translated']) ? 'translated' : 'ids';
+
+ if (\is_array($item[$continues])) {
+ end($item[$continues]);
+ $item[$continues][key($item[$continues])] .= substr($line, 1, -1);
+ } else {
+ $item[$continues] .= substr($line, 1, -1);
+ }
+ } elseif ('msgid_plural "' === substr($line, 0, 14)) {
+ $item['ids']['plural'] = substr($line, 14, -1);
+ } elseif ('msgstr[' === substr($line, 0, 7)) {
+ $size = strpos($line, ']');
+ $item['translated'][(int) substr($line, 7, 1)] = substr($line, $size + 3, -1);
+ }
+ }
+ // save last item
+ if (!\in_array('fuzzy', $flags)) {
+ $this->addMessage($messages, $item);
+ }
+ fclose($stream);
+
+ return $messages;
+ }
+
+ /**
+ * Save a translation item to the messages.
+ *
+ * A .po file could contain by error missing plural indexes. We need to
+ * fix these before saving them.
+ */
+ private function addMessage(array &$messages, array $item)
+ {
+ if (\is_array($item['translated'])) {
+ $messages[stripcslashes($item['ids']['singular'])] = stripcslashes($item['translated'][0]);
+ if (isset($item['ids']['plural'])) {
+ $plurals = $item['translated'];
+ // PO are by definition indexed so sort by index.
+ ksort($plurals);
+ // Make sure every index is filled.
+ end($plurals);
+ $count = key($plurals);
+ // Fill missing spots with '-'.
+ $empties = array_fill(0, $count + 1, '-');
+ $plurals += $empties;
+ ksort($plurals);
+ $messages[stripcslashes($item['ids']['plural'])] = stripcslashes(implode('|', $plurals));
+ }
+ } elseif (!empty($item['ids']['singular'])) {
+ $messages[stripcslashes($item['ids']['singular'])] = stripcslashes($item['translated']);
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Loader/QtFileLoader.php b/vendor/symfony/translation/Loader/QtFileLoader.php
new file mode 100755
index 0000000..2d4a4c0
--- /dev/null
+++ b/vendor/symfony/translation/Loader/QtFileLoader.php
@@ -0,0 +1,77 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Config\Util\XmlUtils;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * QtFileLoader loads translations from QT Translations XML files.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class QtFileLoader implements LoaderInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function load($resource, $locale, $domain = 'messages')
+ {
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
+ try {
+ $dom = XmlUtils::loadFile($resource);
+ } catch (\InvalidArgumentException $e) {
+ throw new InvalidResourceException(sprintf('Unable to load "%s".', $resource), $e->getCode(), $e);
+ }
+
+ $internalErrors = libxml_use_internal_errors(true);
+ libxml_clear_errors();
+
+ $xpath = new \DOMXPath($dom);
+ $nodes = $xpath->evaluate('//TS/context/name[text()="'.$domain.'"]');
+
+ $catalogue = new MessageCatalogue($locale);
+ if (1 == $nodes->length) {
+ $translations = $nodes->item(0)->nextSibling->parentNode->parentNode->getElementsByTagName('message');
+ foreach ($translations as $translation) {
+ $translationValue = (string) $translation->getElementsByTagName('translation')->item(0)->nodeValue;
+
+ if (!empty($translationValue)) {
+ $catalogue->set(
+ (string) $translation->getElementsByTagName('source')->item(0)->nodeValue,
+ $translationValue,
+ $domain
+ );
+ }
+ $translation = $translation->nextSibling;
+ }
+
+ if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
+ $catalogue->addResource(new FileResource($resource));
+ }
+ }
+
+ libxml_use_internal_errors($internalErrors);
+
+ return $catalogue;
+ }
+}
diff --git a/vendor/symfony/translation/Loader/XliffFileLoader.php b/vendor/symfony/translation/Loader/XliffFileLoader.php
new file mode 100755
index 0000000..6e01a71
--- /dev/null
+++ b/vendor/symfony/translation/Loader/XliffFileLoader.php
@@ -0,0 +1,212 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Config\Util\XmlUtils;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\Util\XliffUtils;
+
+/**
+ * XliffFileLoader loads translations from XLIFF files.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class XliffFileLoader implements LoaderInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function load($resource, $locale, $domain = 'messages')
+ {
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
+ $catalogue = new MessageCatalogue($locale);
+ $this->extract($resource, $catalogue, $domain);
+
+ if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
+ $catalogue->addResource(new FileResource($resource));
+ }
+
+ return $catalogue;
+ }
+
+ private function extract($resource, MessageCatalogue $catalogue, $domain)
+ {
+ try {
+ $dom = XmlUtils::loadFile($resource);
+ } catch (\InvalidArgumentException $e) {
+ throw new InvalidResourceException(sprintf('Unable to load "%s": %s', $resource, $e->getMessage()), $e->getCode(), $e);
+ }
+
+ $xliffVersion = XliffUtils::getVersionNumber($dom);
+ if ($errors = XliffUtils::validateSchema($dom)) {
+ throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: %s', $resource, XliffUtils::getErrorsAsString($errors)));
+ }
+
+ if ('1.2' === $xliffVersion) {
+ $this->extractXliff1($dom, $catalogue, $domain);
+ }
+
+ if ('2.0' === $xliffVersion) {
+ $this->extractXliff2($dom, $catalogue, $domain);
+ }
+ }
+
+ /**
+ * Extract messages and metadata from DOMDocument into a MessageCatalogue.
+ *
+ * @param \DOMDocument $dom Source to extract messages and metadata
+ * @param MessageCatalogue $catalogue Catalogue where we'll collect messages and metadata
+ * @param string $domain The domain
+ */
+ private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
+ {
+ $xml = simplexml_import_dom($dom);
+ $encoding = strtoupper($dom->encoding);
+
+ $namespace = 'urn:oasis:names:tc:xliff:document:1.2';
+ $xml->registerXPathNamespace('xliff', $namespace);
+
+ foreach ($xml->xpath('//xliff:file') as $file) {
+ $fileAttributes = $file->attributes();
+
+ $file->registerXPathNamespace('xliff', $namespace);
+
+ foreach ($file->xpath('.//xliff:trans-unit') as $translation) {
+ $attributes = $translation->attributes();
+
+ if (!(isset($attributes['resname']) || isset($translation->source))) {
+ continue;
+ }
+
+ $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source;
+ // If the xlf file has another encoding specified, try to convert it because
+ // simple_xml will always return utf-8 encoded values
+ $target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding);
+
+ $catalogue->set((string) $source, $target, $domain);
+
+ $metadata = [
+ 'source' => (string) $translation->source,
+ 'file' => [
+ 'original' => (string) $fileAttributes['original'],
+ ],
+ ];
+ if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) {
+ $metadata['notes'] = $notes;
+ }
+
+ if (isset($translation->target) && $translation->target->attributes()) {
+ $metadata['target-attributes'] = [];
+ foreach ($translation->target->attributes() as $key => $value) {
+ $metadata['target-attributes'][$key] = (string) $value;
+ }
+ }
+
+ if (isset($attributes['id'])) {
+ $metadata['id'] = (string) $attributes['id'];
+ }
+
+ $catalogue->setMetadata((string) $source, $metadata, $domain);
+ }
+ }
+ }
+
+ private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
+ {
+ $xml = simplexml_import_dom($dom);
+ $encoding = strtoupper($dom->encoding);
+
+ $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0');
+
+ foreach ($xml->xpath('//xliff:unit') as $unit) {
+ foreach ($unit->segment as $segment) {
+ $source = $segment->source;
+
+ // If the xlf file has another encoding specified, try to convert it because
+ // simple_xml will always return utf-8 encoded values
+ $target = $this->utf8ToCharset((string) (isset($segment->target) ? $segment->target : $source), $encoding);
+
+ $catalogue->set((string) $source, $target, $domain);
+
+ $metadata = [];
+ if (isset($segment->target) && $segment->target->attributes()) {
+ $metadata['target-attributes'] = [];
+ foreach ($segment->target->attributes() as $key => $value) {
+ $metadata['target-attributes'][$key] = (string) $value;
+ }
+ }
+
+ if (isset($unit->notes)) {
+ $metadata['notes'] = [];
+ foreach ($unit->notes->note as $noteNode) {
+ $note = [];
+ foreach ($noteNode->attributes() as $key => $value) {
+ $note[$key] = (string) $value;
+ }
+ $note['content'] = (string) $noteNode;
+ $metadata['notes'][] = $note;
+ }
+ }
+
+ $catalogue->setMetadata((string) $source, $metadata, $domain);
+ }
+ }
+ }
+
+ /**
+ * Convert a UTF8 string to the specified encoding.
+ */
+ private function utf8ToCharset(string $content, string $encoding = null): string
+ {
+ if ('UTF-8' !== $encoding && !empty($encoding)) {
+ return mb_convert_encoding($content, $encoding, 'UTF-8');
+ }
+
+ return $content;
+ }
+
+ private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, string $encoding = null): array
+ {
+ $notes = [];
+
+ if (null === $noteElement) {
+ return $notes;
+ }
+
+ /** @var \SimpleXMLElement $xmlNote */
+ foreach ($noteElement as $xmlNote) {
+ $noteAttributes = $xmlNote->attributes();
+ $note = ['content' => $this->utf8ToCharset((string) $xmlNote, $encoding)];
+ if (isset($noteAttributes['priority'])) {
+ $note['priority'] = (int) $noteAttributes['priority'];
+ }
+
+ if (isset($noteAttributes['from'])) {
+ $note['from'] = (string) $noteAttributes['from'];
+ }
+
+ $notes[] = $note;
+ }
+
+ return $notes;
+ }
+}
diff --git a/vendor/symfony/translation/Loader/YamlFileLoader.php b/vendor/symfony/translation/Loader/YamlFileLoader.php
new file mode 100755
index 0000000..584a055
--- /dev/null
+++ b/vendor/symfony/translation/Loader/YamlFileLoader.php
@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\LogicException;
+use Symfony\Component\Yaml\Exception\ParseException;
+use Symfony\Component\Yaml\Parser as YamlParser;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * YamlFileLoader loads translations from Yaml files.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class YamlFileLoader extends FileLoader
+{
+ private $yamlParser;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function loadResource($resource)
+ {
+ if (null === $this->yamlParser) {
+ if (!class_exists('Symfony\Component\Yaml\Parser')) {
+ throw new LogicException('Loading translations from the YAML format requires the Symfony Yaml component.');
+ }
+
+ $this->yamlParser = new YamlParser();
+ }
+
+ try {
+ $messages = $this->yamlParser->parseFile($resource, Yaml::PARSE_CONSTANT);
+ } catch (ParseException $e) {
+ throw new InvalidResourceException(sprintf('Error parsing YAML, invalid file "%s"', $resource), 0, $e);
+ }
+
+ return $messages;
+ }
+}
diff --git a/vendor/symfony/translation/LoggingTranslator.php b/vendor/symfony/translation/LoggingTranslator.php
new file mode 100755
index 0000000..2996167
--- /dev/null
+++ b/vendor/symfony/translation/LoggingTranslator.php
@@ -0,0 +1,157 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
+use Symfony\Contracts\Translation\LocaleAwareInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+/**
+ * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
+ */
+class LoggingTranslator implements TranslatorInterface, LegacyTranslatorInterface, TranslatorBagInterface
+{
+ /**
+ * @var TranslatorInterface|TranslatorBagInterface
+ */
+ private $translator;
+
+ private $logger;
+
+ /**
+ * @param TranslatorInterface $translator The translator must implement TranslatorBagInterface
+ * @param LoggerInterface $logger
+ */
+ public function __construct($translator, LoggerInterface $logger)
+ {
+ if (!$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
+ throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
+ }
+ if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) {
+ throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', \get_class($translator)));
+ }
+
+ $this->translator = $translator;
+ $this->logger = $logger;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function trans($id, array $parameters = [], $domain = null, $locale = null)
+ {
+ $trans = $this->translator->trans($id, $parameters, $domain, $locale);
+ $this->log($id, $domain, $locale);
+
+ return $trans;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter
+ */
+ public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the trans() one instead with a "%%count%%" parameter.', __METHOD__), E_USER_DEPRECATED);
+
+ if ($this->translator instanceof TranslatorInterface) {
+ $trans = $this->translator->trans($id, ['%count%' => $number] + $parameters, $domain, $locale);
+ } else {
+ $trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
+ }
+
+ $this->log($id, $domain, $locale);
+
+ return $trans;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setLocale($locale)
+ {
+ $prev = $this->translator->getLocale();
+ $this->translator->setLocale($locale);
+ if ($prev === $locale) {
+ return;
+ }
+
+ $this->logger->debug(sprintf('The locale of the translator has changed from "%s" to "%s".', $prev, $locale));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLocale()
+ {
+ return $this->translator->getLocale();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCatalogue($locale = null)
+ {
+ return $this->translator->getCatalogue($locale);
+ }
+
+ /**
+ * Gets the fallback locales.
+ *
+ * @return array The fallback locales
+ */
+ public function getFallbackLocales()
+ {
+ if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) {
+ return $this->translator->getFallbackLocales();
+ }
+
+ return [];
+ }
+
+ /**
+ * Passes through all unknown calls onto the translator object.
+ */
+ public function __call($method, $args)
+ {
+ return $this->translator->{$method}(...$args);
+ }
+
+ /**
+ * Logs for missing translations.
+ *
+ * @param string $id
+ * @param string|null $domain
+ * @param string|null $locale
+ */
+ private function log($id, $domain, $locale)
+ {
+ if (null === $domain) {
+ $domain = 'messages';
+ }
+
+ $id = (string) $id;
+ $catalogue = $this->translator->getCatalogue($locale);
+ if ($catalogue->defines($id, $domain)) {
+ return;
+ }
+
+ if ($catalogue->has($id, $domain)) {
+ $this->logger->debug('Translation use fallback catalogue.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]);
+ } else {
+ $this->logger->warning('Translation not found.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]);
+ }
+ }
+}
diff --git a/vendor/symfony/translation/MessageCatalogue.php b/vendor/symfony/translation/MessageCatalogue.php
new file mode 100755
index 0000000..19afb90
--- /dev/null
+++ b/vendor/symfony/translation/MessageCatalogue.php
@@ -0,0 +1,301 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\Config\Resource\ResourceInterface;
+use Symfony\Component\Translation\Exception\LogicException;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface
+{
+ private $messages = [];
+ private $metadata = [];
+ private $resources = [];
+ private $locale;
+ private $fallbackCatalogue;
+ private $parent;
+
+ /**
+ * @param string $locale The locale
+ * @param array $messages An array of messages classified by domain
+ */
+ public function __construct(?string $locale, array $messages = [])
+ {
+ $this->locale = $locale;
+ $this->messages = $messages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLocale()
+ {
+ return $this->locale;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDomains()
+ {
+ $domains = [];
+ $suffixLength = \strlen(self::INTL_DOMAIN_SUFFIX);
+
+ foreach ($this->messages as $domain => $messages) {
+ if (\strlen($domain) > $suffixLength && false !== $i = strpos($domain, self::INTL_DOMAIN_SUFFIX, -$suffixLength)) {
+ $domain = substr($domain, 0, $i);
+ }
+ $domains[$domain] = $domain;
+ }
+
+ return array_values($domains);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function all($domain = null)
+ {
+ if (null !== $domain) {
+ return ($this->messages[$domain.self::INTL_DOMAIN_SUFFIX] ?? []) + ($this->messages[$domain] ?? []);
+ }
+
+ $allMessages = [];
+ $suffixLength = \strlen(self::INTL_DOMAIN_SUFFIX);
+
+ foreach ($this->messages as $domain => $messages) {
+ if (\strlen($domain) > $suffixLength && false !== $i = strpos($domain, self::INTL_DOMAIN_SUFFIX, -$suffixLength)) {
+ $domain = substr($domain, 0, $i);
+ $allMessages[$domain] = $messages + ($allMessages[$domain] ?? []);
+ } else {
+ $allMessages[$domain] = ($allMessages[$domain] ?? []) + $messages;
+ }
+ }
+
+ return $allMessages;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($id, $translation, $domain = 'messages')
+ {
+ $this->add([$id => $translation], $domain);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($id, $domain = 'messages')
+ {
+ if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) {
+ return true;
+ }
+
+ if (null !== $this->fallbackCatalogue) {
+ return $this->fallbackCatalogue->has($id, $domain);
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defines($id, $domain = 'messages')
+ {
+ return isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($id, $domain = 'messages')
+ {
+ if (isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) {
+ return $this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id];
+ }
+
+ if (isset($this->messages[$domain][$id])) {
+ return $this->messages[$domain][$id];
+ }
+
+ if (null !== $this->fallbackCatalogue) {
+ return $this->fallbackCatalogue->get($id, $domain);
+ }
+
+ return $id;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function replace($messages, $domain = 'messages')
+ {
+ unset($this->messages[$domain], $this->messages[$domain.self::INTL_DOMAIN_SUFFIX]);
+
+ $this->add($messages, $domain);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function add($messages, $domain = 'messages')
+ {
+ if (!isset($this->messages[$domain])) {
+ $this->messages[$domain] = $messages;
+ } else {
+ $this->messages[$domain] = array_replace($this->messages[$domain], $messages);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addCatalogue(MessageCatalogueInterface $catalogue)
+ {
+ if ($catalogue->getLocale() !== $this->locale) {
+ throw new LogicException(sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s"', $catalogue->getLocale(), $this->locale));
+ }
+
+ foreach ($catalogue->all() as $domain => $messages) {
+ if ($intlMessages = $catalogue->all($domain.self::INTL_DOMAIN_SUFFIX)) {
+ $this->add($intlMessages, $domain.self::INTL_DOMAIN_SUFFIX);
+ $messages = array_diff_key($messages, $intlMessages);
+ }
+ $this->add($messages, $domain);
+ }
+
+ foreach ($catalogue->getResources() as $resource) {
+ $this->addResource($resource);
+ }
+
+ if ($catalogue instanceof MetadataAwareInterface) {
+ $metadata = $catalogue->getMetadata('', '');
+ $this->addMetadata($metadata);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFallbackCatalogue(MessageCatalogueInterface $catalogue)
+ {
+ // detect circular references
+ $c = $catalogue;
+ while ($c = $c->getFallbackCatalogue()) {
+ if ($c->getLocale() === $this->getLocale()) {
+ throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale()));
+ }
+ }
+
+ $c = $this;
+ do {
+ if ($c->getLocale() === $catalogue->getLocale()) {
+ throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale()));
+ }
+
+ foreach ($catalogue->getResources() as $resource) {
+ $c->addResource($resource);
+ }
+ } while ($c = $c->parent);
+
+ $catalogue->parent = $this;
+ $this->fallbackCatalogue = $catalogue;
+
+ foreach ($catalogue->getResources() as $resource) {
+ $this->addResource($resource);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFallbackCatalogue()
+ {
+ return $this->fallbackCatalogue;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getResources()
+ {
+ return array_values($this->resources);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addResource(ResourceInterface $resource)
+ {
+ $this->resources[$resource->__toString()] = $resource;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadata($key = '', $domain = 'messages')
+ {
+ if ('' == $domain) {
+ return $this->metadata;
+ }
+
+ if (isset($this->metadata[$domain])) {
+ if ('' == $key) {
+ return $this->metadata[$domain];
+ }
+
+ if (isset($this->metadata[$domain][$key])) {
+ return $this->metadata[$domain][$key];
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setMetadata($key, $value, $domain = 'messages')
+ {
+ $this->metadata[$domain][$key] = $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteMetadata($key = '', $domain = 'messages')
+ {
+ if ('' == $domain) {
+ $this->metadata = [];
+ } elseif ('' == $key) {
+ unset($this->metadata[$domain]);
+ } else {
+ unset($this->metadata[$domain][$key]);
+ }
+ }
+
+ /**
+ * Adds current values with the new values.
+ *
+ * @param array $values Values to add
+ */
+ private function addMetadata(array $values)
+ {
+ foreach ($values as $domain => $keys) {
+ foreach ($keys as $key => $value) {
+ $this->setMetadata($key, $value, $domain);
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/translation/MessageCatalogueInterface.php b/vendor/symfony/translation/MessageCatalogueInterface.php
new file mode 100755
index 0000000..f3d3f5e
--- /dev/null
+++ b/vendor/symfony/translation/MessageCatalogueInterface.php
@@ -0,0 +1,138 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\Config\Resource\ResourceInterface;
+
+/**
+ * MessageCatalogueInterface.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface MessageCatalogueInterface
+{
+ const INTL_DOMAIN_SUFFIX = '+intl-icu';
+
+ /**
+ * Gets the catalogue locale.
+ *
+ * @return string The locale
+ */
+ public function getLocale();
+
+ /**
+ * Gets the domains.
+ *
+ * @return array An array of domains
+ */
+ public function getDomains();
+
+ /**
+ * Gets the messages within a given domain.
+ *
+ * If $domain is null, it returns all messages.
+ *
+ * @param string $domain The domain name
+ *
+ * @return array An array of messages
+ */
+ public function all($domain = null);
+
+ /**
+ * Sets a message translation.
+ *
+ * @param string $id The message id
+ * @param string $translation The messages translation
+ * @param string $domain The domain name
+ */
+ public function set($id, $translation, $domain = 'messages');
+
+ /**
+ * Checks if a message has a translation.
+ *
+ * @param string $id The message id
+ * @param string $domain The domain name
+ *
+ * @return bool true if the message has a translation, false otherwise
+ */
+ public function has($id, $domain = 'messages');
+
+ /**
+ * Checks if a message has a translation (it does not take into account the fallback mechanism).
+ *
+ * @param string $id The message id
+ * @param string $domain The domain name
+ *
+ * @return bool true if the message has a translation, false otherwise
+ */
+ public function defines($id, $domain = 'messages');
+
+ /**
+ * Gets a message translation.
+ *
+ * @param string $id The message id
+ * @param string $domain The domain name
+ *
+ * @return string The message translation
+ */
+ public function get($id, $domain = 'messages');
+
+ /**
+ * Sets translations for a given domain.
+ *
+ * @param array $messages An array of translations
+ * @param string $domain The domain name
+ */
+ public function replace($messages, $domain = 'messages');
+
+ /**
+ * Adds translations for a given domain.
+ *
+ * @param array $messages An array of translations
+ * @param string $domain The domain name
+ */
+ public function add($messages, $domain = 'messages');
+
+ /**
+ * Merges translations from the given Catalogue into the current one.
+ *
+ * The two catalogues must have the same locale.
+ */
+ public function addCatalogue(self $catalogue);
+
+ /**
+ * Merges translations from the given Catalogue into the current one
+ * only when the translation does not exist.
+ *
+ * This is used to provide default translations when they do not exist for the current locale.
+ */
+ public function addFallbackCatalogue(self $catalogue);
+
+ /**
+ * Gets the fallback catalogue.
+ *
+ * @return self|null A MessageCatalogueInterface instance or null when no fallback has been set
+ */
+ public function getFallbackCatalogue();
+
+ /**
+ * Returns an array of resources loaded to build this collection.
+ *
+ * @return ResourceInterface[] An array of resources
+ */
+ public function getResources();
+
+ /**
+ * Adds a resource for this collection.
+ */
+ public function addResource(ResourceInterface $resource);
+}
diff --git a/vendor/symfony/translation/MessageSelector.php b/vendor/symfony/translation/MessageSelector.php
new file mode 100755
index 0000000..0f0febb
--- /dev/null
+++ b/vendor/symfony/translation/MessageSelector.php
@@ -0,0 +1,98 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.2, use IdentityTranslator instead.', MessageSelector::class), E_USER_DEPRECATED);
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+
+/**
+ * MessageSelector.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ *
+ * @deprecated since Symfony 4.2, use IdentityTranslator instead.
+ */
+class MessageSelector
+{
+ /**
+ * Given a message with different plural translations separated by a
+ * pipe (|), this method returns the correct portion of the message based
+ * on the given number, locale and the pluralization rules in the message
+ * itself.
+ *
+ * The message supports two different types of pluralization rules:
+ *
+ * interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples
+ * indexed: There is one apple|There are %count% apples
+ *
+ * The indexed solution can also contain labels (e.g. one: There is one apple).
+ * This is purely for making the translations more clear - it does not
+ * affect the functionality.
+ *
+ * The two methods can also be mixed:
+ * {0} There are no apples|one: There is one apple|more: There are %count% apples
+ *
+ * @param string $message The message being translated
+ * @param int|float $number The number of items represented for the message
+ * @param string $locale The locale to use for choosing
+ *
+ * @return string
+ *
+ * @throws InvalidArgumentException
+ */
+ public function choose($message, $number, $locale)
+ {
+ $parts = [];
+ if (preg_match('/^\|++$/', $message)) {
+ $parts = explode('|', $message);
+ } elseif (preg_match_all('/(?:\|\||[^\|])++/', $message, $matches)) {
+ $parts = $matches[0];
+ }
+
+ $explicitRules = [];
+ $standardRules = [];
+ foreach ($parts as $part) {
+ $part = trim(str_replace('||', '|', $part));
+
+ if (preg_match('/^(?P<interval>'.Interval::getIntervalRegexp().')\s*(?P<message>.*?)$/xs', $part, $matches)) {
+ $explicitRules[$matches['interval']] = $matches['message'];
+ } elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) {
+ $standardRules[] = $matches[1];
+ } else {
+ $standardRules[] = $part;
+ }
+ }
+
+ // try to match an explicit rule, then fallback to the standard ones
+ foreach ($explicitRules as $interval => $m) {
+ if (Interval::test($number, $interval)) {
+ return $m;
+ }
+ }
+
+ $position = PluralizationRules::get($number, $locale);
+
+ if (!isset($standardRules[$position])) {
+ // when there's exactly one rule given, and that rule is a standard
+ // rule, use this rule
+ if (1 === \count($parts) && isset($standardRules[0])) {
+ return $standardRules[0];
+ }
+
+ throw new InvalidArgumentException(sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $message, $locale, $number));
+ }
+
+ return $standardRules[$position];
+ }
+}
diff --git a/vendor/symfony/translation/MetadataAwareInterface.php b/vendor/symfony/translation/MetadataAwareInterface.php
new file mode 100755
index 0000000..e93c6fb
--- /dev/null
+++ b/vendor/symfony/translation/MetadataAwareInterface.php
@@ -0,0 +1,54 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+/**
+ * MetadataAwareInterface.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface MetadataAwareInterface
+{
+ /**
+ * Gets metadata for the given domain and key.
+ *
+ * Passing an empty domain will return an array with all metadata indexed by
+ * domain and then by key. Passing an empty key will return an array with all
+ * metadata for the given domain.
+ *
+ * @param string $key The key
+ * @param string $domain The domain name
+ *
+ * @return mixed The value that was set or an array with the domains/keys or null
+ */
+ public function getMetadata($key = '', $domain = 'messages');
+
+ /**
+ * Adds metadata to a message domain.
+ *
+ * @param string $key The key
+ * @param mixed $value The value
+ * @param string $domain The domain name
+ */
+ public function setMetadata($key, $value, $domain = 'messages');
+
+ /**
+ * Deletes metadata for the given key and domain.
+ *
+ * Passing an empty domain will delete all metadata. Passing an empty key will
+ * delete all metadata for the given domain.
+ *
+ * @param string $key The key
+ * @param string $domain The domain name
+ */
+ public function deleteMetadata($key = '', $domain = 'messages');
+}
diff --git a/vendor/symfony/translation/PluralizationRules.php b/vendor/symfony/translation/PluralizationRules.php
new file mode 100755
index 0000000..77c2760
--- /dev/null
+++ b/vendor/symfony/translation/PluralizationRules.php
@@ -0,0 +1,218 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+/**
+ * Returns the plural rules for a given locale.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @deprecated since Symfony 4.2, use IdentityTranslator instead
+ */
+class PluralizationRules
+{
+ private static $rules = [];
+
+ /**
+ * Returns the plural position to use for the given locale and number.
+ *
+ * @param int $number The number
+ * @param string $locale The locale
+ *
+ * @return int The plural position
+ */
+ public static function get($number, $locale/*, bool $triggerDeprecation = true*/)
+ {
+ if (3 > \func_num_args() || func_get_arg(2)) {
+ @trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.2.', __CLASS__), E_USER_DEPRECATED);
+ }
+
+ if ('pt_BR' === $locale) {
+ // temporary set a locale for brazilian
+ $locale = 'xbr';
+ }
+
+ if (\strlen($locale) > 3) {
+ $locale = substr($locale, 0, -\strlen(strrchr($locale, '_')));
+ }
+
+ if (isset(self::$rules[$locale])) {
+ $return = self::$rules[$locale]($number);
+
+ if (!\is_int($return) || $return < 0) {
+ return 0;
+ }
+
+ return $return;
+ }
+
+ /*
+ * The plural rules are derived from code of the Zend Framework (2010-09-25),
+ * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd).
+ * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ */
+ switch ($locale) {
+ case 'az':
+ case 'bo':
+ case 'dz':
+ case 'id':
+ case 'ja':
+ case 'jv':
+ case 'ka':
+ case 'km':
+ case 'kn':
+ case 'ko':
+ case 'ms':
+ case 'th':
+ case 'tr':
+ case 'vi':
+ case 'zh':
+ return 0;
+
+ case 'af':
+ case 'bn':
+ case 'bg':
+ case 'ca':
+ case 'da':
+ case 'de':
+ case 'el':
+ case 'en':
+ case 'eo':
+ case 'es':
+ case 'et':
+ case 'eu':
+ case 'fa':
+ case 'fi':
+ case 'fo':
+ case 'fur':
+ case 'fy':
+ case 'gl':
+ case 'gu':
+ case 'ha':
+ case 'he':
+ case 'hu':
+ case 'is':
+ case 'it':
+ case 'ku':
+ case 'lb':
+ case 'ml':
+ case 'mn':
+ case 'mr':
+ case 'nah':
+ case 'nb':
+ case 'ne':
+ case 'nl':
+ case 'nn':
+ case 'no':
+ case 'oc':
+ case 'om':
+ case 'or':
+ case 'pa':
+ case 'pap':
+ case 'ps':
+ case 'pt':
+ case 'so':
+ case 'sq':
+ case 'sv':
+ case 'sw':
+ case 'ta':
+ case 'te':
+ case 'tk':
+ case 'ur':
+ case 'zu':
+ return (1 == $number) ? 0 : 1;
+
+ case 'am':
+ case 'bh':
+ case 'fil':
+ case 'fr':
+ case 'gun':
+ case 'hi':
+ case 'hy':
+ case 'ln':
+ case 'mg':
+ case 'nso':
+ case 'xbr':
+ case 'ti':
+ case 'wa':
+ return ((0 == $number) || (1 == $number)) ? 0 : 1;
+
+ case 'be':
+ case 'bs':
+ case 'hr':
+ case 'ru':
+ case 'sh':
+ case 'sr':
+ case 'uk':
+ return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
+
+ case 'cs':
+ case 'sk':
+ return (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2);
+
+ case 'ga':
+ return (1 == $number) ? 0 : ((2 == $number) ? 1 : 2);
+
+ case 'lt':
+ return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
+
+ case 'sl':
+ return (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3));
+
+ case 'mk':
+ return (1 == $number % 10) ? 0 : 1;
+
+ case 'mt':
+ return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3));
+
+ case 'lv':
+ return (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2);
+
+ case 'pl':
+ return (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2);
+
+ case 'cy':
+ return (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3));
+
+ case 'ro':
+ return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2);
+
+ case 'ar':
+ return (0 == $number) ? 0 : ((1 == $number) ? 1 : ((2 == $number) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5))));
+
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Overrides the default plural rule for a given locale.
+ *
+ * @param callable $rule A PHP callable
+ * @param string $locale The locale
+ */
+ public static function set(callable $rule, $locale)
+ {
+ @trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.2.', __CLASS__), E_USER_DEPRECATED);
+
+ if ('pt_BR' === $locale) {
+ // temporary set a locale for brazilian
+ $locale = 'xbr';
+ }
+
+ if (\strlen($locale) > 3) {
+ $locale = substr($locale, 0, -\strlen(strrchr($locale, '_')));
+ }
+
+ self::$rules[$locale] = $rule;
+ }
+}
diff --git a/vendor/symfony/translation/README.md b/vendor/symfony/translation/README.md
new file mode 100755
index 0000000..46f3d1f
--- /dev/null
+++ b/vendor/symfony/translation/README.md
@@ -0,0 +1,13 @@
+Translation Component
+=====================
+
+The Translation component provides tools to internationalize your application.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/translation/index.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/vendor/symfony/translation/Reader/TranslationReader.php b/vendor/symfony/translation/Reader/TranslationReader.php
new file mode 100755
index 0000000..e4554f3
--- /dev/null
+++ b/vendor/symfony/translation/Reader/TranslationReader.php
@@ -0,0 +1,63 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Reader;
+
+use Symfony\Component\Finder\Finder;
+use Symfony\Component\Translation\Loader\LoaderInterface;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * TranslationReader reads translation messages from translation files.
+ *
+ * @author Michel Salib <michelsalib@hotmail.com>
+ */
+class TranslationReader implements TranslationReaderInterface
+{
+ /**
+ * Loaders used for import.
+ *
+ * @var array
+ */
+ private $loaders = [];
+
+ /**
+ * Adds a loader to the translation extractor.
+ *
+ * @param string $format The format of the loader
+ * @param LoaderInterface $loader
+ */
+ public function addLoader($format, LoaderInterface $loader)
+ {
+ $this->loaders[$format] = $loader;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($directory, MessageCatalogue $catalogue)
+ {
+ if (!is_dir($directory)) {
+ return;
+ }
+
+ foreach ($this->loaders as $format => $loader) {
+ // load any existing translation files
+ $finder = new Finder();
+ $extension = $catalogue->getLocale().'.'.$format;
+ $files = $finder->files()->name('*.'.$extension)->in($directory);
+ foreach ($files as $file) {
+ $domain = substr($file->getFilename(), 0, -1 * \strlen($extension) - 1);
+ $catalogue->addCatalogue($loader->load($file->getPathname(), $catalogue->getLocale(), $domain));
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Reader/TranslationReaderInterface.php b/vendor/symfony/translation/Reader/TranslationReaderInterface.php
new file mode 100755
index 0000000..0aa55c6
--- /dev/null
+++ b/vendor/symfony/translation/Reader/TranslationReaderInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Reader;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * TranslationReader reads translation messages from translation files.
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+interface TranslationReaderInterface
+{
+ /**
+ * Reads translation messages from a directory to the catalogue.
+ *
+ * @param string $directory
+ * @param MessageCatalogue $catalogue
+ */
+ public function read($directory, MessageCatalogue $catalogue);
+}
diff --git a/vendor/symfony/translation/Resources/bin/translation-status.php b/vendor/symfony/translation/Resources/bin/translation-status.php
new file mode 100755
index 0000000..96ccc10
--- /dev/null
+++ b/vendor/symfony/translation/Resources/bin/translation-status.php
@@ -0,0 +1,200 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+$usageInstructions = <<<END
+
+ Usage instructions
+ -------------------------------------------------------------------------------
+
+ $ cd symfony-code-root-directory/
+
+ # show the translation status of all locales
+ $ php translation-status.php
+
+ # show the translation status of all locales and all their missing translations
+ $ php translation-status.php -v
+
+ # show the status of a single locale
+ $ php translation-status.php fr
+
+ # show the status of a single locale and all its missing translations
+ $ php translation-status.php fr -v
+
+END;
+
+$config = [
+ // if TRUE, the full list of missing translations is displayed
+ 'verbose_output' => false,
+ // NULL = analyze all locales
+ 'locale_to_analyze' => null,
+ // the reference files all the other translations are compared to
+ 'original_files' => [
+ 'src/Symfony/Component/Form/Resources/translations/validators.en.xlf',
+ 'src/Symfony/Component/Security/Core/Resources/translations/security.en.xlf',
+ 'src/Symfony/Component/Validator/Resources/translations/validators.en.xlf',
+ ],
+];
+
+$argc = $_SERVER['argc'];
+$argv = $_SERVER['argv'];
+
+if ($argc > 3) {
+ echo str_replace('translation-status.php', $argv[0], $usageInstructions);
+ exit(1);
+}
+
+foreach (array_slice($argv, 1) as $argumentOrOption) {
+ if (0 === strpos($argumentOrOption, '-')) {
+ $config['verbose_output'] = true;
+ } else {
+ $config['locale_to_analyze'] = $argumentOrOption;
+ }
+}
+
+foreach ($config['original_files'] as $originalFilePath) {
+ if (!file_exists($originalFilePath)) {
+ echo sprintf('The following file does not exist. Make sure that you execute this command at the root dir of the Symfony code repository.%s %s', PHP_EOL, $originalFilePath);
+ exit(1);
+ }
+}
+
+$totalMissingTranslations = 0;
+
+foreach ($config['original_files'] as $originalFilePath) {
+ $translationFilePaths = findTranslationFiles($originalFilePath, $config['locale_to_analyze']);
+ $translationStatus = calculateTranslationStatus($originalFilePath, $translationFilePaths);
+
+ $totalMissingTranslations += array_sum(array_map(function ($translation) {
+ return count($translation['missingKeys']);
+ }, array_values($translationStatus)));
+
+ printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output']);
+}
+
+exit($totalMissingTranslations > 0 ? 1 : 0);
+
+function findTranslationFiles($originalFilePath, $localeToAnalyze)
+{
+ $translations = [];
+
+ $translationsDir = dirname($originalFilePath);
+ $originalFileName = basename($originalFilePath);
+ $translationFileNamePattern = str_replace('.en.', '.*.', $originalFileName);
+
+ $translationFiles = glob($translationsDir.'/'.$translationFileNamePattern);
+ foreach ($translationFiles as $filePath) {
+ $locale = extractLocaleFromFilePath($filePath);
+
+ if (null !== $localeToAnalyze && $locale !== $localeToAnalyze) {
+ continue;
+ }
+
+ $translations[$locale] = $filePath;
+ }
+
+ return $translations;
+}
+
+function calculateTranslationStatus($originalFilePath, $translationFilePaths)
+{
+ $translationStatus = [];
+ $allTranslationKeys = extractTranslationKeys($originalFilePath);
+
+ foreach ($translationFilePaths as $locale => $translationPath) {
+ $translatedKeys = extractTranslationKeys($translationPath);
+ $missingKeys = array_diff_key($allTranslationKeys, $translatedKeys);
+
+ $translationStatus[$locale] = [
+ 'total' => count($allTranslationKeys),
+ 'translated' => count($translatedKeys),
+ 'missingKeys' => $missingKeys,
+ ];
+ }
+
+ return $translationStatus;
+}
+
+function printTranslationStatus($originalFilePath, $translationStatus, $verboseOutput)
+{
+ printTitle($originalFilePath);
+ printTable($translationStatus, $verboseOutput);
+ echo PHP_EOL.PHP_EOL;
+}
+
+function extractLocaleFromFilePath($filePath)
+{
+ $parts = explode('.', $filePath);
+
+ return $parts[count($parts) - 2];
+}
+
+function extractTranslationKeys($filePath)
+{
+ $translationKeys = [];
+ $contents = new \SimpleXMLElement(file_get_contents($filePath));
+
+ foreach ($contents->file->body->{'trans-unit'} as $translationKey) {
+ $translationId = (string) $translationKey['id'];
+ $translationKey = (string) $translationKey->source;
+
+ $translationKeys[$translationId] = $translationKey;
+ }
+
+ return $translationKeys;
+}
+
+function printTitle($title)
+{
+ echo $title.PHP_EOL;
+ echo str_repeat('=', strlen($title)).PHP_EOL.PHP_EOL;
+}
+
+function printTable($translations, $verboseOutput)
+{
+ if (0 === count($translations)) {
+ echo 'No translations found';
+
+ return;
+ }
+ $longestLocaleNameLength = max(array_map('strlen', array_keys($translations)));
+
+ foreach ($translations as $locale => $translation) {
+ $isTranslationCompleted = $translation['translated'] === $translation['total'];
+ if ($isTranslationCompleted) {
+ textColorGreen();
+ }
+
+ echo sprintf('| Locale: %-'.$longestLocaleNameLength.'s | Translated: %d/%d', $locale, $translation['translated'], $translation['total']).PHP_EOL;
+
+ textColorNormal();
+
+ if (true === $verboseOutput && count($translation['missingKeys']) > 0) {
+ echo str_repeat('-', 80).PHP_EOL;
+ echo '| Missing Translations:'.PHP_EOL;
+
+ foreach ($translation['missingKeys'] as $id => $content) {
+ echo sprintf('| (id=%s) %s', $id, $content).PHP_EOL;
+ }
+
+ echo str_repeat('-', 80).PHP_EOL;
+ }
+ }
+}
+
+function textColorGreen()
+{
+ echo "\033[32m";
+}
+
+function textColorNormal()
+{
+ echo "\033[0m";
+}
diff --git a/vendor/symfony/translation/Resources/data/parents.json b/vendor/symfony/translation/Resources/data/parents.json
new file mode 100755
index 0000000..6e45d9f
--- /dev/null
+++ b/vendor/symfony/translation/Resources/data/parents.json
@@ -0,0 +1,136 @@
+{
+ "az_Cyrl": "root",
+ "bs_Cyrl": "root",
+ "en_150": "en_001",
+ "en_AG": "en_001",
+ "en_AI": "en_001",
+ "en_AT": "en_150",
+ "en_AU": "en_001",
+ "en_BB": "en_001",
+ "en_BE": "en_150",
+ "en_BM": "en_001",
+ "en_BS": "en_001",
+ "en_BW": "en_001",
+ "en_BZ": "en_001",
+ "en_CA": "en_001",
+ "en_CC": "en_001",
+ "en_CH": "en_150",
+ "en_CK": "en_001",
+ "en_CM": "en_001",
+ "en_CX": "en_001",
+ "en_CY": "en_001",
+ "en_DE": "en_150",
+ "en_DG": "en_001",
+ "en_DK": "en_150",
+ "en_DM": "en_001",
+ "en_ER": "en_001",
+ "en_FI": "en_150",
+ "en_FJ": "en_001",
+ "en_FK": "en_001",
+ "en_FM": "en_001",
+ "en_GB": "en_001",
+ "en_GD": "en_001",
+ "en_GG": "en_001",
+ "en_GH": "en_001",
+ "en_GI": "en_001",
+ "en_GM": "en_001",
+ "en_GY": "en_001",
+ "en_HK": "en_001",
+ "en_IE": "en_001",
+ "en_IL": "en_001",
+ "en_IM": "en_001",
+ "en_IN": "en_001",
+ "en_IO": "en_001",
+ "en_JE": "en_001",
+ "en_JM": "en_001",
+ "en_KE": "en_001",
+ "en_KI": "en_001",
+ "en_KN": "en_001",
+ "en_KY": "en_001",
+ "en_LC": "en_001",
+ "en_LR": "en_001",
+ "en_LS": "en_001",
+ "en_MG": "en_001",
+ "en_MO": "en_001",
+ "en_MS": "en_001",
+ "en_MT": "en_001",
+ "en_MU": "en_001",
+ "en_MW": "en_001",
+ "en_MY": "en_001",
+ "en_NA": "en_001",
+ "en_NF": "en_001",
+ "en_NG": "en_001",
+ "en_NL": "en_150",
+ "en_NR": "en_001",
+ "en_NU": "en_001",
+ "en_NZ": "en_001",
+ "en_PG": "en_001",
+ "en_PH": "en_001",
+ "en_PK": "en_001",
+ "en_PN": "en_001",
+ "en_PW": "en_001",
+ "en_RW": "en_001",
+ "en_SB": "en_001",
+ "en_SC": "en_001",
+ "en_SD": "en_001",
+ "en_SE": "en_150",
+ "en_SG": "en_001",
+ "en_SH": "en_001",
+ "en_SI": "en_150",
+ "en_SL": "en_001",
+ "en_SS": "en_001",
+ "en_SX": "en_001",
+ "en_SZ": "en_001",
+ "en_TC": "en_001",
+ "en_TK": "en_001",
+ "en_TO": "en_001",
+ "en_TT": "en_001",
+ "en_TV": "en_001",
+ "en_TZ": "en_001",
+ "en_UG": "en_001",
+ "en_VC": "en_001",
+ "en_VG": "en_001",
+ "en_VU": "en_001",
+ "en_WS": "en_001",
+ "en_ZA": "en_001",
+ "en_ZM": "en_001",
+ "en_ZW": "en_001",
+ "es_AR": "es_419",
+ "es_BO": "es_419",
+ "es_BR": "es_419",
+ "es_BZ": "es_419",
+ "es_CL": "es_419",
+ "es_CO": "es_419",
+ "es_CR": "es_419",
+ "es_CU": "es_419",
+ "es_DO": "es_419",
+ "es_EC": "es_419",
+ "es_GT": "es_419",
+ "es_HN": "es_419",
+ "es_MX": "es_419",
+ "es_NI": "es_419",
+ "es_PA": "es_419",
+ "es_PE": "es_419",
+ "es_PR": "es_419",
+ "es_PY": "es_419",
+ "es_SV": "es_419",
+ "es_US": "es_419",
+ "es_UY": "es_419",
+ "es_VE": "es_419",
+ "pa_Arab": "root",
+ "pt_AO": "pt_PT",
+ "pt_CH": "pt_PT",
+ "pt_CV": "pt_PT",
+ "pt_GQ": "pt_PT",
+ "pt_GW": "pt_PT",
+ "pt_LU": "pt_PT",
+ "pt_MO": "pt_PT",
+ "pt_MZ": "pt_PT",
+ "pt_ST": "pt_PT",
+ "pt_TL": "pt_PT",
+ "sr_Latn": "root",
+ "uz_Arab": "root",
+ "uz_Cyrl": "root",
+ "zh_Hant": "root",
+ "zh_Hant_MO": "zh_Hant_HK"
+}
diff --git a/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd b/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd
new file mode 100755
index 0000000..dface62
--- /dev/null
+++ b/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd
@@ -0,0 +1,2223 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+
+May-19-2004:
+- Changed the <choice> for ElemType_header, moving minOccurs="0" maxOccurs="unbounded" from its elements
+to <choice> itself.
+- Added <choice> for ElemType_trans-unit to allow "any order" for <context-group>, <count-group>, <prop-group>, <note>, and
+<alt-trans>.
+
+Oct-2005
+- updated version info to 1.2
+- equiv-trans attribute to <trans-unit> element
+- merged-trans attribute for <group> element
+- Add the <seg-source> element as optional in the <trans-unit> and <alt-trans> content models, at the same level as <source>
+- Create a new value "seg" for the mtype attribute of the <mrk> element
+- Add mid as an optional attribute for the <alt-trans> element
+
+Nov-14-2005
+- Changed name attribute for <context-group> from required to optional
+- Added extension point at <xliff>
+
+Jan-9-2006
+- Added alttranstype type attribute to <alt-trans>, and values
+
+Jan-10-2006
+- Corrected error with overwritten purposeValueList
+- Corrected name="AttrType_Version", attribute should have been "name"
+
+-->
+<xsd:schema xmlns:xlf="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="urn:oasis:names:tc:xliff:document:1.2" xml:lang="en">
+ <!-- Import for xml:lang and xml:space -->
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+ <!-- Attributes Lists -->
+ <xsd:simpleType name="XTend">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="x-[^\s]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="context-typeValueList">
+ <xsd:annotation>
+ <xsd:documentation>Values for the attribute 'context-type'.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="database">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a database content.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="element">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the content of an element within an XML document.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="elementtitle">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the name of an element within an XML document.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="linenumber">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the line number from the sourcefile (see context-type="sourcefile") where the <source> is found.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="numparams">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a the number of parameters contained within the <source>.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="paramnotes">
+ <xsd:annotation>
+ <xsd:documentation>Indicates notes pertaining to the parameters in the <source>.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="record">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the content of a record within a database.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="recordtitle">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the name of a record within a database.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="sourcefile">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the original source file in the case that multiple files are merged to form the original file from which the XLIFF file is created. This differs from the original <file> attribute in that this sourcefile is one of many that make up that file.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="count-typeValueList">
+ <xsd:annotation>
+ <xsd:documentation>Values for the attribute 'count-type'.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="num-usages">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the count units are items that are used X times in a certain context; example: this is a reusable text unit which is used 42 times in other texts.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="repetition">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the count units are translation units existing already in the same document.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="total">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a total count.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="InlineDelimitersValueList">
+ <xsd:annotation>
+ <xsd:documentation>Values for the attribute 'ctype' when used other elements than <ph> or <x>.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="bold">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a run of bolded text.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="italic">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a run of text in italics.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="underlined">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a run of underlined text.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="link">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a run of hyper-text.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="InlinePlaceholdersValueList">
+ <xsd:annotation>
+ <xsd:documentation>Values for the attribute 'ctype' when used with <ph> or <x>.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="image">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a inline image.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="pb">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a page break.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="lb">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a line break.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="mime-typeValueList">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="(text|multipart|message|application|image|audio|video|model)(/.+)*"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="datatypeValueList">
+ <xsd:annotation>
+ <xsd:documentation>Values for the attribute 'datatype'.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="asp">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Active Server Page data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="c">
+ <xsd:annotation>
+ <xsd:documentation>Indicates C source file data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="cdf">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Channel Definition Format (CDF) data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="cfm">
+ <xsd:annotation>
+ <xsd:documentation>Indicates ColdFusion data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="cpp">
+ <xsd:annotation>
+ <xsd:documentation>Indicates C++ source file data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="csharp">
+ <xsd:annotation>
+ <xsd:documentation>Indicates C-Sharp data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="cstring">
+ <xsd:annotation>
+ <xsd:documentation>Indicates strings from C, ASM, and driver files data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="csv">
+ <xsd:annotation>
+ <xsd:documentation>Indicates comma-separated values data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="database">
+ <xsd:annotation>
+ <xsd:documentation>Indicates database data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="documentfooter">
+ <xsd:annotation>
+ <xsd:documentation>Indicates portions of document that follows data and contains metadata.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="documentheader">
+ <xsd:annotation>
+ <xsd:documentation>Indicates portions of document that precedes data and contains metadata.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="filedialog">
+ <xsd:annotation>
+ <xsd:documentation>Indicates data from standard UI file operations dialogs (e.g., Open, Save, Save As, Export, Import).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="form">
+ <xsd:annotation>
+ <xsd:documentation>Indicates standard user input screen data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="html">
+ <xsd:annotation>
+ <xsd:documentation>Indicates HyperText Markup Language (HTML) data - document instance.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="htmlbody">
+ <xsd:annotation>
+ <xsd:documentation>Indicates content within an HTML document’s <body> element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="ini">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Windows INI file data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="interleaf">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Interleaf data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="javaclass">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Java source file data (extension '.java').</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="javapropertyresourcebundle">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Java property resource bundle data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="javalistresourcebundle">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Java list resource bundle data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="javascript">
+ <xsd:annotation>
+ <xsd:documentation>Indicates JavaScript source file data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="jscript">
+ <xsd:annotation>
+ <xsd:documentation>Indicates JScript source file data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="layout">
+ <xsd:annotation>
+ <xsd:documentation>Indicates information relating to formatting.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="lisp">
+ <xsd:annotation>
+ <xsd:documentation>Indicates LISP source file data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="margin">
+ <xsd:annotation>
+ <xsd:documentation>Indicates information relating to margin formats.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="menufile">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a file containing menu.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="messagefile">
+ <xsd:annotation>
+ <xsd:documentation>Indicates numerically identified string table.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="mif">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Maker Interchange Format (MIF) data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="mimetype">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that the datatype attribute value is a MIME Type value and is defined in the mime-type attribute.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="mo">
+ <xsd:annotation>
+ <xsd:documentation>Indicates GNU Machine Object data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="msglib">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Message Librarian strings created by Novell's Message Librarian Tool.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="pagefooter">
+ <xsd:annotation>
+ <xsd:documentation>Indicates information to be displayed at the bottom of each page of a document.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="pageheader">
+ <xsd:annotation>
+ <xsd:documentation>Indicates information to be displayed at the top of each page of a document.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="parameters">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a list of property values (e.g., settings within INI files or preferences dialog).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="pascal">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Pascal source file data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="php">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Hypertext Preprocessor data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="plaintext">
+ <xsd:annotation>
+ <xsd:documentation>Indicates plain text file (no formatting other than, possibly, wrapping).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="po">
+ <xsd:annotation>
+ <xsd:documentation>Indicates GNU Portable Object file.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="report">
+ <xsd:annotation>
+ <xsd:documentation>Indicates dynamically generated user defined document. e.g. Oracle Report, Crystal Report, etc.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="resources">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Windows .NET binary resources.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="resx">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Windows .NET Resources.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="rtf">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Rich Text Format (RTF) data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="sgml">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Standard Generalized Markup Language (SGML) data - document instance.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="sgmldtd">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Standard Generalized Markup Language (SGML) data - Document Type Definition (DTD).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="svg">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Scalable Vector Graphic (SVG) data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="vbscript">
+ <xsd:annotation>
+ <xsd:documentation>Indicates VisualBasic Script source file.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="warning">
+ <xsd:annotation>
+ <xsd:documentation>Indicates warning message.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="winres">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Windows (Win32) resources (i.e. resources extracted from an RC script, a message file, or a compiled file).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="xhtml">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Extensible HyperText Markup Language (XHTML) data - document instance.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="xml">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Extensible Markup Language (XML) data - document instance.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="xmldtd">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Extensible Markup Language (XML) data - Document Type Definition (DTD).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="xsl">
+ <xsd:annotation>
+ <xsd:documentation>Indicates Extensible Stylesheet Language (XSL) data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="xul">
+ <xsd:annotation>
+ <xsd:documentation>Indicates XUL elements.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="mtypeValueList">
+ <xsd:annotation>
+ <xsd:documentation>Values for the attribute 'mtype'.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="abbrev">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the marked text is an abbreviation.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="abbreviated-form">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.8: A term resulting from the omission of any part of the full term while designating the same concept.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="abbreviation">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.8.1: An abbreviated form of a simple term resulting from the omission of some of its letters (e.g. 'adj.' for 'adjective').</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="acronym">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.8.4: An abbreviated form of a term made up of letters from the full form of a multiword term strung together into a sequence pronounced only syllabically (e.g. 'radar' for 'radio detecting and ranging').</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="appellation">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620: A proper-name term, such as the name of an agency or other proper entity.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="collocation">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.18.1: A recurrent word combination characterized by cohesion in that the components of the collocation must co-occur within an utterance or series of utterances, even though they do not necessarily have to maintain immediate proximity to one another.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="common-name">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.5: A synonym for an international scientific term that is used in general discourse in a given language.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="datetime">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the marked text is a date and/or time.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="equation">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.15: An expression used to represent a concept based on a statement that two mathematical expressions are, for instance, equal as identified by the equal sign (=), or assigned to one another by a similar sign.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="expanded-form">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.7: The complete representation of a term for which there is an abbreviated form.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="formula">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.14: Figures, symbols or the like used to express a concept briefly, such as a mathematical or chemical formula.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="head-term">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.1: The concept designation that has been chosen to head a terminological record.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="initialism">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.8.3: An abbreviated form of a term consisting of some of the initial letters of the words making up a multiword term or the term elements making up a compound term when these letters are pronounced individually (e.g. 'BSE' for 'bovine spongiform encephalopathy').</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="international-scientific-term">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.4: A term that is part of an international scientific nomenclature as adopted by an appropriate scientific body.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="internationalism">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.6: A term that has the same or nearly identical orthographic or phonemic form in many languages.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="logical-expression">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.16: An expression used to represent a concept based on mathematical or logical relations, such as statements of inequality, set relationships, Boolean operations, and the like.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="materials-management-unit">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.17: A unit to track object.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="name">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the marked text is a name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="near-synonym">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.3: A term that represents the same or a very similar concept as another term in the same language, but for which interchangeability is limited to some contexts and inapplicable in others.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="part-number">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.17.2: A unique alphanumeric designation assigned to an object in a manufacturing system.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="phrase">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the marked text is a phrase.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="phraseological-unit">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.18: Any group of two or more words that form a unit, the meaning of which frequently cannot be deduced based on the combined sense of the words making up the phrase.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="protected">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the marked text should not be translated.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="romanized-form">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.12: A form of a term resulting from an operation whereby non-Latin writing systems are converted to the Latin alphabet.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="seg">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that the marked text represents a segment.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="set-phrase">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.18.2: A fixed, lexicalized phrase.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="short-form">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.8.2: A variant of a multiword term that includes fewer words than the full form of the term (e.g. 'Group of Twenty-four' for 'Intergovernmental Group of Twenty-four on International Monetary Affairs').</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="sku">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.17.1: Stock keeping unit, an inventory item identified by a unique alphanumeric designation assigned to an object in an inventory control system.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="standard-text">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.19: A fixed chunk of recurring text.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="symbol">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.13: A designation of a concept by letters, numerals, pictograms or any combination thereof.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="synonym">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.2: Any term that represents the same or a very similar concept as the main entry term in a term entry.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="synonymous-phrase">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.18.3: Phraseological unit in a language that expresses the same semantic content as another phrase in that same language.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="term">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the marked text is a term.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="transcribed-form">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.11: A form of a term resulting from an operation whereby the characters of one writing system are represented by characters from another writing system, taking into account the pronunciation of the characters converted.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="transliterated-form">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.10: A form of a term resulting from an operation whereby the characters of an alphabetic writing system are represented by characters from another alphabetic writing system.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="truncated-term">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.8.5: An abbreviated form of a term resulting from the omission of one or more term elements or syllables (e.g. 'flu' for 'influenza').</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="variant">
+ <xsd:annotation>
+ <xsd:documentation>ISO-12620 2.1.9: One of the alternate forms of a term.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="restypeValueList">
+ <xsd:annotation>
+ <xsd:documentation>Values for the attribute 'restype'.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="auto3state">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC AUTO3STATE control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="autocheckbox">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC AUTOCHECKBOX control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="autoradiobutton">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC AUTORADIOBUTTON control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="bedit">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC BEDIT control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="bitmap">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a bitmap, for example a BITMAP resource in Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="button">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a button object, for example a BUTTON control Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="caption">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a caption, such as the caption of a dialog box.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="cell">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the cell in a table, for example the content of the <td> element in HTML.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="checkbox">
+ <xsd:annotation>
+ <xsd:documentation>Indicates check box object, for example a CHECKBOX control in Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="checkboxmenuitem">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a menu item with an associated checkbox.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="checkedlistbox">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a list box, but with a check-box for each item.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="colorchooser">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a color selection dialog.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="combobox">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a combination of edit box and listbox object, for example a COMBOBOX control in Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="comboboxexitem">
+ <xsd:annotation>
+ <xsd:documentation>Indicates an initialization entry of an extended combobox DLGINIT resource block. (code 0x1234).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="comboboxitem">
+ <xsd:annotation>
+ <xsd:documentation>Indicates an initialization entry of a combobox DLGINIT resource block (code 0x0403).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="component">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a UI base class element that cannot be represented by any other element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="contextmenu">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a context menu.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="ctext">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC CTEXT control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="cursor">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a cursor, for example a CURSOR resource in Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="datetimepicker">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a date/time picker.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="defpushbutton">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC DEFPUSHBUTTON control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="dialog">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a dialog box.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="dlginit">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC DLGINIT resource block.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="edit">
+ <xsd:annotation>
+ <xsd:documentation>Indicates an edit box object, for example an EDIT control in Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="file">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a filename.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="filechooser">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a file dialog.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="fn">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a footnote.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="font">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a font name.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="footer">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a footer.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="frame">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a frame object.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="grid">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a XUL grid element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="groupbox">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a groupbox object, for example a GROUPBOX control in Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="header">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a header item.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="heading">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a heading, such has the content of <h1>, <h2>, etc. in HTML.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="hedit">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC HEDIT control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="hscrollbar">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a horizontal scrollbar.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="icon">
+ <xsd:annotation>
+ <xsd:documentation>Indicates an icon, for example an ICON resource in Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="iedit">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC IEDIT control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="keywords">
+ <xsd:annotation>
+ <xsd:documentation>Indicates keyword list, such as the content of the Keywords meta-data in HTML, or a K footnote in WinHelp RTF.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="label">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a label object.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="linklabel">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a label that is also a HTML link (not necessarily a URL).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="list">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a list (a group of list-items, for example an <ol> or <ul> element in HTML).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="listbox">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a listbox object, for example an LISTBOX control in Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="listitem">
+ <xsd:annotation>
+ <xsd:documentation>Indicates an list item (an entry in a list).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="ltext">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC LTEXT control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="menu">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a menu (a group of menu-items).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="menubar">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a toolbar containing one or more tope level menus.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="menuitem">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a menu item (an entry in a menu).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="menuseparator">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a XUL menuseparator element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="message">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a message, for example an entry in a MESSAGETABLE resource in Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="monthcalendar">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a calendar control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="numericupdown">
+ <xsd:annotation>
+ <xsd:documentation>Indicates an edit box beside a spin control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="panel">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a catch all for rectangular areas.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="popupmenu">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a standalone menu not necessarily associated with a menubar.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="pushbox">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a pushbox object, for example a PUSHBOX control in Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="pushbutton">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC PUSHBUTTON control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="radio">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a radio button object.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="radiobuttonmenuitem">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a menuitem with associated radio button.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="rcdata">
+ <xsd:annotation>
+ <xsd:documentation>Indicates raw data resources for an application.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="row">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a row in a table.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="rtext">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC RTEXT control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="scrollpane">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a user navigable container used to show a portion of a document.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="separator">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a generic divider object (e.g. menu group separator).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="shortcut">
+ <xsd:annotation>
+ <xsd:documentation>Windows accelerators, shortcuts in resource or property files.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="spinner">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a UI control to indicate process activity but not progress.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="splitter">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a splitter bar.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="state3">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC STATE3 control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="statusbar">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a window for providing feedback to the users, like 'read-only', etc.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="string">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a string, for example an entry in a STRINGTABLE resource in Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="tabcontrol">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a layers of controls with a tab to select layers.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="table">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a display and edits regular two-dimensional tables of cells.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="textbox">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a XUL textbox element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="togglebutton">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a UI button that can be toggled to on or off state.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="toolbar">
+ <xsd:annotation>
+ <xsd:documentation>Indicates an array of controls, usually buttons.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="tooltip">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a pop up tool tip text.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="trackbar">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a bar with a pointer indicating a position within a certain range.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="tree">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a control that displays a set of hierarchical data.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="uri">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a URI (URN or URL).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="userbutton">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a Windows RC USERBUTTON control.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="usercontrol">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a user-defined control like CONTROL control in Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="var">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the text of a variable.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="versioninfo">
+ <xsd:annotation>
+ <xsd:documentation>Indicates version information about a resource like VERSIONINFO in Windows.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="vscrollbar">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a vertical scrollbar.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="window">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a graphical window.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="size-unitValueList">
+ <xsd:annotation>
+ <xsd:documentation>Values for the attribute 'size-unit'.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="byte">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in 8-bit bytes.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="char">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in Unicode characters.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="col">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in columns. Used for HTML text area.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="cm">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in centimeters.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="dlgunit">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in dialog units, as defined in Windows resources.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="em">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in 'font-size' units (as defined in CSS).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="ex">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in 'x-height' units (as defined in CSS).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="glyph">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in glyphs. A glyph is considered to be one or more combined Unicode characters that represent a single displayable text character. Sometimes referred to as a 'grapheme cluster'</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="in">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in inches.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="mm">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in millimeters.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="percent">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in percentage.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="pixel">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in pixels.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="point">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in point.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="row">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a size in rows. Used for HTML text area.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="stateValueList">
+ <xsd:annotation>
+ <xsd:documentation>Values for the attribute 'state'.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="final">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the terminating state.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="needs-adaptation">
+ <xsd:annotation>
+ <xsd:documentation>Indicates only non-textual information needs adaptation.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="needs-l10n">
+ <xsd:annotation>
+ <xsd:documentation>Indicates both text and non-textual information needs adaptation.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="needs-review-adaptation">
+ <xsd:annotation>
+ <xsd:documentation>Indicates only non-textual information needs review.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="needs-review-l10n">
+ <xsd:annotation>
+ <xsd:documentation>Indicates both text and non-textual information needs review.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="needs-review-translation">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that only the text of the item needs to be reviewed.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="needs-translation">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that the item needs to be translated.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="new">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that the item is new. For example, translation units that were not in a previous version of the document.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="signed-off">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that changes are reviewed and approved.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="translated">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that the item has been translated.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="state-qualifierValueList">
+ <xsd:annotation>
+ <xsd:documentation>Values for the attribute 'state-qualifier'.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="exact-match">
+ <xsd:annotation>
+ <xsd:documentation>Indicates an exact match. An exact match occurs when a source text of a segment is exactly the same as the source text of a segment that was translated previously.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="fuzzy-match">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a fuzzy match. A fuzzy match occurs when a source text of a segment is very similar to the source text of a segment that was translated previously (e.g. when the difference is casing, a few changed words, white-space discripancy, etc.).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="id-match">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a match based on matching IDs (in addition to matching text).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="leveraged-glossary">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a translation derived from a glossary.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="leveraged-inherited">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a translation derived from existing translation.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="leveraged-mt">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a translation derived from machine translation.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="leveraged-repository">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a translation derived from a translation repository.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="leveraged-tm">
+ <xsd:annotation>
+ <xsd:documentation>Indicates a translation derived from a translation memory.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="mt-suggestion">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the translation is suggested by machine translation.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="rejected-grammar">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that the item has been rejected because of incorrect grammar.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="rejected-inaccurate">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that the item has been rejected because it is incorrect.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="rejected-length">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that the item has been rejected because it is too long or too short.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="rejected-spelling">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that the item has been rejected because of incorrect spelling.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="tm-suggestion">
+ <xsd:annotation>
+ <xsd:documentation>Indicates the translation is suggested by translation memory.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="unitValueList">
+ <xsd:annotation>
+ <xsd:documentation>Values for the attribute 'unit'.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="word">
+ <xsd:annotation>
+ <xsd:documentation>Refers to words.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="page">
+ <xsd:annotation>
+ <xsd:documentation>Refers to pages.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="trans-unit">
+ <xsd:annotation>
+ <xsd:documentation>Refers to <trans-unit> elements.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="bin-unit">
+ <xsd:annotation>
+ <xsd:documentation>Refers to <bin-unit> elements.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="glyph">
+ <xsd:annotation>
+ <xsd:documentation>Refers to glyphs.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="item">
+ <xsd:annotation>
+ <xsd:documentation>Refers to <trans-unit> and/or <bin-unit> elements.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="instance">
+ <xsd:annotation>
+ <xsd:documentation>Refers to the occurrences of instances defined by the count-type value.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="character">
+ <xsd:annotation>
+ <xsd:documentation>Refers to characters.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="line">
+ <xsd:annotation>
+ <xsd:documentation>Refers to lines.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="sentence">
+ <xsd:annotation>
+ <xsd:documentation>Refers to sentences.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="paragraph">
+ <xsd:annotation>
+ <xsd:documentation>Refers to paragraphs.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="segment">
+ <xsd:annotation>
+ <xsd:documentation>Refers to segments.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="placeable">
+ <xsd:annotation>
+ <xsd:documentation>Refers to placeables (inline elements).</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="priorityValueList">
+ <xsd:annotation>
+ <xsd:documentation>Values for the attribute 'priority'.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:positiveInteger">
+ <xsd:enumeration value="1">
+ <xsd:annotation>
+ <xsd:documentation>Highest priority.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="2">
+ <xsd:annotation>
+ <xsd:documentation>High priority.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="3">
+ <xsd:annotation>
+ <xsd:documentation>High priority, but not as important as 2.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="4">
+ <xsd:annotation>
+ <xsd:documentation>High priority, but not as important as 3.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="5">
+ <xsd:annotation>
+ <xsd:documentation>Medium priority, but more important than 6.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="6">
+ <xsd:annotation>
+ <xsd:documentation>Medium priority, but less important than 5.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="7">
+ <xsd:annotation>
+ <xsd:documentation>Low priority, but more important than 8.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="8">
+ <xsd:annotation>
+ <xsd:documentation>Low priority, but more important than 9.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="9">
+ <xsd:annotation>
+ <xsd:documentation>Low priority.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="10">
+ <xsd:annotation>
+ <xsd:documentation>Lowest priority.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="reformatValueYesNo">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="yes">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that all properties can be reformatted. This value must be used alone.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="no">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that no properties should be reformatted. This value must be used alone.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="reformatValueList">
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:union memberTypes="xlf:XTend">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="coord">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that all information in the coord attribute can be modified.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="coord-x">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that the x information in the coord attribute can be modified.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="coord-y">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that the y information in the coord attribute can be modified.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="coord-cx">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that the cx information in the coord attribute can be modified.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="coord-cy">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that the cy information in the coord attribute can be modified.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="font">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that all the information in the font attribute can be modified.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="font-name">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that the name information in the font attribute can be modified.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="font-size">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that the size information in the font attribute can be modified.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="font-weight">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that the weight information in the font attribute can be modified.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="css-style">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that the information in the css-style attribute can be modified.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="style">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that the information in the style attribute can be modified.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="ex-style">
+ <xsd:annotation>
+ <xsd:documentation>This value indicates that the information in the exstyle attribute can be modified.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:union>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+ <xsd:simpleType name="purposeValueList">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="information">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that the context is informational in nature, specifying for example, how a term should be translated. Thus, should be displayed to anyone editing the XLIFF document.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="location">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that the context-group is used to specify where the term was found in the translatable source. Thus, it is not displayed.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="match">
+ <xsd:annotation>
+ <xsd:documentation>Indicates that the context information should be used during translation memory lookups. Thus, it is not displayed.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="alttranstypeValueList">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="proposal">
+ <xsd:annotation>
+ <xsd:documentation>Represents a translation proposal from a translation memory or other resource.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="previous-version">
+ <xsd:annotation>
+ <xsd:documentation>Represents a previous version of the target element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="rejected">
+ <xsd:annotation>
+ <xsd:documentation>Represents a rejected version of the target element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="reference">
+ <xsd:annotation>
+ <xsd:documentation>Represents a translation to be used for reference purposes only, for example from a related product or a different language.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ <xsd:enumeration value="accepted">
+ <xsd:annotation>
+ <xsd:documentation>Represents a proposed translation that was used for the translation of the trans-unit, possibly modified.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:enumeration>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <!-- Other Types -->
+ <xsd:complexType name="ElemType_ExternalReference">
+ <xsd:choice>
+ <xsd:element ref="xlf:internal-file"/>
+ <xsd:element ref="xlf:external-file"/>
+ </xsd:choice>
+ </xsd:complexType>
+ <xsd:simpleType name="AttrType_purpose">
+ <xsd:list>
+ <xsd:simpleType>
+ <xsd:union memberTypes="xlf:purposeValueList xlf:XTend"/>
+ </xsd:simpleType>
+ </xsd:list>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_datatype">
+ <xsd:union memberTypes="xlf:datatypeValueList xlf:XTend"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_restype">
+ <xsd:union memberTypes="xlf:restypeValueList xlf:XTend"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_alttranstype">
+ <xsd:union memberTypes="xlf:alttranstypeValueList xlf:XTend"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_context-type">
+ <xsd:union memberTypes="xlf:context-typeValueList xlf:XTend"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_state">
+ <xsd:union memberTypes="xlf:stateValueList xlf:XTend"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_state-qualifier">
+ <xsd:union memberTypes="xlf:state-qualifierValueList xlf:XTend"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_count-type">
+ <xsd:union memberTypes="xlf:restypeValueList xlf:count-typeValueList xlf:datatypeValueList xlf:stateValueList xlf:state-qualifierValueList xlf:XTend"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_InlineDelimiters">
+ <xsd:union memberTypes="xlf:InlineDelimitersValueList xlf:XTend"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_InlinePlaceholders">
+ <xsd:union memberTypes="xlf:InlinePlaceholdersValueList xlf:XTend"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_size-unit">
+ <xsd:union memberTypes="xlf:size-unitValueList xlf:XTend"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_mtype">
+ <xsd:union memberTypes="xlf:mtypeValueList xlf:XTend"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_unit">
+ <xsd:union memberTypes="xlf:unitValueList xlf:XTend"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_priority">
+ <xsd:union memberTypes="xlf:priorityValueList"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_reformat">
+ <xsd:union memberTypes="xlf:reformatValueYesNo xlf:reformatValueList"/>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_YesNo">
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="yes"/>
+ <xsd:enumeration value="no"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_Position">
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="open"/>
+ <xsd:enumeration value="close"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_assoc">
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="preceding"/>
+ <xsd:enumeration value="following"/>
+ <xsd:enumeration value="both"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_annotates">
+ <xsd:restriction base="xsd:NMTOKEN">
+ <xsd:enumeration value="source"/>
+ <xsd:enumeration value="target"/>
+ <xsd:enumeration value="general"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_Coordinates">
+ <xsd:annotation>
+ <xsd:documentation>Values for the attribute 'coord'.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="(-?\d+|#);(-?\d+|#);(-?\d+|#);(-?\d+|#)"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="AttrType_Version">
+ <xsd:annotation>
+ <xsd:documentation>Version values: 1.0 and 1.1 are allowed for backward compatibility.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="1.2"/>
+ <xsd:enumeration value="1.1"/>
+ <xsd:enumeration value="1.0"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <!-- Groups -->
+ <xsd:group name="ElemGroup_TextContent">
+ <xsd:choice>
+ <xsd:element ref="xlf:g"/>
+ <xsd:element ref="xlf:bpt"/>
+ <xsd:element ref="xlf:ept"/>
+ <xsd:element ref="xlf:ph"/>
+ <xsd:element ref="xlf:it"/>
+ <xsd:element ref="xlf:mrk"/>
+ <xsd:element ref="xlf:x"/>
+ <xsd:element ref="xlf:bx"/>
+ <xsd:element ref="xlf:ex"/>
+ </xsd:choice>
+ </xsd:group>
+ <xsd:attributeGroup name="AttrGroup_TextContent">
+ <xsd:attribute name="id" type="xsd:string" use="required"/>
+ <xsd:attribute name="xid" type="xsd:string" use="optional"/>
+ <xsd:attribute name="equiv-text" type="xsd:string" use="optional"/>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:attributeGroup>
+ <!-- XLIFF Structure -->
+ <xsd:element name="xliff">
+ <xsd:complexType>
+ <xsd:sequence maxOccurs="unbounded">
+ <xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
+ <xsd:element ref="xlf:file"/>
+ </xsd:sequence>
+ <xsd:attribute name="version" type="xlf:AttrType_Version" use="required"/>
+ <xsd:attribute ref="xml:lang" use="optional"/>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="file">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element minOccurs="0" ref="xlf:header"/>
+ <xsd:element ref="xlf:body"/>
+ </xsd:sequence>
+ <xsd:attribute name="original" type="xsd:string" use="required"/>
+ <xsd:attribute name="source-language" type="xsd:language" use="required"/>
+ <xsd:attribute name="datatype" type="xlf:AttrType_datatype" use="required"/>
+ <xsd:attribute name="tool-id" type="xsd:string" use="optional"/>
+ <xsd:attribute name="date" type="xsd:dateTime" use="optional"/>
+ <xsd:attribute ref="xml:space" use="optional"/>
+ <xsd:attribute name="category" type="xsd:string" use="optional"/>
+ <xsd:attribute name="target-language" type="xsd:language" use="optional"/>
+ <xsd:attribute name="product-name" type="xsd:string" use="optional"/>
+ <xsd:attribute name="product-version" type="xsd:string" use="optional"/>
+ <xsd:attribute name="build-num" type="xsd:string" use="optional"/>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:complexType>
+ <xsd:unique name="U_group_id">
+ <xsd:selector xpath=".//xlf:group"/>
+ <xsd:field xpath="@id"/>
+ </xsd:unique>
+ <xsd:key name="K_unit_id">
+ <xsd:selector xpath=".//xlf:trans-unit|.//xlf:bin-unit"/>
+ <xsd:field xpath="@id"/>
+ </xsd:key>
+ <xsd:keyref name="KR_unit_id" refer="xlf:K_unit_id">
+ <xsd:selector xpath=".//bpt|.//ept|.//it|.//ph|.//g|.//x|.//bx|.//ex|.//sub"/>
+ <xsd:field xpath="@xid"/>
+ </xsd:keyref>
+ <xsd:key name="K_tool-id">
+ <xsd:selector xpath="xlf:header/xlf:tool"/>
+ <xsd:field xpath="@tool-id"/>
+ </xsd:key>
+ <xsd:keyref name="KR_file_tool-id" refer="xlf:K_tool-id">
+ <xsd:selector xpath="."/>
+ <xsd:field xpath="@tool-id"/>
+ </xsd:keyref>
+ <xsd:keyref name="KR_phase_tool-id" refer="xlf:K_tool-id">
+ <xsd:selector xpath="xlf:header/xlf:phase-group/xlf:phase"/>
+ <xsd:field xpath="@tool-id"/>
+ </xsd:keyref>
+ <xsd:keyref name="KR_alt-trans_tool-id" refer="xlf:K_tool-id">
+ <xsd:selector xpath=".//xlf:trans-unit/xlf:alt-trans"/>
+ <xsd:field xpath="@tool-id"/>
+ </xsd:keyref>
+ <xsd:key name="K_count-group_name">
+ <xsd:selector xpath=".//xlf:count-group"/>
+ <xsd:field xpath="@name"/>
+ </xsd:key>
+ <xsd:unique name="U_context-group_name">
+ <xsd:selector xpath=".//xlf:context-group"/>
+ <xsd:field xpath="@name"/>
+ </xsd:unique>
+ <xsd:key name="K_phase-name">
+ <xsd:selector xpath="xlf:header/xlf:phase-group/xlf:phase"/>
+ <xsd:field xpath="@phase-name"/>
+ </xsd:key>
+ <xsd:keyref name="KR_phase-name" refer="xlf:K_phase-name">
+ <xsd:selector xpath=".//xlf:count|.//xlf:trans-unit|.//xlf:target|.//bin-unit|.//bin-target"/>
+ <xsd:field xpath="@phase-name"/>
+ </xsd:keyref>
+ <xsd:unique name="U_uid">
+ <xsd:selector xpath=".//xlf:external-file"/>
+ <xsd:field xpath="@uid"/>
+ </xsd:unique>
+ </xsd:element>
+ <xsd:element name="header">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element minOccurs="0" name="skl" type="xlf:ElemType_ExternalReference"/>
+ <xsd:element minOccurs="0" ref="xlf:phase-group"/>
+ <xsd:choice maxOccurs="unbounded" minOccurs="0">
+ <xsd:element name="glossary" type="xlf:ElemType_ExternalReference"/>
+ <xsd:element name="reference" type="xlf:ElemType_ExternalReference"/>
+ <xsd:element ref="xlf:count-group"/>
+ <xsd:element ref="xlf:note"/>
+ <xsd:element ref="xlf:tool"/>
+ </xsd:choice>
+ <xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="internal-file">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="form" type="xsd:string"/>
+ <xsd:attribute name="crc" type="xsd:NMTOKEN"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="external-file">
+ <xsd:complexType>
+ <xsd:attribute name="href" type="xsd:string" use="required"/>
+ <xsd:attribute name="crc" type="xsd:NMTOKEN"/>
+ <xsd:attribute name="uid" type="xsd:NMTOKEN"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="note">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute ref="xml:lang" use="optional"/>
+ <xsd:attribute default="1" name="priority" type="xlf:AttrType_priority" use="optional"/>
+ <xsd:attribute name="from" type="xsd:string" use="optional"/>
+ <xsd:attribute default="general" name="annotates" type="xlf:AttrType_annotates" use="optional"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="phase-group">
+ <xsd:complexType>
+ <xsd:sequence maxOccurs="unbounded">
+ <xsd:element ref="xlf:phase"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="phase">
+ <xsd:complexType>
+ <xsd:sequence maxOccurs="unbounded" minOccurs="0">
+ <xsd:element ref="xlf:note"/>
+ </xsd:sequence>
+ <xsd:attribute name="phase-name" type="xsd:string" use="required"/>
+ <xsd:attribute name="process-name" type="xsd:string" use="required"/>
+ <xsd:attribute name="company-name" type="xsd:string" use="optional"/>
+ <xsd:attribute name="tool-id" type="xsd:string" use="optional"/>
+ <xsd:attribute name="date" type="xsd:dateTime" use="optional"/>
+ <xsd:attribute name="job-id" type="xsd:string" use="optional"/>
+ <xsd:attribute name="contact-name" type="xsd:string" use="optional"/>
+ <xsd:attribute name="contact-email" type="xsd:string" use="optional"/>
+ <xsd:attribute name="contact-phone" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="count-group">
+ <xsd:complexType>
+ <xsd:sequence maxOccurs="unbounded" minOccurs="0">
+ <xsd:element ref="xlf:count"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="count">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="count-type" type="xlf:AttrType_count-type" use="optional"/>
+ <xsd:attribute name="phase-name" type="xsd:string" use="optional"/>
+ <xsd:attribute default="word" name="unit" type="xlf:AttrType_unit" use="optional"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="context-group">
+ <xsd:complexType>
+ <xsd:sequence maxOccurs="unbounded">
+ <xsd:element ref="xlf:context"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="optional"/>
+ <xsd:attribute name="crc" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="purpose" type="xlf:AttrType_purpose" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="context">
+ <xsd:complexType>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="context-type" type="xlf:AttrType_context-type" use="required"/>
+ <xsd:attribute default="no" name="match-mandatory" type="xlf:AttrType_YesNo" use="optional"/>
+ <xsd:attribute name="crc" type="xsd:NMTOKEN" use="optional"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="tool">
+ <xsd:complexType mixed="true">
+ <xsd:sequence>
+ <xsd:any namespace="##any" processContents="strict" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="tool-id" type="xsd:string" use="required"/>
+ <xsd:attribute name="tool-name" type="xsd:string" use="required"/>
+ <xsd:attribute name="tool-version" type="xsd:string" use="optional"/>
+ <xsd:attribute name="tool-company" type="xsd:string" use="optional"/>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="body">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded" minOccurs="0">
+ <xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:group"/>
+ <xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:trans-unit"/>
+ <xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:bin-unit"/>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="group">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:sequence>
+ <xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:context-group"/>
+ <xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:count-group"/>
+ <xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:note"/>
+ <xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
+ </xsd:sequence>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:group"/>
+ <xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:trans-unit"/>
+ <xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:bin-unit"/>
+ </xsd:choice>
+ </xsd:sequence>
+ <xsd:attribute name="id" type="xsd:string" use="optional"/>
+ <xsd:attribute name="datatype" type="xlf:AttrType_datatype" use="optional"/>
+ <xsd:attribute default="default" ref="xml:space" use="optional"/>
+ <xsd:attribute name="restype" type="xlf:AttrType_restype" use="optional"/>
+ <xsd:attribute name="resname" type="xsd:string" use="optional"/>
+ <xsd:attribute name="extradata" type="xsd:string" use="optional"/>
+ <xsd:attribute name="extype" type="xsd:string" use="optional"/>
+ <xsd:attribute name="help-id" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="menu" type="xsd:string" use="optional"/>
+ <xsd:attribute name="menu-option" type="xsd:string" use="optional"/>
+ <xsd:attribute name="menu-name" type="xsd:string" use="optional"/>
+ <xsd:attribute name="coord" type="xlf:AttrType_Coordinates" use="optional"/>
+ <xsd:attribute name="font" type="xsd:string" use="optional"/>
+ <xsd:attribute name="css-style" type="xsd:string" use="optional"/>
+ <xsd:attribute name="style" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="exstyle" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute default="yes" name="translate" type="xlf:AttrType_YesNo" use="optional"/>
+ <xsd:attribute default="yes" name="reformat" type="xlf:AttrType_reformat" use="optional"/>
+ <xsd:attribute default="pixel" name="size-unit" type="xlf:AttrType_size-unit" use="optional"/>
+ <xsd:attribute name="maxwidth" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="minwidth" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="maxheight" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="minheight" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="maxbytes" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="minbytes" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="charclass" type="xsd:string" use="optional"/>
+ <xsd:attribute default="no" name="merged-trans" type="xlf:AttrType_YesNo" use="optional"/>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="trans-unit">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element ref="xlf:source"/>
+ <xsd:element minOccurs="0" ref="xlf:seg-source"/>
+ <xsd:element minOccurs="0" ref="xlf:target"/>
+ <xsd:choice maxOccurs="unbounded" minOccurs="0">
+ <xsd:element ref="xlf:context-group"/>
+ <xsd:element ref="xlf:count-group"/>
+ <xsd:element ref="xlf:note"/>
+ <xsd:element ref="xlf:alt-trans"/>
+ </xsd:choice>
+ <xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
+ </xsd:sequence>
+ <xsd:attribute name="id" type="xsd:string" use="required"/>
+ <xsd:attribute name="approved" type="xlf:AttrType_YesNo" use="optional"/>
+ <xsd:attribute default="yes" name="translate" type="xlf:AttrType_YesNo" use="optional"/>
+ <xsd:attribute default="yes" name="reformat" type="xlf:AttrType_reformat" use="optional"/>
+ <xsd:attribute default="default" ref="xml:space" use="optional"/>
+ <xsd:attribute name="datatype" type="xlf:AttrType_datatype" use="optional"/>
+ <xsd:attribute name="phase-name" type="xsd:string" use="optional"/>
+ <xsd:attribute name="restype" type="xlf:AttrType_restype" use="optional"/>
+ <xsd:attribute name="resname" type="xsd:string" use="optional"/>
+ <xsd:attribute name="extradata" type="xsd:string" use="optional"/>
+ <xsd:attribute name="extype" type="xsd:string" use="optional"/>
+ <xsd:attribute name="help-id" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="menu" type="xsd:string" use="optional"/>
+ <xsd:attribute name="menu-option" type="xsd:string" use="optional"/>
+ <xsd:attribute name="menu-name" type="xsd:string" use="optional"/>
+ <xsd:attribute name="coord" type="xlf:AttrType_Coordinates" use="optional"/>
+ <xsd:attribute name="font" type="xsd:string" use="optional"/>
+ <xsd:attribute name="css-style" type="xsd:string" use="optional"/>
+ <xsd:attribute name="style" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="exstyle" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute default="pixel" name="size-unit" type="xlf:AttrType_size-unit" use="optional"/>
+ <xsd:attribute name="maxwidth" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="minwidth" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="maxheight" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="minheight" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="maxbytes" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="minbytes" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="charclass" type="xsd:string" use="optional"/>
+ <xsd:attribute default="yes" name="merged-trans" type="xlf:AttrType_YesNo" use="optional"/>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:complexType>
+ <xsd:unique name="U_tu_segsrc_mid">
+ <xsd:selector xpath="./xlf:seg-source/xlf:mrk"/>
+ <xsd:field xpath="@mid"/>
+ </xsd:unique>
+ <xsd:keyref name="KR_tu_segsrc_mid" refer="xlf:U_tu_segsrc_mid">
+ <xsd:selector xpath="./xlf:target/xlf:mrk|./xlf:alt-trans"/>
+ <xsd:field xpath="@mid"/>
+ </xsd:keyref>
+ </xsd:element>
+ <xsd:element name="source">
+ <xsd:complexType mixed="true">
+ <xsd:group maxOccurs="unbounded" minOccurs="0" ref="xlf:ElemGroup_TextContent"/>
+ <xsd:attribute ref="xml:lang" use="optional"/>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:complexType>
+ <xsd:unique name="U_source_bpt_rid">
+ <xsd:selector xpath=".//xlf:bpt"/>
+ <xsd:field xpath="@rid"/>
+ </xsd:unique>
+ <xsd:keyref name="KR_source_ept_rid" refer="xlf:U_source_bpt_rid">
+ <xsd:selector xpath=".//xlf:ept"/>
+ <xsd:field xpath="@rid"/>
+ </xsd:keyref>
+ <xsd:unique name="U_source_bx_rid">
+ <xsd:selector xpath=".//xlf:bx"/>
+ <xsd:field xpath="@rid"/>
+ </xsd:unique>
+ <xsd:keyref name="KR_source_ex_rid" refer="xlf:U_source_bx_rid">
+ <xsd:selector xpath=".//xlf:ex"/>
+ <xsd:field xpath="@rid"/>
+ </xsd:keyref>
+ </xsd:element>
+ <xsd:element name="seg-source">
+ <xsd:complexType mixed="true">
+ <xsd:group maxOccurs="unbounded" minOccurs="0" ref="xlf:ElemGroup_TextContent"/>
+ <xsd:attribute ref="xml:lang" use="optional"/>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:complexType>
+ <xsd:unique name="U_segsrc_bpt_rid">
+ <xsd:selector xpath=".//xlf:bpt"/>
+ <xsd:field xpath="@rid"/>
+ </xsd:unique>
+ <xsd:keyref name="KR_segsrc_ept_rid" refer="xlf:U_segsrc_bpt_rid">
+ <xsd:selector xpath=".//xlf:ept"/>
+ <xsd:field xpath="@rid"/>
+ </xsd:keyref>
+ <xsd:unique name="U_segsrc_bx_rid">
+ <xsd:selector xpath=".//xlf:bx"/>
+ <xsd:field xpath="@rid"/>
+ </xsd:unique>
+ <xsd:keyref name="KR_segsrc_ex_rid" refer="xlf:U_segsrc_bx_rid">
+ <xsd:selector xpath=".//xlf:ex"/>
+ <xsd:field xpath="@rid"/>
+ </xsd:keyref>
+ </xsd:element>
+ <xsd:element name="target">
+ <xsd:complexType mixed="true">
+ <xsd:group maxOccurs="unbounded" minOccurs="0" ref="xlf:ElemGroup_TextContent"/>
+ <xsd:attribute name="state" type="xlf:AttrType_state" use="optional"/>
+ <xsd:attribute name="state-qualifier" type="xlf:AttrType_state-qualifier" use="optional"/>
+ <xsd:attribute name="phase-name" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute ref="xml:lang" use="optional"/>
+ <xsd:attribute name="resname" type="xsd:string" use="optional"/>
+ <xsd:attribute name="coord" type="xlf:AttrType_Coordinates" use="optional"/>
+ <xsd:attribute name="font" type="xsd:string" use="optional"/>
+ <xsd:attribute name="css-style" type="xsd:string" use="optional"/>
+ <xsd:attribute name="style" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="exstyle" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute default="yes" name="equiv-trans" type="xlf:AttrType_YesNo" use="optional"/>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:complexType>
+ <xsd:unique name="U_target_bpt_rid">
+ <xsd:selector xpath=".//xlf:bpt"/>
+ <xsd:field xpath="@rid"/>
+ </xsd:unique>
+ <xsd:keyref name="KR_target_ept_rid" refer="xlf:U_target_bpt_rid">
+ <xsd:selector xpath=".//xlf:ept"/>
+ <xsd:field xpath="@rid"/>
+ </xsd:keyref>
+ <xsd:unique name="U_target_bx_rid">
+ <xsd:selector xpath=".//xlf:bx"/>
+ <xsd:field xpath="@rid"/>
+ </xsd:unique>
+ <xsd:keyref name="KR_target_ex_rid" refer="xlf:U_target_bx_rid">
+ <xsd:selector xpath=".//xlf:ex"/>
+ <xsd:field xpath="@rid"/>
+ </xsd:keyref>
+ </xsd:element>
+ <xsd:element name="alt-trans">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element minOccurs="0" ref="xlf:source"/>
+ <xsd:element minOccurs="0" ref="xlf:seg-source"/>
+ <xsd:element maxOccurs="1" ref="xlf:target"/>
+ <xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:context-group"/>
+ <xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:note"/>
+ <xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
+ </xsd:sequence>
+ <xsd:attribute name="match-quality" type="xsd:string" use="optional"/>
+ <xsd:attribute name="tool-id" type="xsd:string" use="optional"/>
+ <xsd:attribute name="crc" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute ref="xml:lang" use="optional"/>
+ <xsd:attribute name="origin" type="xsd:string" use="optional"/>
+ <xsd:attribute name="datatype" type="xlf:AttrType_datatype" use="optional"/>
+ <xsd:attribute default="default" ref="xml:space" use="optional"/>
+ <xsd:attribute name="restype" type="xlf:AttrType_restype" use="optional"/>
+ <xsd:attribute name="resname" type="xsd:string" use="optional"/>
+ <xsd:attribute name="extradata" type="xsd:string" use="optional"/>
+ <xsd:attribute name="extype" type="xsd:string" use="optional"/>
+ <xsd:attribute name="help-id" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="menu" type="xsd:string" use="optional"/>
+ <xsd:attribute name="menu-option" type="xsd:string" use="optional"/>
+ <xsd:attribute name="menu-name" type="xsd:string" use="optional"/>
+ <xsd:attribute name="mid" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="coord" type="xlf:AttrType_Coordinates" use="optional"/>
+ <xsd:attribute name="font" type="xsd:string" use="optional"/>
+ <xsd:attribute name="css-style" type="xsd:string" use="optional"/>
+ <xsd:attribute name="style" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="exstyle" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="phase-name" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute default="proposal" name="alttranstype" type="xlf:AttrType_alttranstype" use="optional"/>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:complexType>
+ <xsd:unique name="U_at_segsrc_mid">
+ <xsd:selector xpath="./xlf:seg-source/xlf:mrk"/>
+ <xsd:field xpath="@mid"/>
+ </xsd:unique>
+ <xsd:keyref name="KR_at_segsrc_mid" refer="xlf:U_at_segsrc_mid">
+ <xsd:selector xpath="./xlf:target/xlf:mrk"/>
+ <xsd:field xpath="@mid"/>
+ </xsd:keyref>
+ </xsd:element>
+ <xsd:element name="bin-unit">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element ref="xlf:bin-source"/>
+ <xsd:element minOccurs="0" ref="xlf:bin-target"/>
+ <xsd:choice maxOccurs="unbounded" minOccurs="0">
+ <xsd:element ref="xlf:context-group"/>
+ <xsd:element ref="xlf:count-group"/>
+ <xsd:element ref="xlf:note"/>
+ <xsd:element ref="xlf:trans-unit"/>
+ </xsd:choice>
+ <xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
+ </xsd:sequence>
+ <xsd:attribute name="id" type="xsd:string" use="required"/>
+ <xsd:attribute name="mime-type" type="xlf:mime-typeValueList" use="required"/>
+ <xsd:attribute name="approved" type="xlf:AttrType_YesNo" use="optional"/>
+ <xsd:attribute default="yes" name="translate" type="xlf:AttrType_YesNo" use="optional"/>
+ <xsd:attribute default="yes" name="reformat" type="xlf:AttrType_reformat" use="optional"/>
+ <xsd:attribute name="restype" type="xlf:AttrType_restype" use="optional"/>
+ <xsd:attribute name="resname" type="xsd:string" use="optional"/>
+ <xsd:attribute name="phase-name" type="xsd:string" use="optional"/>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="bin-source">
+ <xsd:complexType>
+ <xsd:choice>
+ <xsd:element ref="xlf:internal-file"/>
+ <xsd:element ref="xlf:external-file"/>
+ </xsd:choice>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="bin-target">
+ <xsd:complexType>
+ <xsd:choice>
+ <xsd:element ref="xlf:internal-file"/>
+ <xsd:element ref="xlf:external-file"/>
+ </xsd:choice>
+ <xsd:attribute name="mime-type" type="xlf:mime-typeValueList" use="optional"/>
+ <xsd:attribute name="state" type="xlf:AttrType_state" use="optional"/>
+ <xsd:attribute name="state-qualifier" type="xlf:AttrType_state-qualifier" use="optional"/>
+ <xsd:attribute name="phase-name" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="restype" type="xlf:AttrType_restype" use="optional"/>
+ <xsd:attribute name="resname" type="xsd:string" use="optional"/>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:complexType>
+ </xsd:element>
+ <!-- Element for inline codes -->
+ <xsd:element name="g">
+ <xsd:complexType mixed="true">
+ <xsd:group maxOccurs="unbounded" minOccurs="0" ref="xlf:ElemGroup_TextContent"/>
+ <xsd:attribute name="ctype" type="xlf:AttrType_InlineDelimiters" use="optional"/>
+ <xsd:attribute default="yes" name="clone" type="xlf:AttrType_YesNo" use="optional"/>
+ <xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="x">
+ <xsd:complexType>
+ <xsd:attribute name="ctype" type="xlf:AttrType_InlinePlaceholders" use="optional"/>
+ <xsd:attribute default="yes" name="clone" type="xlf:AttrType_YesNo" use="optional"/>
+ <xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="bx">
+ <xsd:complexType>
+ <xsd:attribute name="rid" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="ctype" type="xlf:AttrType_InlineDelimiters" use="optional"/>
+ <xsd:attribute default="yes" name="clone" type="xlf:AttrType_YesNo" use="optional"/>
+ <xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ex">
+ <xsd:complexType>
+ <xsd:attribute name="rid" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ph">
+ <xsd:complexType mixed="true">
+ <xsd:sequence maxOccurs="unbounded" minOccurs="0">
+ <xsd:element ref="xlf:sub"/>
+ </xsd:sequence>
+ <xsd:attribute name="ctype" type="xlf:AttrType_InlinePlaceholders" use="optional"/>
+ <xsd:attribute name="crc" type="xsd:string" use="optional"/>
+ <xsd:attribute name="assoc" type="xlf:AttrType_assoc" use="optional"/>
+ <xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="bpt">
+ <xsd:complexType mixed="true">
+ <xsd:sequence maxOccurs="unbounded" minOccurs="0">
+ <xsd:element ref="xlf:sub"/>
+ </xsd:sequence>
+ <xsd:attribute name="rid" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="ctype" type="xlf:AttrType_InlineDelimiters" use="optional"/>
+ <xsd:attribute name="crc" type="xsd:string" use="optional"/>
+ <xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="ept">
+ <xsd:complexType mixed="true">
+ <xsd:sequence maxOccurs="unbounded" minOccurs="0">
+ <xsd:element ref="xlf:sub"/>
+ </xsd:sequence>
+ <xsd:attribute name="rid" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="crc" type="xsd:string" use="optional"/>
+ <xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="it">
+ <xsd:complexType mixed="true">
+ <xsd:sequence maxOccurs="unbounded" minOccurs="0">
+ <xsd:element ref="xlf:sub"/>
+ </xsd:sequence>
+ <xsd:attribute name="pos" type="xlf:AttrType_Position" use="required"/>
+ <xsd:attribute name="rid" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="ctype" type="xlf:AttrType_InlineDelimiters" use="optional"/>
+ <xsd:attribute name="crc" type="xsd:string" use="optional"/>
+ <xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="sub">
+ <xsd:complexType mixed="true">
+ <xsd:group maxOccurs="unbounded" minOccurs="0" ref="xlf:ElemGroup_TextContent"/>
+ <xsd:attribute name="datatype" type="xlf:AttrType_datatype" use="optional"/>
+ <xsd:attribute name="ctype" type="xlf:AttrType_InlineDelimiters" use="optional"/>
+ <xsd:attribute name="xid" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="mrk">
+ <xsd:complexType mixed="true">
+ <xsd:group maxOccurs="unbounded" minOccurs="0" ref="xlf:ElemGroup_TextContent"/>
+ <xsd:attribute name="mtype" type="xlf:AttrType_mtype" use="required"/>
+ <xsd:attribute name="mid" type="xsd:NMTOKEN" use="optional"/>
+ <xsd:attribute name="comment" type="xsd:string" use="optional"/>
+ <xsd:anyAttribute namespace="##other" processContents="strict"/>
+ </xsd:complexType>
+ </xsd:element>
+</xsd:schema>
diff --git a/vendor/symfony/translation/Resources/schemas/xliff-core-2.0.xsd b/vendor/symfony/translation/Resources/schemas/xliff-core-2.0.xsd
new file mode 100755
index 0000000..963232f
--- /dev/null
+++ b/vendor/symfony/translation/Resources/schemas/xliff-core-2.0.xsd
@@ -0,0 +1,411 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ XLIFF Version 2.0
+ OASIS Standard
+ 05 August 2014
+ Copyright (c) OASIS Open 2014. All rights reserved.
+ Source: http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/schemas/
+ -->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ elementFormDefault="qualified"
+ xmlns:xlf="urn:oasis:names:tc:xliff:document:2.0"
+ targetNamespace="urn:oasis:names:tc:xliff:document:2.0">
+
+ <!-- Import -->
+
+ <xs:import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="informativeCopiesOf3rdPartySchemas/w3c/xml.xsd"/>
+
+ <!-- Element Group -->
+
+ <xs:group name="inline">
+ <xs:choice>
+ <xs:element ref="xlf:cp"/>
+ <xs:element ref="xlf:ph"/>
+ <xs:element ref="xlf:pc"/>
+ <xs:element ref="xlf:sc"/>
+ <xs:element ref="xlf:ec"/>
+ <xs:element ref="xlf:mrk"/>
+ <xs:element ref="xlf:sm"/>
+ <xs:element ref="xlf:em"/>
+ </xs:choice>
+ </xs:group>
+
+ <!-- Attribute Types -->
+
+ <xs:simpleType name="yesNo">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="yes"/>
+ <xs:enumeration value="no"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="yesNoFirstNo">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="yes"/>
+ <xs:enumeration value="firstNo"/>
+ <xs:enumeration value="no"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="dirValue">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="ltr"/>
+ <xs:enumeration value="rtl"/>
+ <xs:enumeration value="auto"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="appliesTo">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="source"/>
+ <xs:enumeration value="target"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="userDefinedValue">
+ <xs:restriction base="xs:string">
+ <xs:pattern value="[^\s:]+:[^\s:]+"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="attrType_type">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="fmt"/>
+ <xs:enumeration value="ui"/>
+ <xs:enumeration value="quote"/>
+ <xs:enumeration value="link"/>
+ <xs:enumeration value="image"/>
+ <xs:enumeration value="other"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="typeForMrkValues">
+ <xs:restriction base="xs:NMTOKEN">
+ <xs:enumeration value="generic"/>
+ <xs:enumeration value="comment"/>
+ <xs:enumeration value="term"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="attrType_typeForMrk">
+ <xs:union memberTypes="xlf:typeForMrkValues xlf:userDefinedValue"/>
+ </xs:simpleType>
+
+ <xs:simpleType name="priorityValue">
+ <xs:restriction base="xs:positiveInteger">
+ <xs:minInclusive value="1"/>
+ <xs:maxInclusive value="10"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="stateType">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="initial"/>
+ <xs:enumeration value="translated"/>
+ <xs:enumeration value="reviewed"/>
+ <xs:enumeration value="final"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- Structural Elements -->
+
+ <xs:element name="xliff">
+ <xs:complexType mixed="false">
+ <xs:sequence>
+ <xs:element minOccurs="1" maxOccurs="unbounded" ref="xlf:file"/>
+ </xs:sequence>
+ <xs:attribute name="version" use="required"/>
+ <xs:attribute name="srcLang" use="required"/>
+ <xs:attribute name="trgLang" use="optional"/>
+ <xs:attribute ref="xml:space" use="optional" default="default"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="file">
+ <xs:complexType mixed="false">
+ <xs:sequence>
+ <xs:element minOccurs="0" maxOccurs="1" ref="xlf:skeleton"/>
+ <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"
+ processContents="lax"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="xlf:notes"/>
+ <xs:choice minOccurs="1" maxOccurs="unbounded">
+ <xs:element ref="xlf:unit"/>
+ <xs:element ref="xlf:group"/>
+ </xs:choice>
+ </xs:sequence>
+ <xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
+ <xs:attribute name="canResegment" use="optional" type="xlf:yesNo" default="yes"/>
+ <xs:attribute name="original" use="optional"/>
+ <xs:attribute name="translate" use="optional" type="xlf:yesNo" default="yes"/>
+ <xs:attribute name="srcDir" use="optional" type="xlf:dirValue" default="auto"/>
+ <xs:attribute name="trgDir" use="optional" type="xlf:dirValue" default="auto"/>
+ <xs:attribute ref="xml:space" use="optional"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="skeleton">
+ <xs:complexType mixed="true">
+ <xs:sequence>
+ <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"
+ processContents="lax"/>
+ </xs:sequence>
+ <xs:attribute name="href" use="optional"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="group">
+ <xs:complexType mixed="false">
+ <xs:sequence>
+ <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"
+ processContents="lax"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="xlf:notes"/>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="xlf:unit"/>
+ <xs:element ref="xlf:group"/>
+ </xs:choice>
+ </xs:sequence>
+ <xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
+ <xs:attribute name="name" use="optional"/>
+ <xs:attribute name="canResegment" use="optional" type="xlf:yesNo"/>
+ <xs:attribute name="translate" use="optional" type="xlf:yesNo"/>
+ <xs:attribute name="srcDir" use="optional" type="xlf:dirValue"/>
+ <xs:attribute name="trgDir" use="optional" type="xlf:dirValue"/>
+ <xs:attribute name="type" use="optional" type="xlf:userDefinedValue"/>
+ <xs:attribute ref="xml:space" use="optional"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="unit">
+ <xs:complexType mixed="false">
+ <xs:sequence>
+ <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"
+ processContents="lax"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="xlf:notes"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="xlf:originalData"/>
+ <xs:choice minOccurs="1" maxOccurs="unbounded">
+ <xs:element ref="xlf:segment"/>
+ <xs:element ref="xlf:ignorable"/>
+ </xs:choice>
+ </xs:sequence>
+ <xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
+ <xs:attribute name="name" use="optional"/>
+ <xs:attribute name="canResegment" use="optional" type="xlf:yesNo"/>
+ <xs:attribute name="translate" use="optional" type="xlf:yesNo"/>
+ <xs:attribute name="srcDir" use="optional" type="xlf:dirValue"/>
+ <xs:attribute name="trgDir" use="optional" type="xlf:dirValue"/>
+ <xs:attribute ref="xml:space" use="optional"/>
+ <xs:attribute name="type" use="optional" type="xlf:userDefinedValue"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="segment">
+ <xs:complexType mixed="false">
+ <xs:sequence>
+ <xs:element minOccurs="1" maxOccurs="1" ref="xlf:source"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="xlf:target"/>
+ </xs:sequence>
+ <xs:attribute name="id" use="optional" type="xs:NMTOKEN"/>
+ <xs:attribute name="canResegment" use="optional" type="xlf:yesNo"/>
+ <xs:attribute name="state" use="optional" type="xlf:stateType" default="initial"/>
+ <xs:attribute name="subState" use="optional"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="ignorable">
+ <xs:complexType mixed="false">
+ <xs:sequence>
+ <xs:element minOccurs="1" maxOccurs="1" ref="xlf:source"/>
+ <xs:element minOccurs="0" maxOccurs="1" ref="xlf:target"/>
+ </xs:sequence>
+ <xs:attribute name="id" use="optional" type="xs:NMTOKEN"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="notes">
+ <xs:complexType mixed="false">
+ <xs:sequence>
+ <xs:element minOccurs="1" maxOccurs="unbounded" ref="xlf:note"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="note">
+ <xs:complexType mixed="true">
+ <xs:attribute name="id" use="optional" type="xs:NMTOKEN"/>
+ <xs:attribute name="appliesTo" use="optional" type="xlf:appliesTo"/>
+ <xs:attribute name="category" use="optional"/>
+ <xs:attribute name="priority" use="optional" type="xlf:priorityValue" default="1"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="originalData">
+ <xs:complexType mixed="false">
+ <xs:sequence>
+ <xs:element minOccurs="1" maxOccurs="unbounded" ref="xlf:data"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="data">
+ <xs:complexType mixed="true">
+ <xs:sequence>
+ <xs:element minOccurs="0" maxOccurs="unbounded" ref="xlf:cp"/>
+ </xs:sequence>
+ <xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
+ <xs:attribute name="dir" use="optional" type="xlf:dirValue" default="auto"/>
+ <xs:attribute ref="xml:space" use="optional" fixed="preserve"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="source">
+ <xs:complexType mixed="true">
+ <xs:group ref="xlf:inline" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:attribute ref="xml:lang" use="optional"/>
+ <xs:attribute ref="xml:space" use="optional"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="target">
+ <xs:complexType mixed="true">
+ <xs:group ref="xlf:inline" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:attribute ref="xml:lang" use="optional"/>
+ <xs:attribute ref="xml:space" use="optional"/>
+ <xs:attribute name="order" use="optional" type="xs:positiveInteger"/>
+ </xs:complexType>
+ </xs:element>
+
+ <!-- Inline Elements -->
+
+ <xs:element name="cp">
+ <!-- Code Point -->
+ <xs:complexType mixed="false">
+ <xs:attribute name="hex" use="required" type="xs:hexBinary"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="ph">
+ <!-- Placeholder -->
+ <xs:complexType mixed="false">
+ <xs:attribute name="canCopy" use="optional" type="xlf:yesNo" default="yes"/>
+ <xs:attribute name="canDelete" use="optional" type="xlf:yesNo" default="yes"/>
+ <xs:attribute name="canReorder" use="optional" type="xlf:yesNoFirstNo" default="yes"/>
+ <xs:attribute name="copyOf" use="optional" type="xs:NMTOKEN"/>
+ <xs:attribute name="disp" use="optional"/>
+ <xs:attribute name="equiv" use="optional"/>
+ <xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
+ <xs:attribute name="dataRef" use="optional" type="xs:NMTOKEN"/>
+ <xs:attribute name="subFlows" use="optional" type="xs:NMTOKENS"/>
+ <xs:attribute name="subType" use="optional" type="xlf:userDefinedValue"/>
+ <xs:attribute name="type" use="optional" type="xlf:attrType_type"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="pc">
+ <!-- Paired Code -->
+ <xs:complexType mixed="true">
+ <xs:group ref="xlf:inline" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:attribute name="canCopy" use="optional" type="xlf:yesNo" default="yes"/>
+ <xs:attribute name="canDelete" use="optional" type="xlf:yesNo" default="yes"/>
+ <xs:attribute name="canOverlap" use="optional" type="xlf:yesNo"/>
+ <xs:attribute name="canReorder" use="optional" type="xlf:yesNoFirstNo" default="yes"/>
+ <xs:attribute name="copyOf" use="optional" type="xs:NMTOKEN"/>
+ <xs:attribute name="dispEnd" use="optional"/>
+ <xs:attribute name="dispStart" use="optional"/>
+ <xs:attribute name="equivEnd" use="optional"/>
+ <xs:attribute name="equivStart" use="optional"/>
+ <xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
+ <xs:attribute name="dataRefEnd" use="optional" type="xs:NMTOKEN"/>
+ <xs:attribute name="dataRefStart" use="optional" type="xs:NMTOKEN"/>
+ <xs:attribute name="subFlowsEnd" use="optional" type="xs:NMTOKENS"/>
+ <xs:attribute name="subFlowsStart" use="optional" type="xs:NMTOKENS"/>
+ <xs:attribute name="subType" use="optional" type="xlf:userDefinedValue"/>
+ <xs:attribute name="type" use="optional" type="xlf:attrType_type"/>
+ <xs:attribute name="dir" use="optional" type="xlf:dirValue"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="sc">
+ <!-- Start Code -->
+ <xs:complexType mixed="false">
+ <xs:attribute name="canCopy" use="optional" type="xlf:yesNo" default="yes"/>
+ <xs:attribute name="canDelete" use="optional" type="xlf:yesNo" default="yes"/>
+ <xs:attribute name="canOverlap" use="optional" type="xlf:yesNo" default="yes"/>
+ <xs:attribute name="canReorder" use="optional" type="xlf:yesNoFirstNo" default="yes"/>
+ <xs:attribute name="copyOf" use="optional" type="xs:NMTOKEN"/>
+ <xs:attribute name="dataRef" use="optional" type="xs:NMTOKEN"/>
+ <xs:attribute name="dir" use="optional" type="xlf:dirValue"/>
+ <xs:attribute name="disp" use="optional"/>
+ <xs:attribute name="equiv" use="optional"/>
+ <xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
+ <xs:attribute name="isolated" use="optional" type="xlf:yesNo" default="no"/>
+ <xs:attribute name="subFlows" use="optional" type="xs:NMTOKENS"/>
+ <xs:attribute name="subType" use="optional" type="xlf:userDefinedValue"/>
+ <xs:attribute name="type" use="optional" type="xlf:attrType_type"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="ec">
+ <!-- End Code -->
+ <xs:complexType mixed="false">
+ <xs:attribute name="canCopy" use="optional" type="xlf:yesNo" default="yes"/>
+ <xs:attribute name="canDelete" use="optional" type="xlf:yesNo" default="yes"/>
+ <xs:attribute name="canOverlap" use="optional" type="xlf:yesNo" default="yes"/>
+ <xs:attribute name="canReorder" use="optional" type="xlf:yesNoFirstNo" default="yes"/>
+ <xs:attribute name="copyOf" use="optional" type="xs:NMTOKEN"/>
+ <xs:attribute name="dataRef" use="optional" type="xs:NMTOKEN"/>
+ <xs:attribute name="dir" use="optional" type="xlf:dirValue"/>
+ <xs:attribute name="disp" use="optional"/>
+ <xs:attribute name="equiv" use="optional"/>
+ <xs:attribute name="id" use="optional" type="xs:NMTOKEN"/>
+ <xs:attribute name="isolated" use="optional" type="xlf:yesNo" default="no"/>
+ <xs:attribute name="startRef" use="optional" type="xs:NMTOKEN"/>
+ <xs:attribute name="subFlows" use="optional" type="xs:NMTOKENS"/>
+ <xs:attribute name="subType" use="optional" type="xlf:userDefinedValue"/>
+ <xs:attribute name="type" use="optional" type="xlf:attrType_type"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="mrk">
+ <!-- Annotation Marker -->
+ <xs:complexType mixed="true">
+ <xs:group ref="xlf:inline" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
+ <xs:attribute name="translate" use="optional" type="xlf:yesNo"/>
+ <xs:attribute name="type" use="optional" type="xlf:attrType_typeForMrk"/>
+ <xs:attribute name="ref" use="optional" type="xs:anyURI"/>
+ <xs:attribute name="value" use="optional"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="sm">
+ <!-- Start Annotation Marker -->
+ <xs:complexType mixed="false">
+ <xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
+ <xs:attribute name="translate" use="optional" type="xlf:yesNo"/>
+ <xs:attribute name="type" use="optional" type="xlf:attrType_typeForMrk"/>
+ <xs:attribute name="ref" use="optional" type="xs:anyURI"/>
+ <xs:attribute name="value" use="optional"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="em">
+ <!-- End Annotation Marker -->
+ <xs:complexType mixed="false">
+ <xs:attribute name="startRef" use="required" type="xs:NMTOKEN"/>
+ </xs:complexType>
+ </xs:element>
+
+</xs:schema>
diff --git a/vendor/symfony/translation/Resources/schemas/xml.xsd b/vendor/symfony/translation/Resources/schemas/xml.xsd
new file mode 100755
index 0000000..a46162a
--- /dev/null
+++ b/vendor/symfony/translation/Resources/schemas/xml.xsd
@@ -0,0 +1,309 @@
+<?xml version='1.0'?>
+<?xml-stylesheet href="../2008/09/xsd.xsl" type="text/xsl"?>
+<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns ="http://www.w3.org/1999/xhtml"
+ xml:lang="en">
+
+ <xs:annotation>
+ <xs:documentation>
+ <div>
+ <h1>About the XML namespace</h1>
+
+ <div class="bodytext">
+ <p>
+
+ This schema document describes the XML namespace, in a form
+ suitable for import by other schema documents.
+ </p>
+ <p>
+ See <a href="http://www.w3.org/XML/1998/namespace.html">
+ http://www.w3.org/XML/1998/namespace.html</a> and
+ <a href="http://www.w3.org/TR/REC-xml">
+ http://www.w3.org/TR/REC-xml</a> for information
+ about this namespace.
+ </p>
+
+ <p>
+ Note that local names in this namespace are intended to be
+ defined only by the World Wide Web Consortium or its subgroups.
+ The names currently defined in this namespace are listed below.
+ They should not be used with conflicting semantics by any Working
+ Group, specification, or document instance.
+ </p>
+ <p>
+ See further below in this document for more information about <a
+ href="#usage">how to refer to this schema document from your own
+ XSD schema documents</a> and about <a href="#nsversioning">the
+ namespace-versioning policy governing this schema document</a>.
+ </p>
+ </div>
+ </div>
+
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:attribute name="lang">
+ <xs:annotation>
+ <xs:documentation>
+ <div>
+
+ <h3>lang (as an attribute name)</h3>
+ <p>
+
+ denotes an attribute whose value
+ is a language code for the natural language of the content of
+ any element; its value is inherited. This name is reserved
+ by virtue of its definition in the XML specification.</p>
+
+ </div>
+ <div>
+ <h4>Notes</h4>
+ <p>
+ Attempting to install the relevant ISO 2- and 3-letter
+ codes as the enumerated possible values is probably never
+ going to be a realistic possibility.
+ </p>
+ <p>
+
+ See BCP 47 at <a href="http://www.rfc-editor.org/rfc/bcp/bcp47.txt">
+ http://www.rfc-editor.org/rfc/bcp/bcp47.txt</a>
+ and the IANA language subtag registry at
+ <a href="http://www.iana.org/assignments/language-subtag-registry">
+ http://www.iana.org/assignments/language-subtag-registry</a>
+ for further information.
+ </p>
+ <p>
+
+ The union allows for the 'un-declaration' of xml:lang with
+ the empty string.
+ </p>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:simpleType>
+ <xs:union memberTypes="xs:language">
+ <xs:simpleType>
+ <xs:restriction base="xs:string">
+ <xs:enumeration value=""/>
+
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+ </xs:attribute>
+
+ <xs:attribute name="space">
+ <xs:annotation>
+ <xs:documentation>
+
+ <div>
+
+ <h3>space (as an attribute name)</h3>
+ <p>
+ denotes an attribute whose
+ value is a keyword indicating what whitespace processing
+ discipline is intended for the content of the element; its
+ value is inherited. This name is reserved by virtue of its
+ definition in the XML specification.</p>
+
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:simpleType>
+
+ <xs:restriction base="xs:NCName">
+ <xs:enumeration value="default"/>
+ <xs:enumeration value="preserve"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+
+ <xs:attribute name="base" type="xs:anyURI"> <xs:annotation>
+ <xs:documentation>
+
+ <div>
+
+ <h3>base (as an attribute name)</h3>
+ <p>
+ denotes an attribute whose value
+ provides a URI to be used as the base for interpreting any
+ relative URIs in the scope of the element on which it
+ appears; its value is inherited. This name is reserved
+ by virtue of its definition in the XML Base specification.</p>
+
+ <p>
+ See <a
+ href="http://www.w3.org/TR/xmlbase/">http://www.w3.org/TR/xmlbase/</a>
+ for information about this attribute.
+ </p>
+
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+
+ <xs:attribute name="id" type="xs:ID">
+ <xs:annotation>
+ <xs:documentation>
+ <div>
+
+ <h3>id (as an attribute name)</h3>
+ <p>
+
+ denotes an attribute whose value
+ should be interpreted as if declared to be of type ID.
+ This name is reserved by virtue of its definition in the
+ xml:id specification.</p>
+
+ <p>
+ See <a
+ href="http://www.w3.org/TR/xml-id/">http://www.w3.org/TR/xml-id/</a>
+ for information about this attribute.
+ </p>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+
+ </xs:attribute>
+
+ <xs:attributeGroup name="specialAttrs">
+ <xs:attribute ref="xml:base"/>
+ <xs:attribute ref="xml:lang"/>
+ <xs:attribute ref="xml:space"/>
+ <xs:attribute ref="xml:id"/>
+ </xs:attributeGroup>
+
+ <xs:annotation>
+
+ <xs:documentation>
+ <div>
+
+ <h3>Father (in any context at all)</h3>
+
+ <div class="bodytext">
+ <p>
+ denotes Jon Bosak, the chair of
+ the original XML Working Group. This name is reserved by
+ the following decision of the W3C XML Plenary and
+ XML Coordination groups:
+ </p>
+ <blockquote>
+ <p>
+
+ In appreciation for his vision, leadership and
+ dedication the W3C XML Plenary on this 10th day of
+ February, 2000, reserves for Jon Bosak in perpetuity
+ the XML name "xml:Father".
+ </p>
+ </blockquote>
+ </div>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+ <xs:documentation>
+
+ <div xml:id="usage" id="usage">
+ <h2><a name="usage">About this schema document</a></h2>
+
+ <div class="bodytext">
+ <p>
+ This schema defines attributes and an attribute group suitable
+ for use by schemas wishing to allow <code>xml:base</code>,
+ <code>xml:lang</code>, <code>xml:space</code> or
+ <code>xml:id</code> attributes on elements they define.
+ </p>
+
+ <p>
+ To enable this, such a schema must import this schema for
+ the XML namespace, e.g. as follows:
+ </p>
+ <pre>
+ <schema.. .>
+ .. .
+ <import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+ </pre>
+ <p>
+ or
+ </p>
+ <pre>
+
+ <import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+ </pre>
+ <p>
+ Subsequently, qualified reference to any of the attributes or the
+ group defined below will have the desired effect, e.g.
+ </p>
+ <pre>
+ <type.. .>
+ .. .
+ <attributeGroup ref="xml:specialAttrs"/>
+ </pre>
+ <p>
+ will define a type which will schema-validate an instance element
+ with any of those attributes.
+ </p>
+
+ </div>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+ <xs:documentation>
+ <div id="nsversioning" xml:id="nsversioning">
+ <h2><a name="nsversioning">Versioning policy for this schema document</a></h2>
+
+ <div class="bodytext">
+ <p>
+ In keeping with the XML Schema WG's standard versioning
+ policy, this schema document will persist at
+ <a href="http://www.w3.org/2009/01/xml.xsd">
+ http://www.w3.org/2009/01/xml.xsd</a>.
+ </p>
+ <p>
+ At the date of issue it can also be found at
+ <a href="http://www.w3.org/2001/xml.xsd">
+ http://www.w3.org/2001/xml.xsd</a>.
+ </p>
+
+ <p>
+ The schema document at that URI may however change in the future,
+ in order to remain compatible with the latest version of XML
+ Schema itself, or with the XML namespace itself. In other words,
+ if the XML Schema or XML namespaces change, the version of this
+ document at <a href="http://www.w3.org/2001/xml.xsd">
+ http://www.w3.org/2001/xml.xsd
+ </a>
+ will change accordingly; the version at
+ <a href="http://www.w3.org/2009/01/xml.xsd">
+ http://www.w3.org/2009/01/xml.xsd
+ </a>
+ will not change.
+ </p>
+ <p>
+
+ Previous dated (and unchanging) versions of this schema
+ document are at:
+ </p>
+ <ul>
+ <li><a href="http://www.w3.org/2009/01/xml.xsd">
+ http://www.w3.org/2009/01/xml.xsd</a></li>
+ <li><a href="http://www.w3.org/2007/08/xml.xsd">
+ http://www.w3.org/2007/08/xml.xsd</a></li>
+ <li><a href="http://www.w3.org/2004/10/xml.xsd">
+
+ http://www.w3.org/2004/10/xml.xsd</a></li>
+ <li><a href="http://www.w3.org/2001/03/xml.xsd">
+ http://www.w3.org/2001/03/xml.xsd</a></li>
+ </ul>
+ </div>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+
+</xs:schema>
diff --git a/vendor/symfony/translation/Tests/Catalogue/AbstractOperationTest.php b/vendor/symfony/translation/Tests/Catalogue/AbstractOperationTest.php
new file mode 100755
index 0000000..f82b18f
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Catalogue/AbstractOperationTest.php
@@ -0,0 +1,74 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Catalogue;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\MessageCatalogueInterface;
+
+abstract class AbstractOperationTest extends TestCase
+{
+ public function testGetEmptyDomains()
+ {
+ $this->assertEquals(
+ [],
+ $this->createOperation(
+ new MessageCatalogue('en'),
+ new MessageCatalogue('en')
+ )->getDomains()
+ );
+ }
+
+ public function testGetMergedDomains()
+ {
+ $this->assertEquals(
+ ['a', 'b', 'c'],
+ $this->createOperation(
+ new MessageCatalogue('en', ['a' => [], 'b' => []]),
+ new MessageCatalogue('en', ['b' => [], 'c' => []])
+ )->getDomains()
+ );
+ }
+
+ public function testGetMessagesFromUnknownDomain()
+ {
+ $this->expectException('InvalidArgumentException');
+ $this->createOperation(
+ new MessageCatalogue('en'),
+ new MessageCatalogue('en')
+ )->getMessages('domain');
+ }
+
+ public function testGetEmptyMessages()
+ {
+ $this->assertEquals(
+ [],
+ $this->createOperation(
+ new MessageCatalogue('en', ['a' => []]),
+ new MessageCatalogue('en')
+ )->getMessages('a')
+ );
+ }
+
+ public function testGetEmptyResult()
+ {
+ $this->assertEquals(
+ new MessageCatalogue('en'),
+ $this->createOperation(
+ new MessageCatalogue('en'),
+ new MessageCatalogue('en')
+ )->getResult()
+ );
+ }
+
+ abstract protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target);
+}
diff --git a/vendor/symfony/translation/Tests/Catalogue/MergeOperationTest.php b/vendor/symfony/translation/Tests/Catalogue/MergeOperationTest.php
new file mode 100755
index 0000000..22af86e
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Catalogue/MergeOperationTest.php
@@ -0,0 +1,97 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Catalogue;
+
+use Symfony\Component\Translation\Catalogue\MergeOperation;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\MessageCatalogueInterface;
+
+class MergeOperationTest extends AbstractOperationTest
+{
+ public function testGetMessagesFromSingleDomain()
+ {
+ $operation = $this->createOperation(
+ new MessageCatalogue('en', ['messages' => ['a' => 'old_a', 'b' => 'old_b']]),
+ new MessageCatalogue('en', ['messages' => ['a' => 'new_a', 'c' => 'new_c']])
+ );
+
+ $this->assertEquals(
+ ['a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c'],
+ $operation->getMessages('messages')
+ );
+
+ $this->assertEquals(
+ ['c' => 'new_c'],
+ $operation->getNewMessages('messages')
+ );
+
+ $this->assertEquals(
+ [],
+ $operation->getObsoleteMessages('messages')
+ );
+ }
+
+ public function testGetResultFromSingleDomain()
+ {
+ $this->assertEquals(
+ new MessageCatalogue('en', [
+ 'messages' => ['a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c'],
+ ]),
+ $this->createOperation(
+ new MessageCatalogue('en', ['messages' => ['a' => 'old_a', 'b' => 'old_b']]),
+ new MessageCatalogue('en', ['messages' => ['a' => 'new_a', 'c' => 'new_c']])
+ )->getResult()
+ );
+ }
+
+ public function testGetResultFromIntlDomain()
+ {
+ $this->assertEquals(
+ new MessageCatalogue('en', [
+ 'messages' => ['a' => 'old_a', 'b' => 'old_b'],
+ 'messages+intl-icu' => ['d' => 'old_d', 'c' => 'new_c'],
+ ]),
+ $this->createOperation(
+ new MessageCatalogue('en', ['messages' => ['a' => 'old_a', 'b' => 'old_b'], 'messages+intl-icu' => ['d' => 'old_d']]),
+ new MessageCatalogue('en', ['messages+intl-icu' => ['a' => 'new_a', 'c' => 'new_c']])
+ )->getResult()
+ );
+ }
+
+ public function testGetResultWithMetadata()
+ {
+ $leftCatalogue = new MessageCatalogue('en', ['messages' => ['a' => 'old_a', 'b' => 'old_b']]);
+ $leftCatalogue->setMetadata('a', 'foo', 'messages');
+ $leftCatalogue->setMetadata('b', 'bar', 'messages');
+ $rightCatalogue = new MessageCatalogue('en', ['messages' => ['b' => 'new_b', 'c' => 'new_c']]);
+ $rightCatalogue->setMetadata('b', 'baz', 'messages');
+ $rightCatalogue->setMetadata('c', 'qux', 'messages');
+
+ $mergedCatalogue = new MessageCatalogue('en', ['messages' => ['a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c']]);
+ $mergedCatalogue->setMetadata('a', 'foo', 'messages');
+ $mergedCatalogue->setMetadata('b', 'bar', 'messages');
+ $mergedCatalogue->setMetadata('c', 'qux', 'messages');
+
+ $this->assertEquals(
+ $mergedCatalogue,
+ $this->createOperation(
+ $leftCatalogue,
+ $rightCatalogue
+ )->getResult()
+ );
+ }
+
+ protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target)
+ {
+ return new MergeOperation($source, $target);
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Catalogue/TargetOperationTest.php b/vendor/symfony/translation/Tests/Catalogue/TargetOperationTest.php
new file mode 100755
index 0000000..570b503
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Catalogue/TargetOperationTest.php
@@ -0,0 +1,96 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Catalogue;
+
+use Symfony\Component\Translation\Catalogue\TargetOperation;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\MessageCatalogueInterface;
+
+class TargetOperationTest extends AbstractOperationTest
+{
+ public function testGetMessagesFromSingleDomain()
+ {
+ $operation = $this->createOperation(
+ new MessageCatalogue('en', ['messages' => ['a' => 'old_a', 'b' => 'old_b']]),
+ new MessageCatalogue('en', ['messages' => ['a' => 'new_a', 'c' => 'new_c']])
+ );
+
+ $this->assertEquals(
+ ['a' => 'old_a', 'c' => 'new_c'],
+ $operation->getMessages('messages')
+ );
+
+ $this->assertEquals(
+ ['c' => 'new_c'],
+ $operation->getNewMessages('messages')
+ );
+
+ $this->assertEquals(
+ ['b' => 'old_b'],
+ $operation->getObsoleteMessages('messages')
+ );
+ }
+
+ public function testGetResultFromSingleDomain()
+ {
+ $this->assertEquals(
+ new MessageCatalogue('en', [
+ 'messages' => ['a' => 'old_a', 'c' => 'new_c'],
+ ]),
+ $this->createOperation(
+ new MessageCatalogue('en', ['messages' => ['a' => 'old_a', 'b' => 'old_b']]),
+ new MessageCatalogue('en', ['messages' => ['a' => 'new_a', 'c' => 'new_c']])
+ )->getResult()
+ );
+ }
+
+ public function testGetResultFromIntlDomain()
+ {
+ $this->assertEquals(
+ new MessageCatalogue('en', [
+ 'messages' => ['a' => 'old_a'],
+ 'messages+intl-icu' => ['c' => 'new_c'],
+ ]),
+ $this->createOperation(
+ new MessageCatalogue('en', ['messages' => ['a' => 'old_a'], 'messages+intl-icu' => ['b' => 'old_b']]),
+ new MessageCatalogue('en', ['messages' => ['a' => 'new_a'], 'messages+intl-icu' => ['c' => 'new_c']])
+ )->getResult()
+ );
+ }
+
+ public function testGetResultWithMetadata()
+ {
+ $leftCatalogue = new MessageCatalogue('en', ['messages' => ['a' => 'old_a', 'b' => 'old_b']]);
+ $leftCatalogue->setMetadata('a', 'foo', 'messages');
+ $leftCatalogue->setMetadata('b', 'bar', 'messages');
+ $rightCatalogue = new MessageCatalogue('en', ['messages' => ['b' => 'new_b', 'c' => 'new_c']]);
+ $rightCatalogue->setMetadata('b', 'baz', 'messages');
+ $rightCatalogue->setMetadata('c', 'qux', 'messages');
+
+ $diffCatalogue = new MessageCatalogue('en', ['messages' => ['b' => 'old_b', 'c' => 'new_c']]);
+ $diffCatalogue->setMetadata('b', 'bar', 'messages');
+ $diffCatalogue->setMetadata('c', 'qux', 'messages');
+
+ $this->assertEquals(
+ $diffCatalogue,
+ $this->createOperation(
+ $leftCatalogue,
+ $rightCatalogue
+ )->getResult()
+ );
+ }
+
+ protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target)
+ {
+ return new TargetOperation($source, $target);
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Command/XliffLintCommandTest.php b/vendor/symfony/translation/Tests/Command/XliffLintCommandTest.php
new file mode 100755
index 0000000..df2e2f0
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Command/XliffLintCommandTest.php
@@ -0,0 +1,218 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Command;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Tester\CommandTester;
+use Symfony\Component\Translation\Command\XliffLintCommand;
+
+/**
+ * Tests the XliffLintCommand.
+ *
+ * @author Javier Eguiluz <javier.eguiluz@gmail.com>
+ */
+class XliffLintCommandTest extends TestCase
+{
+ private $files;
+
+ public function testLintCorrectFile()
+ {
+ $tester = $this->createCommandTester();
+ $filename = $this->createFile();
+
+ $tester->execute(
+ ['filename' => $filename],
+ ['verbosity' => OutputInterface::VERBOSITY_VERBOSE, 'decorated' => false]
+ );
+
+ $this->assertEquals(0, $tester->getStatusCode(), 'Returns 0 in case of success');
+ $this->assertContains('OK', trim($tester->getDisplay()));
+ }
+
+ public function testLintCorrectFiles()
+ {
+ $tester = $this->createCommandTester();
+ $filename1 = $this->createFile();
+ $filename2 = $this->createFile();
+
+ $tester->execute(
+ ['filename' => [$filename1, $filename2]],
+ ['verbosity' => OutputInterface::VERBOSITY_VERBOSE, 'decorated' => false]
+ );
+
+ $this->assertEquals(0, $tester->getStatusCode(), 'Returns 0 in case of success');
+ $this->assertContains('OK', trim($tester->getDisplay()));
+ }
+
+ /**
+ * @dataProvider provideStrictFilenames
+ */
+ public function testStrictFilenames($requireStrictFileNames, $fileNamePattern, $targetLanguage, $mustFail)
+ {
+ $tester = $this->createCommandTester($requireStrictFileNames);
+ $filename = $this->createFile('note', $targetLanguage, $fileNamePattern);
+
+ $tester->execute(
+ ['filename' => $filename],
+ ['verbosity' => OutputInterface::VERBOSITY_VERBOSE, 'decorated' => false]
+ );
+
+ $this->assertEquals($mustFail ? 1 : 0, $tester->getStatusCode());
+ $this->assertContains($mustFail ? '[WARNING] 0 XLIFF files have valid syntax and 1 contain errors.' : '[OK] All 1 XLIFF files contain valid syntax.', $tester->getDisplay());
+ }
+
+ public function testLintIncorrectXmlSyntax()
+ {
+ $tester = $this->createCommandTester();
+ $filename = $this->createFile('note <target>');
+
+ $tester->execute(['filename' => $filename], ['decorated' => false]);
+
+ $this->assertEquals(1, $tester->getStatusCode(), 'Returns 1 in case of error');
+ $this->assertContains('Opening and ending tag mismatch: target line 6 and source', trim($tester->getDisplay()));
+ }
+
+ public function testLintIncorrectTargetLanguage()
+ {
+ $tester = $this->createCommandTester();
+ $filename = $this->createFile('note', 'es');
+
+ $tester->execute(['filename' => $filename], ['decorated' => false]);
+
+ $this->assertEquals(1, $tester->getStatusCode(), 'Returns 1 in case of error');
+ $this->assertContains('There is a mismatch between the language included in the file name ("messages.en.xlf") and the "es" value used in the "target-language" attribute of the file.', trim($tester->getDisplay()));
+ }
+
+ public function testLintTargetLanguageIsCaseInsensitive()
+ {
+ $tester = $this->createCommandTester();
+ $filename = $this->createFile('note', 'zh-cn', 'messages.zh_CN.xlf');
+
+ $tester->execute(['filename' => $filename], ['decorated' => false]);
+
+ $this->assertEquals(0, $tester->getStatusCode());
+ $this->assertContains('[OK] All 1 XLIFF files contain valid syntax.', trim($tester->getDisplay()));
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testLintFileNotReadable()
+ {
+ $tester = $this->createCommandTester();
+ $filename = $this->createFile();
+ unlink($filename);
+
+ $tester->execute(['filename' => $filename], ['decorated' => false]);
+ }
+
+ public function testGetHelp()
+ {
+ $command = new XliffLintCommand();
+ $expected = <<<EOF
+The <info>%command.name%</info> command lints a XLIFF file and outputs to STDOUT
+the first encountered syntax error.
+
+You can validates XLIFF contents passed from STDIN:
+
+ <info>cat filename | php %command.full_name%</info>
+
+You can also validate the syntax of a file:
+
+ <info>php %command.full_name% filename</info>
+
+Or of a whole directory:
+
+ <info>php %command.full_name% dirname</info>
+ <info>php %command.full_name% dirname --format=json</info>
+
+EOF;
+
+ $this->assertEquals($expected, $command->getHelp());
+ }
+
+ /**
+ * @return string Path to the new file
+ */
+ private function createFile($sourceContent = 'note', $targetLanguage = 'en', $fileNamePattern = 'messages.%locale%.xlf')
+ {
+ $xliffContent = <<<XLIFF
+<?xml version="1.0"?>
+<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
+ <file source-language="en" target-language="$targetLanguage" datatype="plaintext" original="file.ext">
+ <body>
+ <trans-unit id="note">
+ <source>$sourceContent</source>
+ <target>NOTE</target>
+ </trans-unit>
+ </body>
+ </file>
+</xliff>
+XLIFF;
+
+ $filename = sprintf('%s/translation-xliff-lint-test/%s', sys_get_temp_dir(), str_replace('%locale%', 'en', $fileNamePattern));
+ file_put_contents($filename, $xliffContent);
+
+ $this->files[] = $filename;
+
+ return $filename;
+ }
+
+ /**
+ * @return CommandTester
+ */
+ private function createCommandTester($requireStrictFileNames = true, $application = null)
+ {
+ if (!$application) {
+ $application = new Application();
+ $application->add(new XliffLintCommand(null, null, null, $requireStrictFileNames));
+ }
+
+ $command = $application->find('lint:xliff');
+
+ if ($application) {
+ $command->setApplication($application);
+ }
+
+ return new CommandTester($command);
+ }
+
+ protected function setUp()
+ {
+ $this->files = [];
+ @mkdir(sys_get_temp_dir().'/translation-xliff-lint-test');
+ }
+
+ protected function tearDown()
+ {
+ foreach ($this->files as $file) {
+ if (file_exists($file)) {
+ unlink($file);
+ }
+ }
+ rmdir(sys_get_temp_dir().'/translation-xliff-lint-test');
+ }
+
+ public function provideStrictFilenames()
+ {
+ yield [false, 'messages.%locale%.xlf', 'en', false];
+ yield [false, 'messages.%locale%.xlf', 'es', true];
+ yield [false, '%locale%.messages.xlf', 'en', false];
+ yield [false, '%locale%.messages.xlf', 'es', true];
+ yield [true, 'messages.%locale%.xlf', 'en', false];
+ yield [true, 'messages.%locale%.xlf', 'es', true];
+ yield [true, '%locale%.messages.xlf', 'en', true];
+ yield [true, '%locale%.messages.xlf', 'es', true];
+ }
+}
diff --git a/vendor/symfony/translation/Tests/DataCollector/TranslationDataCollectorTest.php b/vendor/symfony/translation/Tests/DataCollector/TranslationDataCollectorTest.php
new file mode 100755
index 0000000..bd97a24
--- /dev/null
+++ b/vendor/symfony/translation/Tests/DataCollector/TranslationDataCollectorTest.php
@@ -0,0 +1,150 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\DataCollector;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\DataCollector\TranslationDataCollector;
+use Symfony\Component\Translation\DataCollectorTranslator;
+
+class TranslationDataCollectorTest extends TestCase
+{
+ protected function setUp()
+ {
+ if (!class_exists('Symfony\Component\HttpKernel\DataCollector\DataCollector')) {
+ $this->markTestSkipped('The "DataCollector" is not available');
+ }
+ }
+
+ public function testCollectEmptyMessages()
+ {
+ $translator = $this->getTranslator();
+ $translator->expects($this->any())->method('getCollectedMessages')->willReturn([]);
+
+ $dataCollector = new TranslationDataCollector($translator);
+ $dataCollector->lateCollect();
+
+ $this->assertEquals(0, $dataCollector->getCountMissings());
+ $this->assertEquals(0, $dataCollector->getCountFallbacks());
+ $this->assertEquals(0, $dataCollector->getCountDefines());
+ $this->assertEquals([], $dataCollector->getMessages()->getValue());
+ }
+
+ public function testCollect()
+ {
+ $collectedMessages = [
+ [
+ 'id' => 'foo',
+ 'translation' => 'foo (en)',
+ 'locale' => 'en',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_DEFINED,
+ 'parameters' => [],
+ 'transChoiceNumber' => null,
+ ],
+ [
+ 'id' => 'bar',
+ 'translation' => 'bar (fr)',
+ 'locale' => 'fr',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
+ 'parameters' => [],
+ 'transChoiceNumber' => null,
+ ],
+ [
+ 'id' => 'choice',
+ 'translation' => 'choice',
+ 'locale' => 'en',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_MISSING,
+ 'parameters' => ['%count%' => 3],
+ 'transChoiceNumber' => 3,
+ ],
+ [
+ 'id' => 'choice',
+ 'translation' => 'choice',
+ 'locale' => 'en',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_MISSING,
+ 'parameters' => ['%count%' => 3],
+ 'transChoiceNumber' => 3,
+ ],
+ [
+ 'id' => 'choice',
+ 'translation' => 'choice',
+ 'locale' => 'en',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_MISSING,
+ 'parameters' => ['%count%' => 4, '%foo%' => 'bar'],
+ 'transChoiceNumber' => 4,
+ ],
+ ];
+ $expectedMessages = [
+ [
+ 'id' => 'foo',
+ 'translation' => 'foo (en)',
+ 'locale' => 'en',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_DEFINED,
+ 'count' => 1,
+ 'parameters' => [],
+ 'transChoiceNumber' => null,
+ ],
+ [
+ 'id' => 'bar',
+ 'translation' => 'bar (fr)',
+ 'locale' => 'fr',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
+ 'count' => 1,
+ 'parameters' => [],
+ 'transChoiceNumber' => null,
+ ],
+ [
+ 'id' => 'choice',
+ 'translation' => 'choice',
+ 'locale' => 'en',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_MISSING,
+ 'count' => 3,
+ 'parameters' => [
+ ['%count%' => 3],
+ ['%count%' => 3],
+ ['%count%' => 4, '%foo%' => 'bar'],
+ ],
+ 'transChoiceNumber' => 3,
+ ],
+ ];
+
+ $translator = $this->getTranslator();
+ $translator->expects($this->any())->method('getCollectedMessages')->willReturn($collectedMessages);
+
+ $dataCollector = new TranslationDataCollector($translator);
+ $dataCollector->lateCollect();
+
+ $this->assertEquals(1, $dataCollector->getCountMissings());
+ $this->assertEquals(1, $dataCollector->getCountFallbacks());
+ $this->assertEquals(1, $dataCollector->getCountDefines());
+
+ $this->assertEquals($expectedMessages, array_values($dataCollector->getMessages()->getValue(true)));
+ }
+
+ private function getTranslator()
+ {
+ $translator = $this
+ ->getMockBuilder('Symfony\Component\Translation\DataCollectorTranslator')
+ ->disableOriginalConstructor()
+ ->getMock()
+ ;
+
+ return $translator;
+ }
+}
diff --git a/vendor/symfony/translation/Tests/DataCollectorTranslatorTest.php b/vendor/symfony/translation/Tests/DataCollectorTranslatorTest.php
new file mode 100755
index 0000000..138172e
--- /dev/null
+++ b/vendor/symfony/translation/Tests/DataCollectorTranslatorTest.php
@@ -0,0 +1,116 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\DataCollectorTranslator;
+use Symfony\Component\Translation\Loader\ArrayLoader;
+use Symfony\Component\Translation\Translator;
+
+class DataCollectorTranslatorTest extends TestCase
+{
+ public function testCollectMessages()
+ {
+ $collector = $this->createCollector();
+ $collector->setFallbackLocales(['fr', 'ru']);
+
+ $collector->trans('foo');
+ $collector->trans('bar');
+ $collector->trans('choice', ['%count%' => 0]);
+ $collector->trans('bar_ru');
+ $collector->trans('bar_ru', ['foo' => 'bar']);
+
+ $expectedMessages = [];
+ $expectedMessages[] = [
+ 'id' => 'foo',
+ 'translation' => 'foo (en)',
+ 'locale' => 'en',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_DEFINED,
+ 'parameters' => [],
+ 'transChoiceNumber' => null,
+ ];
+ $expectedMessages[] = [
+ 'id' => 'bar',
+ 'translation' => 'bar (fr)',
+ 'locale' => 'fr',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
+ 'parameters' => [],
+ 'transChoiceNumber' => null,
+ ];
+ $expectedMessages[] = [
+ 'id' => 'choice',
+ 'translation' => 'choice',
+ 'locale' => 'en',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_MISSING,
+ 'parameters' => ['%count%' => 0],
+ 'transChoiceNumber' => 0,
+ ];
+ $expectedMessages[] = [
+ 'id' => 'bar_ru',
+ 'translation' => 'bar (ru)',
+ 'locale' => 'ru',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
+ 'parameters' => [],
+ 'transChoiceNumber' => null,
+ ];
+ $expectedMessages[] = [
+ 'id' => 'bar_ru',
+ 'translation' => 'bar (ru)',
+ 'locale' => 'ru',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
+ 'parameters' => ['foo' => 'bar'],
+ 'transChoiceNumber' => null,
+ ];
+
+ $this->assertEquals($expectedMessages, $collector->getCollectedMessages());
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testCollectMessagesTransChoice()
+ {
+ $collector = $this->createCollector();
+ $collector->setFallbackLocales(['fr', 'ru']);
+ $collector->transChoice('choice', 0);
+
+ $expectedMessages = [];
+
+ $expectedMessages[] = [
+ 'id' => 'choice',
+ 'translation' => 'choice',
+ 'locale' => 'en',
+ 'domain' => 'messages',
+ 'state' => DataCollectorTranslator::MESSAGE_MISSING,
+ 'parameters' => ['%count%' => 0],
+ 'transChoiceNumber' => 0,
+ ];
+
+ $this->assertEquals($expectedMessages, $collector->getCollectedMessages());
+ }
+
+ private function createCollector()
+ {
+ $translator = new Translator('en');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foo (en)'], 'en');
+ $translator->addResource('array', ['bar' => 'bar (fr)'], 'fr');
+ $translator->addResource('array', ['bar_ru' => 'bar (ru)'], 'ru');
+
+ return new DataCollectorTranslator($translator);
+ }
+}
diff --git a/vendor/symfony/translation/Tests/DependencyInjection/TranslationDumperPassTest.php b/vendor/symfony/translation/Tests/DependencyInjection/TranslationDumperPassTest.php
new file mode 100755
index 0000000..94769e9
--- /dev/null
+++ b/vendor/symfony/translation/Tests/DependencyInjection/TranslationDumperPassTest.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass;
+
+class TranslationDumperPassTest extends TestCase
+{
+ public function testProcess()
+ {
+ $container = new ContainerBuilder();
+ $writerDefinition = $container->register('translation.writer');
+ $container->register('foo.id')
+ ->addTag('translation.dumper', ['alias' => 'bar.alias']);
+
+ $translationDumperPass = new TranslationDumperPass();
+ $translationDumperPass->process($container);
+
+ $this->assertEquals([['addDumper', ['bar.alias', new Reference('foo.id')]]], $writerDefinition->getMethodCalls());
+ }
+
+ public function testProcessNoDefinitionFound()
+ {
+ $container = new ContainerBuilder();
+
+ $definitionsBefore = \count($container->getDefinitions());
+ $aliasesBefore = \count($container->getAliases());
+
+ $translationDumperPass = new TranslationDumperPass();
+ $translationDumperPass->process($container);
+
+ // the container is untouched (i.e. no new definitions or aliases)
+ $this->assertCount($definitionsBefore, $container->getDefinitions());
+ $this->assertCount($aliasesBefore, $container->getAliases());
+ }
+}
diff --git a/vendor/symfony/translation/Tests/DependencyInjection/TranslationExtractorPassTest.php b/vendor/symfony/translation/Tests/DependencyInjection/TranslationExtractorPassTest.php
new file mode 100755
index 0000000..a638498
--- /dev/null
+++ b/vendor/symfony/translation/Tests/DependencyInjection/TranslationExtractorPassTest.php
@@ -0,0 +1,66 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass;
+
+class TranslationExtractorPassTest extends TestCase
+{
+ public function testProcess()
+ {
+ $container = new ContainerBuilder();
+ $extractorDefinition = $container->register('translation.extractor');
+ $container->register('foo.id')
+ ->addTag('translation.extractor', ['alias' => 'bar.alias']);
+
+ $translationDumperPass = new TranslationExtractorPass();
+ $translationDumperPass->process($container);
+
+ $this->assertEquals([['addExtractor', ['bar.alias', new Reference('foo.id')]]], $extractorDefinition->getMethodCalls());
+ }
+
+ public function testProcessNoDefinitionFound()
+ {
+ $container = new ContainerBuilder();
+
+ $definitionsBefore = \count($container->getDefinitions());
+ $aliasesBefore = \count($container->getAliases());
+
+ $translationDumperPass = new TranslationExtractorPass();
+ $translationDumperPass->process($container);
+
+ // the container is untouched (i.e. no new definitions or aliases)
+ $this->assertCount($definitionsBefore, $container->getDefinitions());
+ $this->assertCount($aliasesBefore, $container->getAliases());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
+ * @expectedExceptionMessage The alias for the tag "translation.extractor" of service "foo.id" must be set.
+ */
+ public function testProcessMissingAlias()
+ {
+ $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->disableOriginalConstructor()->getMock();
+ $container = new ContainerBuilder();
+ $container->register('translation.extractor');
+ $container->register('foo.id')
+ ->addTag('translation.extractor', []);
+
+ $definition->expects($this->never())->method('addMethodCall');
+
+ $translationDumperPass = new TranslationExtractorPass();
+ $translationDumperPass->process($container);
+ }
+}
diff --git a/vendor/symfony/translation/Tests/DependencyInjection/TranslationPassTest.php b/vendor/symfony/translation/Tests/DependencyInjection/TranslationPassTest.php
new file mode 100755
index 0000000..f62fc85
--- /dev/null
+++ b/vendor/symfony/translation/Tests/DependencyInjection/TranslationPassTest.php
@@ -0,0 +1,122 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\Translation\DependencyInjection\TranslatorPass;
+
+class TranslationPassTest extends TestCase
+{
+ public function testValidCollector()
+ {
+ $loader = (new Definition())
+ ->addTag('translation.loader', ['alias' => 'xliff', 'legacy-alias' => 'xlf']);
+
+ $reader = new Definition();
+
+ $translator = (new Definition())
+ ->setArguments([null, null, null, null]);
+
+ $container = new ContainerBuilder();
+ $container->setDefinition('translator.default', $translator);
+ $container->setDefinition('translation.reader', $reader);
+ $container->setDefinition('translation.xliff_loader', $loader);
+
+ $pass = new TranslatorPass('translator.default', 'translation.reader');
+ $pass->process($container);
+
+ $expectedReader = (new Definition())
+ ->addMethodCall('addLoader', ['xliff', new Reference('translation.xliff_loader')])
+ ->addMethodCall('addLoader', ['xlf', new Reference('translation.xliff_loader')])
+ ;
+ $this->assertEquals($expectedReader, $reader);
+
+ $expectedLoader = (new Definition())
+ ->addTag('translation.loader', ['alias' => 'xliff', 'legacy-alias' => 'xlf'])
+ ;
+ $this->assertEquals($expectedLoader, $loader);
+
+ $this->assertSame(['translation.xliff_loader' => ['xliff', 'xlf']], $translator->getArgument(3));
+
+ $expected = ['translation.xliff_loader' => new ServiceClosureArgument(new Reference('translation.xliff_loader'))];
+ $this->assertEquals($expected, $container->getDefinition((string) $translator->getArgument(0))->getArgument(0));
+ }
+
+ public function testValidCommandsViewPathsArgument()
+ {
+ $container = new ContainerBuilder();
+ $container->register('translator.default')
+ ->setArguments([null, null, null, null])
+ ;
+ $debugCommand = $container->register('console.command.translation_debug')
+ ->setArguments([null, null, null, null, null, [], []])
+ ;
+ $updateCommand = $container->register('console.command.translation_update')
+ ->setArguments([null, null, null, null, null, null, [], []])
+ ;
+ $container->register('twig.template_iterator')
+ ->setArguments([null, null, ['other/templates' => null, 'tpl' => 'App']])
+ ;
+ $container->setParameter('twig.default_path', 'templates');
+
+ $pass = new TranslatorPass('translator.default');
+ $pass->process($container);
+
+ $expectedViewPaths = ['other/templates', 'tpl'];
+
+ $this->assertSame('templates', $debugCommand->getArgument(4));
+ $this->assertSame('templates', $updateCommand->getArgument(5));
+ $this->assertSame($expectedViewPaths, $debugCommand->getArgument(6));
+ $this->assertSame($expectedViewPaths, $updateCommand->getArgument(7));
+ }
+
+ public function testCommandsViewPathsArgumentsAreIgnoredWithOldServiceDefinitions()
+ {
+ $container = new ContainerBuilder();
+ $container->register('translator.default')
+ ->setArguments([null, null, null, null])
+ ;
+ $debugCommand = $container->register('console.command.translation_debug')
+ ->setArguments([
+ new Reference('translator'),
+ new Reference('translation.reader'),
+ new Reference('translation.extractor'),
+ '%translator.default_path%',
+ null,
+ ])
+ ;
+ $updateCommand = $container->register('console.command.translation_update')
+ ->setArguments([
+ new Reference('translation.writer'),
+ new Reference('translation.reader'),
+ new Reference('translation.extractor'),
+ '%kernel.default_locale%',
+ '%translator.default_path%',
+ null,
+ ])
+ ;
+ $container->register('twig.template_iterator')
+ ->setArguments([null, null, ['other/templates' => null, 'tpl' => 'App']])
+ ;
+ $container->setParameter('twig.default_path', 'templates');
+
+ $pass = new TranslatorPass('translator.default');
+ $pass->process($container);
+
+ $this->assertSame('templates', $debugCommand->getArgument(4));
+ $this->assertSame('templates', $updateCommand->getArgument(5));
+ }
+}
diff --git a/vendor/symfony/translation/Tests/DependencyInjection/TranslationPathsPassTest.php b/vendor/symfony/translation/Tests/DependencyInjection/TranslationPathsPassTest.php
new file mode 100755
index 0000000..42ab398
--- /dev/null
+++ b/vendor/symfony/translation/Tests/DependencyInjection/TranslationPathsPassTest.php
@@ -0,0 +1,89 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ServiceLocator;
+use Symfony\Component\Translation\DependencyInjection\TranslatorPathsPass;
+use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ControllerArguments;
+use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ServiceArguments;
+use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ServiceMethodCalls;
+use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ServiceProperties;
+use Symfony\Component\Translation\Tests\DependencyInjection\fixtures\ServiceSubscriber;
+
+class TranslationPathsPassTest extends TestCase
+{
+ public function testProcess()
+ {
+ $container = new ContainerBuilder();
+ $container->register('translator');
+ $debugCommand = $container->register('console.command.translation_debug')
+ ->setArguments([null, null, null, null, null, [], []])
+ ;
+ $updateCommand = $container->register('console.command.translation_update')
+ ->setArguments([null, null, null, null, null, null, [], []])
+ ;
+ $container->register(ControllerArguments::class, ControllerArguments::class)
+ ->setTags(['controller.service_arguments'])
+ ;
+ $container->register(ServiceArguments::class, ServiceArguments::class)
+ ->setArguments([new Reference('translator')])
+ ;
+ $container->register(ServiceProperties::class, ServiceProperties::class)
+ ->setProperties([new Reference('translator')])
+ ;
+ $container->register(ServiceMethodCalls::class, ServiceMethodCalls::class)
+ ->setMethodCalls([['setTranslator', [new Reference('translator')]]])
+ ;
+ $container->register('service_rc')
+ ->setArguments([new Definition(), new Reference(ServiceMethodCalls::class)])
+ ;
+ $serviceLocator1 = $container->register('.service_locator.foo', ServiceLocator::class)
+ ->setArguments([new ServiceClosureArgument(new Reference('translator'))])
+ ;
+ $serviceLocator2 = (new Definition(ServiceLocator::class))
+ ->setArguments([ServiceSubscriber::class, new Reference('service_container')])
+ ->setFactory([$serviceLocator1, 'withContext'])
+ ;
+ $container->register('service_subscriber', ServiceSubscriber::class)
+ ->setArguments([$serviceLocator2])
+ ;
+ $container->register('.service_locator.bar', ServiceLocator::class)
+ ->setArguments([[
+ ControllerArguments::class.'::index' => new ServiceClosureArgument(new Reference('.service_locator.foo')),
+ ControllerArguments::class.'::__invoke' => new ServiceClosureArgument(new Reference('.service_locator.foo')),
+ ControllerArguments::class => new ServiceClosureArgument(new Reference('.service_locator.foo')),
+ ]])
+ ;
+ $container->register('argument_resolver.service')
+ ->setArguments([new Reference('.service_locator.bar')])
+ ;
+
+ $pass = new TranslatorPathsPass('translator', 'console.command.translation_debug', 'console.command.translation_update', 'argument_resolver.service');
+ $pass->process($container);
+
+ $expectedPaths = [
+ $container->getReflectionClass(ServiceArguments::class)->getFileName(),
+ $container->getReflectionClass(ServiceProperties::class)->getFileName(),
+ $container->getReflectionClass(ServiceMethodCalls::class)->getFileName(),
+ $container->getReflectionClass(ControllerArguments::class)->getFileName(),
+ $container->getReflectionClass(ServiceSubscriber::class)->getFileName(),
+ ];
+
+ $this->assertSame($expectedPaths, $debugCommand->getArgument(6));
+ $this->assertSame($expectedPaths, $updateCommand->getArgument(7));
+ }
+}
diff --git a/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ControllerArguments.php b/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ControllerArguments.php
new file mode 100755
index 0000000..97a53fa
--- /dev/null
+++ b/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ControllerArguments.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Symfony\Component\Translation\Tests\DependencyInjection\fixtures;
+
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+class ControllerArguments
+{
+ public function __invoke(TranslatorInterface $translator)
+ {
+ }
+
+ public function index(TranslatorInterface $translator)
+ {
+ }
+}
diff --git a/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceArguments.php b/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceArguments.php
new file mode 100755
index 0000000..80c629d
--- /dev/null
+++ b/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceArguments.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Symfony\Component\Translation\Tests\DependencyInjection\fixtures;
+
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+class ServiceArguments
+{
+ public function __construct(TranslatorInterface $translator)
+ {
+ }
+}
diff --git a/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceMethodCalls.php b/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceMethodCalls.php
new file mode 100755
index 0000000..8998d89
--- /dev/null
+++ b/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceMethodCalls.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Symfony\Component\Translation\Tests\DependencyInjection\fixtures;
+
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+class ServiceMethodCalls
+{
+ public function setTranslator(TranslatorInterface $translator)
+ {
+ }
+}
diff --git a/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceProperties.php b/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceProperties.php
new file mode 100755
index 0000000..f5098e9
--- /dev/null
+++ b/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceProperties.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Symfony\Component\Translation\Tests\DependencyInjection\fixtures;
+
+class ServiceProperties
+{
+ public $translator;
+}
diff --git a/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceSubscriber.php b/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceSubscriber.php
new file mode 100755
index 0000000..65b60d6
--- /dev/null
+++ b/vendor/symfony/translation/Tests/DependencyInjection/fixtures/ServiceSubscriber.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Symfony\Component\Translation\Tests\DependencyInjection\fixtures;
+
+use Psr\Container\ContainerInterface;
+use Symfony\Contracts\Service\ServiceSubscriberInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+class ServiceSubscriber implements ServiceSubscriberInterface
+{
+ public function __construct(ContainerInterface $container)
+ {
+ }
+
+ public static function getSubscribedServices()
+ {
+ return ['translator' => TranslatorInterface::class];
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Dumper/CsvFileDumperTest.php b/vendor/symfony/translation/Tests/Dumper/CsvFileDumperTest.php
new file mode 100755
index 0000000..0d1cf2c
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Dumper/CsvFileDumperTest.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Dumper\CsvFileDumper;
+use Symfony\Component\Translation\MessageCatalogue;
+
+class CsvFileDumperTest extends TestCase
+{
+ public function testFormatCatalogue()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add(['foo' => 'bar', 'bar' => 'foo
+foo', 'foo;foo' => 'bar']);
+
+ $dumper = new CsvFileDumper();
+
+ $this->assertStringEqualsFile(__DIR__.'/../fixtures/valid.csv', $dumper->formatCatalogue($catalogue, 'messages'));
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Dumper/FileDumperTest.php b/vendor/symfony/translation/Tests/Dumper/FileDumperTest.php
new file mode 100755
index 0000000..20fa918
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Dumper/FileDumperTest.php
@@ -0,0 +1,90 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Dumper\FileDumper;
+use Symfony\Component\Translation\MessageCatalogue;
+
+class FileDumperTest extends TestCase
+{
+ public function testDump()
+ {
+ $tempDir = sys_get_temp_dir();
+
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add(['foo' => 'bar']);
+
+ $dumper = new ConcreteFileDumper();
+ $dumper->dump($catalogue, ['path' => $tempDir]);
+
+ $this->assertFileExists($tempDir.'/messages.en.concrete');
+
+ @unlink($tempDir.'/messages.en.concrete');
+ }
+
+ public function testDumpIntl()
+ {
+ $tempDir = sys_get_temp_dir();
+
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add(['foo' => 'bar'], 'd1');
+ $catalogue->add(['bar' => 'foo'], 'd1+intl-icu');
+ $catalogue->add(['bar' => 'foo'], 'd2+intl-icu');
+
+ $dumper = new ConcreteFileDumper();
+ @unlink($tempDir.'/d2.en.concrete');
+ $dumper->dump($catalogue, ['path' => $tempDir]);
+
+ $this->assertStringEqualsFile($tempDir.'/d1.en.concrete', 'foo=bar');
+ @unlink($tempDir.'/d1.en.concrete');
+
+ $this->assertStringEqualsFile($tempDir.'/d1+intl-icu.en.concrete', 'bar=foo');
+ @unlink($tempDir.'/d1+intl-icu.en.concrete');
+
+ $this->assertFileNotExists($tempDir.'/d2.en.concrete');
+ $this->assertStringEqualsFile($tempDir.'/d2+intl-icu.en.concrete', 'bar=foo');
+ @unlink($tempDir.'/d2+intl-icu.en.concrete');
+ }
+
+ public function testDumpCreatesNestedDirectoriesAndFile()
+ {
+ $tempDir = sys_get_temp_dir();
+ $translationsDir = $tempDir.'/test/translations';
+ $file = $translationsDir.'/messages.en.concrete';
+
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add(['foo' => 'bar']);
+
+ $dumper = new ConcreteFileDumper();
+ $dumper->setRelativePathTemplate('test/translations/%domain%.%locale%.%extension%');
+ $dumper->dump($catalogue, ['path' => $tempDir]);
+
+ $this->assertFileExists($file);
+
+ @unlink($file);
+ @rmdir($translationsDir);
+ }
+}
+
+class ConcreteFileDumper extends FileDumper
+{
+ public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = [])
+ {
+ return http_build_query($messages->all($domain), '', '&');
+ }
+
+ protected function getExtension()
+ {
+ return 'concrete';
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Dumper/IcuResFileDumperTest.php b/vendor/symfony/translation/Tests/Dumper/IcuResFileDumperTest.php
new file mode 100755
index 0000000..dcb9c2c
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Dumper/IcuResFileDumperTest.php
@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Dumper\IcuResFileDumper;
+use Symfony\Component\Translation\MessageCatalogue;
+
+class IcuResFileDumperTest extends TestCase
+{
+ public function testFormatCatalogue()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add(['foo' => 'bar']);
+
+ $dumper = new IcuResFileDumper();
+
+ $this->assertStringEqualsFile(__DIR__.'/../fixtures/resourcebundle/res/en.res', $dumper->formatCatalogue($catalogue, 'messages'));
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Dumper/IniFileDumperTest.php b/vendor/symfony/translation/Tests/Dumper/IniFileDumperTest.php
new file mode 100755
index 0000000..1ed168b
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Dumper/IniFileDumperTest.php
@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Dumper\IniFileDumper;
+use Symfony\Component\Translation\MessageCatalogue;
+
+class IniFileDumperTest extends TestCase
+{
+ public function testFormatCatalogue()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add(['foo' => 'bar']);
+
+ $dumper = new IniFileDumper();
+
+ $this->assertStringEqualsFile(__DIR__.'/../fixtures/resources.ini', $dumper->formatCatalogue($catalogue, 'messages'));
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Dumper/JsonFileDumperTest.php b/vendor/symfony/translation/Tests/Dumper/JsonFileDumperTest.php
new file mode 100755
index 0000000..04e3d86
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Dumper/JsonFileDumperTest.php
@@ -0,0 +1,39 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Dumper\JsonFileDumper;
+use Symfony\Component\Translation\MessageCatalogue;
+
+class JsonFileDumperTest extends TestCase
+{
+ public function testFormatCatalogue()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add(['foo' => 'bar']);
+
+ $dumper = new JsonFileDumper();
+
+ $this->assertStringEqualsFile(__DIR__.'/../fixtures/resources.json', $dumper->formatCatalogue($catalogue, 'messages'));
+ }
+
+ public function testDumpWithCustomEncoding()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add(['foo' => '"bar"']);
+
+ $dumper = new JsonFileDumper();
+
+ $this->assertStringEqualsFile(__DIR__.'/../fixtures/resources.dump.json', $dumper->formatCatalogue($catalogue, 'messages', ['json_encoding' => JSON_HEX_QUOT]));
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Dumper/MoFileDumperTest.php b/vendor/symfony/translation/Tests/Dumper/MoFileDumperTest.php
new file mode 100755
index 0000000..fbbd75d
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Dumper/MoFileDumperTest.php
@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Dumper\MoFileDumper;
+use Symfony\Component\Translation\MessageCatalogue;
+
+class MoFileDumperTest extends TestCase
+{
+ public function testFormatCatalogue()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add(['foo' => 'bar']);
+
+ $dumper = new MoFileDumper();
+
+ $this->assertStringEqualsFile(__DIR__.'/../fixtures/resources.mo', $dumper->formatCatalogue($catalogue, 'messages'));
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Dumper/PhpFileDumperTest.php b/vendor/symfony/translation/Tests/Dumper/PhpFileDumperTest.php
new file mode 100755
index 0000000..00e535d
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Dumper/PhpFileDumperTest.php
@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Dumper\PhpFileDumper;
+use Symfony\Component\Translation\MessageCatalogue;
+
+class PhpFileDumperTest extends TestCase
+{
+ public function testFormatCatalogue()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add(['foo' => 'bar']);
+
+ $dumper = new PhpFileDumper();
+
+ $this->assertStringEqualsFile(__DIR__.'/../fixtures/resources.php', $dumper->formatCatalogue($catalogue, 'messages'));
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Dumper/PoFileDumperTest.php b/vendor/symfony/translation/Tests/Dumper/PoFileDumperTest.php
new file mode 100755
index 0000000..46df869
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Dumper/PoFileDumperTest.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Dumper\PoFileDumper;
+use Symfony\Component\Translation\MessageCatalogue;
+
+class PoFileDumperTest extends TestCase
+{
+ public function testFormatCatalogue()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add(['foo' => 'bar', 'bar' => 'foo', 'foo_bar' => 'foobar', 'bar_foo' => 'barfoo']);
+ $catalogue->setMetadata('foo_bar', [
+ 'comments' => [
+ 'Comment 1',
+ 'Comment 2',
+ ],
+ 'flags' => [
+ 'fuzzy',
+ 'another',
+ ],
+ 'sources' => [
+ 'src/file_1',
+ 'src/file_2:50',
+ ],
+ ]);
+ $catalogue->setMetadata('bar_foo', [
+ 'comments' => 'Comment',
+ 'flags' => 'fuzzy',
+ 'sources' => 'src/file_1',
+ ]);
+
+ $dumper = new PoFileDumper();
+
+ $this->assertStringEqualsFile(__DIR__.'/../fixtures/resources.po', $dumper->formatCatalogue($catalogue, 'messages'));
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Dumper/QtFileDumperTest.php b/vendor/symfony/translation/Tests/Dumper/QtFileDumperTest.php
new file mode 100755
index 0000000..6c4b559
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Dumper/QtFileDumperTest.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Dumper\QtFileDumper;
+use Symfony\Component\Translation\MessageCatalogue;
+
+class QtFileDumperTest extends TestCase
+{
+ public function testFormatCatalogue()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add(['foo' => 'bar', 'foo_bar' => 'foobar', 'bar_foo' => 'barfoo'], 'resources');
+ $catalogue->setMetadata('foo_bar', [
+ 'comments' => [
+ 'Comment 1',
+ 'Comment 2',
+ ],
+ 'flags' => [
+ 'fuzzy',
+ 'another',
+ ],
+ 'sources' => [
+ 'src/file_1',
+ 'src/file_2:50',
+ ],
+ ], 'resources');
+ $catalogue->setMetadata('bar_foo', [
+ 'comments' => 'Comment',
+ 'flags' => 'fuzzy',
+ 'sources' => 'src/file_1',
+ ], 'resources');
+
+ $dumper = new QtFileDumper();
+
+ $this->assertStringEqualsFile(__DIR__.'/../fixtures/resources.ts', $dumper->formatCatalogue($catalogue, 'resources'));
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Dumper/XliffFileDumperTest.php b/vendor/symfony/translation/Tests/Dumper/XliffFileDumperTest.php
new file mode 100755
index 0000000..6377132
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Dumper/XliffFileDumperTest.php
@@ -0,0 +1,130 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Dumper\XliffFileDumper;
+use Symfony\Component\Translation\MessageCatalogue;
+
+class XliffFileDumperTest extends TestCase
+{
+ public function testFormatCatalogue()
+ {
+ $catalogue = new MessageCatalogue('en_US');
+ $catalogue->add([
+ 'foo' => 'bar',
+ 'key' => '',
+ 'key.with.cdata' => '<source> & <target>',
+ ]);
+ $catalogue->setMetadata('foo', ['notes' => [['priority' => 1, 'from' => 'bar', 'content' => 'baz']]]);
+ $catalogue->setMetadata('key', ['notes' => [['content' => 'baz'], ['content' => 'qux']]]);
+
+ $dumper = new XliffFileDumper();
+
+ $this->assertStringEqualsFile(
+ __DIR__.'/../fixtures/resources-clean.xlf',
+ $dumper->formatCatalogue($catalogue, 'messages', ['default_locale' => 'fr_FR'])
+ );
+ }
+
+ public function testFormatCatalogueXliff2()
+ {
+ $catalogue = new MessageCatalogue('en_US');
+ $catalogue->add([
+ 'foo' => 'bar',
+ 'key' => '',
+ 'key.with.cdata' => '<source> & <target>',
+ ]);
+ $catalogue->setMetadata('key', ['target-attributes' => ['order' => 1]]);
+
+ $dumper = new XliffFileDumper();
+
+ $this->assertStringEqualsFile(
+ __DIR__.'/../fixtures/resources-2.0-clean.xlf',
+ $dumper->formatCatalogue($catalogue, 'messages', ['default_locale' => 'fr_FR', 'xliff_version' => '2.0'])
+ );
+ }
+
+ public function testFormatIcuCatalogueXliff2()
+ {
+ $catalogue = new MessageCatalogue('en_US');
+ $catalogue->add([
+ 'foo' => 'bar',
+ ], 'messages'.MessageCatalogue::INTL_DOMAIN_SUFFIX);
+
+ $dumper = new XliffFileDumper();
+
+ $this->assertStringEqualsFile(
+ __DIR__.'/../fixtures/resources-2.0+intl-icu.xlf',
+ $dumper->formatCatalogue($catalogue, 'messages'.MessageCatalogue::INTL_DOMAIN_SUFFIX, ['default_locale' => 'fr_FR', 'xliff_version' => '2.0'])
+ );
+ }
+
+ public function testFormatCatalogueWithCustomToolInfo()
+ {
+ $options = [
+ 'default_locale' => 'en_US',
+ 'tool_info' => ['tool-id' => 'foo', 'tool-name' => 'foo', 'tool-version' => '0.0', 'tool-company' => 'Foo'],
+ ];
+
+ $catalogue = new MessageCatalogue('en_US');
+ $catalogue->add(['foo' => 'bar']);
+
+ $dumper = new XliffFileDumper();
+
+ $this->assertStringEqualsFile(
+ __DIR__.'/../fixtures/resources-tool-info.xlf',
+ $dumper->formatCatalogue($catalogue, 'messages', $options)
+ );
+ }
+
+ public function testFormatCatalogueWithTargetAttributesMetadata()
+ {
+ $catalogue = new MessageCatalogue('en_US');
+ $catalogue->add([
+ 'foo' => 'bar',
+ ]);
+ $catalogue->setMetadata('foo', ['target-attributes' => ['state' => 'needs-translation']]);
+
+ $dumper = new XliffFileDumper();
+
+ $this->assertStringEqualsFile(
+ __DIR__.'/../fixtures/resources-target-attributes.xlf',
+ $dumper->formatCatalogue($catalogue, 'messages', ['default_locale' => 'fr_FR'])
+ );
+ }
+
+ public function testFormatCatalogueWithNotesMetadata()
+ {
+ $catalogue = new MessageCatalogue('en_US');
+ $catalogue->add([
+ 'foo' => 'bar',
+ 'baz' => 'biz',
+ ]);
+ $catalogue->setMetadata('foo', ['notes' => [
+ ['category' => 'state', 'content' => 'new'],
+ ['category' => 'approved', 'content' => 'true'],
+ ['category' => 'section', 'content' => 'user login', 'priority' => '1'],
+ ]]);
+ $catalogue->setMetadata('baz', ['notes' => [
+ ['id' => 'x', 'content' => 'x_content'],
+ ['appliesTo' => 'target', 'category' => 'quality', 'content' => 'Fuzzy'],
+ ]]);
+
+ $dumper = new XliffFileDumper();
+
+ $this->assertStringEqualsFile(
+ __DIR__.'/../fixtures/resources-notes-meta.xlf',
+ $dumper->formatCatalogue($catalogue, 'messages', ['default_locale' => 'fr_FR', 'xliff_version' => '2.0'])
+ );
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Dumper/YamlFileDumperTest.php b/vendor/symfony/translation/Tests/Dumper/YamlFileDumperTest.php
new file mode 100755
index 0000000..e46da5a
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Dumper/YamlFileDumperTest.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Dumper\YamlFileDumper;
+use Symfony\Component\Translation\MessageCatalogue;
+
+class YamlFileDumperTest extends TestCase
+{
+ public function testTreeFormatCatalogue()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add([
+ 'foo.bar1' => 'value1',
+ 'foo.bar2' => 'value2',
+ ]);
+
+ $dumper = new YamlFileDumper();
+
+ $this->assertStringEqualsFile(__DIR__.'/../fixtures/messages.yml', $dumper->formatCatalogue($catalogue, 'messages', ['as_tree' => true, 'inline' => 999]));
+ }
+
+ public function testLinearFormatCatalogue()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->add([
+ 'foo.bar1' => 'value1',
+ 'foo.bar2' => 'value2',
+ ]);
+
+ $dumper = new YamlFileDumper();
+
+ $this->assertStringEqualsFile(__DIR__.'/../fixtures/messages_linear.yml', $dumper->formatCatalogue($catalogue, 'messages'));
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Extractor/PhpExtractorTest.php b/vendor/symfony/translation/Tests/Extractor/PhpExtractorTest.php
new file mode 100755
index 0000000..a6d7c50
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Extractor/PhpExtractorTest.php
@@ -0,0 +1,101 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Extractor;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Extractor\PhpExtractor;
+use Symfony\Component\Translation\MessageCatalogue;
+
+class PhpExtractorTest extends TestCase
+{
+ /**
+ * @dataProvider resourcesProvider
+ *
+ * @param array|string $resource
+ */
+ public function testExtraction($resource)
+ {
+ // Arrange
+ $extractor = new PhpExtractor();
+ $extractor->setPrefix('prefix');
+ $catalogue = new MessageCatalogue('en');
+
+ // Act
+ $extractor->extract($resource, $catalogue);
+
+ $expectedHeredoc = <<<EOF
+heredoc key with whitespace and escaped \$\n sequences
+EOF;
+ $expectedNowdoc = <<<'EOF'
+nowdoc key with whitespace and nonescaped \$\n sequences
+EOF;
+ // Assert
+ $expectedCatalogue = [
+ 'messages' => [
+ 'single-quoted key' => 'prefixsingle-quoted key',
+ 'double-quoted key' => 'prefixdouble-quoted key',
+ 'heredoc key' => 'prefixheredoc key',
+ 'nowdoc key' => 'prefixnowdoc key',
+ "double-quoted key with whitespace and escaped \$\n\" sequences" => "prefixdouble-quoted key with whitespace and escaped \$\n\" sequences",
+ 'single-quoted key with whitespace and nonescaped \$\n\' sequences' => 'prefixsingle-quoted key with whitespace and nonescaped \$\n\' sequences',
+ 'single-quoted key with "quote mark at the end"' => 'prefixsingle-quoted key with "quote mark at the end"',
+ $expectedHeredoc => 'prefix'.$expectedHeredoc,
+ $expectedNowdoc => 'prefix'.$expectedNowdoc,
+ '{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples' => 'prefix{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
+ 'concatenated message with heredoc and nowdoc' => 'prefixconcatenated message with heredoc and nowdoc',
+ 'default domain' => 'prefixdefault domain',
+ ],
+ 'not_messages' => [
+ 'other-domain-test-no-params-short-array' => 'prefixother-domain-test-no-params-short-array',
+ 'other-domain-test-no-params-long-array' => 'prefixother-domain-test-no-params-long-array',
+ 'other-domain-test-params-short-array' => 'prefixother-domain-test-params-short-array',
+ 'other-domain-test-params-long-array' => 'prefixother-domain-test-params-long-array',
+ 'other-domain-test-trans-choice-short-array-%count%' => 'prefixother-domain-test-trans-choice-short-array-%count%',
+ 'other-domain-test-trans-choice-long-array-%count%' => 'prefixother-domain-test-trans-choice-long-array-%count%',
+ 'typecast' => 'prefixtypecast',
+ 'msg1' => 'prefixmsg1',
+ 'msg2' => 'prefixmsg2',
+ ],
+ ];
+ $actualCatalogue = $catalogue->all();
+
+ $this->assertEquals($expectedCatalogue, $actualCatalogue);
+
+ $filename = str_replace(\DIRECTORY_SEPARATOR, '/', __DIR__).'/../fixtures/extractor/translation.html.php';
+ $this->assertEquals(['sources' => [$filename.':2']], $catalogue->getMetadata('single-quoted key'));
+ $this->assertEquals(['sources' => [$filename.':43']], $catalogue->getMetadata('other-domain-test-no-params-short-array', 'not_messages'));
+ }
+
+ public function resourcesProvider()
+ {
+ $directory = __DIR__.'/../fixtures/extractor/';
+ $splFiles = [];
+ foreach (new \DirectoryIterator($directory) as $fileInfo) {
+ if ($fileInfo->isDot()) {
+ continue;
+ }
+ if ('translation.html.php' === $fileInfo->getBasename()) {
+ $phpFile = $fileInfo->getPathname();
+ }
+ $splFiles[] = $fileInfo->getFileInfo();
+ }
+
+ return [
+ [$directory],
+ [$phpFile],
+ [glob($directory.'*')],
+ [$splFiles],
+ [new \ArrayObject(glob($directory.'*'))],
+ [new \ArrayObject($splFiles)],
+ ];
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Formatter/IntlFormatterTest.php b/vendor/symfony/translation/Tests/Formatter/IntlFormatterTest.php
new file mode 100755
index 0000000..45ce6d4
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Formatter/IntlFormatterTest.php
@@ -0,0 +1,96 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Formatter;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Formatter\IntlFormatter;
+use Symfony\Component\Translation\Formatter\IntlFormatterInterface;
+
+/**
+ * @requires extension intl
+ */
+class IntlFormatterTest extends \PHPUnit\Framework\TestCase
+{
+ /**
+ * @dataProvider provideDataForFormat
+ */
+ public function testFormat($expected, $message, $arguments)
+ {
+ $this->assertEquals($expected, trim((new IntlFormatter())->formatIntl($message, 'en', $arguments)));
+ }
+
+ public function testInvalidFormat()
+ {
+ $this->expectException(InvalidArgumentException::class);
+ (new IntlFormatter())->formatIntl('{foo', 'en', [2]);
+ }
+
+ public function testFormatWithNamedArguments()
+ {
+ if (version_compare(INTL_ICU_VERSION, '4.8', '<')) {
+ $this->markTestSkipped('Format with named arguments can only be run with ICU 4.8 or higher and PHP >= 5.5');
+ }
+
+ $chooseMessage = <<<'_MSG_'
+{gender_of_host, select,
+ female {{num_guests, plural, offset:1
+ =0 {{host} does not give a party.}
+ =1 {{host} invites {guest} to her party.}
+ =2 {{host} invites {guest} and one other person to her party.}
+ other {{host} invites {guest} as one of the # people invited to her party.}}}
+ male {{num_guests, plural, offset:1
+ =0 {{host} does not give a party.}
+ =1 {{host} invites {guest} to his party.}
+ =2 {{host} invites {guest} and one other person to his party.}
+ other {{host} invites {guest} as one of the # people invited to his party.}}}
+ other {{num_guests, plural, offset:1
+ =0 {{host} does not give a party.}
+ =1 {{host} invites {guest} to their party.}
+ =2 {{host} invites {guest} and one other person to their party.}
+ other {{host} invites {guest} as one of the # people invited to their party.}}}}
+_MSG_;
+
+ $message = (new IntlFormatter())->formatIntl($chooseMessage, 'en', [
+ 'gender_of_host' => 'male',
+ 'num_guests' => 10,
+ 'host' => 'Fabien',
+ 'guest' => 'Guilherme',
+ ]);
+
+ $this->assertEquals('Fabien invites Guilherme as one of the 9 people invited to his party.', $message);
+ }
+
+ public function provideDataForFormat()
+ {
+ return [
+ [
+ 'There is one apple',
+ 'There is one apple',
+ [],
+ ],
+ [
+ '4,560 monkeys on 123 trees make 37.073 monkeys per tree',
+ '{0,number,integer} monkeys on {1,number,integer} trees make {2,number} monkeys per tree',
+ [4560, 123, 4560 / 123],
+ ],
+ ];
+ }
+
+ public function testPercentsAndBracketsAreTrimmed()
+ {
+ $formatter = new IntlFormatter();
+ $this->assertInstanceof(IntlFormatterInterface::class, $formatter);
+ $this->assertSame('Hello Fab', $formatter->formatIntl('Hello {name}', 'en', ['name' => 'Fab']));
+ $this->assertSame('Hello Fab', $formatter->formatIntl('Hello {name}', 'en', ['%name%' => 'Fab']));
+ $this->assertSame('Hello Fab', $formatter->formatIntl('Hello {name}', 'en', ['{{ name }}' => 'Fab']));
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Formatter/MessageFormatterTest.php b/vendor/symfony/translation/Tests/Formatter/MessageFormatterTest.php
new file mode 100755
index 0000000..232bcf9
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Formatter/MessageFormatterTest.php
@@ -0,0 +1,83 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Formatter;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Formatter\MessageFormatter;
+
+class MessageFormatterTest extends TestCase
+{
+ /**
+ * @dataProvider getTransMessages
+ */
+ public function testFormat($expected, $message, $parameters = [])
+ {
+ $this->assertEquals($expected, $this->getMessageFormatter()->format($message, 'en', $parameters));
+ }
+
+ /**
+ * @dataProvider getTransChoiceMessages
+ * @group legacy
+ */
+ public function testFormatPlural($expected, $message, $number, $parameters)
+ {
+ $this->assertEquals($expected, $this->getMessageFormatter()->choiceFormat($message, $number, 'fr', $parameters));
+ }
+
+ public function getTransMessages()
+ {
+ return [
+ [
+ 'There is one apple',
+ 'There is one apple',
+ ],
+ [
+ 'There are 5 apples',
+ 'There are %count% apples',
+ ['%count%' => 5],
+ ],
+ [
+ 'There are 5 apples',
+ 'There are {{count}} apples',
+ ['{{count}}' => 5],
+ ],
+ ];
+ }
+
+ public function getTransChoiceMessages()
+ {
+ return [
+ ['Il y a 0 pomme', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, ['%count%' => 0]],
+ ['Il y a 1 pomme', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 1, ['%count%' => 1]],
+ ['Il y a 10 pommes', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 10, ['%count%' => 10]],
+
+ ['Il y a 0 pomme', 'Il y a %count% pomme|Il y a %count% pommes', 0, ['%count%' => 0]],
+ ['Il y a 1 pomme', 'Il y a %count% pomme|Il y a %count% pommes', 1, ['%count%' => 1]],
+ ['Il y a 10 pommes', 'Il y a %count% pomme|Il y a %count% pommes', 10, ['%count%' => 10]],
+
+ ['Il y a 0 pomme', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 0, ['%count%' => 0]],
+ ['Il y a 1 pomme', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 1, ['%count%' => 1]],
+ ['Il y a 10 pommes', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 10, ['%count%' => 10]],
+
+ ['Il n\'y a aucune pomme', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 0, ['%count%' => 0]],
+ ['Il y a 1 pomme', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 1, ['%count%' => 1]],
+ ['Il y a 10 pommes', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 10, ['%count%' => 10]],
+
+ ['Il y a 0 pomme', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, ['%count%' => 0]],
+ ];
+ }
+
+ private function getMessageFormatter()
+ {
+ return new MessageFormatter();
+ }
+}
diff --git a/vendor/symfony/translation/Tests/IdentityTranslatorTest.php b/vendor/symfony/translation/Tests/IdentityTranslatorTest.php
new file mode 100755
index 0000000..cf618d9
--- /dev/null
+++ b/vendor/symfony/translation/Tests/IdentityTranslatorTest.php
@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests;
+
+use Symfony\Component\Translation\IdentityTranslator;
+use Symfony\Contracts\Translation\Test\TranslatorTest;
+
+class IdentityTranslatorTest extends TranslatorTest
+{
+ public function getTranslator()
+ {
+ return new IdentityTranslator();
+ }
+}
diff --git a/vendor/symfony/translation/Tests/IntervalTest.php b/vendor/symfony/translation/Tests/IntervalTest.php
new file mode 100755
index 0000000..bfd90a2
--- /dev/null
+++ b/vendor/symfony/translation/Tests/IntervalTest.php
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Interval;
+
+/**
+ * @group legacy
+ */
+class IntervalTest extends TestCase
+{
+ /**
+ * @dataProvider getTests
+ */
+ public function testTest($expected, $number, $interval)
+ {
+ $this->assertEquals($expected, Interval::test($number, $interval));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
+ */
+ public function testTestException()
+ {
+ Interval::test(1, 'foobar');
+ }
+
+ public function getTests()
+ {
+ return [
+ [true, 3, '{1,2, 3 ,4}'],
+ [false, 10, '{1,2, 3 ,4}'],
+ [false, 3, '[1,2]'],
+ [true, 1, '[1,2]'],
+ [true, 2, '[1,2]'],
+ [false, 1, ']1,2['],
+ [false, 2, ']1,2['],
+ [true, log(0), '[-Inf,2['],
+ [true, -log(0), '[-2,+Inf]'],
+ ];
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Loader/CsvFileLoaderTest.php b/vendor/symfony/translation/Tests/Loader/CsvFileLoaderTest.php
new file mode 100755
index 0000000..4fd5752
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Loader/CsvFileLoaderTest.php
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Loader\CsvFileLoader;
+
+class CsvFileLoaderTest extends TestCase
+{
+ public function testLoad()
+ {
+ $loader = new CsvFileLoader();
+ $resource = __DIR__.'/../fixtures/resources.csv';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(['foo' => 'bar'], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ public function testLoadDoesNothingIfEmpty()
+ {
+ $loader = new CsvFileLoader();
+ $resource = __DIR__.'/../fixtures/empty.csv';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals([], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new CsvFileLoader();
+ $resource = __DIR__.'/../fixtures/not-exists.csv';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadNonLocalResource()
+ {
+ $loader = new CsvFileLoader();
+ $resource = 'http://example.com/resources.csv';
+ $loader->load($resource, 'en', 'domain1');
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Loader/IcuDatFileLoaderTest.php b/vendor/symfony/translation/Tests/Loader/IcuDatFileLoaderTest.php
new file mode 100755
index 0000000..601680a
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Loader/IcuDatFileLoaderTest.php
@@ -0,0 +1,64 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Loader;
+
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Loader\IcuDatFileLoader;
+
+/**
+ * @requires extension intl
+ */
+class IcuDatFileLoaderTest extends LocalizedTestCase
+{
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadInvalidResource()
+ {
+ $loader = new IcuDatFileLoader();
+ $loader->load(__DIR__.'/../fixtures/resourcebundle/corrupted/resources', 'es', 'domain2');
+ }
+
+ public function testDatEnglishLoad()
+ {
+ // bundled resource is build using pkgdata command which at least in ICU 4.2 comes in extremely! buggy form
+ // you must specify an temporary build directory which is not the same as current directory and
+ // MUST reside on the same partition. pkgdata -p resources -T /srv -d.packagelist.txt
+ $loader = new IcuDatFileLoader();
+ $resource = __DIR__.'/../fixtures/resourcebundle/dat/resources';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(['symfony' => 'Symfony 2 is great'], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource.'.dat')], $catalogue->getResources());
+ }
+
+ public function testDatFrenchLoad()
+ {
+ $loader = new IcuDatFileLoader();
+ $resource = __DIR__.'/../fixtures/resourcebundle/dat/resources';
+ $catalogue = $loader->load($resource, 'fr', 'domain1');
+
+ $this->assertEquals(['symfony' => 'Symfony 2 est génial'], $catalogue->all('domain1'));
+ $this->assertEquals('fr', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource.'.dat')], $catalogue->getResources());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new IcuDatFileLoader();
+ $loader->load(__DIR__.'/../fixtures/non-existing.txt', 'en', 'domain1');
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Loader/IcuResFileLoaderTest.php b/vendor/symfony/translation/Tests/Loader/IcuResFileLoaderTest.php
new file mode 100755
index 0000000..962c3af
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Loader/IcuResFileLoaderTest.php
@@ -0,0 +1,51 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Loader;
+
+use Symfony\Component\Config\Resource\DirectoryResource;
+use Symfony\Component\Translation\Loader\IcuResFileLoader;
+
+/**
+ * @requires extension intl
+ */
+class IcuResFileLoaderTest extends LocalizedTestCase
+{
+ public function testLoad()
+ {
+ // resource is build using genrb command
+ $loader = new IcuResFileLoader();
+ $resource = __DIR__.'/../fixtures/resourcebundle/res';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(['foo' => 'bar'], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new DirectoryResource($resource)], $catalogue->getResources());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new IcuResFileLoader();
+ $loader->load(__DIR__.'/../fixtures/non-existing.txt', 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadInvalidResource()
+ {
+ $loader = new IcuResFileLoader();
+ $loader->load(__DIR__.'/../fixtures/resourcebundle/corrupted', 'en', 'domain1');
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Loader/IniFileLoaderTest.php b/vendor/symfony/translation/Tests/Loader/IniFileLoaderTest.php
new file mode 100755
index 0000000..e0d8b2f
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Loader/IniFileLoaderTest.php
@@ -0,0 +1,51 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Loader\IniFileLoader;
+
+class IniFileLoaderTest extends TestCase
+{
+ public function testLoad()
+ {
+ $loader = new IniFileLoader();
+ $resource = __DIR__.'/../fixtures/resources.ini';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(['foo' => 'bar'], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ public function testLoadDoesNothingIfEmpty()
+ {
+ $loader = new IniFileLoader();
+ $resource = __DIR__.'/../fixtures/empty.ini';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals([], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new IniFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.ini';
+ $loader->load($resource, 'en', 'domain1');
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Loader/JsonFileLoaderTest.php b/vendor/symfony/translation/Tests/Loader/JsonFileLoaderTest.php
new file mode 100755
index 0000000..4c507da
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Loader/JsonFileLoaderTest.php
@@ -0,0 +1,62 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Loader\JsonFileLoader;
+
+class JsonFileLoaderTest extends TestCase
+{
+ public function testLoad()
+ {
+ $loader = new JsonFileLoader();
+ $resource = __DIR__.'/../fixtures/resources.json';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(['foo' => 'bar'], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ public function testLoadDoesNothingIfEmpty()
+ {
+ $loader = new JsonFileLoader();
+ $resource = __DIR__.'/../fixtures/empty.json';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals([], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new JsonFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.json';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ * @expectedExceptionMessage Error parsing JSON - Syntax error, malformed JSON
+ */
+ public function testParseException()
+ {
+ $loader = new JsonFileLoader();
+ $resource = __DIR__.'/../fixtures/malformed.json';
+ $loader->load($resource, 'en', 'domain1');
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Loader/LocalizedTestCase.php b/vendor/symfony/translation/Tests/Loader/LocalizedTestCase.php
new file mode 100755
index 0000000..279e9fd
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Loader/LocalizedTestCase.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+
+abstract class LocalizedTestCase extends TestCase
+{
+ protected function setUp()
+ {
+ if (!\extension_loaded('intl')) {
+ $this->markTestSkipped('Extension intl is required.');
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Loader/MoFileLoaderTest.php b/vendor/symfony/translation/Tests/Loader/MoFileLoaderTest.php
new file mode 100755
index 0000000..63de5ce
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Loader/MoFileLoaderTest.php
@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Loader\MoFileLoader;
+
+class MoFileLoaderTest extends TestCase
+{
+ public function testLoad()
+ {
+ $loader = new MoFileLoader();
+ $resource = __DIR__.'/../fixtures/resources.mo';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(['foo' => 'bar'], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ public function testLoadPlurals()
+ {
+ $loader = new MoFileLoader();
+ $resource = __DIR__.'/../fixtures/plurals.mo';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(['foo' => 'bar', 'foos' => '{0} bar|{1} bars'], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new MoFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.mo';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadInvalidResource()
+ {
+ $loader = new MoFileLoader();
+ $resource = __DIR__.'/../fixtures/empty.mo';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ public function testLoadEmptyTranslation()
+ {
+ $loader = new MoFileLoader();
+ $resource = __DIR__.'/../fixtures/empty-translation.mo';
+ $catalogue = $loader->load($resource, 'en', 'message');
+
+ $this->assertEquals([], $catalogue->all('message'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Loader/PhpFileLoaderTest.php b/vendor/symfony/translation/Tests/Loader/PhpFileLoaderTest.php
new file mode 100755
index 0000000..68cb2d0
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Loader/PhpFileLoaderTest.php
@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Loader\PhpFileLoader;
+
+class PhpFileLoaderTest extends TestCase
+{
+ public function testLoad()
+ {
+ $loader = new PhpFileLoader();
+ $resource = __DIR__.'/../fixtures/resources.php';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(['foo' => 'bar'], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new PhpFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.php';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadThrowsAnExceptionIfFileNotLocal()
+ {
+ $loader = new PhpFileLoader();
+ $resource = 'http://example.com/resources.php';
+ $loader->load($resource, 'en', 'domain1');
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Loader/PoFileLoaderTest.php b/vendor/symfony/translation/Tests/Loader/PoFileLoaderTest.php
new file mode 100755
index 0000000..d8e2c19
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Loader/PoFileLoaderTest.php
@@ -0,0 +1,109 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Loader\PoFileLoader;
+
+class PoFileLoaderTest extends TestCase
+{
+ public function testLoad()
+ {
+ $loader = new PoFileLoader();
+ $resource = __DIR__.'/../fixtures/resources.po';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(['foo' => 'bar', 'bar' => 'foo'], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ public function testLoadPlurals()
+ {
+ $loader = new PoFileLoader();
+ $resource = __DIR__.'/../fixtures/plurals.po';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(['foo' => 'bar', 'foos' => 'bar|bars'], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ public function testLoadDoesNothingIfEmpty()
+ {
+ $loader = new PoFileLoader();
+ $resource = __DIR__.'/../fixtures/empty.po';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals([], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new PoFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.po';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ public function testLoadEmptyTranslation()
+ {
+ $loader = new PoFileLoader();
+ $resource = __DIR__.'/../fixtures/empty-translation.po';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(['foo' => ''], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ public function testEscapedId()
+ {
+ $loader = new PoFileLoader();
+ $resource = __DIR__.'/../fixtures/escaped-id.po';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $messages = $catalogue->all('domain1');
+ $this->assertArrayHasKey('escaped "foo"', $messages);
+ $this->assertEquals('escaped "bar"', $messages['escaped "foo"']);
+ }
+
+ public function testEscapedIdPlurals()
+ {
+ $loader = new PoFileLoader();
+ $resource = __DIR__.'/../fixtures/escaped-id-plurals.po';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $messages = $catalogue->all('domain1');
+ $this->assertArrayHasKey('escaped "foo"', $messages);
+ $this->assertArrayHasKey('escaped "foos"', $messages);
+ $this->assertEquals('escaped "bar"', $messages['escaped "foo"']);
+ $this->assertEquals('escaped "bar"|escaped "bars"', $messages['escaped "foos"']);
+ }
+
+ public function testSkipFuzzyTranslations()
+ {
+ $loader = new PoFileLoader();
+ $resource = __DIR__.'/../fixtures/fuzzy-translations.po';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $messages = $catalogue->all('domain1');
+ $this->assertArrayHasKey('foo1', $messages);
+ $this->assertArrayNotHasKey('foo2', $messages);
+ $this->assertArrayHasKey('foo3', $messages);
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Loader/QtFileLoaderTest.php b/vendor/symfony/translation/Tests/Loader/QtFileLoaderTest.php
new file mode 100755
index 0000000..f149b8c
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Loader/QtFileLoaderTest.php
@@ -0,0 +1,79 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Loader\QtFileLoader;
+
+class QtFileLoaderTest extends TestCase
+{
+ public function testLoad()
+ {
+ $loader = new QtFileLoader();
+ $resource = __DIR__.'/../fixtures/resources.ts';
+ $catalogue = $loader->load($resource, 'en', 'resources');
+
+ $this->assertEquals([
+ 'foo' => 'bar',
+ 'foo_bar' => 'foobar',
+ 'bar_foo' => 'barfoo',
+ ], $catalogue->all('resources'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new QtFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.ts';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadNonLocalResource()
+ {
+ $loader = new QtFileLoader();
+ $resource = 'http://domain1.com/resources.ts';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadInvalidResource()
+ {
+ $loader = new QtFileLoader();
+ $resource = __DIR__.'/../fixtures/invalid-xml-resources.xlf';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ public function testLoadEmptyResource()
+ {
+ $loader = new QtFileLoader();
+ $resource = __DIR__.'/../fixtures/empty.xlf';
+
+ if (method_exists($this, 'expectException')) {
+ $this->expectException('Symfony\Component\Translation\Exception\InvalidResourceException');
+ $this->expectExceptionMessage(sprintf('Unable to load "%s".', $resource));
+ } else {
+ $this->setExpectedException('Symfony\Component\Translation\Exception\InvalidResourceException', sprintf('Unable to load "%s".', $resource));
+ }
+
+ $loader->load($resource, 'en', 'domain1');
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Loader/XliffFileLoaderTest.php b/vendor/symfony/translation/Tests/Loader/XliffFileLoaderTest.php
new file mode 100755
index 0000000..1ca8336
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Loader/XliffFileLoaderTest.php
@@ -0,0 +1,331 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Loader\XliffFileLoader;
+
+class XliffFileLoaderTest extends TestCase
+{
+ public function testLoad()
+ {
+ $loader = new XliffFileLoader();
+ $resource = __DIR__.'/../fixtures/resources.xlf';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ $this->assertSame([], libxml_get_errors());
+ $this->assertContainsOnly('string', $catalogue->all('domain1'));
+ }
+
+ public function testLoadWithInternalErrorsEnabled()
+ {
+ $internalErrors = libxml_use_internal_errors(true);
+
+ $this->assertSame([], libxml_get_errors());
+
+ $loader = new XliffFileLoader();
+ $resource = __DIR__.'/../fixtures/resources.xlf';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ $this->assertSame([], libxml_get_errors());
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internalErrors);
+ }
+
+ public function testLoadWithExternalEntitiesDisabled()
+ {
+ $disableEntities = libxml_disable_entity_loader(true);
+
+ $loader = new XliffFileLoader();
+ $resource = __DIR__.'/../fixtures/resources.xlf';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ libxml_disable_entity_loader($disableEntities);
+
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ public function testLoadWithResname()
+ {
+ $loader = new XliffFileLoader();
+ $catalogue = $loader->load(__DIR__.'/../fixtures/resname.xlf', 'en', 'domain1');
+
+ $this->assertEquals(['foo' => 'bar', 'bar' => 'baz', 'baz' => 'foo', 'qux' => 'qux source'], $catalogue->all('domain1'));
+ }
+
+ public function testIncompleteResource()
+ {
+ $loader = new XliffFileLoader();
+ $catalogue = $loader->load(__DIR__.'/../fixtures/resources.xlf', 'en', 'domain1');
+
+ $this->assertEquals(['foo' => 'bar', 'extra' => 'extra', 'key' => '', 'test' => 'with'], $catalogue->all('domain1'));
+ }
+
+ public function testEncoding()
+ {
+ $loader = new XliffFileLoader();
+ $catalogue = $loader->load(__DIR__.'/../fixtures/encoding.xlf', 'en', 'domain1');
+
+ $this->assertEquals(utf8_decode('föö'), $catalogue->get('bar', 'domain1'));
+ $this->assertEquals(utf8_decode('bär'), $catalogue->get('foo', 'domain1'));
+ $this->assertEquals(
+ [
+ 'source' => 'foo',
+ 'notes' => [['content' => utf8_decode('bäz')]],
+ 'id' => '1',
+ 'file' => [
+ 'original' => 'file.ext',
+ ],
+ ],
+ $catalogue->getMetadata('foo', 'domain1')
+ );
+ }
+
+ public function testTargetAttributesAreStoredCorrectly()
+ {
+ $loader = new XliffFileLoader();
+ $catalogue = $loader->load(__DIR__.'/../fixtures/with-attributes.xlf', 'en', 'domain1');
+
+ $metadata = $catalogue->getMetadata('foo', 'domain1');
+ $this->assertEquals('translated', $metadata['target-attributes']['state']);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadInvalidResource()
+ {
+ $loader = new XliffFileLoader();
+ $loader->load(__DIR__.'/../fixtures/resources.php', 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadResourceDoesNotValidate()
+ {
+ $loader = new XliffFileLoader();
+ $loader->load(__DIR__.'/../fixtures/non-valid.xlf', 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new XliffFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.xlf';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadThrowsAnExceptionIfFileNotLocal()
+ {
+ $loader = new XliffFileLoader();
+ $resource = 'http://example.com/resources.xlf';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ * @expectedExceptionMessage Document types are not allowed.
+ */
+ public function testDocTypeIsNotAllowed()
+ {
+ $loader = new XliffFileLoader();
+ $loader->load(__DIR__.'/../fixtures/withdoctype.xlf', 'en', 'domain1');
+ }
+
+ public function testParseEmptyFile()
+ {
+ $loader = new XliffFileLoader();
+ $resource = __DIR__.'/../fixtures/empty.xlf';
+
+ if (method_exists($this, 'expectException')) {
+ $this->expectException('Symfony\Component\Translation\Exception\InvalidResourceException');
+ $this->expectExceptionMessage(sprintf('Unable to load "%s":', $resource));
+ } else {
+ $this->setExpectedException('Symfony\Component\Translation\Exception\InvalidResourceException', sprintf('Unable to load "%s":', $resource));
+ }
+
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ public function testLoadNotes()
+ {
+ $loader = new XliffFileLoader();
+ $catalogue = $loader->load(__DIR__.'/../fixtures/withnote.xlf', 'en', 'domain1');
+
+ $this->assertEquals(
+ [
+ 'source' => 'foo',
+ 'notes' => [['priority' => 1, 'content' => 'foo']],
+ 'id' => '1',
+ 'file' => [
+ 'original' => 'file.ext',
+ ],
+ ],
+ $catalogue->getMetadata('foo', 'domain1')
+ );
+ // message without target
+ $this->assertEquals(
+ [
+ 'source' => 'extrasource',
+ 'notes' => [['content' => 'bar', 'from' => 'foo']],
+ 'id' => '2',
+ 'file' => [
+ 'original' => 'file.ext',
+ ],
+ ],
+ $catalogue->getMetadata('extra', 'domain1')
+ );
+ // message with empty target
+ $this->assertEquals(
+ [
+ 'source' => 'key',
+ 'notes' => [
+ ['content' => 'baz'],
+ ['priority' => 2, 'from' => 'bar', 'content' => 'qux'],
+ ],
+ 'id' => '123',
+ 'file' => [
+ 'original' => 'file.ext',
+ ],
+ ],
+ $catalogue->getMetadata('key', 'domain1')
+ );
+ }
+
+ public function testLoadVersion2()
+ {
+ $loader = new XliffFileLoader();
+ $resource = __DIR__.'/../fixtures/resources-2.0.xlf';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ $this->assertSame([], libxml_get_errors());
+
+ $domains = $catalogue->all();
+ $this->assertCount(3, $domains['domain1']);
+ $this->assertContainsOnly('string', $catalogue->all('domain1'));
+
+ // target attributes
+ $this->assertEquals(['target-attributes' => ['order' => 1]], $catalogue->getMetadata('bar', 'domain1'));
+ }
+
+ public function testLoadVersion2WithNoteMeta()
+ {
+ $loader = new XliffFileLoader();
+ $resource = __DIR__.'/../fixtures/resources-notes-meta.xlf';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ $this->assertSame([], libxml_get_errors());
+
+ // test for "foo" metadata
+ $this->assertTrue($catalogue->defines('foo', 'domain1'));
+ $metadata = $catalogue->getMetadata('foo', 'domain1');
+ $this->assertNotEmpty($metadata);
+ $this->assertCount(3, $metadata['notes']);
+
+ $this->assertEquals('state', $metadata['notes'][0]['category']);
+ $this->assertEquals('new', $metadata['notes'][0]['content']);
+
+ $this->assertEquals('approved', $metadata['notes'][1]['category']);
+ $this->assertEquals('true', $metadata['notes'][1]['content']);
+
+ $this->assertEquals('section', $metadata['notes'][2]['category']);
+ $this->assertEquals('1', $metadata['notes'][2]['priority']);
+ $this->assertEquals('user login', $metadata['notes'][2]['content']);
+
+ // test for "baz" metadata
+ $this->assertTrue($catalogue->defines('baz', 'domain1'));
+ $metadata = $catalogue->getMetadata('baz', 'domain1');
+ $this->assertNotEmpty($metadata);
+ $this->assertCount(2, $metadata['notes']);
+
+ $this->assertEquals('x', $metadata['notes'][0]['id']);
+ $this->assertEquals('x_content', $metadata['notes'][0]['content']);
+
+ $this->assertEquals('target', $metadata['notes'][1]['appliesTo']);
+ $this->assertEquals('quality', $metadata['notes'][1]['category']);
+ $this->assertEquals('Fuzzy', $metadata['notes'][1]['content']);
+ }
+
+ public function testLoadVersion2WithMultiSegmentUnit()
+ {
+ $loader = new XliffFileLoader();
+ $resource = __DIR__.'/../fixtures/resources-2.0-multi-segment-unit.xlf';
+ $catalog = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertSame('en', $catalog->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalog->getResources());
+ $this->assertFalse(libxml_get_last_error());
+
+ // test for "foo" metadata
+ $this->assertTrue($catalog->defines('foo', 'domain1'));
+ $metadata = $catalog->getMetadata('foo', 'domain1');
+ $this->assertNotEmpty($metadata);
+ $this->assertCount(1, $metadata['notes']);
+
+ $this->assertSame('processed', $metadata['notes'][0]['category']);
+ $this->assertSame('true', $metadata['notes'][0]['content']);
+
+ // test for "bar" metadata
+ $this->assertTrue($catalog->defines('bar', 'domain1'));
+ $metadata = $catalog->getMetadata('bar', 'domain1');
+ $this->assertNotEmpty($metadata);
+ $this->assertCount(1, $metadata['notes']);
+
+ $this->assertSame('processed', $metadata['notes'][0]['category']);
+ $this->assertSame('true', $metadata['notes'][0]['content']);
+ }
+
+ public function testLoadWithMultipleFileNodes()
+ {
+ $loader = new XliffFileLoader();
+ $catalogue = $loader->load(__DIR__.'/../fixtures/resources-multi-files.xlf', 'en', 'domain1');
+
+ $this->assertEquals(
+ [
+ 'source' => 'foo',
+ 'id' => '1',
+ 'file' => [
+ 'original' => 'file.ext',
+ ],
+ ],
+ $catalogue->getMetadata('foo', 'domain1')
+ );
+ $this->assertEquals(
+ [
+ 'source' => 'test',
+ 'notes' => [['content' => 'note']],
+ 'id' => '4',
+ 'file' => [
+ 'original' => 'otherfile.ext',
+ ],
+ ],
+ $catalogue->getMetadata('test', 'domain1')
+ );
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Loader/YamlFileLoaderTest.php b/vendor/symfony/translation/Tests/Loader/YamlFileLoaderTest.php
new file mode 100755
index 0000000..a535db5
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Loader/YamlFileLoaderTest.php
@@ -0,0 +1,71 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Loader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Loader\YamlFileLoader;
+
+class YamlFileLoaderTest extends TestCase
+{
+ public function testLoad()
+ {
+ $loader = new YamlFileLoader();
+ $resource = __DIR__.'/../fixtures/resources.yml';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(['foo' => 'bar'], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ public function testLoadDoesNothingIfEmpty()
+ {
+ $loader = new YamlFileLoader();
+ $resource = __DIR__.'/../fixtures/empty.yml';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals([], $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals([new FileResource($resource)], $catalogue->getResources());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new YamlFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.yml';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadThrowsAnExceptionIfFileNotLocal()
+ {
+ $loader = new YamlFileLoader();
+ $resource = 'http://example.com/resources.yml';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadThrowsAnExceptionIfNotAnArray()
+ {
+ $loader = new YamlFileLoader();
+ $resource = __DIR__.'/../fixtures/non-valid.yml';
+ $loader->load($resource, 'en', 'domain1');
+ }
+}
diff --git a/vendor/symfony/translation/Tests/LoggingTranslatorTest.php b/vendor/symfony/translation/Tests/LoggingTranslatorTest.php
new file mode 100755
index 0000000..450e060
--- /dev/null
+++ b/vendor/symfony/translation/Tests/LoggingTranslatorTest.php
@@ -0,0 +1,68 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Loader\ArrayLoader;
+use Symfony\Component\Translation\LoggingTranslator;
+use Symfony\Component\Translation\Translator;
+
+class LoggingTranslatorTest extends TestCase
+{
+ public function testTransWithNoTranslationIsLogged()
+ {
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+ $logger->expects($this->exactly(1))
+ ->method('warning')
+ ->with('Translation not found.')
+ ;
+
+ $translator = new Translator('ar');
+ $loggableTranslator = new LoggingTranslator($translator, $logger);
+ $loggableTranslator->trans('bar');
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testTransChoiceFallbackIsLogged()
+ {
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+ $logger->expects($this->once())
+ ->method('debug')
+ ->with('Translation use fallback catalogue.')
+ ;
+
+ $translator = new Translator('ar');
+ $translator->setFallbackLocales(['en']);
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['some_message2' => 'one thing|%count% things'], 'en');
+ $loggableTranslator = new LoggingTranslator($translator, $logger);
+ $loggableTranslator->transChoice('some_message2', 10, ['%count%' => 10]);
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testTransChoiceWithNoTranslationIsLogged()
+ {
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+ $logger->expects($this->exactly(1))
+ ->method('warning')
+ ->with('Translation not found.')
+ ;
+
+ $translator = new Translator('ar');
+ $loggableTranslator = new LoggingTranslator($translator, $logger);
+ $loggableTranslator->transChoice('some_message2', 10, ['%count%' => 10]);
+ }
+}
diff --git a/vendor/symfony/translation/Tests/MessageCatalogueTest.php b/vendor/symfony/translation/Tests/MessageCatalogueTest.php
new file mode 100755
index 0000000..cf0dd1a
--- /dev/null
+++ b/vendor/symfony/translation/Tests/MessageCatalogueTest.php
@@ -0,0 +1,243 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\MessageCatalogue;
+
+class MessageCatalogueTest extends TestCase
+{
+ public function testGetLocale()
+ {
+ $catalogue = new MessageCatalogue('en');
+
+ $this->assertEquals('en', $catalogue->getLocale());
+ }
+
+ public function testGetDomains()
+ {
+ $catalogue = new MessageCatalogue('en', ['domain1' => [], 'domain2' => [], 'domain2+intl-icu' => [], 'domain3+intl-icu' => []]);
+
+ $this->assertEquals(['domain1', 'domain2', 'domain3'], $catalogue->getDomains());
+ }
+
+ public function testAll()
+ {
+ $catalogue = new MessageCatalogue('en', $messages = ['domain1' => ['foo' => 'foo'], 'domain2' => ['bar' => 'bar']]);
+
+ $this->assertEquals(['foo' => 'foo'], $catalogue->all('domain1'));
+ $this->assertEquals([], $catalogue->all('domain88'));
+ $this->assertEquals($messages, $catalogue->all());
+
+ $messages = ['domain1+intl-icu' => ['foo' => 'bar']] + $messages + [
+ 'domain2+intl-icu' => ['bar' => 'foo'],
+ 'domain3+intl-icu' => ['biz' => 'biz'],
+ ];
+ $catalogue = new MessageCatalogue('en', $messages);
+
+ $this->assertEquals(['foo' => 'bar'], $catalogue->all('domain1'));
+ $this->assertEquals(['bar' => 'foo'], $catalogue->all('domain2'));
+ $this->assertEquals(['biz' => 'biz'], $catalogue->all('domain3'));
+
+ $messages = [
+ 'domain1' => ['foo' => 'bar'],
+ 'domain2' => ['bar' => 'foo'],
+ 'domain3' => ['biz' => 'biz'],
+ ];
+ $this->assertEquals($messages, $catalogue->all());
+ }
+
+ public function testHas()
+ {
+ $catalogue = new MessageCatalogue('en', ['domain1' => ['foo' => 'foo'], 'domain2+intl-icu' => ['bar' => 'bar']]);
+
+ $this->assertTrue($catalogue->has('foo', 'domain1'));
+ $this->assertTrue($catalogue->has('bar', 'domain2'));
+ $this->assertFalse($catalogue->has('bar', 'domain1'));
+ $this->assertFalse($catalogue->has('foo', 'domain88'));
+ }
+
+ public function testGetSet()
+ {
+ $catalogue = new MessageCatalogue('en', ['domain1' => ['foo' => 'foo'], 'domain2' => ['bar' => 'bar'], 'domain2+intl-icu' => ['bar' => 'foo']]);
+ $catalogue->set('foo1', 'foo1', 'domain1');
+
+ $this->assertEquals('foo', $catalogue->get('foo', 'domain1'));
+ $this->assertEquals('foo1', $catalogue->get('foo1', 'domain1'));
+ $this->assertEquals('foo', $catalogue->get('bar', 'domain2'));
+ }
+
+ public function testAdd()
+ {
+ $catalogue = new MessageCatalogue('en', ['domain1' => ['foo' => 'foo'], 'domain2' => ['bar' => 'bar']]);
+ $catalogue->add(['foo1' => 'foo1'], 'domain1');
+
+ $this->assertEquals('foo', $catalogue->get('foo', 'domain1'));
+ $this->assertEquals('foo1', $catalogue->get('foo1', 'domain1'));
+
+ $catalogue->add(['foo' => 'bar'], 'domain1');
+ $this->assertEquals('bar', $catalogue->get('foo', 'domain1'));
+ $this->assertEquals('foo1', $catalogue->get('foo1', 'domain1'));
+
+ $catalogue->add(['foo' => 'bar'], 'domain88');
+ $this->assertEquals('bar', $catalogue->get('foo', 'domain88'));
+ }
+
+ public function testReplace()
+ {
+ $catalogue = new MessageCatalogue('en', ['domain1' => ['foo' => 'foo'], 'domain1+intl-icu' => ['bar' => 'bar']]);
+ $catalogue->replace($messages = ['foo1' => 'foo1'], 'domain1');
+
+ $this->assertEquals($messages, $catalogue->all('domain1'));
+ }
+
+ public function testAddCatalogue()
+ {
+ $r = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock();
+ $r->expects($this->any())->method('__toString')->willReturn('r');
+
+ $r1 = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock();
+ $r1->expects($this->any())->method('__toString')->willReturn('r1');
+
+ $catalogue = new MessageCatalogue('en', ['domain1' => ['foo' => 'foo']]);
+ $catalogue->addResource($r);
+
+ $catalogue1 = new MessageCatalogue('en', ['domain1' => ['foo1' => 'foo1'], 'domain2+intl-icu' => ['bar' => 'bar']]);
+ $catalogue1->addResource($r1);
+
+ $catalogue->addCatalogue($catalogue1);
+
+ $this->assertEquals('foo', $catalogue->get('foo', 'domain1'));
+ $this->assertEquals('foo1', $catalogue->get('foo1', 'domain1'));
+ $this->assertEquals('bar', $catalogue->get('bar', 'domain2'));
+ $this->assertEquals('bar', $catalogue->get('bar', 'domain2+intl-icu'));
+
+ $this->assertEquals([$r, $r1], $catalogue->getResources());
+ }
+
+ public function testAddFallbackCatalogue()
+ {
+ $r = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock();
+ $r->expects($this->any())->method('__toString')->willReturn('r');
+
+ $r1 = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock();
+ $r1->expects($this->any())->method('__toString')->willReturn('r1');
+
+ $r2 = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock();
+ $r2->expects($this->any())->method('__toString')->willReturn('r2');
+
+ $catalogue = new MessageCatalogue('fr_FR', ['domain1' => ['foo' => 'foo'], 'domain2' => ['bar' => 'bar']]);
+ $catalogue->addResource($r);
+
+ $catalogue1 = new MessageCatalogue('fr', ['domain1' => ['foo' => 'bar', 'foo1' => 'foo1']]);
+ $catalogue1->addResource($r1);
+
+ $catalogue2 = new MessageCatalogue('en');
+ $catalogue2->addResource($r2);
+
+ $catalogue->addFallbackCatalogue($catalogue1);
+ $catalogue1->addFallbackCatalogue($catalogue2);
+
+ $this->assertEquals('foo', $catalogue->get('foo', 'domain1'));
+ $this->assertEquals('foo1', $catalogue->get('foo1', 'domain1'));
+
+ $this->assertEquals([$r, $r1, $r2], $catalogue->getResources());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\LogicException
+ */
+ public function testAddFallbackCatalogueWithParentCircularReference()
+ {
+ $main = new MessageCatalogue('en_US');
+ $fallback = new MessageCatalogue('fr_FR');
+
+ $fallback->addFallbackCatalogue($main);
+ $main->addFallbackCatalogue($fallback);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\LogicException
+ */
+ public function testAddFallbackCatalogueWithFallbackCircularReference()
+ {
+ $fr = new MessageCatalogue('fr');
+ $en = new MessageCatalogue('en');
+ $es = new MessageCatalogue('es');
+
+ $fr->addFallbackCatalogue($en);
+ $es->addFallbackCatalogue($en);
+ $en->addFallbackCatalogue($fr);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\LogicException
+ */
+ public function testAddCatalogueWhenLocaleIsNotTheSameAsTheCurrentOne()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->addCatalogue(new MessageCatalogue('fr', []));
+ }
+
+ public function testGetAddResource()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $r = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock();
+ $r->expects($this->any())->method('__toString')->willReturn('r');
+ $catalogue->addResource($r);
+ $catalogue->addResource($r);
+ $r1 = $this->getMockBuilder('Symfony\Component\Config\Resource\ResourceInterface')->getMock();
+ $r1->expects($this->any())->method('__toString')->willReturn('r1');
+ $catalogue->addResource($r1);
+
+ $this->assertEquals([$r, $r1], $catalogue->getResources());
+ }
+
+ public function testMetadataDelete()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $this->assertEquals([], $catalogue->getMetadata('', ''), 'Metadata is empty');
+ $catalogue->deleteMetadata('key', 'messages');
+ $catalogue->deleteMetadata('', 'messages');
+ $catalogue->deleteMetadata();
+ }
+
+ public function testMetadataSetGetDelete()
+ {
+ $catalogue = new MessageCatalogue('en');
+ $catalogue->setMetadata('key', 'value');
+ $this->assertEquals('value', $catalogue->getMetadata('key', 'messages'), "Metadata 'key' = 'value'");
+
+ $catalogue->setMetadata('key2', []);
+ $this->assertEquals([], $catalogue->getMetadata('key2', 'messages'), 'Metadata key2 is array');
+
+ $catalogue->deleteMetadata('key2', 'messages');
+ $this->assertNull($catalogue->getMetadata('key2', 'messages'), 'Metadata key2 should is deleted.');
+
+ $catalogue->deleteMetadata('key2', 'domain');
+ $this->assertNull($catalogue->getMetadata('key2', 'domain'), 'Metadata key2 should is deleted.');
+ }
+
+ public function testMetadataMerge()
+ {
+ $cat1 = new MessageCatalogue('en');
+ $cat1->setMetadata('a', 'b');
+ $this->assertEquals(['messages' => ['a' => 'b']], $cat1->getMetadata('', ''), 'Cat1 contains messages metadata.');
+
+ $cat2 = new MessageCatalogue('en');
+ $cat2->setMetadata('b', 'c', 'domain');
+ $this->assertEquals(['domain' => ['b' => 'c']], $cat2->getMetadata('', ''), 'Cat2 contains domain metadata.');
+
+ $cat1->addCatalogue($cat2);
+ $this->assertEquals(['messages' => ['a' => 'b'], 'domain' => ['b' => 'c']], $cat1->getMetadata('', ''), 'Cat1 contains merged metadata.');
+ }
+}
diff --git a/vendor/symfony/translation/Tests/MessageSelectorTest.php b/vendor/symfony/translation/Tests/MessageSelectorTest.php
new file mode 100755
index 0000000..f099716
--- /dev/null
+++ b/vendor/symfony/translation/Tests/MessageSelectorTest.php
@@ -0,0 +1,140 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\MessageSelector;
+
+/**
+ * @group legacy
+ */
+class MessageSelectorTest extends TestCase
+{
+ /**
+ * @dataProvider getChooseTests
+ */
+ public function testChoose($expected, $id, $number)
+ {
+ $selector = new MessageSelector();
+
+ $this->assertEquals($expected, $selector->choose($id, $number, 'en'));
+ }
+
+ public function testReturnMessageIfExactlyOneStandardRuleIsGiven()
+ {
+ $selector = new MessageSelector();
+
+ $this->assertEquals('There are two apples', $selector->choose('There are two apples', 2, 'en'));
+ }
+
+ /**
+ * @dataProvider getNonMatchingMessages
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
+ */
+ public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number)
+ {
+ $selector = new MessageSelector();
+
+ $selector->choose($id, $number, 'en');
+ }
+
+ public function getNonMatchingMessages()
+ {
+ return [
+ ['{0} There are no apples|{1} There is one apple', 2],
+ ['{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['{1} There is one apple|]2,Inf] There are %count% apples', 2],
+ ['{0} There are no apples|There is one apple', 2],
+ ];
+ }
+
+ public function getChooseTests()
+ {
+ return [
+ ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+
+ ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1],
+
+ ['There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10],
+ ['There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10],
+ ['There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10],
+
+ ['There are %count% apples', 'There is one apple|There are %count% apples', 0],
+ ['There is one apple', 'There is one apple|There are %count% apples', 1],
+ ['There are %count% apples', 'There is one apple|There are %count% apples', 10],
+
+ ['There are %count% apples', 'one: There is one apple|more: There are %count% apples', 0],
+ ['There is one apple', 'one: There is one apple|more: There are %count% apples', 1],
+ ['There are %count% apples', 'one: There is one apple|more: There are %count% apples', 10],
+
+ ['There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0],
+ ['There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1],
+ ['There are %count% apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10],
+
+ ['', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1],
+
+ // Indexed only tests which are Gettext PoFile* compatible strings.
+ ['There are %count% apples', 'There is one apple|There are %count% apples', 0],
+ ['There is one apple', 'There is one apple|There are %count% apples', 1],
+ ['There are %count% apples', 'There is one apple|There are %count% apples', 2],
+
+ // Tests for float numbers
+ ['There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7],
+ ['There is one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1],
+ ['There is more than one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7],
+ ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0],
+ ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0],
+ ['There are no apples', '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0],
+
+ // Test texts with new-lines
+ // with double-quotes and \n in id & double-quotes and actual newlines in text
+ ["This is a text with a\n new-line in it. Selector = 0.", '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 0],
+ // with double-quotes and \n in id and single-quotes and actual newlines in text
+ ["This is a text with a\n new-line in it. Selector = 1.", '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 1],
+ ["This is a text with a\n new-line in it. Selector > 1.", '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 5],
+ // with double-quotes and id split accros lines
+ ['This is a text with a
+ new-line in it. Selector = 1.', '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 1],
+ // with single-quotes and id split accros lines
+ ['This is a text with a
+ new-line in it. Selector > 1.', '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 5],
+ // with single-quotes and \n in text
+ ['This is a text with a\nnew-line in it. Selector = 0.', '{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.', 0],
+ // with double-quotes and id split accros lines
+ ["This is a text with a\nnew-line in it. Selector = 1.", "{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.", 1],
+ // esacape pipe
+ ['This is a text with | in it. Selector = 0.', '{0}This is a text with || in it. Selector = 0.|{1}This is a text with || in it. Selector = 1.', 0],
+ // Empty plural set (2 plural forms) from a .PO file
+ ['', '|', 1],
+ // Empty plural set (3 plural forms) from a .PO file
+ ['', '||', 1],
+ ];
+ }
+}
diff --git a/vendor/symfony/translation/Tests/PluralizationRulesTest.php b/vendor/symfony/translation/Tests/PluralizationRulesTest.php
new file mode 100755
index 0000000..696c92b
--- /dev/null
+++ b/vendor/symfony/translation/Tests/PluralizationRulesTest.php
@@ -0,0 +1,124 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\PluralizationRules;
+
+/**
+ * Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms
+ * and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms.
+ *
+ * See also https://developer.mozilla.org/en/Localization_and_Plurals which mentions 15 rules having a maximum of 6 forms.
+ * The mozilla code is also interesting to check for.
+ *
+ * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199
+ *
+ * The goal to cover all languages is to far fetched so this test case is smaller.
+ *
+ * @author Clemens Tolboom clemens@build2be.nl
+ *
+ * @group legacy
+ */
+class PluralizationRulesTest extends TestCase
+{
+ /**
+ * We test failed langcode here.
+ *
+ * TODO: The languages mentioned in the data provide need to get fixed somehow within PluralizationRules.
+ *
+ * @dataProvider failingLangcodes
+ */
+ public function testFailedLangcodes($nplural, $langCodes)
+ {
+ $matrix = $this->generateTestData($langCodes);
+ $this->validateMatrix($nplural, $matrix, false);
+ }
+
+ /**
+ * @dataProvider successLangcodes
+ */
+ public function testLangcodes($nplural, $langCodes)
+ {
+ $matrix = $this->generateTestData($langCodes);
+ $this->validateMatrix($nplural, $matrix);
+ }
+
+ /**
+ * This array should contain all currently known langcodes.
+ *
+ * As it is impossible to have this ever complete we should try as hard as possible to have it almost complete.
+ *
+ * @return array
+ */
+ public function successLangcodes()
+ {
+ return [
+ ['1', ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky']],
+ ['2', ['nl', 'fr', 'en', 'de', 'de_GE', 'hy', 'hy_AM']],
+ ['3', ['be', 'bs', 'cs', 'hr']],
+ ['4', ['cy', 'mt', 'sl']],
+ ['6', ['ar']],
+ ];
+ }
+
+ /**
+ * This array should be at least empty within the near future.
+ *
+ * This both depends on a complete list trying to add above as understanding
+ * the plural rules of the current failing languages.
+ *
+ * @return array with nplural together with langcodes
+ */
+ public function failingLangcodes()
+ {
+ return [
+ ['1', ['fa']],
+ ['2', ['jbo']],
+ ['3', ['cbs']],
+ ['4', ['gd', 'kw']],
+ ['5', ['ga']],
+ ];
+ }
+
+ /**
+ * We validate only on the plural coverage. Thus the real rules is not tested.
+ *
+ * @param string $nplural Plural expected
+ * @param array $matrix Containing langcodes and their plural index values
+ * @param bool $expectSuccess
+ */
+ protected function validateMatrix($nplural, $matrix, $expectSuccess = true)
+ {
+ foreach ($matrix as $langCode => $data) {
+ $indexes = array_flip($data);
+ if ($expectSuccess) {
+ $this->assertEquals($nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms.");
+ } else {
+ $this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms.");
+ }
+ }
+ }
+
+ protected function generateTestData($langCodes)
+ {
+ $matrix = [];
+ foreach ($langCodes as $langCode) {
+ for ($count = 0; $count < 200; ++$count) {
+ $plural = PluralizationRules::get($count, $langCode);
+ $matrix[$langCode][$count] = $plural;
+ }
+ }
+
+ return $matrix;
+ }
+}
diff --git a/vendor/symfony/translation/Tests/TranslatorCacheTest.php b/vendor/symfony/translation/Tests/TranslatorCacheTest.php
new file mode 100755
index 0000000..5d437ff
--- /dev/null
+++ b/vendor/symfony/translation/Tests/TranslatorCacheTest.php
@@ -0,0 +1,318 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\SelfCheckingResourceInterface;
+use Symfony\Component\Translation\Loader\ArrayLoader;
+use Symfony\Component\Translation\Loader\LoaderInterface;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\Translator;
+
+class TranslatorCacheTest extends TestCase
+{
+ protected $tmpDir;
+
+ protected function setUp()
+ {
+ $this->tmpDir = sys_get_temp_dir().'/sf_translation';
+ $this->deleteTmpDir();
+ }
+
+ protected function tearDown()
+ {
+ $this->deleteTmpDir();
+ }
+
+ protected function deleteTmpDir()
+ {
+ if (!file_exists($dir = $this->tmpDir)) {
+ return;
+ }
+
+ $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->tmpDir), \RecursiveIteratorIterator::CHILD_FIRST);
+ foreach ($iterator as $path) {
+ if (preg_match('#[/\\\\]\.\.?$#', $path->__toString())) {
+ continue;
+ }
+ if ($path->isDir()) {
+ rmdir($path->__toString());
+ } else {
+ unlink($path->__toString());
+ }
+ }
+ rmdir($this->tmpDir);
+ }
+
+ /**
+ * @dataProvider runForDebugAndProduction
+ */
+ public function testThatACacheIsUsed($debug)
+ {
+ $locale = 'any_locale';
+ $format = 'some_format';
+ $msgid = 'test';
+
+ // Prime the cache
+ $translator = new Translator($locale, null, $this->tmpDir, $debug);
+ $translator->addLoader($format, new ArrayLoader());
+ $translator->addResource($format, [$msgid => 'OK'], $locale);
+ $translator->addResource($format, [$msgid.'+intl' => 'OK'], $locale, 'messages+intl-icu');
+ $translator->trans($msgid);
+ $translator->trans($msgid.'+intl', [], 'messages+intl-icu');
+
+ // Try again and see we get a valid result whilst no loader can be used
+ $translator = new Translator($locale, null, $this->tmpDir, $debug);
+ $translator->addLoader($format, $this->createFailingLoader());
+ $translator->addResource($format, [$msgid => 'OK'], $locale);
+ $translator->addResource($format, [$msgid.'+intl' => 'OK'], $locale, 'messages+intl-icu');
+ $this->assertEquals('OK', $translator->trans($msgid), '-> caching does not work in '.($debug ? 'debug' : 'production'));
+ $this->assertEquals('OK', $translator->trans($msgid.'+intl', [], 'messages+intl-icu'));
+ }
+
+ public function testCatalogueIsReloadedWhenResourcesAreNoLongerFresh()
+ {
+ /*
+ * The testThatACacheIsUsed() test showed that we don't need the loader as long as the cache
+ * is fresh.
+ *
+ * Now we add a Resource that is never fresh and make sure that the
+ * cache is discarded (the loader is called twice).
+ *
+ * We need to run this for debug=true only because in production the cache
+ * will never be revalidated.
+ */
+
+ $locale = 'any_locale';
+ $format = 'some_format';
+ $msgid = 'test';
+
+ $catalogue = new MessageCatalogue($locale, []);
+ $catalogue->addResource(new StaleResource()); // better use a helper class than a mock, because it gets serialized in the cache and re-loaded
+
+ /** @var LoaderInterface|\PHPUnit_Framework_MockObject_MockObject $loader */
+ $loader = $this->getMockBuilder('Symfony\Component\Translation\Loader\LoaderInterface')->getMock();
+ $loader
+ ->expects($this->exactly(2))
+ ->method('load')
+ ->willReturn($catalogue)
+ ;
+
+ // 1st pass
+ $translator = new Translator($locale, null, $this->tmpDir, true);
+ $translator->addLoader($format, $loader);
+ $translator->addResource($format, null, $locale);
+ $translator->trans($msgid);
+
+ // 2nd pass
+ $translator = new Translator($locale, null, $this->tmpDir, true);
+ $translator->addLoader($format, $loader);
+ $translator->addResource($format, null, $locale);
+ $translator->trans($msgid);
+ }
+
+ /**
+ * @dataProvider runForDebugAndProduction
+ */
+ public function testDifferentTranslatorsForSameLocaleDoNotOverwriteEachOthersCache($debug)
+ {
+ /*
+ * Similar to the previous test. After we used the second translator, make
+ * sure there's still a usable cache for the first one.
+ */
+
+ $locale = 'any_locale';
+ $format = 'some_format';
+ $msgid = 'test';
+
+ // Create a Translator and prime its cache
+ $translator = new Translator($locale, null, $this->tmpDir, $debug);
+ $translator->addLoader($format, new ArrayLoader());
+ $translator->addResource($format, [$msgid => 'OK'], $locale);
+ $translator->trans($msgid);
+
+ // Create another Translator with a different catalogue for the same locale
+ $translator = new Translator($locale, null, $this->tmpDir, $debug);
+ $translator->addLoader($format, new ArrayLoader());
+ $translator->addResource($format, [$msgid => 'FAIL'], $locale);
+ $translator->trans($msgid);
+
+ // Now the first translator must still have a usable cache.
+ $translator = new Translator($locale, null, $this->tmpDir, $debug);
+ $translator->addLoader($format, $this->createFailingLoader());
+ $translator->addResource($format, [$msgid => 'OK'], $locale);
+ $this->assertEquals('OK', $translator->trans($msgid), '-> the cache was overwritten by another translator instance in '.($debug ? 'debug' : 'production'));
+ }
+
+ public function testGeneratedCacheFilesAreOnlyBelongRequestedLocales()
+ {
+ $translator = new Translator('a', null, $this->tmpDir);
+ $translator->setFallbackLocales(['b']);
+ $translator->trans('bar');
+
+ $cachedFiles = glob($this->tmpDir.'/*.php');
+
+ $this->assertCount(1, $cachedFiles);
+ }
+
+ public function testDifferentCacheFilesAreUsedForDifferentSetsOfFallbackLocales()
+ {
+ /*
+ * Because the cache file contains a catalogue including all of its fallback
+ * catalogues, we must take the set of fallback locales into consideration when
+ * loading a catalogue from the cache.
+ */
+ $translator = new Translator('a', null, $this->tmpDir);
+ $translator->setFallbackLocales(['b']);
+
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foo (a)'], 'a');
+ $translator->addResource('array', ['bar' => 'bar (b)'], 'b');
+
+ $this->assertEquals('bar (b)', $translator->trans('bar'));
+
+ // Remove fallback locale
+ $translator->setFallbackLocales([]);
+ $this->assertEquals('bar', $translator->trans('bar'));
+
+ // Use a fresh translator with no fallback locales, result should be the same
+ $translator = new Translator('a', null, $this->tmpDir);
+
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foo (a)'], 'a');
+ $translator->addResource('array', ['bar' => 'bar (b)'], 'b');
+
+ $this->assertEquals('bar', $translator->trans('bar'));
+ }
+
+ public function testPrimaryAndFallbackCataloguesContainTheSameMessagesRegardlessOfCaching()
+ {
+ /*
+ * As a safeguard against potential BC breaks, make sure that primary and fallback
+ * catalogues (reachable via getFallbackCatalogue()) always contain the full set of
+ * messages provided by the loader. This must also be the case when these catalogues
+ * are (internally) read from a cache.
+ *
+ * Optimizations inside the translator must not change this behavior.
+ */
+
+ /*
+ * Create a translator that loads two catalogues for two different locales.
+ * The catalogues contain distinct sets of messages.
+ */
+ $translator = new Translator('a', null, $this->tmpDir);
+ $translator->setFallbackLocales(['b']);
+
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foo (a)'], 'a');
+ $translator->addResource('array', ['foo' => 'foo (b)'], 'b');
+ $translator->addResource('array', ['bar' => 'bar (b)'], 'b');
+ $translator->addResource('array', ['baz' => 'baz (b)'], 'b', 'messages+intl-icu');
+
+ $catalogue = $translator->getCatalogue('a');
+ $this->assertFalse($catalogue->defines('bar')); // Sure, the "a" catalogue does not contain that message.
+
+ $fallback = $catalogue->getFallbackCatalogue();
+ $this->assertTrue($fallback->defines('foo')); // "foo" is present in "a" and "b"
+
+ /*
+ * Now, repeat the same test.
+ * Behind the scenes, the cache is used. But that should not matter, right?
+ */
+ $translator = new Translator('a', null, $this->tmpDir);
+ $translator->setFallbackLocales(['b']);
+
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foo (a)'], 'a');
+ $translator->addResource('array', ['foo' => 'foo (b)'], 'b');
+ $translator->addResource('array', ['bar' => 'bar (b)'], 'b');
+ $translator->addResource('array', ['baz' => 'baz (b)'], 'b', 'messages+intl-icu');
+
+ $catalogue = $translator->getCatalogue('a');
+ $this->assertFalse($catalogue->defines('bar'));
+
+ $fallback = $catalogue->getFallbackCatalogue();
+ $this->assertTrue($fallback->defines('foo'));
+ $this->assertTrue($fallback->defines('baz', 'messages+intl-icu'));
+ }
+
+ public function testRefreshCacheWhenResourcesAreNoLongerFresh()
+ {
+ $resource = $this->getMockBuilder('Symfony\Component\Config\Resource\SelfCheckingResourceInterface')->getMock();
+ $loader = $this->getMockBuilder('Symfony\Component\Translation\Loader\LoaderInterface')->getMock();
+ $resource->method('isFresh')->willReturn(false);
+ $loader
+ ->expects($this->exactly(2))
+ ->method('load')
+ ->willReturn($this->getCatalogue('fr', [], [$resource]));
+
+ // prime the cache
+ $translator = new Translator('fr', null, $this->tmpDir, true);
+ $translator->addLoader('loader', $loader);
+ $translator->addResource('loader', 'foo', 'fr');
+ $translator->trans('foo');
+
+ // prime the cache second time
+ $translator = new Translator('fr', null, $this->tmpDir, true);
+ $translator->addLoader('loader', $loader);
+ $translator->addResource('loader', 'foo', 'fr');
+ $translator->trans('foo');
+ }
+
+ protected function getCatalogue($locale, $messages, $resources = [])
+ {
+ $catalogue = new MessageCatalogue($locale);
+ foreach ($messages as $key => $translation) {
+ $catalogue->set($key, $translation);
+ }
+ foreach ($resources as $resource) {
+ $catalogue->addResource($resource);
+ }
+
+ return $catalogue;
+ }
+
+ public function runForDebugAndProduction()
+ {
+ return [[true], [false]];
+ }
+
+ /**
+ * @return LoaderInterface
+ */
+ private function createFailingLoader()
+ {
+ $loader = $this->getMockBuilder('Symfony\Component\Translation\Loader\LoaderInterface')->getMock();
+ $loader
+ ->expects($this->never())
+ ->method('load');
+
+ return $loader;
+ }
+}
+
+class StaleResource implements SelfCheckingResourceInterface
+{
+ public function isFresh($timestamp)
+ {
+ return false;
+ }
+
+ public function getResource()
+ {
+ }
+
+ public function __toString()
+ {
+ return '';
+ }
+}
diff --git a/vendor/symfony/translation/Tests/TranslatorTest.php b/vendor/symfony/translation/Tests/TranslatorTest.php
new file mode 100755
index 0000000..51c4a0a
--- /dev/null
+++ b/vendor/symfony/translation/Tests/TranslatorTest.php
@@ -0,0 +1,612 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Loader\ArrayLoader;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\Translator;
+
+class TranslatorTest extends TestCase
+{
+ /**
+ * @dataProvider getInvalidLocalesTests
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
+ */
+ public function testConstructorInvalidLocale($locale)
+ {
+ new Translator($locale);
+ }
+
+ /**
+ * @dataProvider getValidLocalesTests
+ */
+ public function testConstructorValidLocale($locale)
+ {
+ $translator = new Translator($locale);
+
+ $this->assertEquals($locale, $translator->getLocale());
+ }
+
+ public function testConstructorWithoutLocale()
+ {
+ $translator = new Translator(null);
+
+ $this->assertNull($translator->getLocale());
+ }
+
+ public function testSetGetLocale()
+ {
+ $translator = new Translator('en');
+
+ $this->assertEquals('en', $translator->getLocale());
+
+ $translator->setLocale('fr');
+ $this->assertEquals('fr', $translator->getLocale());
+ }
+
+ /**
+ * @dataProvider getInvalidLocalesTests
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
+ */
+ public function testSetInvalidLocale($locale)
+ {
+ $translator = new Translator('fr');
+ $translator->setLocale($locale);
+ }
+
+ /**
+ * @dataProvider getValidLocalesTests
+ */
+ public function testSetValidLocale($locale)
+ {
+ $translator = new Translator($locale);
+ $translator->setLocale($locale);
+
+ $this->assertEquals($locale, $translator->getLocale());
+ }
+
+ public function testGetCatalogue()
+ {
+ $translator = new Translator('en');
+
+ $this->assertEquals(new MessageCatalogue('en'), $translator->getCatalogue());
+
+ $translator->setLocale('fr');
+ $this->assertEquals(new MessageCatalogue('fr'), $translator->getCatalogue('fr'));
+ }
+
+ public function testGetCatalogueReturnsConsolidatedCatalogue()
+ {
+ /*
+ * This will be useful once we refactor so that different domains will be loaded lazily (on-demand).
+ * In that case, getCatalogue() will probably have to load all missing domains in order to return
+ * one complete catalogue.
+ */
+
+ $locale = 'whatever';
+ $translator = new Translator($locale);
+ $translator->addLoader('loader-a', new ArrayLoader());
+ $translator->addLoader('loader-b', new ArrayLoader());
+ $translator->addResource('loader-a', ['foo' => 'foofoo'], $locale, 'domain-a');
+ $translator->addResource('loader-b', ['bar' => 'foobar'], $locale, 'domain-b');
+
+ /*
+ * Test that we get a single catalogue comprising messages
+ * from different loaders and different domains
+ */
+ $catalogue = $translator->getCatalogue($locale);
+ $this->assertTrue($catalogue->defines('foo', 'domain-a'));
+ $this->assertTrue($catalogue->defines('bar', 'domain-b'));
+ }
+
+ public function testSetFallbackLocales()
+ {
+ $translator = new Translator('en');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foofoo'], 'en');
+ $translator->addResource('array', ['bar' => 'foobar'], 'fr');
+
+ // force catalogue loading
+ $translator->trans('bar');
+
+ $translator->setFallbackLocales(['fr']);
+ $this->assertEquals('foobar', $translator->trans('bar'));
+ }
+
+ public function testSetFallbackLocalesMultiple()
+ {
+ $translator = new Translator('en');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foo (en)'], 'en');
+ $translator->addResource('array', ['bar' => 'bar (fr)'], 'fr');
+
+ // force catalogue loading
+ $translator->trans('bar');
+
+ $translator->setFallbackLocales(['fr_FR', 'fr']);
+ $this->assertEquals('bar (fr)', $translator->trans('bar'));
+ }
+
+ /**
+ * @dataProvider getInvalidLocalesTests
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
+ */
+ public function testSetFallbackInvalidLocales($locale)
+ {
+ $translator = new Translator('fr');
+ $translator->setFallbackLocales(['fr', $locale]);
+ }
+
+ /**
+ * @dataProvider getValidLocalesTests
+ */
+ public function testSetFallbackValidLocales($locale)
+ {
+ $translator = new Translator($locale);
+ $translator->setFallbackLocales(['fr', $locale]);
+ // no assertion. this method just asserts that no exception is thrown
+ $this->addToAssertionCount(1);
+ }
+
+ public function testTransWithFallbackLocale()
+ {
+ $translator = new Translator('fr_FR');
+ $translator->setFallbackLocales(['en']);
+
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['bar' => 'foobar'], 'en');
+
+ $this->assertEquals('foobar', $translator->trans('bar'));
+ }
+
+ /**
+ * @dataProvider getInvalidLocalesTests
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
+ */
+ public function testAddResourceInvalidLocales($locale)
+ {
+ $translator = new Translator('fr');
+ $translator->addResource('array', ['foo' => 'foofoo'], $locale);
+ }
+
+ /**
+ * @dataProvider getValidLocalesTests
+ */
+ public function testAddResourceValidLocales($locale)
+ {
+ $translator = new Translator('fr');
+ $translator->addResource('array', ['foo' => 'foofoo'], $locale);
+ // no assertion. this method just asserts that no exception is thrown
+ $this->addToAssertionCount(1);
+ }
+
+ public function testAddResourceAfterTrans()
+ {
+ $translator = new Translator('fr');
+ $translator->addLoader('array', new ArrayLoader());
+
+ $translator->setFallbackLocales(['en']);
+
+ $translator->addResource('array', ['foo' => 'foofoo'], 'en');
+ $this->assertEquals('foofoo', $translator->trans('foo'));
+
+ $translator->addResource('array', ['bar' => 'foobar'], 'en');
+ $this->assertEquals('foobar', $translator->trans('bar'));
+ }
+
+ /**
+ * @dataProvider getTransFileTests
+ * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testTransWithoutFallbackLocaleFile($format, $loader)
+ {
+ $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader;
+ $translator = new Translator('en');
+ $translator->addLoader($format, new $loaderClass());
+ $translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en');
+ $translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en');
+
+ // force catalogue loading
+ $translator->trans('foo');
+ }
+
+ /**
+ * @dataProvider getTransFileTests
+ */
+ public function testTransWithFallbackLocaleFile($format, $loader)
+ {
+ $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader;
+ $translator = new Translator('en_GB');
+ $translator->addLoader($format, new $loaderClass());
+ $translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en_GB');
+ $translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en', 'resources');
+
+ $this->assertEquals('bar', $translator->trans('foo', [], 'resources'));
+ }
+
+ public function testTransWithIcuFallbackLocale()
+ {
+ $translator = new Translator('en_GB');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foofoo'], 'en_GB');
+ $translator->addResource('array', ['bar' => 'foobar'], 'en_001');
+ $translator->addResource('array', ['baz' => 'foobaz'], 'en');
+ $this->assertSame('foofoo', $translator->trans('foo'));
+ $this->assertSame('foobar', $translator->trans('bar'));
+ $this->assertSame('foobaz', $translator->trans('baz'));
+ }
+
+ public function testTransWithIcuVariantFallbackLocale()
+ {
+ $translator = new Translator('en_GB_scouse');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foofoo'], 'en_GB_scouse');
+ $translator->addResource('array', ['bar' => 'foobar'], 'en_GB');
+ $translator->addResource('array', ['baz' => 'foobaz'], 'en_001');
+ $translator->addResource('array', ['qux' => 'fooqux'], 'en');
+ $this->assertSame('foofoo', $translator->trans('foo'));
+ $this->assertSame('foobar', $translator->trans('bar'));
+ $this->assertSame('foobaz', $translator->trans('baz'));
+ $this->assertSame('fooqux', $translator->trans('qux'));
+ }
+
+ public function testTransWithIcuRootFallbackLocale()
+ {
+ $translator = new Translator('az_Cyrl');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foofoo'], 'az_Cyrl');
+ $translator->addResource('array', ['bar' => 'foobar'], 'az');
+ $this->assertSame('foofoo', $translator->trans('foo'));
+ $this->assertSame('bar', $translator->trans('bar'));
+ }
+
+ public function testTransWithFallbackLocaleBis()
+ {
+ $translator = new Translator('en_US');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foofoo'], 'en_US');
+ $translator->addResource('array', ['bar' => 'foobar'], 'en');
+ $this->assertEquals('foobar', $translator->trans('bar'));
+ }
+
+ public function testTransWithFallbackLocaleTer()
+ {
+ $translator = new Translator('fr_FR');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foo (en_US)'], 'en_US');
+ $translator->addResource('array', ['bar' => 'bar (en)'], 'en');
+
+ $translator->setFallbackLocales(['en_US', 'en']);
+
+ $this->assertEquals('foo (en_US)', $translator->trans('foo'));
+ $this->assertEquals('bar (en)', $translator->trans('bar'));
+ }
+
+ public function testTransNonExistentWithFallback()
+ {
+ $translator = new Translator('fr');
+ $translator->setFallbackLocales(['en']);
+ $translator->addLoader('array', new ArrayLoader());
+ $this->assertEquals('non-existent', $translator->trans('non-existent'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Translation\Exception\RuntimeException
+ */
+ public function testWhenAResourceHasNoRegisteredLoader()
+ {
+ $translator = new Translator('en');
+ $translator->addResource('array', ['foo' => 'foofoo'], 'en');
+
+ $translator->trans('foo');
+ }
+
+ public function testNestedFallbackCatalogueWhenUsingMultipleLocales()
+ {
+ $translator = new Translator('fr');
+ $translator->setFallbackLocales(['ru', 'en']);
+
+ $translator->getCatalogue('fr');
+
+ $this->assertNotNull($translator->getCatalogue('ru')->getFallbackCatalogue());
+ }
+
+ public function testFallbackCatalogueResources()
+ {
+ $translator = new Translator('en_GB');
+ $translator->addLoader('yml', new \Symfony\Component\Translation\Loader\YamlFileLoader());
+ $translator->addResource('yml', __DIR__.'/fixtures/empty.yml', 'en_GB');
+ $translator->addResource('yml', __DIR__.'/fixtures/resources.yml', 'en');
+
+ // force catalogue loading
+ $this->assertEquals('bar', $translator->trans('foo', []));
+
+ $resources = $translator->getCatalogue('en')->getResources();
+ $this->assertCount(1, $resources);
+ $this->assertContains(__DIR__.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'resources.yml', $resources);
+
+ $resources = $translator->getCatalogue('en_GB')->getResources();
+ $this->assertCount(2, $resources);
+ $this->assertContains(__DIR__.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'empty.yml', $resources);
+ $this->assertContains(__DIR__.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'resources.yml', $resources);
+ }
+
+ /**
+ * @dataProvider getTransTests
+ */
+ public function testTrans($expected, $id, $translation, $parameters, $locale, $domain)
+ {
+ $translator = new Translator('en');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', [(string) $id => $translation], $locale, $domain);
+
+ $this->assertEquals($expected, $translator->trans($id, $parameters, $domain, $locale));
+ }
+
+ /**
+ * @dataProvider getInvalidLocalesTests
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
+ */
+ public function testTransInvalidLocale($locale)
+ {
+ $translator = new Translator('en');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foofoo'], 'en');
+
+ $translator->trans('foo', [], '', $locale);
+ }
+
+ /**
+ * @dataProvider getValidLocalesTests
+ */
+ public function testTransValidLocale($locale)
+ {
+ $translator = new Translator($locale);
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['test' => 'OK'], $locale);
+
+ $this->assertEquals('OK', $translator->trans('test'));
+ $this->assertEquals('OK', $translator->trans('test', [], null, $locale));
+ }
+
+ /**
+ * @dataProvider getFlattenedTransTests
+ */
+ public function testFlattenedTrans($expected, $messages, $id)
+ {
+ $translator = new Translator('en');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', $messages, 'fr', '');
+
+ $this->assertEquals($expected, $translator->trans($id, [], '', 'fr'));
+ }
+
+ /**
+ * @dataProvider getTransChoiceTests
+ * @group legacy
+ */
+ public function testTransChoice($expected, $id, $translation, $number, $parameters, $locale, $domain)
+ {
+ $translator = new Translator('en');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', [(string) $id => $translation], $locale, $domain);
+
+ $this->assertEquals($expected, $translator->transChoice($id, $number, $parameters, $domain, $locale));
+ }
+
+ /**
+ * @dataProvider getInvalidLocalesTests
+ * @expectedException \Symfony\Component\Translation\Exception\InvalidArgumentException
+ * @group legacy
+ */
+ public function testTransChoiceInvalidLocale($locale)
+ {
+ $translator = new Translator('en');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foofoo'], 'en');
+
+ $translator->transChoice('foo', 1, [], '', $locale);
+ }
+
+ /**
+ * @dataProvider getValidLocalesTests
+ * @group legacy
+ */
+ public function testTransChoiceValidLocale($locale)
+ {
+ $translator = new Translator('en');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foofoo'], 'en');
+
+ $translator->transChoice('foo', 1, [], '', $locale);
+ // no assertion. this method just asserts that no exception is thrown
+ $this->addToAssertionCount(1);
+ }
+
+ public function getTransFileTests()
+ {
+ return [
+ ['csv', 'CsvFileLoader'],
+ ['ini', 'IniFileLoader'],
+ ['mo', 'MoFileLoader'],
+ ['po', 'PoFileLoader'],
+ ['php', 'PhpFileLoader'],
+ ['ts', 'QtFileLoader'],
+ ['xlf', 'XliffFileLoader'],
+ ['yml', 'YamlFileLoader'],
+ ['json', 'JsonFileLoader'],
+ ];
+ }
+
+ public function getTransTests()
+ {
+ return [
+ ['Symfony est super !', 'Symfony is great!', 'Symfony est super !', [], 'fr', ''],
+ ['Symfony est awesome !', 'Symfony is %what%!', 'Symfony est %what% !', ['%what%' => 'awesome'], 'fr', ''],
+ ['Symfony est super !', new StringClass('Symfony is great!'), 'Symfony est super !', [], 'fr', ''],
+ ];
+ }
+
+ public function getFlattenedTransTests()
+ {
+ $messages = [
+ 'symfony' => [
+ 'is' => [
+ 'great' => 'Symfony est super!',
+ ],
+ ],
+ 'foo' => [
+ 'bar' => [
+ 'baz' => 'Foo Bar Baz',
+ ],
+ 'baz' => 'Foo Baz',
+ ],
+ ];
+
+ return [
+ ['Symfony est super!', $messages, 'symfony.is.great'],
+ ['Foo Bar Baz', $messages, 'foo.bar.baz'],
+ ['Foo Baz', $messages, 'foo.baz'],
+ ];
+ }
+
+ public function getTransChoiceTests()
+ {
+ return [
+ ['Il y a 0 pomme', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, [], 'fr', ''],
+ ['Il y a 1 pomme', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 1, [], 'fr', ''],
+ ['Il y a 10 pommes', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 10, [], 'fr', ''],
+
+ ['Il y a 0 pomme', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 0, [], 'fr', ''],
+ ['Il y a 1 pomme', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 1, [], 'fr', ''],
+ ['Il y a 10 pommes', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 10, [], 'fr', ''],
+
+ ['Il y a 0 pomme', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 0, [], 'fr', ''],
+ ['Il y a 1 pomme', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 1, [], 'fr', ''],
+ ['Il y a 10 pommes', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 10, [], 'fr', ''],
+
+ ['Il n\'y a aucune pomme', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 0, [], 'fr', ''],
+ ['Il y a 1 pomme', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 1, [], 'fr', ''],
+ ['Il y a 10 pommes', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 10, [], 'fr', ''],
+
+ ['Il y a 0 pomme', new StringClass('{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples'), '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, [], 'fr', ''],
+
+ // Override %count% with a custom value
+ ['Il y a quelques pommes', 'one: There is one apple|more: There are %count% apples', 'one: Il y a %count% pomme|more: Il y a quelques pommes', 2, ['%count%' => 'quelques'], 'fr', ''],
+ ];
+ }
+
+ public function getInvalidLocalesTests()
+ {
+ return [
+ ['fr FR'],
+ ['français'],
+ ['fr+en'],
+ ['utf#8'],
+ ['fr&en'],
+ ['fr~FR'],
+ [' fr'],
+ ['fr '],
+ ['fr*'],
+ ['fr/FR'],
+ ['fr\\FR'],
+ ];
+ }
+
+ public function getValidLocalesTests()
+ {
+ return [
+ [''],
+ [null],
+ ['fr'],
+ ['francais'],
+ ['FR'],
+ ['frFR'],
+ ['fr-FR'],
+ ['fr_FR'],
+ ['fr.FR'],
+ ['fr-FR.UTF8'],
+ ['sr@latin'],
+ ];
+ }
+
+ /**
+ * @requires extension intl
+ */
+ public function testIntlFormattedDomain()
+ {
+ $translator = new Translator('en');
+ $translator->addLoader('array', new ArrayLoader());
+
+ $translator->addResource('array', ['some_message' => 'Hello %name%'], 'en');
+ $this->assertSame('Hello Bob', $translator->trans('some_message', ['%name%' => 'Bob']));
+
+ $translator->addResource('array', ['some_message' => 'Hi {name}'], 'en', 'messages+intl-icu');
+ $this->assertSame('Hi Bob', $translator->trans('some_message', ['%name%' => 'Bob']));
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testTransChoiceFallback()
+ {
+ $translator = new Translator('ru');
+ $translator->setFallbackLocales(['en']);
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['some_message2' => 'one thing|%count% things'], 'en');
+
+ $this->assertEquals('10 things', $translator->transChoice('some_message2', 10, ['%count%' => 10]));
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testTransChoiceFallbackBis()
+ {
+ $translator = new Translator('ru');
+ $translator->setFallbackLocales(['en_US', 'en']);
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['some_message2' => 'one thing|%count% things'], 'en_US');
+
+ $this->assertEquals('10 things', $translator->transChoice('some_message2', 10, ['%count%' => 10]));
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testTransChoiceFallbackWithNoTranslation()
+ {
+ $translator = new Translator('ru');
+ $translator->setFallbackLocales(['en']);
+ $translator->addLoader('array', new ArrayLoader());
+
+ // consistent behavior with Translator::trans(), which returns the string
+ // unchanged if it can't be found
+ $this->assertEquals('some_message2', $translator->transChoice('some_message2', 10, ['%count%' => 10]));
+ }
+}
+
+class StringClass
+{
+ protected $str;
+
+ public function __construct($str)
+ {
+ $this->str = $str;
+ }
+
+ public function __toString()
+ {
+ return $this->str;
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Util/ArrayConverterTest.php b/vendor/symfony/translation/Tests/Util/ArrayConverterTest.php
new file mode 100755
index 0000000..b033541
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Util/ArrayConverterTest.php
@@ -0,0 +1,74 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Util;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Util\ArrayConverter;
+
+class ArrayConverterTest extends TestCase
+{
+ /**
+ * @dataProvider messagesData
+ */
+ public function testDump($input, $expectedOutput)
+ {
+ $this->assertEquals($expectedOutput, ArrayConverter::expandToTree($input));
+ }
+
+ public function messagesData()
+ {
+ return [
+ [
+ // input
+ [
+ 'foo1' => 'bar',
+ 'foo.bar' => 'value',
+ ],
+ // expected output
+ [
+ 'foo1' => 'bar',
+ 'foo' => ['bar' => 'value'],
+ ],
+ ],
+ [
+ // input
+ [
+ 'foo.bar' => 'value1',
+ 'foo.bar.test' => 'value2',
+ ],
+ // expected output
+ [
+ 'foo' => [
+ 'bar' => 'value1',
+ 'bar.test' => 'value2',
+ ],
+ ],
+ ],
+ [
+ // input
+ [
+ 'foo.level2.level3.level4' => 'value1',
+ 'foo.level2' => 'value2',
+ 'foo.bar' => 'value3',
+ ],
+ // expected output
+ [
+ 'foo' => [
+ 'level2' => 'value2',
+ 'level2.level3.level4' => 'value1',
+ 'bar' => 'value3',
+ ],
+ ],
+ ],
+ ];
+ }
+}
diff --git a/vendor/symfony/translation/Tests/Writer/TranslationWriterTest.php b/vendor/symfony/translation/Tests/Writer/TranslationWriterTest.php
new file mode 100755
index 0000000..d3b6754
--- /dev/null
+++ b/vendor/symfony/translation/Tests/Writer/TranslationWriterTest.php
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Tests\Writer;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Translation\Dumper\DumperInterface;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\Writer\TranslationWriter;
+
+class TranslationWriterTest extends TestCase
+{
+ public function testWrite()
+ {
+ $dumper = $this->getMockBuilder('Symfony\Component\Translation\Dumper\DumperInterface')->getMock();
+ $dumper
+ ->expects($this->once())
+ ->method('dump');
+
+ $writer = new TranslationWriter();
+ $writer->addDumper('test', $dumper);
+ $writer->write(new MessageCatalogue('en'), 'test');
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testDisableBackup()
+ {
+ $nonBackupDumper = new NonBackupDumper();
+ $backupDumper = new BackupDumper();
+
+ $writer = new TranslationWriter();
+ $writer->addDumper('non_backup', $nonBackupDumper);
+ $writer->addDumper('backup', $backupDumper);
+ $writer->disableBackup();
+
+ $this->assertFalse($backupDumper->backup, 'backup can be disabled if setBackup() method does exist');
+ }
+}
+
+class NonBackupDumper implements DumperInterface
+{
+ public function dump(MessageCatalogue $messages, $options = [])
+ {
+ }
+}
+
+class BackupDumper implements DumperInterface
+{
+ public $backup = true;
+
+ public function dump(MessageCatalogue $messages, $options = [])
+ {
+ }
+
+ public function setBackup($backup)
+ {
+ $this->backup = $backup;
+ }
+}
diff --git a/vendor/symfony/translation/Tests/fixtures/empty-translation.mo b/vendor/symfony/translation/Tests/fixtures/empty-translation.mo
new file mode 100755
index 0000000..ed01000
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/empty-translation.mo
Binary files differ
diff --git a/vendor/symfony/translation/Tests/fixtures/empty-translation.po b/vendor/symfony/translation/Tests/fixtures/empty-translation.po
new file mode 100755
index 0000000..ff6f22a
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/empty-translation.po
@@ -0,0 +1,3 @@
+msgid "foo"
+msgstr ""
+
diff --git a/vendor/symfony/translation/Tests/fixtures/empty.csv b/vendor/symfony/translation/Tests/fixtures/empty.csv
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/empty.csv
diff --git a/vendor/symfony/translation/Tests/fixtures/empty.ini b/vendor/symfony/translation/Tests/fixtures/empty.ini
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/empty.ini
diff --git a/vendor/symfony/translation/Tests/fixtures/empty.json b/vendor/symfony/translation/Tests/fixtures/empty.json
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/empty.json
diff --git a/vendor/symfony/translation/Tests/fixtures/empty.mo b/vendor/symfony/translation/Tests/fixtures/empty.mo
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/empty.mo
diff --git a/vendor/symfony/translation/Tests/fixtures/empty.po b/vendor/symfony/translation/Tests/fixtures/empty.po
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/empty.po
diff --git a/vendor/symfony/translation/Tests/fixtures/empty.xlf b/vendor/symfony/translation/Tests/fixtures/empty.xlf
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/empty.xlf
diff --git a/vendor/symfony/translation/Tests/fixtures/empty.yml b/vendor/symfony/translation/Tests/fixtures/empty.yml
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/empty.yml
diff --git a/vendor/symfony/translation/Tests/fixtures/encoding.xlf b/vendor/symfony/translation/Tests/fixtures/encoding.xlf
new file mode 100755
index 0000000..0a88f92
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/encoding.xlf
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
+ <file source-language="en" datatype="plaintext" original="file.ext">
+ <body>
+ <trans-unit id="1" resname="foo">
+ <source>foo</source>
+ <target>b�r</target>
+ <note>b�z</note>
+ </trans-unit>
+ <trans-unit id="2" resname="bar">
+ <source>bar</source>
+ <target>f��</target>
+ </trans-unit>
+ </body>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/escaped-id-plurals.po b/vendor/symfony/translation/Tests/fixtures/escaped-id-plurals.po
new file mode 100755
index 0000000..c412aa2
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/escaped-id-plurals.po
@@ -0,0 +1,10 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+
+msgid "escaped \"foo\""
+msgid_plural "escaped \"foos\""
+msgstr[0] "escaped \"bar\""
+msgstr[1] "escaped \"bars\""
diff --git a/vendor/symfony/translation/Tests/fixtures/escaped-id.po b/vendor/symfony/translation/Tests/fixtures/escaped-id.po
new file mode 100755
index 0000000..308eadd
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/escaped-id.po
@@ -0,0 +1,8 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+
+msgid "escaped \"foo\""
+msgstr "escaped \"bar\""
diff --git a/vendor/symfony/translation/Tests/fixtures/extractor/resource.format.engine b/vendor/symfony/translation/Tests/fixtures/extractor/resource.format.engine
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/extractor/resource.format.engine
diff --git a/vendor/symfony/translation/Tests/fixtures/extractor/this.is.a.template.format.engine b/vendor/symfony/translation/Tests/fixtures/extractor/this.is.a.template.format.engine
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/extractor/this.is.a.template.format.engine
diff --git a/vendor/symfony/translation/Tests/fixtures/extractor/translation.html.php b/vendor/symfony/translation/Tests/fixtures/extractor/translation.html.php
new file mode 100755
index 0000000..5085eab
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/extractor/translation.html.php
@@ -0,0 +1,59 @@
+This template is used for translation message extraction tests
+<?php echo $view['translator']->trans('single-quoted key'); ?>
+<?php echo $view['translator']->trans('double-quoted key'); ?>
+<?php echo $view['translator']->trans(<<<EOF
+heredoc key
+EOF
+); ?>
+<?php echo $view['translator']->trans(<<<'EOF'
+nowdoc key
+EOF
+); ?>
+<?php echo $view['translator']->trans(
+ "double-quoted key with whitespace and escaped \$\n\" sequences"
+); ?>
+<?php echo $view['translator']->trans(
+ 'single-quoted key with whitespace and nonescaped \$\n\' sequences'
+); ?>
+<?php echo $view['translator']->trans(<<<EOF
+heredoc key with whitespace and escaped \$\n sequences
+EOF
+); ?>
+<?php echo $view['translator']->trans(<<<'EOF'
+nowdoc key with whitespace and nonescaped \$\n sequences
+EOF
+); ?>
+
+<?php echo $view['translator']->trans('single-quoted key with "quote mark at the end"'); ?>
+
+<?php echo $view['translator']->transChoice(
+ '{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
+ 10,
+ ['%count%' => 10]
+); ?>
+
+<?php echo $view['translator']->trans('concatenated'.' message'.<<<EOF
+ with heredoc
+EOF
+.<<<'EOF'
+ and nowdoc
+EOF
+); ?>
+
+<?php echo $view['translator']->trans('other-domain-test-no-params-short-array', [], 'not_messages'); ?>
+
+<?php echo $view['translator']->trans('other-domain-test-no-params-long-array', [], 'not_messages'); ?>
+
+<?php echo $view['translator']->trans('other-domain-test-params-short-array', ['foo' => 'bar'], 'not_messages'); ?>
+
+<?php echo $view['translator']->trans('other-domain-test-params-long-array', ['foo' => 'bar'], 'not_messages'); ?>
+
+<?php echo $view['translator']->transChoice('other-domain-test-trans-choice-short-array-%count%', 10, ['%count%' => 10], 'not_messages'); ?>
+
+<?php echo $view['translator']->transChoice('other-domain-test-trans-choice-long-array-%count%', 10, ['%count%' => 10], 'not_messages'); ?>
+
+<?php echo $view['translator']->trans('typecast', ['a' => (int) '123'], 'not_messages'); ?>
+<?php echo $view['translator']->transChoice('msg1', 10 + 1, [], 'not_messages'); ?>
+<?php echo $view['translator']->transChoice('msg2', ceil(4.5), [], 'not_messages'); ?>
+
+<?php echo $view['translator']->trans('default domain', [], null); ?>
diff --git a/vendor/symfony/translation/Tests/fixtures/fuzzy-translations.po b/vendor/symfony/translation/Tests/fixtures/fuzzy-translations.po
new file mode 100755
index 0000000..04d4047
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/fuzzy-translations.po
@@ -0,0 +1,10 @@
+#, php-format
+msgid "foo1"
+msgstr "bar1"
+
+#, fuzzy, php-format
+msgid "foo2"
+msgstr "fuzzy bar2"
+
+msgid "foo3"
+msgstr "bar3"
diff --git a/vendor/symfony/translation/Tests/fixtures/invalid-xml-resources.xlf b/vendor/symfony/translation/Tests/fixtures/invalid-xml-resources.xlf
new file mode 100755
index 0000000..7bf6c98
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/invalid-xml-resources.xlf
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
+ <file source-language="en" datatype="plaintext" original="file.ext">
+ <body>
+ <trans-unit id="1">
+ <source>foo</source>
+ <target>bar
+ </trans-unit>
+ <trans-unit id="2">
+ <source>extra</source>
+ </trans-unit>
+ <trans-unit id="3">
+ <source>key</source>
+ <target></target>
+ </trans-unit>
+ <trans-unit id="4">
+ <source>test</source>
+ <target>with</target>
+ <note>note</note>
+ </trans-unit>
+ </body>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/malformed.json b/vendor/symfony/translation/Tests/fixtures/malformed.json
new file mode 100755
index 0000000..4563ec6
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/malformed.json
@@ -0,0 +1,3 @@
+{
+ "foo" "bar"
+}
\ No newline at end of file
diff --git a/vendor/symfony/translation/Tests/fixtures/messages.yml b/vendor/symfony/translation/Tests/fixtures/messages.yml
new file mode 100755
index 0000000..d4f82d7
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/messages.yml
@@ -0,0 +1,3 @@
+foo:
+ bar1: value1
+ bar2: value2
diff --git a/vendor/symfony/translation/Tests/fixtures/messages_linear.yml b/vendor/symfony/translation/Tests/fixtures/messages_linear.yml
new file mode 100755
index 0000000..6c1687d
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/messages_linear.yml
@@ -0,0 +1,2 @@
+foo.bar1: value1
+foo.bar2: value2
diff --git a/vendor/symfony/translation/Tests/fixtures/non-valid.xlf b/vendor/symfony/translation/Tests/fixtures/non-valid.xlf
new file mode 100755
index 0000000..734fc97
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/non-valid.xlf
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
+ <file source-language="en" datatype="plaintext" original="file.ext">
+ <body>
+ <trans-unit>
+ <source>foo</source>
+ <target>bar</target>
+ </trans-unit>
+ </body>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/non-valid.yml b/vendor/symfony/translation/Tests/fixtures/non-valid.yml
new file mode 100755
index 0000000..257cc56
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/non-valid.yml
@@ -0,0 +1 @@
+foo
diff --git a/vendor/symfony/translation/Tests/fixtures/plurals.mo b/vendor/symfony/translation/Tests/fixtures/plurals.mo
new file mode 100755
index 0000000..6445e77
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/plurals.mo
Binary files differ
diff --git a/vendor/symfony/translation/Tests/fixtures/plurals.po b/vendor/symfony/translation/Tests/fixtures/plurals.po
new file mode 100755
index 0000000..439c41a
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/plurals.po
@@ -0,0 +1,5 @@
+msgid "foo"
+msgid_plural "foos"
+msgstr[0] "bar"
+msgstr[1] "bars"
+
diff --git a/vendor/symfony/translation/Tests/fixtures/resname.xlf b/vendor/symfony/translation/Tests/fixtures/resname.xlf
new file mode 100755
index 0000000..4fa5c00
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resname.xlf
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
+ <file source-language="en" datatype="plaintext" original="file.ext">
+ <body>
+ <trans-unit id="1" resname="foo">
+ <source></source>
+ <target>bar</target>
+ </trans-unit>
+ <trans-unit id="2" resname="bar">
+ <source>bar source</source>
+ <target>baz</target>
+ </trans-unit>
+ <trans-unit id="3">
+ <source>baz</source>
+ <target>foo</target>
+ </trans-unit>
+ <trans-unit id="4" resname="qux">
+ <source>qux source</source>
+ </trans-unit>
+ </body>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/resourcebundle/corrupted/resources.dat b/vendor/symfony/translation/Tests/fixtures/resourcebundle/corrupted/resources.dat
new file mode 100755
index 0000000..391250c
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resourcebundle/corrupted/resources.dat
@@ -0,0 +1 @@
+XXX
\ No newline at end of file
diff --git a/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/en.res b/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/en.res
new file mode 100755
index 0000000..1fc1436
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/en.res
Binary files differ
diff --git a/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/en.txt b/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/en.txt
new file mode 100755
index 0000000..3d9e9ea
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/en.txt
@@ -0,0 +1,3 @@
+en{
+ symfony{"Symfony is great"}
+}
\ No newline at end of file
diff --git a/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/fr.res b/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/fr.res
new file mode 100755
index 0000000..f584160
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/fr.res
Binary files differ
diff --git a/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/fr.txt b/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/fr.txt
new file mode 100755
index 0000000..182d0a0
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/fr.txt
@@ -0,0 +1,3 @@
+fr{
+ symfony{"Symfony est génial"}
+}
\ No newline at end of file
diff --git a/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/packagelist.txt b/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/packagelist.txt
new file mode 100755
index 0000000..c5783ed
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/packagelist.txt
@@ -0,0 +1,2 @@
+en.res
+fr.res
diff --git a/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/resources.dat b/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/resources.dat
new file mode 100755
index 0000000..563b0ea
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/resources.dat
Binary files differ
diff --git a/vendor/symfony/translation/Tests/fixtures/resourcebundle/res/en.res b/vendor/symfony/translation/Tests/fixtures/resourcebundle/res/en.res
new file mode 100755
index 0000000..ad894a9
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resourcebundle/res/en.res
Binary files differ
diff --git a/vendor/symfony/translation/Tests/fixtures/resources-2.0+intl-icu.xlf b/vendor/symfony/translation/Tests/fixtures/resources-2.0+intl-icu.xlf
new file mode 100755
index 0000000..6294f16
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources-2.0+intl-icu.xlf
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="fr-FR" trgLang="en-US">
+ <file id="messages.en_US">
+ <unit id="LCa0a2j" name="foo">
+ <segment>
+ <source>foo</source>
+ <target>bar</target>
+ </segment>
+ </unit>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/resources-2.0-clean.xlf b/vendor/symfony/translation/Tests/fixtures/resources-2.0-clean.xlf
new file mode 100755
index 0000000..efa69b2
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources-2.0-clean.xlf
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="fr-FR" trgLang="en-US">
+ <file id="messages.en_US">
+ <unit id="LCa0a2j" name="foo">
+ <segment>
+ <source>foo</source>
+ <target>bar</target>
+ </segment>
+ </unit>
+ <unit id="LHDhK3o" name="key">
+ <segment>
+ <source>key</source>
+ <target order="1"></target>
+ </segment>
+ </unit>
+ <unit id="2DA_bnh" name="key.with.cdata">
+ <segment>
+ <source>key.with.cdata</source>
+ <target><![CDATA[<source> & <target>]]></target>
+ </segment>
+ </unit>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/resources-2.0-multi-segment-unit.xlf b/vendor/symfony/translation/Tests/fixtures/resources-2.0-multi-segment-unit.xlf
new file mode 100755
index 0000000..d0dc2a8
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources-2.0-multi-segment-unit.xlf
@@ -0,0 +1,17 @@
+<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en-US" trgLang="en-US">
+ <file id="f1">
+ <unit id="1">
+ <notes>
+ <note category="processed">true</note>
+ </notes>
+ <segment id="id-foo">
+ <source>foo</source>
+ <target>foo (translated)</target>
+ </segment>
+ <segment id="id-bar">
+ <source>bar</source>
+ <target>bar (translated)</target>
+ </segment>
+ </unit>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/resources-2.0.xlf b/vendor/symfony/translation/Tests/fixtures/resources-2.0.xlf
new file mode 100755
index 0000000..166172a
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources-2.0.xlf
@@ -0,0 +1,25 @@
+<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en-US" trgLang="ja-JP">
+ <file id="f1" original="Graphic Example.psd">
+ <skeleton href="Graphic Example.psd.skl"/>
+ <unit id="1">
+ <segment>
+ <source>Quetzal</source>
+ <target>Quetzal</target>
+ </segment>
+ </unit>
+ <group id="1">
+ <unit id="2">
+ <segment>
+ <source>foo</source>
+ <target>XLIFF 文書を編集、または処理 するアプリケーションです。</target>
+ </segment>
+ </unit>
+ <unit id="3">
+ <segment>
+ <source>bar</source>
+ <target order="1">XLIFF データ・マネージャ</target>
+ </segment>
+ </unit>
+ </group>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/resources-clean.xlf b/vendor/symfony/translation/Tests/fixtures/resources-clean.xlf
new file mode 100755
index 0000000..00c8a5c
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources-clean.xlf
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
+ <file source-language="fr-FR" target-language="en-US" datatype="plaintext" original="file.ext">
+ <header>
+ <tool tool-id="symfony" tool-name="Symfony"/>
+ </header>
+ <body>
+ <trans-unit id="LCa0a2j" resname="foo">
+ <source>foo</source>
+ <target>bar</target>
+ <note priority="1" from="bar">baz</note>
+ </trans-unit>
+ <trans-unit id="LHDhK3o" resname="key">
+ <source>key</source>
+ <target></target>
+ <note>baz</note>
+ <note>qux</note>
+ </trans-unit>
+ <trans-unit id="2DA_bnh" resname="key.with.cdata">
+ <source>key.with.cdata</source>
+ <target><![CDATA[<source> & <target>]]></target>
+ </trans-unit>
+ </body>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/resources-multi-files.xlf b/vendor/symfony/translation/Tests/fixtures/resources-multi-files.xlf
new file mode 100755
index 0000000..5f45150
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources-multi-files.xlf
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
+ <file source-language="en" datatype="plaintext" original="file.ext">
+ <body>
+ <trans-unit id="1">
+ <source>foo</source>
+ <target>bar</target>
+ </trans-unit>
+ </body>
+ </file>
+ <file source-language="en" datatype="plaintext" original="otherfile.ext">
+ <body>
+ <trans-unit id="2">
+ <source>extra</source>
+ </trans-unit>
+ <trans-unit id="3">
+ <source>key</source>
+ <target></target>
+ </trans-unit>
+ <trans-unit id="4">
+ <source>test</source>
+ <target>with</target>
+ <note>note</note>
+ </trans-unit>
+ </body>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/resources-notes-meta.xlf b/vendor/symfony/translation/Tests/fixtures/resources-notes-meta.xlf
new file mode 100755
index 0000000..7d5bbd4
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources-notes-meta.xlf
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="fr-FR" trgLang="en-US">
+ <file id="messages.en_US">
+ <unit id="LCa0a2j" name="foo">
+ <notes>
+ <note category="state">new</note>
+ <note category="approved">true</note>
+ <note category="section" priority="1">user login</note>
+ </notes>
+ <segment>
+ <source>foo</source>
+ <target>bar</target>
+ </segment>
+ </unit>
+ <unit id="uqWglk0" name="baz">
+ <notes>
+ <note id="x">x_content</note>
+ <note appliesTo="target" category="quality">Fuzzy</note>
+ </notes>
+ <segment>
+ <source>baz</source>
+ <target>biz</target>
+ </segment>
+ </unit>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/resources-target-attributes.xlf b/vendor/symfony/translation/Tests/fixtures/resources-target-attributes.xlf
new file mode 100755
index 0000000..700d281
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources-target-attributes.xlf
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
+ <file source-language="fr-FR" target-language="en-US" datatype="plaintext" original="file.ext">
+ <header>
+ <tool tool-id="symfony" tool-name="Symfony"/>
+ </header>
+ <body>
+ <trans-unit id="LCa0a2j" resname="foo">
+ <source>foo</source>
+ <target state="needs-translation">bar</target>
+ </trans-unit>
+ </body>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/resources-tool-info.xlf b/vendor/symfony/translation/Tests/fixtures/resources-tool-info.xlf
new file mode 100755
index 0000000..1c2ae95
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources-tool-info.xlf
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
+ <file source-language="en-US" target-language="en-US" datatype="plaintext" original="file.ext">
+ <header>
+ <tool tool-id="foo" tool-name="foo" tool-version="0.0" tool-company="Foo"/>
+ </header>
+ <body>
+ <trans-unit id="LCa0a2j" resname="foo">
+ <source>foo</source>
+ <target>bar</target>
+ </trans-unit>
+ </body>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/resources.csv b/vendor/symfony/translation/Tests/fixtures/resources.csv
new file mode 100755
index 0000000..374b9eb
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources.csv
@@ -0,0 +1,4 @@
+"foo"; "bar"
+#"bar"; "foo"
+"incorrect"; "number"; "columns"; "will"; "be"; "ignored"
+"incorrect"
\ No newline at end of file
diff --git a/vendor/symfony/translation/Tests/fixtures/resources.dump.json b/vendor/symfony/translation/Tests/fixtures/resources.dump.json
new file mode 100755
index 0000000..335965d
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources.dump.json
@@ -0,0 +1 @@
+{"foo":"\u0022bar\u0022"}
\ No newline at end of file
diff --git a/vendor/symfony/translation/Tests/fixtures/resources.ini b/vendor/symfony/translation/Tests/fixtures/resources.ini
new file mode 100755
index 0000000..4953062
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources.ini
@@ -0,0 +1 @@
+foo="bar"
diff --git a/vendor/symfony/translation/Tests/fixtures/resources.json b/vendor/symfony/translation/Tests/fixtures/resources.json
new file mode 100755
index 0000000..8a79687
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources.json
@@ -0,0 +1,3 @@
+{
+ "foo": "bar"
+}
\ No newline at end of file
diff --git a/vendor/symfony/translation/Tests/fixtures/resources.mo b/vendor/symfony/translation/Tests/fixtures/resources.mo
new file mode 100755
index 0000000..0a96602
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources.mo
Binary files differ
diff --git a/vendor/symfony/translation/Tests/fixtures/resources.php b/vendor/symfony/translation/Tests/fixtures/resources.php
new file mode 100755
index 0000000..c291398
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources.php
@@ -0,0 +1,5 @@
+<?php
+
+return array (
+ 'foo' => 'bar',
+);
diff --git a/vendor/symfony/translation/Tests/fixtures/resources.po b/vendor/symfony/translation/Tests/fixtures/resources.po
new file mode 100755
index 0000000..68e0f2d
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources.po
@@ -0,0 +1,24 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+
+msgid "foo"
+msgstr "bar"
+
+msgid "bar"
+msgstr "foo"
+
+# Comment 1
+# Comment 2
+#, fuzzy,another
+#: src/file_1 src/file_2:50
+msgid "foo_bar"
+msgstr "foobar"
+
+# Comment
+#, fuzzy
+#: src/file_1
+msgid "bar_foo"
+msgstr "barfoo"
diff --git a/vendor/symfony/translation/Tests/fixtures/resources.ts b/vendor/symfony/translation/Tests/fixtures/resources.ts
new file mode 100755
index 0000000..29e6a6f
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources.ts
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TS>
+ <context>
+ <name>resources</name>
+ <message>
+ <source>foo</source>
+ <translation>bar</translation>
+ </message>
+ <message>
+ <location filename="src/file_1"/>
+ <location filename="src/file_2" line="50"/>
+ <source>foo_bar</source>
+ <translation>foobar</translation>
+ </message>
+ <message>
+ <location filename="src/file_1"/>
+ <source>bar_foo</source>
+ <translation>barfoo</translation>
+ </message>
+ </context>
+</TS>
diff --git a/vendor/symfony/translation/Tests/fixtures/resources.xlf b/vendor/symfony/translation/Tests/fixtures/resources.xlf
new file mode 100755
index 0000000..b0e5988
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources.xlf
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
+ <file source-language="en" datatype="plaintext" original="file.ext">
+ <body>
+ <trans-unit id="1">
+ <source>foo</source>
+ <target>bar</target>
+ </trans-unit>
+ <trans-unit id="2">
+ <source>extra</source>
+ </trans-unit>
+ <trans-unit id="3">
+ <source>key</source>
+ <target></target>
+ </trans-unit>
+ <trans-unit id="4">
+ <source>test</source>
+ <target>with</target>
+ <note>note</note>
+ </trans-unit>
+ </body>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/resources.yml b/vendor/symfony/translation/Tests/fixtures/resources.yml
new file mode 100755
index 0000000..20e9ff3
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/resources.yml
@@ -0,0 +1 @@
+foo: bar
diff --git a/vendor/symfony/translation/Tests/fixtures/valid.csv b/vendor/symfony/translation/Tests/fixtures/valid.csv
new file mode 100755
index 0000000..59882e5
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/valid.csv
@@ -0,0 +1,4 @@
+foo;bar
+bar;"foo
+foo"
+"foo;foo";bar
diff --git a/vendor/symfony/translation/Tests/fixtures/with-attributes.xlf b/vendor/symfony/translation/Tests/fixtures/with-attributes.xlf
new file mode 100755
index 0000000..7873062
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/with-attributes.xlf
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
+ <file source-language="en" datatype="plaintext" original="file.ext">
+ <body>
+ <trans-unit id="1">
+ <source>foo</source>
+ <target state="translated">bar</target>
+ </trans-unit>
+ <trans-unit id="2">
+ <source>extra</source>
+ <target state="needs-translation">bar</target>
+ </trans-unit>
+ <trans-unit id="3">
+ <source>key</source>
+ <target></target>
+ <note>baz</note>
+ <note priority="2" from="bar">qux</note>
+ </trans-unit>
+ </body>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/withdoctype.xlf b/vendor/symfony/translation/Tests/fixtures/withdoctype.xlf
new file mode 100755
index 0000000..f83e834
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/withdoctype.xlf
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<!DOCTYPE foo>
+<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
+ <file source-language="en" datatype="plaintext" original="file.ext">
+ <body>
+ <trans-unit id="1">
+ <source>foo</source>
+ <target>bar</target>
+ </trans-unit>
+ </body>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Tests/fixtures/withnote.xlf b/vendor/symfony/translation/Tests/fixtures/withnote.xlf
new file mode 100755
index 0000000..f98cf7f
--- /dev/null
+++ b/vendor/symfony/translation/Tests/fixtures/withnote.xlf
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
+ <file source-language="en" datatype="plaintext" original="file.ext">
+ <body>
+ <trans-unit id="1">
+ <source>foo</source>
+ <target>bar</target>
+ <note priority="1">foo</note>
+ </trans-unit>
+ <trans-unit id="2" resname="extra">
+ <source>extrasource</source>
+ <note from="foo">bar</note>
+ </trans-unit>
+ <trans-unit id="123">
+ <source>key</source>
+ <target></target>
+ <note>baz</note>
+ <note priority="2" from="bar">qux</note>
+ </trans-unit>
+ </body>
+ </file>
+</xliff>
diff --git a/vendor/symfony/translation/Translator.php b/vendor/symfony/translation/Translator.php
new file mode 100755
index 0000000..9846c83
--- /dev/null
+++ b/vendor/symfony/translation/Translator.php
@@ -0,0 +1,510 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\Config\ConfigCacheFactory;
+use Symfony\Component\Config\ConfigCacheFactoryInterface;
+use Symfony\Component\Config\ConfigCacheInterface;
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\LogicException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Translation\Exception\RuntimeException;
+use Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface;
+use Symfony\Component\Translation\Formatter\IntlFormatterInterface;
+use Symfony\Component\Translation\Formatter\MessageFormatter;
+use Symfony\Component\Translation\Formatter\MessageFormatterInterface;
+use Symfony\Component\Translation\Loader\LoaderInterface;
+use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+/**
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class Translator implements LegacyTranslatorInterface, TranslatorInterface, TranslatorBagInterface
+{
+ /**
+ * @var MessageCatalogueInterface[]
+ */
+ protected $catalogues = [];
+
+ /**
+ * @var string
+ */
+ private $locale;
+
+ /**
+ * @var array
+ */
+ private $fallbackLocales = [];
+
+ /**
+ * @var LoaderInterface[]
+ */
+ private $loaders = [];
+
+ /**
+ * @var array
+ */
+ private $resources = [];
+
+ /**
+ * @var MessageFormatterInterface
+ */
+ private $formatter;
+
+ /**
+ * @var string
+ */
+ private $cacheDir;
+
+ /**
+ * @var bool
+ */
+ private $debug;
+
+ /**
+ * @var ConfigCacheFactoryInterface|null
+ */
+ private $configCacheFactory;
+
+ /**
+ * @var array|null
+ */
+ private $parentLocales;
+
+ private $hasIntlFormatter;
+
+ /**
+ * @throws InvalidArgumentException If a locale contains invalid characters
+ */
+ public function __construct(?string $locale, MessageFormatterInterface $formatter = null, string $cacheDir = null, bool $debug = false)
+ {
+ $this->setLocale($locale);
+
+ if (null === $formatter) {
+ $formatter = new MessageFormatter();
+ }
+
+ $this->formatter = $formatter;
+ $this->cacheDir = $cacheDir;
+ $this->debug = $debug;
+ $this->hasIntlFormatter = $formatter instanceof IntlFormatterInterface;
+ }
+
+ public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
+ {
+ $this->configCacheFactory = $configCacheFactory;
+ }
+
+ /**
+ * Adds a Loader.
+ *
+ * @param string $format The name of the loader (@see addResource())
+ * @param LoaderInterface $loader A LoaderInterface instance
+ */
+ public function addLoader($format, LoaderInterface $loader)
+ {
+ $this->loaders[$format] = $loader;
+ }
+
+ /**
+ * Adds a Resource.
+ *
+ * @param string $format The name of the loader (@see addLoader())
+ * @param mixed $resource The resource name
+ * @param string $locale The locale
+ * @param string $domain The domain
+ *
+ * @throws InvalidArgumentException If the locale contains invalid characters
+ */
+ public function addResource($format, $resource, $locale, $domain = null)
+ {
+ if (null === $domain) {
+ $domain = 'messages';
+ }
+
+ $this->assertValidLocale($locale);
+
+ $this->resources[$locale][] = [$format, $resource, $domain];
+
+ if (\in_array($locale, $this->fallbackLocales)) {
+ $this->catalogues = [];
+ } else {
+ unset($this->catalogues[$locale]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setLocale($locale)
+ {
+ $this->assertValidLocale($locale);
+ $this->locale = $locale;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLocale()
+ {
+ return $this->locale;
+ }
+
+ /**
+ * Sets the fallback locales.
+ *
+ * @param array $locales The fallback locales
+ *
+ * @throws InvalidArgumentException If a locale contains invalid characters
+ */
+ public function setFallbackLocales(array $locales)
+ {
+ // needed as the fallback locales are linked to the already loaded catalogues
+ $this->catalogues = [];
+
+ foreach ($locales as $locale) {
+ $this->assertValidLocale($locale);
+ }
+
+ $this->fallbackLocales = $locales;
+ }
+
+ /**
+ * Gets the fallback locales.
+ *
+ * @internal since Symfony 4.2
+ *
+ * @return array The fallback locales
+ */
+ public function getFallbackLocales()
+ {
+ return $this->fallbackLocales;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function trans($id, array $parameters = [], $domain = null, $locale = null)
+ {
+ if (null === $domain) {
+ $domain = 'messages';
+ }
+
+ $id = (string) $id;
+ $catalogue = $this->getCatalogue($locale);
+ $locale = $catalogue->getLocale();
+ while (!$catalogue->defines($id, $domain)) {
+ if ($cat = $catalogue->getFallbackCatalogue()) {
+ $catalogue = $cat;
+ $locale = $catalogue->getLocale();
+ } else {
+ break;
+ }
+ }
+
+ if ($this->hasIntlFormatter && $catalogue->defines($id, $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)) {
+ return $this->formatter->formatIntl($catalogue->get($id, $domain), $locale, $parameters);
+ }
+
+ return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter
+ */
+ public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the trans() one instead with a "%%count%%" parameter.', __METHOD__), E_USER_DEPRECATED);
+
+ if (!$this->formatter instanceof ChoiceMessageFormatterInterface) {
+ throw new LogicException(sprintf('The formatter "%s" does not support plural translations.', \get_class($this->formatter)));
+ }
+
+ if (null === $domain) {
+ $domain = 'messages';
+ }
+
+ $id = (string) $id;
+ $catalogue = $this->getCatalogue($locale);
+ $locale = $catalogue->getLocale();
+ while (!$catalogue->defines($id, $domain)) {
+ if ($cat = $catalogue->getFallbackCatalogue()) {
+ $catalogue = $cat;
+ $locale = $catalogue->getLocale();
+ } else {
+ break;
+ }
+ }
+
+ if ($this->hasIntlFormatter && $catalogue->defines($id, $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)) {
+ return $this->formatter->formatIntl($catalogue->get($id, $domain), $locale, ['%count%' => $number] + $parameters);
+ }
+
+ return $this->formatter->choiceFormat($catalogue->get($id, $domain), $number, $locale, $parameters);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCatalogue($locale = null)
+ {
+ if (null === $locale) {
+ $locale = $this->getLocale();
+ } else {
+ $this->assertValidLocale($locale);
+ }
+
+ if (!isset($this->catalogues[$locale])) {
+ $this->loadCatalogue($locale);
+ }
+
+ return $this->catalogues[$locale];
+ }
+
+ /**
+ * Gets the loaders.
+ *
+ * @return array LoaderInterface[]
+ */
+ protected function getLoaders()
+ {
+ return $this->loaders;
+ }
+
+ /**
+ * @param string $locale
+ */
+ protected function loadCatalogue($locale)
+ {
+ if (null === $this->cacheDir) {
+ $this->initializeCatalogue($locale);
+ } else {
+ $this->initializeCacheCatalogue($locale);
+ }
+ }
+
+ /**
+ * @param string $locale
+ */
+ protected function initializeCatalogue($locale)
+ {
+ $this->assertValidLocale($locale);
+
+ try {
+ $this->doLoadCatalogue($locale);
+ } catch (NotFoundResourceException $e) {
+ if (!$this->computeFallbackLocales($locale)) {
+ throw $e;
+ }
+ }
+ $this->loadFallbackCatalogues($locale);
+ }
+
+ private function initializeCacheCatalogue(string $locale): void
+ {
+ if (isset($this->catalogues[$locale])) {
+ /* Catalogue already initialized. */
+ return;
+ }
+
+ $this->assertValidLocale($locale);
+ $cache = $this->getConfigCacheFactory()->cache($this->getCatalogueCachePath($locale),
+ function (ConfigCacheInterface $cache) use ($locale) {
+ $this->dumpCatalogue($locale, $cache);
+ }
+ );
+
+ if (isset($this->catalogues[$locale])) {
+ /* Catalogue has been initialized as it was written out to cache. */
+ return;
+ }
+
+ /* Read catalogue from cache. */
+ $this->catalogues[$locale] = include $cache->getPath();
+ }
+
+ private function dumpCatalogue($locale, ConfigCacheInterface $cache): void
+ {
+ $this->initializeCatalogue($locale);
+ $fallbackContent = $this->getFallbackContent($this->catalogues[$locale]);
+
+ $content = sprintf(<<<EOF
+<?php
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+\$catalogue = new MessageCatalogue('%s', %s);
+
+%s
+return \$catalogue;
+
+EOF
+ ,
+ $locale,
+ var_export($this->getAllMessages($this->catalogues[$locale]), true),
+ $fallbackContent
+ );
+
+ $cache->write($content, $this->catalogues[$locale]->getResources());
+ }
+
+ private function getFallbackContent(MessageCatalogue $catalogue): string
+ {
+ $fallbackContent = '';
+ $current = '';
+ $replacementPattern = '/[^a-z0-9_]/i';
+ $fallbackCatalogue = $catalogue->getFallbackCatalogue();
+ while ($fallbackCatalogue) {
+ $fallback = $fallbackCatalogue->getLocale();
+ $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback));
+ $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current));
+
+ $fallbackContent .= sprintf(<<<'EOF'
+$catalogue%s = new MessageCatalogue('%s', %s);
+$catalogue%s->addFallbackCatalogue($catalogue%s);
+
+EOF
+ ,
+ $fallbackSuffix,
+ $fallback,
+ var_export($this->getAllMessages($fallbackCatalogue), true),
+ $currentSuffix,
+ $fallbackSuffix
+ );
+ $current = $fallbackCatalogue->getLocale();
+ $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
+ }
+
+ return $fallbackContent;
+ }
+
+ private function getCatalogueCachePath($locale)
+ {
+ return $this->cacheDir.'/catalogue.'.$locale.'.'.strtr(substr(base64_encode(hash('sha256', serialize($this->fallbackLocales), true)), 0, 7), '/', '_').'.php';
+ }
+
+ /**
+ * @internal
+ */
+ protected function doLoadCatalogue($locale): void
+ {
+ $this->catalogues[$locale] = new MessageCatalogue($locale);
+
+ if (isset($this->resources[$locale])) {
+ foreach ($this->resources[$locale] as $resource) {
+ if (!isset($this->loaders[$resource[0]])) {
+ throw new RuntimeException(sprintf('The "%s" translation loader is not registered.', $resource[0]));
+ }
+ $this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2]));
+ }
+ }
+ }
+
+ private function loadFallbackCatalogues($locale): void
+ {
+ $current = $this->catalogues[$locale];
+
+ foreach ($this->computeFallbackLocales($locale) as $fallback) {
+ if (!isset($this->catalogues[$fallback])) {
+ $this->initializeCatalogue($fallback);
+ }
+
+ $fallbackCatalogue = new MessageCatalogue($fallback, $this->getAllMessages($this->catalogues[$fallback]));
+ foreach ($this->catalogues[$fallback]->getResources() as $resource) {
+ $fallbackCatalogue->addResource($resource);
+ }
+ $current->addFallbackCatalogue($fallbackCatalogue);
+ $current = $fallbackCatalogue;
+ }
+ }
+
+ protected function computeFallbackLocales($locale)
+ {
+ if (null === $this->parentLocales) {
+ $parentLocales = json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true);
+ }
+
+ $locales = [];
+ foreach ($this->fallbackLocales as $fallback) {
+ if ($fallback === $locale) {
+ continue;
+ }
+
+ $locales[] = $fallback;
+ }
+
+ while ($locale) {
+ $parent = $parentLocales[$locale] ?? null;
+
+ if (!$parent && false !== strrchr($locale, '_')) {
+ $locale = substr($locale, 0, -\strlen(strrchr($locale, '_')));
+ } elseif ('root' !== $parent) {
+ $locale = $parent;
+ } else {
+ $locale = null;
+ }
+
+ if (null !== $locale) {
+ array_unshift($locales, $locale);
+ }
+ }
+
+ return array_unique($locales);
+ }
+
+ /**
+ * Asserts that the locale is valid, throws an Exception if not.
+ *
+ * @param string $locale Locale to tests
+ *
+ * @throws InvalidArgumentException If the locale contains invalid characters
+ */
+ protected function assertValidLocale($locale)
+ {
+ if (1 !== preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) {
+ throw new InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale));
+ }
+ }
+
+ /**
+ * Provides the ConfigCache factory implementation, falling back to a
+ * default implementation if necessary.
+ */
+ private function getConfigCacheFactory(): ConfigCacheFactoryInterface
+ {
+ if (!$this->configCacheFactory) {
+ $this->configCacheFactory = new ConfigCacheFactory($this->debug);
+ }
+
+ return $this->configCacheFactory;
+ }
+
+ private function getAllMessages(MessageCatalogueInterface $catalogue): array
+ {
+ $allMessages = [];
+
+ foreach ($catalogue->all() as $domain => $messages) {
+ if ($intlMessages = $catalogue->all($domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)) {
+ $allMessages[$domain.MessageCatalogue::INTL_DOMAIN_SUFFIX] = $intlMessages;
+ $messages = array_diff_key($messages, $intlMessages);
+ }
+ if ($messages) {
+ $allMessages[$domain] = $messages;
+ }
+ }
+
+ return $allMessages;
+ }
+}
diff --git a/vendor/symfony/translation/TranslatorBagInterface.php b/vendor/symfony/translation/TranslatorBagInterface.php
new file mode 100755
index 0000000..5e49e2d
--- /dev/null
+++ b/vendor/symfony/translation/TranslatorBagInterface.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+
+/**
+ * TranslatorBagInterface.
+ *
+ * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
+ */
+interface TranslatorBagInterface
+{
+ /**
+ * Gets the catalogue by locale.
+ *
+ * @param string|null $locale The locale or null to use the default
+ *
+ * @return MessageCatalogueInterface
+ *
+ * @throws InvalidArgumentException If the locale contains invalid characters
+ */
+ public function getCatalogue($locale = null);
+}
diff --git a/vendor/symfony/translation/TranslatorInterface.php b/vendor/symfony/translation/TranslatorInterface.php
new file mode 100755
index 0000000..f677d24
--- /dev/null
+++ b/vendor/symfony/translation/TranslatorInterface.php
@@ -0,0 +1,70 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Contracts\Translation\LocaleAwareInterface;
+
+/**
+ * TranslatorInterface.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @deprecated since Symfony 4.2, use Symfony\Contracts\Translation\TranslatorInterface instead
+ */
+interface TranslatorInterface extends LocaleAwareInterface
+{
+ /**
+ * Translates the given message.
+ *
+ * @param string $id The message id (may also be an object that can be cast to string)
+ * @param array $parameters An array of parameters for the message
+ * @param string|null $domain The domain for the message or null to use the default
+ * @param string|null $locale The locale or null to use the default
+ *
+ * @return string The translated string
+ *
+ * @throws InvalidArgumentException If the locale contains invalid characters
+ */
+ public function trans($id, array $parameters = [], $domain = null, $locale = null);
+
+ /**
+ * Translates the given choice message by choosing a translation according to a number.
+ *
+ * @param string $id The message id (may also be an object that can be cast to string)
+ * @param int $number The number to use to find the index of the message
+ * @param array $parameters An array of parameters for the message
+ * @param string|null $domain The domain for the message or null to use the default
+ * @param string|null $locale The locale or null to use the default
+ *
+ * @return string The translated string
+ *
+ * @throws InvalidArgumentException If the locale contains invalid characters
+ */
+ public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null);
+
+ /**
+ * Sets the current locale.
+ *
+ * @param string $locale The locale
+ *
+ * @throws InvalidArgumentException If the locale contains invalid characters
+ */
+ public function setLocale($locale);
+
+ /**
+ * Returns the current locale.
+ *
+ * @return string The locale
+ */
+ public function getLocale();
+}
diff --git a/vendor/symfony/translation/Util/ArrayConverter.php b/vendor/symfony/translation/Util/ArrayConverter.php
new file mode 100755
index 0000000..22c602e
--- /dev/null
+++ b/vendor/symfony/translation/Util/ArrayConverter.php
@@ -0,0 +1,99 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Util;
+
+/**
+ * ArrayConverter generates tree like structure from a message catalogue.
+ * e.g. this
+ * 'foo.bar1' => 'test1',
+ * 'foo.bar2' => 'test2'
+ * converts to follows:
+ * foo:
+ * bar1: test1
+ * bar2: test2.
+ *
+ * @author Gennady Telegin <gtelegin@gmail.com>
+ */
+class ArrayConverter
+{
+ /**
+ * Converts linear messages array to tree-like array.
+ * For example this array('foo.bar' => 'value') will be converted to ['foo' => ['bar' => 'value']].
+ *
+ * @param array $messages Linear messages array
+ *
+ * @return array Tree-like messages array
+ */
+ public static function expandToTree(array $messages)
+ {
+ $tree = [];
+
+ foreach ($messages as $id => $value) {
+ $referenceToElement = &self::getElementByPath($tree, explode('.', $id));
+
+ $referenceToElement = $value;
+
+ unset($referenceToElement);
+ }
+
+ return $tree;
+ }
+
+ private static function &getElementByPath(array &$tree, array $parts)
+ {
+ $elem = &$tree;
+ $parentOfElem = null;
+
+ foreach ($parts as $i => $part) {
+ if (isset($elem[$part]) && \is_string($elem[$part])) {
+ /* Process next case:
+ * 'foo': 'test1',
+ * 'foo.bar': 'test2'
+ *
+ * $tree['foo'] was string before we found array {bar: test2}.
+ * Treat new element as string too, e.g. add $tree['foo.bar'] = 'test2';
+ */
+ $elem = &$elem[implode('.', \array_slice($parts, $i))];
+ break;
+ }
+ $parentOfElem = &$elem;
+ $elem = &$elem[$part];
+ }
+
+ if ($elem && \is_array($elem) && $parentOfElem) {
+ /* Process next case:
+ * 'foo.bar': 'test1'
+ * 'foo': 'test2'
+ *
+ * $tree['foo'] was array = {bar: 'test1'} before we found string constant `foo`.
+ * Cancel treating $tree['foo'] as array and cancel back it expansion,
+ * e.g. make it $tree['foo.bar'] = 'test1' again.
+ */
+ self::cancelExpand($parentOfElem, $part, $elem);
+ }
+
+ return $elem;
+ }
+
+ private static function cancelExpand(array &$tree, $prefix, array $node)
+ {
+ $prefix .= '.';
+
+ foreach ($node as $id => $value) {
+ if (\is_string($value)) {
+ $tree[$prefix.$id] = $value;
+ } else {
+ self::cancelExpand($tree, $prefix.$id, $value);
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Util/XliffUtils.php b/vendor/symfony/translation/Util/XliffUtils.php
new file mode 100755
index 0000000..3ace285
--- /dev/null
+++ b/vendor/symfony/translation/Util/XliffUtils.php
@@ -0,0 +1,163 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Util;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+
+/**
+ * Provides some utility methods for XLIFF translation files, such as validating
+ * their contents according to the XSD schema.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class XliffUtils
+{
+ /**
+ * Gets xliff file version based on the root "version" attribute.
+ *
+ * Defaults to 1.2 for backwards compatibility.
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function getVersionNumber(\DOMDocument $dom): string
+ {
+ /** @var \DOMNode $xliff */
+ foreach ($dom->getElementsByTagName('xliff') as $xliff) {
+ $version = $xliff->attributes->getNamedItem('version');
+ if ($version) {
+ return $version->nodeValue;
+ }
+
+ $namespace = $xliff->attributes->getNamedItem('xmlns');
+ if ($namespace) {
+ if (0 !== substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34)) {
+ throw new InvalidArgumentException(sprintf('Not a valid XLIFF namespace "%s"', $namespace));
+ }
+
+ return substr($namespace, 34);
+ }
+ }
+
+ // Falls back to v1.2
+ return '1.2';
+ }
+
+ /**
+ * Validates and parses the given file into a DOMDocument.
+ *
+ * @throws InvalidResourceException
+ */
+ public static function validateSchema(\DOMDocument $dom): array
+ {
+ $xliffVersion = static::getVersionNumber($dom);
+ $internalErrors = libxml_use_internal_errors(true);
+ $disableEntities = libxml_disable_entity_loader(false);
+
+ $isValid = @$dom->schemaValidateSource(self::getSchema($xliffVersion));
+ if (!$isValid) {
+ libxml_disable_entity_loader($disableEntities);
+
+ return self::getXmlErrors($internalErrors);
+ }
+
+ libxml_disable_entity_loader($disableEntities);
+
+ $dom->normalizeDocument();
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internalErrors);
+
+ return [];
+ }
+
+ public static function getErrorsAsString(array $xmlErrors): string
+ {
+ $errorsAsString = '';
+
+ foreach ($xmlErrors as $error) {
+ $errorsAsString .= sprintf("[%s %s] %s (in %s - line %d, column %d)\n",
+ LIBXML_ERR_WARNING === $error['level'] ? 'WARNING' : 'ERROR',
+ $error['code'],
+ $error['message'],
+ $error['file'],
+ $error['line'],
+ $error['column']
+ );
+ }
+
+ return $errorsAsString;
+ }
+
+ private static function getSchema(string $xliffVersion): string
+ {
+ if ('1.2' === $xliffVersion) {
+ $schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-1.2-strict.xsd');
+ $xmlUri = 'http://www.w3.org/2001/xml.xsd';
+ } elseif ('2.0' === $xliffVersion) {
+ $schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-2.0.xsd');
+ $xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd';
+ } else {
+ throw new InvalidArgumentException(sprintf('No support implemented for loading XLIFF version "%s".', $xliffVersion));
+ }
+
+ return self::fixXmlLocation($schemaSource, $xmlUri);
+ }
+
+ /**
+ * Internally changes the URI of a dependent xsd to be loaded locally.
+ */
+ private static function fixXmlLocation(string $schemaSource, string $xmlUri): string
+ {
+ $newPath = str_replace('\\', '/', __DIR__).'/../Resources/schemas/xml.xsd';
+ $parts = explode('/', $newPath);
+ $locationstart = 'file:///';
+ if (0 === stripos($newPath, 'phar://')) {
+ $tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
+ if ($tmpfile) {
+ copy($newPath, $tmpfile);
+ $parts = explode('/', str_replace('\\', '/', $tmpfile));
+ } else {
+ array_shift($parts);
+ $locationstart = 'phar:///';
+ }
+ }
+
+ $drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : '';
+ $newPath = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts));
+
+ return str_replace($xmlUri, $newPath, $schemaSource);
+ }
+
+ /**
+ * Returns the XML errors of the internal XML parser.
+ */
+ private static function getXmlErrors(bool $internalErrors): array
+ {
+ $errors = [];
+ foreach (libxml_get_errors() as $error) {
+ $errors[] = [
+ 'level' => LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
+ 'code' => $error->code,
+ 'message' => trim($error->message),
+ 'file' => $error->file ?: 'n/a',
+ 'line' => $error->line,
+ 'column' => $error->column,
+ ];
+ }
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internalErrors);
+
+ return $errors;
+ }
+}
diff --git a/vendor/symfony/translation/Writer/TranslationWriter.php b/vendor/symfony/translation/Writer/TranslationWriter.php
new file mode 100755
index 0000000..a44d24c
--- /dev/null
+++ b/vendor/symfony/translation/Writer/TranslationWriter.php
@@ -0,0 +1,90 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Writer;
+
+use Symfony\Component\Translation\Dumper\DumperInterface;
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\RuntimeException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * TranslationWriter writes translation messages.
+ *
+ * @author Michel Salib <michelsalib@hotmail.com>
+ */
+class TranslationWriter implements TranslationWriterInterface
+{
+ private $dumpers = [];
+
+ /**
+ * Adds a dumper to the writer.
+ *
+ * @param string $format The format of the dumper
+ * @param DumperInterface $dumper The dumper
+ */
+ public function addDumper($format, DumperInterface $dumper)
+ {
+ $this->dumpers[$format] = $dumper;
+ }
+
+ /**
+ * Disables dumper backup.
+ *
+ * @deprecated since Symfony 4.1
+ */
+ public function disableBackup()
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1.', __METHOD__), E_USER_DEPRECATED);
+
+ foreach ($this->dumpers as $dumper) {
+ if (method_exists($dumper, 'setBackup')) {
+ $dumper->setBackup(false);
+ }
+ }
+ }
+
+ /**
+ * Obtains the list of supported formats.
+ *
+ * @return array
+ */
+ public function getFormats()
+ {
+ return array_keys($this->dumpers);
+ }
+
+ /**
+ * Writes translation from the catalogue according to the selected format.
+ *
+ * @param MessageCatalogue $catalogue The message catalogue to write
+ * @param string $format The format to use to dump the messages
+ * @param array $options Options that are passed to the dumper
+ *
+ * @throws InvalidArgumentException
+ */
+ public function write(MessageCatalogue $catalogue, $format, $options = [])
+ {
+ if (!isset($this->dumpers[$format])) {
+ throw new InvalidArgumentException(sprintf('There is no dumper associated with format "%s".', $format));
+ }
+
+ // get the right dumper
+ $dumper = $this->dumpers[$format];
+
+ if (isset($options['path']) && !is_dir($options['path']) && !@mkdir($options['path'], 0777, true) && !is_dir($options['path'])) {
+ throw new RuntimeException(sprintf('Translation Writer was not able to create directory "%s"', $options['path']));
+ }
+
+ // save
+ $dumper->dump($catalogue, $options);
+ }
+}
diff --git a/vendor/symfony/translation/Writer/TranslationWriterInterface.php b/vendor/symfony/translation/Writer/TranslationWriterInterface.php
new file mode 100755
index 0000000..b07c08e
--- /dev/null
+++ b/vendor/symfony/translation/Writer/TranslationWriterInterface.php
@@ -0,0 +1,34 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Writer;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * TranslationWriter writes translation messages.
+ *
+ * @author Michel Salib <michelsalib@hotmail.com>
+ */
+interface TranslationWriterInterface
+{
+ /**
+ * Writes translation from the catalogue according to the selected format.
+ *
+ * @param MessageCatalogue $catalogue The message catalogue to write
+ * @param string $format The format to use to dump the messages
+ * @param array $options Options that are passed to the dumper
+ *
+ * @throws InvalidArgumentException
+ */
+ public function write(MessageCatalogue $catalogue, $format, $options = []);
+}
diff --git a/vendor/symfony/translation/composer.json b/vendor/symfony/translation/composer.json
new file mode 100755
index 0000000..fd25a4c
--- /dev/null
+++ b/vendor/symfony/translation/composer.json
@@ -0,0 +1,60 @@
+{
+ "name": "symfony/translation",
+ "type": "library",
+ "description": "Symfony Translation Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^7.1.3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/translation-contracts": "^1.1.2"
+ },
+ "require-dev": {
+ "symfony/config": "~3.4|~4.0",
+ "symfony/console": "~3.4|~4.0",
+ "symfony/dependency-injection": "~3.4|~4.0",
+ "symfony/http-kernel": "~3.4|~4.0",
+ "symfony/intl": "~3.4|~4.0",
+ "symfony/service-contracts": "^1.1.2",
+ "symfony/var-dumper": "~3.4|~4.0",
+ "symfony/yaml": "~3.4|~4.0",
+ "symfony/finder": "~2.8|~3.0|~4.0",
+ "psr/log": "~1.0"
+ },
+ "conflict": {
+ "symfony/config": "<3.4",
+ "symfony/dependency-injection": "<3.4",
+ "symfony/yaml": "<3.4"
+ },
+ "provide": {
+ "symfony/translation-implementation": "1.0"
+ },
+ "suggest": {
+ "symfony/config": "",
+ "symfony/yaml": "",
+ "psr/log-implementation": "To use logging capability in translator"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Translation\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.3-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/translation/phpunit.xml.dist b/vendor/symfony/translation/phpunit.xml.dist
new file mode 100755
index 0000000..21d3246
--- /dev/null
+++ b/vendor/symfony/translation/phpunit.xml.dist
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
+ backupGlobals="false"
+ colors="true"
+ bootstrap="vendor/autoload.php"
+ failOnRisky="true"
+ failOnWarning="true"
+>
+ <php>
+ <ini name="error_reporting" value="-1" />
+ </php>
+
+ <testsuites>
+ <testsuite name="Symfony Translation Component Test Suite">
+ <directory>./Tests/</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>./</directory>
+ <exclude>
+ <directory>./Tests</directory>
+ <directory>./vendor</directory>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/vendor/symfony/var-dumper/.gitignore b/vendor/symfony/var-dumper/.gitignore
new file mode 100755
index 0000000..5414c2c
--- /dev/null
+++ b/vendor/symfony/var-dumper/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+phpunit.xml
+vendor/
diff --git a/vendor/symfony/var-dumper/CHANGELOG.md b/vendor/symfony/var-dumper/CHANGELOG.md
new file mode 100755
index 0000000..5f88029
--- /dev/null
+++ b/vendor/symfony/var-dumper/CHANGELOG.md
@@ -0,0 +1,42 @@
+CHANGELOG
+=========
+
+4.3.0
+-----
+
+ * added `DsCaster` to support dumping the contents of data structures from the Ds extension
+
+4.2.0
+-----
+
+ * support selecting the format to use by setting the environment variable `VAR_DUMPER_FORMAT` to `html` or `cli`
+
+4.1.0
+-----
+
+ * added a `ServerDumper` to send serialized Data clones to a server
+ * added a `ServerDumpCommand` and `DumpServer` to run a server collecting
+ and displaying dumps on a single place with multiple formats support
+ * added `CliDescriptor` and `HtmlDescriptor` descriptors for `server:dump` CLI and HTML formats support
+
+4.0.0
+-----
+
+ * support for passing `\ReflectionClass` instances to the `Caster::castObject()`
+ method has been dropped, pass class names as strings instead
+ * the `Data::getRawData()` method has been removed
+ * the `VarDumperTestTrait::assertDumpEquals()` method expects a 3rd `$context = null`
+ argument and moves `$message = ''` argument at 4th position.
+ * the `VarDumperTestTrait::assertDumpMatchesFormat()` method expects a 3rd `$context = null`
+ argument and moves `$message = ''` argument at 4th position.
+
+3.4.0
+-----
+
+ * added `AbstractCloner::setMinDepth()` function to ensure minimum tree depth
+ * deprecated `MongoCaster`
+
+2.7.0
+-----
+
+ * deprecated `Cloner\Data::getLimitedClone()`. Use `withMaxDepth`, `withMaxItemsPerDepth` or `withRefHandles` instead.
diff --git a/vendor/symfony/var-dumper/Caster/AmqpCaster.php b/vendor/symfony/var-dumper/Caster/AmqpCaster.php
new file mode 100755
index 0000000..19bdc29
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/AmqpCaster.php
@@ -0,0 +1,210 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts Amqp related classes to array representation.
+ *
+ * @author Grégoire Pineau <lyrixx@lyrixx.info>
+ */
+class AmqpCaster
+{
+ private static $flags = [
+ AMQP_DURABLE => 'AMQP_DURABLE',
+ AMQP_PASSIVE => 'AMQP_PASSIVE',
+ AMQP_EXCLUSIVE => 'AMQP_EXCLUSIVE',
+ AMQP_AUTODELETE => 'AMQP_AUTODELETE',
+ AMQP_INTERNAL => 'AMQP_INTERNAL',
+ AMQP_NOLOCAL => 'AMQP_NOLOCAL',
+ AMQP_AUTOACK => 'AMQP_AUTOACK',
+ AMQP_IFEMPTY => 'AMQP_IFEMPTY',
+ AMQP_IFUNUSED => 'AMQP_IFUNUSED',
+ AMQP_MANDATORY => 'AMQP_MANDATORY',
+ AMQP_IMMEDIATE => 'AMQP_IMMEDIATE',
+ AMQP_MULTIPLE => 'AMQP_MULTIPLE',
+ AMQP_NOWAIT => 'AMQP_NOWAIT',
+ AMQP_REQUEUE => 'AMQP_REQUEUE',
+ ];
+
+ private static $exchangeTypes = [
+ AMQP_EX_TYPE_DIRECT => 'AMQP_EX_TYPE_DIRECT',
+ AMQP_EX_TYPE_FANOUT => 'AMQP_EX_TYPE_FANOUT',
+ AMQP_EX_TYPE_TOPIC => 'AMQP_EX_TYPE_TOPIC',
+ AMQP_EX_TYPE_HEADERS => 'AMQP_EX_TYPE_HEADERS',
+ ];
+
+ public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ $a += [
+ $prefix.'is_connected' => $c->isConnected(),
+ ];
+
+ // Recent version of the extension already expose private properties
+ if (isset($a["\x00AMQPConnection\x00login"])) {
+ return $a;
+ }
+
+ // BC layer in the amqp lib
+ if (method_exists($c, 'getReadTimeout')) {
+ $timeout = $c->getReadTimeout();
+ } else {
+ $timeout = $c->getTimeout();
+ }
+
+ $a += [
+ $prefix.'is_connected' => $c->isConnected(),
+ $prefix.'login' => $c->getLogin(),
+ $prefix.'password' => $c->getPassword(),
+ $prefix.'host' => $c->getHost(),
+ $prefix.'vhost' => $c->getVhost(),
+ $prefix.'port' => $c->getPort(),
+ $prefix.'read_timeout' => $timeout,
+ ];
+
+ return $a;
+ }
+
+ public static function castChannel(\AMQPChannel $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ $a += [
+ $prefix.'is_connected' => $c->isConnected(),
+ $prefix.'channel_id' => $c->getChannelId(),
+ ];
+
+ // Recent version of the extension already expose private properties
+ if (isset($a["\x00AMQPChannel\x00connection"])) {
+ return $a;
+ }
+
+ $a += [
+ $prefix.'connection' => $c->getConnection(),
+ $prefix.'prefetch_size' => $c->getPrefetchSize(),
+ $prefix.'prefetch_count' => $c->getPrefetchCount(),
+ ];
+
+ return $a;
+ }
+
+ public static function castQueue(\AMQPQueue $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ $a += [
+ $prefix.'flags' => self::extractFlags($c->getFlags()),
+ ];
+
+ // Recent version of the extension already expose private properties
+ if (isset($a["\x00AMQPQueue\x00name"])) {
+ return $a;
+ }
+
+ $a += [
+ $prefix.'connection' => $c->getConnection(),
+ $prefix.'channel' => $c->getChannel(),
+ $prefix.'name' => $c->getName(),
+ $prefix.'arguments' => $c->getArguments(),
+ ];
+
+ return $a;
+ }
+
+ public static function castExchange(\AMQPExchange $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ $a += [
+ $prefix.'flags' => self::extractFlags($c->getFlags()),
+ ];
+
+ $type = isset(self::$exchangeTypes[$c->getType()]) ? new ConstStub(self::$exchangeTypes[$c->getType()], $c->getType()) : $c->getType();
+
+ // Recent version of the extension already expose private properties
+ if (isset($a["\x00AMQPExchange\x00name"])) {
+ $a["\x00AMQPExchange\x00type"] = $type;
+
+ return $a;
+ }
+
+ $a += [
+ $prefix.'connection' => $c->getConnection(),
+ $prefix.'channel' => $c->getChannel(),
+ $prefix.'name' => $c->getName(),
+ $prefix.'type' => $type,
+ $prefix.'arguments' => $c->getArguments(),
+ ];
+
+ return $a;
+ }
+
+ public static function castEnvelope(\AMQPEnvelope $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ $deliveryMode = new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode());
+
+ // Recent version of the extension already expose private properties
+ if (isset($a["\x00AMQPEnvelope\x00body"])) {
+ $a["\0AMQPEnvelope\0delivery_mode"] = $deliveryMode;
+
+ return $a;
+ }
+
+ if (!($filter & Caster::EXCLUDE_VERBOSE)) {
+ $a += [$prefix.'body' => $c->getBody()];
+ }
+
+ $a += [
+ $prefix.'delivery_tag' => $c->getDeliveryTag(),
+ $prefix.'is_redelivery' => $c->isRedelivery(),
+ $prefix.'exchange_name' => $c->getExchangeName(),
+ $prefix.'routing_key' => $c->getRoutingKey(),
+ $prefix.'content_type' => $c->getContentType(),
+ $prefix.'content_encoding' => $c->getContentEncoding(),
+ $prefix.'headers' => $c->getHeaders(),
+ $prefix.'delivery_mode' => $deliveryMode,
+ $prefix.'priority' => $c->getPriority(),
+ $prefix.'correlation_id' => $c->getCorrelationId(),
+ $prefix.'reply_to' => $c->getReplyTo(),
+ $prefix.'expiration' => $c->getExpiration(),
+ $prefix.'message_id' => $c->getMessageId(),
+ $prefix.'timestamp' => $c->getTimeStamp(),
+ $prefix.'type' => $c->getType(),
+ $prefix.'user_id' => $c->getUserId(),
+ $prefix.'app_id' => $c->getAppId(),
+ ];
+
+ return $a;
+ }
+
+ private static function extractFlags($flags)
+ {
+ $flagsArray = [];
+
+ foreach (self::$flags as $value => $name) {
+ if ($flags & $value) {
+ $flagsArray[] = $name;
+ }
+ }
+
+ if (!$flagsArray) {
+ $flagsArray = ['AMQP_NOPARAM'];
+ }
+
+ return new ConstStub(implode('|', $flagsArray), $flags);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ArgsStub.php b/vendor/symfony/var-dumper/Caster/ArgsStub.php
new file mode 100755
index 0000000..1ca7a86
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ArgsStub.php
@@ -0,0 +1,80 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Represents a list of function arguments.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class ArgsStub extends EnumStub
+{
+ private static $parameters = [];
+
+ public function __construct(array $args, string $function, ?string $class)
+ {
+ list($variadic, $params) = self::getParameters($function, $class);
+
+ $values = [];
+ foreach ($args as $k => $v) {
+ $values[$k] = !is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v;
+ }
+ if (null === $params) {
+ parent::__construct($values, false);
+
+ return;
+ }
+ if (\count($values) < \count($params)) {
+ $params = \array_slice($params, 0, \count($values));
+ } elseif (\count($values) > \count($params)) {
+ $values[] = new EnumStub(array_splice($values, \count($params)), false);
+ $params[] = $variadic;
+ }
+ if (['...'] === $params) {
+ $this->dumpKeys = false;
+ $this->value = $values[0]->value;
+ } else {
+ $this->value = array_combine($params, $values);
+ }
+ }
+
+ private static function getParameters($function, $class)
+ {
+ if (isset(self::$parameters[$k = $class.'::'.$function])) {
+ return self::$parameters[$k];
+ }
+
+ try {
+ $r = null !== $class ? new \ReflectionMethod($class, $function) : new \ReflectionFunction($function);
+ } catch (\ReflectionException $e) {
+ return [null, null];
+ }
+
+ $variadic = '...';
+ $params = [];
+ foreach ($r->getParameters() as $v) {
+ $k = '$'.$v->name;
+ if ($v->isPassedByReference()) {
+ $k = '&'.$k;
+ }
+ if ($v->isVariadic()) {
+ $variadic .= $k;
+ } else {
+ $params[] = $k;
+ }
+ }
+
+ return self::$parameters[$k] = [$variadic, $params];
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/Caster.php b/vendor/symfony/var-dumper/Caster/Caster.php
new file mode 100755
index 0000000..292bd67
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/Caster.php
@@ -0,0 +1,157 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Helper for filtering out properties in casters.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ *
+ * @final
+ */
+class Caster
+{
+ const EXCLUDE_VERBOSE = 1;
+ const EXCLUDE_VIRTUAL = 2;
+ const EXCLUDE_DYNAMIC = 4;
+ const EXCLUDE_PUBLIC = 8;
+ const EXCLUDE_PROTECTED = 16;
+ const EXCLUDE_PRIVATE = 32;
+ const EXCLUDE_NULL = 64;
+ const EXCLUDE_EMPTY = 128;
+ const EXCLUDE_NOT_IMPORTANT = 256;
+ const EXCLUDE_STRICT = 512;
+
+ const PREFIX_VIRTUAL = "\0~\0";
+ const PREFIX_DYNAMIC = "\0+\0";
+ const PREFIX_PROTECTED = "\0*\0";
+
+ /**
+ * Casts objects to arrays and adds the dynamic property prefix.
+ *
+ * @param object $obj The object to cast
+ * @param string $class The class of the object
+ * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not
+ *
+ * @return array The array-cast of the object, with prefixed dynamic properties
+ */
+ public static function castObject($obj, $class, $hasDebugInfo = false)
+ {
+ if ($hasDebugInfo) {
+ $a = $obj->__debugInfo();
+ } elseif ($obj instanceof \Closure) {
+ $a = [];
+ } else {
+ $a = (array) $obj;
+ }
+ if ($obj instanceof \__PHP_Incomplete_Class) {
+ return $a;
+ }
+
+ if ($a) {
+ static $publicProperties = [];
+
+ $i = 0;
+ $prefixedKeys = [];
+ foreach ($a as $k => $v) {
+ if (isset($k[0]) ? "\0" !== $k[0] : \PHP_VERSION_ID >= 70200) {
+ if (!isset($publicProperties[$class])) {
+ foreach (get_class_vars($class) as $prop => $v) {
+ $publicProperties[$class][$prop] = true;
+ }
+ }
+ if (!isset($publicProperties[$class][$k])) {
+ $prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k;
+ }
+ } elseif (isset($k[16]) && "\0" === $k[16] && 0 === strpos($k, "\0class@anonymous\0")) {
+ $prefixedKeys[$i] = "\0".get_parent_class($class).'@anonymous'.strrchr($k, "\0");
+ }
+ ++$i;
+ }
+ if ($prefixedKeys) {
+ $keys = array_keys($a);
+ foreach ($prefixedKeys as $i => $k) {
+ $keys[$i] = $k;
+ }
+ $a = array_combine($keys, $a);
+ }
+ }
+
+ return $a;
+ }
+
+ /**
+ * Filters out the specified properties.
+ *
+ * By default, a single match in the $filter bit field filters properties out, following an "or" logic.
+ * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed.
+ *
+ * @param array $a The array containing the properties to filter
+ * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out
+ * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set
+ * @param int &$count Set to the number of removed properties
+ *
+ * @return array The filtered array
+ */
+ public static function filter(array $a, $filter, array $listedProperties = [], &$count = 0)
+ {
+ $count = 0;
+
+ foreach ($a as $k => $v) {
+ $type = self::EXCLUDE_STRICT & $filter;
+
+ if (null === $v) {
+ $type |= self::EXCLUDE_NULL & $filter;
+ $type |= self::EXCLUDE_EMPTY & $filter;
+ } elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || [] === $v) {
+ $type |= self::EXCLUDE_EMPTY & $filter;
+ }
+ if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !\in_array($k, $listedProperties, true)) {
+ $type |= self::EXCLUDE_NOT_IMPORTANT;
+ }
+ if ((self::EXCLUDE_VERBOSE & $filter) && \in_array($k, $listedProperties, true)) {
+ $type |= self::EXCLUDE_VERBOSE;
+ }
+
+ if (!isset($k[1]) || "\0" !== $k[0]) {
+ $type |= self::EXCLUDE_PUBLIC & $filter;
+ } elseif ('~' === $k[1]) {
+ $type |= self::EXCLUDE_VIRTUAL & $filter;
+ } elseif ('+' === $k[1]) {
+ $type |= self::EXCLUDE_DYNAMIC & $filter;
+ } elseif ('*' === $k[1]) {
+ $type |= self::EXCLUDE_PROTECTED & $filter;
+ } else {
+ $type |= self::EXCLUDE_PRIVATE & $filter;
+ }
+
+ if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) {
+ unset($a[$k]);
+ ++$count;
+ }
+ }
+
+ return $a;
+ }
+
+ public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, $isNested)
+ {
+ if (isset($a['__PHP_Incomplete_Class_Name'])) {
+ $stub->class .= '('.$a['__PHP_Incomplete_Class_Name'].')';
+ unset($a['__PHP_Incomplete_Class_Name']);
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ClassStub.php b/vendor/symfony/var-dumper/Caster/ClassStub.php
new file mode 100755
index 0000000..b655ae9
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ClassStub.php
@@ -0,0 +1,106 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Represents a PHP class identifier.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class ClassStub extends ConstStub
+{
+ /**
+ * @param string $identifier A PHP identifier, e.g. a class, method, interface, etc. name
+ * @param callable $callable The callable targeted by the identifier when it is ambiguous or not a real PHP identifier
+ */
+ public function __construct(string $identifier, $callable = null)
+ {
+ $this->value = $identifier;
+
+ try {
+ if (null !== $callable) {
+ if ($callable instanceof \Closure) {
+ $r = new \ReflectionFunction($callable);
+ } elseif (\is_object($callable)) {
+ $r = [$callable, '__invoke'];
+ } elseif (\is_array($callable)) {
+ $r = $callable;
+ } elseif (false !== $i = strpos($callable, '::')) {
+ $r = [substr($callable, 0, $i), substr($callable, 2 + $i)];
+ } else {
+ $r = new \ReflectionFunction($callable);
+ }
+ } elseif (0 < $i = strpos($identifier, '::') ?: strpos($identifier, '->')) {
+ $r = [substr($identifier, 0, $i), substr($identifier, 2 + $i)];
+ } else {
+ $r = new \ReflectionClass($identifier);
+ }
+
+ if (\is_array($r)) {
+ try {
+ $r = new \ReflectionMethod($r[0], $r[1]);
+ } catch (\ReflectionException $e) {
+ $r = new \ReflectionClass($r[0]);
+ }
+ }
+
+ if (false !== strpos($identifier, "class@anonymous\0")) {
+ $this->value = $identifier = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) {
+ return \class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
+ }, $identifier);
+ }
+
+ if (null !== $callable && $r instanceof \ReflectionFunctionAbstract) {
+ $s = ReflectionCaster::castFunctionAbstract($r, [], new Stub(), true);
+ $s = ReflectionCaster::getSignature($s);
+
+ if ('()' === substr($identifier, -2)) {
+ $this->value = substr_replace($identifier, $s, -2);
+ } else {
+ $this->value .= $s;
+ }
+ }
+ } catch (\ReflectionException $e) {
+ return;
+ } finally {
+ if (0 < $i = strrpos($this->value, '\\')) {
+ $this->attr['ellipsis'] = \strlen($this->value) - $i;
+ $this->attr['ellipsis-type'] = 'class';
+ $this->attr['ellipsis-tail'] = 1;
+ }
+ }
+
+ if ($f = $r->getFileName()) {
+ $this->attr['file'] = $f;
+ $this->attr['line'] = $r->getStartLine();
+ }
+ }
+
+ public static function wrapCallable($callable)
+ {
+ if (\is_object($callable) || !\is_callable($callable)) {
+ return $callable;
+ }
+
+ if (!\is_array($callable)) {
+ $callable = new static($callable, $callable);
+ } elseif (\is_string($callable[0])) {
+ $callable[0] = new static($callable[0], $callable);
+ } else {
+ $callable[1] = new static($callable[1], $callable);
+ }
+
+ return $callable;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ConstStub.php b/vendor/symfony/var-dumper/Caster/ConstStub.php
new file mode 100755
index 0000000..15868b0
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ConstStub.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Represents a PHP constant and its value.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class ConstStub extends Stub
+{
+ public function __construct(string $name, $value = null)
+ {
+ $this->class = $name;
+ $this->value = 1 < \func_num_args() ? $value : $name;
+ }
+
+ public function __toString()
+ {
+ return (string) $this->value;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/CutArrayStub.php b/vendor/symfony/var-dumper/Caster/CutArrayStub.php
new file mode 100755
index 0000000..0e4fb36
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/CutArrayStub.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+/**
+ * Represents a cut array.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class CutArrayStub extends CutStub
+{
+ public $preservedSubset;
+
+ public function __construct(array $value, array $preservedKeys)
+ {
+ parent::__construct($value);
+
+ $this->preservedSubset = array_intersect_key($value, array_flip($preservedKeys));
+ $this->cut -= \count($this->preservedSubset);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/CutStub.php b/vendor/symfony/var-dumper/Caster/CutStub.php
new file mode 100755
index 0000000..690338f
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/CutStub.php
@@ -0,0 +1,59 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Represents the main properties of a PHP variable, pre-casted by a caster.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class CutStub extends Stub
+{
+ public function __construct($value)
+ {
+ $this->value = $value;
+
+ switch (\gettype($value)) {
+ case 'object':
+ $this->type = self::TYPE_OBJECT;
+ $this->class = \get_class($value);
+ $this->cut = -1;
+ break;
+
+ case 'array':
+ $this->type = self::TYPE_ARRAY;
+ $this->class = self::ARRAY_ASSOC;
+ $this->cut = $this->value = \count($value);
+ break;
+
+ case 'resource':
+ case 'unknown type':
+ case 'resource (closed)':
+ $this->type = self::TYPE_RESOURCE;
+ $this->handle = (int) $value;
+ if ('Unknown' === $this->class = @get_resource_type($value)) {
+ $this->class = 'Closed';
+ }
+ $this->cut = -1;
+ break;
+
+ case 'string':
+ $this->type = self::TYPE_STRING;
+ $this->class = preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY;
+ $this->cut = self::STRING_BINARY === $this->class ? \strlen($value) : mb_strlen($value, 'UTF-8');
+ $this->value = '';
+ break;
+ }
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/DOMCaster.php b/vendor/symfony/var-dumper/Caster/DOMCaster.php
new file mode 100755
index 0000000..65151b4
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/DOMCaster.php
@@ -0,0 +1,302 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts DOM related classes to array representation.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class DOMCaster
+{
+ private static $errorCodes = [
+ DOM_PHP_ERR => 'DOM_PHP_ERR',
+ DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR',
+ DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR',
+ DOM_HIERARCHY_REQUEST_ERR => 'DOM_HIERARCHY_REQUEST_ERR',
+ DOM_WRONG_DOCUMENT_ERR => 'DOM_WRONG_DOCUMENT_ERR',
+ DOM_INVALID_CHARACTER_ERR => 'DOM_INVALID_CHARACTER_ERR',
+ DOM_NO_DATA_ALLOWED_ERR => 'DOM_NO_DATA_ALLOWED_ERR',
+ DOM_NO_MODIFICATION_ALLOWED_ERR => 'DOM_NO_MODIFICATION_ALLOWED_ERR',
+ DOM_NOT_FOUND_ERR => 'DOM_NOT_FOUND_ERR',
+ DOM_NOT_SUPPORTED_ERR => 'DOM_NOT_SUPPORTED_ERR',
+ DOM_INUSE_ATTRIBUTE_ERR => 'DOM_INUSE_ATTRIBUTE_ERR',
+ DOM_INVALID_STATE_ERR => 'DOM_INVALID_STATE_ERR',
+ DOM_SYNTAX_ERR => 'DOM_SYNTAX_ERR',
+ DOM_INVALID_MODIFICATION_ERR => 'DOM_INVALID_MODIFICATION_ERR',
+ DOM_NAMESPACE_ERR => 'DOM_NAMESPACE_ERR',
+ DOM_INVALID_ACCESS_ERR => 'DOM_INVALID_ACCESS_ERR',
+ DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR',
+ ];
+
+ private static $nodeTypes = [
+ XML_ELEMENT_NODE => 'XML_ELEMENT_NODE',
+ XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE',
+ XML_TEXT_NODE => 'XML_TEXT_NODE',
+ XML_CDATA_SECTION_NODE => 'XML_CDATA_SECTION_NODE',
+ XML_ENTITY_REF_NODE => 'XML_ENTITY_REF_NODE',
+ XML_ENTITY_NODE => 'XML_ENTITY_NODE',
+ XML_PI_NODE => 'XML_PI_NODE',
+ XML_COMMENT_NODE => 'XML_COMMENT_NODE',
+ XML_DOCUMENT_NODE => 'XML_DOCUMENT_NODE',
+ XML_DOCUMENT_TYPE_NODE => 'XML_DOCUMENT_TYPE_NODE',
+ XML_DOCUMENT_FRAG_NODE => 'XML_DOCUMENT_FRAG_NODE',
+ XML_NOTATION_NODE => 'XML_NOTATION_NODE',
+ XML_HTML_DOCUMENT_NODE => 'XML_HTML_DOCUMENT_NODE',
+ XML_DTD_NODE => 'XML_DTD_NODE',
+ XML_ELEMENT_DECL_NODE => 'XML_ELEMENT_DECL_NODE',
+ XML_ATTRIBUTE_DECL_NODE => 'XML_ATTRIBUTE_DECL_NODE',
+ XML_ENTITY_DECL_NODE => 'XML_ENTITY_DECL_NODE',
+ XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE',
+ ];
+
+ public static function castException(\DOMException $e, array $a, Stub $stub, $isNested)
+ {
+ $k = Caster::PREFIX_PROTECTED.'code';
+ if (isset($a[$k], self::$errorCodes[$a[$k]])) {
+ $a[$k] = new ConstStub(self::$errorCodes[$a[$k]], $a[$k]);
+ }
+
+ return $a;
+ }
+
+ public static function castLength($dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'length' => $dom->length,
+ ];
+
+ return $a;
+ }
+
+ public static function castImplementation($dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'Core' => '1.0',
+ Caster::PREFIX_VIRTUAL.'XML' => '2.0',
+ ];
+
+ return $a;
+ }
+
+ public static function castNode(\DOMNode $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'nodeName' => $dom->nodeName,
+ 'nodeValue' => new CutStub($dom->nodeValue),
+ 'nodeType' => new ConstStub(self::$nodeTypes[$dom->nodeType], $dom->nodeType),
+ 'parentNode' => new CutStub($dom->parentNode),
+ 'childNodes' => $dom->childNodes,
+ 'firstChild' => new CutStub($dom->firstChild),
+ 'lastChild' => new CutStub($dom->lastChild),
+ 'previousSibling' => new CutStub($dom->previousSibling),
+ 'nextSibling' => new CutStub($dom->nextSibling),
+ 'attributes' => $dom->attributes,
+ 'ownerDocument' => new CutStub($dom->ownerDocument),
+ 'namespaceURI' => $dom->namespaceURI,
+ 'prefix' => $dom->prefix,
+ 'localName' => $dom->localName,
+ 'baseURI' => $dom->baseURI ? new LinkStub($dom->baseURI) : $dom->baseURI,
+ 'textContent' => new CutStub($dom->textContent),
+ ];
+
+ return $a;
+ }
+
+ public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'nodeName' => $dom->nodeName,
+ 'nodeValue' => new CutStub($dom->nodeValue),
+ 'nodeType' => new ConstStub(self::$nodeTypes[$dom->nodeType], $dom->nodeType),
+ 'prefix' => $dom->prefix,
+ 'localName' => $dom->localName,
+ 'namespaceURI' => $dom->namespaceURI,
+ 'ownerDocument' => new CutStub($dom->ownerDocument),
+ 'parentNode' => new CutStub($dom->parentNode),
+ ];
+
+ return $a;
+ }
+
+ public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $a += [
+ 'doctype' => $dom->doctype,
+ 'implementation' => $dom->implementation,
+ 'documentElement' => new CutStub($dom->documentElement),
+ 'actualEncoding' => $dom->actualEncoding,
+ 'encoding' => $dom->encoding,
+ 'xmlEncoding' => $dom->xmlEncoding,
+ 'standalone' => $dom->standalone,
+ 'xmlStandalone' => $dom->xmlStandalone,
+ 'version' => $dom->version,
+ 'xmlVersion' => $dom->xmlVersion,
+ 'strictErrorChecking' => $dom->strictErrorChecking,
+ 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI,
+ 'config' => $dom->config,
+ 'formatOutput' => $dom->formatOutput,
+ 'validateOnParse' => $dom->validateOnParse,
+ 'resolveExternals' => $dom->resolveExternals,
+ 'preserveWhiteSpace' => $dom->preserveWhiteSpace,
+ 'recover' => $dom->recover,
+ 'substituteEntities' => $dom->substituteEntities,
+ ];
+
+ if (!($filter & Caster::EXCLUDE_VERBOSE)) {
+ $formatOutput = $dom->formatOutput;
+ $dom->formatOutput = true;
+ $a += [Caster::PREFIX_VIRTUAL.'xml' => $dom->saveXML()];
+ $dom->formatOutput = $formatOutput;
+ }
+
+ return $a;
+ }
+
+ public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'data' => $dom->data,
+ 'length' => $dom->length,
+ ];
+
+ return $a;
+ }
+
+ public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'name' => $dom->name,
+ 'specified' => $dom->specified,
+ 'value' => $dom->value,
+ 'ownerElement' => $dom->ownerElement,
+ 'schemaTypeInfo' => $dom->schemaTypeInfo,
+ ];
+
+ return $a;
+ }
+
+ public static function castElement(\DOMElement $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'tagName' => $dom->tagName,
+ 'schemaTypeInfo' => $dom->schemaTypeInfo,
+ ];
+
+ return $a;
+ }
+
+ public static function castText(\DOMText $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'wholeText' => $dom->wholeText,
+ ];
+
+ return $a;
+ }
+
+ public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'typeName' => $dom->typeName,
+ 'typeNamespace' => $dom->typeNamespace,
+ ];
+
+ return $a;
+ }
+
+ public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'severity' => $dom->severity,
+ 'message' => $dom->message,
+ 'type' => $dom->type,
+ 'relatedException' => $dom->relatedException,
+ 'related_data' => $dom->related_data,
+ 'location' => $dom->location,
+ ];
+
+ return $a;
+ }
+
+ public static function castLocator(\DOMLocator $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'lineNumber' => $dom->lineNumber,
+ 'columnNumber' => $dom->columnNumber,
+ 'offset' => $dom->offset,
+ 'relatedNode' => $dom->relatedNode,
+ 'uri' => $dom->uri ? new LinkStub($dom->uri, $dom->lineNumber) : $dom->uri,
+ ];
+
+ return $a;
+ }
+
+ public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'name' => $dom->name,
+ 'entities' => $dom->entities,
+ 'notations' => $dom->notations,
+ 'publicId' => $dom->publicId,
+ 'systemId' => $dom->systemId,
+ 'internalSubset' => $dom->internalSubset,
+ ];
+
+ return $a;
+ }
+
+ public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'publicId' => $dom->publicId,
+ 'systemId' => $dom->systemId,
+ ];
+
+ return $a;
+ }
+
+ public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'publicId' => $dom->publicId,
+ 'systemId' => $dom->systemId,
+ 'notationName' => $dom->notationName,
+ 'actualEncoding' => $dom->actualEncoding,
+ 'encoding' => $dom->encoding,
+ 'version' => $dom->version,
+ ];
+
+ return $a;
+ }
+
+ public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'target' => $dom->target,
+ 'data' => $dom->data,
+ ];
+
+ return $a;
+ }
+
+ public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ 'document' => $dom->document,
+ ];
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/DateCaster.php b/vendor/symfony/var-dumper/Caster/DateCaster.php
new file mode 100755
index 0000000..29c123d
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/DateCaster.php
@@ -0,0 +1,122 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts DateTimeInterface related classes to array representation.
+ *
+ * @author Dany Maillard <danymaillard93b@gmail.com>
+ */
+class DateCaster
+{
+ private const PERIOD_LIMIT = 3;
+
+ public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub, $isNested, $filter)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $location = $d->getTimezone()->getLocation();
+ $fromNow = (new \DateTime())->diff($d);
+
+ $title = $d->format('l, F j, Y')
+ ."\n".self::formatInterval($fromNow).' from now'
+ .($location ? ($d->format('I') ? "\nDST On" : "\nDST Off") : '')
+ ;
+
+ $a = [];
+ $a[$prefix.'date'] = new ConstStub(self::formatDateTime($d, $location ? ' e (P)' : ' P'), $title);
+
+ $stub->class .= $d->format(' @U');
+
+ return $a;
+ }
+
+ public static function castInterval(\DateInterval $interval, array $a, Stub $stub, $isNested, $filter)
+ {
+ $now = new \DateTimeImmutable();
+ $numberOfSeconds = $now->add($interval)->getTimestamp() - $now->getTimestamp();
+ $title = number_format($numberOfSeconds, 0, '.', ' ').'s';
+
+ $i = [Caster::PREFIX_VIRTUAL.'interval' => new ConstStub(self::formatInterval($interval), $title)];
+
+ return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a;
+ }
+
+ private static function formatInterval(\DateInterval $i)
+ {
+ $format = '%R ';
+
+ if (0 === $i->y && 0 === $i->m && ($i->h >= 24 || $i->i >= 60 || $i->s >= 60)) {
+ $i = date_diff($d = new \DateTime(), date_add(clone $d, $i)); // recalculate carry over points
+ $format .= 0 < $i->days ? '%ad ' : '';
+ } else {
+ $format .= ($i->y ? '%yy ' : '').($i->m ? '%mm ' : '').($i->d ? '%dd ' : '');
+ }
+
+ $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, substr($i->f, 2)) : '';
+ $format = '%R ' === $format ? '0s' : $format;
+
+ return $i->format(rtrim($format));
+ }
+
+ public static function castTimeZone(\DateTimeZone $timeZone, array $a, Stub $stub, $isNested, $filter)
+ {
+ $location = $timeZone->getLocation();
+ $formatted = (new \DateTime('now', $timeZone))->format($location ? 'e (P)' : 'P');
+ $title = $location && \extension_loaded('intl') ? \Locale::getDisplayRegion('-'.$location['country_code']) : '';
+
+ $z = [Caster::PREFIX_VIRTUAL.'timezone' => new ConstStub($formatted, $title)];
+
+ return $filter & Caster::EXCLUDE_VERBOSE ? $z : $z + $a;
+ }
+
+ public static function castPeriod(\DatePeriod $p, array $a, Stub $stub, $isNested, $filter)
+ {
+ $dates = [];
+ if (\PHP_VERSION_ID >= 70107) { // see https://bugs.php.net/bug.php?id=74639
+ foreach (clone $p as $i => $d) {
+ if (self::PERIOD_LIMIT === $i) {
+ $now = new \DateTimeImmutable();
+ $dates[] = sprintf('%s more', ($end = $p->getEndDate())
+ ? ceil(($end->format('U.u') - $d->format('U.u')) / ($now->add($p->getDateInterval())->format('U.u') - $now->format('U.u')))
+ : $p->recurrences - $i
+ );
+ break;
+ }
+ $dates[] = sprintf('%s) %s', $i + 1, self::formatDateTime($d));
+ }
+ }
+
+ $period = sprintf(
+ 'every %s, from %s (%s) %s',
+ self::formatInterval($p->getDateInterval()),
+ self::formatDateTime($p->getStartDate()),
+ $p->include_start_date ? 'included' : 'excluded',
+ ($end = $p->getEndDate()) ? 'to '.self::formatDateTime($end) : 'recurring '.$p->recurrences.' time/s'
+ );
+
+ $p = [Caster::PREFIX_VIRTUAL.'period' => new ConstStub($period, implode("\n", $dates))];
+
+ return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a;
+ }
+
+ private static function formatDateTime(\DateTimeInterface $d, $extra = '')
+ {
+ return $d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).$extra);
+ }
+
+ private static function formatSeconds($s, $us)
+ {
+ return sprintf('%02d.%s', $s, 0 === ($len = \strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us));
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/DoctrineCaster.php b/vendor/symfony/var-dumper/Caster/DoctrineCaster.php
new file mode 100755
index 0000000..696b878
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/DoctrineCaster.php
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Doctrine\Common\Proxy\Proxy as CommonProxy;
+use Doctrine\ORM\PersistentCollection;
+use Doctrine\ORM\Proxy\Proxy as OrmProxy;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts Doctrine related classes to array representation.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class DoctrineCaster
+{
+ public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, $isNested)
+ {
+ foreach (['__cloner__', '__initializer__'] as $k) {
+ if (\array_key_exists($k, $a)) {
+ unset($a[$k]);
+ ++$stub->cut;
+ }
+ }
+
+ return $a;
+ }
+
+ public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, $isNested)
+ {
+ foreach (['_entityPersister', '_identifier'] as $k) {
+ if (\array_key_exists($k = "\0Doctrine\\ORM\\Proxy\\Proxy\0".$k, $a)) {
+ unset($a[$k]);
+ ++$stub->cut;
+ }
+ }
+
+ return $a;
+ }
+
+ public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, $isNested)
+ {
+ foreach (['snapshot', 'association', 'typeClass'] as $k) {
+ if (\array_key_exists($k = "\0Doctrine\\ORM\\PersistentCollection\0".$k, $a)) {
+ $a[$k] = new CutStub($a[$k]);
+ }
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/DsCaster.php b/vendor/symfony/var-dumper/Caster/DsCaster.php
new file mode 100755
index 0000000..467aadf
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/DsCaster.php
@@ -0,0 +1,68 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Ds\Collection;
+use Ds\Map;
+use Ds\Pair;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts Ds extension classes to array representation.
+ *
+ * @author Jáchym Toušek <enumag@gmail.com>
+ */
+class DsCaster
+{
+ public static function castCollection(Collection $c, array $a, Stub $stub, bool $isNested): array
+ {
+ $a[Caster::PREFIX_VIRTUAL.'count'] = $c->count();
+ $a[Caster::PREFIX_VIRTUAL.'capacity'] = $c->capacity();
+
+ if (!$c instanceof Map) {
+ $a += $c->toArray();
+ }
+
+ return $a;
+ }
+
+ public static function castMap(Map $c, array $a, Stub $stub, bool $isNested): array
+ {
+ foreach ($c as $k => $v) {
+ $a[] = new DsPairStub($k, $v);
+ }
+
+ return $a;
+ }
+
+ public static function castPair(Pair $c, array $a, Stub $stub, bool $isNested): array
+ {
+ foreach ($c->toArray() as $k => $v) {
+ $a[Caster::PREFIX_VIRTUAL.$k] = $v;
+ }
+
+ return $a;
+ }
+
+ public static function castPairStub(DsPairStub $c, array $a, Stub $stub, bool $isNested): array
+ {
+ if ($isNested) {
+ $stub->class = Pair::class;
+ $stub->value = null;
+ $stub->handle = 0;
+
+ $a = $c->value;
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/DsPairStub.php b/vendor/symfony/var-dumper/Caster/DsPairStub.php
new file mode 100755
index 0000000..a1dcc15
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/DsPairStub.php
@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class DsPairStub extends Stub
+{
+ public function __construct($key, $value)
+ {
+ $this->value = [
+ Caster::PREFIX_VIRTUAL.'key' => $key,
+ Caster::PREFIX_VIRTUAL.'value' => $value,
+ ];
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/EnumStub.php b/vendor/symfony/var-dumper/Caster/EnumStub.php
new file mode 100755
index 0000000..7a4e98a
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/EnumStub.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Represents an enumeration of values.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class EnumStub extends Stub
+{
+ public $dumpKeys = true;
+
+ public function __construct(array $values, bool $dumpKeys = true)
+ {
+ $this->value = $values;
+ $this->dumpKeys = $dumpKeys;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ExceptionCaster.php b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php
new file mode 100755
index 0000000..54f0ba1
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php
@@ -0,0 +1,357 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\Debug\Exception\SilencedErrorContext;
+use Symfony\Component\VarDumper\Cloner\Stub;
+use Symfony\Component\VarDumper\Exception\ThrowingCasterException;
+
+/**
+ * Casts common Exception classes to array representation.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class ExceptionCaster
+{
+ public static $srcContext = 1;
+ public static $traceArgs = true;
+ public static $errorTypes = [
+ E_DEPRECATED => 'E_DEPRECATED',
+ E_USER_DEPRECATED => 'E_USER_DEPRECATED',
+ E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
+ E_ERROR => 'E_ERROR',
+ E_WARNING => 'E_WARNING',
+ E_PARSE => 'E_PARSE',
+ E_NOTICE => 'E_NOTICE',
+ E_CORE_ERROR => 'E_CORE_ERROR',
+ E_CORE_WARNING => 'E_CORE_WARNING',
+ E_COMPILE_ERROR => 'E_COMPILE_ERROR',
+ E_COMPILE_WARNING => 'E_COMPILE_WARNING',
+ E_USER_ERROR => 'E_USER_ERROR',
+ E_USER_WARNING => 'E_USER_WARNING',
+ E_USER_NOTICE => 'E_USER_NOTICE',
+ E_STRICT => 'E_STRICT',
+ ];
+
+ private static $framesCache = [];
+
+ public static function castError(\Error $e, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ return self::filterExceptionArray($stub->class, $a, "\0Error\0", $filter);
+ }
+
+ public static function castException(\Exception $e, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ return self::filterExceptionArray($stub->class, $a, "\0Exception\0", $filter);
+ }
+
+ public static function castErrorException(\ErrorException $e, array $a, Stub $stub, $isNested)
+ {
+ if (isset($a[$s = Caster::PREFIX_PROTECTED.'severity'], self::$errorTypes[$a[$s]])) {
+ $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]);
+ }
+
+ return $a;
+ }
+
+ public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, $isNested)
+ {
+ $trace = Caster::PREFIX_VIRTUAL.'trace';
+ $prefix = Caster::PREFIX_PROTECTED;
+ $xPrefix = "\0Exception\0";
+
+ if (isset($a[$xPrefix.'previous'], $a[$trace]) && $a[$xPrefix.'previous'] instanceof \Exception) {
+ $b = (array) $a[$xPrefix.'previous'];
+ $class = \get_class($a[$xPrefix.'previous']);
+ $class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
+ self::traceUnshift($b[$xPrefix.'trace'], $class, $b[$prefix.'file'], $b[$prefix.'line']);
+ $a[$trace] = new TraceStub($b[$xPrefix.'trace'], false, 0, -\count($a[$trace]->value));
+ }
+
+ unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']);
+
+ return $a;
+ }
+
+ public static function castSilencedErrorContext(SilencedErrorContext $e, array $a, Stub $stub, $isNested)
+ {
+ $sPrefix = "\0".SilencedErrorContext::class."\0";
+
+ if (!isset($a[$s = $sPrefix.'severity'])) {
+ return $a;
+ }
+
+ if (isset(self::$errorTypes[$a[$s]])) {
+ $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]);
+ }
+
+ $trace = [[
+ 'file' => $a[$sPrefix.'file'],
+ 'line' => $a[$sPrefix.'line'],
+ ]];
+
+ if (isset($a[$sPrefix.'trace'])) {
+ $trace = array_merge($trace, $a[$sPrefix.'trace']);
+ }
+
+ unset($a[$sPrefix.'file'], $a[$sPrefix.'line'], $a[$sPrefix.'trace']);
+ $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs);
+
+ return $a;
+ }
+
+ public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $isNested)
+ {
+ if (!$isNested) {
+ return $a;
+ }
+ $stub->class = '';
+ $stub->handle = 0;
+ $frames = $trace->value;
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ $a = [];
+ $j = \count($frames);
+ if (0 > $i = $trace->sliceOffset) {
+ $i = max(0, $j + $i);
+ }
+ if (!isset($trace->value[$i])) {
+ return [];
+ }
+ $lastCall = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : '';
+ $frames[] = ['function' => ''];
+ $collapse = false;
+
+ for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) {
+ $f = $frames[$i];
+ $call = isset($f['function']) ? (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'] : '???';
+
+ $frame = new FrameStub(
+ [
+ 'object' => isset($f['object']) ? $f['object'] : null,
+ 'class' => isset($f['class']) ? $f['class'] : null,
+ 'type' => isset($f['type']) ? $f['type'] : null,
+ 'function' => isset($f['function']) ? $f['function'] : null,
+ ] + $frames[$i - 1],
+ false,
+ true
+ );
+ $f = self::castFrameStub($frame, [], $frame, true);
+ if (isset($f[$prefix.'src'])) {
+ foreach ($f[$prefix.'src']->value as $label => $frame) {
+ if (0 === strpos($label, "\0~collapse=0")) {
+ if ($collapse) {
+ $label = substr_replace($label, '1', 11, 1);
+ } else {
+ $collapse = true;
+ }
+ }
+ $label = substr_replace($label, "title=Stack level $j.&", 2, 0);
+ }
+ $f = $frames[$i - 1];
+ if ($trace->keepArgs && !empty($f['args']) && $frame instanceof EnumStub) {
+ $frame->value['arguments'] = new ArgsStub($f['args'], isset($f['function']) ? $f['function'] : null, isset($f['class']) ? $f['class'] : null);
+ }
+ } elseif ('???' !== $lastCall) {
+ $label = new ClassStub($lastCall);
+ if (isset($label->attr['ellipsis'])) {
+ $label->attr['ellipsis'] += 2;
+ $label = substr_replace($prefix, "ellipsis-type=class&ellipsis={$label->attr['ellipsis']}&ellipsis-tail=1&title=Stack level $j.", 2, 0).$label->value.'()';
+ } else {
+ $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$label->value.'()';
+ }
+ } else {
+ $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$lastCall;
+ }
+ $a[substr_replace($label, sprintf('separator=%s&', $frame instanceof EnumStub ? ' ' : ':'), 2, 0)] = $frame;
+
+ $lastCall = $call;
+ }
+ if (null !== $trace->sliceLength) {
+ $a = \array_slice($a, 0, $trace->sliceLength, true);
+ }
+
+ return $a;
+ }
+
+ public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, $isNested)
+ {
+ if (!$isNested) {
+ return $a;
+ }
+ $f = $frame->value;
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ if (isset($f['file'], $f['line'])) {
+ $cacheKey = $f;
+ unset($cacheKey['object'], $cacheKey['args']);
+ $cacheKey[] = self::$srcContext;
+ $cacheKey = implode('-', $cacheKey);
+
+ if (isset(self::$framesCache[$cacheKey])) {
+ $a[$prefix.'src'] = self::$framesCache[$cacheKey];
+ } else {
+ if (preg_match('/\((\d+)\)(?:\([\da-f]{32}\))? : (?:eval\(\)\'d code|runtime-created function)$/', $f['file'], $match)) {
+ $f['file'] = substr($f['file'], 0, -\strlen($match[0]));
+ $f['line'] = (int) $match[1];
+ }
+ $caller = isset($f['function']) ? sprintf('in %s() on line %d', (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'], $f['line']) : null;
+ $src = $f['line'];
+ $srcKey = $f['file'];
+ $ellipsis = new LinkStub($srcKey, 0);
+ $srcAttr = 'collapse='.(int) $ellipsis->inVendor;
+ $ellipsisTail = isset($ellipsis->attr['ellipsis-tail']) ? $ellipsis->attr['ellipsis-tail'] : 0;
+ $ellipsis = isset($ellipsis->attr['ellipsis']) ? $ellipsis->attr['ellipsis'] : 0;
+
+ if (file_exists($f['file']) && 0 <= self::$srcContext) {
+ if (!empty($f['class']) && (is_subclass_of($f['class'], 'Twig\Template') || is_subclass_of($f['class'], 'Twig_Template')) && method_exists($f['class'], 'getDebugInfo')) {
+ $template = isset($f['object']) ? $f['object'] : unserialize(sprintf('O:%d:"%s":0:{}', \strlen($f['class']), $f['class']));
+
+ $ellipsis = 0;
+ $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : '');
+ $templateInfo = $template->getDebugInfo();
+ if (isset($templateInfo[$f['line']])) {
+ if (!method_exists($template, 'getSourceContext') || !file_exists($templatePath = $template->getSourceContext()->getPath())) {
+ $templatePath = null;
+ }
+ if ($templateSrc) {
+ $src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, $caller, 'twig', $templatePath);
+ $srcKey = ($templatePath ?: $template->getTemplateName()).':'.$templateInfo[$f['line']];
+ }
+ }
+ }
+ if ($srcKey == $f['file']) {
+ $src = self::extractSource(file_get_contents($f['file']), $f['line'], self::$srcContext, $caller, 'php', $f['file']);
+ $srcKey .= ':'.$f['line'];
+ if ($ellipsis) {
+ $ellipsis += 1 + \strlen($f['line']);
+ }
+ }
+ $srcAttr .= sprintf('&separator= &file=%s&line=%d', rawurlencode($f['file']), $f['line']);
+ } else {
+ $srcAttr .= '&separator=:';
+ }
+ $srcAttr .= $ellipsis ? '&ellipsis-type=path&ellipsis='.$ellipsis.'&ellipsis-tail='.$ellipsisTail : '';
+ self::$framesCache[$cacheKey] = $a[$prefix.'src'] = new EnumStub(["\0~$srcAttr\0$srcKey" => $src]);
+ }
+ }
+
+ unset($a[$prefix.'args'], $a[$prefix.'line'], $a[$prefix.'file']);
+ if ($frame->inTraceStub) {
+ unset($a[$prefix.'class'], $a[$prefix.'type'], $a[$prefix.'function']);
+ }
+ foreach ($a as $k => $v) {
+ if (!$v) {
+ unset($a[$k]);
+ }
+ }
+ if ($frame->keepArgs && !empty($f['args'])) {
+ $a[$prefix.'arguments'] = new ArgsStub($f['args'], $f['function'], $f['class']);
+ }
+
+ return $a;
+ }
+
+ private static function filterExceptionArray($xClass, array $a, $xPrefix, $filter)
+ {
+ if (isset($a[$xPrefix.'trace'])) {
+ $trace = $a[$xPrefix.'trace'];
+ unset($a[$xPrefix.'trace']); // Ensures the trace is always last
+ } else {
+ $trace = [];
+ }
+
+ if (!($filter & Caster::EXCLUDE_VERBOSE) && $trace) {
+ if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) {
+ self::traceUnshift($trace, $xClass, $a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']);
+ }
+ $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs);
+ }
+ if (empty($a[$xPrefix.'previous'])) {
+ unset($a[$xPrefix.'previous']);
+ }
+ unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message'], $a[Caster::PREFIX_DYNAMIC.'__destructorException']);
+
+ if (isset($a[Caster::PREFIX_PROTECTED.'message']) && false !== strpos($a[Caster::PREFIX_PROTECTED.'message'], "class@anonymous\0")) {
+ $a[Caster::PREFIX_PROTECTED.'message'] = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) {
+ return \class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
+ }, $a[Caster::PREFIX_PROTECTED.'message']);
+ }
+
+ if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) {
+ $a[Caster::PREFIX_PROTECTED.'file'] = new LinkStub($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']);
+ }
+
+ return $a;
+ }
+
+ private static function traceUnshift(&$trace, $class, $file, $line)
+ {
+ if (isset($trace[0]['file'], $trace[0]['line']) && $trace[0]['file'] === $file && $trace[0]['line'] === $line) {
+ return;
+ }
+ array_unshift($trace, [
+ 'function' => $class ? 'new '.$class : null,
+ 'file' => $file,
+ 'line' => $line,
+ ]);
+ }
+
+ private static function extractSource($srcLines, $line, $srcContext, $title, $lang, $file = null)
+ {
+ $srcLines = explode("\n", $srcLines);
+ $src = [];
+
+ for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) {
+ $src[] = (isset($srcLines[$i]) ? $srcLines[$i] : '')."\n";
+ }
+
+ $srcLines = [];
+ $ltrim = 0;
+ do {
+ $pad = null;
+ for ($i = $srcContext << 1; $i >= 0; --$i) {
+ if (isset($src[$i][$ltrim]) && "\r" !== ($c = $src[$i][$ltrim]) && "\n" !== $c) {
+ if (null === $pad) {
+ $pad = $c;
+ }
+ if ((' ' !== $c && "\t" !== $c) || $pad !== $c) {
+ break;
+ }
+ }
+ }
+ ++$ltrim;
+ } while (0 > $i && null !== $pad);
+
+ --$ltrim;
+
+ foreach ($src as $i => $c) {
+ if ($ltrim) {
+ $c = isset($c[$ltrim]) && "\r" !== $c[$ltrim] ? substr($c, $ltrim) : ltrim($c, " \t");
+ }
+ $c = substr($c, 0, -1);
+ if ($i !== $srcContext) {
+ $c = new ConstStub('default', $c);
+ } else {
+ $c = new ConstStub($c, $title);
+ if (null !== $file) {
+ $c->attr['file'] = $file;
+ $c->attr['line'] = $line;
+ }
+ }
+ $c->attr['lang'] = $lang;
+ $srcLines[sprintf("\0~separator=› &%d\0", $i + $line - $srcContext)] = $c;
+ }
+
+ return new EnumStub($srcLines);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/FrameStub.php b/vendor/symfony/var-dumper/Caster/FrameStub.php
new file mode 100755
index 0000000..8786755
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/FrameStub.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+/**
+ * Represents a single backtrace frame as returned by debug_backtrace() or Exception->getTrace().
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class FrameStub extends EnumStub
+{
+ public $keepArgs;
+ public $inTraceStub;
+
+ public function __construct(array $frame, bool $keepArgs = true, bool $inTraceStub = false)
+ {
+ $this->value = $frame;
+ $this->keepArgs = $keepArgs;
+ $this->inTraceStub = $inTraceStub;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/GmpCaster.php b/vendor/symfony/var-dumper/Caster/GmpCaster.php
new file mode 100755
index 0000000..504dc07
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/GmpCaster.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts GMP objects to array representation.
+ *
+ * @author Hamza Amrouche <hamza.simperfit@gmail.com>
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class GmpCaster
+{
+ public static function castGmp(\GMP $gmp, array $a, Stub $stub, $isNested, $filter): array
+ {
+ $a[Caster::PREFIX_VIRTUAL.'value'] = new ConstStub(gmp_strval($gmp), gmp_strval($gmp));
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/IntlCaster.php b/vendor/symfony/var-dumper/Caster/IntlCaster.php
new file mode 100755
index 0000000..31d5cb3
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/IntlCaster.php
@@ -0,0 +1,170 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
+ */
+class IntlCaster
+{
+ public static function castMessageFormatter(\MessageFormatter $c, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(),
+ Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(),
+ ];
+
+ return self::castError($c, $a);
+ }
+
+ public static function castNumberFormatter(\NumberFormatter $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(),
+ Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(),
+ ];
+
+ if ($filter & Caster::EXCLUDE_VERBOSE) {
+ $stub->cut += 3;
+
+ return self::castError($c, $a);
+ }
+
+ $a += [
+ Caster::PREFIX_VIRTUAL.'attributes' => new EnumStub(
+ [
+ 'PARSE_INT_ONLY' => $c->getAttribute(\NumberFormatter::PARSE_INT_ONLY),
+ 'GROUPING_USED' => $c->getAttribute(\NumberFormatter::GROUPING_USED),
+ 'DECIMAL_ALWAYS_SHOWN' => $c->getAttribute(\NumberFormatter::DECIMAL_ALWAYS_SHOWN),
+ 'MAX_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_INTEGER_DIGITS),
+ 'MIN_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_INTEGER_DIGITS),
+ 'INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::INTEGER_DIGITS),
+ 'MAX_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_FRACTION_DIGITS),
+ 'MIN_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_FRACTION_DIGITS),
+ 'FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::FRACTION_DIGITS),
+ 'MULTIPLIER' => $c->getAttribute(\NumberFormatter::MULTIPLIER),
+ 'GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::GROUPING_SIZE),
+ 'ROUNDING_MODE' => $c->getAttribute(\NumberFormatter::ROUNDING_MODE),
+ 'ROUNDING_INCREMENT' => $c->getAttribute(\NumberFormatter::ROUNDING_INCREMENT),
+ 'FORMAT_WIDTH' => $c->getAttribute(\NumberFormatter::FORMAT_WIDTH),
+ 'PADDING_POSITION' => $c->getAttribute(\NumberFormatter::PADDING_POSITION),
+ 'SECONDARY_GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::SECONDARY_GROUPING_SIZE),
+ 'SIGNIFICANT_DIGITS_USED' => $c->getAttribute(\NumberFormatter::SIGNIFICANT_DIGITS_USED),
+ 'MIN_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_SIGNIFICANT_DIGITS),
+ 'MAX_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_SIGNIFICANT_DIGITS),
+ 'LENIENT_PARSE' => $c->getAttribute(\NumberFormatter::LENIENT_PARSE),
+ ]
+ ),
+ Caster::PREFIX_VIRTUAL.'text_attributes' => new EnumStub(
+ [
+ 'POSITIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX),
+ 'POSITIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_SUFFIX),
+ 'NEGATIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_PREFIX),
+ 'NEGATIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_SUFFIX),
+ 'PADDING_CHARACTER' => $c->getTextAttribute(\NumberFormatter::PADDING_CHARACTER),
+ 'CURRENCY_CODE' => $c->getTextAttribute(\NumberFormatter::CURRENCY_CODE),
+ 'DEFAULT_RULESET' => $c->getTextAttribute(\NumberFormatter::DEFAULT_RULESET),
+ 'PUBLIC_RULESETS' => $c->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS),
+ ]
+ ),
+ Caster::PREFIX_VIRTUAL.'symbols' => new EnumStub(
+ [
+ 'DECIMAL_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL),
+ 'GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL),
+ 'PATTERN_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::PATTERN_SEPARATOR_SYMBOL),
+ 'PERCENT_SYMBOL' => $c->getSymbol(\NumberFormatter::PERCENT_SYMBOL),
+ 'ZERO_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::ZERO_DIGIT_SYMBOL),
+ 'DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::DIGIT_SYMBOL),
+ 'MINUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::MINUS_SIGN_SYMBOL),
+ 'PLUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::PLUS_SIGN_SYMBOL),
+ 'CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::CURRENCY_SYMBOL),
+ 'INTL_CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::INTL_CURRENCY_SYMBOL),
+ 'MONETARY_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL),
+ 'EXPONENTIAL_SYMBOL' => $c->getSymbol(\NumberFormatter::EXPONENTIAL_SYMBOL),
+ 'PERMILL_SYMBOL' => $c->getSymbol(\NumberFormatter::PERMILL_SYMBOL),
+ 'PAD_ESCAPE_SYMBOL' => $c->getSymbol(\NumberFormatter::PAD_ESCAPE_SYMBOL),
+ 'INFINITY_SYMBOL' => $c->getSymbol(\NumberFormatter::INFINITY_SYMBOL),
+ 'NAN_SYMBOL' => $c->getSymbol(\NumberFormatter::NAN_SYMBOL),
+ 'SIGNIFICANT_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL),
+ 'MONETARY_GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL),
+ ]
+ ),
+ ];
+
+ return self::castError($c, $a);
+ }
+
+ public static function castIntlTimeZone(\IntlTimeZone $c, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'display_name' => $c->getDisplayName(),
+ Caster::PREFIX_VIRTUAL.'id' => $c->getID(),
+ Caster::PREFIX_VIRTUAL.'raw_offset' => $c->getRawOffset(),
+ ];
+
+ if ($c->useDaylightTime()) {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'dst_savings' => $c->getDSTSavings(),
+ ];
+ }
+
+ return self::castError($c, $a);
+ }
+
+ public static function castIntlCalendar(\IntlCalendar $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'type' => $c->getType(),
+ Caster::PREFIX_VIRTUAL.'first_day_of_week' => $c->getFirstDayOfWeek(),
+ Caster::PREFIX_VIRTUAL.'minimal_days_in_first_week' => $c->getMinimalDaysInFirstWeek(),
+ Caster::PREFIX_VIRTUAL.'repeated_wall_time_option' => $c->getRepeatedWallTimeOption(),
+ Caster::PREFIX_VIRTUAL.'skipped_wall_time_option' => $c->getSkippedWallTimeOption(),
+ Caster::PREFIX_VIRTUAL.'time' => $c->getTime(),
+ Caster::PREFIX_VIRTUAL.'in_daylight_time' => $c->inDaylightTime(),
+ Caster::PREFIX_VIRTUAL.'is_lenient' => $c->isLenient(),
+ Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(),
+ ];
+
+ return self::castError($c, $a);
+ }
+
+ public static function castIntlDateFormatter(\IntlDateFormatter $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(),
+ Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(),
+ Caster::PREFIX_VIRTUAL.'calendar' => $c->getCalendar(),
+ Caster::PREFIX_VIRTUAL.'time_zone_id' => $c->getTimeZoneId(),
+ Caster::PREFIX_VIRTUAL.'time_type' => $c->getTimeType(),
+ Caster::PREFIX_VIRTUAL.'date_type' => $c->getDateType(),
+ Caster::PREFIX_VIRTUAL.'calendar_object' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getCalendarObject()) : $c->getCalendarObject(),
+ Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(),
+ ];
+
+ return self::castError($c, $a);
+ }
+
+ private static function castError($c, array $a): array
+ {
+ if ($errorCode = $c->getErrorCode()) {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'error_code' => $errorCode,
+ Caster::PREFIX_VIRTUAL.'error_message' => $c->getErrorMessage(),
+ ];
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/LinkStub.php b/vendor/symfony/var-dumper/Caster/LinkStub.php
new file mode 100755
index 0000000..84a8b10
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/LinkStub.php
@@ -0,0 +1,108 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+/**
+ * Represents a file or a URL.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class LinkStub extends ConstStub
+{
+ public $inVendor = false;
+
+ private static $vendorRoots;
+ private static $composerRoots;
+
+ public function __construct($label, int $line = 0, $href = null)
+ {
+ $this->value = $label;
+
+ if (null === $href) {
+ $href = $label;
+ }
+ if (!\is_string($href)) {
+ return;
+ }
+ if (0 === strpos($href, 'file://')) {
+ if ($href === $label) {
+ $label = substr($label, 7);
+ }
+ $href = substr($href, 7);
+ } elseif (false !== strpos($href, '://')) {
+ $this->attr['href'] = $href;
+
+ return;
+ }
+ if (!file_exists($href)) {
+ return;
+ }
+ if ($line) {
+ $this->attr['line'] = $line;
+ }
+ if ($label !== $this->attr['file'] = realpath($href) ?: $href) {
+ return;
+ }
+ if ($composerRoot = $this->getComposerRoot($href, $this->inVendor)) {
+ $this->attr['ellipsis'] = \strlen($href) - \strlen($composerRoot) + 1;
+ $this->attr['ellipsis-type'] = 'path';
+ $this->attr['ellipsis-tail'] = 1 + ($this->inVendor ? 2 + \strlen(implode('', \array_slice(explode(\DIRECTORY_SEPARATOR, substr($href, 1 - $this->attr['ellipsis'])), 0, 2))) : 0);
+ } elseif (3 < \count($ellipsis = explode(\DIRECTORY_SEPARATOR, $href))) {
+ $this->attr['ellipsis'] = 2 + \strlen(implode('', \array_slice($ellipsis, -2)));
+ $this->attr['ellipsis-type'] = 'path';
+ $this->attr['ellipsis-tail'] = 1;
+ }
+ }
+
+ private function getComposerRoot($file, &$inVendor)
+ {
+ if (null === self::$vendorRoots) {
+ self::$vendorRoots = [];
+
+ foreach (get_declared_classes() as $class) {
+ if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
+ $r = new \ReflectionClass($class);
+ $v = \dirname(\dirname($r->getFileName()));
+ if (file_exists($v.'/composer/installed.json')) {
+ self::$vendorRoots[] = $v.\DIRECTORY_SEPARATOR;
+ }
+ }
+ }
+ }
+ $inVendor = false;
+
+ if (isset(self::$composerRoots[$dir = \dirname($file)])) {
+ return self::$composerRoots[$dir];
+ }
+
+ foreach (self::$vendorRoots as $root) {
+ if ($inVendor = 0 === strpos($file, $root)) {
+ return $root;
+ }
+ }
+
+ $parent = $dir;
+ while (!@file_exists($parent.'/composer.json')) {
+ if (!@file_exists($parent)) {
+ // open_basedir restriction in effect
+ break;
+ }
+ if ($parent === \dirname($parent)) {
+ return self::$composerRoots[$dir] = false;
+ }
+
+ $parent = \dirname($parent);
+ }
+
+ return self::$composerRoots[$dir] = $parent.\DIRECTORY_SEPARATOR;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/MemcachedCaster.php b/vendor/symfony/var-dumper/Caster/MemcachedCaster.php
new file mode 100755
index 0000000..a326546
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/MemcachedCaster.php
@@ -0,0 +1,79 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
+ */
+class MemcachedCaster
+{
+ private static $optionConstants;
+ private static $defaultOptions;
+
+ public static function castMemcached(\Memcached $c, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'servers' => $c->getServerList(),
+ Caster::PREFIX_VIRTUAL.'options' => new EnumStub(
+ self::getNonDefaultOptions($c)
+ ),
+ ];
+
+ return $a;
+ }
+
+ private static function getNonDefaultOptions(\Memcached $c)
+ {
+ self::$defaultOptions = self::$defaultOptions ?? self::discoverDefaultOptions();
+ self::$optionConstants = self::$optionConstants ?? self::getOptionConstants();
+
+ $nonDefaultOptions = [];
+ foreach (self::$optionConstants as $constantKey => $value) {
+ if (self::$defaultOptions[$constantKey] !== $option = $c->getOption($value)) {
+ $nonDefaultOptions[$constantKey] = $option;
+ }
+ }
+
+ return $nonDefaultOptions;
+ }
+
+ private static function discoverDefaultOptions()
+ {
+ $defaultMemcached = new \Memcached();
+ $defaultMemcached->addServer('127.0.0.1', 11211);
+
+ $defaultOptions = [];
+ self::$optionConstants = self::$optionConstants ?? self::getOptionConstants();
+
+ foreach (self::$optionConstants as $constantKey => $value) {
+ $defaultOptions[$constantKey] = $defaultMemcached->getOption($value);
+ }
+
+ return $defaultOptions;
+ }
+
+ private static function getOptionConstants()
+ {
+ $reflectedMemcached = new \ReflectionClass(\Memcached::class);
+
+ $optionConstants = [];
+ foreach ($reflectedMemcached->getConstants() as $constantKey => $value) {
+ if (0 === strpos($constantKey, 'OPT_')) {
+ $optionConstants[$constantKey] = $value;
+ }
+ }
+
+ return $optionConstants;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/PdoCaster.php b/vendor/symfony/var-dumper/Caster/PdoCaster.php
new file mode 100755
index 0000000..8af5182
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/PdoCaster.php
@@ -0,0 +1,120 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts PDO related classes to array representation.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class PdoCaster
+{
+ private static $pdoAttributes = [
+ 'CASE' => [
+ \PDO::CASE_LOWER => 'LOWER',
+ \PDO::CASE_NATURAL => 'NATURAL',
+ \PDO::CASE_UPPER => 'UPPER',
+ ],
+ 'ERRMODE' => [
+ \PDO::ERRMODE_SILENT => 'SILENT',
+ \PDO::ERRMODE_WARNING => 'WARNING',
+ \PDO::ERRMODE_EXCEPTION => 'EXCEPTION',
+ ],
+ 'TIMEOUT',
+ 'PREFETCH',
+ 'AUTOCOMMIT',
+ 'PERSISTENT',
+ 'DRIVER_NAME',
+ 'SERVER_INFO',
+ 'ORACLE_NULLS' => [
+ \PDO::NULL_NATURAL => 'NATURAL',
+ \PDO::NULL_EMPTY_STRING => 'EMPTY_STRING',
+ \PDO::NULL_TO_STRING => 'TO_STRING',
+ ],
+ 'CLIENT_VERSION',
+ 'SERVER_VERSION',
+ 'STATEMENT_CLASS',
+ 'EMULATE_PREPARES',
+ 'CONNECTION_STATUS',
+ 'STRINGIFY_FETCHES',
+ 'DEFAULT_FETCH_MODE' => [
+ \PDO::FETCH_ASSOC => 'ASSOC',
+ \PDO::FETCH_BOTH => 'BOTH',
+ \PDO::FETCH_LAZY => 'LAZY',
+ \PDO::FETCH_NUM => 'NUM',
+ \PDO::FETCH_OBJ => 'OBJ',
+ ],
+ ];
+
+ public static function castPdo(\PDO $c, array $a, Stub $stub, $isNested)
+ {
+ $attr = [];
+ $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE);
+ $c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+
+ foreach (self::$pdoAttributes as $k => $v) {
+ if (!isset($k[0])) {
+ $k = $v;
+ $v = [];
+ }
+
+ try {
+ $attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(\constant('PDO::ATTR_'.$k));
+ if ($v && isset($v[$attr[$k]])) {
+ $attr[$k] = new ConstStub($v[$attr[$k]], $attr[$k]);
+ }
+ } catch (\Exception $e) {
+ }
+ }
+ if (isset($attr[$k = 'STATEMENT_CLASS'][1])) {
+ if ($attr[$k][1]) {
+ $attr[$k][1] = new ArgsStub($attr[$k][1], '__construct', $attr[$k][0]);
+ }
+ $attr[$k][0] = new ClassStub($attr[$k][0]);
+ }
+
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $a += [
+ $prefix.'inTransaction' => method_exists($c, 'inTransaction'),
+ $prefix.'errorInfo' => $c->errorInfo(),
+ $prefix.'attributes' => new EnumStub($attr),
+ ];
+
+ if ($a[$prefix.'inTransaction']) {
+ $a[$prefix.'inTransaction'] = $c->inTransaction();
+ } else {
+ unset($a[$prefix.'inTransaction']);
+ }
+
+ if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) {
+ unset($a[$prefix.'errorInfo']);
+ }
+
+ $c->setAttribute(\PDO::ATTR_ERRMODE, $errmode);
+
+ return $a;
+ }
+
+ public static function castPdoStatement(\PDOStatement $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $a[$prefix.'errorInfo'] = $c->errorInfo();
+
+ if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) {
+ unset($a[$prefix.'errorInfo']);
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/PgSqlCaster.php b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php
new file mode 100755
index 0000000..cd6bf5b
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php
@@ -0,0 +1,154 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts pqsql resources to array representation.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class PgSqlCaster
+{
+ private static $paramCodes = [
+ 'server_encoding',
+ 'client_encoding',
+ 'is_superuser',
+ 'session_authorization',
+ 'DateStyle',
+ 'TimeZone',
+ 'IntervalStyle',
+ 'integer_datetimes',
+ 'application_name',
+ 'standard_conforming_strings',
+ ];
+
+ private static $transactionStatus = [
+ PGSQL_TRANSACTION_IDLE => 'PGSQL_TRANSACTION_IDLE',
+ PGSQL_TRANSACTION_ACTIVE => 'PGSQL_TRANSACTION_ACTIVE',
+ PGSQL_TRANSACTION_INTRANS => 'PGSQL_TRANSACTION_INTRANS',
+ PGSQL_TRANSACTION_INERROR => 'PGSQL_TRANSACTION_INERROR',
+ PGSQL_TRANSACTION_UNKNOWN => 'PGSQL_TRANSACTION_UNKNOWN',
+ ];
+
+ private static $resultStatus = [
+ PGSQL_EMPTY_QUERY => 'PGSQL_EMPTY_QUERY',
+ PGSQL_COMMAND_OK => 'PGSQL_COMMAND_OK',
+ PGSQL_TUPLES_OK => 'PGSQL_TUPLES_OK',
+ PGSQL_COPY_OUT => 'PGSQL_COPY_OUT',
+ PGSQL_COPY_IN => 'PGSQL_COPY_IN',
+ PGSQL_BAD_RESPONSE => 'PGSQL_BAD_RESPONSE',
+ PGSQL_NONFATAL_ERROR => 'PGSQL_NONFATAL_ERROR',
+ PGSQL_FATAL_ERROR => 'PGSQL_FATAL_ERROR',
+ ];
+
+ private static $diagCodes = [
+ 'severity' => PGSQL_DIAG_SEVERITY,
+ 'sqlstate' => PGSQL_DIAG_SQLSTATE,
+ 'message' => PGSQL_DIAG_MESSAGE_PRIMARY,
+ 'detail' => PGSQL_DIAG_MESSAGE_DETAIL,
+ 'hint' => PGSQL_DIAG_MESSAGE_HINT,
+ 'statement position' => PGSQL_DIAG_STATEMENT_POSITION,
+ 'internal position' => PGSQL_DIAG_INTERNAL_POSITION,
+ 'internal query' => PGSQL_DIAG_INTERNAL_QUERY,
+ 'context' => PGSQL_DIAG_CONTEXT,
+ 'file' => PGSQL_DIAG_SOURCE_FILE,
+ 'line' => PGSQL_DIAG_SOURCE_LINE,
+ 'function' => PGSQL_DIAG_SOURCE_FUNCTION,
+ ];
+
+ public static function castLargeObject($lo, array $a, Stub $stub, $isNested)
+ {
+ $a['seek position'] = pg_lo_tell($lo);
+
+ return $a;
+ }
+
+ public static function castLink($link, array $a, Stub $stub, $isNested)
+ {
+ $a['status'] = pg_connection_status($link);
+ $a['status'] = new ConstStub(PGSQL_CONNECTION_OK === $a['status'] ? 'PGSQL_CONNECTION_OK' : 'PGSQL_CONNECTION_BAD', $a['status']);
+ $a['busy'] = pg_connection_busy($link);
+
+ $a['transaction'] = pg_transaction_status($link);
+ if (isset(self::$transactionStatus[$a['transaction']])) {
+ $a['transaction'] = new ConstStub(self::$transactionStatus[$a['transaction']], $a['transaction']);
+ }
+
+ $a['pid'] = pg_get_pid($link);
+ $a['last error'] = pg_last_error($link);
+ $a['last notice'] = pg_last_notice($link);
+ $a['host'] = pg_host($link);
+ $a['port'] = pg_port($link);
+ $a['dbname'] = pg_dbname($link);
+ $a['options'] = pg_options($link);
+ $a['version'] = pg_version($link);
+
+ foreach (self::$paramCodes as $v) {
+ if (false !== $s = pg_parameter_status($link, $v)) {
+ $a['param'][$v] = $s;
+ }
+ }
+
+ $a['param']['client_encoding'] = pg_client_encoding($link);
+ $a['param'] = new EnumStub($a['param']);
+
+ return $a;
+ }
+
+ public static function castResult($result, array $a, Stub $stub, $isNested)
+ {
+ $a['num rows'] = pg_num_rows($result);
+ $a['status'] = pg_result_status($result);
+ if (isset(self::$resultStatus[$a['status']])) {
+ $a['status'] = new ConstStub(self::$resultStatus[$a['status']], $a['status']);
+ }
+ $a['command-completion tag'] = pg_result_status($result, PGSQL_STATUS_STRING);
+
+ if (-1 === $a['num rows']) {
+ foreach (self::$diagCodes as $k => $v) {
+ $a['error'][$k] = pg_result_error_field($result, $v);
+ }
+ }
+
+ $a['affected rows'] = pg_affected_rows($result);
+ $a['last OID'] = pg_last_oid($result);
+
+ $fields = pg_num_fields($result);
+
+ for ($i = 0; $i < $fields; ++$i) {
+ $field = [
+ 'name' => pg_field_name($result, $i),
+ 'table' => sprintf('%s (OID: %s)', pg_field_table($result, $i), pg_field_table($result, $i, true)),
+ 'type' => sprintf('%s (OID: %s)', pg_field_type($result, $i), pg_field_type_oid($result, $i)),
+ 'nullable' => (bool) pg_field_is_null($result, $i),
+ 'storage' => pg_field_size($result, $i).' bytes',
+ 'display' => pg_field_prtlen($result, $i).' chars',
+ ];
+ if (' (OID: )' === $field['table']) {
+ $field['table'] = null;
+ }
+ if ('-1 bytes' === $field['storage']) {
+ $field['storage'] = 'variable size';
+ } elseif ('1 bytes' === $field['storage']) {
+ $field['storage'] = '1 byte';
+ }
+ if ('1 chars' === $field['display']) {
+ $field['display'] = '1 char';
+ }
+ $a['fields'][] = new EnumStub($field);
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php
new file mode 100755
index 0000000..da037b6
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use ProxyManager\Proxy\ProxyInterface;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class ProxyManagerCaster
+{
+ public static function castProxy(ProxyInterface $c, array $a, Stub $stub, $isNested)
+ {
+ if ($parent = \get_parent_class($c)) {
+ $stub->class .= ' - '.$parent;
+ }
+ $stub->class .= '@proxy';
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/RedisCaster.php b/vendor/symfony/var-dumper/Caster/RedisCaster.php
new file mode 100755
index 0000000..558a080
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/RedisCaster.php
@@ -0,0 +1,150 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts Redis class from ext-redis to array representation.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class RedisCaster
+{
+ private static $serializer = [
+ \Redis::SERIALIZER_NONE => 'NONE',
+ \Redis::SERIALIZER_PHP => 'PHP',
+ 2 => 'IGBINARY', // Optional Redis::SERIALIZER_IGBINARY
+ ];
+
+ private static $mode = [
+ \Redis::ATOMIC => 'ATOMIC',
+ \Redis::MULTI => 'MULTI',
+ \Redis::PIPELINE => 'PIPELINE',
+ ];
+
+ private static $compression = [
+ 0 => 'NONE', // Redis::COMPRESSION_NONE
+ 1 => 'LZF', // Redis::COMPRESSION_LZF
+ ];
+
+ private static $failover = [
+ \RedisCluster::FAILOVER_NONE => 'NONE',
+ \RedisCluster::FAILOVER_ERROR => 'ERROR',
+ \RedisCluster::FAILOVER_DISTRIBUTE => 'DISTRIBUTE',
+ \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES => 'DISTRIBUTE_SLAVES',
+ ];
+
+ public static function castRedis(\Redis $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ if (!$connected = $c->isConnected()) {
+ return $a + [
+ $prefix.'isConnected' => $connected,
+ ];
+ }
+
+ $mode = $c->getMode();
+
+ return $a + [
+ $prefix.'isConnected' => $connected,
+ $prefix.'host' => $c->getHost(),
+ $prefix.'port' => $c->getPort(),
+ $prefix.'auth' => $c->getAuth(),
+ $prefix.'mode' => isset(self::$mode[$mode]) ? new ConstStub(self::$mode[$mode], $mode) : $mode,
+ $prefix.'dbNum' => $c->getDbNum(),
+ $prefix.'timeout' => $c->getTimeout(),
+ $prefix.'lastError' => $c->getLastError(),
+ $prefix.'persistentId' => $c->getPersistentID(),
+ $prefix.'options' => self::getRedisOptions($c),
+ ];
+ }
+
+ public static function castRedisArray(\RedisArray $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ return $a + [
+ $prefix.'hosts' => $c->_hosts(),
+ $prefix.'function' => ClassStub::wrapCallable($c->_function()),
+ $prefix.'lastError' => $c->getLastError(),
+ $prefix.'options' => self::getRedisOptions($c),
+ ];
+ }
+
+ public static function castRedisCluster(\RedisCluster $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $failover = $c->getOption(\RedisCluster::OPT_SLAVE_FAILOVER);
+
+ $a += [
+ $prefix.'_masters' => $c->_masters(),
+ $prefix.'_redir' => $c->_redir(),
+ $prefix.'mode' => new ConstStub($c->getMode() ? 'MULTI' : 'ATOMIC', $c->getMode()),
+ $prefix.'lastError' => $c->getLastError(),
+ $prefix.'options' => self::getRedisOptions($c, [
+ 'SLAVE_FAILOVER' => isset(self::$failover[$failover]) ? new ConstStub(self::$failover[$failover], $failover) : $failover,
+ ]),
+ ];
+
+ return $a;
+ }
+
+ /**
+ * @param \Redis|\RedisArray|\RedisCluster $redis
+ */
+ private static function getRedisOptions($redis, array $options = []): EnumStub
+ {
+ $serializer = $redis->getOption(\Redis::OPT_SERIALIZER);
+ if (\is_array($serializer)) {
+ foreach ($serializer as &$v) {
+ if (isset(self::$serializer[$v])) {
+ $v = new ConstStub(self::$serializer[$v], $v);
+ }
+ }
+ } elseif (isset(self::$serializer[$serializer])) {
+ $serializer = new ConstStub(self::$serializer[$serializer], $serializer);
+ }
+
+ $compression = \defined('Redis::OPT_COMPRESSION') ? $redis->getOption(\Redis::OPT_COMPRESSION) : 0;
+ if (\is_array($compression)) {
+ foreach ($compression as &$v) {
+ if (isset(self::$compression[$v])) {
+ $v = new ConstStub(self::$compression[$v], $v);
+ }
+ }
+ } elseif (isset(self::$compression[$compression])) {
+ $compression = new ConstStub(self::$compression[$compression], $compression);
+ }
+
+ $retry = \defined('Redis::OPT_SCAN') ? $redis->getOption(\Redis::OPT_SCAN) : 0;
+ if (\is_array($retry)) {
+ foreach ($retry as &$v) {
+ $v = new ConstStub($v ? 'RETRY' : 'NORETRY', $v);
+ }
+ } else {
+ $retry = new ConstStub($retry ? 'RETRY' : 'NORETRY', $retry);
+ }
+
+ $options += [
+ 'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : 0,
+ 'READ_TIMEOUT' => $redis->getOption(\Redis::OPT_READ_TIMEOUT),
+ 'COMPRESSION' => $compression,
+ 'SERIALIZER' => $serializer,
+ 'PREFIX' => $redis->getOption(\Redis::OPT_PREFIX),
+ 'SCAN' => $retry,
+ ];
+
+ return new EnumStub($options);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ReflectionCaster.php b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php
new file mode 100755
index 0000000..8dfe7ea
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php
@@ -0,0 +1,388 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts Reflector related classes to array representation.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class ReflectionCaster
+{
+ const UNSET_CLOSURE_FILE_INFO = ['Closure' => __CLASS__.'::unsetClosureFileInfo'];
+
+ private static $extraMap = [
+ 'docComment' => 'getDocComment',
+ 'extension' => 'getExtensionName',
+ 'isDisabled' => 'isDisabled',
+ 'isDeprecated' => 'isDeprecated',
+ 'isInternal' => 'isInternal',
+ 'isUserDefined' => 'isUserDefined',
+ 'isGenerator' => 'isGenerator',
+ 'isVariadic' => 'isVariadic',
+ ];
+
+ public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $c = new \ReflectionFunction($c);
+
+ $a = static::castFunctionAbstract($c, $a, $stub, $isNested, $filter);
+
+ if (false === strpos($c->name, '{closure}')) {
+ $stub->class = isset($a[$prefix.'class']) ? $a[$prefix.'class']->value.'::'.$c->name : $c->name;
+ unset($a[$prefix.'class']);
+ }
+ unset($a[$prefix.'extra']);
+
+ $stub->class .= self::getSignature($a);
+
+ if ($f = $c->getFileName()) {
+ $stub->attr['file'] = $f;
+ $stub->attr['line'] = $c->getStartLine();
+ }
+
+ unset($a[$prefix.'parameters']);
+
+ if ($filter & Caster::EXCLUDE_VERBOSE) {
+ $stub->cut += ($c->getFileName() ? 2 : 0) + \count($a);
+
+ return [];
+ }
+
+ if ($f) {
+ $a[$prefix.'file'] = new LinkStub($f, $c->getStartLine());
+ $a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine();
+ }
+
+ return $a;
+ }
+
+ public static function unsetClosureFileInfo(\Closure $c, array $a)
+ {
+ unset($a[Caster::PREFIX_VIRTUAL.'file'], $a[Caster::PREFIX_VIRTUAL.'line']);
+
+ return $a;
+ }
+
+ public static function castGenerator(\Generator $c, array $a, Stub $stub, $isNested)
+ {
+ if (!class_exists('ReflectionGenerator', false)) {
+ return $a;
+ }
+
+ // Cannot create ReflectionGenerator based on a terminated Generator
+ try {
+ $reflectionGenerator = new \ReflectionGenerator($c);
+ } catch (\Exception $e) {
+ $a[Caster::PREFIX_VIRTUAL.'closed'] = true;
+
+ return $a;
+ }
+
+ return self::castReflectionGenerator($reflectionGenerator, $a, $stub, $isNested);
+ }
+
+ public static function castType(\ReflectionType $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ $a += [
+ $prefix.'name' => $c->getName(),
+ $prefix.'allowsNull' => $c->allowsNull(),
+ $prefix.'isBuiltin' => $c->isBuiltin(),
+ ];
+
+ return $a;
+ }
+
+ public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ if ($c->getThis()) {
+ $a[$prefix.'this'] = new CutStub($c->getThis());
+ }
+ $function = $c->getFunction();
+ $frame = [
+ 'class' => isset($function->class) ? $function->class : null,
+ 'type' => isset($function->class) ? ($function->isStatic() ? '::' : '->') : null,
+ 'function' => $function->name,
+ 'file' => $c->getExecutingFile(),
+ 'line' => $c->getExecutingLine(),
+ ];
+ if ($trace = $c->getTrace(DEBUG_BACKTRACE_IGNORE_ARGS)) {
+ $function = new \ReflectionGenerator($c->getExecutingGenerator());
+ array_unshift($trace, [
+ 'function' => 'yield',
+ 'file' => $function->getExecutingFile(),
+ 'line' => $function->getExecutingLine() - 1,
+ ]);
+ $trace[] = $frame;
+ $a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1);
+ } else {
+ $function = new FrameStub($frame, false, true);
+ $function = ExceptionCaster::castFrameStub($function, [], $function, true);
+ $a[$prefix.'executing'] = new EnumStub([
+ "\0~separator= \0".$frame['class'].$frame['type'].$frame['function'].'()' => $function[$prefix.'src'],
+ ]);
+ }
+
+ $a[Caster::PREFIX_VIRTUAL.'closed'] = false;
+
+ return $a;
+ }
+
+ public static function castClass(\ReflectionClass $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ if ($n = \Reflection::getModifierNames($c->getModifiers())) {
+ $a[$prefix.'modifiers'] = implode(' ', $n);
+ }
+
+ self::addMap($a, $c, [
+ 'extends' => 'getParentClass',
+ 'implements' => 'getInterfaceNames',
+ 'constants' => 'getConstants',
+ ]);
+
+ foreach ($c->getProperties() as $n) {
+ $a[$prefix.'properties'][$n->name] = $n;
+ }
+
+ foreach ($c->getMethods() as $n) {
+ $a[$prefix.'methods'][$n->name] = $n;
+ }
+
+ if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) {
+ self::addExtra($a, $c);
+ }
+
+ return $a;
+ }
+
+ public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, array $a, Stub $stub, $isNested, $filter = 0)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ self::addMap($a, $c, [
+ 'returnsReference' => 'returnsReference',
+ 'returnType' => 'getReturnType',
+ 'class' => 'getClosureScopeClass',
+ 'this' => 'getClosureThis',
+ ]);
+
+ if (isset($a[$prefix.'returnType'])) {
+ $v = $a[$prefix.'returnType'];
+ $v = $v->getName();
+ $a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType']->allowsNull() ? '?'.$v : $v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']);
+ }
+ if (isset($a[$prefix.'class'])) {
+ $a[$prefix.'class'] = new ClassStub($a[$prefix.'class']);
+ }
+ if (isset($a[$prefix.'this'])) {
+ $a[$prefix.'this'] = new CutStub($a[$prefix.'this']);
+ }
+
+ foreach ($c->getParameters() as $v) {
+ $k = '$'.$v->name;
+ if ($v->isVariadic()) {
+ $k = '...'.$k;
+ }
+ if ($v->isPassedByReference()) {
+ $k = '&'.$k;
+ }
+ $a[$prefix.'parameters'][$k] = $v;
+ }
+ if (isset($a[$prefix.'parameters'])) {
+ $a[$prefix.'parameters'] = new EnumStub($a[$prefix.'parameters']);
+ }
+
+ if ($v = $c->getStaticVariables()) {
+ foreach ($v as $k => &$v) {
+ if (\is_object($v)) {
+ $a[$prefix.'use']['$'.$k] = new CutStub($v);
+ } else {
+ $a[$prefix.'use']['$'.$k] = &$v;
+ }
+ }
+ unset($v);
+ $a[$prefix.'use'] = new EnumStub($a[$prefix.'use']);
+ }
+
+ if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) {
+ self::addExtra($a, $c);
+ }
+
+ return $a;
+ }
+
+ public static function castMethod(\ReflectionMethod $c, array $a, Stub $stub, $isNested)
+ {
+ $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers()));
+
+ return $a;
+ }
+
+ public static function castParameter(\ReflectionParameter $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ self::addMap($a, $c, [
+ 'position' => 'getPosition',
+ 'isVariadic' => 'isVariadic',
+ 'byReference' => 'isPassedByReference',
+ 'allowsNull' => 'allowsNull',
+ ]);
+
+ if ($v = $c->getType()) {
+ $a[$prefix.'typeHint'] = $v->getName();
+ }
+
+ if (isset($a[$prefix.'typeHint'])) {
+ $v = $a[$prefix.'typeHint'];
+ $a[$prefix.'typeHint'] = new ClassStub($v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']);
+ } else {
+ unset($a[$prefix.'allowsNull']);
+ }
+
+ try {
+ $a[$prefix.'default'] = $v = $c->getDefaultValue();
+ if ($c->isDefaultValueConstant()) {
+ $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v);
+ }
+ if (null === $v) {
+ unset($a[$prefix.'allowsNull']);
+ }
+ } catch (\ReflectionException $e) {
+ }
+
+ return $a;
+ }
+
+ public static function castProperty(\ReflectionProperty $c, array $a, Stub $stub, $isNested)
+ {
+ $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers()));
+ self::addExtra($a, $c);
+
+ return $a;
+ }
+
+ public static function castReference(\ReflectionReference $c, array $a, Stub $stub, $isNested)
+ {
+ $a[Caster::PREFIX_VIRTUAL.'id'] = $c->getId();
+
+ return $a;
+ }
+
+ public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, $isNested)
+ {
+ self::addMap($a, $c, [
+ 'version' => 'getVersion',
+ 'dependencies' => 'getDependencies',
+ 'iniEntries' => 'getIniEntries',
+ 'isPersistent' => 'isPersistent',
+ 'isTemporary' => 'isTemporary',
+ 'constants' => 'getConstants',
+ 'functions' => 'getFunctions',
+ 'classes' => 'getClasses',
+ ]);
+
+ return $a;
+ }
+
+ public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, $isNested)
+ {
+ self::addMap($a, $c, [
+ 'version' => 'getVersion',
+ 'author' => 'getAuthor',
+ 'copyright' => 'getCopyright',
+ 'url' => 'getURL',
+ ]);
+
+ return $a;
+ }
+
+ public static function getSignature(array $a)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $signature = '';
+
+ if (isset($a[$prefix.'parameters'])) {
+ foreach ($a[$prefix.'parameters']->value as $k => $param) {
+ $signature .= ', ';
+ if ($type = $param->getType()) {
+ if (!$param->isOptional() && $param->allowsNull()) {
+ $signature .= '?';
+ }
+ $signature .= substr(strrchr('\\'.$type->getName(), '\\'), 1).' ';
+ }
+ $signature .= $k;
+
+ if (!$param->isDefaultValueAvailable()) {
+ continue;
+ }
+ $v = $param->getDefaultValue();
+ $signature .= ' = ';
+
+ if ($param->isDefaultValueConstant()) {
+ $signature .= substr(strrchr('\\'.$param->getDefaultValueConstantName(), '\\'), 1);
+ } elseif (null === $v) {
+ $signature .= 'null';
+ } elseif (\is_array($v)) {
+ $signature .= $v ? '[…'.\count($v).']' : '[]';
+ } elseif (\is_string($v)) {
+ $signature .= 10 > \strlen($v) && false === strpos($v, '\\') ? "'{$v}'" : "'…".\strlen($v)."'";
+ } elseif (\is_bool($v)) {
+ $signature .= $v ? 'true' : 'false';
+ } else {
+ $signature .= $v;
+ }
+ }
+ }
+ $signature = (empty($a[$prefix.'returnsReference']) ? '' : '&').'('.substr($signature, 2).')';
+
+ if (isset($a[$prefix.'returnType'])) {
+ $signature .= ': '.substr(strrchr('\\'.$a[$prefix.'returnType'], '\\'), 1);
+ }
+
+ return $signature;
+ }
+
+ private static function addExtra(&$a, \Reflector $c)
+ {
+ $x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : [];
+
+ if (method_exists($c, 'getFileName') && $m = $c->getFileName()) {
+ $x['file'] = new LinkStub($m, $c->getStartLine());
+ $x['line'] = $c->getStartLine().' to '.$c->getEndLine();
+ }
+
+ self::addMap($x, $c, self::$extraMap, '');
+
+ if ($x) {
+ $a[Caster::PREFIX_VIRTUAL.'extra'] = new EnumStub($x);
+ }
+ }
+
+ private static function addMap(&$a, \Reflector $c, $map, $prefix = Caster::PREFIX_VIRTUAL)
+ {
+ foreach ($map as $k => $m) {
+ if (method_exists($c, $m) && false !== ($m = $c->$m()) && null !== $m) {
+ $a[$prefix.$k] = $m instanceof \Reflector ? $m->name : $m;
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ResourceCaster.php b/vendor/symfony/var-dumper/Caster/ResourceCaster.php
new file mode 100755
index 0000000..5d9b80d
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/ResourceCaster.php
@@ -0,0 +1,98 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts common resource types to array representation.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class ResourceCaster
+{
+ public static function castCurl($h, array $a, Stub $stub, $isNested)
+ {
+ return curl_getinfo($h);
+ }
+
+ public static function castDba($dba, array $a, Stub $stub, $isNested)
+ {
+ $list = dba_list();
+ $a['file'] = $list[(int) $dba];
+
+ return $a;
+ }
+
+ public static function castProcess($process, array $a, Stub $stub, $isNested)
+ {
+ return proc_get_status($process);
+ }
+
+ public static function castStream($stream, array $a, Stub $stub, $isNested)
+ {
+ $a = stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested);
+ if (isset($a['uri'])) {
+ $a['uri'] = new LinkStub($a['uri']);
+ }
+
+ return $a;
+ }
+
+ public static function castStreamContext($stream, array $a, Stub $stub, $isNested)
+ {
+ return @stream_context_get_params($stream) ?: $a;
+ }
+
+ public static function castGd($gd, array $a, Stub $stub, $isNested)
+ {
+ $a['size'] = imagesx($gd).'x'.imagesy($gd);
+ $a['trueColor'] = imageistruecolor($gd);
+
+ return $a;
+ }
+
+ public static function castMysqlLink($h, array $a, Stub $stub, $isNested)
+ {
+ $a['host'] = mysql_get_host_info($h);
+ $a['protocol'] = mysql_get_proto_info($h);
+ $a['server'] = mysql_get_server_info($h);
+
+ return $a;
+ }
+
+ public static function castOpensslX509($h, array $a, Stub $stub, $isNested)
+ {
+ $stub->cut = -1;
+ $info = openssl_x509_parse($h, false);
+
+ $pin = openssl_pkey_get_public($h);
+ $pin = openssl_pkey_get_details($pin)['key'];
+ $pin = \array_slice(explode("\n", $pin), 1, -2);
+ $pin = base64_decode(implode('', $pin));
+ $pin = base64_encode(hash('sha256', $pin, true));
+
+ $a += [
+ 'subject' => new EnumStub(array_intersect_key($info['subject'], ['organizationName' => true, 'commonName' => true])),
+ 'issuer' => new EnumStub(array_intersect_key($info['issuer'], ['organizationName' => true, 'commonName' => true])),
+ 'expiry' => new ConstStub(date(\DateTime::ISO8601, $info['validTo_time_t']), $info['validTo_time_t']),
+ 'fingerprint' => new EnumStub([
+ 'md5' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'md5')), 2, ':', true)),
+ 'sha1' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha1')), 2, ':', true)),
+ 'sha256' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha256')), 2, ':', true)),
+ 'pin-sha256' => new ConstStub($pin),
+ ]),
+ ];
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/SplCaster.php b/vendor/symfony/var-dumper/Caster/SplCaster.php
new file mode 100755
index 0000000..37260b5
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/SplCaster.php
@@ -0,0 +1,220 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts SPL related classes to array representation.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class SplCaster
+{
+ private static $splFileObjectFlags = [
+ \SplFileObject::DROP_NEW_LINE => 'DROP_NEW_LINE',
+ \SplFileObject::READ_AHEAD => 'READ_AHEAD',
+ \SplFileObject::SKIP_EMPTY => 'SKIP_EMPTY',
+ \SplFileObject::READ_CSV => 'READ_CSV',
+ ];
+
+ public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, $isNested)
+ {
+ return self::castSplArray($c, $a, $stub, $isNested);
+ }
+
+ public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, $isNested)
+ {
+ return self::castSplArray($c, $a, $stub, $isNested);
+ }
+
+ public static function castHeap(\Iterator $c, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'heap' => iterator_to_array(clone $c),
+ ];
+
+ return $a;
+ }
+
+ public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $mode = $c->getIteratorMode();
+ $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE);
+
+ $a += [
+ $prefix.'mode' => new ConstStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_DELETE) ? 'IT_MODE_DELETE' : 'IT_MODE_KEEP'), $mode),
+ $prefix.'dllist' => iterator_to_array($c),
+ ];
+ $c->setIteratorMode($mode);
+
+ return $a;
+ }
+
+ public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, $isNested)
+ {
+ static $map = [
+ 'path' => 'getPath',
+ 'filename' => 'getFilename',
+ 'basename' => 'getBasename',
+ 'pathname' => 'getPathname',
+ 'extension' => 'getExtension',
+ 'realPath' => 'getRealPath',
+ 'aTime' => 'getATime',
+ 'mTime' => 'getMTime',
+ 'cTime' => 'getCTime',
+ 'inode' => 'getInode',
+ 'size' => 'getSize',
+ 'perms' => 'getPerms',
+ 'owner' => 'getOwner',
+ 'group' => 'getGroup',
+ 'type' => 'getType',
+ 'writable' => 'isWritable',
+ 'readable' => 'isReadable',
+ 'executable' => 'isExecutable',
+ 'file' => 'isFile',
+ 'dir' => 'isDir',
+ 'link' => 'isLink',
+ 'linkTarget' => 'getLinkTarget',
+ ];
+
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ foreach ($map as $key => $accessor) {
+ try {
+ $a[$prefix.$key] = $c->$accessor();
+ } catch (\Exception $e) {
+ }
+ }
+
+ if (isset($a[$prefix.'realPath'])) {
+ $a[$prefix.'realPath'] = new LinkStub($a[$prefix.'realPath']);
+ }
+
+ if (isset($a[$prefix.'perms'])) {
+ $a[$prefix.'perms'] = new ConstStub(sprintf('0%o', $a[$prefix.'perms']), $a[$prefix.'perms']);
+ }
+
+ static $mapDate = ['aTime', 'mTime', 'cTime'];
+ foreach ($mapDate as $key) {
+ if (isset($a[$prefix.$key])) {
+ $a[$prefix.$key] = new ConstStub(date('Y-m-d H:i:s', $a[$prefix.$key]), $a[$prefix.$key]);
+ }
+ }
+
+ return $a;
+ }
+
+ public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, $isNested)
+ {
+ static $map = [
+ 'csvControl' => 'getCsvControl',
+ 'flags' => 'getFlags',
+ 'maxLineLen' => 'getMaxLineLen',
+ 'fstat' => 'fstat',
+ 'eof' => 'eof',
+ 'key' => 'key',
+ ];
+
+ $prefix = Caster::PREFIX_VIRTUAL;
+
+ foreach ($map as $key => $accessor) {
+ try {
+ $a[$prefix.$key] = $c->$accessor();
+ } catch (\Exception $e) {
+ }
+ }
+
+ if (isset($a[$prefix.'flags'])) {
+ $flagsArray = [];
+ foreach (self::$splFileObjectFlags as $value => $name) {
+ if ($a[$prefix.'flags'] & $value) {
+ $flagsArray[] = $name;
+ }
+ }
+ $a[$prefix.'flags'] = new ConstStub(implode('|', $flagsArray), $a[$prefix.'flags']);
+ }
+
+ if (isset($a[$prefix.'fstat'])) {
+ $a[$prefix.'fstat'] = new CutArrayStub($a[$prefix.'fstat'], ['dev', 'ino', 'nlink', 'rdev', 'blksize', 'blocks']);
+ }
+
+ return $a;
+ }
+
+ public static function castFixedArray(\SplFixedArray $c, array $a, Stub $stub, $isNested)
+ {
+ $a += [
+ Caster::PREFIX_VIRTUAL.'storage' => $c->toArray(),
+ ];
+
+ return $a;
+ }
+
+ public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, $isNested)
+ {
+ $storage = [];
+ unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967
+
+ $clone = clone $c;
+ foreach ($clone as $obj) {
+ $storage[] = [
+ 'object' => $obj,
+ 'info' => $clone->getInfo(),
+ ];
+ }
+
+ $a += [
+ Caster::PREFIX_VIRTUAL.'storage' => $storage,
+ ];
+
+ return $a;
+ }
+
+ public static function castOuterIterator(\OuterIterator $c, array $a, Stub $stub, $isNested)
+ {
+ $a[Caster::PREFIX_VIRTUAL.'innerIterator'] = $c->getInnerIterator();
+
+ return $a;
+ }
+
+ public static function castWeakReference(\WeakReference $c, array $a, Stub $stub, $isNested)
+ {
+ $a[Caster::PREFIX_VIRTUAL.'object'] = $c->get();
+
+ return $a;
+ }
+
+ private static function castSplArray($c, array $a, Stub $stub, $isNested)
+ {
+ $prefix = Caster::PREFIX_VIRTUAL;
+ $class = $stub->class;
+ $flags = $c->getFlags();
+
+ if (!($flags & \ArrayObject::STD_PROP_LIST)) {
+ $c->setFlags(\ArrayObject::STD_PROP_LIST);
+ $a = Caster::castObject($c, $class);
+ $c->setFlags($flags);
+ }
+ $a += [
+ $prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST),
+ $prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS),
+ ];
+ if ($c instanceof \ArrayObject) {
+ $a[$prefix.'iteratorClass'] = new ClassStub($c->getIteratorClass());
+ }
+ $a[$prefix.'storage'] = $c->getArrayCopy();
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/StubCaster.php b/vendor/symfony/var-dumper/Caster/StubCaster.php
new file mode 100755
index 0000000..9927d42
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/StubCaster.php
@@ -0,0 +1,82 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts a caster's Stub.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class StubCaster
+{
+ public static function castStub(Stub $c, array $a, Stub $stub, $isNested)
+ {
+ if ($isNested) {
+ $stub->type = $c->type;
+ $stub->class = $c->class;
+ $stub->value = $c->value;
+ $stub->handle = $c->handle;
+ $stub->cut = $c->cut;
+ $stub->attr = $c->attr;
+
+ if (Stub::TYPE_REF === $c->type && !$c->class && \is_string($c->value) && !preg_match('//u', $c->value)) {
+ $stub->type = Stub::TYPE_STRING;
+ $stub->class = Stub::STRING_BINARY;
+ }
+
+ $a = [];
+ }
+
+ return $a;
+ }
+
+ public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, $isNested)
+ {
+ return $isNested ? $c->preservedSubset : $a;
+ }
+
+ public static function cutInternals($obj, array $a, Stub $stub, $isNested)
+ {
+ if ($isNested) {
+ $stub->cut += \count($a);
+
+ return [];
+ }
+
+ return $a;
+ }
+
+ public static function castEnum(EnumStub $c, array $a, Stub $stub, $isNested)
+ {
+ if ($isNested) {
+ $stub->class = $c->dumpKeys ? '' : null;
+ $stub->handle = 0;
+ $stub->value = null;
+ $stub->cut = $c->cut;
+ $stub->attr = $c->attr;
+
+ $a = [];
+
+ if ($c->value) {
+ foreach (array_keys($c->value) as $k) {
+ $keys[] = !isset($k[0]) || "\0" !== $k[0] ? Caster::PREFIX_VIRTUAL.$k : $k;
+ }
+ // Preserve references with array_combine()
+ $a = array_combine($keys, $c->value);
+ }
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/SymfonyCaster.php b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php
new file mode 100755
index 0000000..78acb90
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php
@@ -0,0 +1,51 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+class SymfonyCaster
+{
+ private static $requestGetters = [
+ 'pathInfo' => 'getPathInfo',
+ 'requestUri' => 'getRequestUri',
+ 'baseUrl' => 'getBaseUrl',
+ 'basePath' => 'getBasePath',
+ 'method' => 'getMethod',
+ 'format' => 'getRequestFormat',
+ ];
+
+ public static function castRequest(Request $request, array $a, Stub $stub, $isNested)
+ {
+ $clone = null;
+
+ foreach (self::$requestGetters as $prop => $getter) {
+ if (null === $a[Caster::PREFIX_PROTECTED.$prop]) {
+ if (null === $clone) {
+ $clone = clone $request;
+ }
+ $a[Caster::PREFIX_VIRTUAL.$prop] = $clone->{$getter}();
+ }
+ }
+
+ return $a;
+ }
+
+ public static function castHttpClient($client, array $a, Stub $stub, $isNested)
+ {
+ $multiKey = sprintf("\0%s\0multi", \get_class($client));
+ $a[$multiKey] = new CutStub($a[$multiKey]);
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/TraceStub.php b/vendor/symfony/var-dumper/Caster/TraceStub.php
new file mode 100755
index 0000000..5eea1c8
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/TraceStub.php
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Represents a backtrace as returned by debug_backtrace() or Exception->getTrace().
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class TraceStub extends Stub
+{
+ public $keepArgs;
+ public $sliceOffset;
+ public $sliceLength;
+ public $numberingOffset;
+
+ public function __construct(array $trace, bool $keepArgs = true, int $sliceOffset = 0, int $sliceLength = null, int $numberingOffset = 0)
+ {
+ $this->value = $trace;
+ $this->keepArgs = $keepArgs;
+ $this->sliceOffset = $sliceOffset;
+ $this->sliceLength = $sliceLength;
+ $this->numberingOffset = $numberingOffset;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php
new file mode 100755
index 0000000..3ae9ec0
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php
@@ -0,0 +1,77 @@
+<?php
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts XmlReader class to array representation.
+ *
+ * @author Baptiste Clavié <clavie.b@gmail.com>
+ */
+class XmlReaderCaster
+{
+ private static $nodeTypes = [
+ \XMLReader::NONE => 'NONE',
+ \XMLReader::ELEMENT => 'ELEMENT',
+ \XMLReader::ATTRIBUTE => 'ATTRIBUTE',
+ \XMLReader::TEXT => 'TEXT',
+ \XMLReader::CDATA => 'CDATA',
+ \XMLReader::ENTITY_REF => 'ENTITY_REF',
+ \XMLReader::ENTITY => 'ENTITY',
+ \XMLReader::PI => 'PI (Processing Instruction)',
+ \XMLReader::COMMENT => 'COMMENT',
+ \XMLReader::DOC => 'DOC',
+ \XMLReader::DOC_TYPE => 'DOC_TYPE',
+ \XMLReader::DOC_FRAGMENT => 'DOC_FRAGMENT',
+ \XMLReader::NOTATION => 'NOTATION',
+ \XMLReader::WHITESPACE => 'WHITESPACE',
+ \XMLReader::SIGNIFICANT_WHITESPACE => 'SIGNIFICANT_WHITESPACE',
+ \XMLReader::END_ELEMENT => 'END_ELEMENT',
+ \XMLReader::END_ENTITY => 'END_ENTITY',
+ \XMLReader::XML_DECLARATION => 'XML_DECLARATION',
+ ];
+
+ public static function castXmlReader(\XMLReader $reader, array $a, Stub $stub, $isNested)
+ {
+ $props = Caster::PREFIX_VIRTUAL.'parserProperties';
+ $info = [
+ 'localName' => $reader->localName,
+ 'prefix' => $reader->prefix,
+ 'nodeType' => new ConstStub(self::$nodeTypes[$reader->nodeType], $reader->nodeType),
+ 'depth' => $reader->depth,
+ 'isDefault' => $reader->isDefault,
+ 'isEmptyElement' => \XMLReader::NONE === $reader->nodeType ? null : $reader->isEmptyElement,
+ 'xmlLang' => $reader->xmlLang,
+ 'attributeCount' => $reader->attributeCount,
+ 'value' => $reader->value,
+ 'namespaceURI' => $reader->namespaceURI,
+ 'baseURI' => $reader->baseURI ? new LinkStub($reader->baseURI) : $reader->baseURI,
+ $props => [
+ 'LOADDTD' => $reader->getParserProperty(\XMLReader::LOADDTD),
+ 'DEFAULTATTRS' => $reader->getParserProperty(\XMLReader::DEFAULTATTRS),
+ 'VALIDATE' => $reader->getParserProperty(\XMLReader::VALIDATE),
+ 'SUBST_ENTITIES' => $reader->getParserProperty(\XMLReader::SUBST_ENTITIES),
+ ],
+ ];
+
+ if ($info[$props] = Caster::filter($info[$props], Caster::EXCLUDE_EMPTY, [], $count)) {
+ $info[$props] = new EnumStub($info[$props]);
+ $info[$props]->cut = $count;
+ }
+
+ $info = Caster::filter($info, Caster::EXCLUDE_EMPTY, [], $count);
+ // +2 because hasValue and hasAttributes are always filtered
+ $stub->cut += $count + 2;
+
+ return $a + $info;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php
new file mode 100755
index 0000000..117138c
--- /dev/null
+++ b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * Casts XML resources to array representation.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class XmlResourceCaster
+{
+ private static $xmlErrors = [
+ XML_ERROR_NONE => 'XML_ERROR_NONE',
+ XML_ERROR_NO_MEMORY => 'XML_ERROR_NO_MEMORY',
+ XML_ERROR_SYNTAX => 'XML_ERROR_SYNTAX',
+ XML_ERROR_NO_ELEMENTS => 'XML_ERROR_NO_ELEMENTS',
+ XML_ERROR_INVALID_TOKEN => 'XML_ERROR_INVALID_TOKEN',
+ XML_ERROR_UNCLOSED_TOKEN => 'XML_ERROR_UNCLOSED_TOKEN',
+ XML_ERROR_PARTIAL_CHAR => 'XML_ERROR_PARTIAL_CHAR',
+ XML_ERROR_TAG_MISMATCH => 'XML_ERROR_TAG_MISMATCH',
+ XML_ERROR_DUPLICATE_ATTRIBUTE => 'XML_ERROR_DUPLICATE_ATTRIBUTE',
+ XML_ERROR_JUNK_AFTER_DOC_ELEMENT => 'XML_ERROR_JUNK_AFTER_DOC_ELEMENT',
+ XML_ERROR_PARAM_ENTITY_REF => 'XML_ERROR_PARAM_ENTITY_REF',
+ XML_ERROR_UNDEFINED_ENTITY => 'XML_ERROR_UNDEFINED_ENTITY',
+ XML_ERROR_RECURSIVE_ENTITY_REF => 'XML_ERROR_RECURSIVE_ENTITY_REF',
+ XML_ERROR_ASYNC_ENTITY => 'XML_ERROR_ASYNC_ENTITY',
+ XML_ERROR_BAD_CHAR_REF => 'XML_ERROR_BAD_CHAR_REF',
+ XML_ERROR_BINARY_ENTITY_REF => 'XML_ERROR_BINARY_ENTITY_REF',
+ XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF => 'XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF',
+ XML_ERROR_MISPLACED_XML_PI => 'XML_ERROR_MISPLACED_XML_PI',
+ XML_ERROR_UNKNOWN_ENCODING => 'XML_ERROR_UNKNOWN_ENCODING',
+ XML_ERROR_INCORRECT_ENCODING => 'XML_ERROR_INCORRECT_ENCODING',
+ XML_ERROR_UNCLOSED_CDATA_SECTION => 'XML_ERROR_UNCLOSED_CDATA_SECTION',
+ XML_ERROR_EXTERNAL_ENTITY_HANDLING => 'XML_ERROR_EXTERNAL_ENTITY_HANDLING',
+ ];
+
+ public static function castXml($h, array $a, Stub $stub, $isNested)
+ {
+ $a['current_byte_index'] = xml_get_current_byte_index($h);
+ $a['current_column_number'] = xml_get_current_column_number($h);
+ $a['current_line_number'] = xml_get_current_line_number($h);
+ $a['error_code'] = xml_get_error_code($h);
+
+ if (isset(self::$xmlErrors[$a['error_code']])) {
+ $a['error_code'] = new ConstStub(self::$xmlErrors[$a['error_code']], $a['error_code']);
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Cloner/AbstractCloner.php b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php
new file mode 100755
index 0000000..aacab2c
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php
@@ -0,0 +1,360 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+use Symfony\Component\VarDumper\Caster\Caster;
+use Symfony\Component\VarDumper\Exception\ThrowingCasterException;
+
+/**
+ * AbstractCloner implements a generic caster mechanism for objects and resources.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+abstract class AbstractCloner implements ClonerInterface
+{
+ public static $defaultCasters = [
+ '__PHP_Incomplete_Class' => ['Symfony\Component\VarDumper\Caster\Caster', 'castPhpIncompleteClass'],
+
+ 'Symfony\Component\VarDumper\Caster\CutStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'],
+ 'Symfony\Component\VarDumper\Caster\CutArrayStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'],
+ 'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'],
+ 'Symfony\Component\VarDumper\Caster\EnumStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'],
+
+ 'Closure' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'],
+ 'Generator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'],
+ 'ReflectionType' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'],
+ 'ReflectionGenerator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'],
+ 'ReflectionClass' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'],
+ 'ReflectionFunctionAbstract' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'],
+ 'ReflectionMethod' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'],
+ 'ReflectionParameter' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'],
+ 'ReflectionProperty' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castProperty'],
+ 'ReflectionReference' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReference'],
+ 'ReflectionExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castExtension'],
+ 'ReflectionZendExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castZendExtension'],
+
+ 'Doctrine\Common\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+ 'Doctrine\Common\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castCommonProxy'],
+ 'Doctrine\ORM\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castOrmProxy'],
+ 'Doctrine\ORM\PersistentCollection' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castPersistentCollection'],
+
+ 'DOMException' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'],
+ 'DOMStringList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'],
+ 'DOMNameList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'],
+ 'DOMImplementation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'],
+ 'DOMImplementationList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'],
+ 'DOMNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'],
+ 'DOMNameSpaceNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNameSpaceNode'],
+ 'DOMDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocument'],
+ 'DOMNodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'],
+ 'DOMNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'],
+ 'DOMCharacterData' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'],
+ 'DOMAttr' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'],
+ 'DOMElement' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'],
+ 'DOMText' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'],
+ 'DOMTypeinfo' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castTypeinfo'],
+ 'DOMDomError' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDomError'],
+ 'DOMLocator' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLocator'],
+ 'DOMDocumentType' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'],
+ 'DOMNotation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'],
+ 'DOMEntity' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'],
+ 'DOMProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'],
+ 'DOMXPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'],
+
+ 'XMLReader' => ['Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'],
+
+ 'ErrorException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castErrorException'],
+ 'Exception' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castException'],
+ 'Error' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castError'],
+ 'Symfony\Component\DependencyInjection\ContainerInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+ 'Symfony\Component\HttpClient\CurlHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'],
+ 'Symfony\Component\HttpClient\NativeHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'],
+ 'Symfony\Component\HttpClient\Response\CurlResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'],
+ 'Symfony\Component\HttpClient\Response\NativeResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'],
+ 'Symfony\Component\HttpFoundation\Request' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'],
+ 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'],
+ 'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'],
+ 'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'],
+ 'Symfony\Component\VarDumper\Cloner\AbstractCloner' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+ 'Symfony\Component\Debug\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'],
+
+ 'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'],
+ 'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+ 'Prophecy\Prophecy\ProphecySubjectInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+ 'Mockery\MockInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
+
+ 'PDO' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdo'],
+ 'PDOStatement' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdoStatement'],
+
+ 'AMQPConnection' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castConnection'],
+ 'AMQPChannel' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castChannel'],
+ 'AMQPQueue' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castQueue'],
+ 'AMQPExchange' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castExchange'],
+ 'AMQPEnvelope' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'],
+
+ 'ArrayObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'],
+ 'ArrayIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayIterator'],
+ 'SplDoublyLinkedList' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'],
+ 'SplFileInfo' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'],
+ 'SplFileObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'],
+ 'SplFixedArray' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFixedArray'],
+ 'SplHeap' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'],
+ 'SplObjectStorage' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'],
+ 'SplPriorityQueue' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'],
+ 'OuterIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'],
+ 'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'],
+
+ 'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'],
+ 'RedisArray' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'],
+ 'RedisCluster' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisCluster'],
+
+ 'DateTimeInterface' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'],
+ 'DateInterval' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'],
+ 'DateTimeZone' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castTimeZone'],
+ 'DatePeriod' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castPeriod'],
+
+ 'GMP' => ['Symfony\Component\VarDumper\Caster\GmpCaster', 'castGmp'],
+
+ 'MessageFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castMessageFormatter'],
+ 'NumberFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castNumberFormatter'],
+ 'IntlTimeZone' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlTimeZone'],
+ 'IntlCalendar' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlCalendar'],
+ 'IntlDateFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlDateFormatter'],
+
+ 'Memcached' => ['Symfony\Component\VarDumper\Caster\MemcachedCaster', 'castMemcached'],
+
+ 'Ds\Collection' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castCollection'],
+ 'Ds\Map' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castMap'],
+ 'Ds\Pair' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPair'],
+ 'Symfony\Component\VarDumper\Caster\DsPairStub' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPairStub'],
+
+ ':curl' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'],
+ ':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'],
+ ':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'],
+ ':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'],
+ ':mysql link' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castMysqlLink'],
+ ':pgsql large object' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'],
+ ':pgsql link' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'],
+ ':pgsql link persistent' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'],
+ ':pgsql result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'],
+ ':process' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'],
+ ':stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'],
+ ':OpenSSL X.509' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'],
+ ':persistent stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'],
+ ':stream-context' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'],
+ ':xml' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'],
+ ];
+
+ protected $maxItems = 2500;
+ protected $maxString = -1;
+ protected $minDepth = 1;
+
+ private $casters = [];
+ private $prevErrorHandler;
+ private $classInfo = [];
+ private $filter = 0;
+
+ /**
+ * @param callable[]|null $casters A map of casters
+ *
+ * @see addCasters
+ */
+ public function __construct(array $casters = null)
+ {
+ if (null === $casters) {
+ $casters = static::$defaultCasters;
+ }
+ $this->addCasters($casters);
+ }
+
+ /**
+ * Adds casters for resources and objects.
+ *
+ * Maps resources or objects types to a callback.
+ * Types are in the key, with a callable caster for value.
+ * Resource types are to be prefixed with a `:`,
+ * see e.g. static::$defaultCasters.
+ *
+ * @param callable[] $casters A map of casters
+ */
+ public function addCasters(array $casters)
+ {
+ foreach ($casters as $type => $callback) {
+ $this->casters[$type][] = $callback;
+ }
+ }
+
+ /**
+ * Sets the maximum number of items to clone past the minimum depth in nested structures.
+ *
+ * @param int $maxItems
+ */
+ public function setMaxItems($maxItems)
+ {
+ $this->maxItems = (int) $maxItems;
+ }
+
+ /**
+ * Sets the maximum cloned length for strings.
+ *
+ * @param int $maxString
+ */
+ public function setMaxString($maxString)
+ {
+ $this->maxString = (int) $maxString;
+ }
+
+ /**
+ * Sets the minimum tree depth where we are guaranteed to clone all the items. After this
+ * depth is reached, only setMaxItems items will be cloned.
+ *
+ * @param int $minDepth
+ */
+ public function setMinDepth($minDepth)
+ {
+ $this->minDepth = (int) $minDepth;
+ }
+
+ /**
+ * Clones a PHP variable.
+ *
+ * @param mixed $var Any PHP variable
+ * @param int $filter A bit field of Caster::EXCLUDE_* constants
+ *
+ * @return Data The cloned variable represented by a Data object
+ */
+ public function cloneVar($var, $filter = 0)
+ {
+ $this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) {
+ if (E_RECOVERABLE_ERROR === $type || E_USER_ERROR === $type) {
+ // Cloner never dies
+ throw new \ErrorException($msg, 0, $type, $file, $line);
+ }
+
+ if ($this->prevErrorHandler) {
+ return ($this->prevErrorHandler)($type, $msg, $file, $line, $context);
+ }
+
+ return false;
+ });
+ $this->filter = $filter;
+
+ if ($gc = gc_enabled()) {
+ gc_disable();
+ }
+ try {
+ return new Data($this->doClone($var));
+ } finally {
+ if ($gc) {
+ gc_enable();
+ }
+ restore_error_handler();
+ $this->prevErrorHandler = null;
+ }
+ }
+
+ /**
+ * Effectively clones the PHP variable.
+ *
+ * @param mixed $var Any PHP variable
+ *
+ * @return array The cloned variable represented in an array
+ */
+ abstract protected function doClone($var);
+
+ /**
+ * Casts an object to an array representation.
+ *
+ * @param Stub $stub The Stub for the casted object
+ * @param bool $isNested True if the object is nested in the dumped structure
+ *
+ * @return array The object casted as array
+ */
+ protected function castObject(Stub $stub, $isNested)
+ {
+ $obj = $stub->value;
+ $class = $stub->class;
+
+ if (isset($class[15]) && "\0" === $class[15] && 0 === strpos($class, "class@anonymous\x00")) {
+ $stub->class = get_parent_class($class).'@anonymous';
+ }
+ if (isset($this->classInfo[$class])) {
+ list($i, $parents, $hasDebugInfo, $fileInfo) = $this->classInfo[$class];
+ } else {
+ $i = 2;
+ $parents = [$class];
+ $hasDebugInfo = method_exists($class, '__debugInfo');
+
+ foreach (class_parents($class) as $p) {
+ $parents[] = $p;
+ ++$i;
+ }
+ foreach (class_implements($class) as $p) {
+ $parents[] = $p;
+ ++$i;
+ }
+ $parents[] = '*';
+
+ $r = new \ReflectionClass($class);
+ $fileInfo = $r->isInternal() || $r->isSubclassOf(Stub::class) ? [] : [
+ 'file' => $r->getFileName(),
+ 'line' => $r->getStartLine(),
+ ];
+
+ $this->classInfo[$class] = [$i, $parents, $hasDebugInfo, $fileInfo];
+ }
+
+ $stub->attr += $fileInfo;
+ $a = Caster::castObject($obj, $class, $hasDebugInfo);
+
+ try {
+ while ($i--) {
+ if (!empty($this->casters[$p = $parents[$i]])) {
+ foreach ($this->casters[$p] as $callback) {
+ $a = $callback($obj, $a, $stub, $isNested, $this->filter);
+ }
+ }
+ }
+ } catch (\Exception $e) {
+ $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a;
+ }
+
+ return $a;
+ }
+
+ /**
+ * Casts a resource to an array representation.
+ *
+ * @param Stub $stub The Stub for the casted resource
+ * @param bool $isNested True if the object is nested in the dumped structure
+ *
+ * @return array The resource casted as array
+ */
+ protected function castResource(Stub $stub, $isNested)
+ {
+ $a = [];
+ $res = $stub->value;
+ $type = $stub->class;
+
+ try {
+ if (!empty($this->casters[':'.$type])) {
+ foreach ($this->casters[':'.$type] as $callback) {
+ $a = $callback($res, $a, $stub, $isNested, $this->filter);
+ }
+ }
+ } catch (\Exception $e) {
+ $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a;
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Cloner/ClonerInterface.php b/vendor/symfony/var-dumper/Cloner/ClonerInterface.php
new file mode 100755
index 0000000..7ed287a
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/ClonerInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+interface ClonerInterface
+{
+ /**
+ * Clones a PHP variable.
+ *
+ * @param mixed $var Any PHP variable
+ *
+ * @return Data The cloned variable represented by a Data object
+ */
+ public function cloneVar($var);
+}
diff --git a/vendor/symfony/var-dumper/Cloner/Cursor.php b/vendor/symfony/var-dumper/Cloner/Cursor.php
new file mode 100755
index 0000000..5b0542f
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/Cursor.php
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+/**
+ * Represents the current state of a dumper while dumping.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class Cursor
+{
+ const HASH_INDEXED = Stub::ARRAY_INDEXED;
+ const HASH_ASSOC = Stub::ARRAY_ASSOC;
+ const HASH_OBJECT = Stub::TYPE_OBJECT;
+ const HASH_RESOURCE = Stub::TYPE_RESOURCE;
+
+ public $depth = 0;
+ public $refIndex = 0;
+ public $softRefTo = 0;
+ public $softRefCount = 0;
+ public $softRefHandle = 0;
+ public $hardRefTo = 0;
+ public $hardRefCount = 0;
+ public $hardRefHandle = 0;
+ public $hashType;
+ public $hashKey;
+ public $hashKeyIsBinary;
+ public $hashIndex = 0;
+ public $hashLength = 0;
+ public $hashCut = 0;
+ public $stop = false;
+ public $attr = [];
+ public $skipChildren = false;
+}
diff --git a/vendor/symfony/var-dumper/Cloner/Data.php b/vendor/symfony/var-dumper/Cloner/Data.php
new file mode 100755
index 0000000..838aeb0
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/Data.php
@@ -0,0 +1,423 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+use Symfony\Component\VarDumper\Caster\Caster;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class Data implements \ArrayAccess, \Countable, \IteratorAggregate
+{
+ private $data;
+ private $position = 0;
+ private $key = 0;
+ private $maxDepth = 20;
+ private $maxItemsPerDepth = -1;
+ private $useRefHandles = -1;
+
+ /**
+ * @param array $data An array as returned by ClonerInterface::cloneVar()
+ */
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * @return string The type of the value
+ */
+ public function getType()
+ {
+ $item = $this->data[$this->position][$this->key];
+
+ if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
+ $item = $item->value;
+ }
+ if (!$item instanceof Stub) {
+ return \gettype($item);
+ }
+ if (Stub::TYPE_STRING === $item->type) {
+ return 'string';
+ }
+ if (Stub::TYPE_ARRAY === $item->type) {
+ return 'array';
+ }
+ if (Stub::TYPE_OBJECT === $item->type) {
+ return $item->class;
+ }
+ if (Stub::TYPE_RESOURCE === $item->type) {
+ return $item->class.' resource';
+ }
+ }
+
+ /**
+ * @param bool $recursive Whether values should be resolved recursively or not
+ *
+ * @return string|int|float|bool|array|Data[]|null A native representation of the original value
+ */
+ public function getValue($recursive = false)
+ {
+ $item = $this->data[$this->position][$this->key];
+
+ if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
+ $item = $item->value;
+ }
+ if (!($item = $this->getStub($item)) instanceof Stub) {
+ return $item;
+ }
+ if (Stub::TYPE_STRING === $item->type) {
+ return $item->value;
+ }
+
+ $children = $item->position ? $this->data[$item->position] : [];
+
+ foreach ($children as $k => $v) {
+ if ($recursive && !($v = $this->getStub($v)) instanceof Stub) {
+ continue;
+ }
+ $children[$k] = clone $this;
+ $children[$k]->key = $k;
+ $children[$k]->position = $item->position;
+
+ if ($recursive) {
+ if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) {
+ $recursive = (array) $recursive;
+ if (isset($recursive[$v->position])) {
+ continue;
+ }
+ $recursive[$v->position] = true;
+ }
+ $children[$k] = $children[$k]->getValue($recursive);
+ }
+ }
+
+ return $children;
+ }
+
+ public function count()
+ {
+ return \count($this->getValue());
+ }
+
+ public function getIterator()
+ {
+ if (!\is_array($value = $this->getValue())) {
+ throw new \LogicException(sprintf('%s object holds non-iterable type "%s".', self::class, \gettype($value)));
+ }
+
+ yield from $value;
+ }
+
+ public function __get($key)
+ {
+ if (null !== $data = $this->seek($key)) {
+ $item = $this->getStub($data->data[$data->position][$data->key]);
+
+ return $item instanceof Stub || [] === $item ? $data : $item;
+ }
+ }
+
+ public function __isset($key)
+ {
+ return null !== $this->seek($key);
+ }
+
+ public function offsetExists($key)
+ {
+ return $this->__isset($key);
+ }
+
+ public function offsetGet($key)
+ {
+ return $this->__get($key);
+ }
+
+ public function offsetSet($key, $value)
+ {
+ throw new \BadMethodCallException(self::class.' objects are immutable.');
+ }
+
+ public function offsetUnset($key)
+ {
+ throw new \BadMethodCallException(self::class.' objects are immutable.');
+ }
+
+ public function __toString()
+ {
+ $value = $this->getValue();
+
+ if (!\is_array($value)) {
+ return (string) $value;
+ }
+
+ return sprintf('%s (count=%d)', $this->getType(), \count($value));
+ }
+
+ /**
+ * Returns a depth limited clone of $this.
+ *
+ * @param int $maxDepth The max dumped depth level
+ *
+ * @return self A clone of $this
+ */
+ public function withMaxDepth($maxDepth)
+ {
+ $data = clone $this;
+ $data->maxDepth = (int) $maxDepth;
+
+ return $data;
+ }
+
+ /**
+ * Limits the number of elements per depth level.
+ *
+ * @param int $maxItemsPerDepth The max number of items dumped per depth level
+ *
+ * @return self A clone of $this
+ */
+ public function withMaxItemsPerDepth($maxItemsPerDepth)
+ {
+ $data = clone $this;
+ $data->maxItemsPerDepth = (int) $maxItemsPerDepth;
+
+ return $data;
+ }
+
+ /**
+ * Enables/disables objects' identifiers tracking.
+ *
+ * @param bool $useRefHandles False to hide global ref. handles
+ *
+ * @return self A clone of $this
+ */
+ public function withRefHandles($useRefHandles)
+ {
+ $data = clone $this;
+ $data->useRefHandles = $useRefHandles ? -1 : 0;
+
+ return $data;
+ }
+
+ /**
+ * Seeks to a specific key in nested data structures.
+ *
+ * @param string|int $key The key to seek to
+ *
+ * @return self|null A clone of $this or null if the key is not set
+ */
+ public function seek($key)
+ {
+ $item = $this->data[$this->position][$this->key];
+
+ if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
+ $item = $item->value;
+ }
+ if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) {
+ return;
+ }
+ $keys = [$key];
+
+ switch ($item->type) {
+ case Stub::TYPE_OBJECT:
+ $keys[] = Caster::PREFIX_DYNAMIC.$key;
+ $keys[] = Caster::PREFIX_PROTECTED.$key;
+ $keys[] = Caster::PREFIX_VIRTUAL.$key;
+ $keys[] = "\0$item->class\0$key";
+ // no break
+ case Stub::TYPE_ARRAY:
+ case Stub::TYPE_RESOURCE:
+ break;
+ default:
+ return;
+ }
+
+ $data = null;
+ $children = $this->data[$item->position];
+
+ foreach ($keys as $key) {
+ if (isset($children[$key]) || \array_key_exists($key, $children)) {
+ $data = clone $this;
+ $data->key = $key;
+ $data->position = $item->position;
+ break;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Dumps data with a DumperInterface dumper.
+ */
+ public function dump(DumperInterface $dumper)
+ {
+ $refs = [0];
+ $this->dumpItem($dumper, new Cursor(), $refs, $this->data[$this->position][$this->key]);
+ }
+
+ /**
+ * Depth-first dumping of items.
+ *
+ * @param DumperInterface $dumper The dumper being used for dumping
+ * @param Cursor $cursor A cursor used for tracking dumper state position
+ * @param array &$refs A map of all references discovered while dumping
+ * @param mixed $item A Stub object or the original value being dumped
+ */
+ private function dumpItem($dumper, $cursor, &$refs, $item)
+ {
+ $cursor->refIndex = 0;
+ $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0;
+ $cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0;
+ $firstSeen = true;
+
+ if (!$item instanceof Stub) {
+ $cursor->attr = [];
+ $type = \gettype($item);
+ if ($item && 'array' === $type) {
+ $item = $this->getStub($item);
+ }
+ } elseif (Stub::TYPE_REF === $item->type) {
+ if ($item->handle) {
+ if (!isset($refs[$r = $item->handle - (PHP_INT_MAX >> 1)])) {
+ $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0];
+ } else {
+ $firstSeen = false;
+ }
+ $cursor->hardRefTo = $refs[$r];
+ $cursor->hardRefHandle = $this->useRefHandles & $item->handle;
+ $cursor->hardRefCount = $item->refCount;
+ }
+ $cursor->attr = $item->attr;
+ $type = $item->class ?: \gettype($item->value);
+ $item = $this->getStub($item->value);
+ }
+ if ($item instanceof Stub) {
+ if ($item->refCount) {
+ if (!isset($refs[$r = $item->handle])) {
+ $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0];
+ } else {
+ $firstSeen = false;
+ }
+ $cursor->softRefTo = $refs[$r];
+ }
+ $cursor->softRefHandle = $this->useRefHandles & $item->handle;
+ $cursor->softRefCount = $item->refCount;
+ $cursor->attr = $item->attr;
+ $cut = $item->cut;
+
+ if ($item->position && $firstSeen) {
+ $children = $this->data[$item->position];
+
+ if ($cursor->stop) {
+ if ($cut >= 0) {
+ $cut += \count($children);
+ }
+ $children = [];
+ }
+ } else {
+ $children = [];
+ }
+ switch ($item->type) {
+ case Stub::TYPE_STRING:
+ $dumper->dumpString($cursor, $item->value, Stub::STRING_BINARY === $item->class, $cut);
+ break;
+
+ case Stub::TYPE_ARRAY:
+ $item = clone $item;
+ $item->type = $item->class;
+ $item->class = $item->value;
+ // no break
+ case Stub::TYPE_OBJECT:
+ case Stub::TYPE_RESOURCE:
+ $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth;
+ $dumper->enterHash($cursor, $item->type, $item->class, $withChildren);
+ if ($withChildren) {
+ if ($cursor->skipChildren) {
+ $withChildren = false;
+ $cut = -1;
+ } else {
+ $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type, null !== $item->class);
+ }
+ } elseif ($children && 0 <= $cut) {
+ $cut += \count($children);
+ }
+ $cursor->skipChildren = false;
+ $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut);
+ break;
+
+ default:
+ throw new \RuntimeException(sprintf('Unexpected Stub type: %s', $item->type));
+ }
+ } elseif ('array' === $type) {
+ $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false);
+ $dumper->leaveHash($cursor, Cursor::HASH_INDEXED, 0, false, 0);
+ } elseif ('string' === $type) {
+ $dumper->dumpString($cursor, $item, false, 0);
+ } else {
+ $dumper->dumpScalar($cursor, $type, $item);
+ }
+ }
+
+ /**
+ * Dumps children of hash structures.
+ *
+ * @param DumperInterface $dumper
+ * @param Cursor $parentCursor The cursor of the parent hash
+ * @param array &$refs A map of all references discovered while dumping
+ * @param array $children The children to dump
+ * @param int $hashCut The number of items removed from the original hash
+ * @param string $hashType A Cursor::HASH_* const
+ * @param bool $dumpKeys Whether keys should be dumped or not
+ *
+ * @return int The final number of removed items
+ */
+ private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType, $dumpKeys)
+ {
+ $cursor = clone $parentCursor;
+ ++$cursor->depth;
+ $cursor->hashType = $hashType;
+ $cursor->hashIndex = 0;
+ $cursor->hashLength = \count($children);
+ $cursor->hashCut = $hashCut;
+ foreach ($children as $key => $child) {
+ $cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key);
+ $cursor->hashKey = $dumpKeys ? $key : null;
+ $this->dumpItem($dumper, $cursor, $refs, $child);
+ if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) {
+ $parentCursor->stop = true;
+
+ return $hashCut >= 0 ? $hashCut + $cursor->hashLength - $cursor->hashIndex : $hashCut;
+ }
+ }
+
+ return $hashCut;
+ }
+
+ private function getStub($item)
+ {
+ if (!$item || !\is_array($item)) {
+ return $item;
+ }
+
+ $stub = new Stub();
+ $stub->type = Stub::TYPE_ARRAY;
+ foreach ($item as $stub->class => $stub->position) {
+ }
+ if (isset($item[0])) {
+ $stub->cut = $item[0];
+ }
+ $stub->value = $stub->cut + ($stub->position ? \count($this->data[$stub->position]) : 0);
+
+ return $stub;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Cloner/DumperInterface.php b/vendor/symfony/var-dumper/Cloner/DumperInterface.php
new file mode 100755
index 0000000..cb498ff
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/DumperInterface.php
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+/**
+ * DumperInterface used by Data objects.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+interface DumperInterface
+{
+ /**
+ * Dumps a scalar value.
+ *
+ * @param Cursor $cursor The Cursor position in the dump
+ * @param string $type The PHP type of the value being dumped
+ * @param string|int|float|bool $value The scalar value being dumped
+ */
+ public function dumpScalar(Cursor $cursor, $type, $value);
+
+ /**
+ * Dumps a string.
+ *
+ * @param Cursor $cursor The Cursor position in the dump
+ * @param string $str The string being dumped
+ * @param bool $bin Whether $str is UTF-8 or binary encoded
+ * @param int $cut The number of characters $str has been cut by
+ */
+ public function dumpString(Cursor $cursor, $str, $bin, $cut);
+
+ /**
+ * Dumps while entering an hash.
+ *
+ * @param Cursor $cursor The Cursor position in the dump
+ * @param int $type A Cursor::HASH_* const for the type of hash
+ * @param string $class The object class, resource type or array count
+ * @param bool $hasChild When the dump of the hash has child item
+ */
+ public function enterHash(Cursor $cursor, $type, $class, $hasChild);
+
+ /**
+ * Dumps while leaving an hash.
+ *
+ * @param Cursor $cursor The Cursor position in the dump
+ * @param int $type A Cursor::HASH_* const for the type of hash
+ * @param string $class The object class, resource type or array count
+ * @param bool $hasChild When the dump of the hash has child item
+ * @param int $cut The number of items the hash has been cut by
+ */
+ public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut);
+}
diff --git a/vendor/symfony/var-dumper/Cloner/Stub.php b/vendor/symfony/var-dumper/Cloner/Stub.php
new file mode 100755
index 0000000..27dd3ef
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/Stub.php
@@ -0,0 +1,67 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+/**
+ * Represents the main properties of a PHP variable.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class Stub
+{
+ const TYPE_REF = 1;
+ const TYPE_STRING = 2;
+ const TYPE_ARRAY = 3;
+ const TYPE_OBJECT = 4;
+ const TYPE_RESOURCE = 5;
+
+ const STRING_BINARY = 1;
+ const STRING_UTF8 = 2;
+
+ const ARRAY_ASSOC = 1;
+ const ARRAY_INDEXED = 2;
+
+ public $type = self::TYPE_REF;
+ public $class = '';
+ public $value;
+ public $cut = 0;
+ public $handle = 0;
+ public $refCount = 0;
+ public $position = 0;
+ public $attr = [];
+
+ private static $defaultProperties = [];
+
+ /**
+ * @internal
+ */
+ public function __sleep()
+ {
+ $properties = [];
+
+ if (!isset(self::$defaultProperties[$c = \get_class($this)])) {
+ self::$defaultProperties[$c] = get_class_vars($c);
+
+ foreach ((new \ReflectionClass($c))->getStaticProperties() as $k => $v) {
+ unset(self::$defaultProperties[$c][$k]);
+ }
+ }
+
+ foreach (self::$defaultProperties[$c] as $k => $v) {
+ if ($this->$k !== $v) {
+ $properties[] = $k;
+ }
+ }
+
+ return $properties;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Cloner/VarCloner.php b/vendor/symfony/var-dumper/Cloner/VarCloner.php
new file mode 100755
index 0000000..b8318c5
--- /dev/null
+++ b/vendor/symfony/var-dumper/Cloner/VarCloner.php
@@ -0,0 +1,295 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class VarCloner extends AbstractCloner
+{
+ private static $gid;
+ private static $arrayCache = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClone($var)
+ {
+ $len = 1; // Length of $queue
+ $pos = 0; // Number of cloned items past the minimum depth
+ $refsCounter = 0; // Hard references counter
+ $queue = [[$var]]; // This breadth-first queue is the return value
+ $indexedArrays = []; // Map of queue indexes that hold numerically indexed arrays
+ $hardRefs = []; // Map of original zval ids to stub objects
+ $objRefs = []; // Map of original object handles to their stub object counterpart
+ $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning
+ $resRefs = []; // Map of original resource handles to their stub object counterpart
+ $values = []; // Map of stub objects' ids to original values
+ $maxItems = $this->maxItems;
+ $maxString = $this->maxString;
+ $minDepth = $this->minDepth;
+ $currentDepth = 0; // Current tree depth
+ $currentDepthFinalIndex = 0; // Final $queue index for current tree depth
+ $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached
+ $cookie = (object) []; // Unique object used to detect hard references
+ $a = null; // Array cast for nested structures
+ $stub = null; // Stub capturing the main properties of an original item value
+ // or null if the original value is used directly
+
+ if (!$gid = self::$gid) {
+ $gid = self::$gid = uniqid(mt_rand(), true); // Unique string used to detect the special $GLOBALS variable
+ }
+ $arrayStub = new Stub();
+ $arrayStub->type = Stub::TYPE_ARRAY;
+ $fromObjCast = false;
+
+ for ($i = 0; $i < $len; ++$i) {
+ // Detect when we move on to the next tree depth
+ if ($i > $currentDepthFinalIndex) {
+ ++$currentDepth;
+ $currentDepthFinalIndex = $len - 1;
+ if ($currentDepth >= $minDepth) {
+ $minimumDepthReached = true;
+ }
+ }
+
+ $refs = $vals = $queue[$i];
+ if (\PHP_VERSION_ID < 70200 && empty($indexedArrays[$i])) {
+ // see https://wiki.php.net/rfc/convert_numeric_keys_in_object_array_casts
+ foreach ($vals as $k => $v) {
+ if (\is_int($k)) {
+ continue;
+ }
+ foreach ([$k => true] as $gk => $gv) {
+ }
+ if ($gk !== $k) {
+ $fromObjCast = true;
+ $refs = $vals = \array_values($queue[$i]);
+ break;
+ }
+ }
+ }
+ foreach ($vals as $k => $v) {
+ // $v is the original value or a stub object in case of hard references
+ $refs[$k] = $cookie;
+ if ($zvalIsRef = $vals[$k] === $cookie) {
+ $vals[$k] = &$stub; // Break hard references to make $queue completely
+ unset($stub); // independent from the original structure
+ if ($v instanceof Stub && isset($hardRefs[\spl_object_id($v)])) {
+ $vals[$k] = $refs[$k] = $v;
+ if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) {
+ ++$v->value->refCount;
+ }
+ ++$v->refCount;
+ continue;
+ }
+ $refs[$k] = $vals[$k] = new Stub();
+ $refs[$k]->value = $v;
+ $h = \spl_object_id($refs[$k]);
+ $hardRefs[$h] = &$refs[$k];
+ $values[$h] = $v;
+ $vals[$k]->handle = ++$refsCounter;
+ }
+ // Create $stub when the original value $v can not be used directly
+ // If $v is a nested structure, put that structure in array $a
+ switch (true) {
+ case null === $v:
+ case \is_bool($v):
+ case \is_int($v):
+ case \is_float($v):
+ continue 2;
+
+ case \is_string($v):
+ if ('' === $v) {
+ continue 2;
+ }
+ if (!\preg_match('//u', $v)) {
+ $stub = new Stub();
+ $stub->type = Stub::TYPE_STRING;
+ $stub->class = Stub::STRING_BINARY;
+ if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) {
+ $stub->cut = $cut;
+ $stub->value = \substr($v, 0, -$cut);
+ } else {
+ $stub->value = $v;
+ }
+ } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = \mb_strlen($v, 'UTF-8') - $maxString) {
+ $stub = new Stub();
+ $stub->type = Stub::TYPE_STRING;
+ $stub->class = Stub::STRING_UTF8;
+ $stub->cut = $cut;
+ $stub->value = \mb_substr($v, 0, $maxString, 'UTF-8');
+ } else {
+ continue 2;
+ }
+ $a = null;
+ break;
+
+ case \is_array($v):
+ if (!$v) {
+ continue 2;
+ }
+ $stub = $arrayStub;
+ $stub->class = Stub::ARRAY_INDEXED;
+
+ $j = -1;
+ foreach ($v as $gk => $gv) {
+ if ($gk !== ++$j) {
+ $stub->class = Stub::ARRAY_ASSOC;
+ break;
+ }
+ }
+ $a = $v;
+
+ if (Stub::ARRAY_ASSOC === $stub->class) {
+ // Copies of $GLOBALS have very strange behavior,
+ // let's detect them with some black magic
+ $a[$gid] = true;
+
+ // Happens with copies of $GLOBALS
+ if (isset($v[$gid])) {
+ unset($v[$gid]);
+ $a = [];
+ foreach ($v as $gk => &$gv) {
+ $a[$gk] = &$gv;
+ }
+ unset($gv);
+ } else {
+ $a = $v;
+ }
+ } elseif (\PHP_VERSION_ID < 70200) {
+ $indexedArrays[$len] = true;
+ }
+ break;
+
+ case \is_object($v):
+ case $v instanceof \__PHP_Incomplete_Class:
+ if (empty($objRefs[$h = \spl_object_id($v)])) {
+ $stub = new Stub();
+ $stub->type = Stub::TYPE_OBJECT;
+ $stub->class = \get_class($v);
+ $stub->value = $v;
+ $stub->handle = $h;
+ $a = $this->castObject($stub, 0 < $i);
+ if ($v !== $stub->value) {
+ if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) {
+ break;
+ }
+ $stub->handle = $h = \spl_object_id($stub->value);
+ }
+ $stub->value = null;
+ if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) {
+ $stub->cut = \count($a);
+ $a = null;
+ }
+ }
+ if (empty($objRefs[$h])) {
+ $objRefs[$h] = $stub;
+ $objects[] = $v;
+ } else {
+ $stub = $objRefs[$h];
+ ++$stub->refCount;
+ $a = null;
+ }
+ break;
+
+ default: // resource
+ if (empty($resRefs[$h = (int) $v])) {
+ $stub = new Stub();
+ $stub->type = Stub::TYPE_RESOURCE;
+ if ('Unknown' === $stub->class = @\get_resource_type($v)) {
+ $stub->class = 'Closed';
+ }
+ $stub->value = $v;
+ $stub->handle = $h;
+ $a = $this->castResource($stub, 0 < $i);
+ $stub->value = null;
+ if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) {
+ $stub->cut = \count($a);
+ $a = null;
+ }
+ }
+ if (empty($resRefs[$h])) {
+ $resRefs[$h] = $stub;
+ } else {
+ $stub = $resRefs[$h];
+ ++$stub->refCount;
+ $a = null;
+ }
+ break;
+ }
+
+ if ($a) {
+ if (!$minimumDepthReached || 0 > $maxItems) {
+ $queue[$len] = $a;
+ $stub->position = $len++;
+ } elseif ($pos < $maxItems) {
+ if ($maxItems < $pos += \count($a)) {
+ $a = \array_slice($a, 0, $maxItems - $pos);
+ if ($stub->cut >= 0) {
+ $stub->cut += $pos - $maxItems;
+ }
+ }
+ $queue[$len] = $a;
+ $stub->position = $len++;
+ } elseif ($stub->cut >= 0) {
+ $stub->cut += \count($a);
+ $stub->position = 0;
+ }
+ }
+
+ if ($arrayStub === $stub) {
+ if ($arrayStub->cut) {
+ $stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position];
+ $arrayStub->cut = 0;
+ } elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) {
+ $stub = self::$arrayCache[$arrayStub->class][$arrayStub->position];
+ } else {
+ self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position];
+ }
+ }
+
+ if ($zvalIsRef) {
+ $refs[$k]->value = $stub;
+ } else {
+ $vals[$k] = $stub;
+ }
+ }
+
+ if ($fromObjCast) {
+ $fromObjCast = false;
+ $refs = $vals;
+ $vals = [];
+ $j = -1;
+ foreach ($queue[$i] as $k => $v) {
+ foreach ([$k => true] as $gk => $gv) {
+ }
+ if ($gk !== $k) {
+ $vals = (object) $vals;
+ $vals->{$k} = $refs[++$j];
+ $vals = (array) $vals;
+ } else {
+ $vals[$k] = $refs[++$j];
+ }
+ }
+ }
+
+ $queue[$i] = $vals;
+ }
+
+ foreach ($values as $h => $v) {
+ $hardRefs[$h] = $v;
+ }
+
+ return $queue;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php
new file mode 100755
index 0000000..dc77d03
--- /dev/null
+++ b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php
@@ -0,0 +1,88 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Command\Descriptor;
+
+use Symfony\Component\Console\Formatter\OutputFormatterStyle;
+use Symfony\Component\Console\Input\ArrayInput;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+/**
+ * Describe collected data clones for cli output.
+ *
+ * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
+ *
+ * @final
+ */
+class CliDescriptor implements DumpDescriptorInterface
+{
+ private $dumper;
+ private $lastIdentifier;
+ private $supportsHref;
+
+ public function __construct(CliDumper $dumper)
+ {
+ $this->dumper = $dumper;
+ $this->supportsHref = method_exists(OutputFormatterStyle::class, 'setHref');
+ }
+
+ public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void
+ {
+ $io = $output instanceof SymfonyStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output);
+ $this->dumper->setColors($output->isDecorated());
+
+ $rows = [['date', date('r', $context['timestamp'])]];
+ $lastIdentifier = $this->lastIdentifier;
+ $this->lastIdentifier = $clientId;
+
+ $section = "Received from client #$clientId";
+ if (isset($context['request'])) {
+ $request = $context['request'];
+ $this->lastIdentifier = $request['identifier'];
+ $section = sprintf('%s %s', $request['method'], $request['uri']);
+ if ($controller = $request['controller']) {
+ $rows[] = ['controller', rtrim($this->dumper->dump($controller, true), "\n")];
+ }
+ } elseif (isset($context['cli'])) {
+ $this->lastIdentifier = $context['cli']['identifier'];
+ $section = '$ '.$context['cli']['command_line'];
+ }
+
+ if ($this->lastIdentifier !== $lastIdentifier) {
+ $io->section($section);
+ }
+
+ if (isset($context['source'])) {
+ $source = $context['source'];
+ $sourceInfo = sprintf('%s on line %d', $source['name'], $source['line']);
+ $fileLink = $source['file_link'] ?? null;
+ if ($this->supportsHref && $fileLink) {
+ $sourceInfo = sprintf('<href=%s>%s</>', $fileLink, $sourceInfo);
+ }
+ $rows[] = ['source', $sourceInfo];
+ $file = $source['file_relative'] ?? $source['file'];
+ $rows[] = ['file', $file];
+ }
+
+ $io->table([], $rows);
+
+ if (!$this->supportsHref && isset($fileLink)) {
+ $io->writeln(['<info>Open source in your IDE/browser:</info>', $fileLink]);
+ $io->newLine();
+ }
+
+ $this->dumper->dump($data);
+ $io->newLine();
+ }
+}
diff --git a/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php
new file mode 100755
index 0000000..267d27b
--- /dev/null
+++ b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php
@@ -0,0 +1,23 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Command\Descriptor;
+
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\VarDumper\Cloner\Data;
+
+/**
+ * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
+ */
+interface DumpDescriptorInterface
+{
+ public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void;
+}
diff --git a/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php
new file mode 100755
index 0000000..35a203b
--- /dev/null
+++ b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php
@@ -0,0 +1,119 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Command\Descriptor;
+
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+
+/**
+ * Describe collected data clones for html output.
+ *
+ * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
+ *
+ * @final
+ */
+class HtmlDescriptor implements DumpDescriptorInterface
+{
+ private $dumper;
+ private $initialized = false;
+
+ public function __construct(HtmlDumper $dumper)
+ {
+ $this->dumper = $dumper;
+ }
+
+ public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void
+ {
+ if (!$this->initialized) {
+ $styles = file_get_contents(__DIR__.'/../../Resources/css/htmlDescriptor.css');
+ $scripts = file_get_contents(__DIR__.'/../../Resources/js/htmlDescriptor.js');
+ $output->writeln("<style>$styles</style><script>$scripts</script>");
+ $this->initialized = true;
+ }
+
+ $title = '-';
+ if (isset($context['request'])) {
+ $request = $context['request'];
+ $controller = "<span class='dumped-tag'>{$this->dumper->dump($request['controller'], true, ['maxDepth' => 0])}</span>";
+ $title = sprintf('<code>%s</code> <a href="%s">%s</a>', $request['method'], $uri = $request['uri'], $uri);
+ $dedupIdentifier = $request['identifier'];
+ } elseif (isset($context['cli'])) {
+ $title = '<code>$ </code>'.$context['cli']['command_line'];
+ $dedupIdentifier = $context['cli']['identifier'];
+ } else {
+ $dedupIdentifier = uniqid('', true);
+ }
+
+ $sourceDescription = '';
+ if (isset($context['source'])) {
+ $source = $context['source'];
+ $projectDir = $source['project_dir'] ?? null;
+ $sourceDescription = sprintf('%s on line %d', $source['name'], $source['line']);
+ if (isset($source['file_link'])) {
+ $sourceDescription = sprintf('<a href="%s">%s</a>', $source['file_link'], $sourceDescription);
+ }
+ }
+
+ $isoDate = $this->extractDate($context, 'c');
+ $tags = array_filter([
+ 'controller' => $controller ?? null,
+ 'project dir' => $projectDir ?? null,
+ ]);
+
+ $output->writeln(<<<HTML
+<article data-dedup-id="$dedupIdentifier">
+ <header>
+ <div class="row">
+ <h2 class="col">$title</h2>
+ <time class="col text-small" title="$isoDate" datetime="$isoDate">
+ {$this->extractDate($context)}
+ </time>
+ </div>
+ {$this->renderTags($tags)}
+ </header>
+ <section class="body">
+ <p class="text-small">
+ $sourceDescription
+ </p>
+ {$this->dumper->dump($data, true)}
+ </section>
+</article>
+HTML
+ );
+ }
+
+ private function extractDate(array $context, string $format = 'r'): string
+ {
+ return date($format, $context['timestamp']);
+ }
+
+ private function renderTags(array $tags): string
+ {
+ if (!$tags) {
+ return '';
+ }
+
+ $renderedTags = '';
+ foreach ($tags as $key => $value) {
+ $renderedTags .= sprintf('<li><span class="badge">%s</span>%s</li>', $key, $value);
+ }
+
+ return <<<HTML
+<div class="row">
+ <ul class="tags">
+ $renderedTags
+ </ul>
+</div>
+HTML;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Command/ServerDumpCommand.php b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php
new file mode 100755
index 0000000..eb807e3
--- /dev/null
+++ b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php
@@ -0,0 +1,99 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Command;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor;
+use Symfony\Component\VarDumper\Command\Descriptor\DumpDescriptorInterface;
+use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+use Symfony\Component\VarDumper\Server\DumpServer;
+
+/**
+ * Starts a dump server to collect and output dumps on a single place with multiple formats support.
+ *
+ * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
+ *
+ * @final
+ */
+class ServerDumpCommand extends Command
+{
+ protected static $defaultName = 'server:dump';
+
+ private $server;
+
+ /** @var DumpDescriptorInterface[] */
+ private $descriptors;
+
+ public function __construct(DumpServer $server, array $descriptors = [])
+ {
+ $this->server = $server;
+ $this->descriptors = $descriptors + [
+ 'cli' => new CliDescriptor(new CliDumper()),
+ 'html' => new HtmlDescriptor(new HtmlDumper()),
+ ];
+
+ parent::__construct();
+ }
+
+ protected function configure()
+ {
+ $availableFormats = implode(', ', array_keys($this->descriptors));
+
+ $this
+ ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format (%s)', $availableFormats), 'cli')
+ ->setDescription('Starts a dump server that collects and displays dumps in a single place')
+ ->setHelp(<<<'EOF'
+<info>%command.name%</info> starts a dump server that collects and displays
+dumps in a single place for debugging you application:
+
+ <info>php %command.full_name%</info>
+
+You can consult dumped data in HTML format in your browser by providing the <comment>--format=html</comment> option
+and redirecting the output to a file:
+
+ <info>php %command.full_name% --format="html" > dump.html</info>
+
+EOF
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $io = new SymfonyStyle($input, $output);
+ $format = $input->getOption('format');
+
+ if (!$descriptor = $this->descriptors[$format] ?? null) {
+ throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $format));
+ }
+
+ $errorIo = $io->getErrorStyle();
+ $errorIo->title('Symfony Var Dumper Server');
+
+ $this->server->start();
+
+ $errorIo->success(sprintf('Server listening on %s', $this->server->getHost()));
+ $errorIo->comment('Quit the server with CONTROL-C.');
+
+ $this->server->listen(function (Data $data, array $context, int $clientId) use ($descriptor, $io) {
+ $descriptor->describe($io, $data, $context, $clientId);
+ });
+ }
+}
diff --git a/vendor/symfony/var-dumper/Dumper/AbstractDumper.php b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php
new file mode 100755
index 0000000..9c0ff36
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php
@@ -0,0 +1,211 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper;
+
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Cloner\DumperInterface;
+
+/**
+ * Abstract mechanism for dumping a Data object.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+abstract class AbstractDumper implements DataDumperInterface, DumperInterface
+{
+ const DUMP_LIGHT_ARRAY = 1;
+ const DUMP_STRING_LENGTH = 2;
+ const DUMP_COMMA_SEPARATOR = 4;
+ const DUMP_TRAILING_COMMA = 8;
+
+ public static $defaultOutput = 'php://output';
+
+ protected $line = '';
+ protected $lineDumper;
+ protected $outputStream;
+ protected $decimalPoint; // This is locale dependent
+ protected $indentPad = ' ';
+ protected $flags;
+
+ private $charset;
+
+ /**
+ * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput
+ * @param string|null $charset The default character encoding to use for non-UTF8 strings
+ * @param int $flags A bit field of static::DUMP_* constants to fine tune dumps representation
+ */
+ public function __construct($output = null, string $charset = null, int $flags = 0)
+ {
+ $this->flags = $flags;
+ $this->setCharset($charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8');
+ $this->decimalPoint = localeconv();
+ $this->decimalPoint = $this->decimalPoint['decimal_point'];
+ $this->setOutput($output ?: static::$defaultOutput);
+ if (!$output && \is_string(static::$defaultOutput)) {
+ static::$defaultOutput = $this->outputStream;
+ }
+ }
+
+ /**
+ * Sets the output destination of the dumps.
+ *
+ * @param callable|resource|string $output A line dumper callable, an opened stream or an output path
+ *
+ * @return callable|resource|string The previous output destination
+ */
+ public function setOutput($output)
+ {
+ $prev = null !== $this->outputStream ? $this->outputStream : $this->lineDumper;
+
+ if (\is_callable($output)) {
+ $this->outputStream = null;
+ $this->lineDumper = $output;
+ } else {
+ if (\is_string($output)) {
+ $output = fopen($output, 'wb');
+ }
+ $this->outputStream = $output;
+ $this->lineDumper = [$this, 'echoLine'];
+ }
+
+ return $prev;
+ }
+
+ /**
+ * Sets the default character encoding to use for non-UTF8 strings.
+ *
+ * @param string $charset The default character encoding to use for non-UTF8 strings
+ *
+ * @return string The previous charset
+ */
+ public function setCharset($charset)
+ {
+ $prev = $this->charset;
+
+ $charset = strtoupper($charset);
+ $charset = null === $charset || 'UTF-8' === $charset || 'UTF8' === $charset ? 'CP1252' : $charset;
+
+ $this->charset = $charset;
+
+ return $prev;
+ }
+
+ /**
+ * Sets the indentation pad string.
+ *
+ * @param string $pad A string that will be prepended to dumped lines, repeated by nesting level
+ *
+ * @return string The previous indent pad
+ */
+ public function setIndentPad($pad)
+ {
+ $prev = $this->indentPad;
+ $this->indentPad = $pad;
+
+ return $prev;
+ }
+
+ /**
+ * Dumps a Data object.
+ *
+ * @param Data $data A Data object
+ * @param callable|resource|string|true|null $output A line dumper callable, an opened stream, an output path or true to return the dump
+ *
+ * @return string|null The dump as string when $output is true
+ */
+ public function dump(Data $data, $output = null)
+ {
+ $this->decimalPoint = localeconv();
+ $this->decimalPoint = $this->decimalPoint['decimal_point'];
+
+ if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(LC_NUMERIC, 0) : null) {
+ setlocale(LC_NUMERIC, 'C');
+ }
+
+ if ($returnDump = true === $output) {
+ $output = fopen('php://memory', 'r+b');
+ }
+ if ($output) {
+ $prevOutput = $this->setOutput($output);
+ }
+ try {
+ $data->dump($this);
+ $this->dumpLine(-1);
+
+ if ($returnDump) {
+ $result = stream_get_contents($output, -1, 0);
+ fclose($output);
+
+ return $result;
+ }
+ } finally {
+ if ($output) {
+ $this->setOutput($prevOutput);
+ }
+ if ($locale) {
+ setlocale(LC_NUMERIC, $locale);
+ }
+ }
+ }
+
+ /**
+ * Dumps the current line.
+ *
+ * @param int $depth The recursive depth in the dumped structure for the line being dumped,
+ * or -1 to signal the end-of-dump to the line dumper callable
+ */
+ protected function dumpLine($depth)
+ {
+ ($this->lineDumper)($this->line, $depth, $this->indentPad);
+ $this->line = '';
+ }
+
+ /**
+ * Generic line dumper callback.
+ *
+ * @param string $line The line to write
+ * @param int $depth The recursive depth in the dumped structure
+ * @param string $indentPad The line indent pad
+ */
+ protected function echoLine($line, $depth, $indentPad)
+ {
+ if (-1 !== $depth) {
+ fwrite($this->outputStream, str_repeat($indentPad, $depth).$line."\n");
+ }
+ }
+
+ /**
+ * Converts a non-UTF-8 string to UTF-8.
+ *
+ * @param string $s The non-UTF-8 string to convert
+ *
+ * @return string The string converted to UTF-8
+ */
+ protected function utf8Encode($s)
+ {
+ if (preg_match('//u', $s)) {
+ return $s;
+ }
+
+ if (!\function_exists('iconv')) {
+ throw new \RuntimeException('Unable to convert a non-UTF-8 string to UTF-8: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.');
+ }
+
+ if (false !== $c = @iconv($this->charset, 'UTF-8', $s)) {
+ return $c;
+ }
+ if ('CP1252' !== $this->charset && false !== $c = @iconv('CP1252', 'UTF-8', $s)) {
+ return $c;
+ }
+
+ return iconv('CP850', 'UTF-8', $s);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Dumper/CliDumper.php b/vendor/symfony/var-dumper/Dumper/CliDumper.php
new file mode 100755
index 0000000..9b258f4
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/CliDumper.php
@@ -0,0 +1,643 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper;
+
+use Symfony\Component\VarDumper\Cloner\Cursor;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * CliDumper dumps variables for command line output.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class CliDumper extends AbstractDumper
+{
+ public static $defaultColors;
+ public static $defaultOutput = 'php://stdout';
+
+ protected $colors;
+ protected $maxStringWidth = 0;
+ protected $styles = [
+ // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
+ 'default' => '38;5;208',
+ 'num' => '1;38;5;38',
+ 'const' => '1;38;5;208',
+ 'str' => '1;38;5;113',
+ 'note' => '38;5;38',
+ 'ref' => '38;5;247',
+ 'public' => '',
+ 'protected' => '',
+ 'private' => '',
+ 'meta' => '38;5;170',
+ 'key' => '38;5;113',
+ 'index' => '38;5;38',
+ ];
+
+ protected static $controlCharsRx = '/[\x00-\x1F\x7F]+/';
+ protected static $controlCharsMap = [
+ "\t" => '\t',
+ "\n" => '\n',
+ "\v" => '\v',
+ "\f" => '\f',
+ "\r" => '\r',
+ "\033" => '\e',
+ ];
+
+ protected $collapseNextHash = false;
+ protected $expandNextHash = false;
+
+ private $displayOptions = [
+ 'fileLinkFormat' => null,
+ ];
+
+ private $handlesHrefGracefully;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct($output = null, string $charset = null, int $flags = 0)
+ {
+ parent::__construct($output, $charset, $flags);
+
+ if ('\\' === \DIRECTORY_SEPARATOR && !$this->isWindowsTrueColor()) {
+ // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI
+ $this->setStyles([
+ 'default' => '31',
+ 'num' => '1;34',
+ 'const' => '1;31',
+ 'str' => '1;32',
+ 'note' => '34',
+ 'ref' => '1;30',
+ 'meta' => '35',
+ 'key' => '32',
+ 'index' => '34',
+ ]);
+ }
+
+ $this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f';
+ }
+
+ /**
+ * Enables/disables colored output.
+ *
+ * @param bool $colors
+ */
+ public function setColors($colors)
+ {
+ $this->colors = (bool) $colors;
+ }
+
+ /**
+ * Sets the maximum number of characters per line for dumped strings.
+ *
+ * @param int $maxStringWidth
+ */
+ public function setMaxStringWidth($maxStringWidth)
+ {
+ $this->maxStringWidth = (int) $maxStringWidth;
+ }
+
+ /**
+ * Configures styles.
+ *
+ * @param array $styles A map of style names to style definitions
+ */
+ public function setStyles(array $styles)
+ {
+ $this->styles = $styles + $this->styles;
+ }
+
+ /**
+ * Configures display options.
+ *
+ * @param array $displayOptions A map of display options to customize the behavior
+ */
+ public function setDisplayOptions(array $displayOptions)
+ {
+ $this->displayOptions = $displayOptions + $this->displayOptions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dumpScalar(Cursor $cursor, $type, $value)
+ {
+ $this->dumpKey($cursor);
+
+ $style = 'const';
+ $attr = $cursor->attr;
+
+ switch ($type) {
+ case 'default':
+ $style = 'default';
+ break;
+
+ case 'integer':
+ $style = 'num';
+ break;
+
+ case 'double':
+ $style = 'num';
+
+ switch (true) {
+ case INF === $value: $value = 'INF'; break;
+ case -INF === $value: $value = '-INF'; break;
+ case is_nan($value): $value = 'NAN'; break;
+ default:
+ $value = (string) $value;
+ if (false === strpos($value, $this->decimalPoint)) {
+ $value .= $this->decimalPoint.'0';
+ }
+ break;
+ }
+ break;
+
+ case 'NULL':
+ $value = 'null';
+ break;
+
+ case 'boolean':
+ $value = $value ? 'true' : 'false';
+ break;
+
+ default:
+ $attr += ['value' => $this->utf8Encode($value)];
+ $value = $this->utf8Encode($type);
+ break;
+ }
+
+ $this->line .= $this->style($style, $value, $attr);
+
+ $this->endValue($cursor);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dumpString(Cursor $cursor, $str, $bin, $cut)
+ {
+ $this->dumpKey($cursor);
+ $attr = $cursor->attr;
+
+ if ($bin) {
+ $str = $this->utf8Encode($str);
+ }
+ if ('' === $str) {
+ $this->line .= '""';
+ $this->endValue($cursor);
+ } else {
+ $attr += [
+ 'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0,
+ 'binary' => $bin,
+ ];
+ $str = explode("\n", $str);
+ if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) {
+ unset($str[1]);
+ $str[0] .= "\n";
+ }
+ $m = \count($str) - 1;
+ $i = $lineCut = 0;
+
+ if (self::DUMP_STRING_LENGTH & $this->flags) {
+ $this->line .= '('.$attr['length'].') ';
+ }
+ if ($bin) {
+ $this->line .= 'b';
+ }
+
+ if ($m) {
+ $this->line .= '"""';
+ $this->dumpLine($cursor->depth);
+ } else {
+ $this->line .= '"';
+ }
+
+ foreach ($str as $str) {
+ if ($i < $m) {
+ $str .= "\n";
+ }
+ if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = mb_strlen($str, 'UTF-8')) {
+ $str = mb_substr($str, 0, $this->maxStringWidth, 'UTF-8');
+ $lineCut = $len - $this->maxStringWidth;
+ }
+ if ($m && 0 < $cursor->depth) {
+ $this->line .= $this->indentPad;
+ }
+ if ('' !== $str) {
+ $this->line .= $this->style('str', $str, $attr);
+ }
+ if ($i++ == $m) {
+ if ($m) {
+ if ('' !== $str) {
+ $this->dumpLine($cursor->depth);
+ if (0 < $cursor->depth) {
+ $this->line .= $this->indentPad;
+ }
+ }
+ $this->line .= '"""';
+ } else {
+ $this->line .= '"';
+ }
+ if ($cut < 0) {
+ $this->line .= '…';
+ $lineCut = 0;
+ } elseif ($cut) {
+ $lineCut += $cut;
+ }
+ }
+ if ($lineCut) {
+ $this->line .= '…'.$lineCut;
+ $lineCut = 0;
+ }
+
+ if ($i > $m) {
+ $this->endValue($cursor);
+ } else {
+ $this->dumpLine($cursor->depth);
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function enterHash(Cursor $cursor, $type, $class, $hasChild)
+ {
+ $this->dumpKey($cursor);
+ $attr = $cursor->attr;
+
+ if ($this->collapseNextHash) {
+ $cursor->skipChildren = true;
+ $this->collapseNextHash = $hasChild = false;
+ }
+
+ $class = $this->utf8Encode($class);
+ if (Cursor::HASH_OBJECT === $type) {
+ $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class, $attr).' {' : '{';
+ } elseif (Cursor::HASH_RESOURCE === $type) {
+ $prefix = $this->style('note', $class.' resource', $attr).($hasChild ? ' {' : ' ');
+ } else {
+ $prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:'.$class, $attr).' [' : '[';
+ }
+
+ if ($cursor->softRefCount || 0 < $cursor->softRefHandle) {
+ $prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#').(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), ['count' => $cursor->softRefCount]);
+ } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) {
+ $prefix .= $this->style('ref', '&'.$cursor->hardRefTo, ['count' => $cursor->hardRefCount]);
+ } elseif (!$hasChild && Cursor::HASH_RESOURCE === $type) {
+ $prefix = substr($prefix, 0, -1);
+ }
+
+ $this->line .= $prefix;
+
+ if ($hasChild) {
+ $this->dumpLine($cursor->depth);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut)
+ {
+ $this->dumpEllipsis($cursor, $hasChild, $cut);
+ $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : ''));
+ $this->endValue($cursor);
+ }
+
+ /**
+ * Dumps an ellipsis for cut children.
+ *
+ * @param Cursor $cursor The Cursor position in the dump
+ * @param bool $hasChild When the dump of the hash has child item
+ * @param int $cut The number of items the hash has been cut by
+ */
+ protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut)
+ {
+ if ($cut) {
+ $this->line .= ' …';
+ if (0 < $cut) {
+ $this->line .= $cut;
+ }
+ if ($hasChild) {
+ $this->dumpLine($cursor->depth + 1);
+ }
+ }
+ }
+
+ /**
+ * Dumps a key in a hash structure.
+ *
+ * @param Cursor $cursor The Cursor position in the dump
+ */
+ protected function dumpKey(Cursor $cursor)
+ {
+ if (null !== $key = $cursor->hashKey) {
+ if ($cursor->hashKeyIsBinary) {
+ $key = $this->utf8Encode($key);
+ }
+ $attr = ['binary' => $cursor->hashKeyIsBinary];
+ $bin = $cursor->hashKeyIsBinary ? 'b' : '';
+ $style = 'key';
+ switch ($cursor->hashType) {
+ default:
+ case Cursor::HASH_INDEXED:
+ if (self::DUMP_LIGHT_ARRAY & $this->flags) {
+ break;
+ }
+ $style = 'index';
+ // no break
+ case Cursor::HASH_ASSOC:
+ if (\is_int($key)) {
+ $this->line .= $this->style($style, $key).' => ';
+ } else {
+ $this->line .= $bin.'"'.$this->style($style, $key).'" => ';
+ }
+ break;
+
+ case Cursor::HASH_RESOURCE:
+ $key = "\0~\0".$key;
+ // no break
+ case Cursor::HASH_OBJECT:
+ if (!isset($key[0]) || "\0" !== $key[0]) {
+ $this->line .= '+'.$bin.$this->style('public', $key).': ';
+ } elseif (0 < strpos($key, "\0", 1)) {
+ $key = explode("\0", substr($key, 1), 2);
+
+ switch ($key[0][0]) {
+ case '+': // User inserted keys
+ $attr['dynamic'] = true;
+ $this->line .= '+'.$bin.'"'.$this->style('public', $key[1], $attr).'": ';
+ break 2;
+ case '~':
+ $style = 'meta';
+ if (isset($key[0][1])) {
+ parse_str(substr($key[0], 1), $attr);
+ $attr += ['binary' => $cursor->hashKeyIsBinary];
+ }
+ break;
+ case '*':
+ $style = 'protected';
+ $bin = '#'.$bin;
+ break;
+ default:
+ $attr['class'] = $key[0];
+ $style = 'private';
+ $bin = '-'.$bin;
+ break;
+ }
+
+ if (isset($attr['collapse'])) {
+ if ($attr['collapse']) {
+ $this->collapseNextHash = true;
+ } else {
+ $this->expandNextHash = true;
+ }
+ }
+
+ $this->line .= $bin.$this->style($style, $key[1], $attr).(isset($attr['separator']) ? $attr['separator'] : ': ');
+ } else {
+ // This case should not happen
+ $this->line .= '-'.$bin.'"'.$this->style('private', $key, ['class' => '']).'": ';
+ }
+ break;
+ }
+
+ if ($cursor->hardRefTo) {
+ $this->line .= $this->style('ref', '&'.($cursor->hardRefCount ? $cursor->hardRefTo : ''), ['count' => $cursor->hardRefCount]).' ';
+ }
+ }
+ }
+
+ /**
+ * Decorates a value with some style.
+ *
+ * @param string $style The type of style being applied
+ * @param string $value The value being styled
+ * @param array $attr Optional context information
+ *
+ * @return string The value with style decoration
+ */
+ protected function style($style, $value, $attr = [])
+ {
+ if (null === $this->colors) {
+ $this->colors = $this->supportsColors();
+ }
+
+ if (null === $this->handlesHrefGracefully) {
+ $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') && !getenv('KONSOLE_VERSION');
+ }
+
+ if (isset($attr['ellipsis'], $attr['ellipsis-type'])) {
+ $prefix = substr($value, 0, -$attr['ellipsis']);
+ if ('cli' === \PHP_SAPI && 'path' === $attr['ellipsis-type'] && isset($_SERVER[$pwd = '\\' === \DIRECTORY_SEPARATOR ? 'CD' : 'PWD']) && 0 === strpos($prefix, $_SERVER[$pwd])) {
+ $prefix = '.'.substr($prefix, \strlen($_SERVER[$pwd]));
+ }
+ if (!empty($attr['ellipsis-tail'])) {
+ $prefix .= substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']);
+ $value = substr($value, -$attr['ellipsis'] + $attr['ellipsis-tail']);
+ } else {
+ $value = substr($value, -$attr['ellipsis']);
+ }
+
+ $value = $this->style('default', $prefix).$this->style($style, $value);
+
+ goto href;
+ }
+
+ $map = static::$controlCharsMap;
+ $startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : '';
+ $endCchr = $this->colors ? "\033[m\033[{$this->styles[$style]}m" : '';
+ $value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) {
+ $s = $startCchr;
+ $c = $c[$i = 0];
+ do {
+ $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', \ord($c[$i]));
+ } while (isset($c[++$i]));
+
+ return $s.$endCchr;
+ }, $value, -1, $cchrCount);
+
+ if ($this->colors) {
+ if ($cchrCount && "\033" === $value[0]) {
+ $value = substr($value, \strlen($startCchr));
+ } else {
+ $value = "\033[{$this->styles[$style]}m".$value;
+ }
+ if ($cchrCount && $endCchr === substr($value, -\strlen($endCchr))) {
+ $value = substr($value, 0, -\strlen($endCchr));
+ } else {
+ $value .= "\033[{$this->styles['default']}m";
+ }
+ }
+
+ href:
+ if ($this->colors && $this->handlesHrefGracefully) {
+ if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) {
+ if ('note' === $style) {
+ $value .= "\033]8;;{$href}\033\\^\033]8;;\033\\";
+ } else {
+ $attr['href'] = $href;
+ }
+ }
+ if (isset($attr['href'])) {
+ $value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\";
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * @return bool Tells if the current output stream supports ANSI colors or not
+ */
+ protected function supportsColors()
+ {
+ if ($this->outputStream !== static::$defaultOutput) {
+ return $this->hasColorSupport($this->outputStream);
+ }
+ if (null !== static::$defaultColors) {
+ return static::$defaultColors;
+ }
+ if (isset($_SERVER['argv'][1])) {
+ $colors = $_SERVER['argv'];
+ $i = \count($colors);
+ while (--$i > 0) {
+ if (isset($colors[$i][5])) {
+ switch ($colors[$i]) {
+ case '--ansi':
+ case '--color':
+ case '--color=yes':
+ case '--color=force':
+ case '--color=always':
+ return static::$defaultColors = true;
+
+ case '--no-ansi':
+ case '--color=no':
+ case '--color=none':
+ case '--color=never':
+ return static::$defaultColors = false;
+ }
+ }
+ }
+ }
+
+ $h = stream_get_meta_data($this->outputStream) + ['wrapper_type' => null];
+ $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'wb') : $this->outputStream;
+
+ return static::$defaultColors = $this->hasColorSupport($h);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function dumpLine($depth, $endOfValue = false)
+ {
+ if ($this->colors) {
+ $this->line = sprintf("\033[%sm%s\033[m", $this->styles['default'], $this->line);
+ }
+ parent::dumpLine($depth);
+ }
+
+ protected function endValue(Cursor $cursor)
+ {
+ if (Stub::ARRAY_INDEXED === $cursor->hashType || Stub::ARRAY_ASSOC === $cursor->hashType) {
+ if (self::DUMP_TRAILING_COMMA & $this->flags && 0 < $cursor->depth) {
+ $this->line .= ',';
+ } elseif (self::DUMP_COMMA_SEPARATOR & $this->flags && 1 < $cursor->hashLength - $cursor->hashIndex) {
+ $this->line .= ',';
+ }
+ }
+
+ $this->dumpLine($cursor->depth, true);
+ }
+
+ /**
+ * Returns true if the stream supports colorization.
+ *
+ * Reference: Composer\XdebugHandler\Process::supportsColor
+ * https://github.com/composer/xdebug-handler
+ *
+ * @param mixed $stream A CLI output stream
+ *
+ * @return bool
+ */
+ private function hasColorSupport($stream)
+ {
+ if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) {
+ return false;
+ }
+
+ if ('Hyper' === getenv('TERM_PROGRAM')) {
+ return true;
+ }
+
+ if (\DIRECTORY_SEPARATOR === '\\') {
+ return (\function_exists('sapi_windows_vt100_support')
+ && @sapi_windows_vt100_support($stream))
+ || false !== getenv('ANSICON')
+ || 'ON' === getenv('ConEmuANSI')
+ || 'xterm' === getenv('TERM');
+ }
+
+ if (\function_exists('stream_isatty')) {
+ return @stream_isatty($stream);
+ }
+
+ if (\function_exists('posix_isatty')) {
+ return @posix_isatty($stream);
+ }
+
+ $stat = @fstat($stream);
+ // Check if formatted mode is S_IFCHR
+ return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
+ }
+
+ /**
+ * Returns true if the Windows terminal supports true color.
+ *
+ * Note that this does not check an output stream, but relies on environment
+ * variables from known implementations, or a PHP and Windows version that
+ * supports true color.
+ *
+ * @return bool
+ */
+ private function isWindowsTrueColor()
+ {
+ $result = 183 <= getenv('ANSICON_VER')
+ || 'ON' === getenv('ConEmuANSI')
+ || 'xterm' === getenv('TERM')
+ || 'Hyper' === getenv('TERM_PROGRAM');
+
+ if (!$result && \PHP_VERSION_ID >= 70200) {
+ $version = sprintf(
+ '%s.%s.%s',
+ PHP_WINDOWS_VERSION_MAJOR,
+ PHP_WINDOWS_VERSION_MINOR,
+ PHP_WINDOWS_VERSION_BUILD
+ );
+ $result = $version >= '10.0.15063';
+ }
+
+ return $result;
+ }
+
+ private function getSourceLink($file, $line)
+ {
+ if ($fmt = $this->displayOptions['fileLinkFormat']) {
+ return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://'.$file);
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php
new file mode 100755
index 0000000..e7f8ccf
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php
@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper\ContextProvider;
+
+/**
+ * Tries to provide context on CLI.
+ *
+ * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
+ */
+final class CliContextProvider implements ContextProviderInterface
+{
+ public function getContext(): ?array
+ {
+ if ('cli' !== \PHP_SAPI) {
+ return null;
+ }
+
+ return [
+ 'command_line' => $commandLine = implode(' ', $_SERVER['argv']),
+ 'identifier' => hash('crc32b', $commandLine.$_SERVER['REQUEST_TIME_FLOAT']),
+ ];
+ }
+}
diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php
new file mode 100755
index 0000000..38ef3b0
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper\ContextProvider;
+
+/**
+ * Interface to provide contextual data about dump data clones sent to a server.
+ *
+ * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
+ */
+interface ContextProviderInterface
+{
+ /**
+ * @return array|null Context data or null if unable to provide any context
+ */
+ public function getContext(): ?array;
+}
diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php
new file mode 100755
index 0000000..3684a47
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php
@@ -0,0 +1,51 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper\ContextProvider;
+
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\VarDumper\Caster\ReflectionCaster;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+
+/**
+ * Tries to provide context from a request.
+ *
+ * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
+ */
+final class RequestContextProvider implements ContextProviderInterface
+{
+ private $requestStack;
+ private $cloner;
+
+ public function __construct(RequestStack $requestStack)
+ {
+ $this->requestStack = $requestStack;
+ $this->cloner = new VarCloner();
+ $this->cloner->setMaxItems(0);
+ $this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
+ }
+
+ public function getContext(): ?array
+ {
+ if (null === $request = $this->requestStack->getCurrentRequest()) {
+ return null;
+ }
+
+ $controller = $request->attributes->get('_controller');
+
+ return [
+ 'uri' => $request->getUri(),
+ 'method' => $request->getMethod(),
+ 'controller' => $controller ? $this->cloner->cloneVar($controller) : $controller,
+ 'identifier' => spl_object_hash($request),
+ ];
+ }
+}
diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php
new file mode 100755
index 0000000..e43e19f
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php
@@ -0,0 +1,126 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper\ContextProvider;
+
+use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+use Symfony\Component\VarDumper\VarDumper;
+use Twig\Template;
+
+/**
+ * Tries to provide context from sources (class name, file, line, code excerpt, ...).
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
+ */
+final class SourceContextProvider implements ContextProviderInterface
+{
+ private $limit;
+ private $charset;
+ private $projectDir;
+ private $fileLinkFormatter;
+
+ public function __construct(string $charset = null, string $projectDir = null, FileLinkFormatter $fileLinkFormatter = null, int $limit = 9)
+ {
+ $this->charset = $charset;
+ $this->projectDir = $projectDir;
+ $this->fileLinkFormatter = $fileLinkFormatter;
+ $this->limit = $limit;
+ }
+
+ public function getContext(): ?array
+ {
+ $trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, $this->limit);
+
+ $file = $trace[1]['file'];
+ $line = $trace[1]['line'];
+ $name = false;
+ $fileExcerpt = false;
+
+ for ($i = 2; $i < $this->limit; ++$i) {
+ if (isset($trace[$i]['class'], $trace[$i]['function'])
+ && 'dump' === $trace[$i]['function']
+ && VarDumper::class === $trace[$i]['class']
+ ) {
+ $file = $trace[$i]['file'];
+ $line = $trace[$i]['line'];
+
+ while (++$i < $this->limit) {
+ if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && 0 !== strpos($trace[$i]['function'], 'call_user_func')) {
+ $file = $trace[$i]['file'];
+ $line = $trace[$i]['line'];
+
+ break;
+ } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) {
+ $template = $trace[$i]['object'];
+ $name = $template->getTemplateName();
+ $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false);
+ $info = $template->getDebugInfo();
+ if (isset($info[$trace[$i - 1]['line']])) {
+ $line = $info[$trace[$i - 1]['line']];
+ $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null;
+
+ if ($src) {
+ $src = explode("\n", $src);
+ $fileExcerpt = [];
+
+ for ($i = max($line - 3, 1), $max = min($line + 3, \count($src)); $i <= $max; ++$i) {
+ $fileExcerpt[] = '<li'.($i === $line ? ' class="selected"' : '').'><code>'.$this->htmlEncode($src[$i - 1]).'</code></li>';
+ }
+
+ $fileExcerpt = '<ol start="'.max($line - 3, 1).'">'.implode("\n", $fileExcerpt).'</ol>';
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ if (false === $name) {
+ $name = str_replace('\\', '/', $file);
+ $name = substr($name, strrpos($name, '/') + 1);
+ }
+
+ $context = ['name' => $name, 'file' => $file, 'line' => $line];
+ $context['file_excerpt'] = $fileExcerpt;
+
+ if (null !== $this->projectDir) {
+ $context['project_dir'] = $this->projectDir;
+ if (0 === strpos($file, $this->projectDir)) {
+ $context['file_relative'] = ltrim(substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR);
+ }
+ }
+
+ if ($this->fileLinkFormatter && $fileLink = $this->fileLinkFormatter->format($context['file'], $context['line'])) {
+ $context['file_link'] = $fileLink;
+ }
+
+ return $context;
+ }
+
+ private function htmlEncode(string $s): string
+ {
+ $html = '';
+
+ $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset);
+ $dumper->setDumpHeader('');
+ $dumper->setDumpBoundaries('', '');
+
+ $cloner = new VarCloner();
+ $dumper->dump($cloner->cloneVar($s));
+
+ return substr(strip_tags($html), 1, -1);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php
new file mode 100755
index 0000000..b173bcc
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper;
+
+use Symfony\Component\VarDumper\Cloner\Data;
+
+/**
+ * DataDumperInterface for dumping Data objects.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+interface DataDumperInterface
+{
+ public function dump(Data $data);
+}
diff --git a/vendor/symfony/var-dumper/Dumper/HtmlDumper.php b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php
new file mode 100755
index 0000000..b113fbb
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php
@@ -0,0 +1,969 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper;
+
+use Symfony\Component\VarDumper\Cloner\Cursor;
+use Symfony\Component\VarDumper\Cloner\Data;
+
+/**
+ * HtmlDumper dumps variables as HTML.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class HtmlDumper extends CliDumper
+{
+ public static $defaultOutput = 'php://output';
+
+ protected static $themes = [
+ 'dark' => [
+ 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all',
+ 'num' => 'font-weight:bold; color:#1299DA',
+ 'const' => 'font-weight:bold',
+ 'str' => 'font-weight:bold; color:#56DB3A',
+ 'note' => 'color:#1299DA',
+ 'ref' => 'color:#A0A0A0',
+ 'public' => 'color:#FFFFFF',
+ 'protected' => 'color:#FFFFFF',
+ 'private' => 'color:#FFFFFF',
+ 'meta' => 'color:#B729D9',
+ 'key' => 'color:#56DB3A',
+ 'index' => 'color:#1299DA',
+ 'ellipsis' => 'color:#FF8400',
+ 'ns' => 'user-select:none;',
+ ],
+ 'light' => [
+ 'default' => 'background:none; color:#CC7832; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all',
+ 'num' => 'font-weight:bold; color:#1299DA',
+ 'const' => 'font-weight:bold',
+ 'str' => 'font-weight:bold; color:#629755;',
+ 'note' => 'color:#6897BB',
+ 'ref' => 'color:#6E6E6E',
+ 'public' => 'color:#262626',
+ 'protected' => 'color:#262626',
+ 'private' => 'color:#262626',
+ 'meta' => 'color:#B729D9',
+ 'key' => 'color:#789339',
+ 'index' => 'color:#1299DA',
+ 'ellipsis' => 'color:#CC7832',
+ 'ns' => 'user-select:none;',
+ ],
+ ];
+
+ protected $dumpHeader;
+ protected $dumpPrefix = '<pre class=sf-dump id=%s data-indent-pad="%s">';
+ protected $dumpSuffix = '</pre><script>Sfdump(%s)</script>';
+ protected $dumpId = 'sf-dump';
+ protected $colors = true;
+ protected $headerIsDumped = false;
+ protected $lastDepth = -1;
+ protected $styles;
+
+ private $displayOptions = [
+ 'maxDepth' => 1,
+ 'maxStringLength' => 160,
+ 'fileLinkFormat' => null,
+ ];
+ private $extraDisplayOptions = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct($output = null, string $charset = null, int $flags = 0)
+ {
+ AbstractDumper::__construct($output, $charset, $flags);
+ $this->dumpId = 'sf-dump-'.mt_rand();
+ $this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
+ $this->styles = static::$themes['dark'] ?? self::$themes['dark'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setStyles(array $styles)
+ {
+ $this->headerIsDumped = false;
+ $this->styles = $styles + $this->styles;
+ }
+
+ public function setTheme(string $themeName)
+ {
+ if (!isset(static::$themes[$themeName])) {
+ throw new \InvalidArgumentException(sprintf('Theme "%s" does not exist in class "%s".', $themeName, static::class));
+ }
+
+ $this->setStyles(static::$themes[$themeName]);
+ }
+
+ /**
+ * Configures display options.
+ *
+ * @param array $displayOptions A map of display options to customize the behavior
+ */
+ public function setDisplayOptions(array $displayOptions)
+ {
+ $this->headerIsDumped = false;
+ $this->displayOptions = $displayOptions + $this->displayOptions;
+ }
+
+ /**
+ * Sets an HTML header that will be dumped once in the output stream.
+ *
+ * @param string $header An HTML string
+ */
+ public function setDumpHeader($header)
+ {
+ $this->dumpHeader = $header;
+ }
+
+ /**
+ * Sets an HTML prefix and suffix that will encapse every single dump.
+ *
+ * @param string $prefix The prepended HTML string
+ * @param string $suffix The appended HTML string
+ */
+ public function setDumpBoundaries($prefix, $suffix)
+ {
+ $this->dumpPrefix = $prefix;
+ $this->dumpSuffix = $suffix;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dump(Data $data, $output = null, array $extraDisplayOptions = [])
+ {
+ $this->extraDisplayOptions = $extraDisplayOptions;
+ $result = parent::dump($data, $output);
+ $this->dumpId = 'sf-dump-'.mt_rand();
+
+ return $result;
+ }
+
+ /**
+ * Dumps the HTML header.
+ */
+ protected function getDumpHeader()
+ {
+ $this->headerIsDumped = null !== $this->outputStream ? $this->outputStream : $this->lineDumper;
+
+ if (null !== $this->dumpHeader) {
+ return $this->dumpHeader;
+ }
+
+ $line = str_replace('{$options}', json_encode($this->displayOptions, JSON_FORCE_OBJECT), <<<'EOHTML'
+<script>
+Sfdump = window.Sfdump || (function (doc) {
+
+var refStyle = doc.createElement('style'),
+ rxEsc = /([.*+?^${}()|\[\]\/\\])/g,
+ idRx = /\bsf-dump-\d+-ref[012]\w+\b/,
+ keyHint = 0 <= navigator.platform.toUpperCase().indexOf('MAC') ? 'Cmd' : 'Ctrl',
+ addEventListener = function (e, n, cb) {
+ e.addEventListener(n, cb, false);
+ };
+
+(doc.documentElement.firstElementChild || doc.documentElement.children[0]).appendChild(refStyle);
+
+if (!doc.addEventListener) {
+ addEventListener = function (element, eventName, callback) {
+ element.attachEvent('on' + eventName, function (e) {
+ e.preventDefault = function () {e.returnValue = false;};
+ e.target = e.srcElement;
+ callback(e);
+ });
+ };
+}
+
+function toggle(a, recursive) {
+ var s = a.nextSibling || {}, oldClass = s.className, arrow, newClass;
+
+ if (/\bsf-dump-compact\b/.test(oldClass)) {
+ arrow = '▼';
+ newClass = 'sf-dump-expanded';
+ } else if (/\bsf-dump-expanded\b/.test(oldClass)) {
+ arrow = '▶';
+ newClass = 'sf-dump-compact';
+ } else {
+ return false;
+ }
+
+ if (doc.createEvent && s.dispatchEvent) {
+ var event = doc.createEvent('Event');
+ event.initEvent('sf-dump-expanded' === newClass ? 'sfbeforedumpexpand' : 'sfbeforedumpcollapse', true, false);
+
+ s.dispatchEvent(event);
+ }
+
+ a.lastChild.innerHTML = arrow;
+ s.className = s.className.replace(/\bsf-dump-(compact|expanded)\b/, newClass);
+
+ if (recursive) {
+ try {
+ a = s.querySelectorAll('.'+oldClass);
+ for (s = 0; s < a.length; ++s) {
+ if (-1 == a[s].className.indexOf(newClass)) {
+ a[s].className = newClass;
+ a[s].previousSibling.lastChild.innerHTML = arrow;
+ }
+ }
+ } catch (e) {
+ }
+ }
+
+ return true;
+};
+
+function collapse(a, recursive) {
+ var s = a.nextSibling || {}, oldClass = s.className;
+
+ if (/\bsf-dump-expanded\b/.test(oldClass)) {
+ toggle(a, recursive);
+
+ return true;
+ }
+
+ return false;
+};
+
+function expand(a, recursive) {
+ var s = a.nextSibling || {}, oldClass = s.className;
+
+ if (/\bsf-dump-compact\b/.test(oldClass)) {
+ toggle(a, recursive);
+
+ return true;
+ }
+
+ return false;
+};
+
+function collapseAll(root) {
+ var a = root.querySelector('a.sf-dump-toggle');
+ if (a) {
+ collapse(a, true);
+ expand(a);
+
+ return true;
+ }
+
+ return false;
+}
+
+function reveal(node) {
+ var previous, parents = [];
+
+ while ((node = node.parentNode || {}) && (previous = node.previousSibling) && 'A' === previous.tagName) {
+ parents.push(previous);
+ }
+
+ if (0 !== parents.length) {
+ parents.forEach(function (parent) {
+ expand(parent);
+ });
+
+ return true;
+ }
+
+ return false;
+}
+
+function highlight(root, activeNode, nodes) {
+ resetHighlightedNodes(root);
+
+ Array.from(nodes||[]).forEach(function (node) {
+ if (!/\bsf-dump-highlight\b/.test(node.className)) {
+ node.className = node.className + ' sf-dump-highlight';
+ }
+ });
+
+ if (!/\bsf-dump-highlight-active\b/.test(activeNode.className)) {
+ activeNode.className = activeNode.className + ' sf-dump-highlight-active';
+ }
+}
+
+function resetHighlightedNodes(root) {
+ Array.from(root.querySelectorAll('.sf-dump-str, .sf-dump-key, .sf-dump-public, .sf-dump-protected, .sf-dump-private')).forEach(function (strNode) {
+ strNode.className = strNode.className.replace(/\bsf-dump-highlight\b/, '');
+ strNode.className = strNode.className.replace(/\bsf-dump-highlight-active\b/, '');
+ });
+}
+
+return function (root, x) {
+ root = doc.getElementById(root);
+
+ var indentRx = new RegExp('^('+(root.getAttribute('data-indent-pad') || ' ').replace(rxEsc, '\\$1')+')+', 'm'),
+ options = {$options},
+ elt = root.getElementsByTagName('A'),
+ len = elt.length,
+ i = 0, s, h,
+ t = [];
+
+ while (i < len) t.push(elt[i++]);
+
+ for (i in x) {
+ options[i] = x[i];
+ }
+
+ function a(e, f) {
+ addEventListener(root, e, function (e, n) {
+ if ('A' == e.target.tagName) {
+ f(e.target, e);
+ } else if ('A' == e.target.parentNode.tagName) {
+ f(e.target.parentNode, e);
+ } else if ((n = e.target.nextElementSibling) && 'A' == n.tagName) {
+ if (!/\bsf-dump-toggle\b/.test(n.className)) {
+ n = n.nextElementSibling;
+ }
+
+ f(n, e, true);
+ }
+ });
+ };
+ function isCtrlKey(e) {
+ return e.ctrlKey || e.metaKey;
+ }
+ function xpathString(str) {
+ var parts = str.match(/[^'"]+|['"]/g).map(function (part) {
+ if ("'" == part) {
+ return '"\'"';
+ }
+ if ('"' == part) {
+ return "'\"'";
+ }
+
+ return "'" + part + "'";
+ });
+
+ return "concat(" + parts.join(",") + ", '')";
+ }
+ function xpathHasClass(className) {
+ return "contains(concat(' ', normalize-space(@class), ' '), ' " + className +" ')";
+ }
+ addEventListener(root, 'mouseover', function (e) {
+ if ('' != refStyle.innerHTML) {
+ refStyle.innerHTML = '';
+ }
+ });
+ a('mouseover', function (a, e, c) {
+ if (c) {
+ e.target.style.cursor = "pointer";
+ } else if (a = idRx.exec(a.className)) {
+ try {
+ refStyle.innerHTML = 'pre.sf-dump .'+a[0]+'{background-color: #B729D9; color: #FFF !important; border-radius: 2px}';
+ } catch (e) {
+ }
+ }
+ });
+ a('click', function (a, e, c) {
+ if (/\bsf-dump-toggle\b/.test(a.className)) {
+ e.preventDefault();
+ if (!toggle(a, isCtrlKey(e))) {
+ var r = doc.getElementById(a.getAttribute('href').substr(1)),
+ s = r.previousSibling,
+ f = r.parentNode,
+ t = a.parentNode;
+ t.replaceChild(r, a);
+ f.replaceChild(a, s);
+ t.insertBefore(s, r);
+ f = f.firstChild.nodeValue.match(indentRx);
+ t = t.firstChild.nodeValue.match(indentRx);
+ if (f && t && f[0] !== t[0]) {
+ r.innerHTML = r.innerHTML.replace(new RegExp('^'+f[0].replace(rxEsc, '\\$1'), 'mg'), t[0]);
+ }
+ if (/\bsf-dump-compact\b/.test(r.className)) {
+ toggle(s, isCtrlKey(e));
+ }
+ }
+
+ if (c) {
+ } else if (doc.getSelection) {
+ try {
+ doc.getSelection().removeAllRanges();
+ } catch (e) {
+ doc.getSelection().empty();
+ }
+ } else {
+ doc.selection.empty();
+ }
+ } else if (/\bsf-dump-str-toggle\b/.test(a.className)) {
+ e.preventDefault();
+ e = a.parentNode.parentNode;
+ e.className = e.className.replace(/\bsf-dump-str-(expand|collapse)\b/, a.parentNode.className);
+ }
+ });
+
+ elt = root.getElementsByTagName('SAMP');
+ len = elt.length;
+ i = 0;
+
+ while (i < len) t.push(elt[i++]);
+ len = t.length;
+
+ for (i = 0; i < len; ++i) {
+ elt = t[i];
+ if ('SAMP' == elt.tagName) {
+ a = elt.previousSibling || {};
+ if ('A' != a.tagName) {
+ a = doc.createElement('A');
+ a.className = 'sf-dump-ref';
+ elt.parentNode.insertBefore(a, elt);
+ } else {
+ a.innerHTML += ' ';
+ }
+ a.title = (a.title ? a.title+'\n[' : '[')+keyHint+'+click] Expand all children';
+ a.innerHTML += '<span>▼</span>';
+ a.className += ' sf-dump-toggle';
+
+ x = 1;
+ if ('sf-dump' != elt.parentNode.className) {
+ x += elt.parentNode.getAttribute('data-depth')/1;
+ }
+ elt.setAttribute('data-depth', x);
+ var className = elt.className;
+ elt.className = 'sf-dump-expanded';
+ if (className ? 'sf-dump-expanded' !== className : (x > options.maxDepth)) {
+ toggle(a);
+ }
+ } else if (/\bsf-dump-ref\b/.test(elt.className) && (a = elt.getAttribute('href'))) {
+ a = a.substr(1);
+ elt.className += ' '+a;
+
+ if (/[\[{]$/.test(elt.previousSibling.nodeValue)) {
+ a = a != elt.nextSibling.id && doc.getElementById(a);
+ try {
+ s = a.nextSibling;
+ elt.appendChild(a);
+ s.parentNode.insertBefore(a, s);
+ if (/^[@#]/.test(elt.innerHTML)) {
+ elt.innerHTML += ' <span>▶</span>';
+ } else {
+ elt.innerHTML = '<span>▶</span>';
+ elt.className = 'sf-dump-ref';
+ }
+ elt.className += ' sf-dump-toggle';
+ } catch (e) {
+ if ('&' == elt.innerHTML.charAt(0)) {
+ elt.innerHTML = '…';
+ elt.className = 'sf-dump-ref';
+ }
+ }
+ }
+ }
+ }
+
+ if (doc.evaluate && Array.from && root.children.length > 1) {
+ root.setAttribute('tabindex', 0);
+
+ SearchState = function () {
+ this.nodes = [];
+ this.idx = 0;
+ };
+ SearchState.prototype = {
+ next: function () {
+ if (this.isEmpty()) {
+ return this.current();
+ }
+ this.idx = this.idx < (this.nodes.length - 1) ? this.idx + 1 : 0;
+
+ return this.current();
+ },
+ previous: function () {
+ if (this.isEmpty()) {
+ return this.current();
+ }
+ this.idx = this.idx > 0 ? this.idx - 1 : (this.nodes.length - 1);
+
+ return this.current();
+ },
+ isEmpty: function () {
+ return 0 === this.count();
+ },
+ current: function () {
+ if (this.isEmpty()) {
+ return null;
+ }
+ return this.nodes[this.idx];
+ },
+ reset: function () {
+ this.nodes = [];
+ this.idx = 0;
+ },
+ count: function () {
+ return this.nodes.length;
+ },
+ };
+
+ function showCurrent(state)
+ {
+ var currentNode = state.current(), currentRect, searchRect;
+ if (currentNode) {
+ reveal(currentNode);
+ highlight(root, currentNode, state.nodes);
+ if ('scrollIntoView' in currentNode) {
+ currentNode.scrollIntoView(true);
+ currentRect = currentNode.getBoundingClientRect();
+ searchRect = search.getBoundingClientRect();
+ if (currentRect.top < (searchRect.top + searchRect.height)) {
+ window.scrollBy(0, -(searchRect.top + searchRect.height + 5));
+ }
+ }
+ }
+ counter.textContent = (state.isEmpty() ? 0 : state.idx + 1) + ' of ' + state.count();
+ }
+
+ var search = doc.createElement('div');
+ search.className = 'sf-dump-search-wrapper sf-dump-search-hidden';
+ search.innerHTML = '
+ <input type="text" class="sf-dump-search-input">
+ <span class="sf-dump-search-count">0 of 0<\/span>
+ <button type="button" class="sf-dump-search-input-previous" tabindex="-1">
+ <svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1683 1331l-166 165q-19 19-45 19t-45-19L896 965l-531 531q-19 19-45 19t-45-19l-166-165q-19-19-19-45.5t19-45.5l742-741q19-19 45-19t45 19l742 741q19 19 19 45.5t-19 45.5z"\/><\/svg>
+ <\/button>
+ <button type="button" class="sf-dump-search-input-next" tabindex="-1">
+ <svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1683 808l-742 741q-19 19-45 19t-45-19L109 808q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"\/><\/svg>
+ <\/button>
+ ';
+ root.insertBefore(search, root.firstChild);
+
+ var state = new SearchState();
+ var searchInput = search.querySelector('.sf-dump-search-input');
+ var counter = search.querySelector('.sf-dump-search-count');
+ var searchInputTimer = 0;
+ var previousSearchQuery = '';
+
+ addEventListener(searchInput, 'keyup', function (e) {
+ var searchQuery = e.target.value;
+ /* Don't perform anything if the pressed key didn't change the query */
+ if (searchQuery === previousSearchQuery) {
+ return;
+ }
+ previousSearchQuery = searchQuery;
+ clearTimeout(searchInputTimer);
+ searchInputTimer = setTimeout(function () {
+ state.reset();
+ collapseAll(root);
+ resetHighlightedNodes(root);
+ if ('' === searchQuery) {
+ counter.textContent = '0 of 0';
+
+ return;
+ }
+
+ var classMatches = [
+ "sf-dump-str",
+ "sf-dump-key",
+ "sf-dump-public",
+ "sf-dump-protected",
+ "sf-dump-private",
+ ].map(xpathHasClass).join(' or ');
+
+ var xpathResult = doc.evaluate('.//span[' + classMatches + '][contains(translate(child::text(), ' + xpathString(searchQuery.toUpperCase()) + ', ' + xpathString(searchQuery.toLowerCase()) + '), ' + xpathString(searchQuery.toLowerCase()) + ')]', root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
+
+ while (node = xpathResult.iterateNext()) state.nodes.push(node);
+
+ showCurrent(state);
+ }, 400);
+ });
+
+ Array.from(search.querySelectorAll('.sf-dump-search-input-next, .sf-dump-search-input-previous')).forEach(function (btn) {
+ addEventListener(btn, 'click', function (e) {
+ e.preventDefault();
+ -1 !== e.target.className.indexOf('next') ? state.next() : state.previous();
+ searchInput.focus();
+ collapseAll(root);
+ showCurrent(state);
+ })
+ });
+
+ addEventListener(root, 'keydown', function (e) {
+ var isSearchActive = !/\bsf-dump-search-hidden\b/.test(search.className);
+ if ((114 === e.keyCode && !isSearchActive) || (isCtrlKey(e) && 70 === e.keyCode)) {
+ /* F3 or CMD/CTRL + F */
+ e.preventDefault();
+ search.className = search.className.replace(/\bsf-dump-search-hidden\b/, '');
+ searchInput.focus();
+ } else if (isSearchActive) {
+ if (27 === e.keyCode) {
+ /* ESC key */
+ search.className += ' sf-dump-search-hidden';
+ e.preventDefault();
+ resetHighlightedNodes(root);
+ searchInput.value = '';
+ } else if (
+ (isCtrlKey(e) && 71 === e.keyCode) /* CMD/CTRL + G */
+ || 13 === e.keyCode /* Enter */
+ || 114 === e.keyCode /* F3 */
+ ) {
+ e.preventDefault();
+ e.shiftKey ? state.previous() : state.next();
+ collapseAll(root);
+ showCurrent(state);
+ }
+ }
+ });
+ }
+
+ if (0 >= options.maxStringLength) {
+ return;
+ }
+ try {
+ elt = root.querySelectorAll('.sf-dump-str');
+ len = elt.length;
+ i = 0;
+ t = [];
+
+ while (i < len) t.push(elt[i++]);
+ len = t.length;
+
+ for (i = 0; i < len; ++i) {
+ elt = t[i];
+ s = elt.innerText || elt.textContent;
+ x = s.length - options.maxStringLength;
+ if (0 < x) {
+ h = elt.innerHTML;
+ elt[elt.innerText ? 'innerText' : 'textContent'] = s.substring(0, options.maxStringLength);
+ elt.className += ' sf-dump-str-collapse';
+ elt.innerHTML = '<span class=sf-dump-str-collapse>'+h+'<a class="sf-dump-ref sf-dump-str-toggle" title="Collapse"> ◀</a></span>'+
+ '<span class=sf-dump-str-expand>'+elt.innerHTML+'<a class="sf-dump-ref sf-dump-str-toggle" title="'+x+' remaining characters"> ▶</a></span>';
+ }
+ }
+ } catch (e) {
+ }
+};
+
+})(document);
+</script><style>
+pre.sf-dump {
+ display: block;
+ white-space: pre;
+ padding: 5px;
+ overflow: initial !important;
+}
+pre.sf-dump:after {
+ content: "";
+ visibility: hidden;
+ display: block;
+ height: 0;
+ clear: both;
+}
+pre.sf-dump span {
+ display: inline;
+}
+pre.sf-dump .sf-dump-compact {
+ display: none;
+}
+pre.sf-dump abbr {
+ text-decoration: none;
+ border: none;
+ cursor: help;
+}
+pre.sf-dump a {
+ text-decoration: none;
+ cursor: pointer;
+ border: 0;
+ outline: none;
+ color: inherit;
+}
+pre.sf-dump .sf-dump-ellipsis {
+ display: inline-block;
+ overflow: visible;
+ text-overflow: ellipsis;
+ max-width: 5em;
+ white-space: nowrap;
+ overflow: hidden;
+ vertical-align: top;
+}
+pre.sf-dump .sf-dump-ellipsis+.sf-dump-ellipsis {
+ max-width: none;
+}
+pre.sf-dump code {
+ display:inline;
+ padding:0;
+ background:none;
+}
+.sf-dump-str-collapse .sf-dump-str-collapse {
+ display: none;
+}
+.sf-dump-str-expand .sf-dump-str-expand {
+ display: none;
+}
+.sf-dump-public.sf-dump-highlight,
+.sf-dump-protected.sf-dump-highlight,
+.sf-dump-private.sf-dump-highlight,
+.sf-dump-str.sf-dump-highlight,
+.sf-dump-key.sf-dump-highlight {
+ background: rgba(111, 172, 204, 0.3);
+ border: 1px solid #7DA0B1;
+ border-radius: 3px;
+}
+.sf-dump-public.sf-dump-highlight-active,
+.sf-dump-protected.sf-dump-highlight-active,
+.sf-dump-private.sf-dump-highlight-active,
+.sf-dump-str.sf-dump-highlight-active,
+.sf-dump-key.sf-dump-highlight-active {
+ background: rgba(253, 175, 0, 0.4);
+ border: 1px solid #ffa500;
+ border-radius: 3px;
+}
+pre.sf-dump .sf-dump-search-hidden {
+ display: none !important;
+}
+pre.sf-dump .sf-dump-search-wrapper {
+ font-size: 0;
+ white-space: nowrap;
+ margin-bottom: 5px;
+ display: flex;
+ position: -webkit-sticky;
+ position: sticky;
+ top: 5px;
+}
+pre.sf-dump .sf-dump-search-wrapper > * {
+ vertical-align: top;
+ box-sizing: border-box;
+ height: 21px;
+ font-weight: normal;
+ border-radius: 0;
+ background: #FFF;
+ color: #757575;
+ border: 1px solid #BBB;
+}
+pre.sf-dump .sf-dump-search-wrapper > input.sf-dump-search-input {
+ padding: 3px;
+ height: 21px;
+ font-size: 12px;
+ border-right: none;
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ color: #000;
+ min-width: 15px;
+ width: 100%;
+}
+pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next,
+pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-previous {
+ background: #F2F2F2;
+ outline: none;
+ border-left: none;
+ font-size: 0;
+ line-height: 0;
+}
+pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next {
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+}
+pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next > svg,
+pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-previous > svg {
+ pointer-events: none;
+ width: 12px;
+ height: 12px;
+}
+pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-count {
+ display: inline-block;
+ padding: 0 5px;
+ margin: 0;
+ border-left: none;
+ line-height: 21px;
+ font-size: 12px;
+}
+EOHTML
+ );
+
+ foreach ($this->styles as $class => $style) {
+ $line .= 'pre.sf-dump'.('default' === $class ? ', pre.sf-dump' : '').' .sf-dump-'.$class.'{'.$style.'}';
+ }
+
+ return $this->dumpHeader = preg_replace('/\s+/', ' ', $line).'</style>'.$this->dumpHeader;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function enterHash(Cursor $cursor, $type, $class, $hasChild)
+ {
+ parent::enterHash($cursor, $type, $class, false);
+
+ if ($cursor->skipChildren) {
+ $cursor->skipChildren = false;
+ $eol = ' class=sf-dump-compact>';
+ } elseif ($this->expandNextHash) {
+ $this->expandNextHash = false;
+ $eol = ' class=sf-dump-expanded>';
+ } else {
+ $eol = '>';
+ }
+
+ if ($hasChild) {
+ $this->line .= '<samp';
+ if ($cursor->refIndex) {
+ $r = Cursor::HASH_OBJECT !== $type ? 1 - (Cursor::HASH_RESOURCE !== $type) : 2;
+ $r .= $r && 0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->refIndex;
+
+ $this->line .= sprintf(' id=%s-ref%s', $this->dumpId, $r);
+ }
+ $this->line .= $eol;
+ $this->dumpLine($cursor->depth);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut)
+ {
+ $this->dumpEllipsis($cursor, $hasChild, $cut);
+ if ($hasChild) {
+ $this->line .= '</samp>';
+ }
+ parent::leaveHash($cursor, $type, $class, $hasChild, 0);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function style($style, $value, $attr = [])
+ {
+ if ('' === $value) {
+ return '';
+ }
+
+ $v = esc($value);
+
+ if ('ref' === $style) {
+ if (empty($attr['count'])) {
+ return sprintf('<a class=sf-dump-ref>%s</a>', $v);
+ }
+ $r = ('#' !== $v[0] ? 1 - ('@' !== $v[0]) : 2).substr($value, 1);
+
+ return sprintf('<a class=sf-dump-ref href=#%s-ref%s title="%d occurrences">%s</a>', $this->dumpId, $r, 1 + $attr['count'], $v);
+ }
+
+ if ('const' === $style && isset($attr['value'])) {
+ $style .= sprintf(' title="%s"', esc(is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value'])));
+ } elseif ('public' === $style) {
+ $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property');
+ } elseif ('str' === $style && 1 < $attr['length']) {
+ $style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : '');
+ } elseif ('note' === $style && false !== $c = strrpos($v, '\\')) {
+ if (isset($attr['file']) && $link = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) {
+ $link = sprintf('<a href="%s" rel="noopener noreferrer">^</a>', esc($this->utf8Encode($link)));
+ } else {
+ $link = '';
+ }
+
+ return sprintf('<abbr title="%s" class=sf-dump-%s>%s</abbr>%s', $v, $style, substr($v, $c + 1), $link);
+ } elseif ('protected' === $style) {
+ $style .= ' title="Protected property"';
+ } elseif ('meta' === $style && isset($attr['title'])) {
+ $style .= sprintf(' title="%s"', esc($this->utf8Encode($attr['title'])));
+ } elseif ('private' === $style) {
+ $style .= sprintf(' title="Private property defined in class: `%s`"', esc($this->utf8Encode($attr['class'])));
+ }
+ $map = static::$controlCharsMap;
+
+ if (isset($attr['ellipsis'])) {
+ $class = 'sf-dump-ellipsis';
+ if (isset($attr['ellipsis-type'])) {
+ $class = sprintf('"%s sf-dump-ellipsis-%s"', $class, $attr['ellipsis-type']);
+ }
+ $label = esc(substr($value, -$attr['ellipsis']));
+ $style = str_replace(' title="', " title=\"$v\n", $style);
+ $v = sprintf('<span class=%s>%s</span>', $class, substr($v, 0, -\strlen($label)));
+
+ if (!empty($attr['ellipsis-tail'])) {
+ $tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail'])));
+ $v .= sprintf('<span class=sf-dump-ellipsis>%s</span>%s', substr($label, 0, $tail), substr($label, $tail));
+ } else {
+ $v .= $label;
+ }
+ }
+
+ $v = "<span class=sf-dump-{$style}>".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) {
+ $s = $b = '<span class="sf-dump-default';
+ $c = $c[$i = 0];
+ if ($ns = "\r" === $c[$i] || "\n" === $c[$i]) {
+ $s .= ' sf-dump-ns';
+ }
+ $s .= '">';
+ do {
+ if (("\r" === $c[$i] || "\n" === $c[$i]) !== $ns) {
+ $s .= '</span>'.$b;
+ if ($ns = !$ns) {
+ $s .= ' sf-dump-ns';
+ }
+ $s .= '">';
+ }
+
+ $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', \ord($c[$i]));
+ } while (isset($c[++$i]));
+
+ return $s.'</span>';
+ }, $v).'</span>';
+
+ if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) {
+ $attr['href'] = $href;
+ }
+ if (isset($attr['href'])) {
+ $target = isset($attr['file']) ? '' : ' target="_blank"';
+ $v = sprintf('<a href="%s"%s rel="noopener noreferrer">%s</a>', esc($this->utf8Encode($attr['href'])), $target, $v);
+ }
+ if (isset($attr['lang'])) {
+ $v = sprintf('<code class="%s">%s</code>', esc($attr['lang']), $v);
+ }
+
+ return $v;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function dumpLine($depth, $endOfValue = false)
+ {
+ if (-1 === $this->lastDepth) {
+ $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line;
+ }
+ if ($this->headerIsDumped !== (null !== $this->outputStream ? $this->outputStream : $this->lineDumper)) {
+ $this->line = $this->getDumpHeader().$this->line;
+ }
+
+ if (-1 === $depth) {
+ $args = ['"'.$this->dumpId.'"'];
+ if ($this->extraDisplayOptions) {
+ $args[] = json_encode($this->extraDisplayOptions, JSON_FORCE_OBJECT);
+ }
+ // Replace is for BC
+ $this->line .= sprintf(str_replace('"%s"', '%s', $this->dumpSuffix), implode(', ', $args));
+ }
+ $this->lastDepth = $depth;
+
+ $this->line = mb_convert_encoding($this->line, 'HTML-ENTITIES', 'UTF-8');
+
+ if (-1 === $depth) {
+ AbstractDumper::dumpLine(0);
+ }
+ AbstractDumper::dumpLine($depth);
+ }
+
+ private function getSourceLink($file, $line)
+ {
+ $options = $this->extraDisplayOptions + $this->displayOptions;
+
+ if ($fmt = $options['fileLinkFormat']) {
+ return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line);
+ }
+
+ return false;
+ }
+}
+
+function esc($str)
+{
+ return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
+}
diff --git a/vendor/symfony/var-dumper/Dumper/ServerDumper.php b/vendor/symfony/var-dumper/Dumper/ServerDumper.php
new file mode 100755
index 0000000..94795bf
--- /dev/null
+++ b/vendor/symfony/var-dumper/Dumper/ServerDumper.php
@@ -0,0 +1,53 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper;
+
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
+use Symfony\Component\VarDumper\Server\Connection;
+
+/**
+ * ServerDumper forwards serialized Data clones to a server.
+ *
+ * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
+ */
+class ServerDumper implements DataDumperInterface
+{
+ private $connection;
+ private $wrappedDumper;
+
+ /**
+ * @param string $host The server host
+ * @param DataDumperInterface|null $wrappedDumper A wrapped instance used whenever we failed contacting the server
+ * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name
+ */
+ public function __construct(string $host, DataDumperInterface $wrappedDumper = null, array $contextProviders = [])
+ {
+ $this->connection = new Connection($host, $contextProviders);
+ $this->wrappedDumper = $wrappedDumper;
+ }
+
+ public function getContextProviders(): array
+ {
+ return $this->connection->getContextProviders();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dump(Data $data)
+ {
+ if (!$this->connection->write($data) && $this->wrappedDumper) {
+ $this->wrappedDumper->dump($data);
+ }
+ }
+}
diff --git a/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php
new file mode 100755
index 0000000..af47753
--- /dev/null
+++ b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php
@@ -0,0 +1,26 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Exception;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class ThrowingCasterException extends \Exception
+{
+ /**
+ * @param \Exception $prev The exception thrown from the caster
+ */
+ public function __construct(\Exception $prev)
+ {
+ parent::__construct('Unexpected '.\get_class($prev).' thrown from a caster: '.$prev->getMessage(), 0, $prev);
+ }
+}
diff --git a/vendor/symfony/var-dumper/LICENSE b/vendor/symfony/var-dumper/LICENSE
new file mode 100755
index 0000000..cf8b3eb
--- /dev/null
+++ b/vendor/symfony/var-dumper/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/var-dumper/README.md b/vendor/symfony/var-dumper/README.md
new file mode 100755
index 0000000..339f73e
--- /dev/null
+++ b/vendor/symfony/var-dumper/README.md
@@ -0,0 +1,15 @@
+VarDumper Component
+===================
+
+The VarDumper component provides mechanisms for walking through any arbitrary
+PHP variable. It provides a better `dump()` function that you can use instead
+of `var_dump`.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/vendor/symfony/var-dumper/Resources/bin/var-dump-server b/vendor/symfony/var-dumper/Resources/bin/var-dump-server
new file mode 100755
index 0000000..98c813a
--- /dev/null
+++ b/vendor/symfony/var-dumper/Resources/bin/var-dump-server
@@ -0,0 +1,63 @@
+#!/usr/bin/env php
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Starts a dump server to collect and output dumps on a single place with multiple formats support.
+ *
+ * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
+ */
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Input\ArgvInput;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Logger\ConsoleLogger;
+use Symfony\Component\Console\Output\ConsoleOutput;
+use Symfony\Component\VarDumper\Command\ServerDumpCommand;
+use Symfony\Component\VarDumper\Server\DumpServer;
+
+function includeIfExists(string $file): bool
+{
+ return file_exists($file) && include $file;
+}
+
+if (
+ !includeIfExists(__DIR__ . '/../../../../autoload.php') &&
+ !includeIfExists(__DIR__ . '/../../vendor/autoload.php') &&
+ !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php')
+) {
+ fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL);
+ exit(1);
+}
+
+if (!class_exists(Application::class)) {
+ fwrite(STDERR, 'You need the "symfony/console" component in order to run the VarDumper server.'.PHP_EOL);
+ exit(1);
+}
+
+$input = new ArgvInput();
+$output = new ConsoleOutput();
+$defaultHost = '127.0.0.1:9912';
+$host = $input->getParameterOption(['--host'], $_SERVER['VAR_DUMPER_SERVER'] ?? $defaultHost, true);
+$logger = interface_exists(LoggerInterface::class) ? new ConsoleLogger($output->getErrorOutput()) : null;
+
+$app = new Application();
+
+$app->getDefinition()->addOption(
+ new InputOption('--host', null, InputOption::VALUE_REQUIRED, 'The address the server should listen to', $defaultHost)
+);
+
+$app->add($command = new ServerDumpCommand(new DumpServer($host, $logger)))
+ ->getApplication()
+ ->setDefaultCommand($command->getName(), true)
+ ->run($input, $output)
+;
diff --git a/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css
new file mode 100755
index 0000000..8f706d6
--- /dev/null
+++ b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css
@@ -0,0 +1,130 @@
+body {
+ display: flex;
+ flex-direction: column-reverse;
+ justify-content: flex-end;
+ max-width: 1140px;
+ margin: auto;
+ padding: 15px;
+ word-wrap: break-word;
+ background-color: #F9F9F9;
+ color: #222;
+ font-family: Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ line-height: 1.4;
+}
+p {
+ margin: 0;
+}
+a {
+ color: #218BC3;
+ text-decoration: none;
+}
+a:hover {
+ text-decoration: underline;
+}
+.text-small {
+ font-size: 12px !important;
+}
+article {
+ margin: 5px;
+ margin-bottom: 10px;
+}
+article > header > .row {
+ display: flex;
+ flex-direction: row;
+ align-items: baseline;
+ margin-bottom: 10px;
+}
+article > header > .row > .col {
+ flex: 1;
+ display: flex;
+ align-items: baseline;
+}
+article > header > .row > h2 {
+ font-size: 14px;
+ color: #222;
+ font-weight: normal;
+ font-family: "Lucida Console", monospace, sans-serif;
+ word-break: break-all;
+ margin: 20px 5px 0 0;
+ user-select: all;
+}
+article > header > .row > h2 > code {
+ white-space: nowrap;
+ user-select: none;
+ color: #cc2255;
+ background-color: #f7f7f9;
+ border: 1px solid #e1e1e8;
+ border-radius: 3px;
+ margin-right: 5px;
+ padding: 0 3px;
+}
+article > header > .row > time.col {
+ flex: 0;
+ text-align: right;
+ white-space: nowrap;
+ color: #999;
+ font-style: italic;
+}
+article > header ul.tags {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ font-size: 12px;
+}
+article > header ul.tags > li {
+ user-select: all;
+ margin-bottom: 2px;
+}
+article > header ul.tags > li > span.badge {
+ display: inline-block;
+ padding: .25em .4em;
+ margin-right: 5px;
+ border-radius: 4px;
+ background-color: #6c757d3b;
+ color: #524d4d;
+ font-size: 12px;
+ text-align: center;
+ font-weight: 700;
+ line-height: 1;
+ white-space: nowrap;
+ vertical-align: baseline;
+ user-select: none;
+}
+article > section.body {
+ border: 1px solid #d8d8d8;
+ background: #FFF;
+ padding: 10px;
+ border-radius: 3px;
+}
+pre.sf-dump {
+ border-radius: 3px;
+ margin-bottom: 0;
+}
+.hidden {
+ display: none !important;
+}
+.dumped-tag > .sf-dump {
+ display: inline-block;
+ margin: 0;
+ padding: 1px 5px;
+ line-height: 1.4;
+ vertical-align: top;
+ background-color: transparent;
+ user-select: auto;
+}
+.dumped-tag > pre.sf-dump,
+.dumped-tag > .sf-dump-default {
+ color: #CC7832;
+ background: none;
+}
+.dumped-tag > .sf-dump .sf-dump-str { color: #629755; }
+.dumped-tag > .sf-dump .sf-dump-private,
+.dumped-tag > .sf-dump .sf-dump-protected,
+.dumped-tag > .sf-dump .sf-dump-public { color: #262626; }
+.dumped-tag > .sf-dump .sf-dump-note { color: #6897BB; }
+.dumped-tag > .sf-dump .sf-dump-key { color: #789339; }
+.dumped-tag > .sf-dump .sf-dump-ref { color: #6E6E6E; }
+.dumped-tag > .sf-dump .sf-dump-ellipsis { color: #CC7832; max-width: 100em; }
+.dumped-tag > .sf-dump .sf-dump-ellipsis-path { max-width: 5em; }
+.dumped-tag > .sf-dump .sf-dump-ns { user-select: none; }
diff --git a/vendor/symfony/var-dumper/Resources/functions/dump.php b/vendor/symfony/var-dumper/Resources/functions/dump.php
new file mode 100755
index 0000000..e1543a8
--- /dev/null
+++ b/vendor/symfony/var-dumper/Resources/functions/dump.php
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Component\VarDumper\VarDumper;
+
+if (!function_exists('dump')) {
+ /**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+ function dump($var, ...$moreVars)
+ {
+ VarDumper::dump($var);
+
+ foreach ($moreVars as $v) {
+ VarDumper::dump($v);
+ }
+
+ if (1 < func_num_args()) {
+ return func_get_args();
+ }
+
+ return $var;
+ }
+}
+
+if (!function_exists('dd')) {
+ function dd(...$vars)
+ {
+ foreach ($vars as $v) {
+ VarDumper::dump($v);
+ }
+
+ die(1);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js
new file mode 100755
index 0000000..63101e5
--- /dev/null
+++ b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js
@@ -0,0 +1,10 @@
+document.addEventListener('DOMContentLoaded', function() {
+ let prev = null;
+ Array.from(document.getElementsByTagName('article')).reverse().forEach(function (article) {
+ const dedupId = article.dataset.dedupId;
+ if (dedupId === prev) {
+ article.getElementsByTagName('header')[0].classList.add('hidden');
+ }
+ prev = dedupId;
+ });
+});
diff --git a/vendor/symfony/var-dumper/Server/Connection.php b/vendor/symfony/var-dumper/Server/Connection.php
new file mode 100755
index 0000000..8b814cb
--- /dev/null
+++ b/vendor/symfony/var-dumper/Server/Connection.php
@@ -0,0 +1,95 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Server;
+
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
+
+/**
+ * Forwards serialized Data clones to a server.
+ *
+ * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
+ */
+class Connection
+{
+ private $host;
+ private $contextProviders;
+ private $socket;
+
+ /**
+ * @param string $host The server host
+ * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name
+ */
+ public function __construct(string $host, array $contextProviders = [])
+ {
+ if (false === strpos($host, '://')) {
+ $host = 'tcp://'.$host;
+ }
+
+ $this->host = $host;
+ $this->contextProviders = $contextProviders;
+ }
+
+ public function getContextProviders(): array
+ {
+ return $this->contextProviders;
+ }
+
+ public function write(Data $data): bool
+ {
+ $socketIsFresh = !$this->socket;
+ if (!$this->socket = $this->socket ?: $this->createSocket()) {
+ return false;
+ }
+
+ $context = ['timestamp' => microtime(true)];
+ foreach ($this->contextProviders as $name => $provider) {
+ $context[$name] = $provider->getContext();
+ }
+ $context = array_filter($context);
+ $encodedPayload = base64_encode(serialize([$data, $context]))."\n";
+
+ set_error_handler([self::class, 'nullErrorHandler']);
+ try {
+ if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) {
+ return true;
+ }
+ if (!$socketIsFresh) {
+ stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
+ fclose($this->socket);
+ $this->socket = $this->createSocket();
+ }
+ if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) {
+ return true;
+ }
+ } finally {
+ restore_error_handler();
+ }
+
+ return false;
+ }
+
+ private static function nullErrorHandler($t, $m)
+ {
+ // no-op
+ }
+
+ private function createSocket()
+ {
+ set_error_handler([self::class, 'nullErrorHandler']);
+ try {
+ return stream_socket_client($this->host, $errno, $errstr, 3, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
+ } finally {
+ restore_error_handler();
+ }
+ }
+}
diff --git a/vendor/symfony/var-dumper/Server/DumpServer.php b/vendor/symfony/var-dumper/Server/DumpServer.php
new file mode 100755
index 0000000..ad920bd
--- /dev/null
+++ b/vendor/symfony/var-dumper/Server/DumpServer.php
@@ -0,0 +1,107 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Server;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * A server collecting Data clones sent by a ServerDumper.
+ *
+ * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
+ *
+ * @final
+ */
+class DumpServer
+{
+ private $host;
+ private $socket;
+ private $logger;
+
+ public function __construct(string $host, LoggerInterface $logger = null)
+ {
+ if (false === strpos($host, '://')) {
+ $host = 'tcp://'.$host;
+ }
+
+ $this->host = $host;
+ $this->logger = $logger;
+ }
+
+ public function start(): void
+ {
+ if (!$this->socket = stream_socket_server($this->host, $errno, $errstr)) {
+ throw new \RuntimeException(sprintf('Server start failed on "%s": %s %s.', $this->host, $errstr, $errno));
+ }
+ }
+
+ public function listen(callable $callback): void
+ {
+ if (null === $this->socket) {
+ $this->start();
+ }
+
+ foreach ($this->getMessages() as $clientId => $message) {
+ $payload = @unserialize(base64_decode($message), ['allowed_classes' => [Data::class, Stub::class]]);
+
+ // Impossible to decode the message, give up.
+ if (false === $payload) {
+ if ($this->logger) {
+ $this->logger->warning('Unable to decode a message from {clientId} client.', ['clientId' => $clientId]);
+ }
+
+ continue;
+ }
+
+ if (!\is_array($payload) || \count($payload) < 2 || !$payload[0] instanceof Data || !\is_array($payload[1])) {
+ if ($this->logger) {
+ $this->logger->warning('Invalid payload from {clientId} client. Expected an array of two elements (Data $data, array $context)', ['clientId' => $clientId]);
+ }
+
+ continue;
+ }
+
+ list($data, $context) = $payload;
+
+ $callback($data, $context, $clientId);
+ }
+ }
+
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ private function getMessages(): iterable
+ {
+ $sockets = [(int) $this->socket => $this->socket];
+ $write = [];
+
+ while (true) {
+ $read = $sockets;
+ stream_select($read, $write, $write, null);
+
+ foreach ($read as $stream) {
+ if ($this->socket === $stream) {
+ $stream = stream_socket_accept($this->socket);
+ $sockets[(int) $stream] = $stream;
+ } elseif (feof($stream)) {
+ unset($sockets[(int) $stream]);
+ fclose($stream);
+ } else {
+ yield (int) $stream => fgets($stream);
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php
new file mode 100755
index 0000000..11c9b92
--- /dev/null
+++ b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php
@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Test;
+
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+trait VarDumperTestTrait
+{
+ public function assertDumpEquals($expected, $data, $filter = 0, $message = '')
+ {
+ $this->assertSame($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message);
+ }
+
+ public function assertDumpMatchesFormat($expected, $data, $filter = 0, $message = '')
+ {
+ $this->assertStringMatchesFormat($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message);
+ }
+
+ protected function getDump($data, $key = null, $filter = 0)
+ {
+ $flags = getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0;
+ $flags |= getenv('DUMP_STRING_LENGTH') ? CliDumper::DUMP_STRING_LENGTH : 0;
+ $flags |= getenv('DUMP_COMMA_SEPARATOR') ? CliDumper::DUMP_COMMA_SEPARATOR : 0;
+
+ $cloner = new VarCloner();
+ $cloner->setMaxItems(-1);
+ $dumper = new CliDumper(null, null, $flags);
+ $dumper->setColors(false);
+ $data = $cloner->cloneVar($data, $filter)->withRefHandles(false);
+ if (null !== $key && null === $data = $data->seek($key)) {
+ return;
+ }
+
+ return rtrim($dumper->dump($data, true));
+ }
+
+ private function prepareExpectation($expected, $filter)
+ {
+ if (!\is_string($expected)) {
+ $expected = $this->getDump($expected, null, $filter);
+ }
+
+ return rtrim($expected);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/CasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/CasterTest.php
new file mode 100755
index 0000000..2c2189c
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/CasterTest.php
@@ -0,0 +1,178 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Caster\Caster;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class CasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ private $referenceArray = [
+ 'null' => null,
+ 'empty' => false,
+ 'public' => 'pub',
+ "\0~\0virtual" => 'virt',
+ "\0+\0dynamic" => 'dyn',
+ "\0*\0protected" => 'prot',
+ "\0Foo\0private" => 'priv',
+ ];
+
+ /**
+ * @dataProvider provideFilter
+ */
+ public function testFilter($filter, $expectedDiff, $listedProperties = null)
+ {
+ if (null === $listedProperties) {
+ $filteredArray = Caster::filter($this->referenceArray, $filter);
+ } else {
+ $filteredArray = Caster::filter($this->referenceArray, $filter, $listedProperties);
+ }
+
+ $this->assertSame($expectedDiff, array_diff_assoc($this->referenceArray, $filteredArray));
+ }
+
+ public function provideFilter()
+ {
+ return [
+ [
+ 0,
+ [],
+ ],
+ [
+ Caster::EXCLUDE_PUBLIC,
+ [
+ 'null' => null,
+ 'empty' => false,
+ 'public' => 'pub',
+ ],
+ ],
+ [
+ Caster::EXCLUDE_NULL,
+ [
+ 'null' => null,
+ ],
+ ],
+ [
+ Caster::EXCLUDE_EMPTY,
+ [
+ 'null' => null,
+ 'empty' => false,
+ ],
+ ],
+ [
+ Caster::EXCLUDE_VIRTUAL,
+ [
+ "\0~\0virtual" => 'virt',
+ ],
+ ],
+ [
+ Caster::EXCLUDE_DYNAMIC,
+ [
+ "\0+\0dynamic" => 'dyn',
+ ],
+ ],
+ [
+ Caster::EXCLUDE_PROTECTED,
+ [
+ "\0*\0protected" => 'prot',
+ ],
+ ],
+ [
+ Caster::EXCLUDE_PRIVATE,
+ [
+ "\0Foo\0private" => 'priv',
+ ],
+ ],
+ [
+ Caster::EXCLUDE_VERBOSE,
+ [
+ 'public' => 'pub',
+ "\0*\0protected" => 'prot',
+ ],
+ ['public', "\0*\0protected"],
+ ],
+ [
+ Caster::EXCLUDE_NOT_IMPORTANT,
+ [
+ 'null' => null,
+ 'empty' => false,
+ "\0~\0virtual" => 'virt',
+ "\0+\0dynamic" => 'dyn',
+ "\0Foo\0private" => 'priv',
+ ],
+ ['public', "\0*\0protected"],
+ ],
+ [
+ Caster::EXCLUDE_VIRTUAL | Caster::EXCLUDE_DYNAMIC,
+ [
+ "\0~\0virtual" => 'virt',
+ "\0+\0dynamic" => 'dyn',
+ ],
+ ],
+ [
+ Caster::EXCLUDE_NOT_IMPORTANT | Caster::EXCLUDE_VERBOSE,
+ $this->referenceArray,
+ ['public', "\0*\0protected"],
+ ],
+ [
+ Caster::EXCLUDE_NOT_IMPORTANT | Caster::EXCLUDE_EMPTY,
+ [
+ 'null' => null,
+ 'empty' => false,
+ "\0~\0virtual" => 'virt',
+ "\0+\0dynamic" => 'dyn',
+ "\0*\0protected" => 'prot',
+ "\0Foo\0private" => 'priv',
+ ],
+ ['public', 'empty'],
+ ],
+ [
+ Caster::EXCLUDE_VERBOSE | Caster::EXCLUDE_EMPTY | Caster::EXCLUDE_STRICT,
+ [
+ 'empty' => false,
+ ],
+ ['public', 'empty'],
+ ],
+ ];
+ }
+
+ public function testAnonymousClass()
+ {
+ $c = eval('return new class extends stdClass { private $foo = "foo"; };');
+
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+stdClass@anonymous {
+ -foo: "foo"
+}
+EOTXT
+ , $c
+ );
+
+ $c = eval('return new class { private $foo = "foo"; };');
+
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+@anonymous {
+ -foo: "foo"
+}
+EOTXT
+ , $c
+ );
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/DateCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/DateCasterTest.php
new file mode 100755
index 0000000..dae13ef
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/DateCasterTest.php
@@ -0,0 +1,390 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Caster\Caster;
+use Symfony\Component\VarDumper\Caster\DateCaster;
+use Symfony\Component\VarDumper\Cloner\Stub;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+
+/**
+ * @author Dany Maillard <danymaillard93b@gmail.com>
+ */
+class DateCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ /**
+ * @dataProvider provideDateTimes
+ */
+ public function testDumpDateTime($time, $timezone, $xDate, $xTimestamp)
+ {
+ $date = new \DateTime($time, new \DateTimeZone($timezone));
+
+ $xDump = <<<EODUMP
+DateTime @$xTimestamp {
+ date: $xDate
+}
+EODUMP;
+
+ $this->assertDumpEquals($xDump, $date);
+ }
+
+ /**
+ * @dataProvider provideDateTimes
+ */
+ public function testCastDateTime($time, $timezone, $xDate, $xTimestamp, $xInfos)
+ {
+ $stub = new Stub();
+ $date = new \DateTime($time, new \DateTimeZone($timezone));
+ $cast = DateCaster::castDateTime($date, ['foo' => 'bar'], $stub, false, 0);
+
+ $xDump = <<<EODUMP
+array:1 [
+ "\\x00~\\x00date" => $xDate
+]
+EODUMP;
+
+ $this->assertDumpEquals($xDump, $cast);
+
+ $xDump = <<<EODUMP
+Symfony\Component\VarDumper\Caster\ConstStub {
+ +type: 1
+ +class: "$xDate"
+ +value: "%A$xInfos%A"
+ +cut: 0
+ +handle: 0
+ +refCount: 0
+ +position: 0
+ +attr: []
+}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xDump, $cast["\0~\0date"]);
+ }
+
+ public function provideDateTimes()
+ {
+ return [
+ ['2017-04-30 00:00:00.000000', 'Europe/Zurich', '2017-04-30 00:00:00.0 Europe/Zurich (+02:00)', 1493503200, 'Sunday, April 30, 2017%Afrom now%ADST On'],
+ ['2017-12-31 00:00:00.000000', 'Europe/Zurich', '2017-12-31 00:00:00.0 Europe/Zurich (+01:00)', 1514674800, 'Sunday, December 31, 2017%Afrom now%ADST Off'],
+ ['2017-04-30 00:00:00.000000', '+02:00', '2017-04-30 00:00:00.0 +02:00', 1493503200, 'Sunday, April 30, 2017%Afrom now'],
+
+ ['2017-04-30 00:00:00.100000', '+00:00', '2017-04-30 00:00:00.100 +00:00', 1493510400, 'Sunday, April 30, 2017%Afrom now'],
+ ['2017-04-30 00:00:00.120000', '+00:00', '2017-04-30 00:00:00.120 +00:00', 1493510400, 'Sunday, April 30, 2017%Afrom now'],
+ ['2017-04-30 00:00:00.123000', '+00:00', '2017-04-30 00:00:00.123 +00:00', 1493510400, 'Sunday, April 30, 2017%Afrom now'],
+ ['2017-04-30 00:00:00.123400', '+00:00', '2017-04-30 00:00:00.123400 +00:00', 1493510400, 'Sunday, April 30, 2017%Afrom now'],
+ ['2017-04-30 00:00:00.123450', '+00:00', '2017-04-30 00:00:00.123450 +00:00', 1493510400, 'Sunday, April 30, 2017%Afrom now'],
+ ['2017-04-30 00:00:00.123456', '+00:00', '2017-04-30 00:00:00.123456 +00:00', 1493510400, 'Sunday, April 30, 2017%Afrom now'],
+ ];
+ }
+
+ /**
+ * @dataProvider provideIntervals
+ */
+ public function testDumpInterval($intervalSpec, $ms, $invert, $expected)
+ {
+ if ($ms && \PHP_VERSION_ID >= 70200 && version_compare(PHP_VERSION, '7.2.0rc3', '<=')) {
+ $this->markTestSkipped('Skipped on 7.2 before rc4 because of php bug #75354.');
+ }
+
+ $interval = $this->createInterval($intervalSpec, $ms, $invert);
+
+ $xDump = <<<EODUMP
+DateInterval {
+ interval: $expected
+%A}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xDump, $interval);
+ }
+
+ /**
+ * @dataProvider provideIntervals
+ */
+ public function testDumpIntervalExcludingVerbosity($intervalSpec, $ms, $invert, $expected)
+ {
+ if ($ms && \PHP_VERSION_ID >= 70200 && version_compare(PHP_VERSION, '7.2.0rc3', '<=')) {
+ $this->markTestSkipped('Skipped on 7.2 before rc4 because of php bug #75354.');
+ }
+
+ $interval = $this->createInterval($intervalSpec, $ms, $invert);
+
+ $xDump = <<<EODUMP
+DateInterval {
+ interval: $expected
+}
+EODUMP;
+
+ $this->assertDumpEquals($xDump, $interval, Caster::EXCLUDE_VERBOSE);
+ }
+
+ /**
+ * @dataProvider provideIntervals
+ */
+ public function testCastInterval($intervalSpec, $ms, $invert, $xInterval, $xSeconds)
+ {
+ if ($ms && \PHP_VERSION_ID >= 70200 && version_compare(PHP_VERSION, '7.2.0rc3', '<=')) {
+ $this->markTestSkipped('Skipped on 7.2 before rc4 because of php bug #75354.');
+ }
+
+ $interval = $this->createInterval($intervalSpec, $ms, $invert);
+ $stub = new Stub();
+
+ $cast = DateCaster::castInterval($interval, ['foo' => 'bar'], $stub, false, Caster::EXCLUDE_VERBOSE);
+
+ $xDump = <<<EODUMP
+array:1 [
+ "\\x00~\\x00interval" => $xInterval
+]
+EODUMP;
+
+ $this->assertDumpEquals($xDump, $cast);
+
+ if (null === $xSeconds) {
+ return;
+ }
+
+ $xDump = <<<EODUMP
+Symfony\Component\VarDumper\Caster\ConstStub {
+ +type: 1
+ +class: "$xInterval"
+ +value: "$xSeconds"
+ +cut: 0
+ +handle: 0
+ +refCount: 0
+ +position: 0
+ +attr: []
+}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xDump, $cast["\0~\0interval"]);
+ }
+
+ public function provideIntervals()
+ {
+ return [
+ ['PT0S', 0, 0, '0s', '0s'],
+ ['PT0S', 0.1, 0, '+ 00:00:00.100', '%is'],
+ ['PT1S', 0, 0, '+ 00:00:01.0', '%is'],
+ ['PT2M', 0, 0, '+ 00:02:00.0', '%is'],
+ ['PT3H', 0, 0, '+ 03:00:00.0', '%ss'],
+ ['P4D', 0, 0, '+ 4d', '%ss'],
+ ['P5M', 0, 0, '+ 5m', null],
+ ['P6Y', 0, 0, '+ 6y', null],
+ ['P1Y2M3DT4H5M6S', 0, 0, '+ 1y 2m 3d 04:05:06.0', null],
+ ['PT1M60S', 0, 0, '+ 00:02:00.0', null],
+ ['PT1H60M', 0, 0, '+ 02:00:00.0', null],
+ ['P1DT24H', 0, 0, '+ 2d', null],
+ ['P1M32D', 0, 0, '+ 1m 32d', null],
+
+ ['PT0S', 0, 1, '0s', '0s'],
+ ['PT0S', 0.1, 1, '- 00:00:00.100', '%is'],
+ ['PT1S', 0, 1, '- 00:00:01.0', '%is'],
+ ['PT2M', 0, 1, '- 00:02:00.0', '%is'],
+ ['PT3H', 0, 1, '- 03:00:00.0', '%ss'],
+ ['P4D', 0, 1, '- 4d', '%ss'],
+ ['P5M', 0, 1, '- 5m', null],
+ ['P6Y', 0, 1, '- 6y', null],
+ ['P1Y2M3DT4H5M6S', 0, 1, '- 1y 2m 3d 04:05:06.0', null],
+ ['PT1M60S', 0, 1, '- 00:02:00.0', null],
+ ['PT1H60M', 0, 1, '- 02:00:00.0', null],
+ ['P1DT24H', 0, 1, '- 2d', null],
+ ['P1M32D', 0, 1, '- 1m 32d', null],
+ ];
+ }
+
+ /**
+ * @dataProvider provideTimeZones
+ */
+ public function testDumpTimeZone($timezone, $expected)
+ {
+ $timezone = new \DateTimeZone($timezone);
+
+ $xDump = <<<EODUMP
+DateTimeZone {
+ timezone: $expected
+%A}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xDump, $timezone);
+ }
+
+ /**
+ * @dataProvider provideTimeZones
+ */
+ public function testDumpTimeZoneExcludingVerbosity($timezone, $expected)
+ {
+ $timezone = new \DateTimeZone($timezone);
+
+ $xDump = <<<EODUMP
+DateTimeZone {
+ timezone: $expected
+}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xDump, $timezone, Caster::EXCLUDE_VERBOSE);
+ }
+
+ /**
+ * @dataProvider provideTimeZones
+ */
+ public function testCastTimeZone($timezone, $xTimezone, $xRegion)
+ {
+ $timezone = new \DateTimeZone($timezone);
+ $stub = new Stub();
+
+ $cast = DateCaster::castTimeZone($timezone, ['foo' => 'bar'], $stub, false, Caster::EXCLUDE_VERBOSE);
+
+ $xDump = <<<EODUMP
+array:1 [
+ "\\x00~\\x00timezone" => $xTimezone
+]
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xDump, $cast);
+
+ $xDump = <<<EODUMP
+Symfony\Component\VarDumper\Caster\ConstStub {
+ +type: 1
+ +class: "$xTimezone"
+ +value: "$xRegion"
+ +cut: 0
+ +handle: 0
+ +refCount: 0
+ +position: 0
+ +attr: []
+}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xDump, $cast["\0~\0timezone"]);
+ }
+
+ public function provideTimeZones()
+ {
+ $xRegion = \extension_loaded('intl') ? '%s' : '';
+
+ return [
+ // type 1 (UTC offset)
+ ['-12:00', '-12:00', ''],
+ ['+00:00', '+00:00', ''],
+ ['+14:00', '+14:00', ''],
+
+ // type 2 (timezone abbreviation)
+ ['GMT', '+00:00', ''],
+ ['a', '+01:00', ''],
+ ['b', '+02:00', ''],
+ ['z', '+00:00', ''],
+
+ // type 3 (timezone identifier)
+ ['Africa/Tunis', 'Africa/Tunis (%s:00)', $xRegion],
+ ['America/Panama', 'America/Panama (%s:00)', $xRegion],
+ ['Asia/Jerusalem', 'Asia/Jerusalem (%s:00)', $xRegion],
+ ['Atlantic/Canary', 'Atlantic/Canary (%s:00)', $xRegion],
+ ['Australia/Perth', 'Australia/Perth (%s:00)', $xRegion],
+ ['Europe/Zurich', 'Europe/Zurich (%s:00)', $xRegion],
+ ['Pacific/Tahiti', 'Pacific/Tahiti (%s:00)', $xRegion],
+ ];
+ }
+
+ /**
+ * @dataProvider providePeriods
+ */
+ public function testDumpPeriod($start, $interval, $end, $options, $expected)
+ {
+ $p = new \DatePeriod(new \DateTime($start), new \DateInterval($interval), \is_int($end) ? $end : new \DateTime($end), $options);
+
+ $xDump = <<<EODUMP
+DatePeriod {
+ period: $expected
+%A}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xDump, $p);
+ }
+
+ /**
+ * @dataProvider providePeriods
+ */
+ public function testCastPeriod($start, $interval, $end, $options, $xPeriod, $xDates)
+ {
+ $p = new \DatePeriod(new \DateTime($start), new \DateInterval($interval), \is_int($end) ? $end : new \DateTime($end), $options);
+ $stub = new Stub();
+
+ $cast = DateCaster::castPeriod($p, [], $stub, false, 0);
+
+ $xDump = <<<EODUMP
+array:1 [
+ "\\x00~\\x00period" => $xPeriod
+]
+EODUMP;
+
+ $this->assertDumpEquals($xDump, $cast);
+
+ $xDump = <<<EODUMP
+Symfony\Component\VarDumper\Caster\ConstStub {
+ +type: 1
+ +class: "$xPeriod"
+ +value: "%A$xDates%A"
+ +cut: 0
+ +handle: 0
+ +refCount: 0
+ +position: 0
+ +attr: []
+}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xDump, $cast["\0~\0period"]);
+ }
+
+ public function providePeriods()
+ {
+ $periods = [
+ ['2017-01-01', 'P1D', '2017-01-03', 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) to 2017-01-03 00:00:00.0', '1) 2017-01-01%a2) 2017-01-02'],
+ ['2017-01-01', 'P1D', 1, 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) recurring 2 time/s', '1) 2017-01-01%a2) 2017-01-02'],
+
+ ['2017-01-01', 'P1D', '2017-01-04', 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) to 2017-01-04 00:00:00.0', '1) 2017-01-01%a2) 2017-01-02%a3) 2017-01-03'],
+ ['2017-01-01', 'P1D', 2, 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) recurring 3 time/s', '1) 2017-01-01%a2) 2017-01-02%a3) 2017-01-03'],
+
+ ['2017-01-01', 'P1D', '2017-01-05', 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) to 2017-01-05 00:00:00.0', '1) 2017-01-01%a2) 2017-01-02%a1 more'],
+ ['2017-01-01', 'P1D', 3, 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) recurring 4 time/s', '1) 2017-01-01%a2) 2017-01-02%a3) 2017-01-03%a1 more'],
+
+ ['2017-01-01', 'P1D', '2017-01-21', 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) to 2017-01-21 00:00:00.0', '1) 2017-01-01%a17 more'],
+ ['2017-01-01', 'P1D', 19, 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) recurring 20 time/s', '1) 2017-01-01%a17 more'],
+
+ ['2017-01-01 01:00:00', 'P1D', '2017-01-03 01:00:00', 0, 'every + 1d, from 2017-01-01 01:00:00.0 (included) to 2017-01-03 01:00:00.0', '1) 2017-01-01 01:00:00.0%a2) 2017-01-02 01:00:00.0'],
+ ['2017-01-01 01:00:00', 'P1D', 1, 0, 'every + 1d, from 2017-01-01 01:00:00.0 (included) recurring 2 time/s', '1) 2017-01-01 01:00:00.0%a2) 2017-01-02 01:00:00.0'],
+
+ ['2017-01-01', 'P1DT1H', '2017-01-03', 0, 'every + 1d 01:00:00.0, from 2017-01-01 00:00:00.0 (included) to 2017-01-03 00:00:00.0', '1) 2017-01-01 00:00:00.0%a2) 2017-01-02 01:00:00.0'],
+ ['2017-01-01', 'P1DT1H', 1, 0, 'every + 1d 01:00:00.0, from 2017-01-01 00:00:00.0 (included) recurring 2 time/s', '1) 2017-01-01 00:00:00.0%a2) 2017-01-02 01:00:00.0'],
+
+ ['2017-01-01', 'P1D', '2017-01-04', \DatePeriod::EXCLUDE_START_DATE, 'every + 1d, from 2017-01-01 00:00:00.0 (excluded) to 2017-01-04 00:00:00.0', '1) 2017-01-02%a2) 2017-01-03'],
+ ['2017-01-01', 'P1D', 2, \DatePeriod::EXCLUDE_START_DATE, 'every + 1d, from 2017-01-01 00:00:00.0 (excluded) recurring 2 time/s', '1) 2017-01-02%a2) 2017-01-03'],
+ ];
+
+ if (\PHP_VERSION_ID < 70107) {
+ array_walk($periods, function (&$i) { $i[5] = ''; });
+ }
+
+ return $periods;
+ }
+
+ private function createInterval($intervalSpec, $ms, $invert)
+ {
+ $interval = new \DateInterval($intervalSpec);
+ $interval->f = $ms;
+ $interval->invert = $invert;
+
+ return $interval;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/ExceptionCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/ExceptionCasterTest.php
new file mode 100755
index 0000000..0b9c2ed
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/ExceptionCasterTest.php
@@ -0,0 +1,247 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Caster\Caster;
+use Symfony\Component\VarDumper\Caster\ExceptionCaster;
+use Symfony\Component\VarDumper\Caster\FrameStub;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+
+class ExceptionCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ private function getTestException($msg, &$ref = null)
+ {
+ return new \Exception(''.$msg);
+ }
+
+ protected function tearDown()
+ {
+ ExceptionCaster::$srcContext = 1;
+ ExceptionCaster::$traceArgs = true;
+ }
+
+ public function testDefaultSettings()
+ {
+ $ref = ['foo'];
+ $e = $this->getTestException('foo', $ref);
+
+ $expectedDump = <<<'EODUMP'
+Exception {
+ #message: "foo"
+ #code: 0
+ #file: "%sExceptionCasterTest.php"
+ #line: 28
+ trace: {
+ %s%eTests%eCaster%eExceptionCasterTest.php:28 {
+ › {
+ › return new \Exception(''.$msg);
+ › }
+ }
+ %s%eTests%eCaster%eExceptionCasterTest.php:40 { …}
+ Symfony\Component\VarDumper\Tests\Caster\ExceptionCasterTest->testDefaultSettings() {}
+%A
+EODUMP;
+
+ $this->assertDumpMatchesFormat($expectedDump, $e);
+ $this->assertSame(['foo'], $ref);
+ }
+
+ public function testSeek()
+ {
+ $e = $this->getTestException(2);
+
+ $expectedDump = <<<'EODUMP'
+{
+ %s%eTests%eCaster%eExceptionCasterTest.php:28 {
+ › {
+ › return new \Exception(''.$msg);
+ › }
+ }
+ %s%eTests%eCaster%eExceptionCasterTest.php:65 { …}
+ Symfony\Component\VarDumper\Tests\Caster\ExceptionCasterTest->testSeek() {}
+%A
+EODUMP;
+
+ $this->assertStringMatchesFormat($expectedDump, $this->getDump($e, 'trace'));
+ }
+
+ public function testNoArgs()
+ {
+ $e = $this->getTestException(1);
+ ExceptionCaster::$traceArgs = false;
+
+ $expectedDump = <<<'EODUMP'
+Exception {
+ #message: "1"
+ #code: 0
+ #file: "%sExceptionCasterTest.php"
+ #line: 28
+ trace: {
+ %sExceptionCasterTest.php:28 {
+ › {
+ › return new \Exception(''.$msg);
+ › }
+ }
+ %s%eTests%eCaster%eExceptionCasterTest.php:84 { …}
+ Symfony\Component\VarDumper\Tests\Caster\ExceptionCasterTest->testNoArgs() {}
+%A
+EODUMP;
+
+ $this->assertDumpMatchesFormat($expectedDump, $e);
+ }
+
+ public function testNoSrcContext()
+ {
+ $e = $this->getTestException(1);
+ ExceptionCaster::$srcContext = -1;
+
+ $expectedDump = <<<'EODUMP'
+Exception {
+ #message: "1"
+ #code: 0
+ #file: "%sExceptionCasterTest.php"
+ #line: 28
+ trace: {
+ %s%eTests%eCaster%eExceptionCasterTest.php:28
+ %s%eTests%eCaster%eExceptionCasterTest.php:%d
+%A
+EODUMP;
+
+ $this->assertDumpMatchesFormat($expectedDump, $e);
+ }
+
+ public function testHtmlDump()
+ {
+ if (ini_get('xdebug.file_link_format') || get_cfg_var('xdebug.file_link_format')) {
+ $this->markTestSkipped('A custom file_link_format is defined.');
+ }
+
+ $e = $this->getTestException(1);
+ ExceptionCaster::$srcContext = -1;
+
+ $cloner = new VarCloner();
+ $cloner->setMaxItems(1);
+ $dumper = new HtmlDumper();
+ $dumper->setDumpHeader('<foo></foo>');
+ $dumper->setDumpBoundaries('<bar>', '</bar>');
+ $dump = $dumper->dump($cloner->cloneVar($e)->withRefHandles(false), true);
+
+ $expectedDump = <<<'EODUMP'
+<foo></foo><bar><span class=sf-dump-note>Exception</span> {<samp>
+ #<span class=sf-dump-protected title="Protected property">message</span>: "<span class=sf-dump-str>1</span>"
+ #<span class=sf-dump-protected title="Protected property">code</span>: <span class=sf-dump-num>0</span>
+ #<span class=sf-dump-protected title="Protected property">file</span>: "<span class=sf-dump-str title="%sExceptionCasterTest.php
+%d characters"><span class="sf-dump-ellipsis sf-dump-ellipsis-path">%s%eVarDumper</span><span class=sf-dump-ellipsis>%e</span>Tests%eCaster%eExceptionCasterTest.php</span>"
+ #<span class=sf-dump-protected title="Protected property">line</span>: <span class=sf-dump-num>28</span>
+ <span class=sf-dump-meta>trace</span>: {<samp>
+ <span class=sf-dump-meta title="%sExceptionCasterTest.php
+Stack level %d."><span class="sf-dump-ellipsis sf-dump-ellipsis-path">%s%eVarDumper</span><span class=sf-dump-ellipsis>%e</span>Tests%eCaster%eExceptionCasterTest.php</span>:<span class=sf-dump-num>28</span>
+ …%d
+ </samp>}
+</samp>}
+</bar>
+EODUMP;
+
+ $this->assertStringMatchesFormat($expectedDump, $dump);
+ }
+
+ /**
+ * @requires function Twig\Template::getSourceContext
+ */
+ public function testFrameWithTwig()
+ {
+ require_once \dirname(__DIR__).'/Fixtures/Twig.php';
+
+ $f = [
+ new FrameStub([
+ 'file' => \dirname(__DIR__).'/Fixtures/Twig.php',
+ 'line' => 20,
+ 'class' => '__TwigTemplate_VarDumperFixture_u75a09',
+ ]),
+ new FrameStub([
+ 'file' => \dirname(__DIR__).'/Fixtures/Twig.php',
+ 'line' => 21,
+ 'class' => '__TwigTemplate_VarDumperFixture_u75a09',
+ 'object' => new \__TwigTemplate_VarDumperFixture_u75a09(null, __FILE__),
+ ]),
+ ];
+
+ $expectedDump = <<<'EODUMP'
+array:2 [
+ 0 => {
+ class: "__TwigTemplate_VarDumperFixture_u75a09"
+ src: {
+ %sTwig.php:1 {
+ ›
+ › foo bar
+ › twig source
+ }
+ }
+ }
+ 1 => {
+ class: "__TwigTemplate_VarDumperFixture_u75a09"
+ object: __TwigTemplate_VarDumperFixture_u75a09 {
+ %A
+ }
+ src: {
+ %sExceptionCasterTest.php:2 {
+ › foo bar
+ › twig source
+ ›
+ }
+ }
+ }
+]
+
+EODUMP;
+
+ $this->assertDumpMatchesFormat($expectedDump, $f);
+ }
+
+ public function testExcludeVerbosity()
+ {
+ $e = $this->getTestException('foo');
+
+ $expectedDump = <<<'EODUMP'
+Exception {
+ #message: "foo"
+ #code: 0
+ #file: "%sExceptionCasterTest.php"
+ #line: 28
+}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($expectedDump, $e, Caster::EXCLUDE_VERBOSE);
+ }
+
+ public function testAnonymous()
+ {
+ $e = new \Exception(sprintf('Boo "%s" ba.', \get_class(new class('Foo') extends \Exception {
+ })));
+
+ $expectedDump = <<<'EODUMP'
+Exception {
+ #message: "Boo "Exception@anonymous" ba."
+ #code: 0
+ #file: "%sExceptionCasterTest.php"
+ #line: %d
+}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($expectedDump, $e, Caster::EXCLUDE_VERBOSE);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/GmpCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/GmpCasterTest.php
new file mode 100755
index 0000000..eb758e8
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/GmpCasterTest.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Caster\GmpCaster;
+use Symfony\Component\VarDumper\Cloner\Stub;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+
+class GmpCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ /**
+ * @requires extension gmp
+ */
+ public function testCastGmp()
+ {
+ $gmpString = gmp_init('1234');
+ $gmpOctal = gmp_init(010);
+ $gmp = gmp_init('01101');
+ $gmpDump = <<<EODUMP
+array:1 [
+ "\\x00~\\x00value" => %s
+]
+EODUMP;
+ $this->assertDumpEquals(sprintf($gmpDump, $gmpString), GmpCaster::castGmp($gmpString, [], new Stub(), false, 0));
+ $this->assertDumpEquals(sprintf($gmpDump, $gmpOctal), GmpCaster::castGmp($gmpOctal, [], new Stub(), false, 0));
+ $this->assertDumpEquals(sprintf($gmpDump, $gmp), GmpCaster::castGmp($gmp, [], new Stub(), false, 0));
+
+ $dump = <<<EODUMP
+GMP {
+ value: 577
+}
+EODUMP;
+
+ $this->assertDumpEquals($dump, $gmp);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/IntlCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/IntlCasterTest.php
new file mode 100755
index 0000000..0bff5bf
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/IntlCasterTest.php
@@ -0,0 +1,297 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+
+/**
+ * @requires extension intl
+ */
+class IntlCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ public function testMessageFormatter()
+ {
+ $var = new \MessageFormatter('en', 'Hello {name}');
+
+ $expected = <<<EOTXT
+MessageFormatter {
+ locale: "en"
+ pattern: "Hello {name}"
+}
+EOTXT;
+ $this->assertDumpEquals($expected, $var);
+ }
+
+ public function testCastNumberFormatter()
+ {
+ $var = new \NumberFormatter('en', \NumberFormatter::DECIMAL);
+
+ $expectedLocale = $var->getLocale();
+ $expectedPattern = $var->getPattern();
+
+ $expectedAttribute1 = $var->getAttribute(\NumberFormatter::PARSE_INT_ONLY);
+ $expectedAttribute2 = $var->getAttribute(\NumberFormatter::GROUPING_USED);
+ $expectedAttribute3 = $var->getAttribute(\NumberFormatter::DECIMAL_ALWAYS_SHOWN);
+ $expectedAttribute4 = $var->getAttribute(\NumberFormatter::MAX_INTEGER_DIGITS);
+ $expectedAttribute5 = $var->getAttribute(\NumberFormatter::MIN_INTEGER_DIGITS);
+ $expectedAttribute6 = $var->getAttribute(\NumberFormatter::INTEGER_DIGITS);
+ $expectedAttribute7 = $var->getAttribute(\NumberFormatter::MAX_FRACTION_DIGITS);
+ $expectedAttribute8 = $var->getAttribute(\NumberFormatter::MIN_FRACTION_DIGITS);
+ $expectedAttribute9 = $var->getAttribute(\NumberFormatter::FRACTION_DIGITS);
+ $expectedAttribute10 = $var->getAttribute(\NumberFormatter::MULTIPLIER);
+ $expectedAttribute11 = $var->getAttribute(\NumberFormatter::GROUPING_SIZE);
+ $expectedAttribute12 = $var->getAttribute(\NumberFormatter::ROUNDING_MODE);
+ $expectedAttribute13 = number_format($var->getAttribute(\NumberFormatter::ROUNDING_INCREMENT), 1);
+ $expectedAttribute14 = $this->getDump($var->getAttribute(\NumberFormatter::FORMAT_WIDTH));
+ $expectedAttribute15 = $var->getAttribute(\NumberFormatter::PADDING_POSITION);
+ $expectedAttribute16 = $var->getAttribute(\NumberFormatter::SECONDARY_GROUPING_SIZE);
+ $expectedAttribute17 = $var->getAttribute(\NumberFormatter::SIGNIFICANT_DIGITS_USED);
+ $expectedAttribute18 = $this->getDump($var->getAttribute(\NumberFormatter::MIN_SIGNIFICANT_DIGITS));
+ $expectedAttribute19 = $this->getDump($var->getAttribute(\NumberFormatter::MAX_SIGNIFICANT_DIGITS));
+ $expectedAttribute20 = $var->getAttribute(\NumberFormatter::LENIENT_PARSE);
+
+ $expectedTextAttribute1 = $var->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX);
+ $expectedTextAttribute2 = $var->getTextAttribute(\NumberFormatter::POSITIVE_SUFFIX);
+ $expectedTextAttribute3 = $var->getTextAttribute(\NumberFormatter::NEGATIVE_PREFIX);
+ $expectedTextAttribute4 = $var->getTextAttribute(\NumberFormatter::NEGATIVE_SUFFIX);
+ $expectedTextAttribute5 = $var->getTextAttribute(\NumberFormatter::PADDING_CHARACTER);
+ $expectedTextAttribute6 = $var->getTextAttribute(\NumberFormatter::CURRENCY_CODE);
+ $expectedTextAttribute7 = $var->getTextAttribute(\NumberFormatter::DEFAULT_RULESET) ? 'true' : 'false';
+ $expectedTextAttribute8 = $var->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS) ? 'true' : 'false';
+
+ $expectedSymbol1 = $var->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
+ $expectedSymbol2 = $var->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL);
+ $expectedSymbol3 = $var->getSymbol(\NumberFormatter::PATTERN_SEPARATOR_SYMBOL);
+ $expectedSymbol4 = $var->getSymbol(\NumberFormatter::PERCENT_SYMBOL);
+ $expectedSymbol5 = $var->getSymbol(\NumberFormatter::ZERO_DIGIT_SYMBOL);
+ $expectedSymbol6 = $var->getSymbol(\NumberFormatter::DIGIT_SYMBOL);
+ $expectedSymbol7 = $var->getSymbol(\NumberFormatter::MINUS_SIGN_SYMBOL);
+ $expectedSymbol8 = $var->getSymbol(\NumberFormatter::PLUS_SIGN_SYMBOL);
+ $expectedSymbol9 = $var->getSymbol(\NumberFormatter::CURRENCY_SYMBOL);
+ $expectedSymbol10 = $var->getSymbol(\NumberFormatter::INTL_CURRENCY_SYMBOL);
+ $expectedSymbol11 = $var->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL);
+ $expectedSymbol12 = $var->getSymbol(\NumberFormatter::EXPONENTIAL_SYMBOL);
+ $expectedSymbol13 = $var->getSymbol(\NumberFormatter::PERMILL_SYMBOL);
+ $expectedSymbol14 = $var->getSymbol(\NumberFormatter::PAD_ESCAPE_SYMBOL);
+ $expectedSymbol15 = $var->getSymbol(\NumberFormatter::INFINITY_SYMBOL);
+ $expectedSymbol16 = $var->getSymbol(\NumberFormatter::NAN_SYMBOL);
+ $expectedSymbol17 = $var->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL);
+ $expectedSymbol18 = $var->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL);
+
+ $expected = <<<EOTXT
+NumberFormatter {
+ locale: "$expectedLocale"
+ pattern: "$expectedPattern"
+ attributes: {
+ PARSE_INT_ONLY: $expectedAttribute1
+ GROUPING_USED: $expectedAttribute2
+ DECIMAL_ALWAYS_SHOWN: $expectedAttribute3
+ MAX_INTEGER_DIGITS: $expectedAttribute4
+ MIN_INTEGER_DIGITS: $expectedAttribute5
+ INTEGER_DIGITS: $expectedAttribute6
+ MAX_FRACTION_DIGITS: $expectedAttribute7
+ MIN_FRACTION_DIGITS: $expectedAttribute8
+ FRACTION_DIGITS: $expectedAttribute9
+ MULTIPLIER: $expectedAttribute10
+ GROUPING_SIZE: $expectedAttribute11
+ ROUNDING_MODE: $expectedAttribute12
+ ROUNDING_INCREMENT: $expectedAttribute13
+ FORMAT_WIDTH: $expectedAttribute14
+ PADDING_POSITION: $expectedAttribute15
+ SECONDARY_GROUPING_SIZE: $expectedAttribute16
+ SIGNIFICANT_DIGITS_USED: $expectedAttribute17
+ MIN_SIGNIFICANT_DIGITS: $expectedAttribute18
+ MAX_SIGNIFICANT_DIGITS: $expectedAttribute19
+ LENIENT_PARSE: $expectedAttribute20
+ }
+ text_attributes: {
+ POSITIVE_PREFIX: "$expectedTextAttribute1"
+ POSITIVE_SUFFIX: "$expectedTextAttribute2"
+ NEGATIVE_PREFIX: "$expectedTextAttribute3"
+ NEGATIVE_SUFFIX: "$expectedTextAttribute4"
+ PADDING_CHARACTER: "$expectedTextAttribute5"
+ CURRENCY_CODE: "$expectedTextAttribute6"
+ DEFAULT_RULESET: $expectedTextAttribute7
+ PUBLIC_RULESETS: $expectedTextAttribute8
+ }
+ symbols: {
+ DECIMAL_SEPARATOR_SYMBOL: "$expectedSymbol1"
+ GROUPING_SEPARATOR_SYMBOL: "$expectedSymbol2"
+ PATTERN_SEPARATOR_SYMBOL: "$expectedSymbol3"
+ PERCENT_SYMBOL: "$expectedSymbol4"
+ ZERO_DIGIT_SYMBOL: "$expectedSymbol5"
+ DIGIT_SYMBOL: "$expectedSymbol6"
+ MINUS_SIGN_SYMBOL: "$expectedSymbol7"
+ PLUS_SIGN_SYMBOL: "$expectedSymbol8"
+ CURRENCY_SYMBOL: "$expectedSymbol9"
+ INTL_CURRENCY_SYMBOL: "$expectedSymbol10"
+ MONETARY_SEPARATOR_SYMBOL: "$expectedSymbol11"
+ EXPONENTIAL_SYMBOL: "$expectedSymbol12"
+ PERMILL_SYMBOL: "$expectedSymbol13"
+ PAD_ESCAPE_SYMBOL: "$expectedSymbol14"
+ INFINITY_SYMBOL: "$expectedSymbol15"
+ NAN_SYMBOL: "$expectedSymbol16"
+ SIGNIFICANT_DIGIT_SYMBOL: "$expectedSymbol17"
+ MONETARY_GROUPING_SEPARATOR_SYMBOL: "$expectedSymbol18"
+ }
+}
+EOTXT;
+ $this->assertDumpEquals($expected, $var);
+ }
+
+ public function testCastIntlTimeZoneWithDST()
+ {
+ $var = \IntlTimeZone::createTimeZone('America/Los_Angeles');
+
+ $expectedDisplayName = $var->getDisplayName();
+ $expectedDSTSavings = $var->getDSTSavings();
+ $expectedID = $var->getID();
+ $expectedRawOffset = $var->getRawOffset();
+
+ $expected = <<<EOTXT
+IntlTimeZone {
+ display_name: "$expectedDisplayName"
+ id: "$expectedID"
+ raw_offset: $expectedRawOffset
+ dst_savings: $expectedDSTSavings
+}
+EOTXT;
+ $this->assertDumpEquals($expected, $var);
+ }
+
+ public function testCastIntlTimeZoneWithoutDST()
+ {
+ $var = \IntlTimeZone::createTimeZone('Asia/Bangkok');
+
+ $expectedDisplayName = $var->getDisplayName();
+ $expectedID = $var->getID();
+ $expectedRawOffset = $var->getRawOffset();
+
+ $expected = <<<EOTXT
+IntlTimeZone {
+ display_name: "$expectedDisplayName"
+ id: "$expectedID"
+ raw_offset: $expectedRawOffset
+}
+EOTXT;
+ $this->assertDumpEquals($expected, $var);
+ }
+
+ public function testCastIntlCalendar()
+ {
+ $var = \IntlCalendar::createInstance('America/Los_Angeles', 'en');
+
+ $expectedType = $var->getType();
+ $expectedFirstDayOfWeek = $var->getFirstDayOfWeek();
+ $expectedMinimalDaysInFirstWeek = $var->getMinimalDaysInFirstWeek();
+ $expectedRepeatedWallTimeOption = $var->getRepeatedWallTimeOption();
+ $expectedSkippedWallTimeOption = $var->getSkippedWallTimeOption();
+ $expectedTime = $var->getTime().'.0';
+ $expectedInDaylightTime = $var->inDaylightTime() ? 'true' : 'false';
+ $expectedIsLenient = $var->isLenient() ? 'true' : 'false';
+
+ $expectedTimeZone = $var->getTimeZone();
+ $expectedTimeZoneDisplayName = $expectedTimeZone->getDisplayName();
+ $expectedTimeZoneID = $expectedTimeZone->getID();
+ $expectedTimeZoneRawOffset = $expectedTimeZone->getRawOffset();
+ $expectedTimeZoneDSTSavings = $expectedTimeZone->getDSTSavings();
+
+ $expected = <<<EOTXT
+IntlGregorianCalendar {
+ type: "$expectedType"
+ first_day_of_week: $expectedFirstDayOfWeek
+ minimal_days_in_first_week: $expectedMinimalDaysInFirstWeek
+ repeated_wall_time_option: $expectedRepeatedWallTimeOption
+ skipped_wall_time_option: $expectedSkippedWallTimeOption
+ time: $expectedTime
+ in_daylight_time: $expectedInDaylightTime
+ is_lenient: $expectedIsLenient
+ time_zone: IntlTimeZone {
+ display_name: "$expectedTimeZoneDisplayName"
+ id: "$expectedTimeZoneID"
+ raw_offset: $expectedTimeZoneRawOffset
+ dst_savings: $expectedTimeZoneDSTSavings
+ }
+}
+EOTXT;
+ $this->assertDumpEquals($expected, $var);
+ }
+
+ public function testCastDateFormatter()
+ {
+ $var = new \IntlDateFormatter('en', \IntlDateFormatter::TRADITIONAL, \IntlDateFormatter::TRADITIONAL);
+
+ $expectedLocale = $var->getLocale();
+ $expectedPattern = $var->getPattern();
+ $expectedCalendar = $var->getCalendar();
+ $expectedTimeZoneId = $var->getTimeZoneId();
+ $expectedTimeType = $var->getTimeType();
+ $expectedDateType = $var->getDateType();
+
+ $expectedTimeZone = $var->getTimeZone();
+ $expectedTimeZoneDisplayName = $expectedTimeZone->getDisplayName();
+ $expectedTimeZoneID = $expectedTimeZone->getID();
+ $expectedTimeZoneRawOffset = $expectedTimeZone->getRawOffset();
+ $expectedTimeZoneDSTSavings = $expectedTimeZone->useDaylightTime() ? "\n dst_savings: ".$expectedTimeZone->getDSTSavings() : '';
+
+ $expectedCalendarObject = $var->getCalendarObject();
+ $expectedCalendarObjectType = $expectedCalendarObject->getType();
+ $expectedCalendarObjectFirstDayOfWeek = $expectedCalendarObject->getFirstDayOfWeek();
+ $expectedCalendarObjectMinimalDaysInFirstWeek = $expectedCalendarObject->getMinimalDaysInFirstWeek();
+ $expectedCalendarObjectRepeatedWallTimeOption = $expectedCalendarObject->getRepeatedWallTimeOption();
+ $expectedCalendarObjectSkippedWallTimeOption = $expectedCalendarObject->getSkippedWallTimeOption();
+ $expectedCalendarObjectTime = $expectedCalendarObject->getTime().'.0';
+ $expectedCalendarObjectInDaylightTime = $expectedCalendarObject->inDaylightTime() ? 'true' : 'false';
+ $expectedCalendarObjectIsLenient = $expectedCalendarObject->isLenient() ? 'true' : 'false';
+
+ $expectedCalendarObjectTimeZone = $expectedCalendarObject->getTimeZone();
+ $expectedCalendarObjectTimeZoneDisplayName = $expectedCalendarObjectTimeZone->getDisplayName();
+ $expectedCalendarObjectTimeZoneID = $expectedCalendarObjectTimeZone->getID();
+ $expectedCalendarObjectTimeZoneRawOffset = $expectedCalendarObjectTimeZone->getRawOffset();
+ $expectedCalendarObjectTimeZoneDSTSavings = $expectedTimeZone->useDaylightTime() ? "\n dst_savings: ".$expectedCalendarObjectTimeZone->getDSTSavings() : '';
+
+ $expected = <<<EOTXT
+IntlDateFormatter {
+ locale: "$expectedLocale"
+ pattern: "$expectedPattern"
+ calendar: $expectedCalendar
+ time_zone_id: "$expectedTimeZoneId"
+ time_type: $expectedTimeType
+ date_type: $expectedDateType
+ calendar_object: IntlGregorianCalendar {
+ type: "$expectedCalendarObjectType"
+ first_day_of_week: $expectedCalendarObjectFirstDayOfWeek
+ minimal_days_in_first_week: $expectedCalendarObjectMinimalDaysInFirstWeek
+ repeated_wall_time_option: $expectedCalendarObjectRepeatedWallTimeOption
+ skipped_wall_time_option: $expectedCalendarObjectSkippedWallTimeOption
+ time: $expectedCalendarObjectTime
+ in_daylight_time: $expectedCalendarObjectInDaylightTime
+ is_lenient: $expectedCalendarObjectIsLenient
+ time_zone: IntlTimeZone {
+ display_name: "$expectedCalendarObjectTimeZoneDisplayName"
+ id: "$expectedCalendarObjectTimeZoneID"
+ raw_offset: $expectedCalendarObjectTimeZoneRawOffset$expectedCalendarObjectTimeZoneDSTSavings
+ }
+ }
+ time_zone: IntlTimeZone {
+ display_name: "$expectedTimeZoneDisplayName"
+ id: "$expectedTimeZoneID"
+ raw_offset: $expectedTimeZoneRawOffset$expectedTimeZoneDSTSavings
+ }
+}
+EOTXT;
+ $this->assertDumpEquals($expected, $var);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/MemcachedCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/MemcachedCasterTest.php
new file mode 100755
index 0000000..df48390
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/MemcachedCasterTest.php
@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+
+/**
+ * @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
+ */
+class MemcachedCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ public function testCastMemcachedWithDefaultOptions()
+ {
+ if (!class_exists('Memcached')) {
+ $this->markTestSkipped('Memcached not available');
+ }
+
+ $var = new \Memcached();
+ $var->addServer('127.0.0.1', 11211);
+ $var->addServer('127.0.0.2', 11212);
+
+ $expected = <<<EOTXT
+Memcached {
+ servers: array:2 [
+ 0 => array:3 [
+ "host" => "127.0.0.1"
+ "port" => 11211
+ "type" => "TCP"
+ ]
+ 1 => array:3 [
+ "host" => "127.0.0.2"
+ "port" => 11212
+ "type" => "TCP"
+ ]
+ ]
+ options: {}
+}
+EOTXT;
+ $this->assertDumpEquals($expected, $var);
+ }
+
+ public function testCastMemcachedWithCustomOptions()
+ {
+ if (!class_exists('Memcached')) {
+ $this->markTestSkipped('Memcached not available');
+ }
+
+ $var = new \Memcached();
+ $var->addServer('127.0.0.1', 11211);
+ $var->addServer('127.0.0.2', 11212);
+
+ // set a subset of non default options to test boolean, string and integer output
+ $var->setOption(\Memcached::OPT_COMPRESSION, false);
+ $var->setOption(\Memcached::OPT_PREFIX_KEY, 'pre');
+ $var->setOption(\Memcached::OPT_DISTRIBUTION, \Memcached::DISTRIBUTION_CONSISTENT);
+
+ $expected = <<<'EOTXT'
+Memcached {
+ servers: array:2 [
+ 0 => array:3 [
+ "host" => "127.0.0.1"
+ "port" => 11211
+ "type" => "TCP"
+ ]
+ 1 => array:3 [
+ "host" => "127.0.0.2"
+ "port" => 11212
+ "type" => "TCP"
+ ]
+ ]
+ options: {
+ OPT_COMPRESSION: false
+ OPT_PREFIX_KEY: "pre"
+ OPT_DISTRIBUTION: 1
+ }
+}
+EOTXT;
+
+ $this->assertDumpEquals($expected, $var);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/PdoCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/PdoCasterTest.php
new file mode 100755
index 0000000..19bbe0f
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/PdoCasterTest.php
@@ -0,0 +1,64 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Caster\PdoCaster;
+use Symfony\Component\VarDumper\Cloner\Stub;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class PdoCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ /**
+ * @requires extension pdo_sqlite
+ */
+ public function testCastPdo()
+ {
+ $pdo = new \PDO('sqlite::memory:');
+ $pdo->setAttribute(\PDO::ATTR_STATEMENT_CLASS, ['PDOStatement', [$pdo]]);
+
+ $cast = PdoCaster::castPdo($pdo, [], new Stub(), false);
+
+ $this->assertInstanceOf('Symfony\Component\VarDumper\Caster\EnumStub', $cast["\0~\0attributes"]);
+
+ $attr = $cast["\0~\0attributes"] = $cast["\0~\0attributes"]->value;
+ $this->assertInstanceOf('Symfony\Component\VarDumper\Caster\ConstStub', $attr['CASE']);
+ $this->assertSame('NATURAL', $attr['CASE']->class);
+ $this->assertSame('BOTH', $attr['DEFAULT_FETCH_MODE']->class);
+
+ $xDump = <<<'EODUMP'
+array:2 [
+ "\x00~\x00inTransaction" => false
+ "\x00~\x00attributes" => array:9 [
+ "CASE" => NATURAL
+ "ERRMODE" => SILENT
+ "PERSISTENT" => false
+ "DRIVER_NAME" => "sqlite"
+ "ORACLE_NULLS" => NATURAL
+ "CLIENT_VERSION" => "%s"
+ "SERVER_VERSION" => "%s"
+ "STATEMENT_CLASS" => array:%d [
+ 0 => "PDOStatement"%A
+ ]
+ "DEFAULT_FETCH_MODE" => BOTH
+ ]
+]
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xDump, $cast);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/RedisCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/RedisCasterTest.php
new file mode 100755
index 0000000..3edbed6
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/RedisCasterTest.php
@@ -0,0 +1,70 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @requires extension redis
+ */
+class RedisCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ public function testNotConnected()
+ {
+ $redis = new \Redis();
+
+ $xCast = <<<'EODUMP'
+Redis {
+ isConnected: false
+}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xCast, $redis);
+ }
+
+ public function testConnected()
+ {
+ $redis = new \Redis();
+ if (!@$redis->connect('127.0.0.1')) {
+ $e = error_get_last();
+ self::markTestSkipped($e['message']);
+ }
+
+ $xCast = <<<'EODUMP'
+Redis {%A
+ isConnected: true
+ host: "127.0.0.1"
+ port: 6379
+ auth: null
+ mode: ATOMIC
+ dbNum: 0
+ timeout: 0.0
+ lastError: null
+ persistentId: null
+ options: {
+ TCP_KEEPALIVE: 0
+ READ_TIMEOUT: 0.0
+ COMPRESSION: NONE
+ SERIALIZER: NONE
+ PREFIX: null
+ SCAN: NORETRY
+ }
+}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xCast, $redis);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/ReflectionCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/ReflectionCasterTest.php
new file mode 100755
index 0000000..7e37dfe
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/ReflectionCasterTest.php
@@ -0,0 +1,251 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Caster\Caster;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+use Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo;
+use Symfony\Component\VarDumper\Tests\Fixtures\NotLoadableClass;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class ReflectionCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ public function testReflectionCaster()
+ {
+ $var = new \ReflectionClass('ReflectionClass');
+
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+ReflectionClass {
+ +name: "ReflectionClass"
+%Aimplements: array:%d [
+ 0 => "Reflector"
+%A]
+ constants: array:3 [
+ "IS_IMPLICIT_ABSTRACT" => 16
+ "IS_EXPLICIT_ABSTRACT" => %d
+ "IS_FINAL" => %d
+ ]
+ properties: array:%d [
+ "name" => ReflectionProperty {
+%A +name: "name"
+ +class: "ReflectionClass"
+%A modifiers: "public"
+ }
+%A]
+ methods: array:%d [
+%A
+ "export" => ReflectionMethod {
+ +name: "export"
+ +class: "ReflectionClass"
+%A parameters: {
+ $%s: ReflectionParameter {
+%A position: 0
+%A
+}
+EOTXT
+ , $var
+ );
+ }
+
+ public function testClosureCaster()
+ {
+ $a = $b = 123;
+ $var = function ($x) use ($a, &$b) {};
+
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+Closure($x) {
+%Ause: {
+ $a: 123
+ $b: & 123
+ }
+ file: "%sReflectionCasterTest.php"
+ line: "68 to 68"
+}
+EOTXT
+ , $var
+ );
+ }
+
+ public function testFromCallableClosureCaster()
+ {
+ if (\defined('HHVM_VERSION_ID')) {
+ $this->markTestSkipped('Not for HHVM.');
+ }
+ $var = [
+ (new \ReflectionMethod($this, __FUNCTION__))->getClosure($this),
+ (new \ReflectionMethod(__CLASS__, 'tearDownAfterClass'))->getClosure(),
+ ];
+
+ $this->assertDumpMatchesFormat(
+ <<<EOTXT
+array:2 [
+ 0 => Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest::testFromCallableClosureCaster() {
+ this: Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest { …}
+ file: "%sReflectionCasterTest.php"
+ line: "%d to %d"
+ }
+ 1 => %sTestCase::tearDownAfterClass() {
+ file: "%sTestCase.php"
+ line: "%d to %d"
+ }
+]
+EOTXT
+ , $var
+ );
+ }
+
+ public function testClosureCasterExcludingVerbosity()
+ {
+ $var = function &($a = 5) {};
+
+ $this->assertDumpEquals('Closure&($a = 5) { …5}', $var, Caster::EXCLUDE_VERBOSE);
+ }
+
+ public function testReflectionParameter()
+ {
+ $var = new \ReflectionParameter(__NAMESPACE__.'\reflectionParameterFixture', 0);
+
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+ReflectionParameter {
+ +name: "arg1"
+ position: 0
+ typeHint: "Symfony\Component\VarDumper\Tests\Fixtures\NotLoadableClass"
+ default: null
+}
+EOTXT
+ , $var
+ );
+ }
+
+ public function testReflectionParameterScalar()
+ {
+ $f = eval('return function (int $a) {};');
+ $var = new \ReflectionParameter($f, 0);
+
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+ReflectionParameter {
+ +name: "a"
+ position: 0
+ typeHint: "int"
+}
+EOTXT
+ , $var
+ );
+ }
+
+ public function testReturnType()
+ {
+ $f = eval('return function ():int {};');
+ $line = __LINE__ - 1;
+
+ $this->assertDumpMatchesFormat(
+ <<<EOTXT
+Closure(): int {
+ returnType: "int"
+ class: "Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest"
+ this: Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest { …}
+ file: "%sReflectionCasterTest.php($line) : eval()'d code"
+ line: "1 to 1"
+}
+EOTXT
+ , $f
+ );
+ }
+
+ public function testGenerator()
+ {
+ if (\extension_loaded('xdebug')) {
+ $this->markTestSkipped('xdebug is active');
+ }
+
+ $generator = new GeneratorDemo();
+ $generator = $generator->baz();
+
+ $expectedDump = <<<'EODUMP'
+Generator {
+ this: Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo { …}
+ executing: {
+ Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo->baz() {
+ %sGeneratorDemo.php:14 {
+ › {
+ › yield from bar();
+ › }
+ }
+ }
+ }
+ closed: false
+}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($expectedDump, $generator);
+
+ foreach ($generator as $v) {
+ break;
+ }
+
+ $expectedDump = <<<'EODUMP'
+array:2 [
+ 0 => ReflectionGenerator {
+ this: Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo { …}
+ trace: {
+ %s%eTests%eFixtures%eGeneratorDemo.php:9 {
+ › {
+ › yield 1;
+ › }
+ }
+ %s%eTests%eFixtures%eGeneratorDemo.php:20 { …}
+ %s%eTests%eFixtures%eGeneratorDemo.php:14 { …}
+ }
+ closed: false
+ }
+ 1 => Generator {
+ executing: {
+ Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo::foo() {
+ %sGeneratorDemo.php:10 {
+ › yield 1;
+ › }
+ ›
+ }
+ }
+ }
+ closed: false
+ }
+]
+EODUMP;
+
+ $r = new \ReflectionGenerator($generator);
+ $this->assertDumpMatchesFormat($expectedDump, [$r, $r->getExecutingGenerator()]);
+
+ foreach ($generator as $v) {
+ }
+
+ $expectedDump = <<<'EODUMP'
+Generator {
+ closed: true
+}
+EODUMP;
+ $this->assertDumpMatchesFormat($expectedDump, $generator);
+ }
+}
+
+function reflectionParameterFixture(NotLoadableClass $arg1 = null, $arg2)
+{
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/SplCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/SplCasterTest.php
new file mode 100755
index 0000000..c7d7981
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/SplCasterTest.php
@@ -0,0 +1,207 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+
+/**
+ * @author Grégoire Pineau <lyrixx@lyrixx.info>
+ */
+class SplCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ public function getCastFileInfoTests()
+ {
+ return [
+ [__FILE__, <<<'EOTXT'
+SplFileInfo {
+%Apath: "%sCaster"
+ filename: "SplCasterTest.php"
+ basename: "SplCasterTest.php"
+ pathname: "%sSplCasterTest.php"
+ extension: "php"
+ realPath: "%sSplCasterTest.php"
+ aTime: %s-%s-%d %d:%d:%d
+ mTime: %s-%s-%d %d:%d:%d
+ cTime: %s-%s-%d %d:%d:%d
+ inode: %i
+ size: %d
+ perms: 0%d
+ owner: %d
+ group: %d
+ type: "file"
+ writable: true
+ readable: true
+ executable: false
+ file: true
+ dir: false
+ link: false
+%A}
+EOTXT
+ ],
+ ['https://google.com/about', <<<'EOTXT'
+SplFileInfo {
+%Apath: "https://google.com"
+ filename: "about"
+ basename: "about"
+ pathname: "https://google.com/about"
+ extension: ""
+ realPath: false
+%A}
+EOTXT
+ ],
+ ];
+ }
+
+ /** @dataProvider getCastFileInfoTests */
+ public function testCastFileInfo($file, $dump)
+ {
+ $this->assertDumpMatchesFormat($dump, new \SplFileInfo($file));
+ }
+
+ public function testCastFileObject()
+ {
+ $var = new \SplFileObject(__FILE__);
+ $var->setFlags(\SplFileObject::DROP_NEW_LINE | \SplFileObject::SKIP_EMPTY);
+ $dump = <<<'EOTXT'
+SplFileObject {
+%Apath: "%sCaster"
+ filename: "SplCasterTest.php"
+ basename: "SplCasterTest.php"
+ pathname: "%sSplCasterTest.php"
+ extension: "php"
+ realPath: "%sSplCasterTest.php"
+ aTime: %s-%s-%d %d:%d:%d
+ mTime: %s-%s-%d %d:%d:%d
+ cTime: %s-%s-%d %d:%d:%d
+ inode: %i
+ size: %d
+ perms: 0%d
+ owner: %d
+ group: %d
+ type: "file"
+ writable: true
+ readable: true
+ executable: false
+ file: true
+ dir: false
+ link: false
+%AcsvControl: array:%d [
+ 0 => ","
+ 1 => """
+%A]
+ flags: DROP_NEW_LINE|SKIP_EMPTY
+ maxLineLen: 0
+ fstat: array:26 [
+ "dev" => %d
+ "ino" => %i
+ "nlink" => %d
+ "rdev" => 0
+ "blksize" => %i
+ "blocks" => %i
+ …20
+ ]
+ eof: false
+ key: 0
+}
+EOTXT;
+ $this->assertDumpMatchesFormat($dump, $var);
+ }
+
+ /**
+ * @dataProvider provideCastSplDoublyLinkedList
+ */
+ public function testCastSplDoublyLinkedList($modeValue, $modeDump)
+ {
+ $var = new \SplDoublyLinkedList();
+ $var->setIteratorMode($modeValue);
+ $dump = <<<EOTXT
+SplDoublyLinkedList {
+%Amode: $modeDump
+ dllist: []
+}
+EOTXT;
+ $this->assertDumpMatchesFormat($dump, $var);
+ }
+
+ public function provideCastSplDoublyLinkedList()
+ {
+ return [
+ [\SplDoublyLinkedList::IT_MODE_FIFO, 'IT_MODE_FIFO | IT_MODE_KEEP'],
+ [\SplDoublyLinkedList::IT_MODE_LIFO, 'IT_MODE_LIFO | IT_MODE_KEEP'],
+ [\SplDoublyLinkedList::IT_MODE_FIFO | \SplDoublyLinkedList::IT_MODE_DELETE, 'IT_MODE_FIFO | IT_MODE_DELETE'],
+ [\SplDoublyLinkedList::IT_MODE_LIFO | \SplDoublyLinkedList::IT_MODE_DELETE, 'IT_MODE_LIFO | IT_MODE_DELETE'],
+ ];
+ }
+
+ public function testCastObjectStorageIsntModified()
+ {
+ $var = new \SplObjectStorage();
+ $var->attach(new \stdClass());
+ $var->rewind();
+ $current = $var->current();
+
+ $this->assertDumpMatchesFormat('%A', $var);
+ $this->assertSame($current, $var->current());
+ }
+
+ public function testCastObjectStorageDumpsInfo()
+ {
+ $var = new \SplObjectStorage();
+ $var->attach(new \stdClass(), new \DateTime());
+
+ $this->assertDumpMatchesFormat('%ADateTime%A', $var);
+ }
+
+ public function testCastArrayObject()
+ {
+ $var = new \ArrayObject([123]);
+ $var->foo = 234;
+
+ $expected = <<<EOTXT
+ArrayObject {
+ +"foo": 234
+ flag::STD_PROP_LIST: false
+ flag::ARRAY_AS_PROPS: false
+ iteratorClass: "ArrayIterator"
+ storage: array:1 [
+ 0 => 123
+ ]
+}
+EOTXT;
+ $this->assertDumpEquals($expected, $var);
+ }
+
+ public function testArrayIterator()
+ {
+ $var = new MyArrayIterator([234]);
+
+ $expected = <<<EOTXT
+Symfony\Component\VarDumper\Tests\Caster\MyArrayIterator {
+ -foo: 123
+ flag::STD_PROP_LIST: false
+ flag::ARRAY_AS_PROPS: false
+ storage: array:1 [
+ 0 => 234
+ ]
+}
+EOTXT;
+ $this->assertDumpEquals($expected, $var);
+ }
+}
+
+class MyArrayIterator extends \ArrayIterator
+{
+ private $foo = 123;
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/StubCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/StubCasterTest.php
new file mode 100755
index 0000000..8056f70
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/StubCasterTest.php
@@ -0,0 +1,213 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Caster\ArgsStub;
+use Symfony\Component\VarDumper\Caster\ClassStub;
+use Symfony\Component\VarDumper\Caster\LinkStub;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+use Symfony\Component\VarDumper\Tests\Fixtures\FooInterface;
+
+class StubCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ public function testArgsStubWithDefaults($foo = 234, $bar = 456)
+ {
+ $args = [new ArgsStub([123], __FUNCTION__, __CLASS__)];
+
+ $expectedDump = <<<'EODUMP'
+array:1 [
+ 0 => {
+ $foo: 123
+ }
+]
+EODUMP;
+
+ $this->assertDumpMatchesFormat($expectedDump, $args);
+ }
+
+ public function testArgsStubWithExtraArgs($foo = 234)
+ {
+ $args = [new ArgsStub([123, 456], __FUNCTION__, __CLASS__)];
+
+ $expectedDump = <<<'EODUMP'
+array:1 [
+ 0 => {
+ $foo: 123
+ ...: {
+ 456
+ }
+ }
+]
+EODUMP;
+
+ $this->assertDumpMatchesFormat($expectedDump, $args);
+ }
+
+ public function testArgsStubNoParamWithExtraArgs()
+ {
+ $args = [new ArgsStub([123], __FUNCTION__, __CLASS__)];
+
+ $expectedDump = <<<'EODUMP'
+array:1 [
+ 0 => {
+ 123
+ }
+]
+EODUMP;
+
+ $this->assertDumpMatchesFormat($expectedDump, $args);
+ }
+
+ public function testArgsStubWithClosure()
+ {
+ $args = [new ArgsStub([123], '{closure}', null)];
+
+ $expectedDump = <<<'EODUMP'
+array:1 [
+ 0 => {
+ 123
+ }
+]
+EODUMP;
+
+ $this->assertDumpMatchesFormat($expectedDump, $args);
+ }
+
+ public function testLinkStub()
+ {
+ $var = [new LinkStub(__CLASS__, 0, __FILE__)];
+
+ $cloner = new VarCloner();
+ $dumper = new HtmlDumper();
+ $dumper->setDumpHeader('<foo></foo>');
+ $dumper->setDumpBoundaries('<bar>', '</bar>');
+ $dumper->setDisplayOptions(['fileLinkFormat' => '%f:%l']);
+ $dump = $dumper->dump($cloner->cloneVar($var), true);
+
+ $expectedDump = <<<'EODUMP'
+<foo></foo><bar><span class=sf-dump-note>array:1</span> [<samp>
+ <span class=sf-dump-index>0</span> => "<a href="%sStubCasterTest.php:0" rel="noopener noreferrer"><span class=sf-dump-str title="55 characters">Symfony\Component\VarDumper\Tests\Caster\StubCasterTest</span></a>"
+</samp>]
+</bar>
+EODUMP;
+
+ $this->assertStringMatchesFormat($expectedDump, $dump);
+ }
+
+ public function testLinkStubWithNoFileLink()
+ {
+ $var = [new LinkStub('example.com', 0, 'http://example.com')];
+
+ $cloner = new VarCloner();
+ $dumper = new HtmlDumper();
+ $dumper->setDumpHeader('<foo></foo>');
+ $dumper->setDumpBoundaries('<bar>', '</bar>');
+ $dumper->setDisplayOptions(['fileLinkFormat' => '%f:%l']);
+ $dump = $dumper->dump($cloner->cloneVar($var), true);
+
+ $expectedDump = <<<'EODUMP'
+<foo></foo><bar><span class=sf-dump-note>array:1</span> [<samp>
+ <span class=sf-dump-index>0</span> => "<a href="http://example.com" target="_blank" rel="noopener noreferrer"><span class=sf-dump-str title="11 characters">example.com</span></a>"
+</samp>]
+</bar>
+EODUMP;
+
+ $this->assertStringMatchesFormat($expectedDump, $dump);
+ }
+
+ public function testClassStub()
+ {
+ $var = [new ClassStub('hello', [FooInterface::class, 'foo'])];
+
+ $cloner = new VarCloner();
+ $dumper = new HtmlDumper();
+ $dumper->setDumpHeader('<foo></foo>');
+ $dumper->setDumpBoundaries('<bar>', '</bar>');
+ $dump = $dumper->dump($cloner->cloneVar($var), true, ['fileLinkFormat' => '%f:%l']);
+
+ $expectedDump = <<<'EODUMP'
+<foo></foo><bar><span class=sf-dump-note>array:1</span> [<samp>
+ <span class=sf-dump-index>0</span> => "<a href="%sFooInterface.php:10" rel="noopener noreferrer"><span class=sf-dump-str title="39 characters">hello(?stdClass $a, stdClass $b = null)</span></a>"
+</samp>]
+</bar>
+EODUMP;
+
+ $this->assertStringMatchesFormat($expectedDump, $dump);
+ }
+
+ public function testClassStubWithNotExistingClass()
+ {
+ $var = [new ClassStub(NotExisting::class)];
+
+ $cloner = new VarCloner();
+ $dumper = new HtmlDumper();
+ $dumper->setDumpHeader('<foo></foo>');
+ $dumper->setDumpBoundaries('<bar>', '</bar>');
+ $dump = $dumper->dump($cloner->cloneVar($var), true);
+
+ $expectedDump = <<<'EODUMP'
+<foo></foo><bar><span class=sf-dump-note>array:1</span> [<samp>
+ <span class=sf-dump-index>0</span> => "<span class=sf-dump-str title="Symfony\Component\VarDumper\Tests\Caster\NotExisting
+52 characters"><span class="sf-dump-ellipsis sf-dump-ellipsis-class">Symfony\Component\VarDumper\Tests\Caster</span><span class=sf-dump-ellipsis>\</span>NotExisting</span>"
+</samp>]
+</bar>
+EODUMP;
+
+ $this->assertStringMatchesFormat($expectedDump, $dump);
+ }
+
+ public function testClassStubWithNotExistingMethod()
+ {
+ $var = [new ClassStub('hello', [FooInterface::class, 'missing'])];
+
+ $cloner = new VarCloner();
+ $dumper = new HtmlDumper();
+ $dumper->setDumpHeader('<foo></foo>');
+ $dumper->setDumpBoundaries('<bar>', '</bar>');
+ $dump = $dumper->dump($cloner->cloneVar($var), true, ['fileLinkFormat' => '%f:%l']);
+
+ $expectedDump = <<<'EODUMP'
+<foo></foo><bar><span class=sf-dump-note>array:1</span> [<samp>
+ <span class=sf-dump-index>0</span> => "<a href="%sFooInterface.php:5" rel="noopener noreferrer"><span class=sf-dump-str title="5 characters">hello</span></a>"
+</samp>]
+</bar>
+EODUMP;
+
+ $this->assertStringMatchesFormat($expectedDump, $dump);
+ }
+
+ public function testClassStubWithAnonymousClass()
+ {
+ $var = [new ClassStub(\get_class(new class() extends \Exception {
+ }))];
+
+ $cloner = new VarCloner();
+ $dumper = new HtmlDumper();
+ $dumper->setDumpHeader('<foo></foo>');
+ $dumper->setDumpBoundaries('<bar>', '</bar>');
+ $dump = $dumper->dump($cloner->cloneVar($var), true, ['fileLinkFormat' => '%f:%l']);
+
+ $expectedDump = <<<'EODUMP'
+<foo></foo><bar><span class=sf-dump-note>array:1</span> [<samp>
+ <span class=sf-dump-index>0</span> => "<a href="%sStubCasterTest.php:195" rel="noopener noreferrer"><span class=sf-dump-str title="19 characters">Exception@anonymous</span></a>"
+</samp>]
+</bar>
+EODUMP;
+
+ $this->assertStringMatchesFormat($expectedDump, $dump);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Caster/XmlReaderCasterTest.php b/vendor/symfony/var-dumper/Tests/Caster/XmlReaderCasterTest.php
new file mode 100755
index 0000000..1d7b3f6
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Caster/XmlReaderCasterTest.php
@@ -0,0 +1,248 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Caster;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+
+/**
+ * @author Baptiste Clavié <clavie.b@gmail.com>
+ */
+class XmlReaderCasterTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ /** @var \XmlReader */
+ private $reader;
+
+ protected function setUp()
+ {
+ $this->reader = new \XmlReader();
+ $this->reader->open(__DIR__.'/../Fixtures/xml_reader.xml');
+ }
+
+ protected function tearDown()
+ {
+ $this->reader->close();
+ }
+
+ public function testParserProperty()
+ {
+ $this->reader->setParserProperty(\XMLReader::SUBST_ENTITIES, true);
+
+ $expectedDump = <<<'EODUMP'
+XMLReader {
+ +nodeType: NONE
+ parserProperties: {
+ SUBST_ENTITIES: true
+ …3
+ }
+ …12
+}
+EODUMP;
+
+ $this->assertDumpMatchesFormat($expectedDump, $this->reader);
+ }
+
+ /**
+ * @dataProvider provideNodes
+ */
+ public function testNodes($seek, $expectedDump)
+ {
+ while ($seek--) {
+ $this->reader->read();
+ }
+ $this->assertDumpMatchesFormat($expectedDump, $this->reader);
+ }
+
+ public function provideNodes()
+ {
+ return [
+ [0, <<<'EODUMP'
+XMLReader {
+ +nodeType: NONE
+ …13
+}
+EODUMP
+ ],
+ [1, <<<'EODUMP'
+XMLReader {
+ +localName: "foo"
+ +nodeType: ELEMENT
+ +baseURI: "%sxml_reader.xml"
+ …11
+}
+EODUMP
+ ],
+ [2, <<<'EODUMP'
+XMLReader {
+ +localName: "#text"
+ +nodeType: SIGNIFICANT_WHITESPACE
+ +depth: 1
+ +value: """
+ \n
+
+ """
+ +baseURI: "%sxml_reader.xml"
+ …9
+}
+EODUMP
+ ],
+ [3, <<<'EODUMP'
+XMLReader {
+ +localName: "bar"
+ +nodeType: ELEMENT
+ +depth: 1
+ +baseURI: "%sxml_reader.xml"
+ …10
+}
+EODUMP
+ ],
+ [4, <<<'EODUMP'
+XMLReader {
+ +localName: "bar"
+ +nodeType: END_ELEMENT
+ +depth: 1
+ +baseURI: "%sxml_reader.xml"
+ …10
+}
+EODUMP
+ ],
+ [6, <<<'EODUMP'
+XMLReader {
+ +localName: "bar"
+ +nodeType: ELEMENT
+ +depth: 1
+ +isEmptyElement: true
+ +baseURI: "%sxml_reader.xml"
+ …9
+}
+EODUMP
+ ],
+ [9, <<<'EODUMP'
+XMLReader {
+ +localName: "#text"
+ +nodeType: TEXT
+ +depth: 2
+ +value: "With text"
+ +baseURI: "%sxml_reader.xml"
+ …9
+}
+EODUMP
+ ],
+ [12, <<<'EODUMP'
+XMLReader {
+ +localName: "bar"
+ +nodeType: ELEMENT
+ +depth: 1
+ +attributeCount: 2
+ +baseURI: "%sxml_reader.xml"
+ …9
+}
+EODUMP
+ ],
+ [13, <<<'EODUMP'
+XMLReader {
+ +localName: "bar"
+ +nodeType: END_ELEMENT
+ +depth: 1
+ +baseURI: "%sxml_reader.xml"
+ …10
+}
+EODUMP
+ ],
+ [15, <<<'EODUMP'
+XMLReader {
+ +localName: "bar"
+ +nodeType: ELEMENT
+ +depth: 1
+ +attributeCount: 1
+ +baseURI: "%sxml_reader.xml"
+ …9
+}
+EODUMP
+ ],
+ [16, <<<'EODUMP'
+XMLReader {
+ +localName: "#text"
+ +nodeType: SIGNIFICANT_WHITESPACE
+ +depth: 2
+ +value: """
+ \n
+
+ """
+ +baseURI: "%sxml_reader.xml"
+ …9
+}
+EODUMP
+ ],
+ [17, <<<'EODUMP'
+XMLReader {
+ +localName: "baz"
+ +prefix: "baz"
+ +nodeType: ELEMENT
+ +depth: 2
+ +namespaceURI: "http://symfony.com"
+ +baseURI: "%sxml_reader.xml"
+ …8
+}
+EODUMP
+ ],
+ [18, <<<'EODUMP'
+XMLReader {
+ +localName: "baz"
+ +prefix: "baz"
+ +nodeType: END_ELEMENT
+ +depth: 2
+ +namespaceURI: "http://symfony.com"
+ +baseURI: "%sxml_reader.xml"
+ …8
+}
+EODUMP
+ ],
+ [19, <<<'EODUMP'
+XMLReader {
+ +localName: "#text"
+ +nodeType: SIGNIFICANT_WHITESPACE
+ +depth: 2
+ +value: """
+ \n
+
+ """
+ +baseURI: "%sxml_reader.xml"
+ …9
+}
+EODUMP
+ ],
+ [21, <<<'EODUMP'
+XMLReader {
+ +localName: "#text"
+ +nodeType: SIGNIFICANT_WHITESPACE
+ +depth: 1
+ +value: "\n"
+ +baseURI: "%sxml_reader.xml"
+ …9
+}
+EODUMP
+ ],
+ [22, <<<'EODUMP'
+XMLReader {
+ +localName: "foo"
+ +nodeType: END_ELEMENT
+ +baseURI: "%sxml_reader.xml"
+ …11
+}
+EODUMP
+ ],
+ ];
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Cloner/DataTest.php b/vendor/symfony/var-dumper/Tests/Cloner/DataTest.php
new file mode 100755
index 0000000..7d20bce
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Cloner/DataTest.php
@@ -0,0 +1,115 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Cloner;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Caster\Caster;
+use Symfony\Component\VarDumper\Caster\ClassStub;
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+
+class DataTest extends TestCase
+{
+ public function testBasicData()
+ {
+ $values = [1 => 123, 4.5, 'abc', null, false];
+ $data = $this->cloneVar($values);
+ $clonedValues = [];
+
+ $this->assertInstanceOf(Data::class, $data);
+ $this->assertCount(\count($values), $data);
+ $this->assertFalse(isset($data->{0}));
+ $this->assertFalse(isset($data[0]));
+
+ foreach ($data as $k => $v) {
+ $this->assertTrue(isset($data->{$k}));
+ $this->assertTrue(isset($data[$k]));
+ $this->assertSame(\gettype($values[$k]), $data->seek($k)->getType());
+ $this->assertSame($values[$k], $data->seek($k)->getValue());
+ $this->assertSame($values[$k], $data->{$k});
+ $this->assertSame($values[$k], $data[$k]);
+ $this->assertSame((string) $values[$k], (string) $data->seek($k));
+
+ $clonedValues[$k] = $v->getValue();
+ }
+
+ $this->assertSame($values, $clonedValues);
+ }
+
+ public function testObject()
+ {
+ $data = $this->cloneVar(new \Exception('foo'));
+
+ $this->assertSame('Exception', $data->getType());
+
+ $this->assertSame('foo', $data->message);
+ $this->assertSame('foo', $data->{Caster::PREFIX_PROTECTED.'message'});
+
+ $this->assertSame('foo', $data['message']);
+ $this->assertSame('foo', $data[Caster::PREFIX_PROTECTED.'message']);
+
+ $this->assertStringMatchesFormat('Exception (count=%d)', (string) $data);
+ }
+
+ public function testArray()
+ {
+ $values = [[], [123]];
+ $data = $this->cloneVar($values);
+
+ $this->assertSame($values, $data->getValue(true));
+
+ $children = $data->getValue();
+
+ $this->assertInternalType('array', $children);
+
+ $this->assertInstanceOf(Data::class, $children[0]);
+ $this->assertInstanceOf(Data::class, $children[1]);
+
+ $this->assertEquals($children[0], $data[0]);
+ $this->assertEquals($children[1], $data[1]);
+
+ $this->assertSame($values[0], $children[0]->getValue(true));
+ $this->assertSame($values[1], $children[1]->getValue(true));
+ }
+
+ public function testStub()
+ {
+ $data = $this->cloneVar([new ClassStub('stdClass')]);
+ $data = $data[0];
+
+ $this->assertSame('string', $data->getType());
+ $this->assertSame('stdClass', $data->getValue());
+ $this->assertSame('stdClass', (string) $data);
+ }
+
+ public function testHardRefs()
+ {
+ $values = [[]];
+ $values[1] = &$values[0];
+ $values[2][0] = &$values[2];
+
+ $data = $this->cloneVar($values);
+
+ $this->assertSame([], $data[0]->getValue());
+ $this->assertSame([], $data[1]->getValue());
+ $this->assertEquals([$data[2]->getValue()], $data[2]->getValue(true));
+
+ $this->assertSame('array (count=3)', (string) $data);
+ }
+
+ private function cloneVar($value)
+ {
+ $cloner = new VarCloner();
+
+ return $cloner->cloneVar($value);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Cloner/VarClonerTest.php b/vendor/symfony/var-dumper/Tests/Cloner/VarClonerTest.php
new file mode 100755
index 0000000..d3141c6
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Cloner/VarClonerTest.php
@@ -0,0 +1,439 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Cloner;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class VarClonerTest extends TestCase
+{
+ public function testMaxIntBoundary()
+ {
+ $data = [PHP_INT_MAX => 123];
+
+ $cloner = new VarCloner();
+ $clone = $cloner->cloneVar($data);
+
+ $expected = <<<EOTXT
+Symfony\Component\VarDumper\Cloner\Data Object
+(
+ [data:Symfony\Component\VarDumper\Cloner\Data:private] => Array
+ (
+ [0] => Array
+ (
+ [0] => Array
+ (
+ [1] => 1
+ )
+
+ )
+
+ [1] => Array
+ (
+ [%s] => 123
+ )
+
+ )
+
+ [position:Symfony\Component\VarDumper\Cloner\Data:private] => 0
+ [key:Symfony\Component\VarDumper\Cloner\Data:private] => 0
+ [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
+ [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
+ [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
+)
+
+EOTXT;
+ $this->assertSame(sprintf($expected, PHP_INT_MAX), print_r($clone, true));
+ }
+
+ public function testClone()
+ {
+ $json = json_decode('{"1":{"var":"val"},"2":{"var":"val"}}');
+
+ $cloner = new VarCloner();
+ $clone = $cloner->cloneVar($json);
+
+ $expected = <<<EOTXT
+Symfony\Component\VarDumper\Cloner\Data Object
+(
+ [data:Symfony\Component\VarDumper\Cloner\Data:private] => Array
+ (
+ [0] => Array
+ (
+ [0] => Symfony\Component\VarDumper\Cloner\Stub Object
+ (
+ [type] => 4
+ [class] => stdClass
+ [value] =>
+ [cut] => 0
+ [handle] => %i
+ [refCount] => 0
+ [position] => 1
+ [attr] => Array
+ (
+ )
+
+ )
+
+ )
+
+ [1] => Array
+ (
+ [\000+\0001] => Symfony\Component\VarDumper\Cloner\Stub Object
+ (
+ [type] => 4
+ [class] => stdClass
+ [value] =>
+ [cut] => 0
+ [handle] => %i
+ [refCount] => 0
+ [position] => 2
+ [attr] => Array
+ (
+ )
+
+ )
+
+ [\000+\0002] => Symfony\Component\VarDumper\Cloner\Stub Object
+ (
+ [type] => 4
+ [class] => stdClass
+ [value] =>
+ [cut] => 0
+ [handle] => %i
+ [refCount] => 0
+ [position] => 3
+ [attr] => Array
+ (
+ )
+
+ )
+
+ )
+
+ [2] => Array
+ (
+ [\000+\000var] => val
+ )
+
+ [3] => Array
+ (
+ [\000+\000var] => val
+ )
+
+ )
+
+ [position:Symfony\Component\VarDumper\Cloner\Data:private] => 0
+ [key:Symfony\Component\VarDumper\Cloner\Data:private] => 0
+ [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
+ [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
+ [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
+)
+
+EOTXT;
+ $this->assertStringMatchesFormat($expected, print_r($clone, true));
+ }
+
+ public function testLimits()
+ {
+ // Level 0:
+ $data = [
+ // Level 1:
+ [
+ // Level 2:
+ [
+ // Level 3:
+ 'Level 3 Item 0',
+ 'Level 3 Item 1',
+ 'Level 3 Item 2',
+ 'Level 3 Item 3',
+ ],
+ [
+ 'Level 3 Item 4',
+ 'Level 3 Item 5',
+ 'Level 3 Item 6',
+ ],
+ [
+ 'Level 3 Item 7',
+ ],
+ ],
+ [
+ [
+ 'Level 3 Item 8',
+ ],
+ 'Level 2 Item 0',
+ ],
+ [
+ 'Level 2 Item 1',
+ ],
+ 'Level 1 Item 0',
+ [
+ // Test setMaxString:
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ 'SHORT',
+ ],
+ ];
+
+ $cloner = new VarCloner();
+ $cloner->setMinDepth(2);
+ $cloner->setMaxItems(5);
+ $cloner->setMaxString(20);
+ $clone = $cloner->cloneVar($data);
+
+ $expected = <<<EOTXT
+Symfony\Component\VarDumper\Cloner\Data Object
+(
+ [data:Symfony\Component\VarDumper\Cloner\Data:private] => Array
+ (
+ [0] => Array
+ (
+ [0] => Array
+ (
+ [2] => 1
+ )
+
+ )
+
+ [1] => Array
+ (
+ [0] => Array
+ (
+ [2] => 2
+ )
+
+ [1] => Array
+ (
+ [2] => 3
+ )
+
+ [2] => Array
+ (
+ [2] => 4
+ )
+
+ [3] => Level 1 Item 0
+ [4] => Array
+ (
+ [2] => 5
+ )
+
+ )
+
+ [2] => Array
+ (
+ [0] => Array
+ (
+ [2] => 6
+ )
+
+ [1] => Array
+ (
+ [0] => 2
+ [2] => 7
+ )
+
+ [2] => Array
+ (
+ [0] => 1
+ [2] => 0
+ )
+
+ )
+
+ [3] => Array
+ (
+ [0] => Array
+ (
+ [0] => 1
+ [2] => 0
+ )
+
+ [1] => Level 2 Item 0
+ )
+
+ [4] => Array
+ (
+ [0] => Level 2 Item 1
+ )
+
+ [5] => Array
+ (
+ [0] => Symfony\Component\VarDumper\Cloner\Stub Object
+ (
+ [type] => 2
+ [class] => 2
+ [value] => ABCDEFGHIJKLMNOPQRST
+ [cut] => 6
+ [handle] => 0
+ [refCount] => 0
+ [position] => 0
+ [attr] => Array
+ (
+ )
+
+ )
+
+ [1] => SHORT
+ )
+
+ [6] => Array
+ (
+ [0] => Level 3 Item 0
+ [1] => Level 3 Item 1
+ [2] => Level 3 Item 2
+ [3] => Level 3 Item 3
+ )
+
+ [7] => Array
+ (
+ [0] => Level 3 Item 4
+ )
+
+ )
+
+ [position:Symfony\Component\VarDumper\Cloner\Data:private] => 0
+ [key:Symfony\Component\VarDumper\Cloner\Data:private] => 0
+ [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
+ [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
+ [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
+)
+
+EOTXT;
+ $this->assertStringMatchesFormat($expected, print_r($clone, true));
+ }
+
+ public function testJsonCast()
+ {
+ if (2 == ini_get('xdebug.overload_var_dump')) {
+ $this->markTestSkipped('xdebug is active');
+ }
+
+ $data = (array) json_decode('{"1":{}}');
+
+ $cloner = new VarCloner();
+ $clone = $cloner->cloneVar($data);
+
+ $expected = <<<'EOTXT'
+object(Symfony\Component\VarDumper\Cloner\Data)#%i (6) {
+ ["data":"Symfony\Component\VarDumper\Cloner\Data":private]=>
+ array(2) {
+ [0]=>
+ array(1) {
+ [0]=>
+ array(1) {
+ [1]=>
+ int(1)
+ }
+ }
+ [1]=>
+ array(1) {
+ ["1"]=>
+ object(Symfony\Component\VarDumper\Cloner\Stub)#%i (8) {
+ ["type"]=>
+ int(4)
+ ["class"]=>
+ string(8) "stdClass"
+ ["value"]=>
+ NULL
+ ["cut"]=>
+ int(0)
+ ["handle"]=>
+ int(%i)
+ ["refCount"]=>
+ int(0)
+ ["position"]=>
+ int(0)
+ ["attr"]=>
+ array(0) {
+ }
+ }
+ }
+ }
+ ["position":"Symfony\Component\VarDumper\Cloner\Data":private]=>
+ int(0)
+ ["key":"Symfony\Component\VarDumper\Cloner\Data":private]=>
+ int(0)
+ ["maxDepth":"Symfony\Component\VarDumper\Cloner\Data":private]=>
+ int(20)
+ ["maxItemsPerDepth":"Symfony\Component\VarDumper\Cloner\Data":private]=>
+ int(-1)
+ ["useRefHandles":"Symfony\Component\VarDumper\Cloner\Data":private]=>
+ int(-1)
+}
+
+EOTXT;
+ ob_start();
+ var_dump($clone);
+ $this->assertStringMatchesFormat(\PHP_VERSION_ID >= 70200 ? str_replace('"1"', '1', $expected) : $expected, ob_get_clean());
+ }
+
+ public function testCaster()
+ {
+ $cloner = new VarCloner([
+ '*' => function ($obj, $array) {
+ return ['foo' => 123];
+ },
+ __CLASS__ => function ($obj, $array) {
+ ++$array['foo'];
+
+ return $array;
+ },
+ ]);
+ $clone = $cloner->cloneVar($this);
+
+ $expected = <<<EOTXT
+Symfony\Component\VarDumper\Cloner\Data Object
+(
+ [data:Symfony\Component\VarDumper\Cloner\Data:private] => Array
+ (
+ [0] => Array
+ (
+ [0] => Symfony\Component\VarDumper\Cloner\Stub Object
+ (
+ [type] => 4
+ [class] => %s
+ [value] =>
+ [cut] => 0
+ [handle] => %i
+ [refCount] => 0
+ [position] => 1
+ [attr] => Array
+ (
+ [file] => %a%eVarClonerTest.php
+ [line] => 20
+ )
+
+ )
+
+ )
+
+ [1] => Array
+ (
+ [foo] => 124
+ )
+
+ )
+
+ [position:Symfony\Component\VarDumper\Cloner\Data:private] => 0
+ [key:Symfony\Component\VarDumper\Cloner\Data:private] => 0
+ [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
+ [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
+ [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
+)
+
+EOTXT;
+ $this->assertStringMatchesFormat($expected, print_r($clone, true));
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Command/Descriptor/CliDescriptorTest.php b/vendor/symfony/var-dumper/Tests/Command/Descriptor/CliDescriptorTest.php
new file mode 100755
index 0000000..fef5dcd
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Command/Descriptor/CliDescriptorTest.php
@@ -0,0 +1,173 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Command\Descriptor;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Console\Formatter\OutputFormatterStyle;
+use Symfony\Component\Console\Output\BufferedOutput;
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+class CliDescriptorTest extends TestCase
+{
+ private static $timezone;
+ private static $prevTerminalEmulator;
+
+ public static function setUpBeforeClass()
+ {
+ self::$timezone = date_default_timezone_get();
+ date_default_timezone_set('UTC');
+
+ self::$prevTerminalEmulator = getenv('TERMINAL_EMULATOR');
+ putenv('TERMINAL_EMULATOR');
+ }
+
+ public static function tearDownAfterClass()
+ {
+ date_default_timezone_set(self::$timezone);
+ putenv('TERMINAL_EMULATOR'.(self::$prevTerminalEmulator ? '='.self::$prevTerminalEmulator : ''));
+ }
+
+ /**
+ * @dataProvider provideContext
+ */
+ public function testDescribe(array $context, string $expectedOutput, bool $decorated = false)
+ {
+ $output = new BufferedOutput();
+ $output->setDecorated($decorated);
+ $descriptor = new CliDescriptor(new CliDumper(function ($s) {
+ return $s;
+ }));
+
+ $descriptor->describe($output, new Data([[123]]), $context + ['timestamp' => 1544804268.3668], 1);
+
+ $this->assertStringMatchesFormat(trim($expectedOutput), str_replace(PHP_EOL, "\n", trim($output->fetch())));
+ }
+
+ public function provideContext()
+ {
+ yield 'source' => [
+ [
+ 'source' => [
+ 'name' => 'CliDescriptorTest.php',
+ 'line' => 30,
+ 'file' => '/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php',
+ ],
+ ],
+ <<<TXT
+Received from client #1
+-----------------------
+
+ -------- ---------------------------------------------------------------------------------------------------
+ date Fri, 14 Dec 2018 16:17:48 +0000
+ source CliDescriptorTest.php on line 30
+ file /Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php
+ -------- ---------------------------------------------------------------------------------------------------
+TXT
+ ];
+
+ yield 'source full' => [
+ [
+ 'source' => [
+ 'name' => 'CliDescriptorTest.php',
+ 'line' => 30,
+ 'file_relative' => 'src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php',
+ 'file' => '/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php',
+ 'file_link' => 'phpstorm://open?file=/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php&line=30',
+ ],
+ ],
+ method_exists(OutputFormatterStyle::class, 'setHref') ?
+ <<<TXT
+Received from client #1
+-----------------------
+
+ -------- --------------------------------------------------------------------------------
+ date Fri, 14 Dec 2018 16:17:48 +0000
+ source CliDescriptorTest.php on line 30
+ file src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php
+ -------- --------------------------------------------------------------------------------
+
+TXT
+ :
+ <<<TXT
+Received from client #1
+-----------------------
+
+ -------- --------------------------------------------------------------------------------
+ date Fri, 14 Dec 2018 16:17:48 +0000
+ source CliDescriptorTest.php on line 30
+ file src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php
+ -------- --------------------------------------------------------------------------------
+
+Open source in your IDE/browser:
+phpstorm://open?file=/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php&line=30
+TXT
+ ];
+
+ if (method_exists(OutputFormatterStyle::class, 'setHref')) {
+ yield 'source with hyperlink' => [
+ [
+ 'source' => [
+ 'name' => 'CliDescriptorTest.php',
+ 'line' => 30,
+ 'file_relative' => 'src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php',
+ 'file_link' => 'phpstorm://open?file=/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php&line=30',
+ ],
+ ],
+ <<<TXT
+%A
+ source \033]8;;phpstorm://open?file=/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php&line=30\033\CliDescriptorTest.php on line 30\033]8;;\033%A
+%A
+TXT
+ , true,
+ ];
+ }
+
+ yield 'cli' => [
+ [
+ 'cli' => [
+ 'identifier' => 'd8bece1c',
+ 'command_line' => 'bin/phpunit',
+ ],
+ ],
+ <<<TXT
+$ bin/phpunit
+-------------
+
+ ------ ---------------------------------
+ date Fri, 14 Dec 2018 16:17:48 +0000
+ ------ ---------------------------------
+TXT
+ ];
+
+ yield 'request' => [
+ [
+ 'request' => [
+ 'identifier' => 'd8bece1c',
+ 'controller' => new Data([['FooController.php']]),
+ 'method' => 'GET',
+ 'uri' => 'http://localhost/foo',
+ ],
+ ],
+ <<<TXT
+GET http://localhost/foo
+------------------------
+
+ ------------ ---------------------------------
+ date Fri, 14 Dec 2018 16:17:48 +0000
+ controller "FooController.php"
+ ------------ ---------------------------------
+TXT
+ ];
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Command/Descriptor/HtmlDescriptorTest.php b/vendor/symfony/var-dumper/Tests/Command/Descriptor/HtmlDescriptorTest.php
new file mode 100755
index 0000000..f1febf7
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Command/Descriptor/HtmlDescriptorTest.php
@@ -0,0 +1,195 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Command\Descriptor;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Console\Output\BufferedOutput;
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+
+class HtmlDescriptorTest extends TestCase
+{
+ private static $timezone;
+
+ public static function setUpBeforeClass()
+ {
+ self::$timezone = date_default_timezone_get();
+ date_default_timezone_set('UTC');
+ }
+
+ public static function tearDownAfterClass()
+ {
+ date_default_timezone_set(self::$timezone);
+ }
+
+ public function testItOutputsStylesAndScriptsOnFirstDescribeCall()
+ {
+ $output = new BufferedOutput();
+ $dumper = $this->createMock(HtmlDumper::class);
+ $dumper->method('dump')->willReturn('[DUMPED]');
+ $descriptor = new HtmlDescriptor($dumper);
+
+ $descriptor->describe($output, new Data([[123]]), ['timestamp' => 1544804268.3668], 1);
+
+ $this->assertStringMatchesFormat('<style>%A</style><script>%A</script>%A', $output->fetch(), 'styles & scripts are output');
+
+ $descriptor->describe($output, new Data([[123]]), ['timestamp' => 1544804268.3668], 1);
+
+ $this->assertStringNotMatchesFormat('<style>%A</style><script>%A</script>%A', $output->fetch(), 'styles & scripts are output only once');
+ }
+
+ /**
+ * @dataProvider provideContext
+ */
+ public function testDescribe(array $context, string $expectedOutput)
+ {
+ $output = new BufferedOutput();
+ $dumper = $this->createMock(HtmlDumper::class);
+ $dumper->method('dump')->willReturn('[DUMPED]');
+ $descriptor = new HtmlDescriptor($dumper);
+
+ $descriptor->describe($output, new Data([[123]]), $context + ['timestamp' => 1544804268.3668], 1);
+
+ $this->assertStringMatchesFormat(trim($expectedOutput), trim(preg_replace('@<style>.*</style><script>.*</script>@s', '', $output->fetch())));
+ }
+
+ public function provideContext()
+ {
+ yield 'source' => [
+ [
+ 'source' => [
+ 'name' => 'CliDescriptorTest.php',
+ 'line' => 30,
+ 'file' => '/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php',
+ ],
+ ],
+ <<<TXT
+<article data-dedup-id="%s">
+ <header>
+ <div class="row">
+ <h2 class="col">-</h2>
+ <time class="col text-small" title="2018-12-14T16:17:48+00:00" datetime="2018-12-14T16:17:48+00:00">
+ Fri, 14 Dec 2018 16:17:48 +0000
+ </time>
+ </div>
+
+ </header>
+ <section class="body">
+ <p class="text-small">
+ CliDescriptorTest.php on line 30
+ </p>
+ [DUMPED]
+ </section>
+</article>
+TXT
+ ];
+
+ yield 'source full' => [
+ [
+ 'source' => [
+ 'name' => 'CliDescriptorTest.php',
+ 'project_dir' => 'src/Symfony/',
+ 'line' => 30,
+ 'file_relative' => 'src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php',
+ 'file' => '/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php',
+ 'file_link' => 'phpstorm://open?file=/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php&line=30',
+ ],
+ ],
+ <<<TXT
+<article data-dedup-id="%s">
+ <header>
+ <div class="row">
+ <h2 class="col">-</h2>
+ <time class="col text-small" title="2018-12-14T16:17:48+00:00" datetime="2018-12-14T16:17:48+00:00">
+ Fri, 14 Dec 2018 16:17:48 +0000
+ </time>
+ </div>
+ <div class="row">
+ <ul class="tags">
+ <li><span class="badge">project dir</span>src/Symfony/</li>
+ </ul>
+</div>
+ </header>
+ <section class="body">
+ <p class="text-small">
+ <a href="phpstorm://open?file=/Users/ogi/symfony/src/Symfony/Component/VarDumper/Tests/Command/Descriptor/CliDescriptorTest.php&line=30">CliDescriptorTest.php on line 30</a>
+ </p>
+ [DUMPED]
+ </section>
+</article>
+TXT
+ ];
+
+ yield 'cli' => [
+ [
+ 'cli' => [
+ 'identifier' => 'd8bece1c',
+ 'command_line' => 'bin/phpunit',
+ ],
+ ],
+ <<<TXT
+<article data-dedup-id="d8bece1c">
+ <header>
+ <div class="row">
+ <h2 class="col"><code>$ </code>bin/phpunit</h2>
+ <time class="col text-small" title="2018-12-14T16:17:48+00:00" datetime="2018-12-14T16:17:48+00:00">
+ Fri, 14 Dec 2018 16:17:48 +0000
+ </time>
+ </div>
+
+ </header>
+ <section class="body">
+ <p class="text-small">
+
+ </p>
+ [DUMPED]
+ </section>
+</article>
+TXT
+ ];
+
+ yield 'request' => [
+ [
+ 'request' => [
+ 'identifier' => 'd8bece1c',
+ 'controller' => new Data([['FooController.php']]),
+ 'method' => 'GET',
+ 'uri' => 'http://localhost/foo',
+ ],
+ ],
+ <<<TXT
+<article data-dedup-id="d8bece1c">
+ <header>
+ <div class="row">
+ <h2 class="col"><code>GET</code> <a href="http://localhost/foo">http://localhost/foo</a></h2>
+ <time class="col text-small" title="2018-12-14T16:17:48+00:00" datetime="2018-12-14T16:17:48+00:00">
+ Fri, 14 Dec 2018 16:17:48 +0000
+ </time>
+ </div>
+ <div class="row">
+ <ul class="tags">
+ <li><span class="badge">controller</span><span class='dumped-tag'>[DUMPED]</span></li>
+ </ul>
+</div>
+ </header>
+ <section class="body">
+ <p class="text-small">
+
+ </p>
+ [DUMPED]
+ </section>
+</article>
+TXT
+ ];
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Dumper/CliDumperTest.php b/vendor/symfony/var-dumper/Tests/Dumper/CliDumperTest.php
new file mode 100755
index 0000000..fc62380
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Dumper/CliDumperTest.php
@@ -0,0 +1,534 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+use Twig\Environment;
+use Twig\Loader\FilesystemLoader;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class CliDumperTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ public function testGet()
+ {
+ require __DIR__.'/../Fixtures/dumb-var.php';
+
+ $dumper = new CliDumper('php://output');
+ $dumper->setColors(false);
+ $cloner = new VarCloner();
+ $cloner->addCasters([
+ ':stream' => function ($res, $a) {
+ unset($a['uri'], $a['wrapper_data']);
+
+ return $a;
+ },
+ ]);
+ $data = $cloner->cloneVar($var);
+
+ ob_start();
+ $dumper->dump($data);
+ $out = ob_get_clean();
+ $out = preg_replace('/[ \t]+$/m', '', $out);
+ $intMax = PHP_INT_MAX;
+ $res = (int) $var['res'];
+
+ $this->assertStringMatchesFormat(
+ <<<EOTXT
+array:24 [
+ "number" => 1
+ 0 => &1 null
+ "const" => 1.1
+ 1 => true
+ 2 => false
+ 3 => NAN
+ 4 => INF
+ 5 => -INF
+ 6 => {$intMax}
+ "str" => "déjà\\n"
+ 7 => b"""
+ é\\x00test\\t\\n
+ ing
+ """
+ "[]" => []
+ "res" => stream resource {@{$res}
+%A wrapper_type: "plainfile"
+ stream_type: "STDIO"
+ mode: "r"
+ unread_bytes: 0
+ seekable: true
+%A options: []
+ }
+ "obj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d
+ +foo: "foo"
+ +"bar": "bar"
+ }
+ "closure" => Closure(\$a, PDO &\$b = null) {#%d
+ class: "Symfony\Component\VarDumper\Tests\Dumper\CliDumperTest"
+ this: Symfony\Component\VarDumper\Tests\Dumper\CliDumperTest {#%d …}
+ file: "%s%eTests%eFixtures%edumb-var.php"
+ line: "{$var['line']} to {$var['line']}"
+ }
+ "line" => {$var['line']}
+ "nobj" => array:1 [
+ 0 => &3 {#%d}
+ ]
+ "recurs" => &4 array:1 [
+ 0 => &4 array:1 [&4]
+ ]
+ 8 => &1 null
+ "sobj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d}
+ "snobj" => &3 {#%d}
+ "snobj2" => {#%d}
+ "file" => "{$var['file']}"
+ b"bin-key-é" => ""
+]
+
+EOTXT
+ ,
+ $out
+ );
+ }
+
+ /**
+ * @dataProvider provideDumpWithCommaFlagTests
+ */
+ public function testDumpWithCommaFlag($expected, $flags)
+ {
+ $dumper = new CliDumper(null, null, $flags);
+ $dumper->setColors(false);
+ $cloner = new VarCloner();
+
+ $var = [
+ 'array' => ['a', 'b'],
+ 'string' => 'hello',
+ 'multiline string' => "this\nis\na\multiline\nstring",
+ ];
+
+ $dump = $dumper->dump($cloner->cloneVar($var), true);
+
+ $this->assertSame($expected, $dump);
+ }
+
+ public function testDumpWithCommaFlagsAndExceptionCodeExcerpt()
+ {
+ $dumper = new CliDumper(null, null, CliDumper::DUMP_TRAILING_COMMA);
+ $dumper->setColors(false);
+ $cloner = new VarCloner();
+
+ $ex = new \RuntimeException('foo');
+
+ $dump = $dumper->dump($cloner->cloneVar($ex)->withRefHandles(false), true);
+
+ $this->assertStringMatchesFormat(<<<'EOTXT'
+RuntimeException {
+ #message: "foo"
+ #code: 0
+ #file: "%ACliDumperTest.php"
+ #line: %d
+ trace: {
+ %ACliDumperTest.php:%d {
+ ›
+ › $ex = new \RuntimeException('foo');
+ ›
+ }
+ %A
+ }
+}
+
+EOTXT
+ , $dump);
+ }
+
+ public function provideDumpWithCommaFlagTests()
+ {
+ $expected = <<<'EOTXT'
+array:3 [
+ "array" => array:2 [
+ 0 => "a",
+ 1 => "b"
+ ],
+ "string" => "hello",
+ "multiline string" => """
+ this\n
+ is\n
+ a\multiline\n
+ string
+ """
+]
+
+EOTXT;
+
+ yield [$expected, CliDumper::DUMP_COMMA_SEPARATOR];
+
+ $expected = <<<'EOTXT'
+array:3 [
+ "array" => array:2 [
+ 0 => "a",
+ 1 => "b",
+ ],
+ "string" => "hello",
+ "multiline string" => """
+ this\n
+ is\n
+ a\multiline\n
+ string
+ """,
+]
+
+EOTXT;
+
+ yield [$expected, CliDumper::DUMP_TRAILING_COMMA];
+ }
+
+ /**
+ * @requires extension xml
+ */
+ public function testXmlResource()
+ {
+ $var = xml_parser_create();
+
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+xml resource {
+ current_byte_index: %i
+ current_column_number: %i
+ current_line_number: 1
+ error_code: XML_ERROR_NONE
+}
+EOTXT
+ ,
+ $var
+ );
+ }
+
+ public function testJsonCast()
+ {
+ $var = (array) json_decode('{"0":{},"1":null}');
+ foreach ($var as &$v) {
+ }
+ $var[] = &$v;
+ $var[''] = 2;
+
+ if (\PHP_VERSION_ID >= 70200) {
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+array:4 [
+ 0 => {}
+ 1 => &1 null
+ 2 => &1 null
+ "" => 2
+]
+EOTXT
+ ,
+ $var
+ );
+ } else {
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+array:4 [
+ "0" => {}
+ "1" => &1 null
+ 0 => &1 null
+ "" => 2
+]
+EOTXT
+ ,
+ $var
+ );
+ }
+ }
+
+ public function testObjectCast()
+ {
+ $var = (object) [1 => 1];
+ $var->{1} = 2;
+
+ if (\PHP_VERSION_ID >= 70200) {
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+{
+ +"1": 2
+}
+EOTXT
+ ,
+ $var
+ );
+ } else {
+ $this->assertDumpMatchesFormat(
+ <<<'EOTXT'
+{
+ +1: 1
+ +"1": 2
+}
+EOTXT
+ ,
+ $var
+ );
+ }
+ }
+
+ public function testClosedResource()
+ {
+ $var = fopen(__FILE__, 'r');
+ fclose($var);
+
+ $dumper = new CliDumper('php://output');
+ $dumper->setColors(false);
+ $cloner = new VarCloner();
+ $data = $cloner->cloneVar($var);
+
+ ob_start();
+ $dumper->dump($data);
+ $out = ob_get_clean();
+ $res = (int) $var;
+
+ $this->assertStringMatchesFormat(
+ <<<EOTXT
+Closed resource @{$res}
+
+EOTXT
+ ,
+ $out
+ );
+ }
+
+ public function testFlags()
+ {
+ putenv('DUMP_LIGHT_ARRAY=1');
+ putenv('DUMP_STRING_LENGTH=1');
+
+ $var = [
+ range(1, 3),
+ ['foo', 2 => 'bar'],
+ ];
+
+ $this->assertDumpEquals(
+ <<<EOTXT
+[
+ [
+ 1
+ 2
+ 3
+ ]
+ [
+ 0 => (3) "foo"
+ 2 => (3) "bar"
+ ]
+]
+EOTXT
+ ,
+ $var
+ );
+
+ putenv('DUMP_LIGHT_ARRAY=');
+ putenv('DUMP_STRING_LENGTH=');
+ }
+
+ /**
+ * @requires function Twig\Template::getSourceContext
+ */
+ public function testThrowingCaster()
+ {
+ $out = fopen('php://memory', 'r+b');
+
+ require_once __DIR__.'/../Fixtures/Twig.php';
+ $twig = new \__TwigTemplate_VarDumperFixture_u75a09(new Environment(new FilesystemLoader()));
+
+ $dumper = new CliDumper();
+ $dumper->setColors(false);
+ $cloner = new VarCloner();
+ $cloner->addCasters([
+ ':stream' => function ($res, $a) {
+ unset($a['wrapper_data']);
+
+ return $a;
+ },
+ ]);
+ $cloner->addCasters([
+ ':stream' => eval('return function () use ($twig) {
+ try {
+ $twig->render([]);
+ } catch (\Twig\Error\RuntimeError $e) {
+ throw $e->getPrevious();
+ }
+ };'),
+ ]);
+ $ref = (int) $out;
+
+ $data = $cloner->cloneVar($out);
+ $dumper->dump($data, $out);
+ $out = stream_get_contents($out, -1, 0);
+
+ $this->assertStringMatchesFormat(
+ <<<EOTXT
+stream resource {@{$ref}
+ ⚠: Symfony\Component\VarDumper\Exception\ThrowingCasterException {#%d
+ #message: "Unexpected Exception thrown from a caster: Foobar"
+ trace: {
+ %sTwig.php:2 {
+ › foo bar
+ › twig source
+ ›
+ }
+ %s%eTemplate.php:%d { …}
+ %s%eTemplate.php:%d { …}
+ %s%eTemplate.php:%d { …}
+ %s%eTests%eDumper%eCliDumperTest.php:%d { …}
+%A }
+ }
+%Awrapper_type: "PHP"
+ stream_type: "MEMORY"
+ mode: "%s+b"
+ unread_bytes: 0
+ seekable: true
+ uri: "php://memory"
+%Aoptions: []
+}
+
+EOTXT
+ ,
+ $out
+ );
+ }
+
+ public function testRefsInProperties()
+ {
+ $var = (object) ['foo' => 'foo'];
+ $var->bar = &$var->foo;
+
+ $dumper = new CliDumper();
+ $dumper->setColors(false);
+ $cloner = new VarCloner();
+
+ $data = $cloner->cloneVar($var);
+ $out = $dumper->dump($data, true);
+
+ $this->assertStringMatchesFormat(
+ <<<EOTXT
+{#%d
+ +"foo": &1 "foo"
+ +"bar": &1 "foo"
+}
+
+EOTXT
+ ,
+ $out
+ );
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
+ public function testSpecialVars56()
+ {
+ $var = $this->getSpecialVars();
+
+ $this->assertDumpEquals(
+ <<<'EOTXT'
+array:3 [
+ 0 => array:1 [
+ 0 => &1 array:1 [
+ 0 => &1 array:1 [&1]
+ ]
+ ]
+ 1 => array:1 [
+ "GLOBALS" => &2 array:1 [
+ "GLOBALS" => &2 array:1 [&2]
+ ]
+ ]
+ 2 => &2 array:1 [&2]
+]
+EOTXT
+ ,
+ $var
+ );
+ }
+
+ /**
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
+ public function testGlobals()
+ {
+ $var = $this->getSpecialVars();
+ unset($var[0]);
+ $out = '';
+
+ $dumper = new CliDumper(function ($line, $depth) use (&$out) {
+ if ($depth >= 0) {
+ $out .= str_repeat(' ', $depth).$line."\n";
+ }
+ });
+ $dumper->setColors(false);
+ $cloner = new VarCloner();
+
+ $data = $cloner->cloneVar($var);
+ $dumper->dump($data);
+
+ $this->assertSame(
+ <<<'EOTXT'
+array:2 [
+ 1 => array:1 [
+ "GLOBALS" => &1 array:1 [
+ "GLOBALS" => &1 array:1 [&1]
+ ]
+ ]
+ 2 => &1 array:1 [&1]
+]
+
+EOTXT
+ ,
+ $out
+ );
+ }
+
+ public function testIncompleteClass()
+ {
+ $unserializeCallbackHandler = ini_set('unserialize_callback_func', null);
+ $var = unserialize('O:8:"Foo\Buzz":0:{}');
+ ini_set('unserialize_callback_func', $unserializeCallbackHandler);
+
+ $this->assertDumpMatchesFormat(
+ <<<EOTXT
+__PHP_Incomplete_Class(Foo\Buzz) {}
+EOTXT
+ ,
+ $var
+ );
+ }
+
+ private function getSpecialVars()
+ {
+ foreach (array_keys($GLOBALS) as $var) {
+ if ('GLOBALS' !== $var) {
+ unset($GLOBALS[$var]);
+ }
+ }
+
+ $var = function &() {
+ $var = [];
+ $var[] = &$var;
+
+ return $var;
+ };
+
+ return [$var(), $GLOBALS, &$GLOBALS];
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Dumper/FunctionsTest.php b/vendor/symfony/var-dumper/Tests/Dumper/FunctionsTest.php
new file mode 100755
index 0000000..0bd596c
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Dumper/FunctionsTest.php
@@ -0,0 +1,57 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+use Symfony\Component\VarDumper\VarDumper;
+
+class FunctionsTest extends TestCase
+{
+ public function testDumpReturnsFirstArg()
+ {
+ $this->setupVarDumper();
+
+ $var1 = 'a';
+
+ ob_start();
+ $return = dump($var1);
+ $out = ob_get_clean();
+
+ $this->assertEquals($var1, $return);
+ }
+
+ public function testDumpReturnsAllArgsInArray()
+ {
+ $this->setupVarDumper();
+
+ $var1 = 'a';
+ $var2 = 'b';
+ $var3 = 'c';
+
+ ob_start();
+ $return = dump($var1, $var2, $var3);
+ $out = ob_get_clean();
+
+ $this->assertEquals([$var1, $var2, $var3], $return);
+ }
+
+ protected function setupVarDumper()
+ {
+ $cloner = new VarCloner();
+ $dumper = new CliDumper('php://output');
+ VarDumper::setHandler(function ($var) use ($cloner, $dumper) {
+ $dumper->dump($cloner->cloneVar($var));
+ });
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Dumper/HtmlDumperTest.php b/vendor/symfony/var-dumper/Tests/Dumper/HtmlDumperTest.php
new file mode 100755
index 0000000..ae4ee8e
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Dumper/HtmlDumperTest.php
@@ -0,0 +1,163 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class HtmlDumperTest extends TestCase
+{
+ public function testGet()
+ {
+ if (ini_get('xdebug.file_link_format') || get_cfg_var('xdebug.file_link_format')) {
+ $this->markTestSkipped('A custom file_link_format is defined.');
+ }
+
+ require __DIR__.'/../Fixtures/dumb-var.php';
+
+ $dumper = new HtmlDumper('php://output');
+ $dumper->setDumpHeader('<foo></foo>');
+ $dumper->setDumpBoundaries('<bar>', '</bar>');
+ $cloner = new VarCloner();
+ $cloner->addCasters([
+ ':stream' => function ($res, $a) {
+ unset($a['uri'], $a['wrapper_data']);
+
+ return $a;
+ },
+ ]);
+ $data = $cloner->cloneVar($var);
+
+ ob_start();
+ $dumper->dump($data);
+ $out = ob_get_clean();
+ $out = preg_replace('/[ \t]+$/m', '', $out);
+ $var['file'] = htmlspecialchars($var['file'], ENT_QUOTES, 'UTF-8');
+ $intMax = PHP_INT_MAX;
+ preg_match('/sf-dump-\d+/', $out, $dumpId);
+ $dumpId = $dumpId[0];
+ $res = (int) $var['res'];
+
+ $this->assertStringMatchesFormat(
+ <<<EOTXT
+<foo></foo><bar><span class=sf-dump-note>array:24</span> [<samp>
+ "<span class=sf-dump-key>number</span>" => <span class=sf-dump-num>1</span>
+ <span class=sf-dump-key>0</span> => <a class=sf-dump-ref href=#{$dumpId}-ref01 title="2 occurrences">&1</a> <span class=sf-dump-const>null</span>
+ "<span class=sf-dump-key>const</span>" => <span class=sf-dump-num>1.1</span>
+ <span class=sf-dump-key>1</span> => <span class=sf-dump-const>true</span>
+ <span class=sf-dump-key>2</span> => <span class=sf-dump-const>false</span>
+ <span class=sf-dump-key>3</span> => <span class=sf-dump-num>NAN</span>
+ <span class=sf-dump-key>4</span> => <span class=sf-dump-num>INF</span>
+ <span class=sf-dump-key>5</span> => <span class=sf-dump-num>-INF</span>
+ <span class=sf-dump-key>6</span> => <span class=sf-dump-num>{$intMax}</span>
+ "<span class=sf-dump-key>str</span>" => "<span class=sf-dump-str title="5 characters">d&%s;j&%s;<span class="sf-dump-default sf-dump-ns">\\n</span></span>"
+ <span class=sf-dump-key>7</span> => b"""
+ <span class=sf-dump-str title="11 binary or non-UTF-8 characters">é<span class="sf-dump-default">\\x00</span>test<span class="sf-dump-default">\\t</span><span class="sf-dump-default sf-dump-ns">\\n</span></span>
+ <span class=sf-dump-str title="11 binary or non-UTF-8 characters">ing</span>
+ """
+ "<span class=sf-dump-key>[]</span>" => []
+ "<span class=sf-dump-key>res</span>" => <span class=sf-dump-note>stream resource</span> <a class=sf-dump-ref>@{$res}</a><samp>
+%A <span class=sf-dump-meta>wrapper_type</span>: "<span class=sf-dump-str title="9 characters">plainfile</span>"
+ <span class=sf-dump-meta>stream_type</span>: "<span class=sf-dump-str title="5 characters">STDIO</span>"
+ <span class=sf-dump-meta>mode</span>: "<span class=sf-dump-str>r</span>"
+ <span class=sf-dump-meta>unread_bytes</span>: <span class=sf-dump-num>0</span>
+ <span class=sf-dump-meta>seekable</span>: <span class=sf-dump-const>true</span>
+%A <span class=sf-dump-meta>options</span>: []
+ </samp>}
+ "<span class=sf-dump-key>obj</span>" => <abbr title="Symfony\Component\VarDumper\Tests\Fixture\DumbFoo" class=sf-dump-note>DumbFoo</abbr> {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="2 occurrences">#%d</a><samp id={$dumpId}-ref2%d>
+ +<span class=sf-dump-public title="Public property">foo</span>: "<span class=sf-dump-str title="3 characters">foo</span>"
+ +"<span class=sf-dump-public title="Runtime added dynamic property">bar</span>": "<span class=sf-dump-str title="3 characters">bar</span>"
+ </samp>}
+ "<span class=sf-dump-key>closure</span>" => <span class=sf-dump-note>Closure(\$a, PDO &\$b = null)</span> {<a class=sf-dump-ref>#%d</a><samp>
+ <span class=sf-dump-meta>class</span>: "<span class=sf-dump-str title="Symfony\Component\VarDumper\Tests\Dumper\HtmlDumperTest
+55 characters"><span class="sf-dump-ellipsis sf-dump-ellipsis-class">Symfony\Component\VarDumper\Tests\Dumper</span><span class=sf-dump-ellipsis>\</span>HtmlDumperTest</span>"
+ <span class=sf-dump-meta>this</span>: <abbr title="Symfony\Component\VarDumper\Tests\Dumper\HtmlDumperTest" class=sf-dump-note>HtmlDumperTest</abbr> {<a class=sf-dump-ref>#%d</a> &%s;}
+ <span class=sf-dump-meta>file</span>: "<span class=sf-dump-str title="{$var['file']}
+%d characters"><span class="sf-dump-ellipsis sf-dump-ellipsis-path">%s%eVarDumper</span><span class=sf-dump-ellipsis>%e</span>Tests%eFixtures%edumb-var.php</span>"
+ <span class=sf-dump-meta>line</span>: "<span class=sf-dump-str title="%d characters">{$var['line']} to {$var['line']}</span>"
+ </samp>}
+ "<span class=sf-dump-key>line</span>" => <span class=sf-dump-num>{$var['line']}</span>
+ "<span class=sf-dump-key>nobj</span>" => <span class=sf-dump-note>array:1</span> [<samp>
+ <span class=sf-dump-index>0</span> => <a class=sf-dump-ref href=#{$dumpId}-ref03 title="2 occurrences">&3</a> {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="3 occurrences">#%d</a>}
+ </samp>]
+ "<span class=sf-dump-key>recurs</span>" => <a class=sf-dump-ref href=#{$dumpId}-ref04 title="2 occurrences">&4</a> <span class=sf-dump-note>array:1</span> [<samp id={$dumpId}-ref04>
+ <span class=sf-dump-index>0</span> => <a class=sf-dump-ref href=#{$dumpId}-ref04 title="2 occurrences">&4</a> <span class=sf-dump-note>array:1</span> [<a class=sf-dump-ref href=#{$dumpId}-ref04 title="2 occurrences">&4</a>]
+ </samp>]
+ <span class=sf-dump-key>8</span> => <a class=sf-dump-ref href=#{$dumpId}-ref01 title="2 occurrences">&1</a> <span class=sf-dump-const>null</span>
+ "<span class=sf-dump-key>sobj</span>" => <abbr title="Symfony\Component\VarDumper\Tests\Fixture\DumbFoo" class=sf-dump-note>DumbFoo</abbr> {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="2 occurrences">#%d</a>}
+ "<span class=sf-dump-key>snobj</span>" => <a class=sf-dump-ref href=#{$dumpId}-ref03 title="2 occurrences">&3</a> {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="3 occurrences">#%d</a>}
+ "<span class=sf-dump-key>snobj2</span>" => {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="3 occurrences">#%d</a>}
+ "<span class=sf-dump-key>file</span>" => "<span class=sf-dump-str title="%d characters">{$var['file']}</span>"
+ b"<span class=sf-dump-key>bin-key-&%s;</span>" => ""
+</samp>]
+</bar>
+
+EOTXT
+ ,
+
+ $out
+ );
+ }
+
+ public function testCharset()
+ {
+ $var = mb_convert_encoding('Словарь', 'CP1251', 'UTF-8');
+
+ $dumper = new HtmlDumper('php://output', 'CP1251');
+ $dumper->setDumpHeader('<foo></foo>');
+ $dumper->setDumpBoundaries('<bar>', '</bar>');
+ $cloner = new VarCloner();
+
+ $data = $cloner->cloneVar($var);
+ $out = $dumper->dump($data, true);
+
+ $this->assertStringMatchesFormat(
+ <<<'EOTXT'
+<foo></foo><bar>b"<span class=sf-dump-str title="7 binary or non-UTF-8 characters">Словарь</span>"
+</bar>
+
+EOTXT
+ ,
+ $out
+ );
+ }
+
+ public function testAppend()
+ {
+ $out = fopen('php://memory', 'r+b');
+
+ $dumper = new HtmlDumper();
+ $dumper->setDumpHeader('<foo></foo>');
+ $dumper->setDumpBoundaries('<bar>', '</bar>');
+ $cloner = new VarCloner();
+
+ $dumper->dump($cloner->cloneVar(123), $out);
+ $dumper->dump($cloner->cloneVar(456), $out);
+
+ $out = stream_get_contents($out, -1, 0);
+
+ $this->assertSame(<<<'EOTXT'
+<foo></foo><bar><span class=sf-dump-num>123</span>
+</bar>
+<bar><span class=sf-dump-num>456</span>
+</bar>
+
+EOTXT
+ ,
+ $out
+ );
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Dumper/ServerDumperTest.php b/vendor/symfony/var-dumper/Tests/Dumper/ServerDumperTest.php
new file mode 100755
index 0000000..b4bef49
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Dumper/ServerDumperTest.php
@@ -0,0 +1,95 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Dumper;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\PhpProcess;
+use Symfony\Component\Process\Process;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
+use Symfony\Component\VarDumper\Dumper\DataDumperInterface;
+use Symfony\Component\VarDumper\Dumper\ServerDumper;
+
+class ServerDumperTest extends TestCase
+{
+ private const VAR_DUMPER_SERVER = 'tcp://127.0.0.1:9913';
+
+ public function testDumpForwardsToWrappedDumperWhenServerIsUnavailable()
+ {
+ $wrappedDumper = $this->getMockBuilder(DataDumperInterface::class)->getMock();
+
+ $dumper = new ServerDumper(self::VAR_DUMPER_SERVER, $wrappedDumper);
+
+ $cloner = new VarCloner();
+ $data = $cloner->cloneVar('foo');
+
+ $wrappedDumper->expects($this->once())->method('dump')->with($data);
+
+ $dumper->dump($data);
+ }
+
+ public function testDump()
+ {
+ $wrappedDumper = $this->getMockBuilder(DataDumperInterface::class)->getMock();
+ $wrappedDumper->expects($this->never())->method('dump'); // test wrapped dumper is not used
+
+ $cloner = new VarCloner();
+ $data = $cloner->cloneVar('foo');
+ $dumper = new ServerDumper(self::VAR_DUMPER_SERVER, $wrappedDumper, [
+ 'foo_provider' => new class() implements ContextProviderInterface {
+ public function getContext(): ?array
+ {
+ return ['foo'];
+ }
+ },
+ ]);
+
+ $dumped = null;
+ $process = $this->getServerProcess();
+ $process->start(function ($type, $buffer) use ($process, &$dumped, $dumper, $data) {
+ if (Process::ERR === $type) {
+ $process->stop();
+ $this->fail();
+ } elseif ("READY\n" === $buffer) {
+ $dumper->dump($data);
+ } else {
+ $dumped .= $buffer;
+ }
+ });
+
+ $process->wait();
+
+ $this->assertTrue($process->isSuccessful());
+ $this->assertStringMatchesFormat(<<<'DUMP'
+(3) "foo"
+[
+ "timestamp" => %d.%d
+ "foo_provider" => [
+ (3) "foo"
+ ]
+]
+%d
+DUMP
+ , $dumped);
+ }
+
+ private function getServerProcess(): Process
+ {
+ $process = new PhpProcess(file_get_contents(__DIR__.'/../Fixtures/dump_server.php'), null, [
+ 'COMPONENT_ROOT' => __DIR__.'/../../',
+ 'VAR_DUMPER_SERVER' => self::VAR_DUMPER_SERVER,
+ ]);
+ $process->inheritEnvironmentVariables(true);
+
+ return $process->setTimeout(9);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/FooInterface.php b/vendor/symfony/var-dumper/Tests/Fixtures/FooInterface.php
new file mode 100755
index 0000000..172958b
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Fixtures/FooInterface.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Symfony\Component\VarDumper\Tests\Fixtures;
+
+interface FooInterface
+{
+ /**
+ * Hello.
+ */
+ public function foo(?\stdClass $a, \stdClass $b = null);
+}
diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/GeneratorDemo.php b/vendor/symfony/var-dumper/Tests/Fixtures/GeneratorDemo.php
new file mode 100755
index 0000000..e2d24c8
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Fixtures/GeneratorDemo.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Symfony\Component\VarDumper\Tests\Fixtures;
+
+class GeneratorDemo
+{
+ public static function foo()
+ {
+ yield 1;
+ }
+
+ public function baz()
+ {
+ yield from bar();
+ }
+}
+
+function bar()
+{
+ yield from GeneratorDemo::foo();
+}
diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/NotLoadableClass.php b/vendor/symfony/var-dumper/Tests/Fixtures/NotLoadableClass.php
new file mode 100755
index 0000000..d8e0cb3
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Fixtures/NotLoadableClass.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Symfony\Component\VarDumper\Tests\Fixtures;
+
+class NotLoadableClass extends NotLoadableClass
+{
+}
diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/Twig.php b/vendor/symfony/var-dumper/Tests/Fixtures/Twig.php
new file mode 100755
index 0000000..8b84d82
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Fixtures/Twig.php
@@ -0,0 +1,38 @@
+<?php
+
+/* foo.twig */
+class __TwigTemplate_VarDumperFixture_u75a09 extends Twig\Template
+{
+ private $path;
+
+ public function __construct(Twig\Environment $env = null, $path = null)
+ {
+ if (null !== $env) {
+ parent::__construct($env);
+ }
+ $this->parent = false;
+ $this->blocks = [];
+ $this->path = $path;
+ }
+
+ protected function doDisplay(array $context, array $blocks = [])
+ {
+ // line 2
+ throw new \Exception('Foobar');
+ }
+
+ public function getTemplateName()
+ {
+ return 'foo.twig';
+ }
+
+ public function getDebugInfo()
+ {
+ return [20 => 1, 21 => 2];
+ }
+
+ public function getSourceContext()
+ {
+ return new Twig\Source(" foo bar\n twig source\n\n", 'foo.twig', $this->path ?: __FILE__);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/dumb-var.php b/vendor/symfony/var-dumper/Tests/Fixtures/dumb-var.php
new file mode 100755
index 0000000..dcce237
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Fixtures/dumb-var.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Symfony\Component\VarDumper\Tests\Fixture;
+
+if (!class_exists('Symfony\Component\VarDumper\Tests\Fixture\DumbFoo')) {
+ class DumbFoo
+ {
+ public $foo = 'foo';
+ }
+}
+
+$foo = new DumbFoo();
+$foo->bar = 'bar';
+
+$g = fopen(__FILE__, 'r');
+
+$var = [
+ 'number' => 1, null,
+ 'const' => 1.1, true, false, NAN, INF, -INF, PHP_INT_MAX,
+ 'str' => "déjà\n", "\xE9\x00test\t\ning",
+ '[]' => [],
+ 'res' => $g,
+ 'obj' => $foo,
+ 'closure' => function ($a, \PDO &$b = null) {},
+ 'line' => __LINE__ - 1,
+ 'nobj' => [(object) []],
+];
+
+$r = [];
+$r[] = &$r;
+
+$var['recurs'] = &$r;
+$var[] = &$var[0];
+$var['sobj'] = $var['obj'];
+$var['snobj'] = &$var['nobj'][0];
+$var['snobj2'] = $var['nobj'][0];
+$var['file'] = __FILE__;
+$var["bin-key-\xE9"] = '';
+
+unset($g, $r);
diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/dump_server.php b/vendor/symfony/var-dumper/Tests/Fixtures/dump_server.php
new file mode 100755
index 0000000..ed8bbfb
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Fixtures/dump_server.php
@@ -0,0 +1,38 @@
+<?php
+
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+use Symfony\Component\VarDumper\Server\DumpServer;
+use Symfony\Component\VarDumper\VarDumper;
+
+$componentRoot = $_SERVER['COMPONENT_ROOT'];
+
+if (!is_file($file = $componentRoot.'/vendor/autoload.php')) {
+ $file = $componentRoot.'/../../../../vendor/autoload.php';
+}
+
+require $file;
+
+$cloner = new VarCloner();
+$cloner->setMaxItems(-1);
+
+$dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_STRING_LENGTH);
+$dumper->setColors(false);
+
+VarDumper::setHandler(function ($var) use ($cloner, $dumper) {
+ $data = $cloner->cloneVar($var)->withRefHandles(false);
+ $dumper->dump($data);
+});
+
+$server = new DumpServer(getenv('VAR_DUMPER_SERVER'));
+
+$server->start();
+
+echo "READY\n";
+
+$server->listen(function (Data $data, array $context, $clientId) {
+ dump((string) $data, $context, $clientId);
+
+ exit(0);
+});
diff --git a/vendor/symfony/var-dumper/Tests/Fixtures/xml_reader.xml b/vendor/symfony/var-dumper/Tests/Fixtures/xml_reader.xml
new file mode 100755
index 0000000..740c399
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Fixtures/xml_reader.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<foo>
+ <bar></bar>
+ <bar />
+ <bar>With text</bar>
+ <bar foo="bar" baz="fubar"></bar>
+ <bar xmlns:baz="http://symfony.com">
+ <baz:baz></baz:baz>
+ </bar>
+</foo>
diff --git a/vendor/symfony/var-dumper/Tests/Server/ConnectionTest.php b/vendor/symfony/var-dumper/Tests/Server/ConnectionTest.php
new file mode 100755
index 0000000..21902b5
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Server/ConnectionTest.php
@@ -0,0 +1,88 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Server;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\PhpProcess;
+use Symfony\Component\Process\Process;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
+use Symfony\Component\VarDumper\Server\Connection;
+
+class ConnectionTest extends TestCase
+{
+ private const VAR_DUMPER_SERVER = 'tcp://127.0.0.1:9913';
+
+ public function testDump()
+ {
+ $cloner = new VarCloner();
+ $data = $cloner->cloneVar('foo');
+ $connection = new Connection(self::VAR_DUMPER_SERVER, [
+ 'foo_provider' => new class() implements ContextProviderInterface {
+ public function getContext(): ?array
+ {
+ return ['foo'];
+ }
+ },
+ ]);
+
+ $dumped = null;
+ $process = $this->getServerProcess();
+ $process->start(function ($type, $buffer) use ($process, &$dumped, $connection, $data) {
+ if (Process::ERR === $type) {
+ $process->stop();
+ $this->fail();
+ } elseif ("READY\n" === $buffer) {
+ $connection->write($data);
+ } else {
+ $dumped .= $buffer;
+ }
+ });
+
+ $process->wait();
+
+ $this->assertTrue($process->isSuccessful());
+ $this->assertStringMatchesFormat(<<<'DUMP'
+(3) "foo"
+[
+ "timestamp" => %d.%d
+ "foo_provider" => [
+ (3) "foo"
+ ]
+]
+%d
+
+DUMP
+ , $dumped);
+ }
+
+ public function testNoServer()
+ {
+ $cloner = new VarCloner();
+ $data = $cloner->cloneVar('foo');
+ $connection = new Connection(self::VAR_DUMPER_SERVER);
+ $start = microtime(true);
+ $this->assertFalse($connection->write($data));
+ $this->assertLessThan(1, microtime(true) - $start);
+ }
+
+ private function getServerProcess(): Process
+ {
+ $process = new PhpProcess(file_get_contents(__DIR__.'/../Fixtures/dump_server.php'), null, [
+ 'COMPONENT_ROOT' => __DIR__.'/../../',
+ 'VAR_DUMPER_SERVER' => self::VAR_DUMPER_SERVER,
+ ]);
+ $process->inheritEnvironmentVariables(true);
+
+ return $process->setTimeout(9);
+ }
+}
diff --git a/vendor/symfony/var-dumper/Tests/Test/VarDumperTestTraitTest.php b/vendor/symfony/var-dumper/Tests/Test/VarDumperTestTraitTest.php
new file mode 100755
index 0000000..a4d489c
--- /dev/null
+++ b/vendor/symfony/var-dumper/Tests/Test/VarDumperTestTraitTest.php
@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Tests\Test;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+
+class VarDumperTestTraitTest extends TestCase
+{
+ use VarDumperTestTrait;
+
+ public function testItComparesLargeData()
+ {
+ $howMany = 700;
+ $data = array_fill_keys(range(0, $howMany), ['a', 'b', 'c', 'd']);
+
+ $expected = sprintf("array:%d [\n", $howMany + 1);
+ for ($i = 0; $i <= $howMany; ++$i) {
+ $expected .= <<<EODUMP
+ $i => array:4 [
+ 0 => "a"
+ 1 => "b"
+ 2 => "c"
+ 3 => "d"
+ ]\n
+EODUMP;
+ }
+ $expected .= "]\n";
+
+ $this->assertDumpEquals($expected, $data);
+ }
+
+ public function testAllowsNonScalarExpectation()
+ {
+ $this->assertDumpEquals(new \ArrayObject(['bim' => 'bam']), new \ArrayObject(['bim' => 'bam']));
+ }
+}
diff --git a/vendor/symfony/var-dumper/VarDumper.php b/vendor/symfony/var-dumper/VarDumper.php
new file mode 100755
index 0000000..009f662
--- /dev/null
+++ b/vendor/symfony/var-dumper/VarDumper.php
@@ -0,0 +1,56 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper;
+
+use Symfony\Component\VarDumper\Caster\ReflectionCaster;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+
+// Load the global dump() function
+require_once __DIR__.'/Resources/functions/dump.php';
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class VarDumper
+{
+ private static $handler;
+
+ public static function dump($var)
+ {
+ if (null === self::$handler) {
+ $cloner = new VarCloner();
+ $cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
+
+ if (isset($_SERVER['VAR_DUMPER_FORMAT'])) {
+ $dumper = 'html' === $_SERVER['VAR_DUMPER_FORMAT'] ? new HtmlDumper() : new CliDumper();
+ } else {
+ $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg']) ? new CliDumper() : new HtmlDumper();
+ }
+
+ self::$handler = function ($var) use ($cloner, $dumper) {
+ $dumper->dump($cloner->cloneVar($var));
+ };
+ }
+
+ return (self::$handler)($var);
+ }
+
+ public static function setHandler(callable $callable = null)
+ {
+ $prevHandler = self::$handler;
+ self::$handler = $callable;
+
+ return $prevHandler;
+ }
+}
diff --git a/vendor/symfony/var-dumper/composer.json b/vendor/symfony/var-dumper/composer.json
new file mode 100755
index 0000000..b0c0273
--- /dev/null
+++ b/vendor/symfony/var-dumper/composer.json
@@ -0,0 +1,54 @@
+{
+ "name": "symfony/var-dumper",
+ "type": "library",
+ "description": "Symfony mechanism for exploring and dumping PHP variables",
+ "keywords": ["dump", "debug"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^7.1.3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php72": "~1.5"
+ },
+ "require-dev": {
+ "ext-iconv": "*",
+ "symfony/console": "~3.4|~4.0",
+ "symfony/process": "~3.4|~4.0",
+ "twig/twig": "~1.34|~2.4"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
+ "symfony/console": "<3.4"
+ },
+ "suggest": {
+ "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
+ "ext-intl": "To show region name in time zone dump",
+ "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
+ },
+ "autoload": {
+ "files": [ "Resources/functions/dump.php" ],
+ "psr-4": { "Symfony\\Component\\VarDumper\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "bin": [
+ "Resources/bin/var-dump-server"
+ ],
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.3-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/var-dumper/phpunit.xml.dist b/vendor/symfony/var-dumper/phpunit.xml.dist
new file mode 100755
index 0000000..3243fcd
--- /dev/null
+++ b/vendor/symfony/var-dumper/phpunit.xml.dist
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
+ backupGlobals="false"
+ colors="true"
+ bootstrap="vendor/autoload.php"
+ failOnRisky="true"
+ failOnWarning="true"
+>
+ <php>
+ <ini name="error_reporting" value="-1" />
+ <env name="DUMP_LIGHT_ARRAY" value="" />
+ <env name="DUMP_STRING_LENGTH" value="" />
+ </php>
+
+ <testsuites>
+ <testsuite name="Symfony VarDumper Component Test Suite">
+ <directory>./Tests/</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>./</directory>
+ <exclude>
+ <directory>./Resources</directory>
+ <directory>./Tests</directory>
+ <directory>./vendor</directory>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/vendor/topthink/framework/.gitignore b/vendor/topthink/framework/.gitignore
new file mode 100755
index 0000000..b2fd3b9
--- /dev/null
+++ b/vendor/topthink/framework/.gitignore
@@ -0,0 +1,10 @@
+/vendor
+composer.phar
+composer.lock
+.DS_Store
+Thumbs.db
+/.idea
+/.vscode
+/.settings
+/.buildpath
+/.project
\ No newline at end of file
diff --git a/vendor/topthink/framework/.travis.yml b/vendor/topthink/framework/.travis.yml
new file mode 100755
index 0000000..abaa271
--- /dev/null
+++ b/vendor/topthink/framework/.travis.yml
@@ -0,0 +1,35 @@
+dist: xenial
+language: php
+
+matrix:
+ fast_finish: true
+ include:
+ - php: 7.2
+ - php: 7.3
+ - php: 8.0
+
+cache:
+ directories:
+ - $HOME/.composer/cache
+
+services:
+ - memcached
+ - redis-server
+ - mysql
+
+before_install:
+ - echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
+ - echo 'xdebug.mode = coverage' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
+ - printf "\n" | pecl install -f redis
+ - travis_retry composer self-update
+ - mysql -e 'CREATE DATABASE test;'
+
+install:
+ - travis_retry composer update --prefer-dist --no-interaction --prefer-stable --no-suggest
+
+script:
+ - vendor/bin/phpunit --coverage-clover build/logs/coverage.xml
+
+after_script:
+ - travis_retry wget https://scrutinizer-ci.com/ocular.phar
+ - php ocular.phar code-coverage:upload --format=php-clover build/logs/coverage.xml
diff --git a/vendor/topthink/framework/CONTRIBUTING.md b/vendor/topthink/framework/CONTRIBUTING.md
new file mode 100755
index 0000000..efa3ad9
--- /dev/null
+++ b/vendor/topthink/framework/CONTRIBUTING.md
@@ -0,0 +1,119 @@
+如何贡献我的源代码
+===
+
+此文档介绍了 ThinkPHP 团队的组成以及运转机制,您提交的代码将给 ThinkPHP 项目带来什么好处,以及如何才能加入我们的行列。
+
+## 通过 Github 贡献代码
+
+ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。
+
+参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请并。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。
+
+我们希望你贡献的代码符合:
+
+* ThinkPHP 的编码规范
+* 适当的注释,能让其他人读懂
+* 遵循 Apache2 开源协议
+
+**如果想要了解更多细节或有任何疑问,请继续阅读下面的内容**
+
+### 注意事项
+
+* 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141);
+* 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144);
+* 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。
+* 系统会自动在 PHP 7.1 ~ 7.3 上测试修改,请确保你的修改符合 PHP 7.1 ~ 7.3 的语法规范;
+* 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests);
+
+## GitHub Issue
+
+GitHub 提供了 Issue 功能,该功能可以用于:
+
+* 提出 bug
+* 提出功能改进
+* 反馈使用体验
+
+该功能不应该用于:
+
+ * 提出修改意见(涉及代码署名和修订追溯问题)
+ * 不友善的言论
+
+## 快速修改
+
+**GitHub 提供了快速编辑文件的功能**
+
+1. 登录 GitHub 帐号;
+2. 浏览项目文件,找到要进行修改的文件;
+3. 点击右上角铅笔图标进行修改;
+4. 填写 `Commit changes` 相关内容(Title 必填);
+5. 提交修改,等待 CI 验证和管理员合并。
+
+**若您需要一次提交大量修改,请继续阅读下面的内容**
+
+## 完整流程
+
+1. `fork`本项目;
+2. 克隆(`clone`)你 `fork` 的项目到本地;
+3. 新建分支(`branch`)并检出(`checkout`)新分支;
+4. 添加本项目到你的本地 git 仓库作为上游(`upstream`);
+5. 进行修改,若你的修改包含方法或函数的增减,请记得修改[单元测试文件](tests);
+6. 变基(衍合 `rebase`)你的分支到上游 master 分支;
+7. `push` 你的本地仓库到 GitHub;
+8. 提交 `pull request`;
+9. 等待 CI 验证(若不通过则重复 5~7,GitHub 会自动更新你的 `pull request`);
+10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。
+
+*若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`*
+
+*绝对不可以使用 `git push -f` 强行推送修改到上游*
+
+### 注意事项
+
+* 若对上述流程有任何不清楚的地方,请查阅 GIT 教程,如 [这个](http://backlogtool.com/git-guide/cn/);
+* 对于代码**不同方面**的修改,请在自己 `fork` 的项目中**创建不同的分支**(原因参见`完整流程`第9条备注部分);
+* 变基及交互式变基操作参见 [Git 交互式变基](http://pakchoi.me/2015/03/17/git-interactive-rebase/)
+
+## 推荐资源
+
+### 开发环境
+
+* XAMPP for Windows 5.5.x
+* WampServer (for Windows)
+* upupw Apache PHP5.4 ( for Windows)
+
+或自行安装
+
+- Apache / Nginx
+- PHP 7.1 ~ 7.3
+- MySQL / MariaDB
+
+*Windows 用户推荐添加 PHP bin 目录到 PATH,方便使用 composer*
+
+*Linux 用户自行配置环境, Mac 用户推荐使用内置 Apache 配合 Homebrew 安装 PHP 和 MariaDB*
+
+### 编辑器
+
+Sublime Text 3 + phpfmt 插件
+
+phpfmt 插件参数
+
+```json
+{
+ "autocomplete": true,
+ "enable_auto_align": true,
+ "format_on_save": true,
+ "indent_with_space": true,
+ "psr1_naming": false,
+ "psr2": true,
+ "version": 4
+}
+```
+
+或其他 编辑器 / IDE 配合 PSR2 自动格式化工具
+
+### Git GUI
+
+* SourceTree
+* GitHub Desktop
+
+或其他 Git 图形界面客户端
diff --git a/vendor/topthink/framework/LICENSE.txt b/vendor/topthink/framework/LICENSE.txt
new file mode 100755
index 0000000..4e910bb
--- /dev/null
+++ b/vendor/topthink/framework/LICENSE.txt
@@ -0,0 +1,32 @@
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+版权所有Copyright © 2006-2019 by ThinkPHP (http://thinkphp.cn)
+All rights reserved。
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+Apache Licence是著名的非盈利开源组织Apache采用的协议。
+该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
+允许代码修改,再作为开源或商业软件发布。需要满足
+的条件:
+1. 需要给代码的用户一份Apache Licence ;
+2. 如果你修改了代码,需要在被修改的文件中说明;
+3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
+带有原来代码中的协议,商标,专利声明和其他原来作者规
+定需要包含的说明;
+4. 如果再发布的产品中包含一个Notice文件,则在Notice文
+件中需要带有本协议内容。你可以在Notice中增加自己的
+许可,但不可以表现为对Apache Licence构成更改。
+具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/topthink/framework/README.md b/vendor/topthink/framework/README.md
new file mode 100755
index 0000000..dd38e34
--- /dev/null
+++ b/vendor/topthink/framework/README.md
@@ -0,0 +1,84 @@
+
+
+ThinkPHP 6.1
+===============
+
+[](https://travis-ci.org/top-think/framework)
+[](https://scrutinizer-ci.com/g/top-think/framework/?branch=6.0)
+[](https://scrutinizer-ci.com/g/top-think/framework/?branch=6.0)
+[](https://packagist.org/packages/topthink/framework)
+[](https://packagist.org/packages/topthink/framework)
+[](http://www.php.net/)
+[](https://packagist.org/packages/topthink/framework)
+
+
+[官方服务](https://www.topthink.com) | [`ThinkAPI`——官方统一API](https://doc.topthink.com/think-api)
+
+## 主要新特性
+
+* 采用`PHP7`强类型(严格模式)
+* 支持更多的`PSR`规范
+* 原生多应用支持
+* 系统服务注入支持
+* ORM作为独立组件使用
+* 全新的事件系统
+* 模板引擎分离出核心
+* 内部功能中间件化
+* SESSION机制改进
+* 日志多通道支持
+* 规范扩展接口
+* 更强大的控制台
+* 对Swoole以及协程支持改进
+* 对IDE更加友好
+* 统一和精简大量用法
+
+
+> ThinkPHP6.1的运行环境要求PHP7.2.5+,最高兼容PHP8.2
+
+## 安装
+
+~~~
+composer create-project topthink/think tp
+~~~
+
+启动服务
+
+~~~
+cd tp
+php think run
+~~~
+
+然后就可以在浏览器中访问
+
+~~~
+http://localhost:8000
+~~~
+
+如果需要更新框架使用
+~~~
+composer update topthink/framework
+~~~
+
+## 文档
+
+[完全开发手册](https://www.kancloud.cn/manual/thinkphp6_0/content)
+
+## 命名规范
+
+`ThinkPHP6`遵循PSR-2命名规范和PSR-4自动加载规范。
+
+## 参与开发
+
+直接提交PR或者Issue即可
+
+## 版权信息
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+
+本项目包含的第三方源码和二进制文件之版权信息另行标注。
+
+版权所有Copyright © 2006-2021 by ThinkPHP (http://thinkphp.cn) All rights reserved。
+
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+更多细节参阅 [LICENSE.txt](LICENSE.txt)
diff --git a/vendor/topthink/framework/composer.json b/vendor/topthink/framework/composer.json
new file mode 100755
index 0000000..2d9685e
--- /dev/null
+++ b/vendor/topthink/framework/composer.json
@@ -0,0 +1,54 @@
+{
+ "name": "topthink/framework",
+ "description": "The ThinkPHP Framework.",
+ "keywords": [
+ "framework",
+ "thinkphp",
+ "ORM"
+ ],
+ "homepage": "http://thinkphp.cn/",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ },
+ {
+ "name": "yunwuxin",
+ "email": "448901948@qq.com"
+ }
+ ],
+ "require": {
+ "php": ">=7.2.5",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "psr/log": "~1.0",
+ "psr/container": "~1.0",
+ "psr/simple-cache": "^1.0",
+ "psr/http-message": "^1.0",
+ "topthink/think-orm": "^2.0|^3.0",
+ "topthink/think-helper": "^3.1.1"
+ },
+ "require-dev": {
+ "mikey179/vfsstream": "^1.6",
+ "mockery/mockery": "^1.2",
+ "phpunit/phpunit": "^7.0",
+ "guzzlehttp/psr7": "^2.1.0"
+ },
+ "autoload": {
+ "files": [],
+ "psr-4": {
+ "think\\": "src/think/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "think\\tests\\": "tests/"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "config": {
+ "sort-packages": true
+ }
+}
diff --git a/vendor/topthink/framework/logo.png b/vendor/topthink/framework/logo.png
new file mode 100755
index 0000000..25fd059
--- /dev/null
+++ b/vendor/topthink/framework/logo.png
Binary files differ
diff --git a/vendor/topthink/framework/phpunit.xml.dist b/vendor/topthink/framework/phpunit.xml.dist
new file mode 100755
index 0000000..e20a133
--- /dev/null
+++ b/vendor/topthink/framework/phpunit.xml.dist
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ beStrictAboutTestsThatDoNotTestAnything="false"
+ bootstrap="tests/bootstrap.php"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnError="false"
+ stopOnFailure="false"
+ verbose="true"
+>
+ <testsuites>
+ <testsuite name="ThinkPHP Test Suite">
+ <directory suffix="Test.php">./tests</directory>
+ </testsuite>
+ </testsuites>
+ <filter>
+ <whitelist processUncoveredFilesFromWhitelist="true">
+ <directory suffix=".php">./src/think</directory>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/vendor/topthink/framework/src/helper.php b/vendor/topthink/framework/src/helper.php
new file mode 100755
index 0000000..caba0ae
--- /dev/null
+++ b/vendor/topthink/framework/src/helper.php
@@ -0,0 +1,664 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+//------------------------
+// ThinkPHP 助手函数
+//-------------------------
+
+use think\App;
+use think\Container;
+use think\exception\HttpException;
+use think\exception\HttpResponseException;
+use think\facade\Cache;
+use think\facade\Config;
+use think\facade\Cookie;
+use think\facade\Env;
+use think\facade\Event;
+use think\facade\Lang;
+use think\facade\Log;
+use think\facade\Request;
+use think\facade\Route;
+use think\facade\Session;
+use think\Response;
+use think\response\File;
+use think\response\Json;
+use think\response\Jsonp;
+use think\response\Redirect;
+use think\response\View;
+use think\response\Xml;
+use think\route\Url as UrlBuild;
+use think\Validate;
+
+if (!function_exists('abort')) {
+ /**
+ * 抛出HTTP异常
+ * @param integer|Response $code 状态码 或者 Response对象实例
+ * @param string $message 错误信息
+ * @param array $header 参数
+ */
+ function abort($code, string $message = '', array $header = [])
+ {
+ if ($code instanceof Response) {
+ throw new HttpResponseException($code);
+ } else {
+ throw new HttpException($code, $message, null, $header);
+ }
+ }
+}
+
+if (!function_exists('app')) {
+ /**
+ * 快速获取容器中的实例 支持依赖注入
+ * @template T
+ * @param string|class-string<T> $name 类名或标识 默认获取当前应用实例
+ * @param array $args 参数
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return T|object|App
+ */
+ function app(string $name = '', array $args = [], bool $newInstance = false)
+ {
+ return Container::getInstance()->make($name ?: App::class, $args, $newInstance);
+ }
+}
+
+if (!function_exists('bind')) {
+ /**
+ * 绑定一个类到容器
+ * @param string|array $abstract 类标识、接口(支持批量绑定)
+ * @param mixed $concrete 要绑定的类、闭包或者实例
+ * @return Container
+ */
+ function bind($abstract, $concrete = null)
+ {
+ return Container::getInstance()->bind($abstract, $concrete);
+ }
+}
+
+if (!function_exists('cache')) {
+ /**
+ * 缓存管理
+ * @param string $name 缓存名称
+ * @param mixed $value 缓存值
+ * @param mixed $options 缓存参数
+ * @param string $tag 缓存标签
+ * @return mixed
+ */
+ function cache(string $name = null, $value = '', $options = null, $tag = null)
+ {
+ if (is_null($name)) {
+ return app('cache');
+ }
+
+ if ('' === $value) {
+ // 获取缓存
+ return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name);
+ } elseif (is_null($value)) {
+ // 删除缓存
+ return Cache::delete($name);
+ }
+
+ // 缓存数据
+ if (is_array($options)) {
+ $expire = $options['expire'] ?? null; //修复查询缓存无法设置过期时间
+ } else {
+ $expire = $options;
+ }
+
+ if (is_null($tag)) {
+ return Cache::set($name, $value, $expire);
+ } else {
+ return Cache::tag($tag)->set($name, $value, $expire);
+ }
+ }
+}
+
+if (!function_exists('config')) {
+ /**
+ * 获取和设置配置参数
+ * @param string|array $name 参数名
+ * @param mixed $value 参数值
+ * @return mixed
+ */
+ function config($name = '', $value = null)
+ {
+ if (is_array($name)) {
+ return Config::set($name, $value);
+ }
+
+ return 0 === strpos($name, '?') ? Config::has(substr($name, 1)) : Config::get($name, $value);
+ }
+}
+
+if (!function_exists('cookie')) {
+ /**
+ * Cookie管理
+ * @param string $name cookie名称
+ * @param mixed $value cookie值
+ * @param mixed $option 参数
+ * @return mixed
+ */
+ function cookie(string $name, $value = '', $option = null)
+ {
+ if (is_null($value)) {
+ // 删除
+ Cookie::delete($name, $option ?: []);
+ } elseif ('' === $value) {
+ // 获取
+ return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1)) : Cookie::get($name);
+ } else {
+ // 设置
+ return Cookie::set($name, $value, $option);
+ }
+ }
+}
+
+if (!function_exists('download')) {
+ /**
+ * 获取\think\response\Download对象实例
+ * @param string $filename 要下载的文件
+ * @param string $name 显示文件名
+ * @param bool $content 是否为内容
+ * @param int $expire 有效期(秒)
+ * @return \think\response\File
+ */
+ function download(string $filename, string $name = '', bool $content = false, int $expire = 180): File
+ {
+ return Response::create($filename, 'file')->name($name)->isContent($content)->expire($expire);
+ }
+}
+
+if (!function_exists('dump')) {
+ /**
+ * 浏览器友好的变量输出
+ * @param mixed $vars 要输出的变量
+ * @return void
+ */
+ function dump(...$vars)
+ {
+ ob_start();
+ var_dump(...$vars);
+
+ $output = ob_get_clean();
+ $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output);
+
+ if (PHP_SAPI == 'cli') {
+ $output = PHP_EOL . $output . PHP_EOL;
+ } else {
+ if (!extension_loaded('xdebug')) {
+ $output = htmlspecialchars($output, ENT_SUBSTITUTE);
+ }
+ $output = '<pre>' . $output . '</pre>';
+ }
+
+ echo $output;
+ }
+}
+
+if (!function_exists('env')) {
+ /**
+ * 获取环境变量值
+ * @access public
+ * @param string $name 环境变量名(支持二级 .号分割)
+ * @param string $default 默认值
+ * @return mixed
+ */
+ function env(string $name = null, $default = null)
+ {
+ return Env::get($name, $default);
+ }
+}
+
+if (!function_exists('event')) {
+ /**
+ * 触发事件
+ * @param mixed $event 事件名(或者类名)
+ * @param mixed $args 参数
+ * @return mixed
+ */
+ function event($event, $args = null)
+ {
+ return Event::trigger($event, $args);
+ }
+}
+
+if (!function_exists('halt')) {
+ /**
+ * 调试变量并且中断输出
+ * @param mixed $vars 调试变量或者信息
+ */
+ function halt(...$vars)
+ {
+ dump(...$vars);
+
+ throw new HttpResponseException(Response::create());
+ }
+}
+
+if (!function_exists('input')) {
+ /**
+ * 获取输入数据 支持默认值和过滤
+ * @param string $key 获取的变量名
+ * @param mixed $default 默认值
+ * @param string $filter 过滤方法
+ * @return mixed
+ */
+ function input(string $key = '', $default = null, $filter = '')
+ {
+ if (0 === strpos($key, '?')) {
+ $key = substr($key, 1);
+ $has = true;
+ }
+
+ if ($pos = strpos($key, '.')) {
+ // 指定参数来源
+ $method = substr($key, 0, $pos);
+ if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) {
+ $key = substr($key, $pos + 1);
+ if ('server' == $method && is_null($default)) {
+ $default = '';
+ }
+ } else {
+ $method = 'param';
+ }
+ } else {
+ // 默认为自动判断
+ $method = 'param';
+ }
+
+ return isset($has) ?
+ request()->has($key, $method) :
+ request()->$method($key, $default, $filter);
+ }
+}
+
+if (!function_exists('invoke')) {
+ /**
+ * 调用反射实例化对象或者执行方法 支持依赖注入
+ * @param mixed $call 类名或者callable
+ * @param array $args 参数
+ * @return mixed
+ */
+ function invoke($call, array $args = [])
+ {
+ if (is_callable($call)) {
+ return Container::getInstance()->invoke($call, $args);
+ }
+
+ return Container::getInstance()->invokeClass($call, $args);
+ }
+}
+
+if (!function_exists('json')) {
+ /**
+ * 获取\think\response\Json对象实例
+ * @param mixed $data 返回的数据
+ * @param int $code 状态码
+ * @param array $header 头部
+ * @param array $options 参数
+ * @return \think\response\Json
+ */
+ function json($data = [], $code = 200, $header = [], $options = []): Json
+ {
+ return Response::create($data, 'json', $code)->header($header)->options($options);
+ }
+}
+
+if (!function_exists('jsonp')) {
+ /**
+ * 获取\think\response\Jsonp对象实例
+ * @param mixed $data 返回的数据
+ * @param int $code 状态码
+ * @param array $header 头部
+ * @param array $options 参数
+ * @return \think\response\Jsonp
+ */
+ function jsonp($data = [], $code = 200, $header = [], $options = []): Jsonp
+ {
+ return Response::create($data, 'jsonp', $code)->header($header)->options($options);
+ }
+}
+
+if (!function_exists('lang')) {
+ /**
+ * 获取语言变量值
+ * @param string $name 语言变量名
+ * @param array $vars 动态变量值
+ * @param string $lang 语言
+ * @return mixed
+ */
+ function lang(string $name, array $vars = [], string $lang = '')
+ {
+ return Lang::get($name, $vars, $lang);
+ }
+}
+
+if (!function_exists('parse_name')) {
+ /**
+ * 字符串命名风格转换
+ * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
+ * @param string $name 字符串
+ * @param int $type 转换类型
+ * @param bool $ucfirst 首字母是否大写(驼峰规则)
+ * @return string
+ */
+ function parse_name(string $name, int $type = 0, bool $ucfirst = true): string
+ {
+ if ($type) {
+ $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
+ return strtoupper($match[1]);
+ }, $name);
+
+ return $ucfirst ? ucfirst($name) : lcfirst($name);
+ }
+
+ return strtolower(trim(preg_replace('/[A-Z]/', '_\\0', $name), '_'));
+ }
+}
+
+if (!function_exists('redirect')) {
+ /**
+ * 获取\think\response\Redirect对象实例
+ * @param string $url 重定向地址
+ * @param int $code 状态码
+ * @return \think\response\Redirect
+ */
+ function redirect(string $url = '', int $code = 302): Redirect
+ {
+ return Response::create($url, 'redirect', $code);
+ }
+}
+
+if (!function_exists('request')) {
+ /**
+ * 获取当前Request对象实例
+ * @return Request
+ */
+ function request(): \think\Request
+ {
+ return app('request');
+ }
+}
+
+if (!function_exists('response')) {
+ /**
+ * 创建普通 Response 对象实例
+ * @param mixed $data 输出数据
+ * @param int|string $code 状态码
+ * @param array $header 头信息
+ * @param string $type
+ * @return Response
+ */
+ function response($data = '', $code = 200, $header = [], $type = 'html'): Response
+ {
+ return Response::create($data, $type, $code)->header($header);
+ }
+}
+
+if (!function_exists('session')) {
+ /**
+ * Session管理
+ * @param string $name session名称
+ * @param mixed $value session值
+ * @return mixed
+ */
+ function session($name = '', $value = '')
+ {
+ if (is_null($name)) {
+ // 清除
+ Session::clear();
+ } elseif ('' === $name) {
+ return Session::all();
+ } elseif (is_null($value)) {
+ // 删除
+ Session::delete($name);
+ } elseif ('' === $value) {
+ // 判断或获取
+ return 0 === strpos($name, '?') ? Session::has(substr($name, 1)) : Session::get($name);
+ } else {
+ // 设置
+ Session::set($name, $value);
+ }
+ }
+}
+
+if (!function_exists('token')) {
+ /**
+ * 获取Token令牌
+ * @param string $name 令牌名称
+ * @param mixed $type 令牌生成方法
+ * @return string
+ */
+ function token(string $name = '__token__', string $type = 'md5'): string
+ {
+ return Request::buildToken($name, $type);
+ }
+}
+
+if (!function_exists('token_field')) {
+ /**
+ * 生成令牌隐藏表单
+ * @param string $name 令牌名称
+ * @param mixed $type 令牌生成方法
+ * @return string
+ */
+ function token_field(string $name = '__token__', string $type = 'md5'): string
+ {
+ $token = Request::buildToken($name, $type);
+
+ return '<input type="hidden" name="' . $name . '" value="' . $token . '" />';
+ }
+}
+
+if (!function_exists('token_meta')) {
+ /**
+ * 生成令牌meta
+ * @param string $name 令牌名称
+ * @param mixed $type 令牌生成方法
+ * @return string
+ */
+ function token_meta(string $name = '__token__', string $type = 'md5'): string
+ {
+ $token = Request::buildToken($name, $type);
+
+ return '<meta name="csrf-token" content="' . $token . '">';
+ }
+}
+
+if (!function_exists('trace')) {
+ /**
+ * 记录日志信息
+ * @param mixed $log log信息 支持字符串和数组
+ * @param string $level 日志级别
+ * @return array|void
+ */
+ function trace($log = '[think]', string $level = 'log')
+ {
+ if ('[think]' === $log) {
+ return Log::getLog();
+ }
+
+ Log::record($log, $level);
+ }
+}
+
+if (!function_exists('url')) {
+ /**
+ * Url生成
+ * @param string $url 路由地址
+ * @param array $vars 变量
+ * @param bool|string $suffix 生成的URL后缀
+ * @param bool|string $domain 域名
+ * @return UrlBuild
+ */
+ function url(string $url = '', array $vars = [], $suffix = true, $domain = false): UrlBuild
+ {
+ return Route::buildUrl($url, $vars)->suffix($suffix)->domain($domain);
+ }
+}
+
+if (!function_exists('validate')) {
+ /**
+ * 生成验证对象
+ * @param string|array $validate 验证器类名或者验证规则数组
+ * @param array $message 错误提示信息
+ * @param bool $batch 是否批量验证
+ * @param bool $failException 是否抛出异常
+ * @return Validate
+ */
+ function validate($validate = '', array $message = [], bool $batch = false, bool $failException = true): Validate
+ {
+ if (is_array($validate) || '' === $validate) {
+ $v = new Validate();
+ if (is_array($validate)) {
+ $v->rule($validate);
+ }
+ } else {
+ if (strpos($validate, '.')) {
+ // 支持场景
+ [$validate, $scene] = explode('.', $validate);
+ }
+
+ $class = false !== strpos($validate, '\\') ? $validate : app()->parseClass('validate', $validate);
+
+ $v = new $class();
+
+ if (!empty($scene)) {
+ $v->scene($scene);
+ }
+ }
+
+ return $v->message($message)->batch($batch)->failException($failException);
+ }
+}
+
+if (!function_exists('view')) {
+ /**
+ * 渲染模板输出
+ * @param string $template 模板文件
+ * @param array $vars 模板变量
+ * @param int $code 状态码
+ * @param callable $filter 内容过滤
+ * @return \think\response\View
+ */
+ function view(string $template = '', $vars = [], $code = 200, $filter = null): View
+ {
+ return Response::create($template, 'view', $code)->assign($vars)->filter($filter);
+ }
+}
+
+if (!function_exists('display')) {
+ /**
+ * 渲染模板输出
+ * @param string $content 渲染内容
+ * @param array $vars 模板变量
+ * @param int $code 状态码
+ * @param callable $filter 内容过滤
+ * @return \think\response\View
+ */
+ function display(string $content, $vars = [], $code = 200, $filter = null): View
+ {
+ return Response::create($content, 'view', $code)->isContent(true)->assign($vars)->filter($filter);
+ }
+}
+
+if (!function_exists('xml')) {
+ /**
+ * 获取\think\response\Xml对象实例
+ * @param mixed $data 返回的数据
+ * @param int $code 状态码
+ * @param array $header 头部
+ * @param array $options 参数
+ * @return \think\response\Xml
+ */
+ function xml($data = [], $code = 200, $header = [], $options = []): Xml
+ {
+ return Response::create($data, 'xml', $code)->header($header)->options($options);
+ }
+}
+
+if (!function_exists('app_path')) {
+ /**
+ * 获取当前应用目录
+ *
+ * @param string $path
+ * @return string
+ */
+ function app_path($path = '')
+ {
+ return app()->getAppPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+ }
+}
+
+if (!function_exists('base_path')) {
+ /**
+ * 获取应用基础目录
+ *
+ * @param string $path
+ * @return string
+ */
+ function base_path($path = '')
+ {
+ return app()->getBasePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+ }
+}
+
+if (!function_exists('config_path')) {
+ /**
+ * 获取应用配置目录
+ *
+ * @param string $path
+ * @return string
+ */
+ function config_path($path = '')
+ {
+ return app()->getConfigPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+ }
+}
+
+if (!function_exists('public_path')) {
+ /**
+ * 获取web根目录
+ *
+ * @param string $path
+ * @return string
+ */
+ function public_path($path = '')
+ {
+ return app()->getRootPath() . 'public' . DIRECTORY_SEPARATOR . ($path ? ltrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $path);
+ }
+}
+
+if (!function_exists('runtime_path')) {
+ /**
+ * 获取应用运行时目录
+ *
+ * @param string $path
+ * @return string
+ */
+ function runtime_path($path = '')
+ {
+ return app()->getRuntimePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+ }
+}
+
+if (!function_exists('root_path')) {
+ /**
+ * 获取项目根目录
+ *
+ * @param string $path
+ * @return string
+ */
+ function root_path($path = '')
+ {
+ return app()->getRootPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+ }
+}
diff --git a/vendor/topthink/framework/src/lang/zh-cn.php b/vendor/topthink/framework/src/lang/zh-cn.php
new file mode 100755
index 0000000..a546330
--- /dev/null
+++ b/vendor/topthink/framework/src/lang/zh-cn.php
@@ -0,0 +1,148 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// 核心中文语言包
+return [
+ // 系统错误提示
+ 'Undefined variable' => '未定义变量',
+ 'Undefined index' => '未定义数组索引',
+ 'Undefined offset' => '未定义数组下标',
+ 'Parse error' => '语法解析错误',
+ 'Type error' => '类型错误',
+ 'Fatal error' => '致命错误',
+ 'syntax error' => '语法错误',
+
+ // 框架核心错误提示
+ 'dispatch type not support' => '不支持的调度类型',
+ 'method param miss' => '方法参数错误',
+ 'method not exists' => '方法不存在',
+ 'function not exists' => '函数不存在',
+ 'app not exists' => '应用不存在',
+ 'controller not exists' => '控制器不存在',
+ 'class not exists' => '类不存在',
+ 'property not exists' => '类的属性不存在',
+ 'template not exists' => '模板文件不存在',
+ 'illegal controller name' => '非法的控制器名称',
+ 'illegal action name' => '非法的操作名称',
+ 'url suffix deny' => '禁止的URL后缀访问',
+ 'Undefined cache config' => '缓存配置未定义',
+ 'Route Not Found' => '当前访问路由未定义或不匹配',
+ 'Undefined db config' => '数据库配置未定义',
+ 'Undefined log config' => '日志配置未定义',
+ 'Undefined db type' => '未定义数据库类型',
+ 'variable type error' => '变量类型错误',
+ 'PSR-4 error' => 'PSR-4 规范错误',
+ 'not support type' => '不支持的分页索引字段类型',
+ 'not support total' => '简洁模式下不能获取数据总数',
+ 'not support last' => '简洁模式下不能获取最后一页',
+ 'error session handler' => '错误的SESSION处理器类',
+ 'not allow php tag' => '模板不允许使用PHP语法',
+ 'not support' => '不支持',
+ 'database config error' => '数据库配置信息错误',
+ 'redisd master' => 'Redisd 主服务器错误',
+ 'redisd slave' => 'Redisd 从服务器错误',
+ 'must run at sae' => '必须在SAE运行',
+ 'memcache init error' => '未开通Memcache服务,请在SAE管理平台初始化Memcache服务',
+ 'KVDB init error' => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务',
+ 'fields not exists' => '数据表字段不存在',
+ 'where express error' => '查询表达式错误',
+ 'no data to update' => '没有任何数据需要更新',
+ 'miss data to insert' => '缺少需要写入的数据',
+ 'miss complex primary data' => '缺少复合主键数据',
+ 'miss update condition' => '缺少更新条件',
+ 'model data Not Found' => '模型数据不存在',
+ 'table data not Found' => '表数据不存在',
+ 'delete without condition' => '没有条件不会执行删除操作',
+ 'miss relation data' => '缺少关联表数据',
+ 'tag attr must' => '模板标签属性必须',
+ 'tag error' => '模板标签错误',
+ 'cache write error' => '缓存写入失败',
+ 'sae mc write error' => 'SAE mc 写入错误',
+ 'route name not exists' => '路由标识不存在(或参数不够)',
+ 'invalid request' => '非法请求',
+ 'bind attr has exists' => '模型的属性已经存在',
+ 'relation data not exists' => '关联数据不存在',
+ 'relation not support' => '关联不支持',
+ 'chunk not support order' => 'Chunk不支持调用order方法',
+ 'route pattern error' => '路由变量规则定义错误',
+ 'route behavior will not support' => '路由行为废弃(使用中间件替代)',
+ 'closure not support cache(true)' => '使用闭包查询不支持cache(true),请指定缓存Key',
+
+ // 上传错误信息
+ 'unknown upload error' => '未知上传错误!',
+ 'file write error' => '文件写入失败!',
+ 'upload temp dir not found' => '找不到临时文件夹!',
+ 'no file to uploaded' => '没有文件被上传!',
+ 'only the portion of file is uploaded' => '文件只有部分被上传!',
+ 'upload File size exceeds the maximum value' => '上传文件大小超过了最大值!',
+ 'upload write error' => '文件上传保存错误!',
+ 'has the same filename: {:filename}' => '存在同名文件:{:filename}',
+ 'upload illegal files' => '非法上传文件',
+ 'illegal image files' => '非法图片文件',
+ 'extensions to upload is not allowed' => '上传文件后缀不允许',
+ 'mimetype to upload is not allowed' => '上传文件MIME类型不允许!',
+ 'filesize not match' => '上传文件大小不符!',
+ 'directory {:path} creation failed' => '目录 {:path} 创建失败!',
+
+ 'The middleware must return Response instance' => '中间件方法必须返回Response对象实例',
+ 'The queue was exhausted, with no response returned' => '中间件队列为空',
+ // Validate Error Message
+ ':attribute require' => ':attribute不能为空',
+ ':attribute must' => ':attribute必须',
+ ':attribute must be numeric' => ':attribute必须是数字',
+ ':attribute must be integer' => ':attribute必须是整数',
+ ':attribute must be float' => ':attribute必须是浮点数',
+ ':attribute must be bool' => ':attribute必须是布尔值',
+ ':attribute not a valid email address' => ':attribute格式不符',
+ ':attribute not a valid mobile' => ':attribute格式不符',
+ ':attribute must be a array' => ':attribute必须是数组',
+ ':attribute must be yes,on or 1' => ':attribute必须是yes、on或者1',
+ ':attribute not a valid datetime' => ':attribute不是一个有效的日期或时间格式',
+ ':attribute not a valid file' => ':attribute不是有效的上传文件',
+ ':attribute not a valid image' => ':attribute不是有效的图像文件',
+ ':attribute must be alpha' => ':attribute只能是字母',
+ ':attribute must be alpha-numeric' => ':attribute只能是字母和数字',
+ ':attribute must be alpha-numeric, dash, underscore' => ':attribute只能是字母、数字和下划线_及破折号-',
+ ':attribute not a valid domain or ip' => ':attribute不是有效的域名或者IP',
+ ':attribute must be chinese' => ':attribute只能是汉字',
+ ':attribute must be chinese or alpha' => ':attribute只能是汉字、字母',
+ ':attribute must be chinese,alpha-numeric' => ':attribute只能是汉字、字母和数字',
+ ':attribute must be chinese,alpha-numeric,underscore, dash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-',
+ ':attribute not a valid url' => ':attribute不是有效的URL地址',
+ ':attribute not a valid ip' => ':attribute不是有效的IP地址',
+ ':attribute must be dateFormat of :rule' => ':attribute必须使用日期格式 :rule',
+ ':attribute must be in :rule' => ':attribute必须在 :rule 范围内',
+ ':attribute be notin :rule' => ':attribute不能在 :rule 范围内',
+ ':attribute must between :1 - :2' => ':attribute只能在 :1 - :2 之间',
+ ':attribute not between :1 - :2' => ':attribute不能在 :1 - :2 之间',
+ 'size of :attribute must be :rule' => ':attribute长度不符合要求 :rule',
+ 'max size of :attribute must be :rule' => ':attribute长度不能超过 :rule',
+ 'min size of :attribute must be :rule' => ':attribute长度不能小于 :rule',
+ ':attribute cannot be less than :rule' => ':attribute日期不能小于 :rule',
+ ':attribute cannot exceed :rule' => ':attribute日期不能超过 :rule',
+ ':attribute not within :rule' => '不在有效期内 :rule',
+ 'access IP is not allowed' => '不允许的IP访问',
+ 'access IP denied' => '禁止的IP访问',
+ ':attribute out of accord with :2' => ':attribute和确认字段:2不一致',
+ ':attribute cannot be same with :2' => ':attribute和比较字段:2不能相同',
+ ':attribute must greater than or equal :rule' => ':attribute必须大于等于 :rule',
+ ':attribute must greater than :rule' => ':attribute必须大于 :rule',
+ ':attribute must less than or equal :rule' => ':attribute必须小于等于 :rule',
+ ':attribute must less than :rule' => ':attribute必须小于 :rule',
+ ':attribute must equal :rule' => ':attribute必须等于 :rule',
+ ':attribute has exists' => ':attribute已存在',
+ ':attribute not conform to the rules' => ':attribute不符合指定规则',
+ 'invalid Request method' => '无效的请求类型',
+ 'invalid token' => '令牌数据无效',
+ 'not conform to the rules' => '规则错误',
+
+ 'record has update' => '记录已经被更新了',
+];
diff --git a/vendor/topthink/framework/src/think/App.php b/vendor/topthink/framework/src/think/App.php
new file mode 100755
index 0000000..9f309c0
--- /dev/null
+++ b/vendor/topthink/framework/src/think/App.php
@@ -0,0 +1,620 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use think\event\AppInit;
+use think\helper\Str;
+use think\initializer\BootService;
+use think\initializer\Error;
+use think\initializer\RegisterService;
+
+/**
+ * App 基础类
+ * @property Route $route
+ * @property Config $config
+ * @property Cache $cache
+ * @property Request $request
+ * @property Http $http
+ * @property Console $console
+ * @property Env $env
+ * @property Event $event
+ * @property Middleware $middleware
+ * @property Log $log
+ * @property Lang $lang
+ * @property Db $db
+ * @property Cookie $cookie
+ * @property Session $session
+ * @property Validate $validate
+ */
+class App extends Container
+{
+ const VERSION = '6.1.1';
+
+ /**
+ * 应用调试模式
+ * @var bool
+ */
+ protected $appDebug = false;
+
+ /**
+ * 环境变量标识
+ * @var string
+ */
+ protected $envName = '';
+
+ /**
+ * 应用开始时间
+ * @var float
+ */
+ protected $beginTime;
+
+ /**
+ * 应用内存初始占用
+ * @var integer
+ */
+ protected $beginMem;
+
+ /**
+ * 当前应用类库命名空间
+ * @var string
+ */
+ protected $namespace = 'app';
+
+ /**
+ * 应用根目录
+ * @var string
+ */
+ protected $rootPath = '';
+
+ /**
+ * 框架目录
+ * @var string
+ */
+ protected $thinkPath = '';
+
+ /**
+ * 应用目录
+ * @var string
+ */
+ protected $appPath = '';
+
+ /**
+ * Runtime目录
+ * @var string
+ */
+ protected $runtimePath = '';
+
+ /**
+ * 路由定义目录
+ * @var string
+ */
+ protected $routePath = '';
+
+ /**
+ * 配置后缀
+ * @var string
+ */
+ protected $configExt = '.php';
+
+ /**
+ * 应用初始化器
+ * @var array
+ */
+ protected $initializers = [
+ Error::class,
+ RegisterService::class,
+ BootService::class,
+ ];
+
+ /**
+ * 注册的系统服务
+ * @var array
+ */
+ protected $services = [];
+
+ /**
+ * 初始化
+ * @var bool
+ */
+ protected $initialized = false;
+
+ /**
+ * 容器绑定标识
+ * @var array
+ */
+ protected $bind = [
+ 'app' => App::class,
+ 'cache' => Cache::class,
+ 'config' => Config::class,
+ 'console' => Console::class,
+ 'cookie' => Cookie::class,
+ 'db' => Db::class,
+ 'env' => Env::class,
+ 'event' => Event::class,
+ 'http' => Http::class,
+ 'lang' => Lang::class,
+ 'log' => Log::class,
+ 'middleware' => Middleware::class,
+ 'request' => Request::class,
+ 'response' => Response::class,
+ 'route' => Route::class,
+ 'session' => Session::class,
+ 'validate' => Validate::class,
+ 'view' => View::class,
+ 'think\DbManager' => Db::class,
+ 'think\LogManager' => Log::class,
+ 'think\CacheManager' => Cache::class,
+
+ // 接口依赖注入
+ 'Psr\Log\LoggerInterface' => Log::class,
+ ];
+
+ /**
+ * 架构方法
+ * @access public
+ * @param string $rootPath 应用根目录
+ */
+ public function __construct(string $rootPath = '')
+ {
+ $this->thinkPath = realpath(dirname(__DIR__)) . DIRECTORY_SEPARATOR;
+ $this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
+ $this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
+ $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
+
+ if (is_file($this->appPath . 'provider.php')) {
+ $this->bind(include $this->appPath . 'provider.php');
+ }
+
+ static::setInstance($this);
+
+ $this->instance('app', $this);
+ $this->instance('think\Container', $this);
+ }
+
+ /**
+ * 注册服务
+ * @access public
+ * @param Service|string $service 服务
+ * @param bool $force 强制重新注册
+ * @return Service|null
+ */
+ public function register($service, bool $force = false)
+ {
+ $registered = $this->getService($service);
+
+ if ($registered && !$force) {
+ return $registered;
+ }
+
+ if (is_string($service)) {
+ $service = new $service($this);
+ }
+
+ if (method_exists($service, 'register')) {
+ $service->register();
+ }
+
+ if (property_exists($service, 'bind')) {
+ $this->bind($service->bind);
+ }
+
+ $this->services[] = $service;
+ }
+
+ /**
+ * 执行服务
+ * @access public
+ * @param Service $service 服务
+ * @return mixed
+ */
+ public function bootService($service)
+ {
+ if (method_exists($service, 'boot')) {
+ return $this->invoke([$service, 'boot']);
+ }
+ }
+
+ /**
+ * 获取服务
+ * @param string|Service $service
+ * @return Service|null
+ */
+ public function getService($service)
+ {
+ $name = is_string($service) ? $service : get_class($service);
+ return array_values(array_filter($this->services, function ($value) use ($name) {
+ return $value instanceof $name;
+ }, ARRAY_FILTER_USE_BOTH))[0] ?? null;
+ }
+
+ /**
+ * 开启应用调试模式
+ * @access public
+ * @param bool $debug 开启应用调试模式
+ * @return $this
+ */
+ public function debug(bool $debug = true)
+ {
+ $this->appDebug = $debug;
+ return $this;
+ }
+
+ /**
+ * 是否为调试模式
+ * @access public
+ * @return bool
+ */
+ public function isDebug(): bool
+ {
+ return $this->appDebug;
+ }
+
+ /**
+ * 设置应用命名空间
+ * @access public
+ * @param string $namespace 应用命名空间
+ * @return $this
+ */
+ public function setNamespace(string $namespace)
+ {
+ $this->namespace = $namespace;
+ return $this;
+ }
+
+ /**
+ * 获取应用类库命名空间
+ * @access public
+ * @return string
+ */
+ public function getNamespace(): string
+ {
+ return $this->namespace;
+ }
+
+ /**
+ * 设置环境变量标识
+ * @access public
+ * @param string $name 环境标识
+ * @return $this
+ */
+ public function setEnvName(string $name)
+ {
+ $this->envName = $name;
+ return $this;
+ }
+
+ /**
+ * 获取框架版本
+ * @access public
+ * @return string
+ */
+ public function version(): string
+ {
+ return static::VERSION;
+ }
+
+ /**
+ * 获取应用根目录
+ * @access public
+ * @return string
+ */
+ public function getRootPath(): string
+ {
+ return $this->rootPath;
+ }
+
+ /**
+ * 获取应用基础目录
+ * @access public
+ * @return string
+ */
+ public function getBasePath(): string
+ {
+ return $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
+ }
+
+ /**
+ * 获取当前应用目录
+ * @access public
+ * @return string
+ */
+ public function getAppPath(): string
+ {
+ return $this->appPath;
+ }
+
+ /**
+ * 设置应用目录
+ * @param string $path 应用目录
+ */
+ public function setAppPath(string $path)
+ {
+ $this->appPath = $path;
+ }
+
+ /**
+ * 获取应用运行时目录
+ * @access public
+ * @return string
+ */
+ public function getRuntimePath(): string
+ {
+ return $this->runtimePath;
+ }
+
+ /**
+ * 设置runtime目录
+ * @param string $path 定义目录
+ */
+ public function setRuntimePath(string $path): void
+ {
+ $this->runtimePath = $path;
+ }
+
+ /**
+ * 获取核心框架目录
+ * @access public
+ * @return string
+ */
+ public function getThinkPath(): string
+ {
+ return $this->thinkPath;
+ }
+
+ /**
+ * 获取应用配置目录
+ * @access public
+ * @return string
+ */
+ public function getConfigPath(): string
+ {
+ return $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
+ }
+
+ /**
+ * 获取配置后缀
+ * @access public
+ * @return string
+ */
+ public function getConfigExt(): string
+ {
+ return $this->configExt;
+ }
+
+ /**
+ * 获取应用开启时间
+ * @access public
+ * @return float
+ */
+ public function getBeginTime(): float
+ {
+ return $this->beginTime;
+ }
+
+ /**
+ * 获取应用初始内存占用
+ * @access public
+ * @return integer
+ */
+ public function getBeginMem(): int
+ {
+ return $this->beginMem;
+ }
+
+ /**
+ * 加载环境变量定义
+ * @access public
+ * @param string $envName 环境标识
+ * @return void
+ */
+ public function loadEnv(string $envName = ''): void
+ {
+ // 加载环境变量
+ $envFile = $envName ? $this->rootPath . '.env.' . $envName : $this->rootPath . '.env';
+
+ if (is_file($envFile)) {
+ $this->env->load($envFile);
+ }
+ }
+
+ /**
+ * 初始化应用
+ * @access public
+ * @return $this
+ */
+ public function initialize()
+ {
+ $this->initialized = true;
+
+ $this->beginTime = microtime(true);
+ $this->beginMem = memory_get_usage();
+
+ $this->loadEnv($this->envName);
+
+ $this->configExt = $this->env->get('config_ext', '.php');
+
+ $this->debugModeInit();
+
+ // 加载全局初始化文件
+ $this->load();
+
+ // 加载应用默认语言包
+ $this->loadLangPack();
+
+ // 监听AppInit
+ $this->event->trigger(AppInit::class);
+
+ date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai'));
+
+ // 初始化
+ foreach ($this->initializers as $initializer) {
+ $this->make($initializer)->init($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 是否初始化过
+ * @return bool
+ */
+ public function initialized()
+ {
+ return $this->initialized;
+ }
+
+ /**
+ * 加载语言包
+ * @return void
+ */
+ public function loadLangPack()
+ {
+ // 加载默认语言包
+ $langSet = $this->lang->defaultLangSet();
+ $this->lang->switchLangSet($langSet);
+ }
+
+ /**
+ * 引导应用
+ * @access public
+ * @return void
+ */
+ public function boot(): void
+ {
+ array_walk($this->services, function ($service) {
+ $this->bootService($service);
+ });
+ }
+
+ /**
+ * 加载应用文件和配置
+ * @access protected
+ * @return void
+ */
+ protected function load(): void
+ {
+ $appPath = $this->getAppPath();
+
+ if (is_file($appPath . 'common.php')) {
+ include_once $appPath . 'common.php';
+ }
+
+ include_once $this->thinkPath . 'helper.php';
+
+ $configPath = $this->getConfigPath();
+
+ $files = [];
+
+ if (is_dir($configPath)) {
+ $files = glob($configPath . '*' . $this->configExt);
+ }
+
+ foreach ($files as $file) {
+ $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
+ }
+
+ if (is_file($appPath . 'event.php')) {
+ $this->loadEvent(include $appPath . 'event.php');
+ }
+
+ if (is_file($appPath . 'service.php')) {
+ $services = include $appPath . 'service.php';
+ foreach ($services as $service) {
+ $this->register($service);
+ }
+ }
+ }
+
+ /**
+ * 调试模式设置
+ * @access protected
+ * @return void
+ */
+ protected function debugModeInit(): void
+ {
+ // 应用调试模式
+ if (!$this->appDebug) {
+ $this->appDebug = $this->env->get('app_debug') ? true : false;
+ ini_set('display_errors', 'Off');
+ }
+
+ if (!$this->runningInConsole()) {
+ //重新申请一块比较大的buffer
+ if (ob_get_level() > 0) {
+ $output = ob_get_clean();
+ }
+ ob_start();
+ if (!empty($output)) {
+ echo $output;
+ }
+ }
+ }
+
+ /**
+ * 注册应用事件
+ * @access protected
+ * @param array $event 事件数据
+ * @return void
+ */
+ public function loadEvent(array $event): void
+ {
+ if (isset($event['bind'])) {
+ $this->event->bind($event['bind']);
+ }
+
+ if (isset($event['listen'])) {
+ $this->event->listenEvents($event['listen']);
+ }
+
+ if (isset($event['subscribe'])) {
+ $this->event->subscribe($event['subscribe']);
+ }
+ }
+
+ /**
+ * 解析应用类的类名
+ * @access public
+ * @param string $layer 层名 controller model ...
+ * @param string $name 类名
+ * @return string
+ */
+ public function parseClass(string $layer, string $name): string
+ {
+ $name = str_replace(['/', '.'], '\\', $name);
+ $array = explode('\\', $name);
+ $class = Str::studly(array_pop($array));
+ $path = $array ? implode('\\', $array) . '\\' : '';
+
+ return $this->namespace . '\\' . $layer . '\\' . $path . $class;
+ }
+
+ /**
+ * 是否运行在命令行下
+ * @return bool
+ */
+ public function runningInConsole(): bool
+ {
+ return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';
+ }
+
+ /**
+ * 获取应用根目录
+ * @access protected
+ * @return string
+ */
+ protected function getDefaultRootPath(): string
+ {
+ return dirname($this->thinkPath, 4) . DIRECTORY_SEPARATOR;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Cache.php b/vendor/topthink/framework/src/think/Cache.php
new file mode 100755
index 0000000..f802b55
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Cache.php
@@ -0,0 +1,197 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Psr\SimpleCache\CacheInterface;
+use think\cache\Driver;
+use think\cache\TagSet;
+use think\exception\InvalidArgumentException;
+use think\helper\Arr;
+
+/**
+ * 缓存管理类
+ * @mixin Driver
+ * @mixin \think\cache\driver\File
+ */
+class Cache extends Manager implements CacheInterface
+{
+
+ protected $namespace = '\\think\\cache\\driver\\';
+
+ /**
+ * 默认驱动
+ * @return string|null
+ */
+ public function getDefaultDriver()
+ {
+ return $this->getConfig('default');
+ }
+
+ /**
+ * 获取缓存配置
+ * @access public
+ * @param null|string $name 名称
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getConfig(string $name = null, $default = null)
+ {
+ if (!is_null($name)) {
+ return $this->app->config->get('cache.' . $name, $default);
+ }
+
+ return $this->app->config->get('cache');
+ }
+
+ /**
+ * 获取驱动配置
+ * @param string $store
+ * @param string $name
+ * @param null $default
+ * @return array
+ */
+ public function getStoreConfig(string $store, string $name = null, $default = null)
+ {
+ if ($config = $this->getConfig("stores.{$store}")) {
+ return Arr::get($config, $name, $default);
+ }
+
+ throw new \InvalidArgumentException("Store [$store] not found.");
+ }
+
+ protected function resolveType(string $name)
+ {
+ return $this->getStoreConfig($name, 'type', 'file');
+ }
+
+ protected function resolveConfig(string $name)
+ {
+ return $this->getStoreConfig($name);
+ }
+
+ /**
+ * 连接或者切换缓存
+ * @access public
+ * @param string $name 连接配置名
+ * @return Driver
+ */
+ public function store(string $name = null)
+ {
+ return $this->driver($name);
+ }
+
+ /**
+ * 清空缓冲池
+ * @access public
+ * @return bool
+ */
+ public function clear(): bool
+ {
+ return $this->store()->clear();
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $key 缓存变量名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ return $this->store()->get($key, $default);
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $key 缓存变量名
+ * @param mixed $value 存储数据
+ * @param int|\DateTime $ttl 有效时间 0为永久
+ * @return bool
+ */
+ public function set($key, $value, $ttl = null): bool
+ {
+ return $this->store()->set($key, $value, $ttl);
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $key 缓存变量名
+ * @return bool
+ */
+ public function delete($key): bool
+ {
+ return $this->store()->delete($key);
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param iterable $keys 缓存变量名
+ * @param mixed $default 默认值
+ * @return iterable
+ * @throws InvalidArgumentException
+ */
+ public function getMultiple($keys, $default = null): iterable
+ {
+ return $this->store()->getMultiple($keys, $default);
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param iterable $values 缓存数据
+ * @param null|int|\DateInterval $ttl 有效时间 0为永久
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null): bool
+ {
+ return $this->store()->setMultiple($values, $ttl);
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param iterable $keys 缓存变量名
+ * @return bool
+ * @throws InvalidArgumentException
+ */
+ public function deleteMultiple($keys): bool
+ {
+ return $this->store()->deleteMultiple($keys);
+ }
+
+ /**
+ * 判断缓存是否存在
+ * @access public
+ * @param string $key 缓存变量名
+ * @return bool
+ */
+ public function has($key): bool
+ {
+ return $this->store()->has($key);
+ }
+
+ /**
+ * 缓存标签
+ * @access public
+ * @param string|array $name 标签名
+ * @return TagSet
+ */
+ public function tag($name): TagSet
+ {
+ return $this->store()->tag($name);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Config.php b/vendor/topthink/framework/src/think/Config.php
new file mode 100755
index 0000000..9162e82
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Config.php
@@ -0,0 +1,197 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+/**
+ * 配置管理类
+ * @package think
+ */
+class Config
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [];
+
+ /**
+ * 配置文件目录
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * 配置文件后缀
+ * @var string
+ */
+ protected $ext;
+
+ /**
+ * 构造方法
+ * @access public
+ */
+ public function __construct(string $path = null, string $ext = '.php')
+ {
+ $this->path = $path ?: '';
+ $this->ext = $ext;
+ }
+
+ public static function __make(App $app)
+ {
+ $path = $app->getConfigPath();
+ $ext = $app->getConfigExt();
+
+ return new static($path, $ext);
+ }
+
+ /**
+ * 加载配置文件(多种格式)
+ * @access public
+ * @param string $file 配置文件名
+ * @param string $name 一级配置名
+ * @return array
+ */
+ public function load(string $file, string $name = ''): array
+ {
+ if (is_file($file)) {
+ $filename = $file;
+ } elseif (is_file($this->path . $file . $this->ext)) {
+ $filename = $this->path . $file . $this->ext;
+ }
+
+ if (isset($filename)) {
+ return $this->parse($filename, $name);
+ }
+
+ return $this->config;
+ }
+
+ /**
+ * 解析配置文件
+ * @access public
+ * @param string $file 配置文件名
+ * @param string $name 一级配置名
+ * @return array
+ */
+ protected function parse(string $file, string $name): array
+ {
+ $type = pathinfo($file, PATHINFO_EXTENSION);
+ $config = [];
+ switch ($type) {
+ case 'php':
+ $config = include $file;
+ break;
+ case 'yml':
+ case 'yaml':
+ if (function_exists('yaml_parse_file')) {
+ $config = yaml_parse_file($file);
+ }
+ break;
+ case 'ini':
+ $config = parse_ini_file($file, true, INI_SCANNER_TYPED) ?: [];
+ break;
+ case 'json':
+ $config = json_decode(file_get_contents($file), true);
+ break;
+ }
+
+ return is_array($config) ? $this->set($config, strtolower($name)) : [];
+ }
+
+ /**
+ * 检测配置是否存在
+ * @access public
+ * @param string $name 配置参数名(支持多级配置 .号分割)
+ * @return bool
+ */
+ public function has(string $name): bool
+ {
+ if (false === strpos($name, '.') && !isset($this->config[strtolower($name)])) {
+ return false;
+ }
+
+ return !is_null($this->get($name));
+ }
+
+ /**
+ * 获取一级配置
+ * @access protected
+ * @param string $name 一级配置名
+ * @return array
+ */
+ protected function pull(string $name): array
+ {
+ $name = strtolower($name);
+
+ return $this->config[$name] ?? [];
+ }
+
+ /**
+ * 获取配置参数 为空则获取所有配置
+ * @access public
+ * @param string $name 配置参数名(支持多级配置 .号分割)
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get(string $name = null, $default = null)
+ {
+ // 无参数时获取所有
+ if (empty($name)) {
+ return $this->config;
+ }
+
+ if (false === strpos($name, '.')) {
+ return $this->pull($name);
+ }
+
+ $name = explode('.', $name);
+ $name[0] = strtolower($name[0]);
+ $config = $this->config;
+
+ // 按.拆分成多维数组进行判断
+ foreach ($name as $val) {
+ if (isset($config[$val])) {
+ $config = $config[$val];
+ } else {
+ return $default;
+ }
+ }
+
+ return $config;
+ }
+
+ /**
+ * 设置配置参数 name为数组则为批量设置
+ * @access public
+ * @param array $config 配置参数
+ * @param string $name 配置名
+ * @return array
+ */
+ public function set(array $config, string $name = null): array
+ {
+ if (!empty($name)) {
+ if (isset($this->config[$name])) {
+ $result = array_merge($this->config[$name], $config);
+ } else {
+ $result = $config;
+ }
+
+ $this->config[$name] = $result;
+ } else {
+ $result = $this->config = array_merge($this->config, array_change_key_case($config));
+ }
+
+ return $result;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Console.php b/vendor/topthink/framework/src/think/Console.php
new file mode 100755
index 0000000..27f12ba
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Console.php
@@ -0,0 +1,786 @@
+<?php
+// +----------------------------------------------------------------------
+// | TopThink [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2015 http://www.topthink.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Closure;
+use InvalidArgumentException;
+use LogicException;
+use think\console\Command;
+use think\console\command\Clear;
+use think\console\command\Help;
+use think\console\command\Help as HelpCommand;
+use think\console\command\Lists;
+use think\console\command\make\Command as MakeCommand;
+use think\console\command\make\Controller;
+use think\console\command\make\Event;
+use think\console\command\make\Listener;
+use think\console\command\make\Middleware;
+use think\console\command\make\Model;
+use think\console\command\make\Service;
+use think\console\command\make\Subscribe;
+use think\console\command\make\Validate;
+use think\console\command\optimize\Route;
+use think\console\command\optimize\Schema;
+use think\console\command\RouteList;
+use think\console\command\RunServer;
+use think\console\command\ServiceDiscover;
+use think\console\command\VendorPublish;
+use think\console\command\Version;
+use think\console\Input;
+use think\console\input\Argument as InputArgument;
+use think\console\input\Definition as InputDefinition;
+use think\console\input\Option as InputOption;
+use think\console\Output;
+use think\console\output\driver\Buffer;
+
+/**
+ * 控制台应用管理类
+ */
+class Console
+{
+
+ protected $app;
+
+ /** @var Command[] */
+ protected $commands = [];
+
+ protected $wantHelps = false;
+
+ protected $catchExceptions = true;
+ protected $autoExit = true;
+ protected $definition;
+ protected $defaultCommand = 'list';
+
+ protected $defaultCommands = [
+ 'help' => Help::class,
+ 'list' => Lists::class,
+ 'clear' => Clear::class,
+ 'make:command' => MakeCommand::class,
+ 'make:controller' => Controller::class,
+ 'make:model' => Model::class,
+ 'make:middleware' => Middleware::class,
+ 'make:validate' => Validate::class,
+ 'make:event' => Event::class,
+ 'make:listener' => Listener::class,
+ 'make:service' => Service::class,
+ 'make:subscribe' => Subscribe::class,
+ 'optimize:route' => Route::class,
+ 'optimize:schema' => Schema::class,
+ 'run' => RunServer::class,
+ 'version' => Version::class,
+ 'route:list' => RouteList::class,
+ 'service:discover' => ServiceDiscover::class,
+ 'vendor:publish' => VendorPublish::class,
+ ];
+
+ /**
+ * 启动器
+ * @var array
+ */
+ protected static $startCallbacks = [];
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+
+ $this->initialize();
+
+ $this->definition = $this->getDefaultInputDefinition();
+
+ //加载指令
+ $this->loadCommands();
+
+ $this->start();
+ }
+
+ /**
+ * 初始化
+ */
+ protected function initialize()
+ {
+ if (!$this->app->initialized()) {
+ $this->app->initialize();
+ }
+ $this->makeRequest();
+ }
+
+ /**
+ * 构造request
+ */
+ protected function makeRequest()
+ {
+ $url = $this->app->config->get('app.url', 'http://localhost');
+
+ $components = parse_url($url);
+
+ $server = $_SERVER;
+
+ if (isset($components['path'])) {
+ $server = array_merge($server, [
+ 'SCRIPT_FILENAME' => $components['path'],
+ 'SCRIPT_NAME' => $components['path'],
+ 'REQUEST_URI' => $components['path'],
+ ]);
+ }
+
+ if (isset($components['host'])) {
+ $server['SERVER_NAME'] = $components['host'];
+ $server['HTTP_HOST'] = $components['host'];
+ }
+
+ if (isset($components['scheme'])) {
+ if ('https' === $components['scheme']) {
+ $server['HTTPS'] = 'on';
+ $server['SERVER_PORT'] = 443;
+ } else {
+ unset($server['HTTPS']);
+ $server['SERVER_PORT'] = 80;
+ }
+ }
+
+ if (isset($components['port'])) {
+ $server['SERVER_PORT'] = $components['port'];
+ $server['HTTP_HOST'] .= ':' . $components['port'];
+ }
+
+ /** @var Request $request */
+ $request = $this->app->make('request');
+
+ $request->withServer($server);
+ }
+
+ /**
+ * 添加初始化器
+ * @param Closure $callback
+ */
+ public static function starting(Closure $callback): void
+ {
+ static::$startCallbacks[] = $callback;
+ }
+
+ /**
+ * 清空启动器
+ */
+ public static function flushStartCallbacks(): void
+ {
+ static::$startCallbacks = [];
+ }
+
+ /**
+ * 设置执行用户
+ * @param $user
+ */
+ public static function setUser(string $user): void
+ {
+ if (extension_loaded('posix')) {
+ $user = posix_getpwnam($user);
+
+ if (!empty($user)) {
+ posix_setgid($user['gid']);
+ posix_setuid($user['uid']);
+ }
+ }
+ }
+
+ /**
+ * 启动
+ */
+ protected function start(): void
+ {
+ foreach (static::$startCallbacks as $callback) {
+ $callback($this);
+ }
+ }
+
+ /**
+ * 加载指令
+ * @access protected
+ */
+ protected function loadCommands(): void
+ {
+ $commands = $this->app->config->get('console.commands', []);
+ $commands = array_merge($this->defaultCommands, $commands);
+
+ $this->addCommands($commands);
+ }
+
+ /**
+ * @access public
+ * @param string $command
+ * @param array $parameters
+ * @param string $driver
+ * @return Output|Buffer
+ */
+ public function call(string $command, array $parameters = [], string $driver = 'buffer')
+ {
+ array_unshift($parameters, $command);
+
+ $input = new Input($parameters);
+ $output = new Output($driver);
+
+ $this->setCatchExceptions(false);
+ $this->find($command)->run($input, $output);
+
+ return $output;
+ }
+
+ /**
+ * 执行当前的指令
+ * @access public
+ * @return int
+ * @throws \Exception
+ * @api
+ */
+ public function run()
+ {
+ $input = new Input();
+ $output = new Output();
+
+ $this->configureIO($input, $output);
+
+ try {
+ $exitCode = $this->doRun($input, $output);
+ } catch (\Exception $e) {
+ if (!$this->catchExceptions) {
+ throw $e;
+ }
+
+ $output->renderException($e);
+
+ $exitCode = $e->getCode();
+ if (is_numeric($exitCode)) {
+ $exitCode = (int) $exitCode;
+ if (0 === $exitCode) {
+ $exitCode = 1;
+ }
+ } else {
+ $exitCode = 1;
+ }
+ }
+
+ if ($this->autoExit) {
+ if ($exitCode > 255) {
+ $exitCode = 255;
+ }
+
+ exit($exitCode);
+ }
+
+ return $exitCode;
+ }
+
+ /**
+ * 执行指令
+ * @access public
+ * @param Input $input
+ * @param Output $output
+ * @return int
+ */
+ public function doRun(Input $input, Output $output)
+ {
+ if (true === $input->hasParameterOption(['--version', '-V'])) {
+ $output->writeln($this->getLongVersion());
+
+ return 0;
+ }
+
+ $name = $this->getCommandName($input);
+
+ if (true === $input->hasParameterOption(['--help', '-h'])) {
+ if (!$name) {
+ $name = 'help';
+ $input = new Input(['help']);
+ } else {
+ $this->wantHelps = true;
+ }
+ }
+
+ if (!$name) {
+ $name = $this->defaultCommand;
+ $input = new Input([$this->defaultCommand]);
+ }
+
+ $command = $this->find($name);
+
+ return $this->doRunCommand($command, $input, $output);
+ }
+
+ /**
+ * 设置输入参数定义
+ * @access public
+ * @param InputDefinition $definition
+ */
+ public function setDefinition(InputDefinition $definition): void
+ {
+ $this->definition = $definition;
+ }
+
+ /**
+ * 获取输入参数定义
+ * @access public
+ * @return InputDefinition The InputDefinition instance
+ */
+ public function getDefinition(): InputDefinition
+ {
+ return $this->definition;
+ }
+
+ /**
+ * Gets the help message.
+ * @access public
+ * @return string A help message.
+ */
+ public function getHelp(): string
+ {
+ return $this->getLongVersion();
+ }
+
+ /**
+ * 是否捕获异常
+ * @access public
+ * @param bool $boolean
+ * @api
+ */
+ public function setCatchExceptions(bool $boolean): void
+ {
+ $this->catchExceptions = $boolean;
+ }
+
+ /**
+ * 是否自动退出
+ * @access public
+ * @param bool $boolean
+ * @api
+ */
+ public function setAutoExit(bool $boolean): void
+ {
+ $this->autoExit = $boolean;
+ }
+
+ /**
+ * 获取完整的版本号
+ * @access public
+ * @return string
+ */
+ public function getLongVersion(): string
+ {
+ if ($this->app->version()) {
+ return sprintf('version <comment>%s</comment>', $this->app->version());
+ }
+
+ return '<info>Console Tool</info>';
+ }
+
+ /**
+ * 添加指令集
+ * @access public
+ * @param array $commands
+ */
+ public function addCommands(array $commands): void
+ {
+ foreach ($commands as $key => $command) {
+ if (is_subclass_of($command, Command::class)) {
+ // 注册指令
+ $this->addCommand($command, is_numeric($key) ? '' : $key);
+ }
+ }
+ }
+
+ /**
+ * 添加一个指令
+ * @access public
+ * @param string|Command $command 指令对象或者指令类名
+ * @param string $name 指令名 留空则自动获取
+ * @return Command|void
+ */
+ public function addCommand($command, string $name = '')
+ {
+ if ($name) {
+ $this->commands[$name] = $command;
+ return;
+ }
+
+ if (is_string($command)) {
+ $command = $this->app->invokeClass($command);
+ }
+
+ $command->setConsole($this);
+
+ if (!$command->isEnabled()) {
+ $command->setConsole(null);
+ return;
+ }
+
+ $command->setApp($this->app);
+
+ if (null === $command->getDefinition()) {
+ throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
+ }
+
+ $this->commands[$command->getName()] = $command;
+
+ foreach ($command->getAliases() as $alias) {
+ $this->commands[$alias] = $command;
+ }
+
+ return $command;
+ }
+
+ /**
+ * 获取指令
+ * @access public
+ * @param string $name 指令名称
+ * @return Command
+ * @throws InvalidArgumentException
+ */
+ public function getCommand(string $name): Command
+ {
+ if (!isset($this->commands[$name])) {
+ throw new InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
+ }
+
+ $command = $this->commands[$name];
+
+ if (is_string($command)) {
+ $command = $this->app->invokeClass($command);
+ /** @var Command $command */
+ $command->setConsole($this);
+ $command->setApp($this->app);
+ }
+
+ if ($this->wantHelps) {
+ $this->wantHelps = false;
+
+ /** @var HelpCommand $helpCommand */
+ $helpCommand = $this->getCommand('help');
+ $helpCommand->setCommand($command);
+
+ return $helpCommand;
+ }
+
+ return $command;
+ }
+
+ /**
+ * 某个指令是否存在
+ * @access public
+ * @param string $name 指令名称
+ * @return bool
+ */
+ public function hasCommand(string $name): bool
+ {
+ return isset($this->commands[$name]);
+ }
+
+ /**
+ * 获取所有的命名空间
+ * @access public
+ * @return array
+ */
+ public function getNamespaces(): array
+ {
+ $namespaces = [];
+ foreach ($this->commands as $key => $command) {
+ if (is_string($command)) {
+ $namespaces = array_merge($namespaces, $this->extractAllNamespaces($key));
+ } else {
+ $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
+
+ foreach ($command->getAliases() as $alias) {
+ $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
+ }
+ }
+ }
+
+ return array_values(array_unique(array_filter($namespaces)));
+ }
+
+ /**
+ * 查找注册命名空间中的名称或缩写。
+ * @access public
+ * @param string $namespace
+ * @return string
+ * @throws InvalidArgumentException
+ */
+ public function findNamespace(string $namespace): string
+ {
+ $allNamespaces = $this->getNamespaces();
+ $expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
+ return preg_quote($matches[1]) . '[^:]*';
+ }, $namespace);
+ $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces);
+
+ if (empty($namespaces)) {
+ $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
+
+ if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
+ if (1 == count($alternatives)) {
+ $message .= "\n\nDid you mean this?\n ";
+ } else {
+ $message .= "\n\nDid you mean one of these?\n ";
+ }
+
+ $message .= implode("\n ", $alternatives);
+ }
+
+ throw new InvalidArgumentException($message);
+ }
+
+ $exact = in_array($namespace, $namespaces, true);
+ if (count($namespaces) > 1 && !$exact) {
+ throw new InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))));
+ }
+
+ return $exact ? $namespace : reset($namespaces);
+ }
+
+ /**
+ * 查找指令
+ * @access public
+ * @param string $name 名称或者别名
+ * @return Command
+ * @throws InvalidArgumentException
+ */
+ public function find(string $name): Command
+ {
+ $allCommands = array_keys($this->commands);
+
+ $expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
+ return preg_quote($matches[1]) . '[^:]*';
+ }, $name);
+
+ $commands = preg_grep('{^' . $expr . '}', $allCommands);
+
+ if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) {
+ if (false !== $pos = strrpos($name, ':')) {
+ $this->findNamespace(substr($name, 0, $pos));
+ }
+
+ $message = sprintf('Command "%s" is not defined.', $name);
+
+ if ($alternatives = $this->findAlternatives($name, $allCommands)) {
+ if (1 == count($alternatives)) {
+ $message .= "\n\nDid you mean this?\n ";
+ } else {
+ $message .= "\n\nDid you mean one of these?\n ";
+ }
+ $message .= implode("\n ", $alternatives);
+ }
+
+ throw new InvalidArgumentException($message);
+ }
+
+ $exact = in_array($name, $commands, true);
+ if (count($commands) > 1 && !$exact) {
+ $suggestions = $this->getAbbreviationSuggestions(array_values($commands));
+
+ throw new InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
+ }
+
+ return $this->getCommand($exact ? $name : reset($commands));
+ }
+
+ /**
+ * 获取所有的指令
+ * @access public
+ * @param string $namespace 命名空间
+ * @return Command[]
+ * @api
+ */
+ public function all(string $namespace = null): array
+ {
+ if (null === $namespace) {
+ return $this->commands;
+ }
+
+ $commands = [];
+ foreach ($this->commands as $name => $command) {
+ if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) {
+ $commands[$name] = $command;
+ }
+ }
+
+ return $commands;
+ }
+
+ /**
+ * 配置基于用户的参数和选项的输入和输出实例。
+ * @access protected
+ * @param Input $input 输入实例
+ * @param Output $output 输出实例
+ */
+ protected function configureIO(Input $input, Output $output): void
+ {
+ if (true === $input->hasParameterOption(['--ansi'])) {
+ $output->setDecorated(true);
+ } elseif (true === $input->hasParameterOption(['--no-ansi'])) {
+ $output->setDecorated(false);
+ }
+
+ if (true === $input->hasParameterOption(['--no-interaction', '-n'])) {
+ $input->setInteractive(false);
+ }
+
+ if (true === $input->hasParameterOption(['--quiet', '-q'])) {
+ $output->setVerbosity(Output::VERBOSITY_QUIET);
+ } elseif ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
+ $output->setVerbosity(Output::VERBOSITY_DEBUG);
+ } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
+ $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE);
+ } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
+ $output->setVerbosity(Output::VERBOSITY_VERBOSE);
+ }
+ }
+
+ /**
+ * 执行指令
+ * @access protected
+ * @param Command $command 指令实例
+ * @param Input $input 输入实例
+ * @param Output $output 输出实例
+ * @return int
+ * @throws \Exception
+ */
+ protected function doRunCommand(Command $command, Input $input, Output $output)
+ {
+ return $command->run($input, $output);
+ }
+
+ /**
+ * 获取指令的基础名称
+ * @access protected
+ * @param Input $input
+ * @return string
+ */
+ protected function getCommandName(Input $input): string
+ {
+ return $input->getFirstArgument() ?: '';
+ }
+
+ /**
+ * 获取默认输入定义
+ * @access protected
+ * @return InputDefinition
+ */
+ protected function getDefaultInputDefinition(): InputDefinition
+ {
+ return new InputDefinition([
+ new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
+ new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
+ new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'),
+ new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
+ new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
+ new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
+ new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
+ new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
+ ]);
+ }
+
+ /**
+ * 获取可能的建议
+ * @access private
+ * @param array $abbrevs
+ * @return string
+ */
+ private function getAbbreviationSuggestions(array $abbrevs): string
+ {
+ return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
+ }
+
+ /**
+ * 返回命名空间部分
+ * @access public
+ * @param string $name 指令
+ * @param int $limit 部分的命名空间的最大数量
+ * @return string
+ */
+ public function extractNamespace(string $name, int $limit = 0): string
+ {
+ $parts = explode(':', $name);
+ array_pop($parts);
+
+ return implode(':', 0 === $limit ? $parts : array_slice($parts, 0, $limit));
+ }
+
+ /**
+ * 查找可替代的建议
+ * @access private
+ * @param string $name
+ * @param array|\Traversable $collection
+ * @return array
+ */
+ private function findAlternatives(string $name, $collection): array
+ {
+ $threshold = 1e3;
+ $alternatives = [];
+
+ $collectionParts = [];
+ foreach ($collection as $item) {
+ $collectionParts[$item] = explode(':', $item);
+ }
+
+ foreach (explode(':', $name) as $i => $subname) {
+ foreach ($collectionParts as $collectionName => $parts) {
+ $exists = isset($alternatives[$collectionName]);
+ if (!isset($parts[$i]) && $exists) {
+ $alternatives[$collectionName] += $threshold;
+ continue;
+ } elseif (!isset($parts[$i])) {
+ continue;
+ }
+
+ $lev = levenshtein($subname, $parts[$i]);
+ if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
+ $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
+ } elseif ($exists) {
+ $alternatives[$collectionName] += $threshold;
+ }
+ }
+ }
+
+ foreach ($collection as $item) {
+ $lev = levenshtein($name, $item);
+ if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
+ $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
+ }
+ }
+
+ $alternatives = array_filter($alternatives, function ($lev) use ($threshold) {
+ return $lev < 2 * $threshold;
+ });
+ asort($alternatives);
+
+ return array_keys($alternatives);
+ }
+
+ /**
+ * 返回所有的命名空间
+ * @access private
+ * @param string $name
+ * @return array
+ */
+ private function extractAllNamespaces(string $name): array
+ {
+ $parts = explode(':', $name, -1);
+ $namespaces = [];
+
+ foreach ($parts as $part) {
+ if (count($namespaces)) {
+ $namespaces[] = end($namespaces) . ':' . $part;
+ } else {
+ $namespaces[] = $part;
+ }
+ }
+
+ return $namespaces;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Container.php b/vendor/topthink/framework/src/think/Container.php
new file mode 100755
index 0000000..f286c73
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Container.php
@@ -0,0 +1,561 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ArrayAccess;
+use ArrayIterator;
+use Closure;
+use Countable;
+use InvalidArgumentException;
+use IteratorAggregate;
+use Psr\Container\ContainerInterface;
+use ReflectionClass;
+use ReflectionException;
+use ReflectionFunction;
+use ReflectionFunctionAbstract;
+use ReflectionMethod;
+use think\exception\ClassNotFoundException;
+use think\exception\FuncNotFoundException;
+use think\helper\Str;
+use Traversable;
+
+/**
+ * 容器管理类 支持PSR-11
+ */
+class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable
+{
+ /**
+ * 容器对象实例
+ * @var Container|Closure
+ */
+ protected static $instance;
+
+ /**
+ * 容器中的对象实例
+ * @var array
+ */
+ protected $instances = [];
+
+ /**
+ * 容器绑定标识
+ * @var array
+ */
+ protected $bind = [];
+
+ /**
+ * 容器回调
+ * @var array
+ */
+ protected $invokeCallback = [];
+
+ /**
+ * 获取当前容器的实例(单例)
+ * @access public
+ * @return static
+ */
+ public static function getInstance()
+ {
+ if (is_null(static::$instance)) {
+ static::$instance = new static;
+ }
+
+ if (static::$instance instanceof Closure) {
+ return (static::$instance)();
+ }
+
+ return static::$instance;
+ }
+
+ /**
+ * 设置当前容器的实例
+ * @access public
+ * @param object|Closure $instance
+ * @return void
+ */
+ public static function setInstance($instance): void
+ {
+ static::$instance = $instance;
+ }
+
+ /**
+ * 注册一个容器对象回调
+ *
+ * @param string|Closure $abstract
+ * @param Closure|null $callback
+ * @return void
+ */
+ public function resolving($abstract, Closure $callback = null): void
+ {
+ if ($abstract instanceof Closure) {
+ $this->invokeCallback['*'][] = $abstract;
+ return;
+ }
+
+ $abstract = $this->getAlias($abstract);
+
+ $this->invokeCallback[$abstract][] = $callback;
+ }
+
+ /**
+ * 获取容器中的对象实例 不存在则创建
+ * @template T
+ * @param string|class-string<T> $abstract 类名或者标识
+ * @param array $vars 变量
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return T|object
+ */
+ public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
+ {
+ return static::getInstance()->make($abstract, $vars, $newInstance);
+ }
+
+ /**
+ * 获取容器中的对象实例
+ * @template T
+ * @param string|class-string<T> $abstract 类名或者标识
+ * @return T|object
+ */
+ public function get($abstract)
+ {
+ if ($this->has($abstract)) {
+ return $this->make($abstract);
+ }
+
+ throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
+ }
+
+ /**
+ * 绑定一个类、闭包、实例、接口实现到容器
+ * @access public
+ * @param string|array $abstract 类标识、接口
+ * @param mixed $concrete 要绑定的类、闭包或者实例
+ * @return $this
+ */
+ public function bind($abstract, $concrete = null)
+ {
+ if (is_array($abstract)) {
+ foreach ($abstract as $key => $val) {
+ $this->bind($key, $val);
+ }
+ } elseif ($concrete instanceof Closure) {
+ $this->bind[$abstract] = $concrete;
+ } elseif (is_object($concrete)) {
+ $this->instance($abstract, $concrete);
+ } else {
+ $abstract = $this->getAlias($abstract);
+ if ($abstract != $concrete) {
+ $this->bind[$abstract] = $concrete;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 根据别名获取真实类名
+ * @param string $abstract
+ * @return string
+ */
+ public function getAlias(string $abstract): string
+ {
+ if (isset($this->bind[$abstract])) {
+ $bind = $this->bind[$abstract];
+
+ if (is_string($bind)) {
+ return $this->getAlias($bind);
+ }
+ }
+
+ return $abstract;
+ }
+
+ /**
+ * 绑定一个类实例到容器
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @param object $instance 类的实例
+ * @return $this
+ */
+ public function instance(string $abstract, $instance)
+ {
+ $abstract = $this->getAlias($abstract);
+
+ $this->instances[$abstract] = $instance;
+
+ return $this;
+ }
+
+ /**
+ * 判断容器中是否存在类及标识
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @return bool
+ */
+ public function bound(string $abstract): bool
+ {
+ return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
+ }
+
+ /**
+ * 判断容器中是否存在类及标识
+ * @access public
+ * @param string $name 类名或者标识
+ * @return bool
+ */
+ public function has($name): bool
+ {
+ return $this->bound($name);
+ }
+
+ /**
+ * 判断容器中是否存在对象实例
+ * @access public
+ * @param string $abstract 类名或者标识
+ * @return bool
+ */
+ public function exists(string $abstract): bool
+ {
+ $abstract = $this->getAlias($abstract);
+
+ return isset($this->instances[$abstract]);
+ }
+
+ /**
+ * 创建类的实例 已经存在则直接获取
+ * @template T
+ * @param string|class-string<T> $abstract 类名或者标识
+ * @param array $vars 变量
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return T|object
+ */
+ public function make(string $abstract, array $vars = [], bool $newInstance = false)
+ {
+ $abstract = $this->getAlias($abstract);
+
+ if (isset($this->instances[$abstract]) && !$newInstance) {
+ return $this->instances[$abstract];
+ }
+
+ if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
+ $object = $this->invokeFunction($this->bind[$abstract], $vars);
+ } else {
+ $object = $this->invokeClass($abstract, $vars);
+ }
+
+ if (!$newInstance) {
+ $this->instances[$abstract] = $object;
+ }
+
+ return $object;
+ }
+
+ /**
+ * 删除容器中的对象实例
+ * @access public
+ * @param string $name 类名或者标识
+ * @return void
+ */
+ public function delete($name)
+ {
+ $name = $this->getAlias($name);
+
+ if (isset($this->instances[$name])) {
+ unset($this->instances[$name]);
+ }
+ }
+
+ /**
+ * 执行函数或者闭包方法 支持参数调用
+ * @access public
+ * @param string|Closure $function 函数或者闭包
+ * @param array $vars 参数
+ * @return mixed
+ */
+ public function invokeFunction($function, array $vars = [])
+ {
+ try {
+ $reflect = new ReflectionFunction($function);
+ } catch (ReflectionException $e) {
+ throw new FuncNotFoundException("function not exists: {$function}()", $function, $e);
+ }
+
+ $args = $this->bindParams($reflect, $vars);
+
+ return $function(...$args);
+ }
+
+ /**
+ * 调用反射执行类的方法 支持参数绑定
+ * @access public
+ * @param mixed $method 方法
+ * @param array $vars 参数
+ * @param bool $accessible 设置是否可访问
+ * @return mixed
+ */
+ public function invokeMethod($method, array $vars = [], bool $accessible = false)
+ {
+ if (is_array($method)) {
+ [$class, $method] = $method;
+
+ $class = is_object($class) ? $class : $this->invokeClass($class);
+ } else {
+ // 静态方法
+ [$class, $method] = explode('::', $method);
+ }
+
+ try {
+ $reflect = new ReflectionMethod($class, $method);
+ } catch (ReflectionException $e) {
+ $class = is_object($class) ? get_class($class) : $class;
+ throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e);
+ }
+
+ $args = $this->bindParams($reflect, $vars);
+
+ if ($accessible) {
+ $reflect->setAccessible($accessible);
+ }
+
+ return $reflect->invokeArgs(is_object($class) ? $class : null, $args);
+ }
+
+ /**
+ * 调用反射执行类的方法 支持参数绑定
+ * @access public
+ * @param object $instance 对象实例
+ * @param mixed $reflect 反射类
+ * @param array $vars 参数
+ * @return mixed
+ */
+ public function invokeReflectMethod($instance, $reflect, array $vars = [])
+ {
+ $args = $this->bindParams($reflect, $vars);
+
+ return $reflect->invokeArgs($instance, $args);
+ }
+
+ /**
+ * 调用反射执行callable 支持参数绑定
+ * @access public
+ * @param mixed $callable
+ * @param array $vars 参数
+ * @param bool $accessible 设置是否可访问
+ * @return mixed
+ */
+ public function invoke($callable, array $vars = [], bool $accessible = false)
+ {
+ if ($callable instanceof Closure) {
+ return $this->invokeFunction($callable, $vars);
+ } elseif (is_string($callable) && false === strpos($callable, '::')) {
+ return $this->invokeFunction($callable, $vars);
+ } else {
+ return $this->invokeMethod($callable, $vars, $accessible);
+ }
+ }
+
+ /**
+ * 调用反射执行类的实例化 支持依赖注入
+ * @access public
+ * @param string $class 类名
+ * @param array $vars 参数
+ * @return mixed
+ */
+ public function invokeClass(string $class, array $vars = [])
+ {
+ try {
+ $reflect = new ReflectionClass($class);
+ } catch (ReflectionException $e) {
+ throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
+ }
+
+ if ($reflect->hasMethod('__make')) {
+ $method = $reflect->getMethod('__make');
+ if ($method->isPublic() && $method->isStatic()) {
+ $args = $this->bindParams($method, $vars);
+ $object = $method->invokeArgs(null, $args);
+ $this->invokeAfter($class, $object);
+ return $object;
+ }
+ }
+
+ $constructor = $reflect->getConstructor();
+
+ $args = $constructor ? $this->bindParams($constructor, $vars) : [];
+
+ $object = $reflect->newInstanceArgs($args);
+
+ $this->invokeAfter($class, $object);
+
+ return $object;
+ }
+
+ /**
+ * 执行invokeClass回调
+ * @access protected
+ * @param string $class 对象类名
+ * @param object $object 容器对象实例
+ * @return void
+ */
+ protected function invokeAfter(string $class, $object): void
+ {
+ if (isset($this->invokeCallback['*'])) {
+ foreach ($this->invokeCallback['*'] as $callback) {
+ $callback($object, $this);
+ }
+ }
+
+ if (isset($this->invokeCallback[$class])) {
+ foreach ($this->invokeCallback[$class] as $callback) {
+ $callback($object, $this);
+ }
+ }
+ }
+
+ /**
+ * 绑定参数
+ * @access protected
+ * @param ReflectionFunctionAbstract $reflect 反射类
+ * @param array $vars 参数
+ * @return array
+ */
+ protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array
+ {
+ if ($reflect->getNumberOfParameters() == 0) {
+ return [];
+ }
+
+ // 判断数组类型 数字数组时按顺序绑定参数
+ reset($vars);
+ $type = key($vars) === 0 ? 1 : 0;
+ $params = $reflect->getParameters();
+ $args = [];
+
+ foreach ($params as $param) {
+ $name = $param->getName();
+ $lowerName = Str::snake($name);
+ $reflectionType = $param->getType();
+
+ if ($param->isVariadic()) {
+ return array_merge($args, array_values($vars));
+ } elseif ($reflectionType && $reflectionType instanceof \ReflectionNamedType && $reflectionType->isBuiltin() === false) {
+ $args[] = $this->getObjectParam($reflectionType->getName(), $vars);
+ } elseif (1 == $type && !empty($vars)) {
+ $args[] = array_shift($vars);
+ } elseif (0 == $type && array_key_exists($name, $vars)) {
+ $args[] = $vars[$name];
+ } elseif (0 == $type && array_key_exists($lowerName, $vars)) {
+ $args[] = $vars[$lowerName];
+ } elseif ($param->isDefaultValueAvailable()) {
+ $args[] = $param->getDefaultValue();
+ } else {
+ throw new InvalidArgumentException('method param miss:' . $name);
+ }
+ }
+
+ return $args;
+ }
+
+ /**
+ * 创建工厂对象实例
+ * @param string $name 工厂类名
+ * @param string $namespace 默认命名空间
+ * @param array $args
+ * @return mixed
+ * @deprecated
+ * @access public
+ */
+ public static function factory(string $name, string $namespace = '', ...$args)
+ {
+ $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name);
+
+ return Container::getInstance()->invokeClass($class, $args);
+ }
+
+ /**
+ * 获取对象类型的参数值
+ * @access protected
+ * @param string $className 类名
+ * @param array $vars 参数
+ * @return mixed
+ */
+ protected function getObjectParam(string $className, array &$vars)
+ {
+ $array = $vars;
+ $value = array_shift($array);
+
+ if ($value instanceof $className) {
+ $result = $value;
+ array_shift($vars);
+ } else {
+ $result = $this->make($className);
+ }
+
+ return $result;
+ }
+
+ public function __set($name, $value)
+ {
+ $this->bind($name, $value);
+ }
+
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ public function __isset($name): bool
+ {
+ return $this->exists($name);
+ }
+
+ public function __unset($name)
+ {
+ $this->delete($name);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetExists($key): bool
+ {
+ return $this->exists($key);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetGet($key)
+ {
+ return $this->make($key);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetSet($key, $value)
+ {
+ $this->bind($key, $value);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($key)
+ {
+ $this->delete($key);
+ }
+
+ //Countable
+ public function count(): int
+ {
+ return count($this->instances);
+ }
+
+ //IteratorAggregate
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator($this->instances);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Cookie.php b/vendor/topthink/framework/src/think/Cookie.php
new file mode 100755
index 0000000..04774a6
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Cookie.php
@@ -0,0 +1,232 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use DateTimeInterface;
+
+/**
+ * Cookie管理类
+ * @package think
+ */
+class Cookie
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [
+ // cookie 保存时间
+ 'expire' => 0,
+ // cookie 保存路径
+ 'path' => '/',
+ // cookie 有效域名
+ 'domain' => '',
+ // cookie 启用安全传输
+ 'secure' => false,
+ // httponly设置
+ 'httponly' => false,
+ // samesite 设置,支持 'strict' 'lax'
+ 'samesite' => '',
+ ];
+
+ /**
+ * Cookie写入数据
+ * @var array
+ */
+ protected $cookie = [];
+
+ /**
+ * 当前Request对象
+ * @var Request
+ */
+ protected $request;
+
+ /**
+ * 构造方法
+ * @access public
+ */
+ public function __construct(Request $request, array $config = [])
+ {
+ $this->request = $request;
+ $this->config = array_merge($this->config, array_change_key_case($config));
+ }
+
+ public static function __make(Request $request, Config $config)
+ {
+ return new static($request, $config->get('cookie'));
+ }
+
+ /**
+ * 获取cookie
+ * @access public
+ * @param mixed $name 数据名称
+ * @param string $default 默认值
+ * @return mixed
+ */
+ public function get(string $name = '', $default = null)
+ {
+ return $this->request->cookie($name, $default);
+ }
+
+ /**
+ * 是否存在Cookie参数
+ * @access public
+ * @param string $name 变量名
+ * @return bool
+ */
+ public function has(string $name): bool
+ {
+ return $this->request->has($name, 'cookie');
+ }
+
+ /**
+ * Cookie 设置
+ *
+ * @access public
+ * @param string $name cookie名称
+ * @param string $value cookie值
+ * @param mixed $option 可选参数
+ * @return void
+ */
+ public function set(string $name, string $value, $option = null): void
+ {
+ // 参数设置(会覆盖黙认设置)
+ if (!is_null($option)) {
+ if (is_numeric($option) || $option instanceof DateTimeInterface) {
+ $option = ['expire' => $option];
+ }
+
+ $config = array_merge($this->config, array_change_key_case($option));
+ } else {
+ $config = $this->config;
+ }
+
+ if ($config['expire'] instanceof DateTimeInterface) {
+ $expire = $config['expire']->getTimestamp();
+ } else {
+ $expire = !empty($config['expire']) ? time() + intval($config['expire']) : 0;
+ }
+
+ $this->setCookie($name, $value, $expire, $config);
+ }
+
+ /**
+ * Cookie 保存
+ *
+ * @access public
+ * @param string $name cookie名称
+ * @param string $value cookie值
+ * @param int $expire 有效期
+ * @param array $option 可选参数
+ * @return void
+ */
+ protected function setCookie(string $name, string $value, int $expire, array $option = []): void
+ {
+ $this->cookie[$name] = [$value, $expire, $option];
+ }
+
+ /**
+ * 永久保存Cookie数据
+ * @access public
+ * @param string $name cookie名称
+ * @param string $value cookie值
+ * @param mixed $option 可选参数 可能会是 null|integer|string
+ * @return void
+ */
+ public function forever(string $name, string $value = '', $option = null): void
+ {
+ if (is_null($option) || is_numeric($option)) {
+ $option = [];
+ }
+
+ $option['expire'] = 315360000;
+
+ $this->set($name, $value, $option);
+ }
+
+ /**
+ * Cookie删除
+ * @access public
+ * @param string $name cookie名称
+ * @param array $options cookie参数
+ * @return void
+ */
+ public function delete(string $name, array $options = []): void
+ {
+ $config = array_merge($this->config, array_change_key_case($options));
+ $this->setCookie($name, '', time() - 3600, $config);
+ }
+
+ /**
+ * 获取cookie保存数据
+ * @access public
+ * @return array
+ */
+ public function getCookie(): array
+ {
+ return $this->cookie;
+ }
+
+ /**
+ * 保存Cookie
+ * @access public
+ * @return void
+ */
+ public function save(): void
+ {
+ foreach ($this->cookie as $name => $val) {
+ [$value, $expire, $option] = $val;
+
+ $this->saveCookie(
+ $name,
+ $value,
+ $expire,
+ $option['path'],
+ $option['domain'],
+ $option['secure'] ? true : false,
+ $option['httponly'] ? true : false,
+ $option['samesite']
+ );
+ }
+ }
+
+ /**
+ * 保存Cookie
+ * @access public
+ * @param string $name cookie名称
+ * @param string $value cookie值
+ * @param int $expire cookie过期时间
+ * @param string $path 有效的服务器路径
+ * @param string $domain 有效域名/子域名
+ * @param bool $secure 是否仅仅通过HTTPS
+ * @param bool $httponly 仅可通过HTTP访问
+ * @param string $samesite 防止CSRF攻击和用户追踪
+ * @return void
+ */
+ protected function saveCookie(string $name, string $value, int $expire, string $path, string $domain, bool $secure, bool $httponly, string $samesite): void
+ {
+ if (version_compare(PHP_VERSION, '7.3.0', '>=')) {
+ setcookie($name, $value, [
+ 'expires' => $expire,
+ 'path' => $path,
+ 'domain' => $domain,
+ 'secure' => $secure,
+ 'httponly' => $httponly,
+ 'samesite' => $samesite,
+ ]);
+ } else {
+ setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
+ }
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Db.php b/vendor/topthink/framework/src/think/Db.php
new file mode 100755
index 0000000..0048874
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Db.php
@@ -0,0 +1,117 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+/**
+ * 数据库管理类
+ * @package think
+ * @property Config $config
+ */
+class Db extends DbManager
+{
+ /**
+ * @param Event $event
+ * @param Config $config
+ * @param Log $log
+ * @param Cache $cache
+ * @return Db
+ * @codeCoverageIgnore
+ */
+ public static function __make(Event $event, Config $config, Log $log, Cache $cache)
+ {
+ $db = new static();
+ $db->setConfig($config);
+ $db->setEvent($event);
+ $db->setLog($log);
+
+ $store = $db->getConfig('cache_store');
+ $db->setCache($cache->store($store));
+ $db->triggerSql();
+
+ return $db;
+ }
+
+ /**
+ * 注入模型对象
+ * @access public
+ * @return void
+ */
+ protected function modelMaker()
+ {
+ }
+
+ /**
+ * 设置配置对象
+ * @access public
+ * @param Config $config 配置对象
+ * @return void
+ */
+ public function setConfig($config): void
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * 获取配置参数
+ * @access public
+ * @param string $name 配置参数
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getConfig(string $name = '', $default = null)
+ {
+ if ('' !== $name) {
+ return $this->config->get('database.' . $name, $default);
+ }
+
+ return $this->config->get('database', []);
+ }
+
+ /**
+ * 设置Event对象
+ * @param Event $event
+ */
+ public function setEvent(Event $event): void
+ {
+ $this->event = $event;
+ }
+
+ /**
+ * 注册回调方法
+ * @access public
+ * @param string $event 事件名
+ * @param callable $callback 回调方法
+ * @return void
+ */
+ public function event(string $event, callable $callback): void
+ {
+ if ($this->event) {
+ $this->event->listen('db.' . $event, $callback);
+ }
+ }
+
+ /**
+ * 触发事件
+ * @access public
+ * @param string $event 事件名
+ * @param mixed $params 传入参数
+ * @param bool $once
+ * @return mixed
+ */
+ public function trigger(string $event, $params = null, bool $once = false)
+ {
+ if ($this->event) {
+ return $this->event->trigger('db.' . $event, $params, $once);
+ }
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Env.php b/vendor/topthink/framework/src/think/Env.php
new file mode 100755
index 0000000..9e1b7f4
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Env.php
@@ -0,0 +1,201 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ArrayAccess;
+
+/**
+ * Env管理类
+ * @package think
+ */
+class Env implements ArrayAccess
+{
+ /**
+ * 环境变量数据
+ * @var array
+ */
+ protected $data = [];
+
+ /**
+ * 数据转换映射
+ * @var array
+ */
+ protected $convert = [
+ 'true' => true,
+ 'false' => false,
+ 'off' => false,
+ 'on' => true,
+ ];
+
+ public function __construct()
+ {
+ $this->data = $_ENV;
+ }
+
+ /**
+ * 读取环境变量定义文件
+ * @access public
+ * @param string $file 环境变量定义文件
+ * @return void
+ */
+ public function load(string $file): void
+ {
+ $env = parse_ini_file($file, true, INI_SCANNER_RAW) ?: [];
+ $this->set($env);
+ }
+
+ /**
+ * 获取环境变量值
+ * @access public
+ * @param string $name 环境变量名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get(string $name = null, $default = null)
+ {
+ if (is_null($name)) {
+ return $this->data;
+ }
+
+ $name = strtoupper(str_replace('.', '_', $name));
+ if (isset($this->data[$name])) {
+ $result = $this->data[$name];
+
+ if (is_string($result) && isset($this->convert[$result])) {
+ return $this->convert[$result];
+ }
+
+ return $result;
+ }
+
+ return $this->getEnv($name, $default);
+ }
+
+ protected function getEnv(string $name, $default = null)
+ {
+ $result = getenv('PHP_' . $name);
+
+ if (false === $result) {
+ return $default;
+ }
+
+ if ('false' === $result) {
+ $result = false;
+ } elseif ('true' === $result) {
+ $result = true;
+ }
+
+ if (!isset($this->data[$name])) {
+ $this->data[$name] = $result;
+ }
+
+ return $result;
+ }
+
+ /**
+ * 设置环境变量值
+ * @access public
+ * @param string|array $env 环境变量
+ * @param mixed $value 值
+ * @return void
+ */
+ public function set($env, $value = null): void
+ {
+ if (is_array($env)) {
+ $env = array_change_key_case($env, CASE_UPPER);
+
+ foreach ($env as $key => $val) {
+ if (is_array($val)) {
+ foreach ($val as $k => $v) {
+ $this->data[$key . '_' . strtoupper($k)] = $v;
+ }
+ } else {
+ $this->data[$key] = $val;
+ }
+ }
+ } else {
+ $name = strtoupper(str_replace('.', '_', $env));
+
+ $this->data[$name] = $value;
+ }
+ }
+
+ /**
+ * 检测是否存在环境变量
+ * @access public
+ * @param string $name 参数名
+ * @return bool
+ */
+ public function has(string $name): bool
+ {
+ return !is_null($this->get($name));
+ }
+
+ /**
+ * 设置环境变量
+ * @access public
+ * @param string $name 参数名
+ * @param mixed $value 值
+ */
+ public function __set(string $name, $value): void
+ {
+ $this->set($name, $value);
+ }
+
+ /**
+ * 获取环境变量
+ * @access public
+ * @param string $name 参数名
+ * @return mixed
+ */
+ public function __get(string $name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * 检测是否存在环境变量
+ * @access public
+ * @param string $name 参数名
+ * @return bool
+ */
+ public function __isset(string $name): bool
+ {
+ return $this->has($name);
+ }
+
+ // ArrayAccess
+ #[\ReturnTypeWillChange]
+ public function offsetSet($name, $value): void
+ {
+ $this->set($name, $value);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetExists($name): bool
+ {
+ return $this->__isset($name);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($name): void
+ {
+ throw new Exception('not support: unset');
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetGet($name)
+ {
+ return $this->get($name);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Event.php b/vendor/topthink/framework/src/think/Event.php
new file mode 100755
index 0000000..3c70aad
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Event.php
@@ -0,0 +1,272 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ReflectionClass;
+use ReflectionMethod;
+
+/**
+ * 事件管理类
+ * @package think
+ */
+class Event
+{
+ /**
+ * 监听者
+ * @var array
+ */
+ protected $listener = [];
+
+ /**
+ * 事件别名
+ * @var array
+ */
+ protected $bind = [
+ 'AppInit' => event\AppInit::class,
+ 'HttpRun' => event\HttpRun::class,
+ 'HttpEnd' => event\HttpEnd::class,
+ 'RouteLoaded' => event\RouteLoaded::class,
+ 'LogWrite' => event\LogWrite::class,
+ 'LogRecord' => event\LogRecord::class,
+ ];
+
+ /**
+ * 应用对象
+ * @var App
+ */
+ protected $app;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * 批量注册事件监听
+ * @access public
+ * @param array $events 事件定义
+ * @return $this
+ */
+ public function listenEvents(array $events)
+ {
+ foreach ($events as $event => $listeners) {
+ if (isset($this->bind[$event])) {
+ $event = $this->bind[$event];
+ }
+
+ $this->listener[$event] = array_merge($this->listener[$event] ?? [], $listeners);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 注册事件监听
+ * @access public
+ * @param string $event 事件名称
+ * @param mixed $listener 监听操作(或者类名)
+ * @param bool $first 是否优先执行
+ * @return $this
+ */
+ public function listen(string $event, $listener, bool $first = false)
+ {
+ if (isset($this->bind[$event])) {
+ $event = $this->bind[$event];
+ }
+
+ if ($first && isset($this->listener[$event])) {
+ array_unshift($this->listener[$event], $listener);
+ } else {
+ $this->listener[$event][] = $listener;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 是否存在事件监听
+ * @access public
+ * @param string $event 事件名称
+ * @return bool
+ */
+ public function hasListener(string $event): bool
+ {
+ if (isset($this->bind[$event])) {
+ $event = $this->bind[$event];
+ }
+
+ return isset($this->listener[$event]);
+ }
+
+ /**
+ * 移除事件监听
+ * @access public
+ * @param string $event 事件名称
+ * @return void
+ */
+ public function remove(string $event): void
+ {
+ if (isset($this->bind[$event])) {
+ $event = $this->bind[$event];
+ }
+
+ unset($this->listener[$event]);
+ }
+
+ /**
+ * 指定事件别名标识 便于调用
+ * @access public
+ * @param array $events 事件别名
+ * @return $this
+ */
+ public function bind(array $events)
+ {
+ $this->bind = array_merge($this->bind, $events);
+
+ return $this;
+ }
+
+ /**
+ * 注册事件订阅者
+ * @access public
+ * @param mixed $subscriber 订阅者
+ * @return $this
+ */
+ public function subscribe($subscriber)
+ {
+ $subscribers = (array) $subscriber;
+
+ foreach ($subscribers as $subscriber) {
+ if (is_string($subscriber)) {
+ $subscriber = $this->app->make($subscriber);
+ }
+
+ if (method_exists($subscriber, 'subscribe')) {
+ // 手动订阅
+ $subscriber->subscribe($this);
+ } else {
+ // 智能订阅
+ $this->observe($subscriber);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 自动注册事件观察者
+ * @access public
+ * @param string|object $observer 观察者
+ * @param null|string $prefix 事件名前缀
+ * @return $this
+ */
+ public function observe($observer, string $prefix = '')
+ {
+ if (is_string($observer)) {
+ $observer = $this->app->make($observer);
+ }
+
+ $reflect = new ReflectionClass($observer);
+ $methods = $reflect->getMethods(ReflectionMethod::IS_PUBLIC);
+
+ if (empty($prefix) && $reflect->hasProperty('eventPrefix')) {
+ $reflectProperty = $reflect->getProperty('eventPrefix');
+ $reflectProperty->setAccessible(true);
+ $prefix = $reflectProperty->getValue($observer);
+ }
+
+ foreach ($methods as $method) {
+ $name = $method->getName();
+ if (0 === strpos($name, 'on')) {
+ $this->listen($prefix . substr($name, 2), [$observer, $name]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 触发事件
+ * @access public
+ * @param string|object $event 事件名称
+ * @param mixed $params 传入参数
+ * @param bool $once 只获取一个有效返回值
+ * @return mixed
+ */
+ public function trigger($event, $params = null, bool $once = false)
+ {
+ if (is_object($event)) {
+ $params = $event;
+ $event = get_class($event);
+ }
+
+ if (isset($this->bind[$event])) {
+ $event = $this->bind[$event];
+ }
+
+ $result = [];
+ $listeners = $this->listener[$event] ?? [];
+
+ if (strpos($event, '.')) {
+ [$prefix, $event] = explode('.', $event, 2);
+ if (isset($this->listener[$prefix . '.*'])) {
+ $listeners = array_merge($listeners, $this->listener[$prefix . '.*']);
+ }
+ }
+
+ $listeners = array_unique($listeners, SORT_REGULAR);
+
+ foreach ($listeners as $key => $listener) {
+ $result[$key] = $this->dispatch($listener, $params);
+
+ if (false === $result[$key] || (!is_null($result[$key]) && $once)) {
+ break;
+ }
+ }
+
+ return $once ? end($result) : $result;
+ }
+
+ /**
+ * 触发事件(只获取一个有效返回值)
+ * @param $event
+ * @param null $params
+ * @return mixed
+ */
+ public function until($event, $params = null)
+ {
+ return $this->trigger($event, $params, true);
+ }
+
+ /**
+ * 执行事件调度
+ * @access protected
+ * @param mixed $event 事件方法
+ * @param mixed $params 参数
+ * @return mixed
+ */
+ protected function dispatch($event, $params = null)
+ {
+ if (!is_string($event)) {
+ $call = $event;
+ } elseif (strpos($event, '::')) {
+ $call = $event;
+ } else {
+ $obj = $this->app->make($event);
+ $call = [$obj, 'handle'];
+ }
+
+ return $this->app->invoke($call, [$params]);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Exception.php b/vendor/topthink/framework/src/think/Exception.php
new file mode 100755
index 0000000..5cf7954
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Exception.php
@@ -0,0 +1,60 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+/**
+ * 异常基础类
+ * @package think
+ */
+class Exception extends \Exception
+{
+ /**
+ * 保存异常页面显示的额外Debug数据
+ * @var array
+ */
+ protected $data = [];
+
+ /**
+ * 设置异常额外的Debug数据
+ * 数据将会显示为下面的格式
+ *
+ * Exception Data
+ * --------------------------------------------------
+ * Label 1
+ * key1 value1
+ * key2 value2
+ * Label 2
+ * key1 value1
+ * key2 value2
+ *
+ * @access protected
+ * @param string $label 数据分类,用于异常页面显示
+ * @param array $data 需要显示的数据,必须为关联数组
+ */
+ final protected function setData(string $label, array $data)
+ {
+ $this->data[$label] = $data;
+ }
+
+ /**
+ * 获取异常额外Debug数据
+ * 主要用于输出到异常页面便于调试
+ * @access public
+ * @return array 由setData设置的Debug数据
+ */
+ final public function getData()
+ {
+ return $this->data;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Facade.php b/vendor/topthink/framework/src/think/Facade.php
new file mode 100755
index 0000000..9a0e333
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Facade.php
@@ -0,0 +1,98 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace think;
+
+/**
+ * Facade管理类
+ */
+class Facade
+{
+ /**
+ * 始终创建新的对象实例
+ * @var bool
+ */
+ protected static $alwaysNewInstance;
+
+ /**
+ * 创建Facade实例
+ * @static
+ * @access protected
+ * @param string $class 类名或标识
+ * @param array $args 变量
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return object
+ */
+ protected static function createFacade(string $class = '', array $args = [], bool $newInstance = false)
+ {
+ $class = $class ?: static::class;
+
+ $facadeClass = static::getFacadeClass();
+
+ if ($facadeClass) {
+ $class = $facadeClass;
+ }
+
+ if (static::$alwaysNewInstance) {
+ $newInstance = true;
+ }
+
+ return Container::getInstance()->make($class, $args, $newInstance);
+ }
+
+ /**
+ * 获取当前Facade对应类名
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {}
+
+ /**
+ * 带参数实例化当前Facade类
+ * @access public
+ * @return object
+ */
+ public static function instance(...$args)
+ {
+ if (__CLASS__ != static::class) {
+ return self::createFacade('', $args);
+ }
+ }
+
+ /**
+ * 调用类的实例
+ * @access public
+ * @param string $class 类名或者标识
+ * @param array|true $args 变量
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return object
+ */
+ public static function make(string $class, $args = [], $newInstance = false)
+ {
+ if (__CLASS__ != static::class) {
+ return self::__callStatic('make', func_get_args());
+ }
+
+ if (true === $args) {
+ // 总是创建新的实例化对象
+ $newInstance = true;
+ $args = [];
+ }
+
+ return self::createFacade($class, $args, $newInstance);
+ }
+
+ // 调用实际类的方法
+ public static function __callStatic($method, $params)
+ {
+ return call_user_func_array([static::createFacade(), $method], $params);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/File.php b/vendor/topthink/framework/src/think/File.php
new file mode 100755
index 0000000..59230b5
--- /dev/null
+++ b/vendor/topthink/framework/src/think/File.php
@@ -0,0 +1,204 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use SplFileInfo;
+use think\exception\FileException;
+
+/**
+ * 文件上传类
+ * @package think
+ */
+class File extends SplFileInfo
+{
+
+ /**
+ * 文件hash规则
+ * @var array
+ */
+ protected $hash = [];
+
+ protected $hashName;
+
+ /**
+ * 保存的文件后缀
+ * @var string
+ */
+ protected $extension;
+
+ public function __construct(string $path, bool $checkPath = true)
+ {
+ if ($checkPath && !is_file($path)) {
+ throw new FileException(sprintf('The file "%s" does not exist', $path));
+ }
+
+ parent::__construct($path);
+ }
+
+ /**
+ * 获取文件的哈希散列值
+ * @access public
+ * @param string $type
+ * @return string
+ */
+ public function hash(string $type = 'sha1'): string
+ {
+ if (!isset($this->hash[$type])) {
+ $this->hash[$type] = hash_file($type, $this->getPathname());
+ }
+
+ return $this->hash[$type];
+ }
+
+ /**
+ * 获取文件的MD5值
+ * @access public
+ * @return string
+ */
+ public function md5(): string
+ {
+ return $this->hash('md5');
+ }
+
+ /**
+ * 获取文件的SHA1值
+ * @access public
+ * @return string
+ */
+ public function sha1(): string
+ {
+ return $this->hash('sha1');
+ }
+
+ /**
+ * 获取文件类型信息
+ * @access public
+ * @return string
+ */
+ public function getMime(): string
+ {
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
+
+ return finfo_file($finfo, $this->getPathname());
+ }
+
+ /**
+ * 移动文件
+ * @access public
+ * @param string $directory 保存路径
+ * @param string|null $name 保存的文件名
+ * @return File
+ */
+ public function move(string $directory, string $name = null): File
+ {
+ $target = $this->getTargetFile($directory, $name);
+
+ set_error_handler(function ($type, $msg) use (&$error) {
+ $error = $msg;
+ });
+ $renamed = rename($this->getPathname(), (string) $target);
+ restore_error_handler();
+ if (!$renamed) {
+ throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
+ }
+
+ @chmod((string) $target, 0666 & ~umask());
+
+ return $target;
+ }
+
+ /**
+ * 实例化一个新文件
+ * @param string $directory
+ * @param null|string $name
+ * @return File
+ */
+ protected function getTargetFile(string $directory, string $name = null): File
+ {
+ if (!is_dir($directory)) {
+ if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
+ throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
+ }
+ } elseif (!is_writable($directory)) {
+ throw new FileException(sprintf('Unable to write in the "%s" directory', $directory));
+ }
+
+ $target = rtrim($directory, '/\\') . \DIRECTORY_SEPARATOR . (null === $name ? $this->getBasename() : $this->getName($name));
+
+ return new self($target, false);
+ }
+
+ /**
+ * 获取文件名
+ * @param string $name
+ * @return string
+ */
+ protected function getName(string $name): string
+ {
+ $originalName = str_replace('\\', '/', $name);
+ $pos = strrpos($originalName, '/');
+ $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1);
+
+ return $originalName;
+ }
+
+ /**
+ * 文件扩展名
+ * @return string
+ */
+ public function extension(): string
+ {
+ return $this->getExtension();
+ }
+
+ /**
+ * 指定保存文件的扩展名
+ * @param string $extension
+ * @return void
+ */
+ public function setExtension(string $extension): void
+ {
+ $this->extension = $extension;
+ }
+
+ /**
+ * 自动生成文件名
+ * @access public
+ * @param string|\Closure $rule
+ * @return string
+ */
+ public function hashName($rule = ''): string
+ {
+ if (!$this->hashName) {
+ if ($rule instanceof \Closure) {
+ $this->hashName = call_user_func_array($rule, [$this]);
+ } else {
+ switch (true) {
+ case in_array($rule, hash_algos()):
+ $hash = $this->hash($rule);
+ $this->hashName = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2);
+ break;
+ case is_callable($rule):
+ $this->hashName = call_user_func($rule);
+ break;
+ default:
+ $this->hashName = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true) . $this->getPathname());
+ break;
+ }
+ }
+ }
+
+ $extension = $this->extension ?? $this->extension();
+ return $this->hashName . ($extension ? '.' . $extension : '');
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Http.php b/vendor/topthink/framework/src/think/Http.php
new file mode 100755
index 0000000..4e49c88
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Http.php
@@ -0,0 +1,288 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use think\event\HttpEnd;
+use think\event\HttpRun;
+use think\event\RouteLoaded;
+use think\exception\Handle;
+use Throwable;
+
+/**
+ * Web应用管理类
+ * @package think
+ */
+class Http
+{
+
+ /**
+ * @var App
+ */
+ protected $app;
+
+ /**
+ * 应用名称
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * 应用路径
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * 路由路径
+ * @var string
+ */
+ protected $routePath;
+
+ /**
+ * 是否绑定应用
+ * @var bool
+ */
+ protected $isBind = false;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+
+ $this->routePath = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR;
+ }
+
+ /**
+ * 设置应用名称
+ * @access public
+ * @param string $name 应用名称
+ * @return $this
+ */
+ public function name(string $name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * 获取应用名称
+ * @access public
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name ?: '';
+ }
+
+ /**
+ * 设置应用目录
+ * @access public
+ * @param string $path 应用目录
+ * @return $this
+ */
+ public function path(string $path)
+ {
+ if (substr($path, -1) != DIRECTORY_SEPARATOR) {
+ $path .= DIRECTORY_SEPARATOR;
+ }
+
+ $this->path = $path;
+ return $this;
+ }
+
+ /**
+ * 获取应用路径
+ * @access public
+ * @return string
+ */
+ public function getPath(): string
+ {
+ return $this->path ?: '';
+ }
+
+ /**
+ * 获取路由目录
+ * @access public
+ * @return string
+ */
+ public function getRoutePath(): string
+ {
+ return $this->routePath;
+ }
+
+ /**
+ * 设置路由目录
+ * @access public
+ * @param string $path 路由定义目录
+ */
+ public function setRoutePath(string $path): void
+ {
+ $this->routePath = $path;
+ }
+
+ /**
+ * 设置应用绑定
+ * @access public
+ * @param bool $bind 是否绑定
+ * @return $this
+ */
+ public function setBind(bool $bind = true)
+ {
+ $this->isBind = $bind;
+ return $this;
+ }
+
+ /**
+ * 是否绑定应用
+ * @access public
+ * @return bool
+ */
+ public function isBind(): bool
+ {
+ return $this->isBind;
+ }
+
+ /**
+ * 执行应用程序
+ * @access public
+ * @param Request|null $request
+ * @return Response
+ */
+ public function run(Request $request = null): Response
+ {
+ //初始化
+ $this->initialize();
+
+ //自动创建request对象
+ $request = $request ?? $this->app->make('request', [], true);
+ $this->app->instance('request', $request);
+
+ try {
+ $response = $this->runWithRequest($request);
+ } catch (Throwable $e) {
+ $this->reportException($e);
+
+ $response = $this->renderException($request, $e);
+ }
+
+ return $response;
+ }
+
+ /**
+ * 初始化
+ */
+ protected function initialize()
+ {
+ if (!$this->app->initialized()) {
+ $this->app->initialize();
+ }
+ }
+
+ /**
+ * 执行应用程序
+ * @param Request $request
+ * @return mixed
+ */
+ protected function runWithRequest(Request $request)
+ {
+ // 加载全局中间件
+ $this->loadMiddleware();
+
+ // 监听HttpRun
+ $this->app->event->trigger(HttpRun::class);
+
+ return $this->app->middleware->pipeline()
+ ->send($request)
+ ->then(function ($request) {
+ return $this->dispatchToRoute($request);
+ });
+ }
+
+ protected function dispatchToRoute($request)
+ {
+ $withRoute = $this->app->config->get('app.with_route', true) ? function () {
+ $this->loadRoutes();
+ } : null;
+
+ return $this->app->route->dispatch($request, $withRoute);
+ }
+
+ /**
+ * 加载全局中间件
+ */
+ protected function loadMiddleware(): void
+ {
+ if (is_file($this->app->getBasePath() . 'middleware.php')) {
+ $this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php');
+ }
+ }
+
+ /**
+ * 加载路由
+ * @access protected
+ * @return void
+ */
+ protected function loadRoutes(): void
+ {
+ // 加载路由定义
+ $routePath = $this->getRoutePath();
+
+ if (is_dir($routePath)) {
+ $files = glob($routePath . '*.php');
+ foreach ($files as $file) {
+ include $file;
+ }
+ }
+
+ $this->app->event->trigger(RouteLoaded::class);
+ }
+
+ /**
+ * Report the exception to the exception handler.
+ *
+ * @param Throwable $e
+ * @return void
+ */
+ protected function reportException(Throwable $e)
+ {
+ $this->app->make(Handle::class)->report($e);
+ }
+
+ /**
+ * Render the exception to a response.
+ *
+ * @param Request $request
+ * @param Throwable $e
+ * @return Response
+ */
+ protected function renderException($request, Throwable $e)
+ {
+ return $this->app->make(Handle::class)->render($request, $e);
+ }
+
+ /**
+ * HttpEnd
+ * @param Response $response
+ * @return void
+ */
+ public function end(Response $response): void
+ {
+ $this->app->event->trigger(HttpEnd::class, $response);
+
+ //执行中间件
+ $this->app->middleware->end($response);
+
+ // 写入日志
+ $this->app->log->save();
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Lang.php b/vendor/topthink/framework/src/think/Lang.php
new file mode 100755
index 0000000..b891084
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Lang.php
@@ -0,0 +1,290 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+/**
+ * 多语言管理类
+ * @package think
+ */
+class Lang
+{
+ protected $app;
+
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [
+ // 默认语言
+ 'default_lang' => 'zh-cn',
+ // 允许的语言列表
+ 'allow_lang_list' => [],
+ // 是否使用Cookie记录
+ 'use_cookie' => true,
+ // 扩展语言包
+ 'extend_list' => [],
+ // 多语言cookie变量
+ 'cookie_var' => 'think_lang',
+ // 多语言header变量
+ 'header_var' => 'think-lang',
+ // 多语言自动侦测变量名
+ 'detect_var' => 'lang',
+ // Accept-Language转义为对应语言包名称
+ 'accept_language' => [
+ 'zh-hans-cn' => 'zh-cn',
+ ],
+ // 是否支持语言分组
+ 'allow_group' => false,
+ ];
+
+ /**
+ * 多语言信息
+ * @var array
+ */
+ private $lang = [];
+
+ /**
+ * 当前语言
+ * @var string
+ */
+ private $range = 'zh-cn';
+
+ /**
+ * 构造方法
+ * @access public
+ * @param array $config
+ */
+ public function __construct(App $app, array $config = [])
+ {
+ $this->config = array_merge($this->config, array_change_key_case($config));
+ $this->range = $this->config['default_lang'];
+ $this->app = $app;
+ }
+
+ public static function __make(App $app, Config $config)
+ {
+ return new static($app, $config->get('lang'));
+ }
+
+ /**
+ * 获取当前语言配置
+ * @access public
+ * @return array
+ */
+ public function getConfig(): array
+ {
+ return $this->config;
+ }
+
+ /**
+ * 设置当前语言
+ * @access public
+ * @param string $lang 语言
+ * @return void
+ */
+ public function setLangSet(string $lang): void
+ {
+ $this->range = $lang;
+ }
+
+ /**
+ * 获取当前语言
+ * @access public
+ * @return string
+ */
+ public function getLangSet(): string
+ {
+ return $this->range;
+ }
+
+ /**
+ * 获取默认语言
+ * @access public
+ * @return string
+ */
+ public function defaultLangSet()
+ {
+ return $this->config['default_lang'];
+ }
+
+ /**
+ * 切换语言
+ * @access public
+ * @param string $langset 语言
+ * @return void
+ */
+ public function switchLangSet(string $langset)
+ {
+ if (empty($langset)) {
+ return;
+ }
+
+ $this->setLangSet($langset);
+
+ // 加载系统语言包
+ $this->load([
+ $this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.php',
+ ]);
+
+ // 加载系统语言包
+ $files = glob($this->app->getAppPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*');
+ $this->load($files);
+
+ // 加载扩展(自定义)语言包
+ $list = $this->app->config->get('lang.extend_list', []);
+
+ if (isset($list[$langset])) {
+ $this->load($list[$langset]);
+ }
+ }
+
+ /**
+ * 加载语言定义(不区分大小写)
+ * @access public
+ * @param string|array $file 语言文件
+ * @param string $range 语言作用域
+ * @return array
+ */
+ public function load($file, $range = ''): array
+ {
+ $range = $range ?: $this->range;
+ if (!isset($this->lang[$range])) {
+ $this->lang[$range] = [];
+ }
+
+ $lang = [];
+
+ foreach ((array) $file as $name) {
+ if (is_file($name)) {
+ $result = $this->parse($name);
+ $lang = array_change_key_case($result) + $lang;
+ }
+ }
+
+ if (!empty($lang)) {
+ $this->lang[$range] = $lang + $this->lang[$range];
+ }
+
+ return $this->lang[$range];
+ }
+
+ /**
+ * 解析语言文件
+ * @access protected
+ * @param string $file 语言文件名
+ * @return array
+ */
+ protected function parse(string $file): array
+ {
+ $type = pathinfo($file, PATHINFO_EXTENSION);
+
+ switch ($type) {
+ case 'php':
+ $result = include $file;
+ break;
+ case 'yml':
+ case 'yaml':
+ if (function_exists('yaml_parse_file')) {
+ $result = yaml_parse_file($file);
+ }
+ break;
+ case 'json':
+ $data = file_get_contents($file);
+
+ if (false !== $data) {
+ $data = json_decode($data, true);
+
+ if (json_last_error() === JSON_ERROR_NONE) {
+ $result = $data;
+ }
+ }
+
+ break;
+ }
+
+ return isset($result) && is_array($result) ? $result : [];
+ }
+
+ /**
+ * 判断是否存在语言定义(不区分大小写)
+ * @access public
+ * @param string|null $name 语言变量
+ * @param string $range 语言作用域
+ * @return bool
+ */
+ public function has(string $name, string $range = ''): bool
+ {
+ $range = $range ?: $this->range;
+
+ if ($this->config['allow_group'] && strpos($name, '.')) {
+ [$name1, $name2] = explode('.', $name, 2);
+ return isset($this->lang[$range][strtolower($name1)][$name2]);
+ }
+
+ return isset($this->lang[$range][strtolower($name)]);
+ }
+
+ /**
+ * 获取语言定义(不区分大小写)
+ * @access public
+ * @param string|null $name 语言变量
+ * @param array $vars 变量替换
+ * @param string $range 语言作用域
+ * @return mixed
+ */
+ public function get(string $name = null, array $vars = [], string $range = '')
+ {
+ $range = $range ?: $this->range;
+
+ if (!isset($this->lang[$range])) {
+ $this->switchLangSet($range);
+ }
+
+ // 空参数返回所有定义
+ if (is_null($name)) {
+ return $this->lang[$range] ?? [];
+ }
+
+ if ($this->config['allow_group'] && strpos($name, '.')) {
+ [$name1, $name2] = explode('.', $name, 2);
+
+ $value = $this->lang[$range][strtolower($name1)][$name2] ?? $name;
+ } else {
+ $value = $this->lang[$range][strtolower($name)] ?? $name;
+ }
+
+ // 变量解析
+ if (!empty($vars) && is_array($vars)) {
+ /**
+ * Notes:
+ * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0
+ * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数
+ */
+ if (key($vars) === 0) {
+ // 数字索引解析
+ array_unshift($vars, $value);
+ $value = call_user_func_array('sprintf', $vars);
+ } else {
+ // 关联索引解析
+ $replace = array_keys($vars);
+ foreach ($replace as &$v) {
+ $v = "{:{$v}}";
+ }
+ $value = str_replace($replace, $vars, $value);
+ }
+ }
+
+ return $value;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Log.php b/vendor/topthink/framework/src/think/Log.php
new file mode 100755
index 0000000..c31210c
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Log.php
@@ -0,0 +1,342 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use InvalidArgumentException;
+use Psr\Log\LoggerInterface;
+use think\event\LogWrite;
+use think\helper\Arr;
+use think\log\Channel;
+use think\log\ChannelSet;
+
+/**
+ * 日志管理类
+ * @package think
+ * @mixin Channel
+ */
+class Log extends Manager implements LoggerInterface
+{
+ const EMERGENCY = 'emergency';
+ const ALERT = 'alert';
+ const CRITICAL = 'critical';
+ const ERROR = 'error';
+ const WARNING = 'warning';
+ const NOTICE = 'notice';
+ const INFO = 'info';
+ const DEBUG = 'debug';
+ const SQL = 'sql';
+
+ protected $namespace = '\\think\\log\\driver\\';
+
+ /**
+ * 默认驱动
+ * @return string|null
+ */
+ public function getDefaultDriver()
+ {
+ return $this->getConfig('default');
+ }
+
+ /**
+ * 获取日志配置
+ * @access public
+ * @param null|string $name 名称
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getConfig(string $name = null, $default = null)
+ {
+ if (!is_null($name)) {
+ return $this->app->config->get('log.' . $name, $default);
+ }
+
+ return $this->app->config->get('log');
+ }
+
+ /**
+ * 获取渠道配置
+ * @param string $channel
+ * @param null $name
+ * @param null $default
+ * @return array
+ */
+ public function getChannelConfig($channel, $name = null, $default = null)
+ {
+ if ($config = $this->getConfig("channels.{$channel}")) {
+ return Arr::get($config, $name, $default);
+ }
+
+ throw new InvalidArgumentException("Channel [$channel] not found.");
+ }
+
+ /**
+ * driver()的别名
+ * @param string|array $name 渠道名
+ * @return Channel|ChannelSet
+ */
+ public function channel($name = null)
+ {
+ if (is_array($name)) {
+ return new ChannelSet($this, $name);
+ }
+
+ return $this->driver($name);
+ }
+
+ protected function resolveType(string $name)
+ {
+ return $this->getChannelConfig($name, 'type', 'file');
+ }
+
+ public function createDriver(string $name)
+ {
+ $driver = parent::createDriver($name);
+
+ $lazy = !$this->getChannelConfig($name, "realtime_write", false) && !$this->app->runningInConsole();
+ $allow = array_merge($this->getConfig("level", []), $this->getChannelConfig($name, "level", []));
+
+ return new Channel($name, $driver, $allow, $lazy, $this->app->event);
+ }
+
+ protected function resolveConfig(string $name)
+ {
+ return $this->getChannelConfig($name);
+ }
+
+ /**
+ * 清空日志信息
+ * @access public
+ * @param string|array $channel 日志通道名
+ * @return $this
+ */
+ public function clear($channel = '*')
+ {
+ if ('*' == $channel) {
+ $channel = array_keys($this->drivers);
+ }
+
+ $this->channel($channel)->clear();
+
+ return $this;
+ }
+
+ /**
+ * 关闭本次请求日志写入
+ * @access public
+ * @param string|array $channel 日志通道名
+ * @return $this
+ */
+ public function close($channel = '*')
+ {
+ if ('*' == $channel) {
+ $channel = array_keys($this->drivers);
+ }
+
+ $this->channel($channel)->close();
+
+ return $this;
+ }
+
+ /**
+ * 获取日志信息
+ * @access public
+ * @param string $channel 日志通道名
+ * @return array
+ */
+ public function getLog(string $channel = null): array
+ {
+ return $this->channel($channel)->getLog();
+ }
+
+ /**
+ * 保存日志信息
+ * @access public
+ * @return bool
+ */
+ public function save(): bool
+ {
+ /** @var Channel $channel */
+ foreach ($this->drivers as $channel) {
+ $channel->save();
+ }
+
+ return true;
+ }
+
+ /**
+ * 记录日志信息
+ * @access public
+ * @param mixed $msg 日志信息
+ * @param string $type 日志级别
+ * @param array $context 替换内容
+ * @param bool $lazy
+ * @return $this
+ */
+ public function record($msg, string $type = 'info', array $context = [], bool $lazy = true)
+ {
+ $channel = $this->getConfig('type_channel.' . $type);
+
+ $this->channel($channel)->record($msg, $type, $context, $lazy);
+
+ return $this;
+ }
+
+ /**
+ * 实时写入日志信息
+ * @access public
+ * @param mixed $msg 调试信息
+ * @param string $type 日志级别
+ * @param array $context 替换内容
+ * @return $this
+ */
+ public function write($msg, string $type = 'info', array $context = [])
+ {
+ return $this->record($msg, $type, $context, false);
+ }
+
+ /**
+ * 注册日志写入事件监听
+ * @param $listener
+ * @return Event
+ */
+ public function listen($listener)
+ {
+ return $this->app->event->listen(LogWrite::class, $listener);
+ }
+
+ /**
+ * 记录日志信息
+ * @access public
+ * @param string $level 日志级别
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function log($level, $message, array $context = []): void
+ {
+ $this->record($message, $level, $context);
+ }
+
+ /**
+ * 记录emergency信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function emergency($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录警报信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function alert($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录紧急情况
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function critical($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录错误信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function error($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录warning信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function warning($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录notice信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function notice($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录一般信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function info($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录调试信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function debug($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * 记录sql信息
+ * @access public
+ * @param mixed $message 日志信息
+ * @param array $context 替换内容
+ * @return void
+ */
+ public function sql($message, array $context = []): void
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ public function __call($method, $parameters)
+ {
+ $this->log($method, ...$parameters);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Manager.php b/vendor/topthink/framework/src/think/Manager.php
new file mode 100755
index 0000000..ca3f6a5
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Manager.php
@@ -0,0 +1,177 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use InvalidArgumentException;
+use think\helper\Str;
+
+abstract class Manager
+{
+ /** @var App */
+ protected $app;
+
+ /**
+ * 驱动
+ * @var array
+ */
+ protected $drivers = [];
+
+ /**
+ * 驱动的命名空间
+ * @var string
+ */
+ protected $namespace = null;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * 获取驱动实例
+ * @param null|string $name
+ * @return mixed
+ */
+ protected function driver(string $name = null)
+ {
+ $name = $name ?: $this->getDefaultDriver();
+
+ if (is_null($name)) {
+ throw new InvalidArgumentException(sprintf(
+ 'Unable to resolve NULL driver for [%s].',
+ static::class
+ ));
+ }
+
+ return $this->drivers[$name] = $this->getDriver($name);
+ }
+
+ /**
+ * 获取驱动实例
+ * @param string $name
+ * @return mixed
+ */
+ protected function getDriver(string $name)
+ {
+ return $this->drivers[$name] ?? $this->createDriver($name);
+ }
+
+ /**
+ * 获取驱动类型
+ * @param string $name
+ * @return mixed
+ */
+ protected function resolveType(string $name)
+ {
+ return $name;
+ }
+
+ /**
+ * 获取驱动配置
+ * @param string $name
+ * @return mixed
+ */
+ protected function resolveConfig(string $name)
+ {
+ return $name;
+ }
+
+ /**
+ * 获取驱动类
+ * @param string $type
+ * @return string
+ */
+ protected function resolveClass(string $type): string
+ {
+ if ($this->namespace || false !== strpos($type, '\\')) {
+ $class = false !== strpos($type, '\\') ? $type : $this->namespace . Str::studly($type);
+
+ if (class_exists($class)) {
+ return $class;
+ }
+ }
+
+ throw new InvalidArgumentException("Driver [$type] not supported.");
+ }
+
+ /**
+ * 获取驱动参数
+ * @param $name
+ * @return array
+ */
+ protected function resolveParams($name): array
+ {
+ $config = $this->resolveConfig($name);
+ return [$config];
+ }
+
+ /**
+ * 创建驱动
+ *
+ * @param string $name
+ * @return mixed
+ *
+ */
+ protected function createDriver(string $name)
+ {
+ $type = $this->resolveType($name);
+
+ $method = 'create' . Str::studly($type) . 'Driver';
+
+ $params = $this->resolveParams($name);
+
+ if (method_exists($this, $method)) {
+ return $this->$method(...$params);
+ }
+
+ $class = $this->resolveClass($type);
+
+ return $this->app->invokeClass($class, $params);
+ }
+
+ /**
+ * 移除一个驱动实例
+ *
+ * @param array|string|null $name
+ * @return $this
+ */
+ public function forgetDriver($name = null)
+ {
+ $name = $name ?? $this->getDefaultDriver();
+
+ foreach ((array) $name as $cacheName) {
+ if (isset($this->drivers[$cacheName])) {
+ unset($this->drivers[$cacheName]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 默认驱动
+ * @return string|null
+ */
+ abstract public function getDefaultDriver();
+
+ /**
+ * 动态调用
+ * @param string $method
+ * @param array $parameters
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ return $this->driver()->$method(...$parameters);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Middleware.php b/vendor/topthink/framework/src/think/Middleware.php
new file mode 100755
index 0000000..a3db0f2
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Middleware.php
@@ -0,0 +1,257 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: Slince <taosikai@yeah.net>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Closure;
+use InvalidArgumentException;
+use LogicException;
+use think\exception\Handle;
+use Throwable;
+
+/**
+ * 中间件管理类
+ * @package think
+ */
+class Middleware
+{
+ /**
+ * 中间件执行队列
+ * @var array
+ */
+ protected $queue = [];
+
+ /**
+ * 应用对象
+ * @var App
+ */
+ protected $app;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * 导入中间件
+ * @access public
+ * @param array $middlewares
+ * @param string $type 中间件类型
+ * @return void
+ */
+ public function import(array $middlewares = [], string $type = 'global'): void
+ {
+ foreach ($middlewares as $middleware) {
+ $this->add($middleware, $type);
+ }
+ }
+
+ /**
+ * 注册中间件
+ * @access public
+ * @param mixed $middleware
+ * @param string $type 中间件类型
+ * @return void
+ */
+ public function add($middleware, string $type = 'global'): void
+ {
+ $middleware = $this->buildMiddleware($middleware, $type);
+
+ if (!empty($middleware)) {
+ $this->queue[$type][] = $middleware;
+ $this->queue[$type] = array_unique($this->queue[$type], SORT_REGULAR);
+ }
+ }
+
+ /**
+ * 注册路由中间件
+ * @access public
+ * @param mixed $middleware
+ * @return void
+ */
+ public function route($middleware): void
+ {
+ $this->add($middleware, 'route');
+ }
+
+ /**
+ * 注册控制器中间件
+ * @access public
+ * @param mixed $middleware
+ * @return void
+ */
+ public function controller($middleware): void
+ {
+ $this->add($middleware, 'controller');
+ }
+
+ /**
+ * 注册中间件到开始位置
+ * @access public
+ * @param mixed $middleware
+ * @param string $type 中间件类型
+ */
+ public function unshift($middleware, string $type = 'global')
+ {
+ $middleware = $this->buildMiddleware($middleware, $type);
+
+ if (!empty($middleware)) {
+ if (!isset($this->queue[$type])) {
+ $this->queue[$type] = [];
+ }
+
+ array_unshift($this->queue[$type], $middleware);
+ }
+ }
+
+ /**
+ * 获取注册的中间件
+ * @access public
+ * @param string $type 中间件类型
+ * @return array
+ */
+ public function all(string $type = 'global'): array
+ {
+ return $this->queue[$type] ?? [];
+ }
+
+ /**
+ * 调度管道
+ * @access public
+ * @param string $type 中间件类型
+ * @return Pipeline
+ */
+ public function pipeline(string $type = 'global')
+ {
+ return (new Pipeline())
+ ->through(array_map(function ($middleware) {
+ return function ($request, $next) use ($middleware) {
+ [$call, $params] = $middleware;
+ if (is_array($call) && is_string($call[0])) {
+ $call = [$this->app->make($call[0]), $call[1]];
+ }
+ $response = call_user_func($call, $request, $next, ...$params);
+
+ if (!$response instanceof Response) {
+ throw new LogicException('The middleware must return Response instance');
+ }
+ return $response;
+ };
+ }, $this->sortMiddleware($this->queue[$type] ?? [])))
+ ->whenException([$this, 'handleException']);
+ }
+
+ /**
+ * 结束调度
+ * @param Response $response
+ */
+ public function end(Response $response)
+ {
+ foreach ($this->queue as $queue) {
+ foreach ($queue as $middleware) {
+ [$call] = $middleware;
+ if (is_array($call) && is_string($call[0])) {
+ $instance = $this->app->make($call[0]);
+ if (method_exists($instance, 'end')) {
+ $instance->end($response);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 异常处理
+ * @param Request $passable
+ * @param Throwable $e
+ * @return Response
+ */
+ public function handleException($passable, Throwable $e)
+ {
+ /** @var Handle $handler */
+ $handler = $this->app->make(Handle::class);
+
+ $handler->report($e);
+
+ return $handler->render($passable, $e);
+ }
+
+ /**
+ * 解析中间件
+ * @access protected
+ * @param mixed $middleware
+ * @param string $type 中间件类型
+ * @return array
+ */
+ protected function buildMiddleware($middleware, string $type): array
+ {
+ if (is_array($middleware)) {
+ [$middleware, $params] = $middleware;
+ }
+
+ if ($middleware instanceof Closure) {
+ return [$middleware, $params ?? []];
+ }
+
+ if (!is_string($middleware)) {
+ throw new InvalidArgumentException('The middleware is invalid');
+ }
+
+ //中间件别名检查
+ $alias = $this->app->config->get('middleware.alias', []);
+
+ if (isset($alias[$middleware])) {
+ $middleware = $alias[$middleware];
+ }
+
+ if (is_array($middleware)) {
+ $this->import($middleware, $type);
+ return [];
+ }
+
+ return [[$middleware, 'handle'], $params ?? []];
+ }
+
+ /**
+ * 中间件排序
+ * @param array $middlewares
+ * @return array
+ */
+ protected function sortMiddleware(array $middlewares)
+ {
+ $priority = $this->app->config->get('middleware.priority', []);
+ uasort($middlewares, function ($a, $b) use ($priority) {
+ $aPriority = $this->getMiddlewarePriority($priority, $a);
+ $bPriority = $this->getMiddlewarePriority($priority, $b);
+ return $bPriority - $aPriority;
+ });
+
+ return $middlewares;
+ }
+
+ /**
+ * 获取中间件优先级
+ * @param $priority
+ * @param $middleware
+ * @return int
+ */
+ protected function getMiddlewarePriority($priority, $middleware)
+ {
+ [$call] = $middleware;
+ if (is_array($call) && is_string($call[0])) {
+ $index = array_search($call[0], array_reverse($priority));
+ return false === $index ? -1 : $index;
+ }
+ return -1;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Pipeline.php b/vendor/topthink/framework/src/think/Pipeline.php
new file mode 100755
index 0000000..77151f3
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Pipeline.php
@@ -0,0 +1,107 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think;
+
+use Closure;
+use Exception;
+use Throwable;
+
+class Pipeline
+{
+ protected $passable;
+
+ protected $pipes = [];
+
+ protected $exceptionHandler;
+
+ /**
+ * 初始数据
+ * @param $passable
+ * @return $this
+ */
+ public function send($passable)
+ {
+ $this->passable = $passable;
+ return $this;
+ }
+
+ /**
+ * 调用栈
+ * @param $pipes
+ * @return $this
+ */
+ public function through($pipes)
+ {
+ $this->pipes = is_array($pipes) ? $pipes : func_get_args();
+ return $this;
+ }
+
+ /**
+ * 执行
+ * @param Closure $destination
+ * @return mixed
+ */
+ public function then(Closure $destination)
+ {
+ $pipeline = array_reduce(
+ array_reverse($this->pipes),
+ $this->carry(),
+ function ($passable) use ($destination) {
+ try {
+ return $destination($passable);
+ } catch (Throwable | Exception $e) {
+ return $this->handleException($passable, $e);
+ }
+ }
+ );
+
+ return $pipeline($this->passable);
+ }
+
+ /**
+ * 设置异常处理器
+ * @param callable $handler
+ * @return $this
+ */
+ public function whenException($handler)
+ {
+ $this->exceptionHandler = $handler;
+ return $this;
+ }
+
+ protected function carry()
+ {
+ return function ($stack, $pipe) {
+ return function ($passable) use ($stack, $pipe) {
+ try {
+ return $pipe($passable, $stack);
+ } catch (Throwable | Exception $e) {
+ return $this->handleException($passable, $e);
+ }
+ };
+ };
+ }
+
+ /**
+ * 异常处理
+ * @param $passable
+ * @param $e
+ * @return mixed
+ */
+ protected function handleException($passable, Throwable $e)
+ {
+ if ($this->exceptionHandler) {
+ return call_user_func($this->exceptionHandler, $passable, $e);
+ }
+ throw $e;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/Request.php b/vendor/topthink/framework/src/think/Request.php
new file mode 100755
index 0000000..99f4f80
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Request.php
@@ -0,0 +1,2178 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ArrayAccess;
+use think\facade\Lang;
+use think\file\UploadedFile;
+use think\route\Rule;
+
+/**
+ * 请求管理类
+ * @package think
+ */
+class Request implements ArrayAccess
+{
+ /**
+ * 兼容PATH_INFO获取
+ * @var array
+ */
+ protected $pathinfoFetch = ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'];
+
+ /**
+ * PATHINFO变量名 用于兼容模式
+ * @var string
+ */
+ protected $varPathinfo = 's';
+
+ /**
+ * 请求类型
+ * @var string
+ */
+ protected $varMethod = '_method';
+
+ /**
+ * 表单ajax伪装变量
+ * @var string
+ */
+ protected $varAjax = '_ajax';
+
+ /**
+ * 表单pjax伪装变量
+ * @var string
+ */
+ protected $varPjax = '_pjax';
+
+ /**
+ * 域名根
+ * @var string
+ */
+ protected $rootDomain = '';
+
+ /**
+ * HTTPS代理标识
+ * @var string
+ */
+ protected $httpsAgentName = '';
+
+ /**
+ * 前端代理服务器IP
+ * @var array
+ */
+ protected $proxyServerIp = [];
+
+ /**
+ * 前端代理服务器真实IP头
+ * @var array
+ */
+ protected $proxyServerIpHeader = ['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP'];
+
+ /**
+ * 请求类型
+ * @var string
+ */
+ protected $method;
+
+ /**
+ * 域名(含协议及端口)
+ * @var string
+ */
+ protected $domain;
+
+ /**
+ * HOST(含端口)
+ * @var string
+ */
+ protected $host;
+
+ /**
+ * 子域名
+ * @var string
+ */
+ protected $subDomain;
+
+ /**
+ * 泛域名
+ * @var string
+ */
+ protected $panDomain;
+
+ /**
+ * 当前URL地址
+ * @var string
+ */
+ protected $url;
+
+ /**
+ * 基础URL
+ * @var string
+ */
+ protected $baseUrl;
+
+ /**
+ * 当前执行的文件
+ * @var string
+ */
+ protected $baseFile;
+
+ /**
+ * 访问的ROOT地址
+ * @var string
+ */
+ protected $root;
+
+ /**
+ * pathinfo
+ * @var string
+ */
+ protected $pathinfo;
+
+ /**
+ * pathinfo(不含后缀)
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * 当前请求的IP地址
+ * @var string
+ */
+ protected $realIP;
+
+ /**
+ * 当前控制器名
+ * @var string
+ */
+ protected $controller;
+
+ /**
+ * 当前操作名
+ * @var string
+ */
+ protected $action;
+
+ /**
+ * 当前请求参数
+ * @var array
+ */
+ protected $param = [];
+
+ /**
+ * 当前GET参数
+ * @var array
+ */
+ protected $get = [];
+
+ /**
+ * 当前POST参数
+ * @var array
+ */
+ protected $post = [];
+
+ /**
+ * 当前REQUEST参数
+ * @var array
+ */
+ protected $request = [];
+
+ /**
+ * 当前路由对象
+ * @var Rule
+ */
+ protected $rule;
+
+ /**
+ * 当前ROUTE参数
+ * @var array
+ */
+ protected $route = [];
+
+ /**
+ * 中间件传递的参数
+ * @var array
+ */
+ protected $middleware = [];
+
+ /**
+ * 当前PUT参数
+ * @var array
+ */
+ protected $put;
+
+ /**
+ * SESSION对象
+ * @var Session
+ */
+ protected $session;
+
+ /**
+ * COOKIE数据
+ * @var array
+ */
+ protected $cookie = [];
+
+ /**
+ * ENV对象
+ * @var Env
+ */
+ protected $env;
+
+ /**
+ * 当前SERVER参数
+ * @var array
+ */
+ protected $server = [];
+
+ /**
+ * 当前FILE参数
+ * @var array
+ */
+ protected $file = [];
+
+ /**
+ * 当前HEADER参数
+ * @var array
+ */
+ protected $header = [];
+
+ /**
+ * 资源类型定义
+ * @var array
+ */
+ protected $mimeType = [
+ 'xml' => 'application/xml,text/xml,application/x-xml',
+ 'json' => 'application/json,text/x-json,application/jsonrequest,text/json',
+ 'js' => 'text/javascript,application/javascript,application/x-javascript',
+ 'css' => 'text/css',
+ 'rss' => 'application/rss+xml',
+ 'yaml' => 'application/x-yaml,text/yaml',
+ 'atom' => 'application/atom+xml',
+ 'pdf' => 'application/pdf',
+ 'text' => 'text/plain',
+ 'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*',
+ 'csv' => 'text/csv',
+ 'html' => 'text/html,application/xhtml+xml,*/*',
+ ];
+
+ /**
+ * 当前请求内容
+ * @var string
+ */
+ protected $content;
+
+ /**
+ * 全局过滤规则
+ * @var array
+ */
+ protected $filter;
+
+ /**
+ * php://input内容
+ * @var string
+ */
+ // php://input
+ protected $input;
+
+ /**
+ * 请求安全Key
+ * @var string
+ */
+ protected $secureKey;
+
+ /**
+ * 是否合并Param
+ * @var bool
+ */
+ protected $mergeParam = false;
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ public function __construct()
+ {
+ // 保存 php://input
+ $this->input = file_get_contents('php://input');
+ }
+
+ public static function __make(App $app)
+ {
+ $request = new static();
+
+ if (function_exists('apache_request_headers') && $result = apache_request_headers()) {
+ $header = $result;
+ } else {
+ $header = [];
+ $server = $_SERVER;
+ foreach ($server as $key => $val) {
+ if (0 === strpos($key, 'HTTP_')) {
+ $key = str_replace('_', '-', strtolower(substr($key, 5)));
+ $header[$key] = $val;
+ }
+ }
+ if (isset($server['CONTENT_TYPE'])) {
+ $header['content-type'] = $server['CONTENT_TYPE'];
+ }
+ if (isset($server['CONTENT_LENGTH'])) {
+ $header['content-length'] = $server['CONTENT_LENGTH'];
+ }
+ }
+
+ $request->header = array_change_key_case($header);
+ $request->server = $_SERVER;
+ $request->env = $app->env;
+
+ $inputData = $request->getInputData($request->input);
+
+ $request->get = $_GET;
+ $request->post = $_POST ?: $inputData;
+ $request->put = $inputData;
+ $request->request = $_REQUEST;
+ $request->cookie = $_COOKIE;
+ $request->file = $_FILES ?? [];
+
+ return $request;
+ }
+
+ /**
+ * 设置当前包含协议的域名
+ * @access public
+ * @param string $domain 域名
+ * @return $this
+ */
+ public function setDomain(string $domain)
+ {
+ $this->domain = $domain;
+ return $this;
+ }
+
+ /**
+ * 获取当前包含协议的域名
+ * @access public
+ * @param bool $port 是否需要去除端口号
+ * @return string
+ */
+ public function domain(bool $port = false): string
+ {
+ return $this->scheme() . '://' . $this->host($port);
+ }
+
+ /**
+ * 获取当前根域名
+ * @access public
+ * @return string
+ */
+ public function rootDomain(): string
+ {
+ $root = $this->rootDomain;
+
+ if (!$root) {
+ $item = explode('.', $this->host());
+ $count = count($item);
+ $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0];
+ }
+
+ return $root;
+ }
+
+ /**
+ * 设置当前泛域名的值
+ * @access public
+ * @param string $domain 域名
+ * @return $this
+ */
+ public function setSubDomain(string $domain)
+ {
+ $this->subDomain = $domain;
+ return $this;
+ }
+
+ /**
+ * 获取当前子域名
+ * @access public
+ * @return string
+ */
+ public function subDomain(): string
+ {
+ if (is_null($this->subDomain)) {
+ // 获取当前主域名
+ $rootDomain = $this->rootDomain();
+
+ if ($rootDomain) {
+ $sub = stristr($this->host(), $rootDomain, true);
+ $this->subDomain = $sub ? rtrim($sub, '.') : '';
+ } else {
+ $this->subDomain = '';
+ }
+ }
+
+ return $this->subDomain;
+ }
+
+ /**
+ * 设置当前泛域名的值
+ * @access public
+ * @param string $domain 域名
+ * @return $this
+ */
+ public function setPanDomain(string $domain)
+ {
+ $this->panDomain = $domain;
+ return $this;
+ }
+
+ /**
+ * 获取当前泛域名的值
+ * @access public
+ * @return string
+ */
+ public function panDomain(): string
+ {
+ return $this->panDomain ?: '';
+ }
+
+ /**
+ * 设置当前完整URL 包括QUERY_STRING
+ * @access public
+ * @param string $url URL地址
+ * @return $this
+ */
+ public function setUrl(string $url)
+ {
+ $this->url = $url;
+ return $this;
+ }
+
+ /**
+ * 获取当前完整URL 包括QUERY_STRING
+ * @access public
+ * @param bool $complete 是否包含完整域名
+ * @return string
+ */
+ public function url(bool $complete = false): string
+ {
+ if ($this->url) {
+ $url = $this->url;
+ } elseif ($this->server('HTTP_X_REWRITE_URL')) {
+ $url = $this->server('HTTP_X_REWRITE_URL');
+ } elseif ($this->server('REQUEST_URI')) {
+ $url = $this->server('REQUEST_URI');
+ } elseif ($this->server('ORIG_PATH_INFO')) {
+ $url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : '');
+ } elseif (isset($_SERVER['argv'][1])) {
+ $url = $_SERVER['argv'][1];
+ } else {
+ $url = '';
+ }
+
+ return $complete ? $this->domain() . $url : $url;
+ }
+
+ /**
+ * 设置当前URL 不含QUERY_STRING
+ * @access public
+ * @param string $url URL地址
+ * @return $this
+ */
+ public function setBaseUrl(string $url)
+ {
+ $this->baseUrl = $url;
+ return $this;
+ }
+
+ /**
+ * 获取当前URL 不含QUERY_STRING
+ * @access public
+ * @param bool $complete 是否包含完整域名
+ * @return string
+ */
+ public function baseUrl(bool $complete = false): string
+ {
+ if (!$this->baseUrl) {
+ $str = $this->url();
+ $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str;
+ }
+
+ return $complete ? $this->domain() . $this->baseUrl : $this->baseUrl;
+ }
+
+ /**
+ * 获取当前执行的文件 SCRIPT_NAME
+ * @access public
+ * @param bool $complete 是否包含完整域名
+ * @return string
+ */
+ public function baseFile(bool $complete = false): string
+ {
+ if (!$this->baseFile) {
+ $url = '';
+ if (!$this->isCli()) {
+ $script_name = basename($this->server('SCRIPT_FILENAME'));
+ if (basename($this->server('SCRIPT_NAME')) === $script_name) {
+ $url = $this->server('SCRIPT_NAME');
+ } elseif (basename($this->server('PHP_SELF')) === $script_name) {
+ $url = $this->server('PHP_SELF');
+ } elseif (basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) {
+ $url = $this->server('ORIG_SCRIPT_NAME');
+ } elseif (($pos = strpos($this->server('PHP_SELF'), '/' . $script_name)) !== false) {
+ $url = substr($this->server('SCRIPT_NAME'), 0, $pos) . '/' . $script_name;
+ } elseif ($this->server('DOCUMENT_ROOT') && strpos($this->server('SCRIPT_FILENAME'), $this->server('DOCUMENT_ROOT')) === 0) {
+ $url = str_replace('\\', '/', str_replace($this->server('DOCUMENT_ROOT'), '', $this->server('SCRIPT_FILENAME')));
+ }
+ }
+ $this->baseFile = $url;
+ }
+
+ return $complete ? $this->domain() . $this->baseFile : $this->baseFile;
+ }
+
+ /**
+ * 设置URL访问根地址
+ * @access public
+ * @param string $url URL地址
+ * @return $this
+ */
+ public function setRoot(string $url)
+ {
+ $this->root = $url;
+ return $this;
+ }
+
+ /**
+ * 获取URL访问根地址
+ * @access public
+ * @param bool $complete 是否包含完整域名
+ * @return string
+ */
+ public function root(bool $complete = false): string
+ {
+ if (!$this->root) {
+ $file = $this->baseFile();
+ if ($file && 0 !== strpos($this->url(), $file)) {
+ $file = str_replace('\\', '/', dirname($file));
+ }
+ $this->root = rtrim($file, '/');
+ }
+
+ return $complete ? $this->domain() . $this->root : $this->root;
+ }
+
+ /**
+ * 获取URL访问根目录
+ * @access public
+ * @return string
+ */
+ public function rootUrl(): string
+ {
+ $base = $this->root();
+ $root = strpos($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base;
+
+ if ('' != $root) {
+ $root = '/' . ltrim($root, '/');
+ }
+
+ return $root;
+ }
+
+ /**
+ * 设置当前请求的pathinfo
+ * @access public
+ * @param string $pathinfo
+ * @return $this
+ */
+ public function setPathinfo(string $pathinfo)
+ {
+ $this->pathinfo = $pathinfo;
+ return $this;
+ }
+
+ /**
+ * 获取当前请求URL的pathinfo信息(含URL后缀)
+ * @access public
+ * @return string
+ */
+ public function pathinfo(): string
+ {
+ if (is_null($this->pathinfo)) {
+ if (isset($_GET[$this->varPathinfo])) {
+ // 判断URL里面是否有兼容模式参数
+ $pathinfo = $_GET[$this->varPathinfo];
+ unset($_GET[$this->varPathinfo]);
+ unset($this->get[$this->varPathinfo]);
+ } elseif ($this->server('PATH_INFO')) {
+ $pathinfo = $this->server('PATH_INFO');
+ } elseif (false !== strpos(PHP_SAPI, 'cli')) {
+ $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI');
+ }
+
+ // 分析PATHINFO信息
+ if (!isset($pathinfo)) {
+ foreach ($this->pathinfoFetch as $type) {
+ if ($this->server($type)) {
+ $pathinfo = (0 === strpos($this->server($type), $this->server('SCRIPT_NAME'))) ?
+ substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type);
+ break;
+ }
+ }
+ }
+
+ if (!empty($pathinfo)) {
+ unset($this->get[$pathinfo], $this->request[$pathinfo]);
+ }
+
+ $this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/');
+ }
+
+ return $this->pathinfo;
+ }
+
+ /**
+ * 当前URL的访问后缀
+ * @access public
+ * @return string
+ */
+ public function ext(): string
+ {
+ return pathinfo($this->pathinfo(), PATHINFO_EXTENSION);
+ }
+
+ /**
+ * 获取当前请求的时间
+ * @access public
+ * @param bool $float 是否使用浮点类型
+ * @return integer|float
+ */
+ public function time(bool $float = false)
+ {
+ return $float ? $this->server('REQUEST_TIME_FLOAT') : $this->server('REQUEST_TIME');
+ }
+
+ /**
+ * 当前请求的资源类型
+ * @access public
+ * @return string
+ */
+ public function type(): string
+ {
+ $accept = $this->server('HTTP_ACCEPT');
+
+ if (empty($accept)) {
+ return '';
+ }
+
+ foreach ($this->mimeType as $key => $val) {
+ $array = explode(',', $val);
+ foreach ($array as $k => $v) {
+ if (stristr($accept, $v)) {
+ return $key;
+ }
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * 设置资源类型
+ * @access public
+ * @param string|array $type 资源类型名
+ * @param string $val 资源类型
+ * @return void
+ */
+ public function mimeType($type, $val = ''): void
+ {
+ if (is_array($type)) {
+ $this->mimeType = array_merge($this->mimeType, $type);
+ } else {
+ $this->mimeType[$type] = $val;
+ }
+ }
+
+ /**
+ * 设置请求类型
+ * @access public
+ * @param string $method 请求类型
+ * @return $this
+ */
+ public function setMethod(string $method)
+ {
+ $this->method = strtoupper($method);
+ return $this;
+ }
+
+ /**
+ * 当前的请求类型
+ * @access public
+ * @param bool $origin 是否获取原始请求类型
+ * @return string
+ */
+ public function method(bool $origin = false): string
+ {
+ if ($origin) {
+ // 获取原始请求类型
+ return $this->server('REQUEST_METHOD') ?: 'GET';
+ } elseif (!$this->method) {
+ if (isset($this->post[$this->varMethod])) {
+ $method = strtolower($this->post[$this->varMethod]);
+ if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) {
+ $this->method = strtoupper($method);
+ $this->{$method} = $this->post;
+ } else {
+ $this->method = 'POST';
+ }
+ unset($this->post[$this->varMethod]);
+ } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) {
+ $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE'));
+ } else {
+ $this->method = $this->server('REQUEST_METHOD') ?: 'GET';
+ }
+ }
+
+ return $this->method;
+ }
+
+ /**
+ * 是否为GET请求
+ * @access public
+ * @return bool
+ */
+ public function isGet(): bool
+ {
+ return $this->method() == 'GET';
+ }
+
+ /**
+ * 是否为POST请求
+ * @access public
+ * @return bool
+ */
+ public function isPost(): bool
+ {
+ return $this->method() == 'POST';
+ }
+
+ /**
+ * 是否为PUT请求
+ * @access public
+ * @return bool
+ */
+ public function isPut(): bool
+ {
+ return $this->method() == 'PUT';
+ }
+
+ /**
+ * 是否为DELTE请求
+ * @access public
+ * @return bool
+ */
+ public function isDelete(): bool
+ {
+ return $this->method() == 'DELETE';
+ }
+
+ /**
+ * 是否为HEAD请求
+ * @access public
+ * @return bool
+ */
+ public function isHead(): bool
+ {
+ return $this->method() == 'HEAD';
+ }
+
+ /**
+ * 是否为PATCH请求
+ * @access public
+ * @return bool
+ */
+ public function isPatch(): bool
+ {
+ return $this->method() == 'PATCH';
+ }
+
+ /**
+ * 是否为OPTIONS请求
+ * @access public
+ * @return bool
+ */
+ public function isOptions(): bool
+ {
+ return $this->method() == 'OPTIONS';
+ }
+
+ /**
+ * 是否为cli
+ * @access public
+ * @return bool
+ */
+ public function isCli(): bool
+ {
+ return PHP_SAPI == 'cli';
+ }
+
+ /**
+ * 是否为cgi
+ * @access public
+ * @return bool
+ */
+ public function isCgi(): bool
+ {
+ return strpos(PHP_SAPI, 'cgi') === 0;
+ }
+
+ /**
+ * 获取当前请求的参数
+ * @access public
+ * @param string|array $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
+ * @return mixed
+ */
+ public function param($name = '', $default = null, $filter = '')
+ {
+ if (empty($this->mergeParam)) {
+ $method = $this->method(true);
+
+ // 自动获取请求变量
+ switch ($method) {
+ case 'POST':
+ $vars = $this->post(false);
+ break;
+ case 'PUT':
+ case 'DELETE':
+ case 'PATCH':
+ $vars = $this->put(false);
+ break;
+ default:
+ $vars = [];
+ }
+
+ // 当前请求参数和URL地址中的参数合并
+ $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));
+
+ $this->mergeParam = true;
+ }
+
+ if (is_array($name)) {
+ return $this->only($name, $this->param, $filter);
+ }
+
+ return $this->input($this->param, $name, $default, $filter);
+ }
+
+ /**
+ * 获取包含文件在内的请求参数
+ * @access public
+ * @param string|array $name 变量名
+ * @param string|array $filter 过滤方法
+ * @return mixed
+ */
+ public function all($name = '', $filter = '')
+ {
+ $data = array_merge($this->param(), $this->file() ?: []);
+
+ if (is_array($name)) {
+ $data = $this->only($name, $data, $filter);
+ } elseif ($name) {
+ $data = $data[$name] ?? null;
+ }
+
+ return $data;
+ }
+
+ /**
+ * 设置路由变量
+ * @access public
+ * @param Rule $rule 路由对象
+ * @return $this
+ */
+ public function setRule(Rule $rule)
+ {
+ $this->rule = $rule;
+ return $this;
+ }
+
+ /**
+ * 获取当前路由对象
+ * @access public
+ * @return Rule|null
+ */
+ public function rule()
+ {
+ return $this->rule;
+ }
+
+ /**
+ * 设置路由变量
+ * @access public
+ * @param array $route 路由变量
+ * @return $this
+ */
+ public function setRoute(array $route)
+ {
+ $this->route = array_merge($this->route, $route);
+ $this->mergeParam = false;
+ return $this;
+ }
+
+ /**
+ * 获取路由参数
+ * @access public
+ * @param string|array $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
+ * @return mixed
+ */
+ public function route($name = '', $default = null, $filter = '')
+ {
+ if (is_array($name)) {
+ return $this->only($name, $this->route, $filter);
+ }
+
+ return $this->input($this->route, $name, $default, $filter);
+ }
+
+ /**
+ * 获取GET参数
+ * @access public
+ * @param string|array $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
+ * @return mixed
+ */
+ public function get($name = '', $default = null, $filter = '')
+ {
+ if (is_array($name)) {
+ return $this->only($name, $this->get, $filter);
+ }
+
+ return $this->input($this->get, $name, $default, $filter);
+ }
+
+ /**
+ * 获取中间件传递的参数
+ * @access public
+ * @param mixed $name 变量名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function middleware($name, $default = null)
+ {
+ return $this->middleware[$name] ?? $default;
+ }
+
+ /**
+ * 获取POST参数
+ * @access public
+ * @param string|array $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
+ * @return mixed
+ */
+ public function post($name = '', $default = null, $filter = '')
+ {
+ if (is_array($name)) {
+ return $this->only($name, $this->post, $filter);
+ }
+
+ return $this->input($this->post, $name, $default, $filter);
+ }
+
+ /**
+ * 获取PUT参数
+ * @access public
+ * @param string|array $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
+ * @return mixed
+ */
+ public function put($name = '', $default = null, $filter = '')
+ {
+ if (is_array($name)) {
+ return $this->only($name, $this->put, $filter);
+ }
+
+ return $this->input($this->put, $name, $default, $filter);
+ }
+
+ protected function getInputData($content): array
+ {
+ $contentType = $this->contentType();
+ if ('application/x-www-form-urlencoded' == $contentType) {
+ parse_str($content, $data);
+ return $data;
+ } elseif (false !== strpos($contentType, 'json')) {
+ return (array) json_decode($content, true);
+ }
+
+ return [];
+ }
+
+ /**
+ * 设置获取DELETE参数
+ * @access public
+ * @param mixed $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
+ * @return mixed
+ */
+ public function delete($name = '', $default = null, $filter = '')
+ {
+ return $this->put($name, $default, $filter);
+ }
+
+ /**
+ * 设置获取PATCH参数
+ * @access public
+ * @param mixed $name 变量名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
+ * @return mixed
+ */
+ public function patch($name = '', $default = null, $filter = '')
+ {
+ return $this->put($name, $default, $filter);
+ }
+
+ /**
+ * 获取request变量
+ * @access public
+ * @param string|array $name 数据名称
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤方法
+ * @return mixed
+ */
+ public function request($name = '', $default = null, $filter = '')
+ {
+ if (is_array($name)) {
+ return $this->only($name, $this->request, $filter);
+ }
+
+ return $this->input($this->request, $name, $default, $filter);
+ }
+
+ /**
+ * 获取环境变量
+ * @access public
+ * @param string $name 数据名称
+ * @param string $default 默认值
+ * @return mixed
+ */
+ public function env(string $name = '', string $default = null)
+ {
+ if (empty($name)) {
+ return $this->env->get();
+ } else {
+ $name = strtoupper($name);
+ }
+
+ return $this->env->get($name, $default);
+ }
+
+ /**
+ * 获取session数据
+ * @access public
+ * @param string $name 数据名称
+ * @param string $default 默认值
+ * @return mixed
+ */
+ public function session(string $name = '', $default = null)
+ {
+ if ('' === $name) {
+ return $this->session->all();
+ }
+ return $this->session->get($name, $default);
+ }
+
+ /**
+ * 获取cookie参数
+ * @access public
+ * @param mixed $name 数据名称
+ * @param string $default 默认值
+ * @param string|array $filter 过滤方法
+ * @return mixed
+ */
+ public function cookie(string $name = '', $default = null, $filter = '')
+ {
+ if (!empty($name)) {
+ $data = $this->getData($this->cookie, $name, $default);
+ } else {
+ $data = $this->cookie;
+ }
+
+ // 解析过滤器
+ $filter = $this->getFilter($filter, $default);
+
+ if (is_array($data)) {
+ array_walk_recursive($data, [$this, 'filterValue'], $filter);
+ } else {
+ $this->filterValue($data, $name, $filter);
+ }
+
+ return $data;
+ }
+
+ /**
+ * 获取server参数
+ * @access public
+ * @param string $name 数据名称
+ * @param string $default 默认值
+ * @return mixed
+ */
+ public function server(string $name = '', string $default = '')
+ {
+ if (empty($name)) {
+ return $this->server;
+ } else {
+ $name = strtoupper($name);
+ }
+
+ return $this->server[$name] ?? $default;
+ }
+
+ /**
+ * 获取上传的文件信息
+ * @access public
+ * @param string $name 名称
+ * @return null|array|UploadedFile
+ */
+ public function file(string $name = '')
+ {
+ $files = $this->file;
+ if (!empty($files)) {
+ if (strpos($name, '.')) {
+ [$name, $sub] = explode('.', $name);
+ }
+
+ // 处理上传文件
+ $array = $this->dealUploadFile($files, $name);
+
+ if ('' === $name) {
+ // 获取全部文件
+ return $array;
+ } elseif (isset($sub) && isset($array[$name][$sub])) {
+ return $array[$name][$sub];
+ } elseif (isset($array[$name])) {
+ return $array[$name];
+ }
+ }
+ }
+
+ protected function dealUploadFile(array $files, string $name): array
+ {
+ $array = [];
+ foreach ($files as $key => $file) {
+ if (is_array($file['name'])) {
+ $item = [];
+ $keys = array_keys($file);
+ $count = count($file['name']);
+
+ for ($i = 0; $i < $count; $i++) {
+ if ($file['error'][$i] > 0) {
+ if ($name == $key) {
+ $this->throwUploadFileError($file['error'][$i]);
+ } else {
+ continue;
+ }
+ }
+
+ $temp['key'] = $key;
+
+ foreach ($keys as $_key) {
+ $temp[$_key] = $file[$_key][$i];
+ }
+
+ $item[] = new UploadedFile($temp['tmp_name'], $temp['name'], $temp['type'], $temp['error']);
+ }
+
+ $array[$key] = $item;
+ } else {
+ if ($file instanceof File) {
+ $array[$key] = $file;
+ } else {
+ if ($file['error'] > 0) {
+ if ($key == $name) {
+ $this->throwUploadFileError($file['error']);
+ } else {
+ continue;
+ }
+ }
+
+ $array[$key] = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error']);
+ }
+ }
+ }
+
+ return $array;
+ }
+
+ protected function throwUploadFileError($error)
+ {
+ static $fileUploadErrors = [
+ 1 => 'upload File size exceeds the maximum value',
+ 2 => 'upload File size exceeds the maximum value',
+ 3 => 'only the portion of file is uploaded',
+ 4 => 'no file to uploaded',
+ 6 => 'upload temp dir not found',
+ 7 => 'file write error',
+ ];
+
+ $msg = Lang::get($fileUploadErrors[$error]);
+ throw new Exception($msg, $error);
+ }
+
+ /**
+ * 设置或者获取当前的Header
+ * @access public
+ * @param string $name header名称
+ * @param string $default 默认值
+ * @return string|array|null
+ */
+ public function header(string $name = '', string $default = null)
+ {
+ if ('' === $name) {
+ return $this->header;
+ }
+
+ $name = str_replace('_', '-', strtolower($name));
+
+ return $this->header[$name] ?? $default;
+ }
+
+ /**
+ * 获取变量 支持过滤和默认值
+ * @access public
+ * @param array $data 数据源
+ * @param string|false $name 字段名
+ * @param mixed $default 默认值
+ * @param string|array $filter 过滤函数
+ * @return mixed
+ */
+ public function input(array $data = [], $name = '', $default = null, $filter = '')
+ {
+ if (false === $name) {
+ // 获取原始数据
+ return $data;
+ }
+
+ $name = (string) $name;
+ if ('' != $name) {
+ // 解析name
+ if (strpos($name, '/')) {
+ [$name, $type] = explode('/', $name);
+ }
+
+ $data = $this->getData($data, $name);
+
+ if (is_null($data)) {
+ return $default;
+ }
+
+ if (is_object($data)) {
+ return $data;
+ }
+ }
+
+ $data = $this->filterData($data, $filter, $name, $default);
+
+ if (isset($type) && $data !== $default) {
+ // 强制类型转换
+ $this->typeCast($data, $type);
+ }
+
+ return $data;
+ }
+
+ protected function filterData($data, $filter, $name, $default)
+ {
+ // 解析过滤器
+ $filter = $this->getFilter($filter, $default);
+
+ if (is_array($data)) {
+ array_walk_recursive($data, [$this, 'filterValue'], $filter);
+ } else {
+ $this->filterValue($data, $name, $filter);
+ }
+
+ return $data;
+ }
+
+ /**
+ * 强制类型转换
+ * @access protected
+ * @param mixed $data
+ * @param string $type
+ * @return mixed
+ */
+ protected function typeCast(&$data, string $type)
+ {
+ switch (strtolower($type)) {
+ // 数组
+ case 'a':
+ $data = (array) $data;
+ break;
+ // 数字
+ case 'd':
+ $data = (int) $data;
+ break;
+ // 浮点
+ case 'f':
+ $data = (float) $data;
+ break;
+ // 布尔
+ case 'b':
+ $data = (boolean) $data;
+ break;
+ // 字符串
+ case 's':
+ if (is_scalar($data)) {
+ $data = (string) $data;
+ } else {
+ throw new \InvalidArgumentException('variable type error:' . gettype($data));
+ }
+ break;
+ }
+ }
+
+ /**
+ * 获取数据
+ * @access protected
+ * @param array $data 数据源
+ * @param string $name 字段名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ protected function getData(array $data, string $name, $default = null)
+ {
+ foreach (explode('.', $name) as $val) {
+ if (isset($data[$val])) {
+ $data = $data[$val];
+ } else {
+ return $default;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * 设置或获取当前的过滤规则
+ * @access public
+ * @param mixed $filter 过滤规则
+ * @return mixed
+ */
+ public function filter($filter = null)
+ {
+ if (is_null($filter)) {
+ return $this->filter;
+ }
+
+ $this->filter = $filter;
+
+ return $this;
+ }
+
+ protected function getFilter($filter, $default): array
+ {
+ if (is_null($filter)) {
+ $filter = [];
+ } else {
+ $filter = $filter ?: $this->filter;
+ if (is_string($filter) && false === strpos($filter, '/')) {
+ $filter = explode(',', $filter);
+ } else {
+ $filter = (array) $filter;
+ }
+ }
+
+ $filter[] = $default;
+
+ return $filter;
+ }
+
+ /**
+ * 递归过滤给定的值
+ * @access public
+ * @param mixed $value 键值
+ * @param mixed $key 键名
+ * @param array $filters 过滤方法+默认值
+ * @return mixed
+ */
+ public function filterValue(&$value, $key, $filters)
+ {
+ $default = array_pop($filters);
+
+ foreach ($filters as $filter) {
+ if (is_callable($filter)) {
+ // 调用函数或者方法过滤
+ if (is_null($value)) {
+ continue;
+ }
+
+ $value = call_user_func($filter, $value);
+ } elseif (is_scalar($value)) {
+ if (is_string($filter) && false !== strpos($filter, '/')) {
+ // 正则过滤
+ if (!preg_match($filter, $value)) {
+ // 匹配不成功返回默认值
+ $value = $default;
+ break;
+ }
+ } elseif (!empty($filter)) {
+ // filter函数不存在时, 则使用filter_var进行过滤
+ // filter为非整形值时, 调用filter_id取得过滤id
+ $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
+ if (false === $value) {
+ $value = $default;
+ break;
+ }
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * 是否存在某个请求参数
+ * @access public
+ * @param string $name 变量名
+ * @param string $type 变量类型
+ * @param bool $checkEmpty 是否检测空值
+ * @return bool
+ */
+ public function has(string $name, string $type = 'param', bool $checkEmpty = false): bool
+ {
+ if (!in_array($type, ['param', 'get', 'post', 'put', 'patch', 'route', 'delete', 'cookie', 'session', 'env', 'request', 'server', 'header', 'file'])) {
+ return false;
+ }
+
+ $param = empty($this->$type) ? $this->$type() : $this->$type;
+
+ if (is_object($param)) {
+ return $param->has($name);
+ }
+
+ // 按.拆分成多维数组进行判断
+ foreach (explode('.', $name) as $val) {
+ if (isset($param[$val])) {
+ $param = $param[$val];
+ } else {
+ return false;
+ }
+ }
+
+ return ($checkEmpty && '' === $param) ? false : true;
+ }
+
+ /**
+ * 获取指定的参数
+ * @access public
+ * @param array $name 变量名
+ * @param mixed $data 数据或者变量类型
+ * @param string|array $filter 过滤方法
+ * @return array
+ */
+ public function only(array $name, $data = 'param', $filter = ''): array
+ {
+ $data = is_array($data) ? $data : $this->$data();
+
+ $item = [];
+ foreach ($name as $key => $val) {
+
+ if (is_int($key)) {
+ $default = null;
+ $key = $val;
+ if (!key_exists($key, $data)) {
+ continue;
+ }
+ } else {
+ $default = $val;
+ }
+
+ $item[$key] = $this->filterData($data[$key] ?? $default, $filter, $key, $default);
+ }
+
+ return $item;
+ }
+
+ /**
+ * 排除指定参数获取
+ * @access public
+ * @param array $name 变量名
+ * @param string $type 变量类型
+ * @return mixed
+ */
+ public function except(array $name, string $type = 'param'): array
+ {
+ $param = $this->$type();
+
+ foreach ($name as $key) {
+ if (isset($param[$key])) {
+ unset($param[$key]);
+ }
+ }
+
+ return $param;
+ }
+
+ /**
+ * 当前是否ssl
+ * @access public
+ * @return bool
+ */
+ public function isSsl(): bool
+ {
+ if ($this->server('HTTPS') && ('1' == $this->server('HTTPS') || 'on' == strtolower($this->server('HTTPS')))) {
+ return true;
+ } elseif ('https' == $this->server('REQUEST_SCHEME')) {
+ return true;
+ } elseif ('443' == $this->server('SERVER_PORT')) {
+ return true;
+ } elseif ('https' == $this->server('HTTP_X_FORWARDED_PROTO')) {
+ return true;
+ } elseif ($this->httpsAgentName && $this->server($this->httpsAgentName)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 当前是否JSON请求
+ * @access public
+ * @return bool
+ */
+ public function isJson(): bool
+ {
+ $acceptType = $this->type();
+
+ return false !== strpos($acceptType, 'json');
+ }
+
+ /**
+ * 当前是否Ajax请求
+ * @access public
+ * @param bool $ajax true 获取原始ajax请求
+ * @return bool
+ */
+ public function isAjax(bool $ajax = false): bool
+ {
+ $value = $this->server('HTTP_X_REQUESTED_WITH');
+ $result = $value && 'xmlhttprequest' == strtolower($value) ? true : false;
+
+ if (true === $ajax) {
+ return $result;
+ }
+
+ return $this->param($this->varAjax) ? true : $result;
+ }
+
+ /**
+ * 当前是否Pjax请求
+ * @access public
+ * @param bool $pjax true 获取原始pjax请求
+ * @return bool
+ */
+ public function isPjax(bool $pjax = false): bool
+ {
+ $result = !empty($this->server('HTTP_X_PJAX')) ? true : false;
+
+ if (true === $pjax) {
+ return $result;
+ }
+
+ return $this->param($this->varPjax) ? true : $result;
+ }
+
+ /**
+ * 获取客户端IP地址
+ * @access public
+ * @return string
+ */
+ public function ip(): string
+ {
+ if (!empty($this->realIP)) {
+ return $this->realIP;
+ }
+
+ $this->realIP = $this->server('REMOTE_ADDR', '');
+
+ // 如果指定了前端代理服务器IP以及其会发送的IP头
+ // 则尝试获取前端代理服务器发送过来的真实IP
+ $proxyIp = $this->proxyServerIp;
+ $proxyIpHeader = $this->proxyServerIpHeader;
+
+ if (count($proxyIp) > 0 && count($proxyIpHeader) > 0) {
+ // 从指定的HTTP头中依次尝试获取IP地址
+ // 直到获取到一个合法的IP地址
+ foreach ($proxyIpHeader as $header) {
+ $tempIP = $this->server($header);
+
+ if (empty($tempIP)) {
+ continue;
+ }
+
+ $tempIP = trim(explode(',', $tempIP)[0]);
+
+ if (!$this->isValidIP($tempIP)) {
+ $tempIP = null;
+ } else {
+ break;
+ }
+ }
+
+ // tempIP不为空,说明获取到了一个IP地址
+ // 这时我们检查 REMOTE_ADDR 是不是指定的前端代理服务器之一
+ // 如果是的话说明该 IP头 是由前端代理服务器设置的
+ // 否则则是伪装的
+ if (!empty($tempIP)) {
+ $realIPBin = $this->ip2bin($this->realIP);
+
+ foreach ($proxyIp as $ip) {
+ $serverIPElements = explode('/', $ip);
+ $serverIP = $serverIPElements[0];
+ $serverIPPrefix = $serverIPElements[1] ?? 128;
+ $serverIPBin = $this->ip2bin($serverIP);
+
+ // IP类型不符
+ if (strlen($realIPBin) !== strlen($serverIPBin)) {
+ continue;
+ }
+
+ if (strncmp($realIPBin, $serverIPBin, (int) $serverIPPrefix) === 0) {
+ $this->realIP = $tempIP;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!$this->isValidIP($this->realIP)) {
+ $this->realIP = '0.0.0.0';
+ }
+
+ return $this->realIP;
+ }
+
+ /**
+ * 检测是否是合法的IP地址
+ *
+ * @param string $ip IP地址
+ * @param string $type IP地址类型 (ipv4, ipv6)
+ *
+ * @return boolean
+ */
+ public function isValidIP(string $ip, string $type = ''): bool
+ {
+ switch (strtolower($type)) {
+ case 'ipv4':
+ $flag = FILTER_FLAG_IPV4;
+ break;
+ case 'ipv6':
+ $flag = FILTER_FLAG_IPV6;
+ break;
+ default:
+ $flag = 0;
+ break;
+ }
+
+ return boolval(filter_var($ip, FILTER_VALIDATE_IP, $flag));
+ }
+
+ /**
+ * 将IP地址转换为二进制字符串
+ *
+ * @param string $ip
+ *
+ * @return string
+ */
+ public function ip2bin(string $ip): string
+ {
+ if ($this->isValidIP($ip, 'ipv6')) {
+ $IPHex = str_split(bin2hex(inet_pton($ip)), 4);
+ foreach ($IPHex as $key => $value) {
+ $IPHex[$key] = intval($value, 16);
+ }
+ $IPBin = vsprintf('%016b%016b%016b%016b%016b%016b%016b%016b', $IPHex);
+ } else {
+ $IPHex = str_split(bin2hex(inet_pton($ip)), 2);
+ foreach ($IPHex as $key => $value) {
+ $IPHex[$key] = intval($value, 16);
+ }
+ $IPBin = vsprintf('%08b%08b%08b%08b', $IPHex);
+ }
+
+ return $IPBin;
+ }
+
+ /**
+ * 检测是否使用手机访问
+ * @access public
+ * @return bool
+ */
+ public function isMobile(): bool
+ {
+ if ($this->server('HTTP_VIA') && stristr($this->server('HTTP_VIA'), "wap")) {
+ return true;
+ } elseif ($this->server('HTTP_ACCEPT') && strpos(strtoupper($this->server('HTTP_ACCEPT')), "VND.WAP.WML")) {
+ return true;
+ } elseif ($this->server('HTTP_X_WAP_PROFILE') || $this->server('HTTP_PROFILE')) {
+ return true;
+ } elseif ($this->server('HTTP_USER_AGENT') && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $this->server('HTTP_USER_AGENT'))) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 当前URL地址中的scheme参数
+ * @access public
+ * @return string
+ */
+ public function scheme(): string
+ {
+ return $this->isSsl() ? 'https' : 'http';
+ }
+
+ /**
+ * 当前请求URL地址中的query参数
+ * @access public
+ * @return string
+ */
+ public function query(): string
+ {
+ return $this->server('QUERY_STRING', '');
+ }
+
+ /**
+ * 设置当前请求的host(包含端口)
+ * @access public
+ * @param string $host 主机名(含端口)
+ * @return $this
+ */
+ public function setHost(string $host)
+ {
+ $this->host = $host;
+
+ return $this;
+ }
+
+ /**
+ * 当前请求的host
+ * @access public
+ * @param bool $strict true 仅仅获取HOST
+ * @return string
+ */
+ public function host(bool $strict = false): string
+ {
+ if ($this->host) {
+ $host = $this->host;
+ } else {
+ $host = strval($this->server('HTTP_X_FORWARDED_HOST') ?: $this->server('HTTP_HOST'));
+ }
+
+ return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host;
+ }
+
+ /**
+ * 当前请求URL地址中的port参数
+ * @access public
+ * @return int
+ */
+ public function port(): int
+ {
+ return (int) ($this->server('HTTP_X_FORWARDED_PORT') ?: $this->server('SERVER_PORT', ''));
+ }
+
+ /**
+ * 当前请求 SERVER_PROTOCOL
+ * @access public
+ * @return string
+ */
+ public function protocol(): string
+ {
+ return $this->server('SERVER_PROTOCOL', '');
+ }
+
+ /**
+ * 当前请求 REMOTE_PORT
+ * @access public
+ * @return int
+ */
+ public function remotePort(): int
+ {
+ return (int) $this->server('REMOTE_PORT', '');
+ }
+
+ /**
+ * 当前请求 HTTP_CONTENT_TYPE
+ * @access public
+ * @return string
+ */
+ public function contentType(): string
+ {
+ $contentType = $this->header('Content-Type');
+
+ if ($contentType) {
+ if (strpos($contentType, ';')) {
+ [$type] = explode(';', $contentType);
+ } else {
+ $type = $contentType;
+ }
+ return trim($type);
+ }
+
+ return '';
+ }
+
+ /**
+ * 获取当前请求的安全Key
+ * @access public
+ * @return string
+ */
+ public function secureKey(): string
+ {
+ if (is_null($this->secureKey)) {
+ $this->secureKey = uniqid('', true);
+ }
+
+ return $this->secureKey;
+ }
+
+ /**
+ * 设置当前的控制器名
+ * @access public
+ * @param string $controller 控制器名
+ * @return $this
+ */
+ public function setController(string $controller)
+ {
+ $this->controller = $controller;
+ return $this;
+ }
+
+ /**
+ * 设置当前的操作名
+ * @access public
+ * @param string $action 操作名
+ * @return $this
+ */
+ public function setAction(string $action)
+ {
+ $this->action = $action;
+ return $this;
+ }
+
+ /**
+ * 获取当前的控制器名
+ * @access public
+ * @param bool $convert 转换为小写
+ * @return string
+ */
+ public function controller(bool $convert = false): string
+ {
+ $name = $this->controller ?: '';
+ return $convert ? strtolower($name) : $name;
+ }
+
+ /**
+ * 获取当前的操作名
+ * @access public
+ * @param bool $convert 转换为小写
+ * @return string
+ */
+ public function action(bool $convert = false): string
+ {
+ $name = $this->action ?: '';
+ return $convert ? strtolower($name) : $name;
+ }
+
+ /**
+ * 设置或者获取当前请求的content
+ * @access public
+ * @return string
+ */
+ public function getContent(): string
+ {
+ if (is_null($this->content)) {
+ $this->content = $this->input;
+ }
+
+ return $this->content;
+ }
+
+ /**
+ * 获取当前请求的php://input
+ * @access public
+ * @return string
+ */
+ public function getInput(): string
+ {
+ return $this->input;
+ }
+
+ /**
+ * 生成请求令牌
+ * @access public
+ * @param string $name 令牌名称
+ * @param mixed $type 令牌生成方法
+ * @return string
+ */
+ public function buildToken(string $name = '__token__', $type = 'md5'): string
+ {
+ $type = is_callable($type) ? $type : 'md5';
+ $token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT'));
+
+ $this->session->set($name, $token);
+
+ return $token;
+ }
+
+ /**
+ * 检查请求令牌
+ * @access public
+ * @param string $token 令牌名称
+ * @param array $data 表单数据
+ * @return bool
+ */
+ public function checkToken(string $token = '__token__', array $data = []): bool
+ {
+ if (in_array($this->method(), ['GET', 'HEAD', 'OPTIONS'], true)) {
+ return true;
+ }
+
+ if (!$this->session->has($token)) {
+ // 令牌数据无效
+ return false;
+ }
+
+ // Header验证
+ if ($this->header('X-CSRF-TOKEN') && $this->session->get($token) === $this->header('X-CSRF-TOKEN')) {
+ // 防止重复提交
+ $this->session->delete($token); // 验证完成销毁session
+ return true;
+ }
+
+ if (empty($data)) {
+ $data = $this->post();
+ }
+
+ // 令牌验证
+ if (isset($data[$token]) && $this->session->get($token) === $data[$token]) {
+ // 防止重复提交
+ $this->session->delete($token); // 验证完成销毁session
+ return true;
+ }
+
+ // 开启TOKEN重置
+ $this->session->delete($token);
+ return false;
+ }
+
+ /**
+ * 设置在中间件传递的数据
+ * @access public
+ * @param array $middleware 数据
+ * @return $this
+ */
+ public function withMiddleware(array $middleware)
+ {
+ $this->middleware = array_merge($this->middleware, $middleware);
+ return $this;
+ }
+
+ /**
+ * 设置GET数据
+ * @access public
+ * @param array $get 数据
+ * @return $this
+ */
+ public function withGet(array $get)
+ {
+ $this->get = $get;
+ return $this;
+ }
+
+ /**
+ * 设置POST数据
+ * @access public
+ * @param array $post 数据
+ * @return $this
+ */
+ public function withPost(array $post)
+ {
+ $this->post = $post;
+ return $this;
+ }
+
+ /**
+ * 设置COOKIE数据
+ * @access public
+ * @param array $cookie 数据
+ * @return $this
+ */
+ public function withCookie(array $cookie)
+ {
+ $this->cookie = $cookie;
+ return $this;
+ }
+
+ /**
+ * 设置SESSION数据
+ * @access public
+ * @param Session $session 数据
+ * @return $this
+ */
+ public function withSession(Session $session)
+ {
+ $this->session = $session;
+ return $this;
+ }
+
+ /**
+ * 设置SERVER数据
+ * @access public
+ * @param array $server 数据
+ * @return $this
+ */
+ public function withServer(array $server)
+ {
+ $this->server = array_change_key_case($server, CASE_UPPER);
+ return $this;
+ }
+
+ /**
+ * 设置HEADER数据
+ * @access public
+ * @param array $header 数据
+ * @return $this
+ */
+ public function withHeader(array $header)
+ {
+ $this->header = array_change_key_case($header);
+ return $this;
+ }
+
+ /**
+ * 设置ENV数据
+ * @access public
+ * @param Env $env 数据
+ * @return $this
+ */
+ public function withEnv(Env $env)
+ {
+ $this->env = $env;
+ return $this;
+ }
+
+ /**
+ * 设置php://input数据
+ * @access public
+ * @param string $input RAW数据
+ * @return $this
+ */
+ public function withInput(string $input)
+ {
+ $this->input = $input;
+ if (!empty($input)) {
+ $inputData = $this->getInputData($input);
+ if (!empty($inputData)) {
+ $this->post = $inputData;
+ $this->put = $inputData;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * 设置文件上传数据
+ * @access public
+ * @param array $files 上传信息
+ * @return $this
+ */
+ public function withFiles(array $files)
+ {
+ $this->file = $files;
+ return $this;
+ }
+
+ /**
+ * 设置ROUTE变量
+ * @access public
+ * @param array $route 数据
+ * @return $this
+ */
+ public function withRoute(array $route)
+ {
+ $this->route = $route;
+ return $this;
+ }
+
+ /**
+ * 设置中间传递数据
+ * @access public
+ * @param string $name 参数名
+ * @param mixed $value 值
+ */
+ public function __set(string $name, $value)
+ {
+ $this->middleware[$name] = $value;
+ }
+
+ /**
+ * 获取中间传递数据的值
+ * @access public
+ * @param string $name 名称
+ * @return mixed
+ */
+ public function __get(string $name)
+ {
+ return $this->middleware($name);
+ }
+
+ /**
+ * 检测中间传递数据的值
+ * @access public
+ * @param string $name 名称
+ * @return boolean
+ */
+ public function __isset(string $name): bool
+ {
+ return isset($this->middleware[$name]);
+ }
+
+ // ArrayAccess
+ #[\ReturnTypeWillChange]
+ public function offsetExists($name): bool
+ {
+ return $this->has($name);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetGet($name)
+ {
+ return $this->param($name);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetSet($name, $value)
+ {}
+
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($name)
+ {}
+
+}
diff --git a/vendor/topthink/framework/src/think/Response.php b/vendor/topthink/framework/src/think/Response.php
new file mode 100755
index 0000000..49f26dc
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Response.php
@@ -0,0 +1,413 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+/**
+ * 响应输出基础类
+ * @package think
+ */
+abstract class Response
+{
+ /**
+ * 原始数据
+ * @var mixed
+ */
+ protected $data;
+
+ /**
+ * 当前contentType
+ * @var string
+ */
+ protected $contentType = 'text/html';
+
+ /**
+ * 字符集
+ * @var string
+ */
+ protected $charset = 'utf-8';
+
+ /**
+ * 状态码
+ * @var integer
+ */
+ protected $code = 200;
+
+ /**
+ * 是否允许请求缓存
+ * @var bool
+ */
+ protected $allowCache = true;
+
+ /**
+ * 输出参数
+ * @var array
+ */
+ protected $options = [];
+
+ /**
+ * header参数
+ * @var array
+ */
+ protected $header = [];
+
+ /**
+ * 输出内容
+ * @var string
+ */
+ protected $content = null;
+
+ /**
+ * Cookie对象
+ * @var Cookie
+ */
+ protected $cookie;
+
+ /**
+ * Session对象
+ * @var Session
+ */
+ protected $session;
+
+ /**
+ * 初始化
+ * @access protected
+ * @param mixed $data 输出数据
+ * @param int $code 状态码
+ */
+ protected function init($data = '', int $code = 200)
+ {
+ $this->data($data);
+ $this->code = $code;
+
+ $this->contentType($this->contentType, $this->charset);
+ }
+
+ /**
+ * 创建Response对象
+ * @access public
+ * @param mixed $data 输出数据
+ * @param string $type 输出类型
+ * @param int $code 状态码
+ * @return Response
+ */
+ public static function create($data = '', string $type = 'html', int $code = 200): Response
+ {
+ $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type));
+
+ return Container::getInstance()->invokeClass($class, [$data, $code]);
+ }
+
+ /**
+ * 设置Session对象
+ * @access public
+ * @param Session $session Session对象
+ * @return $this
+ */
+ public function setSession(Session $session)
+ {
+ $this->session = $session;
+ return $this;
+ }
+
+ /**
+ * 发送数据到客户端
+ * @access public
+ * @return void
+ * @throws \InvalidArgumentException
+ */
+ public function send(): void
+ {
+ // 处理输出数据
+ $data = $this->getContent();
+
+ if (!headers_sent()) {
+ if (!empty($this->header)) {
+ // 发送状态码
+ http_response_code($this->code);
+ // 发送头部信息
+ foreach ($this->header as $name => $val) {
+ header($name . (!is_null($val) ? ':' . $val : ''));
+ }
+ }
+
+ if ($this->cookie) {
+ $this->cookie->save();
+ }
+ }
+
+ $this->sendData($data);
+
+ if (function_exists('fastcgi_finish_request')) {
+ // 提高页面响应
+ fastcgi_finish_request();
+ }
+ }
+
+ /**
+ * 处理数据
+ * @access protected
+ * @param mixed $data 要处理的数据
+ * @return mixed
+ */
+ protected function output($data)
+ {
+ return $data;
+ }
+
+ /**
+ * 输出数据
+ * @access protected
+ * @param string $data 要处理的数据
+ * @return void
+ */
+ protected function sendData(string $data): void
+ {
+ echo $data;
+ }
+
+ /**
+ * 输出的参数
+ * @access public
+ * @param mixed $options 输出参数
+ * @return $this
+ */
+ public function options(array $options = [])
+ {
+ $this->options = array_merge($this->options, $options);
+
+ return $this;
+ }
+
+ /**
+ * 输出数据设置
+ * @access public
+ * @param mixed $data 输出数据
+ * @return $this
+ */
+ public function data($data)
+ {
+ $this->data = $data;
+
+ return $this;
+ }
+
+ /**
+ * 是否允许请求缓存
+ * @access public
+ * @param bool $cache 允许请求缓存
+ * @return $this
+ */
+ public function allowCache(bool $cache)
+ {
+ $this->allowCache = $cache;
+
+ return $this;
+ }
+
+ /**
+ * 是否允许请求缓存
+ * @access public
+ * @return bool
+ */
+ public function isAllowCache()
+ {
+ return $this->allowCache;
+ }
+
+ /**
+ * 设置Cookie
+ * @access public
+ * @param string $name cookie名称
+ * @param string $value cookie值
+ * @param mixed $option 可选参数
+ * @return $this
+ */
+ public function cookie(string $name, string $value, $option = null)
+ {
+ $this->cookie->set($name, $value, $option);
+
+ return $this;
+ }
+
+ /**
+ * 设置响应头
+ * @access public
+ * @param array $header 参数
+ * @return $this
+ */
+ public function header(array $header = [])
+ {
+ $this->header = array_merge($this->header, $header);
+
+ return $this;
+ }
+
+ /**
+ * 设置页面输出内容
+ * @access public
+ * @param mixed $content
+ * @return $this
+ */
+ public function content($content)
+ {
+ if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
+ $content,
+ '__toString',
+ ])
+ ) {
+ throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content)));
+ }
+
+ $this->content = (string) $content;
+
+ return $this;
+ }
+
+ /**
+ * 发送HTTP状态
+ * @access public
+ * @param integer $code 状态码
+ * @return $this
+ */
+ public function code(int $code)
+ {
+ $this->code = $code;
+
+ return $this;
+ }
+
+ /**
+ * LastModified
+ * @access public
+ * @param string $time
+ * @return $this
+ */
+ public function lastModified(string $time)
+ {
+ $this->header['Last-Modified'] = $time;
+
+ return $this;
+ }
+
+ /**
+ * Expires
+ * @access public
+ * @param string $time
+ * @return $this
+ */
+ public function expires(string $time)
+ {
+ $this->header['Expires'] = $time;
+
+ return $this;
+ }
+
+ /**
+ * ETag
+ * @access public
+ * @param string $eTag
+ * @return $this
+ */
+ public function eTag(string $eTag)
+ {
+ $this->header['ETag'] = $eTag;
+
+ return $this;
+ }
+
+ /**
+ * 页面缓存控制
+ * @access public
+ * @param string $cache 状态码
+ * @return $this
+ */
+ public function cacheControl(string $cache)
+ {
+ $this->header['Cache-control'] = $cache;
+
+ return $this;
+ }
+
+ /**
+ * 页面输出类型
+ * @access public
+ * @param string $contentType 输出类型
+ * @param string $charset 输出编码
+ * @return $this
+ */
+ public function contentType(string $contentType, string $charset = 'utf-8')
+ {
+ $this->header['Content-Type'] = $contentType . '; charset=' . $charset;
+
+ return $this;
+ }
+
+ /**
+ * 获取头部信息
+ * @access public
+ * @param string $name 头部名称
+ * @return mixed
+ */
+ public function getHeader(string $name = '')
+ {
+ if (!empty($name)) {
+ return $this->header[$name] ?? null;
+ }
+
+ return $this->header;
+ }
+
+ /**
+ * 获取原始数据
+ * @access public
+ * @return mixed
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * 获取输出数据
+ * @access public
+ * @return string
+ */
+ public function getContent(): string
+ {
+ if (null == $this->content) {
+ $content = $this->output($this->data);
+
+ if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
+ $content,
+ '__toString',
+ ])
+ ) {
+ throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content)));
+ }
+
+ $this->content = (string) $content;
+ }
+
+ return $this->content;
+ }
+
+ /**
+ * 获取状态码
+ * @access public
+ * @return integer
+ */
+ public function getCode(): int
+ {
+ return $this->code;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Route.php b/vendor/topthink/framework/src/think/Route.php
new file mode 100755
index 0000000..e45cb33
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Route.php
@@ -0,0 +1,939 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Closure;
+use think\exception\RouteNotFoundException;
+use think\route\Dispatch;
+use think\route\dispatch\Callback;
+use think\route\dispatch\Url as UrlDispatch;
+use think\route\Domain;
+use think\route\Resource;
+use think\route\Rule;
+use think\route\RuleGroup;
+use think\route\RuleItem;
+use think\route\RuleName;
+use think\route\Url as UrlBuild;
+
+/**
+ * 路由管理类
+ * @package think
+ */
+class Route
+{
+ /**
+ * REST定义
+ * @var array
+ */
+ protected $rest = [
+ 'index' => ['get', '', 'index'],
+ 'create' => ['get', '/create', 'create'],
+ 'edit' => ['get', '/<id>/edit', 'edit'],
+ 'read' => ['get', '/<id>', 'read'],
+ 'save' => ['post', '', 'save'],
+ 'update' => ['put', '/<id>', 'update'],
+ 'delete' => ['delete', '/<id>', 'delete'],
+ ];
+
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [
+ // pathinfo分隔符
+ 'pathinfo_depr' => '/',
+ // 是否开启路由延迟解析
+ 'url_lazy_route' => false,
+ // 是否强制使用路由
+ 'url_route_must' => false,
+ // 合并路由规则
+ 'route_rule_merge' => false,
+ // 路由是否完全匹配
+ 'route_complete_match' => false,
+ // 去除斜杠
+ 'remove_slash' => false,
+ // 使用注解路由
+ 'route_annotation' => false,
+ // 默认的路由变量规则
+ 'default_route_pattern' => '[\w\.]+',
+ // URL伪静态后缀
+ 'url_html_suffix' => 'html',
+ // 访问控制器层名称
+ 'controller_layer' => 'controller',
+ // 空控制器名
+ 'empty_controller' => 'Error',
+ // 是否使用控制器后缀
+ 'controller_suffix' => false,
+ // 默认控制器名
+ 'default_controller' => 'Index',
+ // 默认操作名
+ 'default_action' => 'index',
+ // 操作方法后缀
+ 'action_suffix' => '',
+ // 非路由变量是否使用普通参数方式(用于URL生成)
+ 'url_common_param' => true,
+ ];
+
+ /**
+ * 当前应用
+ * @var App
+ */
+ protected $app;
+
+ /**
+ * 请求对象
+ * @var Request
+ */
+ protected $request;
+
+ /**
+ * @var RuleName
+ */
+ protected $ruleName;
+
+ /**
+ * 当前HOST
+ * @var string
+ */
+ protected $host;
+
+ /**
+ * 当前分组对象
+ * @var RuleGroup
+ */
+ protected $group;
+
+ /**
+ * 路由绑定
+ * @var array
+ */
+ protected $bind = [];
+
+ /**
+ * 域名对象
+ * @var Domain[]
+ */
+ protected $domains = [];
+
+ /**
+ * 跨域路由规则
+ * @var RuleGroup
+ */
+ protected $cross;
+
+ /**
+ * 路由是否延迟解析
+ * @var bool
+ */
+ protected $lazy = false;
+
+ /**
+ * 路由是否测试模式
+ * @var bool
+ */
+ protected $isTest = false;
+
+ /**
+ * (分组)路由规则是否合并解析
+ * @var bool
+ */
+ protected $mergeRuleRegex = false;
+
+ /**
+ * 是否去除URL最后的斜线
+ * @var bool
+ */
+ protected $removeSlash = false;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ $this->ruleName = new RuleName();
+ $this->setDefaultDomain();
+
+ if (is_file($this->app->getRuntimePath() . 'route.php')) {
+ // 读取路由映射文件
+ $this->import(include $this->app->getRuntimePath() . 'route.php');
+ }
+
+ $this->config = array_merge($this->config, $this->app->config->get('route'));
+
+ $this->init();
+ }
+
+ protected function init()
+ {
+ if (!empty($this->config['middleware'])) {
+ $this->app->middleware->import($this->config['middleware'], 'route');
+ }
+
+ $this->lazy($this->config['url_lazy_route']);
+ $this->mergeRuleRegex = $this->config['route_rule_merge'];
+ $this->removeSlash = $this->config['remove_slash'];
+
+ $this->group->removeSlash($this->removeSlash);
+ }
+
+ public function config(string $name = null)
+ {
+ if (is_null($name)) {
+ return $this->config;
+ }
+
+ return $this->config[$name] ?? null;
+ }
+
+ /**
+ * 设置路由域名及分组(包括资源路由)是否延迟解析
+ * @access public
+ * @param bool $lazy 路由是否延迟解析
+ * @return $this
+ */
+ public function lazy(bool $lazy = true)
+ {
+ $this->lazy = $lazy;
+ return $this;
+ }
+
+ /**
+ * 设置路由为测试模式
+ * @access public
+ * @param bool $test 路由是否测试模式
+ * @return void
+ */
+ public function setTestMode(bool $test): void
+ {
+ $this->isTest = $test;
+ }
+
+ /**
+ * 检查路由是否为测试模式
+ * @access public
+ * @return bool
+ */
+ public function isTest(): bool
+ {
+ return $this->isTest;
+ }
+
+ /**
+ * 设置路由域名及分组(包括资源路由)是否合并解析
+ * @access public
+ * @param bool $merge 路由是否合并解析
+ * @return $this
+ */
+ public function mergeRuleRegex(bool $merge = true)
+ {
+ $this->mergeRuleRegex = $merge;
+ $this->group->mergeRuleRegex($merge);
+
+ return $this;
+ }
+
+ /**
+ * 初始化默认域名
+ * @access protected
+ * @return void
+ */
+ protected function setDefaultDomain(): void
+ {
+ // 注册默认域名
+ $domain = new Domain($this);
+
+ $this->domains['-'] = $domain;
+
+ // 默认分组
+ $this->group = $domain;
+ }
+
+ /**
+ * 设置当前分组
+ * @access public
+ * @param RuleGroup $group 域名
+ * @return void
+ */
+ public function setGroup(RuleGroup $group): void
+ {
+ $this->group = $group;
+ }
+
+ /**
+ * 获取指定标识的路由分组 不指定则获取当前分组
+ * @access public
+ * @param string $name 分组标识
+ * @return RuleGroup
+ */
+ public function getGroup(string $name = null)
+ {
+ return $name ? $this->ruleName->getGroup($name) : $this->group;
+ }
+
+ /**
+ * 注册变量规则
+ * @access public
+ * @param array $pattern 变量规则
+ * @return $this
+ */
+ public function pattern(array $pattern)
+ {
+ $this->group->pattern($pattern);
+
+ return $this;
+ }
+
+ /**
+ * 注册路由参数
+ * @access public
+ * @param array $option 参数
+ * @return $this
+ */
+ public function option(array $option)
+ {
+ $this->group->option($option);
+
+ return $this;
+ }
+
+ /**
+ * 注册域名路由
+ * @access public
+ * @param string|array $name 子域名
+ * @param mixed $rule 路由规则
+ * @return Domain
+ */
+ public function domain($name, $rule = null): Domain
+ {
+ // 支持多个域名使用相同路由规则
+ $domainName = is_array($name) ? array_shift($name) : $name;
+
+ if (!isset($this->domains[$domainName])) {
+ $domain = (new Domain($this, $domainName, $rule))
+ ->lazy($this->lazy)
+ ->removeSlash($this->removeSlash)
+ ->mergeRuleRegex($this->mergeRuleRegex);
+
+ $this->domains[$domainName] = $domain;
+ } else {
+ $domain = $this->domains[$domainName];
+ $domain->parseGroupRule($rule);
+ }
+
+ if (is_array($name) && !empty($name)) {
+ foreach ($name as $item) {
+ $this->domains[$item] = $domainName;
+ }
+ }
+
+ // 返回域名对象
+ return $domain;
+ }
+
+ /**
+ * 获取域名
+ * @access public
+ * @return array
+ */
+ public function getDomains(): array
+ {
+ return $this->domains;
+ }
+
+ /**
+ * 获取RuleName对象
+ * @access public
+ * @return RuleName
+ */
+ public function getRuleName(): RuleName
+ {
+ return $this->ruleName;
+ }
+
+ /**
+ * 设置路由绑定
+ * @access public
+ * @param string $bind 绑定信息
+ * @param string $domain 域名
+ * @return $this
+ */
+ public function bind(string $bind, string $domain = null)
+ {
+ $domain = is_null($domain) ? '-' : $domain;
+
+ $this->bind[$domain] = $bind;
+
+ return $this;
+ }
+
+ /**
+ * 读取路由绑定信息
+ * @access public
+ * @return array
+ */
+ public function getBind(): array
+ {
+ return $this->bind;
+ }
+
+ /**
+ * 读取路由绑定
+ * @access public
+ * @param string $domain 域名
+ * @return string|null
+ */
+ public function getDomainBind(string $domain = null)
+ {
+ if (is_null($domain)) {
+ $domain = $this->host;
+ } elseif (false === strpos($domain, '.') && $this->request) {
+ $domain .= '.' . $this->request->rootDomain();
+ }
+
+ if ($this->request) {
+ $subDomain = $this->request->subDomain();
+
+ if (strpos($subDomain, '.')) {
+ $name = '*' . strstr($subDomain, '.');
+ }
+ }
+
+ if (isset($this->bind[$domain])) {
+ $result = $this->bind[$domain];
+ } elseif (isset($name) && isset($this->bind[$name])) {
+ $result = $this->bind[$name];
+ } elseif (!empty($subDomain) && isset($this->bind['*'])) {
+ $result = $this->bind['*'];
+ } else {
+ $result = null;
+ }
+
+ return $result;
+ }
+
+ /**
+ * 读取路由标识
+ * @access public
+ * @param string $name 路由标识
+ * @param string $domain 域名
+ * @param string $method 请求类型
+ * @return array
+ */
+ public function getName(string $name = null, string $domain = null, string $method = '*'): array
+ {
+ return $this->ruleName->getName($name, $domain, $method);
+ }
+
+ /**
+ * 批量导入路由标识
+ * @access public
+ * @param array $name 路由标识
+ * @return void
+ */
+ public function import(array $name): void
+ {
+ $this->ruleName->import($name);
+ }
+
+ /**
+ * 注册路由标识
+ * @access public
+ * @param string $name 路由标识
+ * @param RuleItem $ruleItem 路由规则
+ * @param bool $first 是否优先
+ * @return void
+ */
+ public function setName(string $name, RuleItem $ruleItem, bool $first = false): void
+ {
+ $this->ruleName->setName($name, $ruleItem, $first);
+ }
+
+ /**
+ * 保存路由规则
+ * @access public
+ * @param string $rule 路由规则
+ * @param RuleItem $ruleItem RuleItem对象
+ * @return void
+ */
+ public function setRule(string $rule, RuleItem $ruleItem = null): void
+ {
+ $this->ruleName->setRule($rule, $ruleItem);
+ }
+
+ /**
+ * 读取路由
+ * @access public
+ * @param string $rule 路由规则
+ * @return RuleItem[]
+ */
+ public function getRule(string $rule): array
+ {
+ return $this->ruleName->getRule($rule);
+ }
+
+ /**
+ * 读取路由列表
+ * @access public
+ * @return array
+ */
+ public function getRuleList(): array
+ {
+ return $this->ruleName->getRuleList();
+ }
+
+ /**
+ * 清空路由规则
+ * @access public
+ * @return void
+ */
+ public function clear(): void
+ {
+ $this->ruleName->clear();
+
+ if ($this->group) {
+ $this->group->clear();
+ }
+ }
+
+ /**
+ * 注册路由规则
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @param string $method 请求类型
+ * @return RuleItem
+ */
+ public function rule(string $rule, $route = null, string $method = '*'): RuleItem
+ {
+ if ($route instanceof Response) {
+ // 兼容之前的路由到响应对象,感觉不需要,使用场景很少,闭包就能实现
+ $route = function () use ($route) {
+ return $route;
+ };
+ }
+ return $this->group->addRule($rule, $route, $method);
+ }
+
+ /**
+ * 设置跨域有效路由规则
+ * @access public
+ * @param Rule $rule 路由规则
+ * @param string $method 请求类型
+ * @return $this
+ */
+ public function setCrossDomainRule(Rule $rule, string $method = '*')
+ {
+ if (!isset($this->cross)) {
+ $this->cross = (new RuleGroup($this))->mergeRuleRegex($this->mergeRuleRegex);
+ }
+
+ $this->cross->addRuleItem($rule, $method);
+
+ return $this;
+ }
+
+ /**
+ * 注册路由分组
+ * @access public
+ * @param string|\Closure $name 分组名称或者参数
+ * @param mixed $route 分组路由
+ * @return RuleGroup
+ */
+ public function group($name, $route = null): RuleGroup
+ {
+ if ($name instanceof Closure) {
+ $route = $name;
+ $name = '';
+ }
+
+ return (new RuleGroup($this, $this->group, $name, $route))
+ ->lazy($this->lazy)
+ ->removeSlash($this->removeSlash)
+ ->mergeRuleRegex($this->mergeRuleRegex);
+ }
+
+ /**
+ * 注册路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function any(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, '*');
+ }
+
+ /**
+ * 注册GET路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function get(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, 'GET');
+ }
+
+ /**
+ * 注册POST路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function post(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, 'POST');
+ }
+
+ /**
+ * 注册PUT路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function put(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, 'PUT');
+ }
+
+ /**
+ * 注册DELETE路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function delete(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, 'DELETE');
+ }
+
+ /**
+ * 注册PATCH路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function patch(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, 'PATCH');
+ }
+
+ /**
+ * 注册HEAD路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function head(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, 'HEAD');
+ }
+
+ /**
+ * 注册OPTIONS路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @return RuleItem
+ */
+ public function options(string $rule, $route): RuleItem
+ {
+ return $this->rule($rule, $route, 'OPTIONS');
+ }
+
+ /**
+ * 注册资源路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param string $route 路由地址
+ * @return Resource
+ */
+ public function resource(string $rule, string $route): Resource
+ {
+ return (new Resource($this, $this->group, $rule, $route, $this->rest))
+ ->lazy($this->lazy);
+ }
+
+ /**
+ * 注册视图路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param string $template 路由模板地址
+ * @param array $vars 模板变量
+ * @return RuleItem
+ */
+ public function view(string $rule, string $template = '', array $vars = []): RuleItem
+ {
+ return $this->rule($rule, function () use ($vars, $template) {
+ return Response::create($template, 'view')->assign($vars);
+ }, 'GET');
+ }
+
+ /**
+ * 注册重定向路由
+ * @access public
+ * @param string $rule 路由规则
+ * @param string $route 路由地址
+ * @param int $status 状态码
+ * @return RuleItem
+ */
+ public function redirect(string $rule, string $route = '', int $status = 301): RuleItem
+ {
+ return $this->rule($rule, function (Request $request) use ($status, $route) {
+ $search = $replace = [];
+ $matches = $request->rule()->getVars();
+
+ foreach ($matches as $key => $value) {
+ $search[] = '<' . $key . '>';
+ $replace[] = $value;
+
+ $search[] = ':' . $key;
+ $replace[] = $value;
+ }
+
+ $route = str_replace($search, $replace, $route);
+ return Response::create($route, 'redirect')->code($status);
+ }, '*');
+ }
+
+ /**
+ * rest方法定义和修改
+ * @access public
+ * @param string|array $name 方法名称
+ * @param array|bool $resource 资源
+ * @return $this
+ */
+ public function rest($name, $resource = [])
+ {
+ if (is_array($name)) {
+ $this->rest = $resource ? $name : array_merge($this->rest, $name);
+ } else {
+ $this->rest[$name] = $resource;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 获取rest方法定义的参数
+ * @access public
+ * @param string $name 方法名称
+ * @return array|null
+ */
+ public function getRest(string $name = null)
+ {
+ if (is_null($name)) {
+ return $this->rest;
+ }
+
+ return $this->rest[$name] ?? null;
+ }
+
+ /**
+ * 注册未匹配路由规则后的处理
+ * @access public
+ * @param string|Closure $route 路由地址
+ * @param string $method 请求类型
+ * @return RuleItem
+ */
+ public function miss($route, string $method = '*'): RuleItem
+ {
+ return $this->group->miss($route, $method);
+ }
+
+ /**
+ * 路由调度
+ * @param Request $request
+ * @param Closure|bool $withRoute
+ * @return Response
+ */
+ public function dispatch(Request $request, $withRoute = true)
+ {
+ $this->request = $request;
+ $this->host = $this->request->host(true);
+
+ if ($withRoute) {
+ //加载路由
+ if ($withRoute instanceof Closure) {
+ $withRoute();
+ }
+ $dispatch = $this->check();
+ } else {
+ $dispatch = $this->url($this->path());
+ }
+
+ $dispatch->init($this->app);
+
+ return $this->app->middleware->pipeline('route')
+ ->send($request)
+ ->then(function () use ($dispatch) {
+ return $dispatch->run();
+ });
+ }
+
+ /**
+ * 检测URL路由
+ * @access public
+ * @return Dispatch|false
+ * @throws RouteNotFoundException
+ */
+ public function check()
+ {
+ // 自动检测域名路由
+ $url = str_replace($this->config['pathinfo_depr'], '|', $this->path());
+
+ $completeMatch = $this->config['route_complete_match'];
+
+ $result = $this->checkDomain()->check($this->request, $url, $completeMatch);
+
+ if (false === $result && !empty($this->cross)) {
+ // 检测跨域路由
+ $result = $this->cross->check($this->request, $url, $completeMatch);
+ }
+
+ if (false !== $result) {
+ return $result;
+ } elseif ($this->config['url_route_must']) {
+ throw new RouteNotFoundException();
+ }
+
+ return $this->url($url);
+ }
+
+ /**
+ * 获取当前请求URL的pathinfo信息(不含URL后缀)
+ * @access protected
+ * @return string
+ */
+ protected function path(): string
+ {
+ $suffix = $this->config['url_html_suffix'];
+ $pathinfo = $this->request->pathinfo();
+
+ if (false === $suffix) {
+ // 禁止伪静态访问
+ $path = $pathinfo;
+ } elseif ($suffix) {
+ // 去除正常的URL后缀
+ $path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
+ } else {
+ // 允许任何后缀访问
+ $path = preg_replace('/\.' . $this->request->ext() . '$/i', '', $pathinfo);
+ }
+
+ return $path;
+ }
+
+ /**
+ * 默认URL解析
+ * @access public
+ * @param string $url URL地址
+ * @return Dispatch
+ */
+ public function url(string $url): Dispatch
+ {
+ if ($this->request->method() == 'OPTIONS') {
+ // 自动响应options请求
+ return new Callback($this->request, $this->group, function () {
+ return Response::create('', 'html', 204)->header(['Allow' => 'GET, POST, PUT, DELETE']);
+ });
+ }
+
+ return new UrlDispatch($this->request, $this->group, $url);
+ }
+
+ /**
+ * 检测域名的路由规则
+ * @access protected
+ * @return Domain
+ */
+ protected function checkDomain(): Domain
+ {
+ $item = false;
+
+ if (count($this->domains) > 1) {
+ // 获取当前子域名
+ $subDomain = $this->request->subDomain();
+
+ $domain = $subDomain ? explode('.', $subDomain) : [];
+ $domain2 = $domain ? array_pop($domain) : '';
+
+ if ($domain) {
+ // 存在三级域名
+ $domain3 = array_pop($domain);
+ }
+
+ if (isset($this->domains[$this->host])) {
+ // 子域名配置
+ $item = $this->domains[$this->host];
+ } elseif (isset($this->domains[$subDomain])) {
+ $item = $this->domains[$subDomain];
+ } elseif (isset($this->domains['*.' . $domain2]) && !empty($domain3)) {
+ // 泛三级域名
+ $item = $this->domains['*.' . $domain2];
+ $panDomain = $domain3;
+ } elseif (isset($this->domains['*']) && !empty($domain2)) {
+ // 泛二级域名
+ if ('www' != $domain2) {
+ $item = $this->domains['*'];
+ $panDomain = $domain2;
+ }
+ }
+
+ if (isset($panDomain)) {
+ // 保存当前泛域名
+ $this->request->setPanDomain($panDomain);
+ }
+ }
+
+ if (false === $item) {
+ // 检测全局域名规则
+ $item = $this->domains['-'];
+ }
+
+ if (is_string($item)) {
+ $item = $this->domains[$item];
+ }
+
+ return $item;
+ }
+
+ /**
+ * URL生成 支持路由反射
+ * @access public
+ * @param string $url 路由地址
+ * @param array $vars 参数 ['a'=>'val1', 'b'=>'val2']
+ * @return UrlBuild
+ */
+ public function buildUrl(string $url = '', array $vars = []): UrlBuild
+ {
+ return $this->app->make(UrlBuild::class, [$this, $this->app, $url, $vars], true);
+ }
+
+ /**
+ * 设置全局的路由分组参数
+ * @access public
+ * @param string $method 方法名
+ * @param array $args 调用参数
+ * @return RuleGroup
+ */
+ public function __call($method, $args)
+ {
+ return call_user_func_array([$this->group, $method], $args);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Service.php b/vendor/topthink/framework/src/think/Service.php
new file mode 100755
index 0000000..d9e8960
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Service.php
@@ -0,0 +1,66 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Closure;
+use think\event\RouteLoaded;
+
+/**
+ * 系统服务基础类
+ * @method void register()
+ * @method void boot()
+ */
+abstract class Service
+{
+ protected $app;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * 加载路由
+ * @access protected
+ * @param string $path 路由路径
+ */
+ protected function loadRoutesFrom($path)
+ {
+ $this->registerRoutes(function () use ($path) {
+ include $path;
+ });
+ }
+
+ /**
+ * 注册路由
+ * @param Closure $closure
+ */
+ protected function registerRoutes(Closure $closure)
+ {
+ $this->app->event->listen(RouteLoaded::class, $closure);
+ }
+
+ /**
+ * 添加指令
+ * @access protected
+ * @param array|string $commands 指令
+ */
+ protected function commands($commands)
+ {
+ $commands = is_array($commands) ? $commands : func_get_args();
+
+ Console::starting(function (Console $console) use ($commands) {
+ $console->addCommands($commands);
+ });
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Session.php b/vendor/topthink/framework/src/think/Session.php
new file mode 100755
index 0000000..6c84faf
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Session.php
@@ -0,0 +1,65 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use think\helper\Arr;
+use think\session\Store;
+
+/**
+ * Session管理类
+ * @package think
+ * @mixin Store
+ */
+class Session extends Manager
+{
+ protected $namespace = '\\think\\session\\driver\\';
+
+ protected function createDriver(string $name)
+ {
+ $handler = parent::createDriver($name);
+
+ return new Store($this->getConfig('name') ?: 'PHPSESSID', $handler, $this->getConfig('serialize'));
+ }
+
+ /**
+ * 获取Session配置
+ * @access public
+ * @param null|string $name 名称
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getConfig(string $name = null, $default = null)
+ {
+ if (!is_null($name)) {
+ return $this->app->config->get('session.' . $name, $default);
+ }
+
+ return $this->app->config->get('session');
+ }
+
+ protected function resolveConfig(string $name)
+ {
+ $config = $this->app->config->get('session', []);
+ Arr::forget($config, 'type');
+ return $config;
+ }
+
+ /**
+ * 默认驱动
+ * @return string|null
+ */
+ public function getDefaultDriver()
+ {
+ return $this->app->config->get('session.type', 'file');
+ }
+}
diff --git a/vendor/topthink/framework/src/think/Validate.php b/vendor/topthink/framework/src/think/Validate.php
new file mode 100755
index 0000000..0788406
--- /dev/null
+++ b/vendor/topthink/framework/src/think/Validate.php
@@ -0,0 +1,1688 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Closure;
+use think\exception\ValidateException;
+use think\helper\Str;
+use think\validate\ValidateRule;
+
+/**
+ * 数据验证类
+ * @package think
+ */
+class Validate
+{
+ /**
+ * 自定义验证类型
+ * @var array
+ */
+ protected $type = [];
+
+ /**
+ * 验证类型别名
+ * @var array
+ */
+ protected $alias = [
+ '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq',
+ ];
+
+ /**
+ * 当前验证规则
+ * @var array
+ */
+ protected $rule = [];
+
+ /**
+ * 验证提示信息
+ * @var array
+ */
+ protected $message = [];
+
+ /**
+ * 验证字段描述
+ * @var array
+ */
+ protected $field = [];
+
+ /**
+ * 默认规则提示
+ * @var array
+ */
+ protected $typeMsg = [
+ 'require' => ':attribute require',
+ 'must' => ':attribute must',
+ 'number' => ':attribute must be numeric',
+ 'integer' => ':attribute must be integer',
+ 'float' => ':attribute must be float',
+ 'boolean' => ':attribute must be bool',
+ 'email' => ':attribute not a valid email address',
+ 'mobile' => ':attribute not a valid mobile',
+ 'array' => ':attribute must be a array',
+ 'accepted' => ':attribute must be yes,on or 1',
+ 'date' => ':attribute not a valid datetime',
+ 'file' => ':attribute not a valid file',
+ 'image' => ':attribute not a valid image',
+ 'alpha' => ':attribute must be alpha',
+ 'alphaNum' => ':attribute must be alpha-numeric',
+ 'alphaDash' => ':attribute must be alpha-numeric, dash, underscore',
+ 'activeUrl' => ':attribute not a valid domain or ip',
+ 'chs' => ':attribute must be chinese',
+ 'chsAlpha' => ':attribute must be chinese or alpha',
+ 'chsAlphaNum' => ':attribute must be chinese,alpha-numeric',
+ 'chsDash' => ':attribute must be chinese,alpha-numeric,underscore, dash',
+ 'url' => ':attribute not a valid url',
+ 'ip' => ':attribute not a valid ip',
+ 'dateFormat' => ':attribute must be dateFormat of :rule',
+ 'in' => ':attribute must be in :rule',
+ 'notIn' => ':attribute be notin :rule',
+ 'between' => ':attribute must between :1 - :2',
+ 'notBetween' => ':attribute not between :1 - :2',
+ 'length' => 'size of :attribute must be :rule',
+ 'max' => 'max size of :attribute must be :rule',
+ 'min' => 'min size of :attribute must be :rule',
+ 'after' => ':attribute cannot be less than :rule',
+ 'before' => ':attribute cannot exceed :rule',
+ 'expire' => ':attribute not within :rule',
+ 'allowIp' => 'access IP is not allowed',
+ 'denyIp' => 'access IP denied',
+ 'confirm' => ':attribute out of accord with :2',
+ 'different' => ':attribute cannot be same with :2',
+ 'egt' => ':attribute must greater than or equal :rule',
+ 'gt' => ':attribute must greater than :rule',
+ 'elt' => ':attribute must less than or equal :rule',
+ 'lt' => ':attribute must less than :rule',
+ 'eq' => ':attribute must equal :rule',
+ 'unique' => ':attribute has exists',
+ 'regex' => ':attribute not conform to the rules',
+ 'method' => 'invalid Request method',
+ 'token' => 'invalid token',
+ 'fileSize' => 'filesize not match',
+ 'fileExt' => 'extensions to upload is not allowed',
+ 'fileMime' => 'mimetype to upload is not allowed',
+ ];
+
+ /**
+ * 当前验证场景
+ * @var string
+ */
+ protected $currentScene;
+
+ /**
+ * 内置正则验证规则
+ * @var array
+ */
+ protected $defaultRegex = [
+ 'alpha' => '/^[A-Za-z]+$/',
+ 'alphaNum' => '/^[A-Za-z0-9]+$/',
+ 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/',
+ 'chs' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}]+$/u',
+ 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z]+$/u',
+ 'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z0-9]+$/u',
+ 'chsDash' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z0-9\_\-]+$/u',
+ 'mobile' => '/^1[3-9]\d{9}$/',
+ 'idCard' => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)/',
+ 'zip' => '/\d{6}/',
+ ];
+
+ /**
+ * Filter_var 规则
+ * @var array
+ */
+ protected $filter = [
+ 'email' => FILTER_VALIDATE_EMAIL,
+ 'ip' => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6],
+ 'integer' => FILTER_VALIDATE_INT,
+ 'url' => FILTER_VALIDATE_URL,
+ 'macAddr' => FILTER_VALIDATE_MAC,
+ 'float' => FILTER_VALIDATE_FLOAT,
+ ];
+
+ /**
+ * 验证场景定义
+ * @var array
+ */
+ protected $scene = [];
+
+ /**
+ * 验证失败错误信息
+ * @var string|array
+ */
+ protected $error = [];
+
+ /**
+ * 是否批量验证
+ * @var bool
+ */
+ protected $batch = false;
+
+ /**
+ * 验证失败是否抛出异常
+ * @var bool
+ */
+ protected $failException = false;
+
+ /**
+ * 场景需要验证的规则
+ * @var array
+ */
+ protected $only = [];
+
+ /**
+ * 场景需要移除的验证规则
+ * @var array
+ */
+ protected $remove = [];
+
+ /**
+ * 场景需要追加的验证规则
+ * @var array
+ */
+ protected $append = [];
+
+ /**
+ * 验证正则定义
+ * @var array
+ */
+ protected $regex = [];
+
+ /**
+ * Db对象
+ * @var Db
+ */
+ protected $db;
+
+ /**
+ * 语言对象
+ * @var Lang
+ */
+ protected $lang;
+
+ /**
+ * 请求对象
+ * @var Request
+ */
+ protected $request;
+
+ /**
+ * @var Closure[]
+ */
+ protected static $maker = [];
+
+ /**
+ * 构造方法
+ * @access public
+ */
+ public function __construct()
+ {
+ if (!empty(static::$maker)) {
+ foreach (static::$maker as $maker) {
+ call_user_func($maker, $this);
+ }
+ }
+ }
+
+ /**
+ * 设置服务注入
+ * @access public
+ * @param Closure $maker
+ * @return void
+ */
+ public static function maker(Closure $maker)
+ {
+ static::$maker[] = $maker;
+ }
+
+ /**
+ * 设置Lang对象
+ * @access public
+ * @param Lang $lang Lang对象
+ * @return void
+ */
+ public function setLang(Lang $lang)
+ {
+ $this->lang = $lang;
+ }
+
+ /**
+ * 设置Db对象
+ * @access public
+ * @param Db $db Db对象
+ * @return void
+ */
+ public function setDb(Db $db)
+ {
+ $this->db = $db;
+ }
+
+ /**
+ * 设置Request对象
+ * @access public
+ * @param Request $request Request对象
+ * @return void
+ */
+ public function setRequest(Request $request)
+ {
+ $this->request = $request;
+ }
+
+ /**
+ * 添加字段验证规则
+ * @access protected
+ * @param string|array $name 字段名称或者规则数组
+ * @param mixed $rule 验证规则或者字段描述信息
+ * @return $this
+ */
+ public function rule($name, $rule = '')
+ {
+ if (is_array($name)) {
+ $this->rule = $name + $this->rule;
+ if (is_array($rule)) {
+ $this->field = array_merge($this->field, $rule);
+ }
+ } else {
+ $this->rule[$name] = $rule;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 注册验证(类型)规则
+ * @access public
+ * @param string $type 验证规则类型
+ * @param callable $callback callback方法(或闭包)
+ * @param string $message 验证失败提示信息
+ * @return $this
+ */
+ public function extend(string $type, callable $callback = null, string $message = null)
+ {
+ $this->type[$type] = $callback;
+
+ if ($message) {
+ $this->typeMsg[$type] = $message;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置验证规则的默认提示信息
+ * @access public
+ * @param string|array $type 验证规则类型名称或者数组
+ * @param string $msg 验证提示信息
+ * @return void
+ */
+ public function setTypeMsg($type, string $msg = null): void
+ {
+ if (is_array($type)) {
+ $this->typeMsg = array_merge($this->typeMsg, $type);
+ } else {
+ $this->typeMsg[$type] = $msg;
+ }
+ }
+
+ /**
+ * 设置提示信息
+ * @access public
+ * @param array $message 错误信息
+ * @return Validate
+ */
+ public function message(array $message)
+ {
+ $this->message = array_merge($this->message, $message);
+
+ return $this;
+ }
+
+ /**
+ * 设置验证场景
+ * @access public
+ * @param string $name 场景名
+ * @return $this
+ */
+ public function scene(string $name)
+ {
+ // 设置当前场景
+ $this->currentScene = $name;
+
+ return $this;
+ }
+
+ /**
+ * 判断是否存在某个验证场景
+ * @access public
+ * @param string $name 场景名
+ * @return bool
+ */
+ public function hasScene(string $name): bool
+ {
+ return isset($this->scene[$name]) || method_exists($this, 'scene' . $name);
+ }
+
+ /**
+ * 设置批量验证
+ * @access public
+ * @param bool $batch 是否批量验证
+ * @return $this
+ */
+ public function batch(bool $batch = true)
+ {
+ $this->batch = $batch;
+
+ return $this;
+ }
+
+ /**
+ * 设置验证失败后是否抛出异常
+ * @access protected
+ * @param bool $fail 是否抛出异常
+ * @return $this
+ */
+ public function failException(bool $fail = true)
+ {
+ $this->failException = $fail;
+
+ return $this;
+ }
+
+ /**
+ * 指定需要验证的字段列表
+ * @access public
+ * @param array $fields 字段名
+ * @return $this
+ */
+ public function only(array $fields)
+ {
+ $this->only = $fields;
+
+ return $this;
+ }
+
+ /**
+ * 移除某个字段的验证规则
+ * @access public
+ * @param string|array $field 字段名
+ * @param mixed $rule 验证规则 true 移除所有规则
+ * @return $this
+ */
+ public function remove($field, $rule = null)
+ {
+ if (is_array($field)) {
+ foreach ($field as $key => $rule) {
+ if (is_int($key)) {
+ $this->remove($rule);
+ } else {
+ $this->remove($key, $rule);
+ }
+ }
+ } else {
+ if (is_string($rule)) {
+ $rule = explode('|', $rule);
+ }
+
+ $this->remove[$field] = $rule;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 追加某个字段的验证规则
+ * @access public
+ * @param string|array $field 字段名
+ * @param mixed $rule 验证规则
+ * @return $this
+ */
+ public function append($field, $rule = null)
+ {
+ if (is_array($field)) {
+ foreach ($field as $key => $rule) {
+ $this->append($key, $rule);
+ }
+ } else {
+ if (is_string($rule)) {
+ $rule = explode('|', $rule);
+ }
+
+ $this->append[$field] = $rule;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 数据自动验证
+ * @access public
+ * @param array $data 数据
+ * @param array $rules 验证规则
+ * @return bool
+ */
+ public function check(array $data, array $rules = []): bool
+ {
+ $this->error = [];
+
+ if ($this->currentScene) {
+ $this->getScene($this->currentScene);
+ }
+
+ if (empty($rules)) {
+ // 读取验证规则
+ $rules = $this->rule;
+ }
+
+ foreach ($this->append as $key => $rule) {
+ if (!isset($rules[$key])) {
+ $rules[$key] = $rule;
+ unset($this->append[$key]);
+ }
+ }
+
+ foreach ($rules as $key => $rule) {
+ // field => 'rule1|rule2...' field => ['rule1','rule2',...]
+ if (strpos($key, '|')) {
+ // 字段|描述 用于指定属性名称
+ [$key, $title] = explode('|', $key);
+ } else {
+ $title = $this->field[$key] ?? $key;
+ }
+
+ // 场景检测
+ if (!empty($this->only) && !in_array($key, $this->only)) {
+ continue;
+ }
+
+ // 获取数据 支持二维数组
+ $value = $this->getDataValue($data, $key);
+
+ // 字段验证
+ if ($rule instanceof Closure) {
+ $result = call_user_func_array($rule, [$value, $data]);
+ } elseif ($rule instanceof ValidateRule) {
+ // 验证因子
+ $result = $this->checkItem($key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg());
+ } else {
+ $result = $this->checkItem($key, $value, $rule, $data, $title);
+ }
+
+ if (true !== $result) {
+ // 没有返回true 则表示验证失败
+ if (!empty($this->batch)) {
+ // 批量验证
+ $this->error[$key] = $result;
+ } elseif ($this->failException) {
+ throw new ValidateException($result);
+ } else {
+ $this->error = $result;
+ return false;
+ }
+ }
+ }
+
+ if (!empty($this->error)) {
+ if ($this->failException) {
+ throw new ValidateException($this->error);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 根据验证规则验证数据
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rules 验证规则
+ * @return bool
+ */
+ public function checkRule($value, $rules): bool
+ {
+ if ($rules instanceof Closure) {
+ return call_user_func_array($rules, [$value]);
+ } elseif ($rules instanceof ValidateRule) {
+ $rules = $rules->getRule();
+ } elseif (is_string($rules)) {
+ $rules = explode('|', $rules);
+ }
+
+ foreach ($rules as $key => $rule) {
+ if ($rule instanceof Closure) {
+ $result = call_user_func_array($rule, [$value]);
+ } else {
+ // 判断验证类型
+ [$type, $rule] = $this->getValidateType($key, $rule);
+
+ $callback = $this->type[$type] ?? [$this, $type];
+
+ $result = call_user_func_array($callback, [$value, $rule]);
+ }
+
+ if (true !== $result) {
+ if ($this->failException) {
+ throw new ValidateException($result);
+ }
+
+ return $result;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 验证单个字段规则
+ * @access protected
+ * @param string $field 字段名
+ * @param mixed $value 字段值
+ * @param mixed $rules 验证规则
+ * @param array $data 数据
+ * @param string $title 字段描述
+ * @param array $msg 提示信息
+ * @return mixed
+ */
+ protected function checkItem(string $field, $value, $rules, $data, string $title = '', array $msg = [])
+ {
+ if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) {
+ // 字段已经移除 无需验证
+ return true;
+ }
+
+ // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...]
+ if (is_string($rules)) {
+ $rules = explode('|', $rules);
+ }
+
+ if (isset($this->append[$field])) {
+ // 追加额外的验证规则
+ $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR);
+ unset($this->append[$field]);
+ }
+
+ if (empty($rules)) {
+ return true;
+ }
+
+ $i = 0;
+ foreach ($rules as $key => $rule) {
+ if ($rule instanceof Closure) {
+ $result = call_user_func_array($rule, [$value, $data]);
+ $info = is_numeric($key) ? '' : $key;
+ } else {
+ // 判断验证类型
+ [$type, $rule, $info] = $this->getValidateType($key, $rule);
+
+ if (isset($this->append[$field]) && in_array($info, $this->append[$field])) {
+ } elseif (isset($this->remove[$field]) && in_array($info, $this->remove[$field])) {
+ // 规则已经移除
+ $i++;
+ continue;
+ }
+
+ if (isset($this->type[$type])) {
+ $result = call_user_func_array($this->type[$type], [$value, $rule, $data, $field, $title]);
+ } elseif ('must' == $info || 0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) {
+ $result = call_user_func_array([$this, $type], [$value, $rule, $data, $field, $title]);
+ } else {
+ $result = true;
+ }
+ }
+
+ if (false === $result) {
+ // 验证失败 返回错误信息
+ if (!empty($msg[$i])) {
+ $message = $msg[$i];
+ if (is_string($message) && strpos($message, '{%') === 0) {
+ $message = $this->lang->get(substr($message, 2, -1));
+ }
+ } else {
+ $message = $this->getRuleMsg($field, $title, $info, $rule);
+ }
+
+ return $message;
+ } elseif (true !== $result) {
+ // 返回自定义错误信息
+ if (is_string($result) && false !== strpos($result, ':')) {
+ $result = str_replace(':attribute', $title, $result);
+
+ if (strpos($result, ':rule') && is_scalar($rule)) {
+ $result = str_replace(':rule', (string) $rule, $result);
+ }
+ }
+
+ return $result;
+ }
+ $i++;
+ }
+
+ return $result ?? true;
+ }
+
+ /**
+ * 获取当前验证类型及规则
+ * @access public
+ * @param mixed $key
+ * @param mixed $rule
+ * @return array
+ */
+ protected function getValidateType($key, $rule): array
+ {
+ // 判断验证类型
+ if (!is_numeric($key)) {
+ if (isset($this->alias[$key])) {
+ // 判断别名
+ $key = $this->alias[$key];
+ }
+ return [$key, $rule, $key];
+ }
+
+ if (strpos($rule, ':')) {
+ [$type, $rule] = explode(':', $rule, 2);
+ if (isset($this->alias[$type])) {
+ // 判断别名
+ $type = $this->alias[$type];
+ }
+ $info = $type;
+ } elseif (method_exists($this, $rule)) {
+ $type = $rule;
+ $info = $rule;
+ $rule = '';
+ } else {
+ $type = 'is';
+ $info = $rule;
+ }
+
+ return [$type, $rule, $info];
+ }
+
+ /**
+ * 验证是否和某个字段的值一致
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @param string $field 字段名
+ * @return bool
+ */
+ public function confirm($value, $rule, array $data = [], string $field = ''): bool
+ {
+ if ('' == $rule) {
+ if (strpos($field, '_confirm')) {
+ $rule = strstr($field, '_confirm', true);
+ } else {
+ $rule = $field . '_confirm';
+ }
+ }
+
+ return $this->getDataValue($data, $rule) === $value;
+ }
+
+ /**
+ * 验证是否和某个字段的值是否不同
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function different($value, $rule, array $data = []): bool
+ {
+ return $this->getDataValue($data, $rule) != $value;
+ }
+
+ /**
+ * 验证是否大于等于某个值
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function egt($value, $rule, array $data = []): bool
+ {
+ return $value >= $this->getDataValue($data, $rule);
+ }
+
+ /**
+ * 验证是否大于某个值
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function gt($value, $rule, array $data = []): bool
+ {
+ return $value > $this->getDataValue($data, $rule);
+ }
+
+ /**
+ * 验证是否小于等于某个值
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function elt($value, $rule, array $data = []): bool
+ {
+ return $value <= $this->getDataValue($data, $rule);
+ }
+
+ /**
+ * 验证是否小于某个值
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function lt($value, $rule, array $data = []): bool
+ {
+ return $value < $this->getDataValue($data, $rule);
+ }
+
+ /**
+ * 验证是否等于某个值
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function eq($value, $rule): bool
+ {
+ return $value == $rule;
+ }
+
+ /**
+ * 必须验证
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function must($value, $rule = null): bool
+ {
+ return !empty($value) || '0' == $value;
+ }
+
+ /**
+ * 验证字段值是否为有效格式
+ * @access public
+ * @param mixed $value 字段值
+ * @param string $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function is($value, string $rule, array $data = []): bool
+ {
+ switch (Str::camel($rule)) {
+ case 'require':
+ // 必须
+ $result = !empty($value) || '0' == $value;
+ break;
+ case 'accepted':
+ // 接受
+ $result = in_array($value, ['1', 'on', 'yes']);
+ break;
+ case 'date':
+ // 是否是一个有效日期
+ $result = false !== strtotime($value);
+ break;
+ case 'activeUrl':
+ // 是否为有效的网址
+ $result = checkdnsrr($value);
+ break;
+ case 'boolean':
+ case 'bool':
+ // 是否为布尔值
+ $result = in_array($value, [true, false, 0, 1, '0', '1'], true);
+ break;
+ case 'number':
+ $result = ctype_digit((string) $value);
+ break;
+ case 'alphaNum':
+ $result = ctype_alnum($value);
+ break;
+ case 'array':
+ // 是否为数组
+ $result = is_array($value);
+ break;
+ case 'file':
+ $result = $value instanceof File;
+ break;
+ case 'image':
+ $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]);
+ break;
+ case 'token':
+ $result = $this->token($value, '__token__', $data);
+ break;
+ default:
+ if (isset($this->type[$rule])) {
+ // 注册的验证规则
+ $result = call_user_func_array($this->type[$rule], [$value]);
+ } elseif (function_exists('ctype_' . $rule)) {
+ // ctype验证规则
+ $ctypeFun = 'ctype_' . $rule;
+ $result = $ctypeFun($value);
+ } elseif (isset($this->filter[$rule])) {
+ // Filter_var验证规则
+ $result = $this->filter($value, $this->filter[$rule]);
+ } else {
+ // 正则验证
+ $result = $this->regex($value, $rule);
+ }
+ }
+
+ return $result;
+ }
+
+ // 判断图像类型
+ protected function getImageType($image)
+ {
+ if (function_exists('exif_imagetype')) {
+ return exif_imagetype($image);
+ }
+
+ try {
+ $info = getimagesize($image);
+ return $info ? $info[2] : false;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * 验证表单令牌
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function token($value, string $rule, array $data): bool
+ {
+ $rule = !empty($rule) ? $rule : '__token__';
+ return $this->request->checkToken($rule, $data);
+ }
+
+ /**
+ * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function activeUrl(string $value, string $rule = 'MX'): bool
+ {
+ if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) {
+ $rule = 'MX';
+ }
+
+ return checkdnsrr($value, $rule);
+ }
+
+ /**
+ * 验证是否有效IP
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则 ipv4 ipv6
+ * @return bool
+ */
+ public function ip($value, string $rule = 'ipv4'): bool
+ {
+ if (!in_array($rule, ['ipv4', 'ipv6'])) {
+ $rule = 'ipv4';
+ }
+
+ return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]);
+ }
+
+ /**
+ * 检测上传文件后缀
+ * @access public
+ * @param File $file
+ * @param array|string $ext 允许后缀
+ * @return bool
+ */
+ protected function checkExt(File $file, $ext): bool
+ {
+ if (is_string($ext)) {
+ $ext = explode(',', $ext);
+ }
+
+ return in_array(strtolower($file->extension()), $ext);
+ }
+
+ /**
+ * 检测上传文件大小
+ * @access public
+ * @param File $file
+ * @param integer $size 最大大小
+ * @return bool
+ */
+ protected function checkSize(File $file, $size): bool
+ {
+ return $file->getSize() <= (int) $size;
+ }
+
+ /**
+ * 检测上传文件类型
+ * @access public
+ * @param File $file
+ * @param array|string $mime 允许类型
+ * @return bool
+ */
+ protected function checkMime(File $file, $mime): bool
+ {
+ if (is_string($mime)) {
+ $mime = explode(',', $mime);
+ }
+
+ return in_array(strtolower($file->getMime()), $mime);
+ }
+
+ /**
+ * 验证上传文件后缀
+ * @access public
+ * @param mixed $file 上传文件
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function fileExt($file, $rule): bool
+ {
+ if (is_array($file)) {
+ foreach ($file as $item) {
+ if (!($item instanceof File) || !$this->checkExt($item, $rule)) {
+ return false;
+ }
+ }
+ return true;
+ } elseif ($file instanceof File) {
+ return $this->checkExt($file, $rule);
+ }
+
+ return false;
+ }
+
+ /**
+ * 验证上传文件类型
+ * @access public
+ * @param mixed $file 上传文件
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function fileMime($file, $rule): bool
+ {
+ if (is_array($file)) {
+ foreach ($file as $item) {
+ if (!($item instanceof File) || !$this->checkMime($item, $rule)) {
+ return false;
+ }
+ }
+ return true;
+ } elseif ($file instanceof File) {
+ return $this->checkMime($file, $rule);
+ }
+
+ return false;
+ }
+
+ /**
+ * 验证上传文件大小
+ * @access public
+ * @param mixed $file 上传文件
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function fileSize($file, $rule): bool
+ {
+ if (is_array($file)) {
+ foreach ($file as $item) {
+ if (!($item instanceof File) || !$this->checkSize($item, $rule)) {
+ return false;
+ }
+ }
+ return true;
+ } elseif ($file instanceof File) {
+ return $this->checkSize($file, $rule);
+ }
+
+ return false;
+ }
+
+ /**
+ * 验证图片的宽高及类型
+ * @access public
+ * @param mixed $file 上传文件
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function image($file, $rule): bool
+ {
+ if (!($file instanceof File)) {
+ return false;
+ }
+
+ if ($rule) {
+ $rule = explode(',', $rule);
+
+ [$width, $height, $type] = getimagesize($file->getRealPath());
+
+ if (isset($rule[2])) {
+ $imageType = strtolower($rule[2]);
+
+ if ('jpg' == $imageType) {
+ $imageType = 'jpeg';
+ }
+
+ if (image_type_to_extension($type, false) != $imageType) {
+ return false;
+ }
+ }
+
+ [$w, $h] = $rule;
+
+ return $w == $width && $h == $height;
+ }
+
+ return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]);
+ }
+
+ /**
+ * 验证时间和日期是否符合指定格式
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function dateFormat($value, $rule): bool
+ {
+ $info = date_parse_from_format($rule, $value);
+ return 0 == $info['warning_count'] && 0 == $info['error_count'];
+ }
+
+ /**
+ * 验证是否唯一
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名
+ * @param array $data 数据
+ * @param string $field 验证字段名
+ * @return bool
+ */
+ public function unique($value, $rule, array $data = [], string $field = ''): bool
+ {
+ if (is_string($rule)) {
+ $rule = explode(',', $rule);
+ }
+
+ if (false !== strpos($rule[0], '\\')) {
+ // 指定模型类
+ $db = new $rule[0];
+ } else {
+ $db = $this->db->name($rule[0]);
+ }
+
+ $key = $rule[1] ?? $field;
+ $map = [];
+
+ if (strpos($key, '^')) {
+ // 支持多个字段验证
+ $fields = explode('^', $key);
+ foreach ($fields as $key) {
+ if (isset($data[$key])) {
+ $map[] = [$key, '=', $data[$key]];
+ }
+ }
+ } elseif (isset($data[$field])) {
+ $map[] = [$key, '=', $data[$field]];
+ } else {
+ $map = [];
+ }
+
+ $pk = !empty($rule[3]) ? $rule[3] : $db->getPk();
+
+ if (is_string($pk)) {
+ if (isset($rule[2])) {
+ $map[] = [$pk, '<>', $rule[2]];
+ } elseif (isset($data[$pk])) {
+ $map[] = [$pk, '<>', $data[$pk]];
+ }
+ }
+
+ if ($db->where($map)->field($pk)->find()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 使用filter_var方式验证
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function filter($value, $rule): bool
+ {
+ if (is_string($rule) && strpos($rule, ',')) {
+ [$rule, $param] = explode(',', $rule);
+ } elseif (is_array($rule)) {
+ $param = $rule[1] ?? 0;
+ $rule = $rule[0];
+ } else {
+ $param = 0;
+ }
+
+ return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param);
+ }
+
+ /**
+ * 验证某个字段等于某个值的时候必须
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function requireIf($value, $rule, array $data = []): bool
+ {
+ [$field, $val] = explode(',', $rule);
+
+ if ($this->getDataValue($data, $field) == $val) {
+ return !empty($value) || '0' == $value;
+ }
+
+ return true;
+ }
+
+ /**
+ * 通过回调方法验证某个字段是否必须
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function requireCallback($value, $rule, array $data = []): bool
+ {
+ $result = call_user_func_array([$this, $rule], [$value, $data]);
+
+ if ($result) {
+ return !empty($value) || '0' == $value;
+ }
+
+ return true;
+ }
+
+ /**
+ * 验证某个字段有值的情况下必须
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function requireWith($value, $rule, array $data = []): bool
+ {
+ $val = $this->getDataValue($data, $rule);
+
+ if (!empty($val)) {
+ return !empty($value) || '0' == $value;
+ }
+
+ return true;
+ }
+
+ /**
+ * 验证某个字段没有值的情况下必须
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function requireWithout($value, $rule, array $data = []): bool
+ {
+ $val = $this->getDataValue($data, $rule);
+
+ if (empty($val)) {
+ return !empty($value) || '0' == $value;
+ }
+
+ return true;
+ }
+
+ /**
+ * 验证是否在范围内
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function in($value, $rule): bool
+ {
+ return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
+ }
+
+ /**
+ * 验证是否不在某个范围
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function notIn($value, $rule): bool
+ {
+ return !in_array($value, is_array($rule) ? $rule : explode(',', $rule));
+ }
+
+ /**
+ * between验证数据
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function between($value, $rule): bool
+ {
+ if (is_string($rule)) {
+ $rule = explode(',', $rule);
+ }
+ [$min, $max] = $rule;
+
+ return $value >= $min && $value <= $max;
+ }
+
+ /**
+ * 使用notbetween验证数据
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function notBetween($value, $rule): bool
+ {
+ if (is_string($rule)) {
+ $rule = explode(',', $rule);
+ }
+ [$min, $max] = $rule;
+
+ return $value < $min || $value > $max;
+ }
+
+ /**
+ * 验证数据长度
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function length($value, $rule): bool
+ {
+ if (is_array($value)) {
+ $length = count($value);
+ } elseif ($value instanceof File) {
+ $length = $value->getSize();
+ } else {
+ $length = mb_strlen((string) $value);
+ }
+
+ if (is_string($rule) && strpos($rule, ',')) {
+ // 长度区间
+ [$min, $max] = explode(',', $rule);
+ return $length >= $min && $length <= $max;
+ }
+
+ // 指定长度
+ return $length == $rule;
+ }
+
+ /**
+ * 验证数据最大长度
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function max($value, $rule): bool
+ {
+ if (is_array($value)) {
+ $length = count($value);
+ } elseif ($value instanceof File) {
+ $length = $value->getSize();
+ } else {
+ $length = mb_strlen((string) $value);
+ }
+
+ return $length <= $rule;
+ }
+
+ /**
+ * 验证数据最小长度
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function min($value, $rule): bool
+ {
+ if (is_array($value)) {
+ $length = count($value);
+ } elseif ($value instanceof File) {
+ $length = $value->getSize();
+ } else {
+ $length = mb_strlen((string) $value);
+ }
+
+ return $length >= $rule;
+ }
+
+ /**
+ * 验证日期
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function after($value, $rule, array $data = []): bool
+ {
+ return strtotime($value) >= strtotime($rule);
+ }
+
+ /**
+ * 验证日期
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function before($value, $rule, array $data = []): bool
+ {
+ return strtotime($value) <= strtotime($rule);
+ }
+
+ /**
+ * 验证日期
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function afterWith($value, $rule, array $data = []): bool
+ {
+ $rule = $this->getDataValue($data, $rule);
+ return !is_null($rule) && strtotime($value) >= strtotime($rule);
+ }
+
+ /**
+ * 验证日期
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @param array $data 数据
+ * @return bool
+ */
+ public function beforeWith($value, $rule, array $data = []): bool
+ {
+ $rule = $this->getDataValue($data, $rule);
+ return !is_null($rule) && strtotime($value) <= strtotime($rule);
+ }
+
+ /**
+ * 验证有效期
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function expire($value, $rule): bool
+ {
+ if (is_string($rule)) {
+ $rule = explode(',', $rule);
+ }
+
+ [$start, $end] = $rule;
+
+ if (!is_numeric($start)) {
+ $start = strtotime($start);
+ }
+
+ if (!is_numeric($end)) {
+ $end = strtotime($end);
+ }
+
+ return time() >= $start && time() <= $end;
+ }
+
+ /**
+ * 验证IP许可
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function allowIp($value, $rule): bool
+ {
+ return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
+ }
+
+ /**
+ * 验证IP禁用
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则
+ * @return bool
+ */
+ public function denyIp($value, $rule): bool
+ {
+ return !in_array($value, is_array($rule) ? $rule : explode(',', $rule));
+ }
+
+ /**
+ * 使用正则验证数据
+ * @access public
+ * @param mixed $value 字段值
+ * @param mixed $rule 验证规则 正则规则或者预定义正则名
+ * @return bool
+ */
+ public function regex($value, $rule): bool
+ {
+ if (isset($this->regex[$rule])) {
+ $rule = $this->regex[$rule];
+ } elseif (isset($this->defaultRegex[$rule])) {
+ $rule = $this->defaultRegex[$rule];
+ }
+
+ if (is_string($rule) && 0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) {
+ // 不是正则表达式则两端补上/
+ $rule = '/^' . $rule . '$/';
+ }
+
+ return is_scalar($value) && 1 === preg_match($rule, (string) $value);
+ }
+
+ /**
+ * 获取错误信息
+ * @return array|string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * 获取数据值
+ * @access protected
+ * @param array $data 数据
+ * @param string $key 数据标识 支持二维
+ * @return mixed
+ */
+ protected function getDataValue(array $data, $key)
+ {
+ if (is_numeric($key)) {
+ $value = $key;
+ } elseif (is_string($key) && strpos($key, '.')) {
+ // 支持多维数组验证
+ foreach (explode('.', $key) as $key) {
+ if (!isset($data[$key])) {
+ $value = null;
+ break;
+ }
+ $value = $data = $data[$key];
+ }
+ } else {
+ $value = $data[$key] ?? null;
+ }
+
+ return $value;
+ }
+
+ /**
+ * 获取验证规则的错误提示信息
+ * @access protected
+ * @param string $attribute 字段英文名
+ * @param string $title 字段描述名
+ * @param string $type 验证规则名称
+ * @param mixed $rule 验证规则数据
+ * @return string|array
+ */
+ protected function getRuleMsg(string $attribute, string $title, string $type, $rule)
+ {
+ if (isset($this->message[$attribute . '.' . $type])) {
+ $msg = $this->message[$attribute . '.' . $type];
+ } elseif (isset($this->message[$attribute][$type])) {
+ $msg = $this->message[$attribute][$type];
+ } elseif (isset($this->message[$attribute])) {
+ $msg = $this->message[$attribute];
+ } elseif (isset($this->typeMsg[$type])) {
+ $msg = $this->typeMsg[$type];
+ } elseif (0 === strpos($type, 'require')) {
+ $msg = $this->typeMsg['require'];
+ } else {
+ $msg = $title . $this->lang->get('not conform to the rules');
+ }
+
+ if (is_array($msg)) {
+ return $this->errorMsgIsArray($msg, $rule, $title);
+ }
+
+ return $this->parseErrorMsg($msg, $rule, $title);
+ }
+
+ /**
+ * 获取验证规则的错误提示信息
+ * @access protected
+ * @param string $msg 错误信息
+ * @param mixed $rule 验证规则数据
+ * @param string $title 字段描述名
+ * @return string|array
+ */
+ protected function parseErrorMsg(string $msg, $rule, string $title)
+ {
+ if (0 === strpos($msg, '{%')) {
+ $msg = $this->lang->get(substr($msg, 2, -1));
+ } elseif ($this->lang->has($msg)) {
+ $msg = $this->lang->get($msg);
+ }
+
+ if (is_array($msg)) {
+ return $this->errorMsgIsArray($msg, $rule, $title);
+ }
+
+ // rule若是数组则转为字符串
+ if (is_array($rule)) {
+ $rule = implode(',', $rule);
+ }
+
+ if (is_scalar($rule) && false !== strpos($msg, ':')) {
+ // 变量替换
+ if (is_string($rule) && strpos($rule, ',')) {
+ $array = array_pad(explode(',', $rule), 3, '');
+ } else {
+ $array = array_pad([], 3, '');
+ }
+
+ $msg = str_replace(
+ [':attribute', ':1', ':2', ':3'],
+ [$title, $array[0], $array[1], $array[2]],
+ $msg
+ );
+
+ if (strpos($msg, ':rule')) {
+ $msg = str_replace(':rule', (string) $rule, $msg);
+ }
+ }
+
+ return $msg;
+ }
+
+ /**
+ * 错误信息数组处理
+ * @access protected
+ * @param array $msg 错误信息
+ * @param mixed $rule 验证规则数据
+ * @param string $title 字段描述名
+ * @return array
+ */
+ protected function errorMsgIsArray(array $msg, $rule, string $title)
+ {
+ foreach ($msg as $key => $val) {
+ if (is_string($val)) {
+ $msg[$key] = $this->parseErrorMsg($val, $rule, $title);
+ }
+ }
+ return $msg;
+ }
+
+ /**
+ * 获取数据验证的场景
+ * @access protected
+ * @param string $scene 验证场景
+ * @return void
+ */
+ protected function getScene(string $scene): void
+ {
+ $this->only = $this->append = $this->remove = [];
+
+ if (method_exists($this, 'scene' . $scene)) {
+ call_user_func([$this, 'scene' . $scene]);
+ } elseif (isset($this->scene[$scene])) {
+ // 如果设置了验证适用场景
+ $this->only = $this->scene[$scene];
+ }
+ }
+
+ /**
+ * 动态方法 直接调用is方法进行验证
+ * @access public
+ * @param string $method 方法名
+ * @param array $args 调用参数
+ * @return bool
+ */
+ public function __call($method, $args)
+ {
+ if ('is' == strtolower(substr($method, 0, 2))) {
+ $method = substr($method, 2);
+ }
+
+ array_push($args, lcfirst($method));
+
+ return call_user_func_array([$this, 'is'], $args);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/View.php b/vendor/topthink/framework/src/think/View.php
new file mode 100755
index 0000000..2e71088
--- /dev/null
+++ b/vendor/topthink/framework/src/think/View.php
@@ -0,0 +1,191 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use think\helper\Arr;
+
+/**
+ * 视图类
+ * @package think
+ */
+class View extends Manager
+{
+
+ protected $namespace = '\\think\\view\\driver\\';
+
+ /**
+ * 模板变量
+ * @var array
+ */
+ protected $data = [];
+
+ /**
+ * 内容过滤
+ * @var mixed
+ */
+ protected $filter;
+
+ /**
+ * 获取模板引擎
+ * @access public
+ * @param string $type 模板引擎类型
+ * @return $this
+ */
+ public function engine(string $type = null)
+ {
+ return $this->driver($type);
+ }
+
+ /**
+ * 模板变量赋值
+ * @access public
+ * @param string|array $name 模板变量
+ * @param mixed $value 变量值
+ * @return $this
+ */
+ public function assign($name, $value = null)
+ {
+ if (is_array($name)) {
+ $this->data = array_merge($this->data, $name);
+ } else {
+ $this->data[$name] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 视图过滤
+ * @access public
+ * @param Callable $filter 过滤方法或闭包
+ * @return $this
+ */
+ public function filter(callable $filter = null)
+ {
+ $this->filter = $filter;
+ return $this;
+ }
+
+ /**
+ * 解析和获取模板内容 用于输出
+ * @access public
+ * @param string $template 模板文件名或者内容
+ * @param array $vars 模板变量
+ * @return string
+ * @throws \Exception
+ */
+ public function fetch(string $template = '', array $vars = []): string
+ {
+ return $this->getContent(function () use ($vars, $template) {
+ $this->engine()->fetch($template, array_merge($this->data, $vars));
+ });
+ }
+
+ /**
+ * 渲染内容输出
+ * @access public
+ * @param string $content 内容
+ * @param array $vars 模板变量
+ * @return string
+ */
+ public function display(string $content, array $vars = []): string
+ {
+ return $this->getContent(function () use ($vars, $content) {
+ $this->engine()->display($content, array_merge($this->data, $vars));
+ });
+ }
+
+ /**
+ * 获取模板引擎渲染内容
+ * @param $callback
+ * @return string
+ * @throws \Exception
+ */
+ protected function getContent($callback): string
+ {
+ // 页面缓存
+ ob_start();
+ if (PHP_VERSION > 8.0) {
+ ob_implicit_flush(false);
+ } else {
+ ob_implicit_flush(0);
+ }
+
+ // 渲染输出
+ try {
+ $callback();
+ } catch (\Exception $e) {
+ ob_end_clean();
+ throw $e;
+ }
+
+ // 获取并清空缓存
+ $content = ob_get_clean();
+
+ if ($this->filter) {
+ $content = call_user_func_array($this->filter, [$content]);
+ }
+
+ return $content;
+ }
+
+ /**
+ * 模板变量赋值
+ * @access public
+ * @param string $name 变量名
+ * @param mixed $value 变量值
+ */
+ public function __set($name, $value)
+ {
+ $this->data[$name] = $value;
+ }
+
+ /**
+ * 取得模板显示变量的值
+ * @access protected
+ * @param string $name 模板变量
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->data[$name];
+ }
+
+ /**
+ * 检测模板变量是否设置
+ * @access public
+ * @param string $name 模板变量名
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return isset($this->data[$name]);
+ }
+
+ protected function resolveConfig(string $name)
+ {
+ $config = $this->app->config->get('view', []);
+ Arr::forget($config, 'type');
+ return $config;
+ }
+
+ /**
+ * 默认驱动
+ * @return string|null
+ */
+ public function getDefaultDriver()
+ {
+ return $this->app->config->get('view.type', 'php');
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/cache/Driver.php b/vendor/topthink/framework/src/think/cache/Driver.php
new file mode 100755
index 0000000..7dc0d30
--- /dev/null
+++ b/vendor/topthink/framework/src/think/cache/Driver.php
@@ -0,0 +1,359 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache;
+
+use Closure;
+use DateInterval;
+use DateTime;
+use DateTimeInterface;
+use Exception;
+use Psr\SimpleCache\CacheInterface;
+use think\Container;
+use think\contract\CacheHandlerInterface;
+use think\exception\InvalidArgumentException;
+use throwable;
+
+/**
+ * 缓存基础类
+ */
+abstract class Driver implements CacheInterface, CacheHandlerInterface
+{
+ /**
+ * 驱动句柄
+ * @var object
+ */
+ protected $handler = null;
+
+ /**
+ * 缓存读取次数
+ * @var integer
+ */
+ protected $readTimes = 0;
+
+ /**
+ * 缓存写入次数
+ * @var integer
+ */
+ protected $writeTimes = 0;
+
+ /**
+ * 缓存参数
+ * @var array
+ */
+ protected $options = [];
+
+ /**
+ * 缓存标签
+ * @var array
+ */
+ protected $tag = [];
+
+ /**
+ * 获取有效期
+ * @access protected
+ * @param integer|DateTimeInterface|DateInterval $expire 有效期
+ * @return int
+ */
+ protected function getExpireTime($expire): int
+ {
+ if ($expire instanceof DateTimeInterface) {
+ $expire = $expire->getTimestamp() - time();
+ } elseif ($expire instanceof DateInterval) {
+ $expire = DateTime::createFromFormat('U', (string) time())
+ ->add($expire)
+ ->format('U') - time();
+ }
+
+ return (int) $expire;
+ }
+
+ /**
+ * 获取实际的缓存标识
+ * @access public
+ * @param string $name 缓存名
+ * @return string
+ */
+ public function getCacheKey(string $name): string
+ {
+ return $this->options['prefix'] . $name;
+ }
+
+ /**
+ * 读取缓存并删除
+ * @access public
+ * @param string $name 缓存变量名
+ * @return mixed
+ */
+ public function pull(string $name)
+ {
+ $result = $this->get($name, false);
+
+ if ($result) {
+ $this->delete($name);
+ return $result;
+ }
+ }
+
+ /**
+ * 追加(数组)缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @return void
+ */
+ public function push(string $name, $value): void
+ {
+ $item = $this->get($name, []);
+
+ if (!is_array($item)) {
+ throw new InvalidArgumentException('only array cache can be push');
+ }
+
+ $item[] = $value;
+
+ if (count($item) > 1000) {
+ array_shift($item);
+ }
+
+ $item = array_unique($item);
+
+ $this->set($name, $item);
+ }
+
+ /**
+ * 追加TagSet数据
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @return void
+ */
+ public function append(string $name, $value): void
+ {
+ $this->push($name, $value);
+ }
+
+ /**
+ * 如果不存在则写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param int $expire 有效时间 0为永久
+ * @return mixed
+ */
+ public function remember(string $name, $value, $expire = null)
+ {
+ if ($this->has($name)) {
+ if (($hit = $this->get($name)) !== null) {
+ return $hit;
+ }
+ }
+
+ $time = time();
+
+ while ($time + 5 > time() && $this->has($name . '_lock')) {
+ // 存在锁定则等待
+ usleep(200000);
+ }
+
+ try {
+ // 锁定
+ $this->set($name . '_lock', true);
+
+ if ($value instanceof Closure) {
+ // 获取缓存数据
+ $value = Container::getInstance()->invokeFunction($value);
+ }
+
+ // 缓存数据
+ $this->set($name, $value, $expire);
+
+ // 解锁
+ $this->delete($name . '_lock');
+ } catch (Exception | throwable $e) {
+ $this->delete($name . '_lock');
+ throw $e;
+ }
+
+ return $value;
+ }
+
+ /**
+ * 缓存标签
+ * @access public
+ * @param string|array $name 标签名
+ * @return TagSet
+ */
+ public function tag($name): TagSet
+ {
+ $name = (array) $name;
+ $key = implode('-', $name);
+
+ if (!isset($this->tag[$key])) {
+ $this->tag[$key] = new TagSet($name, $this);
+ }
+
+ return $this->tag[$key];
+ }
+
+ /**
+ * 获取标签包含的缓存标识
+ * @access public
+ * @param string $tag 标签标识
+ * @return array
+ */
+ public function getTagItems(string $tag): array
+ {
+ $name = $this->getTagKey($tag);
+ return $this->get($name, []);
+ }
+
+ /**
+ * 获取实际标签名
+ * @access public
+ * @param string $tag 标签名
+ * @return string
+ */
+ public function getTagKey(string $tag): string
+ {
+ return $this->options['tag_prefix'] . md5($tag);
+ }
+
+ /**
+ * 序列化数据
+ * @access protected
+ * @param mixed $data 缓存数据
+ * @return string
+ */
+ protected function serialize($data): string
+ {
+ if (is_numeric($data)) {
+ return (string) $data;
+ }
+
+ $serialize = $this->options['serialize'][0] ?? "serialize";
+
+ return $serialize($data);
+ }
+
+ /**
+ * 反序列化数据
+ * @access protected
+ * @param string $data 缓存数据
+ * @return mixed
+ */
+ protected function unserialize($data)
+ {
+ if (is_numeric($data)) {
+ return $data;
+ }
+
+ $unserialize = $this->options['serialize'][1] ?? "unserialize";
+
+ return $unserialize($data);
+ }
+
+ /**
+ * 返回句柄对象,可执行其它高级方法
+ *
+ * @access public
+ * @return object
+ */
+ public function handler()
+ {
+ return $this->handler;
+ }
+
+ /**
+ * 返回缓存读取次数
+ * @access public
+ * @return int
+ */
+ public function getReadTimes(): int
+ {
+ return $this->readTimes;
+ }
+
+ /**
+ * 返回缓存写入次数
+ * @access public
+ * @return int
+ */
+ public function getWriteTimes(): int
+ {
+ return $this->writeTimes;
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param iterable $keys 缓存变量名
+ * @param mixed $default 默认值
+ * @return iterable
+ * @throws InvalidArgumentException
+ */
+ public function getMultiple($keys, $default = null): iterable
+ {
+ $result = [];
+
+ foreach ($keys as $key) {
+ $result[$key] = $this->get($key, $default);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param iterable $values 缓存数据
+ * @param null|int|\DateInterval $ttl 有效时间 0为永久
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null): bool
+ {
+ foreach ($values as $key => $val) {
+ $result = $this->set($key, $val, $ttl);
+
+ if (false === $result) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param iterable $keys 缓存变量名
+ * @return bool
+ * @throws InvalidArgumentException
+ */
+ public function deleteMultiple($keys): bool
+ {
+ foreach ($keys as $key) {
+ $result = $this->delete($key);
+
+ if (false === $result) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public function __call($method, $args)
+ {
+ return call_user_func_array([$this->handler, $method], $args);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/cache/TagSet.php b/vendor/topthink/framework/src/think/cache/TagSet.php
new file mode 100755
index 0000000..5ba2076
--- /dev/null
+++ b/vendor/topthink/framework/src/think/cache/TagSet.php
@@ -0,0 +1,132 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache;
+
+/**
+ * 标签集合
+ */
+class TagSet
+{
+ /**
+ * 标签的缓存Key
+ * @var array
+ */
+ protected $tag;
+
+ /**
+ * 缓存句柄
+ * @var Driver
+ */
+ protected $handler;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param array $tag 缓存标签
+ * @param Driver $cache 缓存对象
+ */
+ public function __construct(array $tag, Driver $cache)
+ {
+ $this->tag = $tag;
+ $this->handler = $cache;
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer|\DateTime $expire 有效时间(秒)
+ * @return bool
+ */
+ public function set(string $name, $value, $expire = null): bool
+ {
+ $this->handler->set($name, $value, $expire);
+
+ $this->append($name);
+
+ return true;
+ }
+
+ /**
+ * 追加缓存标识到标签
+ * @access public
+ * @param string $name 缓存变量名
+ * @return void
+ */
+ public function append(string $name): void
+ {
+ $name = $this->handler->getCacheKey($name);
+
+ foreach ($this->tag as $tag) {
+ $key = $this->handler->getTagKey($tag);
+ $this->handler->append($key, $name);
+ }
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param iterable $values 缓存数据
+ * @param null|int|\DateInterval $ttl 有效时间 0为永久
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null): bool
+ {
+ foreach ($values as $key => $val) {
+ $result = $this->set($key, $val, $ttl);
+
+ if (false === $result) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 如果不存在则写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param int $expire 有效时间 0为永久
+ * @return mixed
+ */
+ public function remember(string $name, $value, $expire = null)
+ {
+ $result = $this->handler->remember($name, $value, $expire);
+
+ $this->append($name);
+
+ return $result;
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return bool
+ */
+ public function clear(): bool
+ {
+ // 指定标签清除
+ foreach ($this->tag as $tag) {
+ $names = $this->handler->getTagItems($tag);
+ $this->handler->clearTag($names);
+
+ $key = $this->handler->getTagKey($tag);
+ $this->handler->delete($key);
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/cache/driver/File.php b/vendor/topthink/framework/src/think/cache/driver/File.php
new file mode 100755
index 0000000..b36b069
--- /dev/null
+++ b/vendor/topthink/framework/src/think/cache/driver/File.php
@@ -0,0 +1,304 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache\driver;
+
+use FilesystemIterator;
+use think\App;
+use think\cache\Driver;
+
+/**
+ * 文件缓存类
+ */
+class File extends Driver
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $options = [
+ 'expire' => 0,
+ 'cache_subdir' => true,
+ 'prefix' => '',
+ 'path' => '',
+ 'hash_type' => 'md5',
+ 'data_compress' => false,
+ 'tag_prefix' => 'tag:',
+ 'serialize' => [],
+ ];
+
+ /**
+ * 架构函数
+ * @param App $app
+ * @param array $options 参数
+ */
+ public function __construct(App $app, array $options = [])
+ {
+ if (!empty($options)) {
+ $this->options = array_merge($this->options, $options);
+ }
+
+ if (empty($this->options['path'])) {
+ $this->options['path'] = $app->getRuntimePath() . 'cache';
+ }
+
+ if (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) {
+ $this->options['path'] .= DIRECTORY_SEPARATOR;
+ }
+ }
+
+ /**
+ * 取得变量的存储文件名
+ * @access public
+ * @param string $name 缓存变量名
+ * @return string
+ */
+ public function getCacheKey(string $name): string
+ {
+ $name = hash($this->options['hash_type'], $name);
+
+ if ($this->options['cache_subdir']) {
+ // 使用子目录
+ $name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2);
+ }
+
+ if ($this->options['prefix']) {
+ $name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name;
+ }
+
+ return $this->options['path'] . $name . '.php';
+ }
+
+ /**
+ * 获取缓存数据
+ * @param string $name 缓存标识名
+ * @return array|null
+ */
+ protected function getRaw(string $name)
+ {
+ $filename = $this->getCacheKey($name);
+
+ if (!is_file($filename)) {
+ return;
+ }
+
+ $content = @file_get_contents($filename);
+
+ if (false !== $content) {
+ $expire = (int) substr($content, 8, 12);
+ if (0 != $expire && time() - $expire > filemtime($filename)) {
+ //缓存过期删除缓存文件
+ $this->unlink($filename);
+ return;
+ }
+
+ $content = substr($content, 32);
+
+ if ($this->options['data_compress'] && function_exists('gzcompress')) {
+ //启用数据压缩
+ $content = gzuncompress($content);
+ }
+
+ return is_string($content) ? ['content' => $content, 'expire' => $expire] : null;
+ }
+ }
+
+ /**
+ * 判断缓存是否存在
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function has($name): bool
+ {
+ return $this->getRaw($name) !== null;
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get($name, $default = null)
+ {
+ $this->readTimes++;
+
+ $raw = $this->getRaw($name);
+
+ return is_null($raw) ? $default : $this->unserialize($raw['content']);
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param int|\DateTime $expire 有效时间 0为永久
+ * @return bool
+ */
+ public function set($name, $value, $expire = null): bool
+ {
+ $this->writeTimes++;
+
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+
+ $expire = $this->getExpireTime($expire);
+ $filename = $this->getCacheKey($name);
+
+ $dir = dirname($filename);
+
+ if (!is_dir($dir)) {
+ try {
+ mkdir($dir, 0755, true);
+ } catch (\Exception $e) {
+ // 创建失败
+ }
+ }
+
+ $data = $this->serialize($value);
+
+ if ($this->options['data_compress'] && function_exists('gzcompress')) {
+ //数据压缩
+ $data = gzcompress($data, 3);
+ }
+
+ $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
+ $result = file_put_contents($filename, $data);
+
+ if ($result) {
+ clearstatcache();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 自增缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function inc(string $name, int $step = 1)
+ {
+ if ($raw = $this->getRaw($name)) {
+ $value = $this->unserialize($raw['content']) + $step;
+ $expire = $raw['expire'];
+ } else {
+ $value = $step;
+ $expire = 0;
+ }
+
+ return $this->set($name, $value, $expire) ? $value : false;
+ }
+
+ /**
+ * 自减缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function dec(string $name, int $step = 1)
+ {
+ return $this->inc($name, -$step);
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function delete($name): bool
+ {
+ $this->writeTimes++;
+
+ return $this->unlink($this->getCacheKey($name));
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return bool
+ */
+ public function clear(): bool
+ {
+ $this->writeTimes++;
+
+ $dirname = $this->options['path'] . $this->options['prefix'];
+
+ $this->rmdir($dirname);
+
+ return true;
+ }
+
+ /**
+ * 删除缓存标签
+ * @access public
+ * @param array $keys 缓存标识列表
+ * @return void
+ */
+ public function clearTag(array $keys): void
+ {
+ foreach ($keys as $key) {
+ $this->unlink($key);
+ }
+ }
+
+ /**
+ * 判断文件是否存在后,删除
+ * @access private
+ * @param string $path
+ * @return bool
+ */
+ private function unlink(string $path): bool
+ {
+ try {
+ return is_file($path) && unlink($path);
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * 删除文件夹
+ * @param $dirname
+ * @return bool
+ */
+ private function rmdir($dirname)
+ {
+ if (!is_dir($dirname)) {
+ return false;
+ }
+
+ $items = new FilesystemIterator($dirname);
+
+ foreach ($items as $item) {
+ if ($item->isDir() && !$item->isLink()) {
+ $this->rmdir($item->getPathname());
+ } else {
+ $this->unlink($item->getPathname());
+ }
+ }
+
+ @rmdir($dirname);
+
+ return true;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/cache/driver/Memcache.php b/vendor/topthink/framework/src/think/cache/driver/Memcache.php
new file mode 100755
index 0000000..2fbbb9c
--- /dev/null
+++ b/vendor/topthink/framework/src/think/cache/driver/Memcache.php
@@ -0,0 +1,209 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+/**
+ * Memcache缓存类
+ */
+class Memcache extends Driver
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $options = [
+ 'host' => '127.0.0.1',
+ 'port' => 11211,
+ 'expire' => 0,
+ 'timeout' => 0, // 超时时间(单位:毫秒)
+ 'persistent' => true,
+ 'prefix' => '',
+ 'tag_prefix' => 'tag:',
+ 'serialize' => [],
+ ];
+
+ /**
+ * 架构函数
+ * @access public
+ * @param array $options 缓存参数
+ * @throws \BadFunctionCallException
+ */
+ public function __construct(array $options = [])
+ {
+ if (!extension_loaded('memcache')) {
+ throw new \BadFunctionCallException('not support: memcache');
+ }
+
+ if (!empty($options)) {
+ $this->options = array_merge($this->options, $options);
+ }
+
+ $this->handler = new \Memcache;
+
+ // 支持集群
+ $hosts = (array) $this->options['host'];
+ $ports = (array) $this->options['port'];
+
+ if (empty($ports[0])) {
+ $ports[0] = 11211;
+ }
+
+ // 建立连接
+ foreach ($hosts as $i => $host) {
+ $port = $ports[$i] ?? $ports[0];
+ $this->options['timeout'] > 0 ?
+ $this->handler->addServer($host, (int) $port, $this->options['persistent'], 1, (int) $this->options['timeout']) :
+ $this->handler->addServer($host, (int) $port, $this->options['persistent'], 1);
+ }
+ }
+
+ /**
+ * 判断缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function has($name): bool
+ {
+ $key = $this->getCacheKey($name);
+
+ return false !== $this->handler->get($key);
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get($name, $default = null)
+ {
+ $this->readTimes++;
+
+ $result = $this->handler->get($this->getCacheKey($name));
+
+ return false !== $result ? $this->unserialize($result) : $default;
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param int|\DateTime $expire 有效时间(秒)
+ * @return bool
+ */
+ public function set($name, $value, $expire = null): bool
+ {
+ $this->writeTimes++;
+
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+
+ $key = $this->getCacheKey($name);
+ $expire = $this->getExpireTime($expire);
+ $value = $this->serialize($value);
+
+ if ($this->handler->set($key, $value, 0, $expire)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 自增缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function inc(string $name, int $step = 1)
+ {
+ $this->writeTimes++;
+
+ $key = $this->getCacheKey($name);
+
+ if ($this->handler->get($key)) {
+ return $this->handler->increment($key, $step);
+ }
+
+ return $this->handler->set($key, $step);
+ }
+
+ /**
+ * 自减缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function dec(string $name, int $step = 1)
+ {
+ $this->writeTimes++;
+
+ $key = $this->getCacheKey($name);
+ $value = $this->handler->get($key) - $step;
+ $res = $this->handler->set($key, $value);
+
+ return !$res ? false : $value;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param bool|false $ttl
+ * @return bool
+ */
+ public function delete($name, $ttl = false): bool
+ {
+ $this->writeTimes++;
+
+ $key = $this->getCacheKey($name);
+
+ return false === $ttl ?
+ $this->handler->delete($key) :
+ $this->handler->delete($key, $ttl);
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return bool
+ */
+ public function clear(): bool
+ {
+ $this->writeTimes++;
+
+ return $this->handler->flush();
+ }
+
+ /**
+ * 删除缓存标签
+ * @access public
+ * @param array $keys 缓存标识列表
+ * @return void
+ */
+ public function clearTag(array $keys): void
+ {
+ foreach ($keys as $key) {
+ $this->handler->delete($key);
+ }
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/cache/driver/Memcached.php b/vendor/topthink/framework/src/think/cache/driver/Memcached.php
new file mode 100755
index 0000000..71edb05
--- /dev/null
+++ b/vendor/topthink/framework/src/think/cache/driver/Memcached.php
@@ -0,0 +1,221 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+/**
+ * Memcached缓存类
+ */
+class Memcached extends Driver
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $options = [
+ 'host' => '127.0.0.1',
+ 'port' => 11211,
+ 'expire' => 0,
+ 'timeout' => 0, // 超时时间(单位:毫秒)
+ 'prefix' => '',
+ 'username' => '', //账号
+ 'password' => '', //密码
+ 'option' => [],
+ 'tag_prefix' => 'tag:',
+ 'serialize' => [],
+ ];
+
+ /**
+ * 架构函数
+ * @access public
+ * @param array $options 缓存参数
+ */
+ public function __construct(array $options = [])
+ {
+ if (!extension_loaded('memcached')) {
+ throw new \BadFunctionCallException('not support: memcached');
+ }
+
+ if (!empty($options)) {
+ $this->options = array_merge($this->options, $options);
+ }
+
+ $this->handler = new \Memcached;
+
+ if (!empty($this->options['option'])) {
+ $this->handler->setOptions($this->options['option']);
+ }
+
+ // 设置连接超时时间(单位:毫秒)
+ if ($this->options['timeout'] > 0) {
+ $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']);
+ }
+
+ // 支持集群
+ $hosts = (array) $this->options['host'];
+ $ports = (array) $this->options['port'];
+ if (empty($ports[0])) {
+ $ports[0] = 11211;
+ }
+
+ // 建立连接
+ $servers = [];
+ foreach ($hosts as $i => $host) {
+ $servers[] = [$host, $ports[$i] ?? $ports[0], 1];
+ }
+
+ $this->handler->addServers($servers);
+
+ if ('' != $this->options['username']) {
+ $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
+ $this->handler->setSaslAuthData($this->options['username'], $this->options['password']);
+ }
+ }
+
+ /**
+ * 判断缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function has($name): bool
+ {
+ $key = $this->getCacheKey($name);
+
+ return $this->handler->get($key) ? true : false;
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get($name, $default = null)
+ {
+ $this->readTimes++;
+
+ $result = $this->handler->get($this->getCacheKey($name));
+
+ return false !== $result ? $this->unserialize($result) : $default;
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer|\DateTime $expire 有效时间(秒)
+ * @return bool
+ */
+ public function set($name, $value, $expire = null): bool
+ {
+ $this->writeTimes++;
+
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+
+ $key = $this->getCacheKey($name);
+ $expire = $this->getExpireTime($expire);
+ $value = $this->serialize($value);
+
+ if ($this->handler->set($key, $value, $expire)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 自增缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function inc(string $name, int $step = 1)
+ {
+ $this->writeTimes++;
+
+ $key = $this->getCacheKey($name);
+
+ if ($this->handler->get($key)) {
+ return $this->handler->increment($key, $step);
+ }
+
+ return $this->handler->set($key, $step);
+ }
+
+ /**
+ * 自减缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function dec(string $name, int $step = 1)
+ {
+ $this->writeTimes++;
+
+ $key = $this->getCacheKey($name);
+ $value = $this->handler->get($key) - $step;
+ $res = $this->handler->set($key, $value);
+
+ return !$res ? false : $value;
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param bool|false $ttl
+ * @return bool
+ */
+ public function delete($name, $ttl = false): bool
+ {
+ $this->writeTimes++;
+
+ $key = $this->getCacheKey($name);
+
+ return false === $ttl ?
+ $this->handler->delete($key) :
+ $this->handler->delete($key, $ttl);
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return bool
+ */
+ public function clear(): bool
+ {
+ $this->writeTimes++;
+
+ return $this->handler->flush();
+ }
+
+ /**
+ * 删除缓存标签
+ * @access public
+ * @param array $keys 缓存标识列表
+ * @return void
+ */
+ public function clearTag(array $keys): void
+ {
+ $this->handler->deleteMulti($keys);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/cache/driver/Redis.php b/vendor/topthink/framework/src/think/cache/driver/Redis.php
new file mode 100755
index 0000000..791b27b
--- /dev/null
+++ b/vendor/topthink/framework/src/think/cache/driver/Redis.php
@@ -0,0 +1,249 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+/**
+ * Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好
+ * 有需要在业务层实现读写分离、或者使用RedisCluster的需求,请使用Redisd驱动
+ *
+ * 要求安装phpredis扩展:https://github.com/nicolasff/phpredis
+ * @author 尘缘 <130775@qq.com>
+ */
+class Redis extends Driver
+{
+ /** @var \Predis\Client|\Redis */
+ protected $handler;
+
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $options = [
+ 'host' => '127.0.0.1',
+ 'port' => 6379,
+ 'password' => '',
+ 'select' => 0,
+ 'timeout' => 0,
+ 'expire' => 0,
+ 'persistent' => false,
+ 'prefix' => '',
+ 'tag_prefix' => 'tag:',
+ 'serialize' => [],
+ ];
+
+ /**
+ * 架构函数
+ * @access public
+ * @param array $options 缓存参数
+ */
+ public function __construct(array $options = [])
+ {
+ if (!empty($options)) {
+ $this->options = array_merge($this->options, $options);
+ }
+
+ if (extension_loaded('redis')) {
+ $this->handler = new \Redis;
+
+ if ($this->options['persistent']) {
+ $this->handler->pconnect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout'], 'persistent_id_' . $this->options['select']);
+ } else {
+ $this->handler->connect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout']);
+ }
+
+ if ('' != $this->options['password']) {
+ $this->handler->auth($this->options['password']);
+ }
+ } elseif (class_exists('\Predis\Client')) {
+ $params = [];
+ foreach ($this->options as $key => $val) {
+ if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication', 'parameters'])) {
+ $params[$key] = $val;
+ unset($this->options[$key]);
+ }
+ }
+
+ if ('' == $this->options['password']) {
+ unset($this->options['password']);
+ }
+
+ $this->handler = new \Predis\Client($this->options, $params);
+
+ $this->options['prefix'] = '';
+ } else {
+ throw new \BadFunctionCallException('not support: redis');
+ }
+
+ if (0 != $this->options['select']) {
+ $this->handler->select((int) $this->options['select']);
+ }
+ }
+
+ /**
+ * 判断缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function has($name): bool
+ {
+ return $this->handler->exists($this->getCacheKey($name)) ? true : false;
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get($name, $default = null)
+ {
+ $this->readTimes++;
+ $key = $this->getCacheKey($name);
+ $value = $this->handler->get($key);
+
+ if (false === $value || is_null($value)) {
+ return $default;
+ }
+
+ return $this->unserialize($value);
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer|\DateTime $expire 有效时间(秒)
+ * @return bool
+ */
+ public function set($name, $value, $expire = null): bool
+ {
+ $this->writeTimes++;
+
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+
+ $key = $this->getCacheKey($name);
+ $expire = $this->getExpireTime($expire);
+ $value = $this->serialize($value);
+
+ if ($expire) {
+ $this->handler->setex($key, $expire, $value);
+ } else {
+ $this->handler->set($key, $value);
+ }
+
+ return true;
+ }
+
+ /**
+ * 自增缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function inc(string $name, int $step = 1)
+ {
+ $this->writeTimes++;
+ $key = $this->getCacheKey($name);
+
+ return $this->handler->incrby($key, $step);
+ }
+
+ /**
+ * 自减缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function dec(string $name, int $step = 1)
+ {
+ $this->writeTimes++;
+ $key = $this->getCacheKey($name);
+
+ return $this->handler->decrby($key, $step);
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function delete($name): bool
+ {
+ $this->writeTimes++;
+
+ $key = $this->getCacheKey($name);
+ $result = $this->handler->del($key);
+ return $result > 0;
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return bool
+ */
+ public function clear(): bool
+ {
+ $this->writeTimes++;
+ $this->handler->flushDB();
+ return true;
+ }
+
+ /**
+ * 删除缓存标签
+ * @access public
+ * @param array $keys 缓存标识列表
+ * @return void
+ */
+ public function clearTag(array $keys): void
+ {
+ // 指定标签清除
+ $this->handler->del($keys);
+ }
+
+ /**
+ * 追加TagSet数据
+ * @access public
+ * @param string $name 缓存标识
+ * @param mixed $value 数据
+ * @return void
+ */
+ public function append(string $name, $value): void
+ {
+ $key = $this->getCacheKey($name);
+ $this->handler->sAdd($key, $value);
+ }
+
+ /**
+ * 获取标签包含的缓存标识
+ * @access public
+ * @param string $tag 缓存标签
+ * @return array
+ */
+ public function getTagItems(string $tag): array
+ {
+ $name = $this->getTagKey($tag);
+ $key = $this->getCacheKey($name);
+ return $this->handler->sMembers($key);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/cache/driver/Wincache.php b/vendor/topthink/framework/src/think/cache/driver/Wincache.php
new file mode 100755
index 0000000..8b3e8b8
--- /dev/null
+++ b/vendor/topthink/framework/src/think/cache/driver/Wincache.php
@@ -0,0 +1,175 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+/**
+ * Wincache缓存驱动
+ */
+class Wincache extends Driver
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $options = [
+ 'prefix' => '',
+ 'expire' => 0,
+ 'tag_prefix' => 'tag:',
+ 'serialize' => [],
+ ];
+
+ /**
+ * 架构函数
+ * @access public
+ * @param array $options 缓存参数
+ * @throws \BadFunctionCallException
+ */
+ public function __construct(array $options = [])
+ {
+ if (!function_exists('wincache_ucache_info')) {
+ throw new \BadFunctionCallException('not support: WinCache');
+ }
+
+ if (!empty($options)) {
+ $this->options = array_merge($this->options, $options);
+ }
+ }
+
+ /**
+ * 判断缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function has($name): bool
+ {
+ $this->readTimes++;
+
+ $key = $this->getCacheKey($name);
+
+ return wincache_ucache_exists($key);
+ }
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get($name, $default = null)
+ {
+ $this->readTimes++;
+
+ $key = $this->getCacheKey($name);
+
+ return wincache_ucache_exists($key) ? $this->unserialize(wincache_ucache_get($key)) : $default;
+ }
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer|\DateTime $expire 有效时间(秒)
+ * @return bool
+ */
+ public function set($name, $value, $expire = null): bool
+ {
+ $this->writeTimes++;
+
+ if (is_null($expire)) {
+ $expire = $this->options['expire'];
+ }
+
+ $key = $this->getCacheKey($name);
+ $expire = $this->getExpireTime($expire);
+ $value = $this->serialize($value);
+
+ if (wincache_ucache_set($key, $value, $expire)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 自增缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function inc(string $name, int $step = 1)
+ {
+ $this->writeTimes++;
+
+ $key = $this->getCacheKey($name);
+
+ return wincache_ucache_inc($key, $step);
+ }
+
+ /**
+ * 自减缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function dec(string $name, int $step = 1)
+ {
+ $this->writeTimes++;
+
+ $key = $this->getCacheKey($name);
+
+ return wincache_ucache_dec($key, $step);
+ }
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function delete($name): bool
+ {
+ $this->writeTimes++;
+
+ return wincache_ucache_delete($this->getCacheKey($name));
+ }
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return bool
+ */
+ public function clear(): bool
+ {
+ $this->writeTimes++;
+ return wincache_ucache_clear();
+ }
+
+ /**
+ * 删除缓存标签
+ * @access public
+ * @param array $keys 缓存标识列表
+ * @return void
+ */
+ public function clearTag(array $keys): void
+ {
+ wincache_ucache_delete($keys);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/console/Command.php b/vendor/topthink/framework/src/think/console/Command.php
new file mode 100755
index 0000000..bd3fb20
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/Command.php
@@ -0,0 +1,504 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console;
+
+use Exception;
+use InvalidArgumentException;
+use LogicException;
+use think\App;
+use think\Console;
+use think\console\input\Argument;
+use think\console\input\Definition;
+use think\console\input\Option;
+
+abstract class Command
+{
+
+ /** @var Console */
+ private $console;
+ private $name;
+ private $processTitle;
+ private $aliases = [];
+ private $definition;
+ private $help;
+ private $description;
+ private $ignoreValidationErrors = false;
+ private $consoleDefinitionMerged = false;
+ private $consoleDefinitionMergedWithArgs = false;
+ private $synopsis = [];
+ private $usages = [];
+
+ /** @var Input */
+ protected $input;
+
+ /** @var Output */
+ protected $output;
+
+ /** @var App */
+ protected $app;
+
+ /**
+ * 构造方法
+ * @throws LogicException
+ * @api
+ */
+ public function __construct()
+ {
+ $this->definition = new Definition();
+
+ $this->configure();
+
+ if (!$this->name) {
+ throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
+ }
+ }
+
+ /**
+ * 忽略验证错误
+ */
+ public function ignoreValidationErrors(): void
+ {
+ $this->ignoreValidationErrors = true;
+ }
+
+ /**
+ * 设置控制台
+ * @param Console $console
+ */
+ public function setConsole(Console $console = null): void
+ {
+ $this->console = $console;
+ }
+
+ /**
+ * 获取控制台
+ * @return Console
+ * @api
+ */
+ public function getConsole(): Console
+ {
+ return $this->console;
+ }
+
+ /**
+ * 设置app
+ * @param App $app
+ */
+ public function setApp(App $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * 获取app
+ * @return App
+ */
+ public function getApp()
+ {
+ return $this->app;
+ }
+
+ /**
+ * 是否有效
+ * @return bool
+ */
+ public function isEnabled(): bool
+ {
+ return true;
+ }
+
+ /**
+ * 配置指令
+ */
+ protected function configure()
+ {
+ }
+
+ /**
+ * 执行指令
+ * @param Input $input
+ * @param Output $output
+ * @return null|int
+ * @throws LogicException
+ * @see setCode()
+ */
+ protected function execute(Input $input, Output $output)
+ {
+ return $this->app->invoke([$this, 'handle']);
+ }
+
+ /**
+ * 用户验证
+ * @param Input $input
+ * @param Output $output
+ */
+ protected function interact(Input $input, Output $output)
+ {
+ }
+
+ /**
+ * 初始化
+ * @param Input $input An InputInterface instance
+ * @param Output $output An OutputInterface instance
+ */
+ protected function initialize(Input $input, Output $output)
+ {
+ }
+
+ /**
+ * 执行
+ * @param Input $input
+ * @param Output $output
+ * @return int
+ * @throws Exception
+ * @see setCode()
+ * @see execute()
+ */
+ public function run(Input $input, Output $output): int
+ {
+ $this->input = $input;
+ $this->output = $output;
+
+ $this->getSynopsis(true);
+ $this->getSynopsis(false);
+
+ $this->mergeConsoleDefinition();
+
+ try {
+ $input->bind($this->definition);
+ } catch (Exception $e) {
+ if (!$this->ignoreValidationErrors) {
+ throw $e;
+ }
+ }
+
+ $this->initialize($input, $output);
+
+ if (null !== $this->processTitle) {
+ if (function_exists('cli_set_process_title')) {
+ if (false === @cli_set_process_title($this->processTitle)) {
+ if ('Darwin' === PHP_OS) {
+ $output->writeln('<comment>Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.</comment>');
+ } else {
+ $error = error_get_last();
+ trigger_error($error['message'], E_USER_WARNING);
+ }
+ }
+ } elseif (function_exists('setproctitle')) {
+ setproctitle($this->processTitle);
+ } elseif (Output::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
+ $output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
+ }
+ }
+
+ if ($input->isInteractive()) {
+ $this->interact($input, $output);
+ }
+
+ $input->validate();
+
+ $statusCode = $this->execute($input, $output);
+
+ return is_numeric($statusCode) ? (int) $statusCode : 0;
+ }
+
+ /**
+ * 合并参数定义
+ * @param bool $mergeArgs
+ */
+ public function mergeConsoleDefinition(bool $mergeArgs = true)
+ {
+ if (null === $this->console
+ || (true === $this->consoleDefinitionMerged
+ && ($this->consoleDefinitionMergedWithArgs || !$mergeArgs))
+ ) {
+ return;
+ }
+
+ if ($mergeArgs) {
+ $currentArguments = $this->definition->getArguments();
+ $this->definition->setArguments($this->console->getDefinition()->getArguments());
+ $this->definition->addArguments($currentArguments);
+ }
+
+ $this->definition->addOptions($this->console->getDefinition()->getOptions());
+
+ $this->consoleDefinitionMerged = true;
+ if ($mergeArgs) {
+ $this->consoleDefinitionMergedWithArgs = true;
+ }
+ }
+
+ /**
+ * 设置参数定义
+ * @param array|Definition $definition
+ * @return Command
+ * @api
+ */
+ public function setDefinition($definition)
+ {
+ if ($definition instanceof Definition) {
+ $this->definition = $definition;
+ } else {
+ $this->definition->setDefinition($definition);
+ }
+
+ $this->consoleDefinitionMerged = false;
+
+ return $this;
+ }
+
+ /**
+ * 获取参数定义
+ * @return Definition
+ * @api
+ */
+ public function getDefinition(): Definition
+ {
+ return $this->definition;
+ }
+
+ /**
+ * 获取当前指令的参数定义
+ * @return Definition
+ */
+ public function getNativeDefinition(): Definition
+ {
+ return $this->getDefinition();
+ }
+
+ /**
+ * 添加参数
+ * @param string $name 名称
+ * @param int $mode 类型
+ * @param string $description 描述
+ * @param mixed $default 默认值
+ * @return Command
+ */
+ public function addArgument(string $name, int $mode = null, string $description = '', $default = null)
+ {
+ $this->definition->addArgument(new Argument($name, $mode, $description, $default));
+
+ return $this;
+ }
+
+ /**
+ * 添加选项
+ * @param string $name 选项名称
+ * @param string $shortcut 别名
+ * @param int $mode 类型
+ * @param string $description 描述
+ * @param mixed $default 默认值
+ * @return Command
+ */
+ public function addOption(string $name, string $shortcut = null, int $mode = null, string $description = '', $default = null)
+ {
+ $this->definition->addOption(new Option($name, $shortcut, $mode, $description, $default));
+
+ return $this;
+ }
+
+ /**
+ * 设置指令名称
+ * @param string $name
+ * @return Command
+ * @throws InvalidArgumentException
+ */
+ public function setName(string $name)
+ {
+ $this->validateName($name);
+
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * 设置进程名称
+ *
+ * PHP 5.5+ or the proctitle PECL library is required
+ *
+ * @param string $title The process title
+ *
+ * @return $this
+ */
+ public function setProcessTitle($title)
+ {
+ $this->processTitle = $title;
+
+ return $this;
+ }
+
+ /**
+ * 获取指令名称
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name ?: '';
+ }
+
+ /**
+ * 设置描述
+ * @param string $description
+ * @return Command
+ */
+ public function setDescription(string $description)
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ /**
+ * 获取描述
+ * @return string
+ */
+ public function getDescription(): string
+ {
+ return $this->description ?: '';
+ }
+
+ /**
+ * 设置帮助信息
+ * @param string $help
+ * @return Command
+ */
+ public function setHelp(string $help)
+ {
+ $this->help = $help;
+
+ return $this;
+ }
+
+ /**
+ * 获取帮助信息
+ * @return string
+ */
+ public function getHelp(): string
+ {
+ return $this->help ?: '';
+ }
+
+ /**
+ * 描述信息
+ * @return string
+ */
+ public function getProcessedHelp(): string
+ {
+ $name = $this->name;
+
+ $placeholders = [
+ '%command.name%',
+ '%command.full_name%',
+ ];
+ $replacements = [
+ $name,
+ $_SERVER['PHP_SELF'] . ' ' . $name,
+ ];
+
+ return str_replace($placeholders, $replacements, $this->getHelp());
+ }
+
+ /**
+ * 设置别名
+ * @param string[] $aliases
+ * @return Command
+ * @throws InvalidArgumentException
+ */
+ public function setAliases(iterable $aliases)
+ {
+ foreach ($aliases as $alias) {
+ $this->validateName($alias);
+ }
+
+ $this->aliases = $aliases;
+
+ return $this;
+ }
+
+ /**
+ * 获取别名
+ * @return array
+ */
+ public function getAliases(): array
+ {
+ return $this->aliases;
+ }
+
+ /**
+ * 获取简介
+ * @param bool $short 是否简单的
+ * @return string
+ */
+ public function getSynopsis(bool $short = false): string
+ {
+ $key = $short ? 'short' : 'long';
+
+ if (!isset($this->synopsis[$key])) {
+ $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
+ }
+
+ return $this->synopsis[$key];
+ }
+
+ /**
+ * 添加用法介绍
+ * @param string $usage
+ * @return $this
+ */
+ public function addUsage(string $usage)
+ {
+ if (0 !== strpos($usage, $this->name)) {
+ $usage = sprintf('%s %s', $this->name, $usage);
+ }
+
+ $this->usages[] = $usage;
+
+ return $this;
+ }
+
+ /**
+ * 获取用法介绍
+ * @return array
+ */
+ public function getUsages(): array
+ {
+ return $this->usages;
+ }
+
+ /**
+ * 验证指令名称
+ * @param string $name
+ * @throws InvalidArgumentException
+ */
+ private function validateName(string $name)
+ {
+ if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
+ throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
+ }
+ }
+
+ /**
+ * 输出表格
+ * @param Table $table
+ * @return string
+ */
+ protected function table(Table $table): string
+ {
+ $content = $table->render();
+ $this->output->writeln($content);
+ return $content;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/console/Input.php b/vendor/topthink/framework/src/think/console/Input.php
new file mode 100755
index 0000000..9ae9077
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/Input.php
@@ -0,0 +1,465 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console;
+
+use think\console\input\Argument;
+use think\console\input\Definition;
+use think\console\input\Option;
+
+class Input
+{
+
+ /**
+ * @var Definition
+ */
+ protected $definition;
+
+ /**
+ * @var Option[]
+ */
+ protected $options = [];
+
+ /**
+ * @var Argument[]
+ */
+ protected $arguments = [];
+
+ protected $interactive = true;
+
+ private $tokens;
+ private $parsed;
+
+ public function __construct($argv = null)
+ {
+ if (null === $argv) {
+ $argv = $_SERVER['argv'];
+ // 去除命令名
+ array_shift($argv);
+ }
+
+ $this->tokens = $argv;
+
+ $this->definition = new Definition();
+ }
+
+ protected function setTokens(array $tokens)
+ {
+ $this->tokens = $tokens;
+ }
+
+ /**
+ * 绑定实例
+ * @param Definition $definition A InputDefinition instance
+ */
+ public function bind(Definition $definition): void
+ {
+ $this->arguments = [];
+ $this->options = [];
+ $this->definition = $definition;
+
+ $this->parse();
+ }
+
+ /**
+ * 解析参数
+ */
+ protected function parse(): void
+ {
+ $parseOptions = true;
+ $this->parsed = $this->tokens;
+ while (null !== $token = array_shift($this->parsed)) {
+ if ($parseOptions && '' == $token) {
+ $this->parseArgument($token);
+ } elseif ($parseOptions && '--' == $token) {
+ $parseOptions = false;
+ } elseif ($parseOptions && 0 === strpos($token, '--')) {
+ $this->parseLongOption($token);
+ } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
+ $this->parseShortOption($token);
+ } else {
+ $this->parseArgument($token);
+ }
+ }
+ }
+
+ /**
+ * 解析短选项
+ * @param string $token 当前的指令.
+ */
+ private function parseShortOption(string $token): void
+ {
+ $name = substr($token, 1);
+
+ if (strlen($name) > 1) {
+ if ($this->definition->hasShortcut($name[0])
+ && $this->definition->getOptionForShortcut($name[0])->acceptValue()
+ ) {
+ $this->addShortOption($name[0], substr($name, 1));
+ } else {
+ $this->parseShortOptionSet($name);
+ }
+ } else {
+ $this->addShortOption($name, null);
+ }
+ }
+
+ /**
+ * 解析短选项
+ * @param string $name 当前指令
+ * @throws \RuntimeException
+ */
+ private function parseShortOptionSet(string $name): void
+ {
+ $len = strlen($name);
+ for ($i = 0; $i < $len; ++$i) {
+ if (!$this->definition->hasShortcut($name[$i])) {
+ throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i]));
+ }
+
+ $option = $this->definition->getOptionForShortcut($name[$i]);
+ if ($option->acceptValue()) {
+ $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
+
+ break;
+ } else {
+ $this->addLongOption($option->getName(), null);
+ }
+ }
+ }
+
+ /**
+ * 解析完整选项
+ * @param string $token 当前指令
+ */
+ private function parseLongOption(string $token): void
+ {
+ $name = substr($token, 2);
+
+ if (false !== $pos = strpos($name, '=')) {
+ $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1));
+ } else {
+ $this->addLongOption($name, null);
+ }
+ }
+
+ /**
+ * 解析参数
+ * @param string $token 当前指令
+ * @throws \RuntimeException
+ */
+ private function parseArgument(string $token): void
+ {
+ $c = count($this->arguments);
+
+ if ($this->definition->hasArgument($c)) {
+ $arg = $this->definition->getArgument($c);
+
+ $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;
+
+ } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
+ $arg = $this->definition->getArgument($c - 1);
+
+ $this->arguments[$arg->getName()][] = $token;
+ } else {
+ throw new \RuntimeException('Too many arguments.');
+ }
+ }
+
+ /**
+ * 添加一个短选项的值
+ * @param string $shortcut 短名称
+ * @param mixed $value 值
+ * @throws \RuntimeException
+ */
+ private function addShortOption(string $shortcut, $value): void
+ {
+ if (!$this->definition->hasShortcut($shortcut)) {
+ throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
+ }
+
+ $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
+ }
+
+ /**
+ * 添加一个完整选项的值
+ * @param string $name 选项名
+ * @param mixed $value 值
+ * @throws \RuntimeException
+ */
+ private function addLongOption(string $name, $value): void
+ {
+ if (!$this->definition->hasOption($name)) {
+ throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name));
+ }
+
+ $option = $this->definition->getOption($name);
+
+ if (false === $value) {
+ $value = null;
+ }
+
+ if (null !== $value && !$option->acceptValue()) {
+ throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value));
+ }
+
+ if (null === $value && $option->acceptValue() && count($this->parsed)) {
+ $next = array_shift($this->parsed);
+ if (isset($next[0]) && '-' !== $next[0]) {
+ $value = $next;
+ } elseif (empty($next)) {
+ $value = '';
+ } else {
+ array_unshift($this->parsed, $next);
+ }
+ }
+
+ if (null === $value) {
+ if ($option->isValueRequired()) {
+ throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name));
+ }
+
+ if (!$option->isArray()) {
+ $value = $option->isValueOptional() ? $option->getDefault() : true;
+ }
+ }
+
+ if ($option->isArray()) {
+ $this->options[$name][] = $value;
+ } else {
+ $this->options[$name] = $value;
+ }
+ }
+
+ /**
+ * 获取第一个参数
+ * @return string|null
+ */
+ public function getFirstArgument()
+ {
+ foreach ($this->tokens as $token) {
+ if ($token && '-' === $token[0]) {
+ continue;
+ }
+
+ return $token;
+ }
+ return;
+ }
+
+ /**
+ * 检查原始参数是否包含某个值
+ * @param string|array $values 需要检查的值
+ * @return bool
+ */
+ public function hasParameterOption($values): bool
+ {
+ $values = (array) $values;
+
+ foreach ($this->tokens as $token) {
+ foreach ($values as $value) {
+ if ($token === $value || 0 === strpos($token, $value . '=')) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 获取原始选项的值
+ * @param string|array $values 需要检查的值
+ * @param mixed $default 默认值
+ * @return mixed The option value
+ */
+ public function getParameterOption($values, $default = false)
+ {
+ $values = (array) $values;
+ $tokens = $this->tokens;
+
+ while (0 < count($tokens)) {
+ $token = array_shift($tokens);
+
+ foreach ($values as $value) {
+ if ($token === $value || 0 === strpos($token, $value . '=')) {
+ if (false !== $pos = strpos($token, '=')) {
+ return substr($token, $pos + 1);
+ }
+
+ return array_shift($tokens);
+ }
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * 验证输入
+ * @throws \RuntimeException
+ */
+ public function validate()
+ {
+ if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) {
+ throw new \RuntimeException('Not enough arguments.');
+ }
+ }
+
+ /**
+ * 检查输入是否是交互的
+ * @return bool
+ */
+ public function isInteractive(): bool
+ {
+ return $this->interactive;
+ }
+
+ /**
+ * 设置输入的交互
+ * @param bool
+ */
+ public function setInteractive(bool $interactive): void
+ {
+ $this->interactive = $interactive;
+ }
+
+ /**
+ * 获取所有的参数
+ * @return Argument[]
+ */
+ public function getArguments(): array
+ {
+ return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
+ }
+
+ /**
+ * 根据名称获取参数
+ * @param string $name 参数名
+ * @return mixed
+ * @throws \InvalidArgumentException
+ */
+ public function getArgument(string $name)
+ {
+ if (!$this->definition->hasArgument($name)) {
+ throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
+ }
+
+ return $this->arguments[$name] ?? $this->definition->getArgument($name)
+ ->getDefault();
+ }
+
+ /**
+ * 设置参数的值
+ * @param string $name 参数名
+ * @param string $value 值
+ * @throws \InvalidArgumentException
+ */
+ public function setArgument(string $name, $value)
+ {
+ if (!$this->definition->hasArgument($name)) {
+ throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
+ }
+
+ $this->arguments[$name] = $value;
+ }
+
+ /**
+ * 检查是否存在某个参数
+ * @param string|int $name 参数名或位置
+ * @return bool
+ */
+ public function hasArgument($name): bool
+ {
+ return $this->definition->hasArgument($name);
+ }
+
+ /**
+ * 获取所有的选项
+ * @return Option[]
+ */
+ public function getOptions(): array
+ {
+ return array_merge($this->definition->getOptionDefaults(), $this->options);
+ }
+
+ /**
+ * 获取选项值
+ * @param string $name 选项名称
+ * @return mixed
+ * @throws \InvalidArgumentException
+ */
+ public function getOption(string $name)
+ {
+ if (!$this->definition->hasOption($name)) {
+ throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
+ }
+
+ return $this->options[$name] ?? $this->definition->getOption($name)->getDefault();
+ }
+
+ /**
+ * 设置选项值
+ * @param string $name 选项名
+ * @param string|bool $value 值
+ * @throws \InvalidArgumentException
+ */
+ public function setOption(string $name, $value): void
+ {
+ if (!$this->definition->hasOption($name)) {
+ throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
+ }
+
+ $this->options[$name] = $value;
+ }
+
+ /**
+ * 是否有某个选项
+ * @param string $name 选项名
+ * @return bool
+ */
+ public function hasOption(string $name): bool
+ {
+ return $this->definition->hasOption($name) && isset($this->options[$name]);
+ }
+
+ /**
+ * 转义指令
+ * @param string $token
+ * @return string
+ */
+ public function escapeToken(string $token): string
+ {
+ return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
+ }
+
+ /**
+ * 返回传递给命令的参数的字符串
+ * @return string
+ */
+ public function __toString()
+ {
+ $tokens = array_map(function ($token) {
+ if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
+ return $match[1] . $this->escapeToken($match[2]);
+ }
+
+ if ($token && '-' !== $token[0]) {
+ return $this->escapeToken($token);
+ }
+
+ return $token;
+ }, $this->tokens);
+
+ return implode(' ', $tokens);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/LICENSE b/vendor/topthink/framework/src/think/console/LICENSE
new file mode 100755
index 0000000..0abe056
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2016 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/vendor/topthink/framework/src/think/console/Output.php b/vendor/topthink/framework/src/think/console/Output.php
new file mode 100755
index 0000000..294c4b8
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/Output.php
@@ -0,0 +1,231 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2020 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console;
+
+use Exception;
+use think\console\output\Ask;
+use think\console\output\Descriptor;
+use think\console\output\driver\Buffer;
+use think\console\output\driver\Console;
+use think\console\output\driver\Nothing;
+use think\console\output\Question;
+use think\console\output\question\Choice;
+use think\console\output\question\Confirmation;
+use Throwable;
+
+/**
+ * Class Output
+ * @package think\console
+ *
+ * @see \think\console\output\driver\Console::setDecorated
+ * @method void setDecorated($decorated)
+ *
+ * @see \think\console\output\driver\Buffer::fetch
+ * @method string fetch()
+ *
+ * @method void info($message)
+ * @method void error($message)
+ * @method void comment($message)
+ * @method void warning($message)
+ * @method void highlight($message)
+ * @method void question($message)
+ */
+class Output
+{
+ // 不显示信息(静默)
+ const VERBOSITY_QUIET = 0;
+ // 正常信息
+ const VERBOSITY_NORMAL = 1;
+ // 详细信息
+ const VERBOSITY_VERBOSE = 2;
+ // 非常详细的信息
+ const VERBOSITY_VERY_VERBOSE = 3;
+ // 调试信息
+ const VERBOSITY_DEBUG = 4;
+
+ const OUTPUT_NORMAL = 0;
+ const OUTPUT_RAW = 1;
+ const OUTPUT_PLAIN = 2;
+
+ // 输出信息级别
+ private $verbosity = self::VERBOSITY_NORMAL;
+
+ /** @var Buffer|Console|Nothing */
+ private $handle = null;
+
+ protected $styles = [
+ 'info',
+ 'error',
+ 'comment',
+ 'question',
+ 'highlight',
+ 'warning',
+ ];
+
+ public function __construct($driver = 'console')
+ {
+ $class = '\\think\\console\\output\\driver\\' . ucwords($driver);
+
+ $this->handle = new $class($this);
+ }
+
+ public function ask(Input $input, $question, $default = null, $validator = null)
+ {
+ $question = new Question($question, $default);
+ $question->setValidator($validator);
+
+ return $this->askQuestion($input, $question);
+ }
+
+ public function askHidden(Input $input, $question, $validator = null)
+ {
+ $question = new Question($question);
+
+ $question->setHidden(true);
+ $question->setValidator($validator);
+
+ return $this->askQuestion($input, $question);
+ }
+
+ public function confirm(Input $input, $question, $default = true)
+ {
+ return $this->askQuestion($input, new Confirmation($question, $default));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function choice(Input $input, $question, array $choices, $default = null)
+ {
+ if (null !== $default) {
+ $values = array_flip($choices);
+ $default = $values[$default];
+ }
+
+ return $this->askQuestion($input, new Choice($question, $choices, $default));
+ }
+
+ protected function askQuestion(Input $input, Question $question)
+ {
+ $ask = new Ask($input, $this, $question);
+ $answer = $ask->run();
+
+ if ($input->isInteractive()) {
+ $this->newLine();
+ }
+
+ return $answer;
+ }
+
+ protected function block(string $style, string $message): void
+ {
+ $this->writeln("<{$style}>{$message}</$style>");
+ }
+
+ /**
+ * 输出空行
+ * @param int $count
+ */
+ public function newLine(int $count = 1): void
+ {
+ $this->write(str_repeat(PHP_EOL, $count));
+ }
+
+ /**
+ * 输出信息并换行
+ * @param string $messages
+ * @param int $type
+ */
+ public function writeln(string $messages, int $type = 0): void
+ {
+ $this->write($messages, true, $type);
+ }
+
+ /**
+ * 输出信息
+ * @param string $messages
+ * @param bool $newline
+ * @param int $type
+ */
+ public function write(string $messages, bool $newline = false, int $type = 0): void
+ {
+ $this->handle->write($messages, $newline, $type);
+ }
+
+ public function renderException(Throwable $e): void
+ {
+ $this->handle->renderException($e);
+ }
+
+ /**
+ * 设置输出信息级别
+ * @param int $level 输出信息级别
+ */
+ public function setVerbosity(int $level)
+ {
+ $this->verbosity = $level;
+ }
+
+ /**
+ * 获取输出信息级别
+ * @return int
+ */
+ public function getVerbosity(): int
+ {
+ return $this->verbosity;
+ }
+
+ public function isQuiet(): bool
+ {
+ return self::VERBOSITY_QUIET === $this->verbosity;
+ }
+
+ public function isVerbose(): bool
+ {
+ return self::VERBOSITY_VERBOSE <= $this->verbosity;
+ }
+
+ public function isVeryVerbose(): bool
+ {
+ return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
+ }
+
+ public function isDebug(): bool
+ {
+ return self::VERBOSITY_DEBUG <= $this->verbosity;
+ }
+
+ public function describe($object, array $options = []): void
+ {
+ $descriptor = new Descriptor();
+ $options = array_merge([
+ 'raw_text' => false,
+ ], $options);
+
+ $descriptor->describe($this, $object, $options);
+ }
+
+ public function __call($method, $args)
+ {
+ if (in_array($method, $this->styles)) {
+ array_unshift($args, $method);
+ return call_user_func_array([$this, 'block'], $args);
+ }
+
+ if ($this->handle && method_exists($this->handle, $method)) {
+ return call_user_func_array([$this->handle, $method], $args);
+ } else {
+ throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
+ }
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/Table.php b/vendor/topthink/framework/src/think/console/Table.php
new file mode 100755
index 0000000..5a861d7
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/Table.php
@@ -0,0 +1,300 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console;
+
+class Table
+{
+ const ALIGN_LEFT = 1;
+ const ALIGN_RIGHT = 0;
+ const ALIGN_CENTER = 2;
+
+ /**
+ * 头信息数据
+ * @var array
+ */
+ protected $header = [];
+
+ /**
+ * 头部对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+ * @var int
+ */
+ protected $headerAlign = 1;
+
+ /**
+ * 表格数据(二维数组)
+ * @var array
+ */
+ protected $rows = [];
+
+ /**
+ * 单元格对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+ * @var int
+ */
+ protected $cellAlign = 1;
+
+ /**
+ * 单元格宽度信息
+ * @var array
+ */
+ protected $colWidth = [];
+
+ /**
+ * 表格输出样式
+ * @var string
+ */
+ protected $style = 'default';
+
+ /**
+ * 表格样式定义
+ * @var array
+ */
+ protected $format = [
+ 'compact' => [],
+ 'default' => [
+ 'top' => ['+', '-', '+', '+'],
+ 'cell' => ['|', ' ', '|', '|'],
+ 'middle' => ['+', '-', '+', '+'],
+ 'bottom' => ['+', '-', '+', '+'],
+ 'cross-top' => ['+', '-', '-', '+'],
+ 'cross-bottom' => ['+', '-', '-', '+'],
+ ],
+ 'markdown' => [
+ 'top' => [' ', ' ', ' ', ' '],
+ 'cell' => ['|', ' ', '|', '|'],
+ 'middle' => ['|', '-', '|', '|'],
+ 'bottom' => [' ', ' ', ' ', ' '],
+ 'cross-top' => ['|', ' ', ' ', '|'],
+ 'cross-bottom' => ['|', ' ', ' ', '|'],
+ ],
+ 'borderless' => [
+ 'top' => ['=', '=', ' ', '='],
+ 'cell' => [' ', ' ', ' ', ' '],
+ 'middle' => ['=', '=', ' ', '='],
+ 'bottom' => ['=', '=', ' ', '='],
+ 'cross-top' => ['=', '=', ' ', '='],
+ 'cross-bottom' => ['=', '=', ' ', '='],
+ ],
+ 'box' => [
+ 'top' => ['┌', '─', '┬', '┐'],
+ 'cell' => ['│', ' ', '│', '│'],
+ 'middle' => ['├', '─', '┼', '┤'],
+ 'bottom' => ['└', '─', '┴', '┘'],
+ 'cross-top' => ['├', '─', '┴', '┤'],
+ 'cross-bottom' => ['├', '─', '┬', '┤'],
+ ],
+ 'box-double' => [
+ 'top' => ['╔', '═', '╤', '╗'],
+ 'cell' => ['║', ' ', '│', '║'],
+ 'middle' => ['╠', '─', '╪', '╣'],
+ 'bottom' => ['╚', '═', '╧', '╝'],
+ 'cross-top' => ['╠', '═', '╧', '╣'],
+ 'cross-bottom' => ['╠', '═', '╤', '╣'],
+ ],
+ ];
+
+ /**
+ * 设置表格头信息 以及对齐方式
+ * @access public
+ * @param array $header 要输出的Header信息
+ * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+ * @return void
+ */
+ public function setHeader(array $header, int $align = 1): void
+ {
+ $this->header = $header;
+ $this->headerAlign = $align;
+
+ $this->checkColWidth($header);
+ }
+
+ /**
+ * 设置输出表格数据 及对齐方式
+ * @access public
+ * @param array $rows 要输出的表格数据(二维数组)
+ * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+ * @return void
+ */
+ public function setRows(array $rows, int $align = 1): void
+ {
+ $this->rows = $rows;
+ $this->cellAlign = $align;
+
+ foreach ($rows as $row) {
+ $this->checkColWidth($row);
+ }
+ }
+
+ /**
+ * 设置全局单元格对齐方式
+ * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+ * @return $this
+ */
+ public function setCellAlign(int $align = 1)
+ {
+ $this->cellAlign = $align;
+ return $this;
+ }
+
+ /**
+ * 检查列数据的显示宽度
+ * @access public
+ * @param mixed $row 行数据
+ * @return void
+ */
+ protected function checkColWidth($row): void
+ {
+ if (is_array($row)) {
+ foreach ($row as $key => $cell) {
+ $width = mb_strwidth((string) $cell);
+ if (!isset($this->colWidth[$key]) || $width > $this->colWidth[$key]) {
+ $this->colWidth[$key] = $width;
+ }
+ }
+ }
+ }
+
+ /**
+ * 增加一行表格数据
+ * @access public
+ * @param mixed $row 行数据
+ * @param bool $first 是否在开头插入
+ * @return void
+ */
+ public function addRow($row, bool $first = false): void
+ {
+ if ($first) {
+ array_unshift($this->rows, $row);
+ } else {
+ $this->rows[] = $row;
+ }
+
+ $this->checkColWidth($row);
+ }
+
+ /**
+ * 设置输出表格的样式
+ * @access public
+ * @param string $style 样式名
+ * @return void
+ */
+ public function setStyle(string $style): void
+ {
+ $this->style = isset($this->format[$style]) ? $style : 'default';
+ }
+
+ /**
+ * 输出分隔行
+ * @access public
+ * @param string $pos 位置
+ * @return string
+ */
+ protected function renderSeparator(string $pos): string
+ {
+ $style = $this->getStyle($pos);
+ $array = [];
+
+ foreach ($this->colWidth as $width) {
+ $array[] = str_repeat($style[1], $width + 2);
+ }
+
+ return $style[0] . implode($style[2], $array) . $style[3] . PHP_EOL;
+ }
+
+ /**
+ * 输出表格头部
+ * @access public
+ * @return string
+ */
+ protected function renderHeader(): string
+ {
+ $style = $this->getStyle('cell');
+ $content = $this->renderSeparator('top');
+
+ foreach ($this->header as $key => $header) {
+ $array[] = ' ' . str_pad($header, $this->colWidth[$key], $style[1], $this->headerAlign);
+ }
+
+ if (!empty($array)) {
+ $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL;
+
+ if (!empty($this->rows)) {
+ $content .= $this->renderSeparator('middle');
+ }
+ }
+
+ return $content;
+ }
+
+ protected function getStyle(string $style): array
+ {
+ if ($this->format[$this->style]) {
+ $style = $this->format[$this->style][$style];
+ } else {
+ $style = [' ', ' ', ' ', ' '];
+ }
+
+ return $style;
+ }
+
+ /**
+ * 输出表格
+ * @access public
+ * @param array $dataList 表格数据
+ * @return string
+ */
+ public function render(array $dataList = []): string
+ {
+ if (!empty($dataList)) {
+ $this->setRows($dataList);
+ }
+
+ // 输出头部
+ $content = $this->renderHeader();
+ $style = $this->getStyle('cell');
+
+ if (!empty($this->rows)) {
+ foreach ($this->rows as $row) {
+ if (is_string($row) && '-' === $row) {
+ $content .= $this->renderSeparator('middle');
+ } elseif (is_scalar($row)) {
+ $content .= $this->renderSeparator('cross-top');
+ $width = 3 * (count($this->colWidth) - 1) + array_reduce($this->colWidth, function ($a, $b) {
+ return $a + $b;
+ });
+ $array = str_pad($row, $width);
+
+ $content .= $style[0] . ' ' . $array . ' ' . $style[3] . PHP_EOL;
+ $content .= $this->renderSeparator('cross-bottom');
+ } else {
+ $array = [];
+
+ foreach ($row as $key => $val) {
+ $width = $this->colWidth[$key];
+ // form https://github.com/symfony/console/blob/20c9821c8d1c2189f287dcee709b2f86353ea08f/Helper/Table.php#L467
+ // str_pad won't work properly with multi-byte strings, we need to fix the padding
+ if (false !== $encoding = mb_detect_encoding((string) $val, null, true)) {
+ $width += strlen((string) $val) - mb_strwidth((string) $val, $encoding);
+ }
+ $array[] = ' ' . str_pad((string) $val, $width, ' ', $this->cellAlign);
+ }
+
+ $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL;
+ }
+ }
+ }
+
+ $content .= $this->renderSeparator('bottom');
+
+ return $content;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/bin/README.md b/vendor/topthink/framework/src/think/console/bin/README.md
new file mode 100755
index 0000000..9acc52f
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/bin/README.md
@@ -0,0 +1 @@
+console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。
diff --git a/vendor/topthink/framework/src/think/console/bin/hiddeninput.exe b/vendor/topthink/framework/src/think/console/bin/hiddeninput.exe
new file mode 100755
index 0000000..c8cf65e
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/bin/hiddeninput.exe
Binary files differ
diff --git a/vendor/topthink/framework/src/think/console/command/Clear.php b/vendor/topthink/framework/src/think/console/command/Clear.php
new file mode 100755
index 0000000..da70b35
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/Clear.php
@@ -0,0 +1,85 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Option;
+use think\console\Output;
+
+class Clear extends Command
+{
+ protected function configure()
+ {
+ // 指令配置
+ $this->setName('clear')
+ ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null)
+ ->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file')
+ ->addOption('log', 'l', Option::VALUE_NONE, 'clear log file')
+ ->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir')
+ ->addOption('expire', 'e', Option::VALUE_NONE, 'clear cache file if cache has expired')
+ ->setDescription('Clear runtime file');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $runtimePath = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR;
+
+ if ($input->getOption('cache')) {
+ $path = $runtimePath . 'cache';
+ } elseif ($input->getOption('log')) {
+ $path = $runtimePath . 'log';
+ } else {
+ $path = $input->getOption('path') ?: $runtimePath;
+ }
+
+ $rmdir = $input->getOption('dir') ? true : false;
+ // --expire 仅当 --cache 时生效
+ $cache_expire = $input->getOption('expire') && $input->getOption('cache') ? true : false;
+ $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir, $cache_expire);
+
+ $output->writeln("<info>Clear Successed</info>");
+ }
+
+ protected function clear(string $path, bool $rmdir, bool $cache_expire): void
+ {
+ $files = is_dir($path) ? scandir($path) : [];
+
+ foreach ($files as $file) {
+ if ('.' != $file && '..' != $file && is_dir($path . $file)) {
+ $this->clear($path . $file . DIRECTORY_SEPARATOR, $rmdir, $cache_expire);
+ if ($rmdir) {
+ @rmdir($path . $file);
+ }
+ } elseif ('.gitignore' != $file && is_file($path . $file)) {
+ if ($cache_expire) {
+ if ($this->cacheHasExpired($path . $file)) {
+ unlink($path . $file);
+ }
+ } else {
+ unlink($path . $file);
+ }
+ }
+ }
+ }
+
+ /**
+ * 缓存文件是否已过期
+ * @param $filename string 文件路径
+ * @return bool
+ */
+ protected function cacheHasExpired($filename) {
+ $content = file_get_contents($filename);
+ $expire = (int) substr($content, 8, 12);
+ return 0 != $expire && time() - $expire > filemtime($filename);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/console/command/Help.php b/vendor/topthink/framework/src/think/console/command/Help.php
new file mode 100755
index 0000000..2e4f2ca
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/Help.php
@@ -0,0 +1,70 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument as InputArgument;
+use think\console\input\Option as InputOption;
+use think\console\Output;
+
+class Help extends Command
+{
+
+ private $command;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this->ignoreValidationErrors();
+
+ $this->setName('help')->setDefinition([
+ new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
+ new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
+ ])->setDescription('Displays help for a command')->setHelp(
+ <<<EOF
+The <info>%command.name%</info> command displays help for a given command:
+
+ <info>php %command.full_name% list</info>
+
+To display the list of available commands, please use the <info>list</info> command.
+EOF
+ );
+ }
+
+ /**
+ * Sets the command.
+ * @param Command $command The command to set
+ */
+ public function setCommand(Command $command): void
+ {
+ $this->command = $command;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(Input $input, Output $output)
+ {
+ if (null === $this->command) {
+ $this->command = $this->getConsole()->find($input->getArgument('command_name'));
+ }
+
+ $output->describe($this->command, [
+ 'raw_text' => $input->getOption('raw'),
+ ]);
+
+ $this->command = null;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/Lists.php b/vendor/topthink/framework/src/think/console/command/Lists.php
new file mode 100755
index 0000000..d20fc75
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/Lists.php
@@ -0,0 +1,74 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument as InputArgument;
+use think\console\input\Definition as InputDefinition;
+use think\console\input\Option as InputOption;
+use think\console\Output;
+
+class Lists extends Command
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this->setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp(
+ <<<EOF
+The <info>%command.name%</info> command lists all commands:
+
+ <info>php %command.full_name%</info>
+
+You can also display the commands for a specific namespace:
+
+ <info>php %command.full_name% test</info>
+
+It's also possible to get raw list of commands (useful for embedding command runner):
+
+ <info>php %command.full_name% --raw</info>
+EOF
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getNativeDefinition(): InputDefinition
+ {
+ return $this->createDefinition();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(Input $input, Output $output)
+ {
+ $output->describe($this->getConsole(), [
+ 'raw_text' => $input->getOption('raw'),
+ 'namespace' => $input->getArgument('namespace'),
+ ]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ private function createDefinition(): InputDefinition
+ {
+ return new InputDefinition([
+ new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
+ new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
+ ]);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/Make.php b/vendor/topthink/framework/src/think/console/command/Make.php
new file mode 100755
index 0000000..a74e9e8
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/Make.php
@@ -0,0 +1,99 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 刘志淳 <chun@engineer.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\Output;
+
+abstract class Make extends Command
+{
+ protected $type;
+
+ abstract protected function getStub();
+
+ protected function configure()
+ {
+ $this->addArgument('name', Argument::REQUIRED, "The name of the class");
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $name = trim($input->getArgument('name'));
+
+ $classname = $this->getClassName($name);
+
+ $pathname = $this->getPathName($classname);
+
+ if (is_file($pathname)) {
+ $output->writeln('<error>' . $this->type . ':' . $classname . ' already exists!</error>');
+ return false;
+ }
+
+ if (!is_dir(dirname($pathname))) {
+ mkdir(dirname($pathname), 0755, true);
+ }
+
+ file_put_contents($pathname, $this->buildClass($classname));
+
+ $output->writeln('<info>' . $this->type . ':' . $classname . ' created successfully.</info>');
+ }
+
+ protected function buildClass(string $name)
+ {
+ $stub = file_get_contents($this->getStub());
+
+ $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
+
+ $class = str_replace($namespace . '\\', '', $name);
+
+ return str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [
+ $class,
+ $this->app->config->get('route.action_suffix'),
+ $namespace,
+ $this->app->getNamespace(),
+ ], $stub);
+ }
+
+ protected function getPathName(string $name): string
+ {
+ $name = substr($name, 4);
+
+ return $this->app->getBasePath() . ltrim(str_replace('\\', '/', $name), '/') . '.php';
+ }
+
+ protected function getClassName(string $name): string
+ {
+ if (strpos($name, '\\') !== false) {
+ return $name;
+ }
+
+ if (strpos($name, '@')) {
+ [$app, $name] = explode('@', $name);
+ } else {
+ $app = '';
+ }
+
+ if (strpos($name, '/') !== false) {
+ $name = str_replace('/', '\\', $name);
+ }
+
+ return $this->getNamespace($app) . '\\' . $name;
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return 'app' . ($app ? '\\' . $app : '');
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/console/command/RouteList.php b/vendor/topthink/framework/src/think/console/command/RouteList.php
new file mode 100755
index 0000000..e9d4c5d
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/RouteList.php
@@ -0,0 +1,128 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\input\Option;
+use think\console\Output;
+use think\console\Table;
+use think\event\RouteLoaded;
+
+class RouteList extends Command
+{
+ protected $sortBy = [
+ 'rule' => 0,
+ 'route' => 1,
+ 'method' => 2,
+ 'name' => 3,
+ 'domain' => 4,
+ ];
+
+ protected function configure()
+ {
+ $this->setName('route:list')
+ ->addArgument('dir', Argument::OPTIONAL, 'dir name .')
+ ->addArgument('style', Argument::OPTIONAL, "the style of the table.", 'default')
+ ->addOption('sort', 's', Option::VALUE_OPTIONAL, 'order by rule name.', 0)
+ ->addOption('more', 'm', Option::VALUE_NONE, 'show route options.')
+ ->setDescription('show route list.');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $dir = $input->getArgument('dir') ?: '';
+
+ $filename = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '') . 'route_list.php';
+
+ if (is_file($filename)) {
+ unlink($filename);
+ } elseif (!is_dir(dirname($filename))) {
+ mkdir(dirname($filename), 0755);
+ }
+
+ $content = $this->getRouteList($dir);
+ file_put_contents($filename, 'Route List' . PHP_EOL . $content);
+ }
+
+ protected function getRouteList(string $dir = null): string
+ {
+ $this->app->route->setTestMode(true);
+ $this->app->route->clear();
+
+ if ($dir) {
+ $path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR;
+ } else {
+ $path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR;
+ }
+
+ $files = is_dir($path) ? scandir($path) : [];
+
+ foreach ($files as $file) {
+ if (strpos($file, '.php')) {
+ include $path . $file;
+ }
+ }
+
+ //触发路由载入完成事件
+ $this->app->event->trigger(RouteLoaded::class);
+
+ $table = new Table();
+
+ if ($this->input->hasOption('more')) {
+ $header = ['Rule', 'Route', 'Method', 'Name', 'Domain', 'Option', 'Pattern'];
+ } else {
+ $header = ['Rule', 'Route', 'Method', 'Name'];
+ }
+
+ $table->setHeader($header);
+
+ $routeList = $this->app->route->getRuleList();
+ $rows = [];
+
+ foreach ($routeList as $item) {
+ $item['route'] = $item['route'] instanceof \Closure ? '<Closure>' : $item['route'];
+ $row = [$item['rule'], $item['route'], $item['method'], $item['name']];
+
+ if ($this->input->hasOption('more')) {
+ array_push($row, $item['domain'], json_encode($item['option']), json_encode($item['pattern']));
+ }
+
+ $rows[] = $row;
+ }
+
+ if ($this->input->getOption('sort')) {
+ $sort = strtolower($this->input->getOption('sort'));
+
+ if (isset($this->sortBy[$sort])) {
+ $sort = $this->sortBy[$sort];
+ }
+
+ uasort($rows, function ($a, $b) use ($sort) {
+ $itemA = $a[$sort] ?? null;
+ $itemB = $b[$sort] ?? null;
+
+ return strcasecmp($itemA, $itemB);
+ });
+ }
+
+ $table->setRows($rows);
+
+ if ($this->input->getArgument('style')) {
+ $style = $this->input->getArgument('style');
+ $table->setStyle($style);
+ }
+
+ return $this->table($table);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/console/command/RunServer.php b/vendor/topthink/framework/src/think/console/command/RunServer.php
new file mode 100755
index 0000000..d507c1d
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/RunServer.php
@@ -0,0 +1,72 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: Slince <taosikai@yeah.net>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Option;
+use think\console\Output;
+
+class RunServer extends Command
+{
+ public function configure()
+ {
+ $this->setName('run')
+ ->addOption(
+ 'host',
+ 'H',
+ Option::VALUE_OPTIONAL,
+ 'The host to server the application on',
+ '0.0.0.0'
+ )
+ ->addOption(
+ 'port',
+ 'p',
+ Option::VALUE_OPTIONAL,
+ 'The port to server the application on',
+ 8000
+ )
+ ->addOption(
+ 'root',
+ 'r',
+ Option::VALUE_OPTIONAL,
+ 'The document root of the application',
+ ''
+ )
+ ->setDescription('PHP Built-in Server for ThinkPHP');
+ }
+
+ public function execute(Input $input, Output $output)
+ {
+ $host = $input->getOption('host');
+ $port = $input->getOption('port');
+ $root = $input->getOption('root');
+ if (empty($root)) {
+ $root = $this->app->getRootPath() . 'public';
+ }
+
+ $command = sprintf(
+ 'php -S %s:%d -t %s %s',
+ $host,
+ $port,
+ escapeshellarg($root),
+ escapeshellarg($root . DIRECTORY_SEPARATOR . 'router.php')
+ );
+
+ $output->writeln(sprintf('ThinkPHP Development server is started On <http://%s:%s/>', $host, $port));
+ $output->writeln(sprintf('You can exit with <info>`CTRL-C`</info>'));
+ $output->writeln(sprintf('Document root is: %s', $root));
+ passthru($command);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php b/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php
new file mode 100755
index 0000000..e90f433
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php
@@ -0,0 +1,52 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+
+class ServiceDiscover extends Command
+{
+ public function configure()
+ {
+ $this->setName('service:discover')
+ ->setDescription('Discover Services for ThinkPHP');
+ }
+
+ public function execute(Input $input, Output $output)
+ {
+ if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) {
+ $packages = json_decode(@file_get_contents($path), true);
+ // Compatibility with Composer 2.0
+ if (isset($packages['packages'])) {
+ $packages = $packages['packages'];
+ }
+
+ $services = [];
+ foreach ($packages as $package) {
+ if (!empty($package['extra']['think']['services'])) {
+ $services = array_merge($services, (array) $package['extra']['think']['services']);
+ }
+ }
+
+ $header = '// This file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL . 'declare (strict_types = 1);' . PHP_EOL;
+
+ $content = '<?php ' . PHP_EOL . $header . "return " . var_export($services, true) . ';';
+
+ file_put_contents($this->app->getRootPath() . 'vendor/services.php', $content);
+
+ $output->writeln('<info>Succeed!</info>');
+ }
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/VendorPublish.php b/vendor/topthink/framework/src/think/console/command/VendorPublish.php
new file mode 100755
index 0000000..3998765
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/VendorPublish.php
@@ -0,0 +1,69 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\input\Option;
+
+class VendorPublish extends Command
+{
+ public function configure()
+ {
+ $this->setName('vendor:publish')
+ ->addOption('force', 'f', Option::VALUE_NONE, 'Overwrite any existing files')
+ ->setDescription('Publish any publishable assets from vendor packages');
+ }
+
+ public function handle()
+ {
+
+ $force = $this->input->getOption('force');
+
+ if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) {
+ $packages = json_decode(@file_get_contents($path), true);
+ // Compatibility with Composer 2.0
+ if (isset($packages['packages'])) {
+ $packages = $packages['packages'];
+ }
+ foreach ($packages as $package) {
+ //配置
+ $configDir = $this->app->getConfigPath();
+
+ if (!empty($package['extra']['think']['config'])) {
+
+ $installPath = $this->app->getRootPath() . 'vendor/' . $package['name'] . DIRECTORY_SEPARATOR;
+
+ foreach ((array) $package['extra']['think']['config'] as $name => $file) {
+
+ $target = $configDir . $name . '.php';
+ $source = $installPath . $file;
+
+ if (is_file($target) && !$force) {
+ $this->output->info("File {$target} exist!");
+ continue;
+ }
+
+ if (!is_file($source)) {
+ $this->output->info("File {$source} not exist!");
+ continue;
+ }
+
+ copy($source, $target);
+ }
+ }
+ }
+
+ $this->output->writeln('<info>Succeed!</info>');
+ }
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/Version.php b/vendor/topthink/framework/src/think/console/command/Version.php
new file mode 100755
index 0000000..beb49d2
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/Version.php
@@ -0,0 +1,33 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+
+class Version extends Command
+{
+ protected function configure()
+ {
+ // 指令配置
+ $this->setName('version')
+ ->setDescription('show thinkphp framework version');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $output->writeln('v' . $this->app->version());
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/Command.php b/vendor/topthink/framework/src/think/console/command/make/Command.php
new file mode 100755
index 0000000..9549a02
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/Command.php
@@ -0,0 +1,55 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command\make;
+
+use think\console\command\Make;
+use think\console\input\Argument;
+
+class Command extends Make
+{
+ protected $type = "Command";
+
+ protected function configure()
+ {
+ parent::configure();
+ $this->setName('make:command')
+ ->addArgument('commandName', Argument::OPTIONAL, "The name of the command")
+ ->setDescription('Create a new command class');
+ }
+
+ protected function buildClass(string $name): string
+ {
+ $commandName = $this->input->getArgument('commandName') ?: strtolower(basename($name));
+ $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
+
+ $class = str_replace($namespace . '\\', '', $name);
+ $stub = file_get_contents($this->getStub());
+
+ return str_replace(['{%commandName%}', '{%className%}', '{%namespace%}', '{%app_namespace%}'], [
+ $commandName,
+ $class,
+ $namespace,
+ $this->app->getNamespace(),
+ ], $stub);
+ }
+
+ protected function getStub(): string
+ {
+ return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'command.stub';
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return parent::getNamespace($app) . '\\command';
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/Controller.php b/vendor/topthink/framework/src/think/console/command/make/Controller.php
new file mode 100755
index 0000000..4a8d226
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/Controller.php
@@ -0,0 +1,56 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command\make;
+
+use think\console\command\Make;
+use think\console\input\Option;
+
+class Controller extends Make
+{
+
+ protected $type = "Controller";
+
+ protected function configure()
+ {
+ parent::configure();
+ $this->setName('make:controller')
+ ->addOption('api', null, Option::VALUE_NONE, 'Generate an api controller class.')
+ ->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.')
+ ->setDescription('Create a new resource controller class');
+ }
+
+ protected function getStub(): string
+ {
+ $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR;
+
+ if ($this->input->getOption('api')) {
+ return $stubPath . 'controller.api.stub';
+ }
+
+ if ($this->input->getOption('plain')) {
+ return $stubPath . 'controller.plain.stub';
+ }
+
+ return $stubPath . 'controller.stub';
+ }
+
+ protected function getClassName(string $name): string
+ {
+ return parent::getClassName($name) . ($this->app->config->get('route.controller_suffix') ? 'Controller' : '');
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return parent::getNamespace($app) . '\\controller';
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/Event.php b/vendor/topthink/framework/src/think/console/command/make/Event.php
new file mode 100755
index 0000000..6b16689
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/Event.php
@@ -0,0 +1,35 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Event extends Make
+{
+ protected $type = "Event";
+
+ protected function configure()
+ {
+ parent::configure();
+ $this->setName('make:event')
+ ->setDescription('Create a new event class');
+ }
+
+ protected function getStub(): string
+ {
+ return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'event.stub';
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return parent::getNamespace($app) . '\\event';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/Listener.php b/vendor/topthink/framework/src/think/console/command/make/Listener.php
new file mode 100755
index 0000000..5c92673
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/Listener.php
@@ -0,0 +1,35 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Listener extends Make
+{
+ protected $type = "Listener";
+
+ protected function configure()
+ {
+ parent::configure();
+ $this->setName('make:listener')
+ ->setDescription('Create a new listener class');
+ }
+
+ protected function getStub(): string
+ {
+ return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'listener.stub';
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return parent::getNamespace($app) . '\\listener';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/Middleware.php b/vendor/topthink/framework/src/think/console/command/make/Middleware.php
new file mode 100755
index 0000000..3b68b4a
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/Middleware.php
@@ -0,0 +1,36 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Middleware extends Make
+{
+ protected $type = "Middleware";
+
+ protected function configure()
+ {
+ parent::configure();
+ $this->setName('make:middleware')
+ ->setDescription('Create a new middleware class');
+ }
+
+ protected function getStub(): string
+ {
+ return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'middleware.stub';
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return parent::getNamespace($app) . '\\middleware';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/Model.php b/vendor/topthink/framework/src/think/console/command/make/Model.php
new file mode 100755
index 0000000..cb7a23c
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/Model.php
@@ -0,0 +1,36 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Model extends Make
+{
+ protected $type = "Model";
+
+ protected function configure()
+ {
+ parent::configure();
+ $this->setName('make:model')
+ ->setDescription('Create a new model class');
+ }
+
+ protected function getStub(): string
+ {
+ return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub';
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return parent::getNamespace($app) . '\\model';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/Service.php b/vendor/topthink/framework/src/think/console/command/make/Service.php
new file mode 100755
index 0000000..c4bbaa0
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/Service.php
@@ -0,0 +1,36 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Service extends Make
+{
+ protected $type = "Service";
+
+ protected function configure()
+ {
+ parent::configure();
+ $this->setName('make:service')
+ ->setDescription('Create a new Service class');
+ }
+
+ protected function getStub(): string
+ {
+ return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'service.stub';
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return parent::getNamespace($app) . '\\service';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/Subscribe.php b/vendor/topthink/framework/src/think/console/command/make/Subscribe.php
new file mode 100755
index 0000000..a1dc2a8
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/Subscribe.php
@@ -0,0 +1,35 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Subscribe extends Make
+{
+ protected $type = "Subscribe";
+
+ protected function configure()
+ {
+ parent::configure();
+ $this->setName('make:subscribe')
+ ->setDescription('Create a new subscribe class');
+ }
+
+ protected function getStub(): string
+ {
+ return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'subscribe.stub';
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return parent::getNamespace($app) . '\\subscribe';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/Validate.php b/vendor/topthink/framework/src/think/console/command/make/Validate.php
new file mode 100755
index 0000000..8d36431
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/Validate.php
@@ -0,0 +1,39 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Validate extends Make
+{
+ protected $type = "Validate";
+
+ protected function configure()
+ {
+ parent::configure();
+ $this->setName('make:validate')
+ ->setDescription('Create a validate class');
+ }
+
+ protected function getStub(): string
+ {
+ $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR;
+
+ return $stubPath . 'validate.stub';
+ }
+
+ protected function getNamespace(string $app): string
+ {
+ return parent::getNamespace($app) . '\\validate';
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub
new file mode 100755
index 0000000..3ee2b1c
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub
@@ -0,0 +1,26 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\input\Option;
+use think\console\Output;
+
+class {%className%} extends Command
+{
+ protected function configure()
+ {
+ // 指令配置
+ $this->setName('{%commandName%}')
+ ->setDescription('the {%commandName%} command');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ // 指令输出
+ $output->writeln('{%commandName%}');
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub
new file mode 100755
index 0000000..5d3383d
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub
@@ -0,0 +1,64 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+use think\Request;
+
+class {%className%}
+{
+ /**
+ * 显示资源列表
+ *
+ * @return \think\Response
+ */
+ public function index{%actionSuffix%}()
+ {
+ //
+ }
+
+ /**
+ * 保存新建的资源
+ *
+ * @param \think\Request $request
+ * @return \think\Response
+ */
+ public function save{%actionSuffix%}(Request $request)
+ {
+ //
+ }
+
+ /**
+ * 显示指定的资源
+ *
+ * @param int $id
+ * @return \think\Response
+ */
+ public function read{%actionSuffix%}($id)
+ {
+ //
+ }
+
+ /**
+ * 保存更新的资源
+ *
+ * @param \think\Request $request
+ * @param int $id
+ * @return \think\Response
+ */
+ public function update{%actionSuffix%}(Request $request, $id)
+ {
+ //
+ }
+
+ /**
+ * 删除指定资源
+ *
+ * @param int $id
+ * @return \think\Response
+ */
+ public function delete{%actionSuffix%}($id)
+ {
+ //
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/controller.plain.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.plain.stub
new file mode 100755
index 0000000..347c209
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.plain.stub
@@ -0,0 +1,9 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+class {%className%}
+{
+ //
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/controller.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.stub
new file mode 100755
index 0000000..8e5d82d
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.stub
@@ -0,0 +1,85 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+use think\Request;
+
+class {%className%}
+{
+ /**
+ * 显示资源列表
+ *
+ * @return \think\Response
+ */
+ public function index{%actionSuffix%}()
+ {
+ //
+ }
+
+ /**
+ * 显示创建资源表单页.
+ *
+ * @return \think\Response
+ */
+ public function create{%actionSuffix%}()
+ {
+ //
+ }
+
+ /**
+ * 保存新建的资源
+ *
+ * @param \think\Request $request
+ * @return \think\Response
+ */
+ public function save{%actionSuffix%}(Request $request)
+ {
+ //
+ }
+
+ /**
+ * 显示指定的资源
+ *
+ * @param int $id
+ * @return \think\Response
+ */
+ public function read{%actionSuffix%}($id)
+ {
+ //
+ }
+
+ /**
+ * 显示编辑资源表单页.
+ *
+ * @param int $id
+ * @return \think\Response
+ */
+ public function edit{%actionSuffix%}($id)
+ {
+ //
+ }
+
+ /**
+ * 保存更新的资源
+ *
+ * @param \think\Request $request
+ * @param int $id
+ * @return \think\Response
+ */
+ public function update{%actionSuffix%}(Request $request, $id)
+ {
+ //
+ }
+
+ /**
+ * 删除指定资源
+ *
+ * @param int $id
+ * @return \think\Response
+ */
+ public function delete{%actionSuffix%}($id)
+ {
+ //
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/event.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/event.stub
new file mode 100755
index 0000000..a173089
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/stubs/event.stub
@@ -0,0 +1,8 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+class {%className%}
+{
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/listener.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/listener.stub
new file mode 100755
index 0000000..0372438
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/stubs/listener.stub
@@ -0,0 +1,17 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+class {%className%}
+{
+ /**
+ * 事件监听处理
+ *
+ * @return mixed
+ */
+ public function handle($event)
+ {
+ //
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/middleware.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/middleware.stub
new file mode 100755
index 0000000..38cf5f2
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/stubs/middleware.stub
@@ -0,0 +1,19 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+class {%className%}
+{
+ /**
+ * 处理请求
+ *
+ * @param \think\Request $request
+ * @param \Closure $next
+ * @return Response
+ */
+ public function handle($request, \Closure $next)
+ {
+ //
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/model.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/model.stub
new file mode 100755
index 0000000..bb94a38
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/stubs/model.stub
@@ -0,0 +1,14 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+use think\Model;
+
+/**
+ * @mixin \think\Model
+ */
+class {%className%} extends Model
+{
+ //
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/service.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/service.stub
new file mode 100755
index 0000000..5d74d15
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/stubs/service.stub
@@ -0,0 +1,27 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+class {%className%} extends \think\Service
+{
+ /**
+ * 注册服务
+ *
+ * @return mixed
+ */
+ public function register()
+ {
+ //
+ }
+
+ /**
+ * 执行服务
+ *
+ * @return mixed
+ */
+ public function boot()
+ {
+ //
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/subscribe.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/subscribe.stub
new file mode 100755
index 0000000..a173089
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/stubs/subscribe.stub
@@ -0,0 +1,8 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+class {%className%}
+{
+}
diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/validate.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/validate.stub
new file mode 100755
index 0000000..d1fedd8
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/make/stubs/validate.stub
@@ -0,0 +1,25 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+use think\Validate;
+
+class {%className%} extends Validate
+{
+ /**
+ * 定义验证规则
+ * 格式:'字段名' => ['规则1','规则2'...]
+ *
+ * @var array
+ */
+ protected $rule = [];
+
+ /**
+ * 定义错误信息
+ * 格式:'字段名.规则名' => '错误信息'
+ *
+ * @var array
+ */
+ protected $message = [];
+}
diff --git a/vendor/topthink/framework/src/think/console/command/optimize/Route.php b/vendor/topthink/framework/src/think/console/command/optimize/Route.php
new file mode 100755
index 0000000..56f7f5a
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/optimize/Route.php
@@ -0,0 +1,66 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\console\command\optimize;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\Output;
+use think\event\RouteLoaded;
+
+class Route extends Command
+{
+ protected function configure()
+ {
+ $this->setName('optimize:route')
+ ->addArgument('dir', Argument::OPTIONAL, 'dir name .')
+ ->setDescription('Build app route cache.');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $dir = $input->getArgument('dir') ?: '';
+
+ $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '');
+
+ $filename = $path . 'route.php';
+ if (is_file($filename)) {
+ unlink($filename);
+ }
+
+ file_put_contents($filename, $this->buildRouteCache($dir));
+ $output->writeln('<info>Succeed!</info>');
+ }
+
+ protected function buildRouteCache(string $dir = null): string
+ {
+ $this->app->route->clear();
+ $this->app->route->lazy(false);
+
+ // 路由检测
+ $path = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'route' . DIRECTORY_SEPARATOR;
+
+ $files = is_dir($path) ? scandir($path) : [];
+
+ foreach ($files as $file) {
+ if (strpos($file, '.php')) {
+ include $path . $file;
+ }
+ }
+
+ //触发路由载入完成事件
+ $this->app->event->trigger(RouteLoaded::class);
+ $rules = $this->app->route->getName();
+
+ return '<?php ' . PHP_EOL . 'return unserialize(\'' . serialize($rules) . '\');';
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/console/command/optimize/Schema.php b/vendor/topthink/framework/src/think/console/command/optimize/Schema.php
new file mode 100755
index 0000000..6ce3e64
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/command/optimize/Schema.php
@@ -0,0 +1,104 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\console\command\optimize;
+
+use Exception;
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\input\Option;
+use think\console\Output;
+use think\db\PDOConnection;
+
+class Schema extends Command
+{
+ protected function configure()
+ {
+ $this->setName('optimize:schema')
+ ->addArgument('dir', Argument::OPTIONAL, 'dir name .')
+ ->addOption('connection', null, Option::VALUE_REQUIRED, 'connection name .')
+ ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .')
+ ->setDescription('Build database schema cache.');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $dir = $input->getArgument('dir') ?: '';
+
+ if ($input->hasOption('table')) {
+ $connection = $this->app->db->connect($input->getOption('connection'));
+ if (!$connection instanceof PDOConnection) {
+ $output->error("only PDO connection support schema cache!");
+ return;
+ }
+ $table = $input->getOption('table');
+ if (false === strpos($table, '.')) {
+ $dbName = $connection->getConfig('database');
+ } else {
+ [$dbName, $table] = explode('.', $table);
+ }
+
+ if ($table == '*') {
+ $table = $connection->getTables($dbName);
+ }
+
+ $this->buildDataBaseSchema($connection, (array) $table, $dbName);
+ } else {
+ if ($dir) {
+ $appPath = $this->app->getBasePath() . $dir . DIRECTORY_SEPARATOR;
+ $namespace = 'app\\' . $dir;
+ } else {
+ $appPath = $this->app->getBasePath();
+ $namespace = 'app';
+ }
+
+ $path = $appPath . 'model';
+ $list = is_dir($path) ? scandir($path) : [];
+
+ foreach ($list as $file) {
+ if (0 === strpos($file, '.')) {
+ continue;
+ }
+ $class = '\\' . $namespace . '\\model\\' . pathinfo($file, PATHINFO_FILENAME);
+ $this->buildModelSchema($class);
+ }
+ }
+
+ $output->writeln('<info>Succeed!</info>');
+ }
+
+ protected function buildModelSchema(string $class): void
+ {
+ $reflect = new \ReflectionClass($class);
+ if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) {
+ try {
+ /** @var \think\Model $model */
+ $model = new $class;
+ $connection = $model->db()->getConnection();
+ if ($connection instanceof PDOConnection) {
+ $table = $model->getTable();
+ //预读字段信息
+ $connection->getSchemaInfo($table, true);
+ }
+ } catch (Exception $e) {
+
+ }
+ }
+ }
+
+ protected function buildDataBaseSchema(PDOConnection $connection, array $tables, string $dbName): void
+ {
+ foreach ($tables as $table) {
+ //预读字段信息
+ $connection->getSchemaInfo("{$dbName}.{$table}", true);
+ }
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/input/Argument.php b/vendor/topthink/framework/src/think/console/input/Argument.php
new file mode 100755
index 0000000..86cca36
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/input/Argument.php
@@ -0,0 +1,138 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\input;
+
+class Argument
+{
+ // 必传参数
+ const REQUIRED = 1;
+
+ // 可选参数
+ const OPTIONAL = 2;
+
+ // 数组参数
+ const IS_ARRAY = 4;
+
+ /**
+ * 参数名
+ * @var string
+ */
+ private $name;
+
+ /**
+ * 参数类型
+ * @var int
+ */
+ private $mode;
+
+ /**
+ * 参数默认值
+ * @var mixed
+ */
+ private $default;
+
+ /**
+ * 参数描述
+ * @var string
+ */
+ private $description;
+
+ /**
+ * 构造方法
+ * @param string $name 参数名
+ * @param int $mode 参数类型: self::REQUIRED 或者 self::OPTIONAL
+ * @param string $description 描述
+ * @param mixed $default 默认值 (仅 self::OPTIONAL 类型有效)
+ * @throws \InvalidArgumentException
+ */
+ public function __construct(string $name, int $mode = null, string $description = '', $default = null)
+ {
+ if (null === $mode) {
+ $mode = self::OPTIONAL;
+ } elseif (!is_int($mode) || $mode > 7 || $mode < 1) {
+ throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
+ }
+
+ $this->name = $name;
+ $this->mode = $mode;
+ $this->description = $description;
+
+ $this->setDefault($default);
+ }
+
+ /**
+ * 获取参数名
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * 是否必须
+ * @return bool
+ */
+ public function isRequired(): bool
+ {
+ return self::REQUIRED === (self::REQUIRED & $this->mode);
+ }
+
+ /**
+ * 该参数是否接受数组
+ * @return bool
+ */
+ public function isArray(): bool
+ {
+ return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
+ }
+
+ /**
+ * 设置默认值
+ * @param mixed $default 默认值
+ * @throws \LogicException
+ */
+ public function setDefault($default = null): void
+ {
+ if (self::REQUIRED === $this->mode && null !== $default) {
+ throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
+ }
+
+ if ($this->isArray()) {
+ if (null === $default) {
+ $default = [];
+ } elseif (!is_array($default)) {
+ throw new \LogicException('A default value for an array argument must be an array.');
+ }
+ }
+
+ $this->default = $default;
+ }
+
+ /**
+ * 获取默认值
+ * @return mixed
+ */
+ public function getDefault()
+ {
+ return $this->default;
+ }
+
+ /**
+ * 获取描述
+ * @return string
+ */
+ public function getDescription(): string
+ {
+ return $this->description;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/input/Definition.php b/vendor/topthink/framework/src/think/console/input/Definition.php
new file mode 100755
index 0000000..ccf02a0
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/input/Definition.php
@@ -0,0 +1,375 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\input;
+
+class Definition
+{
+
+ /**
+ * @var Argument[]
+ */
+ private $arguments;
+
+ private $requiredCount;
+ private $hasAnArrayArgument = false;
+ private $hasOptional;
+
+ /**
+ * @var Option[]
+ */
+ private $options;
+ private $shortcuts;
+
+ /**
+ * 构造方法
+ * @param array $definition
+ * @api
+ */
+ public function __construct(array $definition = [])
+ {
+ $this->setDefinition($definition);
+ }
+
+ /**
+ * 设置指令的定义
+ * @param array $definition 定义的数组
+ */
+ public function setDefinition(array $definition): void
+ {
+ $arguments = [];
+ $options = [];
+ foreach ($definition as $item) {
+ if ($item instanceof Option) {
+ $options[] = $item;
+ } else {
+ $arguments[] = $item;
+ }
+ }
+
+ $this->setArguments($arguments);
+ $this->setOptions($options);
+ }
+
+ /**
+ * 设置参数
+ * @param Argument[] $arguments 参数数组
+ */
+ public function setArguments(array $arguments = []): void
+ {
+ $this->arguments = [];
+ $this->requiredCount = 0;
+ $this->hasOptional = false;
+ $this->hasAnArrayArgument = false;
+ $this->addArguments($arguments);
+ }
+
+ /**
+ * 添加参数
+ * @param Argument[] $arguments 参数数组
+ * @api
+ */
+ public function addArguments(array $arguments = []): void
+ {
+ if (null !== $arguments) {
+ foreach ($arguments as $argument) {
+ $this->addArgument($argument);
+ }
+ }
+ }
+
+ /**
+ * 添加一个参数
+ * @param Argument $argument 参数
+ * @throws \LogicException
+ */
+ public function addArgument(Argument $argument): void
+ {
+ if (isset($this->arguments[$argument->getName()])) {
+ throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName()));
+ }
+
+ if ($this->hasAnArrayArgument) {
+ throw new \LogicException('Cannot add an argument after an array argument.');
+ }
+
+ if ($argument->isRequired() && $this->hasOptional) {
+ throw new \LogicException('Cannot add a required argument after an optional one.');
+ }
+
+ if ($argument->isArray()) {
+ $this->hasAnArrayArgument = true;
+ }
+
+ if ($argument->isRequired()) {
+ ++$this->requiredCount;
+ } else {
+ $this->hasOptional = true;
+ }
+
+ $this->arguments[$argument->getName()] = $argument;
+ }
+
+ /**
+ * 根据名称或者位置获取参数
+ * @param string|int $name 参数名或者位置
+ * @return Argument 参数
+ * @throws \InvalidArgumentException
+ */
+ public function getArgument($name): Argument
+ {
+ if (!$this->hasArgument($name)) {
+ throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
+ }
+
+ $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
+
+ return $arguments[$name];
+ }
+
+ /**
+ * 根据名称或位置检查是否具有某个参数
+ * @param string|int $name 参数名或者位置
+ * @return bool
+ * @api
+ */
+ public function hasArgument($name): bool
+ {
+ $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
+
+ return isset($arguments[$name]);
+ }
+
+ /**
+ * 获取所有的参数
+ * @return Argument[] 参数数组
+ */
+ public function getArguments(): array
+ {
+ return $this->arguments;
+ }
+
+ /**
+ * 获取参数数量
+ * @return int
+ */
+ public function getArgumentCount(): int
+ {
+ return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments);
+ }
+
+ /**
+ * 获取必填的参数的数量
+ * @return int
+ */
+ public function getArgumentRequiredCount(): int
+ {
+ return $this->requiredCount;
+ }
+
+ /**
+ * 获取参数默认值
+ * @return array
+ */
+ public function getArgumentDefaults(): array
+ {
+ $values = [];
+ foreach ($this->arguments as $argument) {
+ $values[$argument->getName()] = $argument->getDefault();
+ }
+
+ return $values;
+ }
+
+ /**
+ * 设置选项
+ * @param Option[] $options 选项数组
+ */
+ public function setOptions(array $options = []): void
+ {
+ $this->options = [];
+ $this->shortcuts = [];
+ $this->addOptions($options);
+ }
+
+ /**
+ * 添加选项
+ * @param Option[] $options 选项数组
+ * @api
+ */
+ public function addOptions(array $options = []): void
+ {
+ foreach ($options as $option) {
+ $this->addOption($option);
+ }
+ }
+
+ /**
+ * 添加一个选项
+ * @param Option $option 选项
+ * @throws \LogicException
+ * @api
+ */
+ public function addOption(Option $option): void
+ {
+ if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
+ throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
+ }
+
+ if ($option->getShortcut()) {
+ foreach (explode('|', $option->getShortcut()) as $shortcut) {
+ if (isset($this->shortcuts[$shortcut])
+ && !$option->equals($this->options[$this->shortcuts[$shortcut]])
+ ) {
+ throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut));
+ }
+ }
+ }
+
+ $this->options[$option->getName()] = $option;
+ if ($option->getShortcut()) {
+ foreach (explode('|', $option->getShortcut()) as $shortcut) {
+ $this->shortcuts[$shortcut] = $option->getName();
+ }
+ }
+ }
+
+ /**
+ * 根据名称获取选项
+ * @param string $name 选项名
+ * @return Option
+ * @throws \InvalidArgumentException
+ * @api
+ */
+ public function getOption(string $name): Option
+ {
+ if (!$this->hasOption($name)) {
+ throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
+ }
+
+ return $this->options[$name];
+ }
+
+ /**
+ * 根据名称检查是否有这个选项
+ * @param string $name 选项名
+ * @return bool
+ * @api
+ */
+ public function hasOption(string $name): bool
+ {
+ return isset($this->options[$name]);
+ }
+
+ /**
+ * 获取所有选项
+ * @return Option[]
+ * @api
+ */
+ public function getOptions(): array
+ {
+ return $this->options;
+ }
+
+ /**
+ * 根据名称检查某个选项是否有短名称
+ * @param string $name 短名称
+ * @return bool
+ */
+ public function hasShortcut(string $name): bool
+ {
+ return isset($this->shortcuts[$name]);
+ }
+
+ /**
+ * 根据短名称获取选项
+ * @param string $shortcut 短名称
+ * @return Option
+ */
+ public function getOptionForShortcut(string $shortcut): Option
+ {
+ return $this->getOption($this->shortcutToName($shortcut));
+ }
+
+ /**
+ * 获取所有选项的默认值
+ * @return array
+ */
+ public function getOptionDefaults(): array
+ {
+ $values = [];
+ foreach ($this->options as $option) {
+ $values[$option->getName()] = $option->getDefault();
+ }
+
+ return $values;
+ }
+
+ /**
+ * 根据短名称获取选项名
+ * @param string $shortcut 短名称
+ * @return string
+ * @throws \InvalidArgumentException
+ */
+ private function shortcutToName(string $shortcut): string
+ {
+ if (!isset($this->shortcuts[$shortcut])) {
+ throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
+ }
+
+ return $this->shortcuts[$shortcut];
+ }
+
+ /**
+ * 获取该指令的介绍
+ * @param bool $short 是否简洁介绍
+ * @return string
+ */
+ public function getSynopsis(bool $short = false): string
+ {
+ $elements = [];
+
+ if ($short && $this->getOptions()) {
+ $elements[] = '[options]';
+ } elseif (!$short) {
+ foreach ($this->getOptions() as $option) {
+ $value = '';
+ if ($option->acceptValue()) {
+ $value = sprintf(' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : '');
+ }
+
+ $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
+ $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value);
+ }
+ }
+
+ if (count($elements) && $this->getArguments()) {
+ $elements[] = '[--]';
+ }
+
+ foreach ($this->getArguments() as $argument) {
+ $element = '<' . $argument->getName() . '>';
+ if (!$argument->isRequired()) {
+ $element = '[' . $element . ']';
+ } elseif ($argument->isArray()) {
+ $element .= ' (' . $element . ')';
+ }
+
+ if ($argument->isArray()) {
+ $element .= '...';
+ }
+
+ $elements[] = $element;
+ }
+
+ return implode(' ', $elements);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/input/Option.php b/vendor/topthink/framework/src/think/console/input/Option.php
new file mode 100755
index 0000000..d7e6514
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/input/Option.php
@@ -0,0 +1,221 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\input;
+
+/**
+ * 命令行选项
+ * @package think\console\input
+ */
+class Option
+{
+ // 无需传值
+ const VALUE_NONE = 1;
+ // 必须传值
+ const VALUE_REQUIRED = 2;
+ // 可选传值
+ const VALUE_OPTIONAL = 4;
+ // 传数组值
+ const VALUE_IS_ARRAY = 8;
+
+ /**
+ * 选项名
+ * @var string
+ */
+ private $name = '';
+
+ /**
+ * 选项短名称
+ * @var string
+ */
+ private $shortcut = '';
+
+ /**
+ * 选项类型
+ * @var int
+ */
+ private $mode;
+
+ /**
+ * 选项默认值
+ * @var mixed
+ */
+ private $default;
+
+ /**
+ * 选项描述
+ * @var string
+ */
+ private $description = '';
+
+ /**
+ * 构造方法
+ * @param string $name 选项名
+ * @param string|array $shortcut 短名称,多个用|隔开或者使用数组
+ * @param int $mode 选项类型(可选类型为 self::VALUE_*)
+ * @param string $description 描述
+ * @param mixed $default 默认值 (类型为 self::VALUE_REQUIRED 或者 self::VALUE_NONE 的时候必须为null)
+ * @throws \InvalidArgumentException
+ */
+ public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null)
+ {
+ if (0 === strpos($name, '--')) {
+ $name = substr($name, 2);
+ }
+
+ if (empty($name)) {
+ throw new \InvalidArgumentException('An option name cannot be empty.');
+ }
+
+ if (empty($shortcut)) {
+ $shortcut = '';
+ }
+
+ if ('' !== $shortcut) {
+ if (is_array($shortcut)) {
+ $shortcut = implode('|', $shortcut);
+ }
+ $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-'));
+ $shortcuts = array_filter($shortcuts);
+ $shortcut = implode('|', $shortcuts);
+
+ if (empty($shortcut)) {
+ throw new \InvalidArgumentException('An option shortcut cannot be empty.');
+ }
+ }
+
+ if (null === $mode) {
+ $mode = self::VALUE_NONE;
+ } elseif (!is_int($mode) || $mode > 15 || $mode < 1) {
+ throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
+ }
+
+ $this->name = $name;
+ $this->shortcut = $shortcut;
+ $this->mode = $mode;
+ $this->description = $description;
+
+ if ($this->isArray() && !$this->acceptValue()) {
+ throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
+ }
+
+ $this->setDefault($default);
+ }
+
+ /**
+ * 获取短名称
+ * @return string
+ */
+ public function getShortcut(): string
+ {
+ return $this->shortcut;
+ }
+
+ /**
+ * 获取选项名
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * 是否可以设置值
+ * @return bool 类型不是 self::VALUE_NONE 的时候返回true,其他均返回false
+ */
+ public function acceptValue(): bool
+ {
+ return $this->isValueRequired() || $this->isValueOptional();
+ }
+
+ /**
+ * 是否必须
+ * @return bool 类型是 self::VALUE_REQUIRED 的时候返回true,其他均返回false
+ */
+ public function isValueRequired(): bool
+ {
+ return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
+ }
+
+ /**
+ * 是否可选
+ * @return bool 类型是 self::VALUE_OPTIONAL 的时候返回true,其他均返回false
+ */
+ public function isValueOptional(): bool
+ {
+ return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
+ }
+
+ /**
+ * 选项值是否接受数组
+ * @return bool 类型是 self::VALUE_IS_ARRAY 的时候返回true,其他均返回false
+ */
+ public function isArray(): bool
+ {
+ return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
+ }
+
+ /**
+ * 设置默认值
+ * @param mixed $default 默认值
+ * @throws \LogicException
+ */
+ public function setDefault($default = null)
+ {
+ if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
+ throw new \LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
+ }
+
+ if ($this->isArray()) {
+ if (null === $default) {
+ $default = [];
+ } elseif (!is_array($default)) {
+ throw new \LogicException('A default value for an array option must be an array.');
+ }
+ }
+
+ $this->default = $this->acceptValue() ? $default : false;
+ }
+
+ /**
+ * 获取默认值
+ * @return mixed
+ */
+ public function getDefault()
+ {
+ return $this->default;
+ }
+
+ /**
+ * 获取描述文字
+ * @return string
+ */
+ public function getDescription(): string
+ {
+ return $this->description;
+ }
+
+ /**
+ * 检查所给选项是否是当前这个
+ * @param Option $option
+ * @return bool
+ */
+ public function equals(Option $option): bool
+ {
+ return $option->getName() === $this->getName()
+ && $option->getShortcut() === $this->getShortcut()
+ && $option->getDefault() === $this->getDefault()
+ && $option->isArray() === $this->isArray()
+ && $option->isValueRequired() === $this->isValueRequired()
+ && $option->isValueOptional() === $this->isValueOptional();
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/output/Ask.php b/vendor/topthink/framework/src/think/console/output/Ask.php
new file mode 100755
index 0000000..0058d05
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/output/Ask.php
@@ -0,0 +1,336 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output;
+
+use think\console\Input;
+use think\console\Output;
+use think\console\output\question\Choice;
+use think\console\output\question\Confirmation;
+
+class Ask
+{
+ private static $stty;
+
+ private static $shell;
+
+ /** @var Input */
+ protected $input;
+
+ /** @var Output */
+ protected $output;
+
+ /** @var Question */
+ protected $question;
+
+ public function __construct(Input $input, Output $output, Question $question)
+ {
+ $this->input = $input;
+ $this->output = $output;
+ $this->question = $question;
+ }
+
+ public function run()
+ {
+ if (!$this->input->isInteractive()) {
+ return $this->question->getDefault();
+ }
+
+ if (!$this->question->getValidator()) {
+ return $this->doAsk();
+ }
+
+ $that = $this;
+
+ $interviewer = function () use ($that) {
+ return $that->doAsk();
+ };
+
+ return $this->validateAttempts($interviewer);
+ }
+
+ protected function doAsk()
+ {
+ $this->writePrompt();
+
+ $inputStream = STDIN;
+ $autocomplete = $this->question->getAutocompleterValues();
+
+ if (null === $autocomplete || !$this->hasSttyAvailable()) {
+ $ret = false;
+ if ($this->question->isHidden()) {
+ try {
+ $ret = trim($this->getHiddenResponse($inputStream));
+ } catch (\RuntimeException $e) {
+ if (!$this->question->isHiddenFallback()) {
+ throw $e;
+ }
+ }
+ }
+
+ if (false === $ret) {
+ $ret = fgets($inputStream, 4096);
+ if (false === $ret) {
+ throw new \RuntimeException('Aborted');
+ }
+ $ret = trim($ret);
+ }
+ } else {
+ $ret = trim($this->autocomplete($inputStream));
+ }
+
+ $ret = strlen($ret) > 0 ? $ret : $this->question->getDefault();
+
+ if ($normalizer = $this->question->getNormalizer()) {
+ return $normalizer($ret);
+ }
+
+ return $ret;
+ }
+
+ private function autocomplete($inputStream)
+ {
+ $autocomplete = $this->question->getAutocompleterValues();
+ $ret = '';
+
+ $i = 0;
+ $ofs = -1;
+ $matches = $autocomplete;
+ $numMatches = count($matches);
+
+ $sttyMode = shell_exec('stty -g');
+
+ shell_exec('stty -icanon -echo');
+
+ while (!feof($inputStream)) {
+ $c = fread($inputStream, 1);
+
+ if ("\177" === $c) {
+ if (0 === $numMatches && 0 !== $i) {
+ --$i;
+ $this->output->write("\033[1D");
+ }
+
+ if ($i === 0) {
+ $ofs = -1;
+ $matches = $autocomplete;
+ $numMatches = count($matches);
+ } else {
+ $numMatches = 0;
+ }
+
+ $ret = substr($ret, 0, $i);
+ } elseif ("\033" === $c) {
+ $c .= fread($inputStream, 2);
+
+ if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
+ if ('A' === $c[2] && -1 === $ofs) {
+ $ofs = 0;
+ }
+
+ if (0 === $numMatches) {
+ continue;
+ }
+
+ $ofs += ('A' === $c[2]) ? -1 : 1;
+ $ofs = ($numMatches + $ofs) % $numMatches;
+ }
+ } elseif (ord($c) < 32) {
+ if ("\t" === $c || "\n" === $c) {
+ if ($numMatches > 0 && -1 !== $ofs) {
+ $ret = $matches[$ofs];
+ $this->output->write(substr($ret, $i));
+ $i = strlen($ret);
+ }
+
+ if ("\n" === $c) {
+ $this->output->write($c);
+ break;
+ }
+
+ $numMatches = 0;
+ }
+
+ continue;
+ } else {
+ $this->output->write($c);
+ $ret .= $c;
+ ++$i;
+
+ $numMatches = 0;
+ $ofs = 0;
+
+ foreach ($autocomplete as $value) {
+ if (0 === strpos($value, $ret) && $i !== strlen($value)) {
+ $matches[$numMatches++] = $value;
+ }
+ }
+ }
+
+ $this->output->write("\033[K");
+
+ if ($numMatches > 0 && -1 !== $ofs) {
+ $this->output->write("\0337");
+ $this->output->highlight(substr($matches[$ofs], $i));
+ $this->output->write("\0338");
+ }
+ }
+
+ shell_exec(sprintf('stty %s', $sttyMode));
+
+ return $ret;
+ }
+
+ protected function getHiddenResponse($inputStream)
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $exe = __DIR__ . '/../bin/hiddeninput.exe';
+
+ $value = rtrim(shell_exec($exe));
+ $this->output->writeln('');
+
+ return $value;
+ }
+
+ if ($this->hasSttyAvailable()) {
+ $sttyMode = shell_exec('stty -g');
+
+ shell_exec('stty -echo');
+ $value = fgets($inputStream, 4096);
+ shell_exec(sprintf('stty %s', $sttyMode));
+
+ if (false === $value) {
+ throw new \RuntimeException('Aborted');
+ }
+
+ $value = trim($value);
+ $this->output->writeln('');
+
+ return $value;
+ }
+
+ if (false !== $shell = $this->getShell()) {
+ $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword';
+ $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
+ $value = rtrim(shell_exec($command));
+ $this->output->writeln('');
+
+ return $value;
+ }
+
+ throw new \RuntimeException('Unable to hide the response.');
+ }
+
+ protected function validateAttempts($interviewer)
+ {
+ /** @var \Exception $error */
+ $error = null;
+ $attempts = $this->question->getMaxAttempts();
+ while (null === $attempts || $attempts--) {
+ if (null !== $error) {
+ $this->output->error($error->getMessage());
+ }
+
+ try {
+ return call_user_func($this->question->getValidator(), $interviewer());
+ } catch (\Exception $error) {
+ }
+ }
+
+ throw $error;
+ }
+
+ /**
+ * 显示问题的提示信息
+ */
+ protected function writePrompt()
+ {
+ $text = $this->question->getQuestion();
+ $default = $this->question->getDefault();
+
+ switch (true) {
+ case null === $default:
+ $text = sprintf(' <info>%s</info>:', $text);
+
+ break;
+
+ case $this->question instanceof Confirmation:
+ $text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');
+
+ break;
+
+ case $this->question instanceof Choice && $this->question->isMultiselect():
+ $choices = $this->question->getChoices();
+ $default = explode(',', $default);
+
+ foreach ($default as $key => $value) {
+ $default[$key] = $choices[trim($value)];
+ }
+
+ $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, implode(', ', $default));
+
+ break;
+
+ case $this->question instanceof Choice:
+ $choices = $this->question->getChoices();
+ $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, $choices[$default]);
+
+ break;
+
+ default:
+ $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, $default);
+ }
+
+ $this->output->writeln($text);
+
+ if ($this->question instanceof Choice) {
+ $width = max(array_map('strlen', array_keys($this->question->getChoices())));
+
+ foreach ($this->question->getChoices() as $key => $value) {
+ $this->output->writeln(sprintf(" [<comment>%-{$width}s</comment>] %s", $key, $value));
+ }
+ }
+
+ $this->output->write(' > ');
+ }
+
+ private function getShell()
+ {
+ if (null !== self::$shell) {
+ return self::$shell;
+ }
+
+ self::$shell = false;
+
+ if (file_exists('/usr/bin/env')) {
+ $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
+ foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) {
+ if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
+ self::$shell = $sh;
+ break;
+ }
+ }
+ }
+
+ return self::$shell;
+ }
+
+ private function hasSttyAvailable()
+ {
+ if (null !== self::$stty) {
+ return self::$stty;
+ }
+
+ exec('stty 2>&1', $output, $exitcode);
+
+ return self::$stty = $exitcode === 0;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/output/Descriptor.php b/vendor/topthink/framework/src/think/console/output/Descriptor.php
new file mode 100755
index 0000000..84d487c
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/output/Descriptor.php
@@ -0,0 +1,323 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output;
+
+use think\Console;
+use think\console\Command;
+use think\console\input\Argument as InputArgument;
+use think\console\input\Definition as InputDefinition;
+use think\console\input\Option as InputOption;
+use think\console\Output;
+use think\console\output\descriptor\Console as ConsoleDescription;
+
+class Descriptor
+{
+
+ /**
+ * @var Output
+ */
+ protected $output;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function describe(Output $output, $object, array $options = [])
+ {
+ $this->output = $output;
+
+ switch (true) {
+ case $object instanceof InputArgument:
+ $this->describeInputArgument($object, $options);
+ break;
+ case $object instanceof InputOption:
+ $this->describeInputOption($object, $options);
+ break;
+ case $object instanceof InputDefinition:
+ $this->describeInputDefinition($object, $options);
+ break;
+ case $object instanceof Command:
+ $this->describeCommand($object, $options);
+ break;
+ case $object instanceof Console:
+ $this->describeConsole($object, $options);
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
+ }
+ }
+
+ /**
+ * 输出内容
+ * @param string $content
+ * @param bool $decorated
+ */
+ protected function write($content, $decorated = false)
+ {
+ $this->output->write($content, false, $decorated ? Output::OUTPUT_NORMAL : Output::OUTPUT_RAW);
+ }
+
+ /**
+ * 描述参数
+ * @param InputArgument $argument
+ * @param array $options
+ * @return string|mixed
+ */
+ protected function describeInputArgument(InputArgument $argument, array $options = [])
+ {
+ if (null !== $argument->getDefault()
+ && (!is_array($argument->getDefault())
+ || count($argument->getDefault()))
+ ) {
+ $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
+ } else {
+ $default = '';
+ }
+
+ $totalWidth = $options['total_width'] ?? strlen($argument->getName());
+ $spacingWidth = $totalWidth - strlen($argument->getName()) + 2;
+
+ $this->writeText(sprintf(" <info>%s</info>%s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + <info> + </info> + 2 spaces
+ preg_replace('/\s*\R\s*/', PHP_EOL . str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default), $options);
+ }
+
+ /**
+ * 描述选项
+ * @param InputOption $option
+ * @param array $options
+ * @return string|mixed
+ */
+ protected function describeInputOption(InputOption $option, array $options = [])
+ {
+ if ($option->acceptValue() && null !== $option->getDefault()
+ && (!is_array($option->getDefault())
+ || count($option->getDefault()))
+ ) {
+ $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
+ } else {
+ $default = '';
+ }
+
+ $value = '';
+ if ($option->acceptValue()) {
+ $value = '=' . strtoupper($option->getName());
+
+ if ($option->isValueOptional()) {
+ $value = '[' . $value . ']';
+ }
+ }
+
+ $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
+ $synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf('--%s%s', $option->getName(), $value));
+
+ $spacingWidth = $totalWidth - strlen($synopsis) + 2;
+
+ $this->writeText(sprintf(" <info>%s</info>%s%s%s%s", $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + <info> + </info> + 2 spaces
+ preg_replace('/\s*\R\s*/', "\n" . str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''), $options);
+ }
+
+ /**
+ * 描述输入
+ * @param InputDefinition $definition
+ * @param array $options
+ * @return string|mixed
+ */
+ protected function describeInputDefinition(InputDefinition $definition, array $options = [])
+ {
+ $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
+ foreach ($definition->getArguments() as $argument) {
+ $totalWidth = max($totalWidth, strlen($argument->getName()));
+ }
+
+ if ($definition->getArguments()) {
+ $this->writeText('<comment>Arguments:</comment>', $options);
+ $this->writeText("\n");
+ foreach ($definition->getArguments() as $argument) {
+ $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
+ $this->writeText("\n");
+ }
+ }
+
+ if ($definition->getArguments() && $definition->getOptions()) {
+ $this->writeText("\n");
+ }
+
+ if ($definition->getOptions()) {
+ $laterOptions = [];
+
+ $this->writeText('<comment>Options:</comment>', $options);
+ foreach ($definition->getOptions() as $option) {
+ if (strlen($option->getShortcut()) > 1) {
+ $laterOptions[] = $option;
+ continue;
+ }
+ $this->writeText("\n");
+ $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
+ }
+ foreach ($laterOptions as $option) {
+ $this->writeText("\n");
+ $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
+ }
+ }
+ }
+
+ /**
+ * 描述指令
+ * @param Command $command
+ * @param array $options
+ * @return string|mixed
+ */
+ protected function describeCommand(Command $command, array $options = [])
+ {
+ $command->getSynopsis(true);
+ $command->getSynopsis(false);
+ $command->mergeConsoleDefinition(false);
+
+ $this->writeText('<comment>Usage:</comment>', $options);
+ foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
+ $this->writeText("\n");
+ $this->writeText(' ' . $usage, $options);
+ }
+ $this->writeText("\n");
+
+ $definition = $command->getNativeDefinition();
+ if ($definition->getOptions() || $definition->getArguments()) {
+ $this->writeText("\n");
+ $this->describeInputDefinition($definition, $options);
+ $this->writeText("\n");
+ }
+
+ if ($help = $command->getProcessedHelp()) {
+ $this->writeText("\n");
+ $this->writeText('<comment>Help:</comment>', $options);
+ $this->writeText("\n");
+ $this->writeText(' ' . str_replace("\n", "\n ", $help), $options);
+ $this->writeText("\n");
+ }
+ }
+
+ /**
+ * 描述控制台
+ * @param Console $console
+ * @param array $options
+ * @return string|mixed
+ */
+ protected function describeConsole(Console $console, array $options = [])
+ {
+ $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
+ $description = new ConsoleDescription($console, $describedNamespace);
+
+ if (isset($options['raw_text']) && $options['raw_text']) {
+ $width = $this->getColumnWidth($description->getNamespaces());
+
+ foreach ($description->getCommands() as $command) {
+ $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
+ $this->writeText("\n");
+ }
+ } else {
+ if ('' != $help = $console->getHelp()) {
+ $this->writeText("$help\n\n", $options);
+ }
+
+ $this->writeText("<comment>Usage:</comment>\n", $options);
+ $this->writeText(" command [options] [arguments]\n\n", $options);
+
+ $this->describeInputDefinition(new InputDefinition($console->getDefinition()->getOptions()), $options);
+
+ $this->writeText("\n");
+ $this->writeText("\n");
+
+ $width = $this->getColumnWidth($description->getNamespaces());
+
+ if ($describedNamespace) {
+ $this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
+ } else {
+ $this->writeText('<comment>Available commands:</comment>', $options);
+ }
+
+ // add commands by namespace
+ foreach ($description->getNamespaces() as $namespace) {
+ if (!$describedNamespace && ConsoleDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
+ $this->writeText("\n");
+ $this->writeText(' <comment>' . $namespace['id'] . '</comment>', $options);
+ }
+
+ foreach ($namespace['commands'] as $name) {
+ $this->writeText("\n");
+ $spacingWidth = $width - strlen($name);
+ $this->writeText(sprintf(" <info>%s</info>%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)
+ ->getDescription()), $options);
+ }
+ }
+
+ $this->writeText("\n");
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ private function writeText($content, array $options = [])
+ {
+ $this->write(isset($options['raw_text'])
+ && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true);
+ }
+
+ /**
+ * 格式化
+ * @param mixed $default
+ * @return string
+ */
+ private function formatDefaultValue($default)
+ {
+ return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+ }
+
+ /**
+ * @param Namespaces[] $namespaces
+ * @return int
+ */
+ private function getColumnWidth(array $namespaces)
+ {
+ $width = 0;
+ foreach ($namespaces as $namespace) {
+ foreach ($namespace['commands'] as $name) {
+ if (strlen($name) > $width) {
+ $width = strlen($name);
+ }
+ }
+ }
+
+ return $width + 2;
+ }
+
+ /**
+ * @param InputOption[] $options
+ * @return int
+ */
+ private function calculateTotalWidthForOptions($options)
+ {
+ $totalWidth = 0;
+ foreach ($options as $option) {
+ $nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + --
+
+ if ($option->acceptValue()) {
+ $valueLength = 1 + strlen($option->getName()); // = + value
+ $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
+
+ $nameLength += $valueLength;
+ }
+ $totalWidth = max($totalWidth, $nameLength);
+ }
+
+ return $totalWidth;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/output/Formatter.php b/vendor/topthink/framework/src/think/console/output/Formatter.php
new file mode 100755
index 0000000..1b97ca3
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/output/Formatter.php
@@ -0,0 +1,198 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\console\output;
+
+use think\console\output\formatter\Stack as StyleStack;
+use think\console\output\formatter\Style;
+
+class Formatter
+{
+
+ private $decorated = false;
+ private $styles = [];
+ private $styleStack;
+
+ /**
+ * 转义
+ * @param string $text
+ * @return string
+ */
+ public static function escape($text)
+ {
+ return preg_replace('/([^\\\\]?)</is', '$1\\<', $text);
+ }
+
+ /**
+ * 初始化命令行输出格式
+ */
+ public function __construct()
+ {
+ $this->setStyle('error', new Style('white', 'red'));
+ $this->setStyle('info', new Style('green'));
+ $this->setStyle('comment', new Style('yellow'));
+ $this->setStyle('question', new Style('black', 'cyan'));
+ $this->setStyle('highlight', new Style('red'));
+ $this->setStyle('warning', new Style('black', 'yellow'));
+
+ $this->styleStack = new StyleStack();
+ }
+
+ /**
+ * 设置外观标识
+ * @param bool $decorated 是否美化文字
+ */
+ public function setDecorated($decorated)
+ {
+ $this->decorated = (bool) $decorated;
+ }
+
+ /**
+ * 获取外观标识
+ * @return bool
+ */
+ public function isDecorated()
+ {
+ return $this->decorated;
+ }
+
+ /**
+ * 添加一个新样式
+ * @param string $name 样式名
+ * @param Style $style 样式实例
+ */
+ public function setStyle($name, Style $style)
+ {
+ $this->styles[strtolower($name)] = $style;
+ }
+
+ /**
+ * 是否有这个样式
+ * @param string $name
+ * @return bool
+ */
+ public function hasStyle($name)
+ {
+ return isset($this->styles[strtolower($name)]);
+ }
+
+ /**
+ * 获取样式
+ * @param string $name
+ * @return Style
+ * @throws \InvalidArgumentException
+ */
+ public function getStyle($name)
+ {
+ if (!$this->hasStyle($name)) {
+ throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name));
+ }
+
+ return $this->styles[strtolower($name)];
+ }
+
+ /**
+ * 使用所给的样式格式化文字
+ * @param string $message 文字
+ * @return string
+ */
+ public function format($message)
+ {
+ $offset = 0;
+ $output = '';
+ $tagRegex = '[a-z][a-z0-9_=;-]*';
+ preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#isx", $message, $matches, PREG_OFFSET_CAPTURE);
+ foreach ($matches[0] as $i => $match) {
+ $pos = $match[1];
+ $text = $match[0];
+
+ if (0 != $pos && '\\' == $message[$pos - 1]) {
+ continue;
+ }
+
+ $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset));
+ $offset = $pos + strlen($text);
+
+ if ($open = '/' != $text[1]) {
+ $tag = $matches[1][$i][0];
+ } else {
+ $tag = $matches[3][$i][0] ?? '';
+ }
+
+ if (!$open && !$tag) {
+ // </>
+ $this->styleStack->pop();
+ } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) {
+ $output .= $this->applyCurrentStyle($text);
+ } elseif ($open) {
+ $this->styleStack->push($style);
+ } else {
+ $this->styleStack->pop($style);
+ }
+ }
+
+ $output .= $this->applyCurrentStyle(substr($message, $offset));
+
+ return str_replace('\\<', '<', $output);
+ }
+
+ /**
+ * @return StyleStack
+ */
+ public function getStyleStack()
+ {
+ return $this->styleStack;
+ }
+
+ /**
+ * 根据字符串创建新的样式实例
+ * @param string $string
+ * @return Style|bool
+ */
+ private function createStyleFromString($string)
+ {
+ if (isset($this->styles[$string])) {
+ return $this->styles[$string];
+ }
+
+ if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) {
+ return false;
+ }
+
+ $style = new Style();
+ foreach ($matches as $match) {
+ array_shift($match);
+
+ if ('fg' == $match[0]) {
+ $style->setForeground($match[1]);
+ } elseif ('bg' == $match[0]) {
+ $style->setBackground($match[1]);
+ } else {
+ try {
+ $style->setOption($match[1]);
+ } catch (\InvalidArgumentException $e) {
+ return false;
+ }
+ }
+ }
+
+ return $style;
+ }
+
+ /**
+ * 从堆栈应用样式到文字
+ * @param string $text 文字
+ * @return string
+ */
+ private function applyCurrentStyle($text)
+ {
+ return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/output/Question.php b/vendor/topthink/framework/src/think/console/output/Question.php
new file mode 100755
index 0000000..03975f2
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/output/Question.php
@@ -0,0 +1,211 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output;
+
+class Question
+{
+
+ private $question;
+ private $attempts;
+ private $hidden = false;
+ private $hiddenFallback = true;
+ private $autocompleterValues;
+ private $validator;
+ private $default;
+ private $normalizer;
+
+ /**
+ * 构造方法
+ * @param string $question 问题
+ * @param mixed $default 默认答案
+ */
+ public function __construct($question, $default = null)
+ {
+ $this->question = $question;
+ $this->default = $default;
+ }
+
+ /**
+ * 获取问题
+ * @return string
+ */
+ public function getQuestion()
+ {
+ return $this->question;
+ }
+
+ /**
+ * 获取默认答案
+ * @return mixed
+ */
+ public function getDefault()
+ {
+ return $this->default;
+ }
+
+ /**
+ * 是否隐藏答案
+ * @return bool
+ */
+ public function isHidden()
+ {
+ return $this->hidden;
+ }
+
+ /**
+ * 隐藏答案
+ * @param bool $hidden
+ * @return Question
+ */
+ public function setHidden($hidden)
+ {
+ if ($this->autocompleterValues) {
+ throw new \LogicException('A hidden question cannot use the autocompleter.');
+ }
+
+ $this->hidden = (bool) $hidden;
+
+ return $this;
+ }
+
+ /**
+ * 不能被隐藏是否撤销
+ * @return bool
+ */
+ public function isHiddenFallback()
+ {
+ return $this->hiddenFallback;
+ }
+
+ /**
+ * 设置不能被隐藏的时候的操作
+ * @param bool $fallback
+ * @return Question
+ */
+ public function setHiddenFallback($fallback)
+ {
+ $this->hiddenFallback = (bool) $fallback;
+
+ return $this;
+ }
+
+ /**
+ * 获取自动完成
+ * @return null|array|\Traversable
+ */
+ public function getAutocompleterValues()
+ {
+ return $this->autocompleterValues;
+ }
+
+ /**
+ * 设置自动完成的值
+ * @param null|array|\Traversable $values
+ * @return Question
+ * @throws \InvalidArgumentException
+ * @throws \LogicException
+ */
+ public function setAutocompleterValues($values)
+ {
+ if (is_array($values) && $this->isAssoc($values)) {
+ $values = array_merge(array_keys($values), array_values($values));
+ }
+
+ if (null !== $values && !is_array($values)) {
+ if (!$values instanceof \Traversable || $values instanceof \Countable) {
+ throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.');
+ }
+ }
+
+ if ($this->hidden) {
+ throw new \LogicException('A hidden question cannot use the autocompleter.');
+ }
+
+ $this->autocompleterValues = $values;
+
+ return $this;
+ }
+
+ /**
+ * 设置答案的验证器
+ * @param null|callable $validator
+ * @return Question The current instance
+ */
+ public function setValidator($validator)
+ {
+ $this->validator = $validator;
+
+ return $this;
+ }
+
+ /**
+ * 获取验证器
+ * @return null|callable
+ */
+ public function getValidator()
+ {
+ return $this->validator;
+ }
+
+ /**
+ * 设置最大重试次数
+ * @param null|int $attempts
+ * @return Question
+ * @throws \InvalidArgumentException
+ */
+ public function setMaxAttempts($attempts)
+ {
+ if (null !== $attempts && $attempts < 1) {
+ throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.');
+ }
+
+ $this->attempts = $attempts;
+
+ return $this;
+ }
+
+ /**
+ * 获取最大重试次数
+ * @return null|int
+ */
+ public function getMaxAttempts()
+ {
+ return $this->attempts;
+ }
+
+ /**
+ * 设置响应的回调
+ * @param string|\Closure $normalizer
+ * @return Question
+ */
+ public function setNormalizer($normalizer)
+ {
+ $this->normalizer = $normalizer;
+
+ return $this;
+ }
+
+ /**
+ * 获取响应回调
+ * The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
+ * @return string|\Closure
+ */
+ public function getNormalizer()
+ {
+ return $this->normalizer;
+ }
+
+ protected function isAssoc($array)
+ {
+ return (bool) count(array_filter(array_keys($array), 'is_string'));
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/output/descriptor/Console.php b/vendor/topthink/framework/src/think/console/output/descriptor/Console.php
new file mode 100755
index 0000000..ff9f464
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/output/descriptor/Console.php
@@ -0,0 +1,153 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\descriptor;
+
+use think\Console as ThinkConsole;
+use think\console\Command;
+
+class Console
+{
+
+ const GLOBAL_NAMESPACE = '_global';
+
+ /**
+ * @var ThinkConsole
+ */
+ private $console;
+
+ /**
+ * @var null|string
+ */
+ private $namespace;
+
+ /**
+ * @var array
+ */
+ private $namespaces;
+
+ /**
+ * @var Command[]
+ */
+ private $commands;
+
+ /**
+ * @var Command[]
+ */
+ private $aliases;
+
+ /**
+ * 构造方法
+ * @param ThinkConsole $console
+ * @param string|null $namespace
+ */
+ public function __construct(ThinkConsole $console, $namespace = null)
+ {
+ $this->console = $console;
+ $this->namespace = $namespace;
+ }
+
+ /**
+ * @return array
+ */
+ public function getNamespaces(): array
+ {
+ if (null === $this->namespaces) {
+ $this->inspectConsole();
+ }
+
+ return $this->namespaces;
+ }
+
+ /**
+ * @return Command[]
+ */
+ public function getCommands(): array
+ {
+ if (null === $this->commands) {
+ $this->inspectConsole();
+ }
+
+ return $this->commands;
+ }
+
+ /**
+ * @param string $name
+ * @return Command
+ * @throws \InvalidArgumentException
+ */
+ public function getCommand(string $name): Command
+ {
+ if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
+ throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name));
+ }
+
+ return $this->commands[$name] ?? $this->aliases[$name];
+ }
+
+ private function inspectConsole(): void
+ {
+ $this->commands = [];
+ $this->namespaces = [];
+
+ $all = $this->console->all($this->namespace ? $this->console->findNamespace($this->namespace) : null);
+ foreach ($this->sortCommands($all) as $namespace => $commands) {
+ $names = [];
+
+ /** @var Command $command */
+ foreach ($commands as $name => $command) {
+ if (is_string($command)) {
+ $command = new $command();
+ }
+
+ if (!$command->getName()) {
+ continue;
+ }
+
+ if ($command->getName() === $name) {
+ $this->commands[$name] = $command;
+ } else {
+ $this->aliases[$name] = $command;
+ }
+
+ $names[] = $name;
+ }
+
+ $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
+ }
+ }
+
+ /**
+ * @param array $commands
+ * @return array
+ */
+ private function sortCommands(array $commands): array
+ {
+ $namespacedCommands = [];
+ foreach ($commands as $name => $command) {
+ $key = $this->console->extractNamespace($name, 1);
+ if (!$key) {
+ $key = self::GLOBAL_NAMESPACE;
+ }
+
+ $namespacedCommands[$key][$name] = $command;
+ }
+ ksort($namespacedCommands);
+
+ foreach ($namespacedCommands as &$commandsSet) {
+ ksort($commandsSet);
+ }
+ // unset reference to keep scope clear
+ unset($commandsSet);
+
+ return $namespacedCommands;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/output/driver/Buffer.php b/vendor/topthink/framework/src/think/console/output/driver/Buffer.php
new file mode 100755
index 0000000..576f31a
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/output/driver/Buffer.php
@@ -0,0 +1,52 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\driver;
+
+use think\console\Output;
+
+class Buffer
+{
+ /**
+ * @var string
+ */
+ private $buffer = '';
+
+ public function __construct(Output $output)
+ {
+ // do nothing
+ }
+
+ public function fetch()
+ {
+ $content = $this->buffer;
+ $this->buffer = '';
+ return $content;
+ }
+
+ public function write($messages, bool $newline = false, int $options = 0)
+ {
+ $messages = (array) $messages;
+
+ foreach ($messages as $message) {
+ $this->buffer .= $message;
+ }
+ if ($newline) {
+ $this->buffer .= "\n";
+ }
+ }
+
+ public function renderException(\Throwable $e)
+ {
+ // do nothing
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/console/output/driver/Console.php b/vendor/topthink/framework/src/think/console/output/driver/Console.php
new file mode 100755
index 0000000..3061f55
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/output/driver/Console.php
@@ -0,0 +1,369 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\driver;
+
+use think\console\Output;
+use think\console\output\Formatter;
+
+class Console
+{
+
+ /** @var Resource */
+ private $stdout;
+
+ /** @var Formatter */
+ private $formatter;
+
+ private $terminalDimensions;
+
+ /** @var Output */
+ private $output;
+
+ public function __construct(Output $output)
+ {
+ $this->output = $output;
+ $this->formatter = new Formatter();
+ $this->stdout = $this->openOutputStream();
+ $decorated = $this->hasColorSupport($this->stdout);
+ $this->formatter->setDecorated($decorated);
+ }
+
+ public function setDecorated($decorated)
+ {
+ $this->formatter->setDecorated($decorated);
+ }
+
+ public function write($messages, bool $newline = false, int $type = 0, $stream = null)
+ {
+ if (Output::VERBOSITY_QUIET === $this->output->getVerbosity()) {
+ return;
+ }
+
+ $messages = (array) $messages;
+
+ foreach ($messages as $message) {
+ switch ($type) {
+ case Output::OUTPUT_NORMAL:
+ $message = $this->formatter->format($message);
+ break;
+ case Output::OUTPUT_RAW:
+ break;
+ case Output::OUTPUT_PLAIN:
+ $message = strip_tags($this->formatter->format($message));
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type));
+ }
+
+ $this->doWrite($message, $newline, $stream);
+ }
+ }
+
+ public function renderException(\Throwable $e)
+ {
+ $stderr = $this->openErrorStream();
+ $decorated = $this->hasColorSupport($stderr);
+ $this->formatter->setDecorated($decorated);
+
+ do {
+ $title = sprintf(' [%s] ', get_class($e));
+
+ $len = $this->stringWidth($title);
+
+ $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX;
+
+ if (defined('HHVM_VERSION') && $width > 1 << 31) {
+ $width = 1 << 31;
+ }
+ $lines = [];
+ foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) {
+ foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
+
+ $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $line)) + 4;
+ $lines[] = [$line, $lineLength];
+
+ $len = max($lineLength, $len);
+ }
+ }
+
+ $messages = ['', ''];
+ $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
+ $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title))));
+ foreach ($lines as $line) {
+ $messages[] = sprintf('<error> %s %s</error>', $line[0], str_repeat(' ', $len - $line[1]));
+ }
+ $messages[] = $emptyLine;
+ $messages[] = '';
+ $messages[] = '';
+
+ $this->write($messages, true, Output::OUTPUT_NORMAL, $stderr);
+
+ if (Output::VERBOSITY_VERBOSE <= $this->output->getVerbosity()) {
+ $this->write('<comment>Exception trace:</comment>', true, Output::OUTPUT_NORMAL, $stderr);
+
+ // exception related properties
+ $trace = $e->getTrace();
+ array_unshift($trace, [
+ 'function' => '',
+ 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a',
+ 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a',
+ 'args' => [],
+ ]);
+
+ for ($i = 0, $count = count($trace); $i < $count; ++$i) {
+ $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
+ $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
+ $function = $trace[$i]['function'];
+ $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
+ $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
+
+ $this->write(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr);
+ }
+
+ $this->write('', true, Output::OUTPUT_NORMAL, $stderr);
+ $this->write('', true, Output::OUTPUT_NORMAL, $stderr);
+ }
+ } while ($e = $e->getPrevious());
+
+ }
+
+ /**
+ * 获取终端宽度
+ * @return int|null
+ */
+ protected function getTerminalWidth()
+ {
+ $dimensions = $this->getTerminalDimensions();
+
+ return $dimensions[0];
+ }
+
+ /**
+ * 获取终端高度
+ * @return int|null
+ */
+ protected function getTerminalHeight()
+ {
+ $dimensions = $this->getTerminalDimensions();
+
+ return $dimensions[1];
+ }
+
+ /**
+ * 获取当前终端的尺寸
+ * @return array
+ */
+ public function getTerminalDimensions(): array
+ {
+ if ($this->terminalDimensions) {
+ return $this->terminalDimensions;
+ }
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) {
+ return [(int) $matches[1], (int) $matches[2]];
+ }
+ if (preg_match('/^(\d+)x(\d+)$/', $this->getMode(), $matches)) {
+ return [(int) $matches[1], (int) $matches[2]];
+ }
+ }
+
+ if ($sttyString = $this->getSttyColumns()) {
+ if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
+ return [(int) $matches[2], (int) $matches[1]];
+ }
+ if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
+ return [(int) $matches[2], (int) $matches[1]];
+ }
+ }
+
+ return [null, null];
+ }
+
+ /**
+ * 获取stty列数
+ * @return string
+ */
+ private function getSttyColumns()
+ {
+ if (!function_exists('proc_open')) {
+ return;
+ }
+
+ $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
+ $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
+ if (is_resource($process)) {
+ $info = stream_get_contents($pipes[1]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($process);
+
+ return $info;
+ }
+ return;
+ }
+
+ /**
+ * 获取终端模式
+ * @return string <width>x<height>
+ */
+ private function getMode()
+ {
+ if (!function_exists('proc_open')) {
+ return '';
+ }
+
+ $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
+ $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
+ if (is_resource($process)) {
+ $info = stream_get_contents($pipes[1]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($process);
+
+ if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
+ return $matches[2] . 'x' . $matches[1];
+ }
+ }
+
+ return '';
+ }
+
+ private function stringWidth(string $string): int
+ {
+ if (!function_exists('mb_strwidth')) {
+ return strlen($string);
+ }
+
+ if (false === $encoding = mb_detect_encoding($string)) {
+ return strlen($string);
+ }
+
+ return mb_strwidth($string, $encoding);
+ }
+
+ private function splitStringByWidth(string $string, int $width): array
+ {
+ if (!function_exists('mb_strwidth')) {
+ return str_split($string, $width);
+ }
+
+ if (false === $encoding = mb_detect_encoding($string)) {
+ return str_split($string, $width);
+ }
+
+ $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
+ $lines = [];
+ $line = '';
+ foreach (preg_split('//u', $utf8String) as $char) {
+ if (mb_strwidth($line . $char, 'utf8') <= $width) {
+ $line .= $char;
+ continue;
+ }
+ $lines[] = str_pad($line, $width);
+ $line = $char;
+ }
+ if (strlen($line)) {
+ $lines[] = count($lines) ? str_pad($line, $width) : $line;
+ }
+
+ mb_convert_variables($encoding, 'utf8', $lines);
+
+ return $lines;
+ }
+
+ private function isRunningOS400(): bool
+ {
+ $checks = [
+ function_exists('php_uname') ? php_uname('s') : '',
+ getenv('OSTYPE'),
+ PHP_OS,
+ ];
+ return false !== stripos(implode(';', $checks), 'OS400');
+ }
+
+ /**
+ * 当前环境是否支持写入控制台输出到stdout.
+ *
+ * @return bool
+ */
+ protected function hasStdoutSupport(): bool
+ {
+ return false === $this->isRunningOS400();
+ }
+
+ /**
+ * 当前环境是否支持写入控制台输出到stderr.
+ *
+ * @return bool
+ */
+ protected function hasStderrSupport(): bool
+ {
+ return false === $this->isRunningOS400();
+ }
+
+ /**
+ * @return resource
+ */
+ private function openOutputStream()
+ {
+ if (!$this->hasStdoutSupport()) {
+ return fopen('php://output', 'w');
+ }
+ return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
+ }
+
+ /**
+ * @return resource
+ */
+ private function openErrorStream()
+ {
+ return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w');
+ }
+
+ /**
+ * 将消息写入到输出。
+ * @param string $message 消息
+ * @param bool $newline 是否另起一行
+ * @param null $stream
+ */
+ protected function doWrite($message, $newline, $stream = null)
+ {
+ if (null === $stream) {
+ $stream = $this->stdout;
+ }
+ if (false === @fwrite($stream, $message . ($newline ? PHP_EOL : ''))) {
+ throw new \RuntimeException('Unable to write output.');
+ }
+
+ fflush($stream);
+ }
+
+ /**
+ * 是否支持着色
+ * @param $stream
+ * @return bool
+ */
+ protected function hasColorSupport($stream): bool
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ return
+ '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD
+ || false !== getenv('ANSICON')
+ || 'ON' === getenv('ConEmuANSI')
+ || 'xterm' === getenv('TERM');
+ }
+
+ return function_exists('posix_isatty') && @posix_isatty($stream);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/console/output/driver/Nothing.php b/vendor/topthink/framework/src/think/console/output/driver/Nothing.php
new file mode 100755
index 0000000..a7cc49e
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/output/driver/Nothing.php
@@ -0,0 +1,33 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\driver;
+
+use think\console\Output;
+
+class Nothing
+{
+
+ public function __construct(Output $output)
+ {
+ // do nothing
+ }
+
+ public function write($messages, bool $newline = false, int $options = 0)
+ {
+ // do nothing
+ }
+
+ public function renderException(\Throwable $e)
+ {
+ // do nothing
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/output/formatter/Stack.php b/vendor/topthink/framework/src/think/console/output/formatter/Stack.php
new file mode 100755
index 0000000..5366259
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/output/formatter/Stack.php
@@ -0,0 +1,116 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\formatter;
+
+class Stack
+{
+
+ /**
+ * @var Style[]
+ */
+ private $styles;
+
+ /**
+ * @var Style
+ */
+ private $emptyStyle;
+
+ /**
+ * 构造方法
+ * @param Style|null $emptyStyle
+ */
+ public function __construct(Style $emptyStyle = null)
+ {
+ $this->emptyStyle = $emptyStyle ?: new Style();
+ $this->reset();
+ }
+
+ /**
+ * 重置堆栈
+ */
+ public function reset(): void
+ {
+ $this->styles = [];
+ }
+
+ /**
+ * 推一个样式进入堆栈
+ * @param Style $style
+ */
+ public function push(Style $style): void
+ {
+ $this->styles[] = $style;
+ }
+
+ /**
+ * 从堆栈中弹出一个样式
+ * @param Style|null $style
+ * @return Style
+ * @throws \InvalidArgumentException
+ */
+ public function pop(Style $style = null): Style
+ {
+ if (empty($this->styles)) {
+ return $this->emptyStyle;
+ }
+
+ if (null === $style) {
+ return array_pop($this->styles);
+ }
+
+ /**
+ * @var int $index
+ * @var Style $stackedStyle
+ */
+ foreach (array_reverse($this->styles, true) as $index => $stackedStyle) {
+ if ($style->apply('') === $stackedStyle->apply('')) {
+ $this->styles = array_slice($this->styles, 0, $index);
+
+ return $stackedStyle;
+ }
+ }
+
+ throw new \InvalidArgumentException('Incorrectly nested style tag found.');
+ }
+
+ /**
+ * 计算堆栈的当前样式。
+ * @return Style
+ */
+ public function getCurrent(): Style
+ {
+ if (empty($this->styles)) {
+ return $this->emptyStyle;
+ }
+
+ return $this->styles[count($this->styles) - 1];
+ }
+
+ /**
+ * @param Style $emptyStyle
+ * @return Stack
+ */
+ public function setEmptyStyle(Style $emptyStyle)
+ {
+ $this->emptyStyle = $emptyStyle;
+
+ return $this;
+ }
+
+ /**
+ * @return Style
+ */
+ public function getEmptyStyle(): Style
+ {
+ return $this->emptyStyle;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/output/formatter/Style.php b/vendor/topthink/framework/src/think/console/output/formatter/Style.php
new file mode 100755
index 0000000..2aae768
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/output/formatter/Style.php
@@ -0,0 +1,190 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\formatter;
+
+class Style
+{
+ protected static $availableForegroundColors = [
+ 'black' => ['set' => 30, 'unset' => 39],
+ 'red' => ['set' => 31, 'unset' => 39],
+ 'green' => ['set' => 32, 'unset' => 39],
+ 'yellow' => ['set' => 33, 'unset' => 39],
+ 'blue' => ['set' => 34, 'unset' => 39],
+ 'magenta' => ['set' => 35, 'unset' => 39],
+ 'cyan' => ['set' => 36, 'unset' => 39],
+ 'white' => ['set' => 37, 'unset' => 39],
+ ];
+
+ protected static $availableBackgroundColors = [
+ 'black' => ['set' => 40, 'unset' => 49],
+ 'red' => ['set' => 41, 'unset' => 49],
+ 'green' => ['set' => 42, 'unset' => 49],
+ 'yellow' => ['set' => 43, 'unset' => 49],
+ 'blue' => ['set' => 44, 'unset' => 49],
+ 'magenta' => ['set' => 45, 'unset' => 49],
+ 'cyan' => ['set' => 46, 'unset' => 49],
+ 'white' => ['set' => 47, 'unset' => 49],
+ ];
+
+ protected static $availableOptions = [
+ 'bold' => ['set' => 1, 'unset' => 22],
+ 'underscore' => ['set' => 4, 'unset' => 24],
+ 'blink' => ['set' => 5, 'unset' => 25],
+ 'reverse' => ['set' => 7, 'unset' => 27],
+ 'conceal' => ['set' => 8, 'unset' => 28],
+ ];
+
+ private $foreground;
+ private $background;
+ private $options = [];
+
+ /**
+ * 初始化输出的样式
+ * @param string|null $foreground 字体颜色
+ * @param string|null $background 背景色
+ * @param array $options 格式
+ * @api
+ */
+ public function __construct($foreground = null, $background = null, array $options = [])
+ {
+ if (null !== $foreground) {
+ $this->setForeground($foreground);
+ }
+ if (null !== $background) {
+ $this->setBackground($background);
+ }
+ if (count($options)) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * 设置字体颜色
+ * @param string|null $color 颜色名
+ * @throws \InvalidArgumentException
+ * @api
+ */
+ public function setForeground($color = null)
+ {
+ if (null === $color) {
+ $this->foreground = null;
+
+ return;
+ }
+
+ if (!isset(static::$availableForegroundColors[$color])) {
+ throw new \InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors))));
+ }
+
+ $this->foreground = static::$availableForegroundColors[$color];
+ }
+
+ /**
+ * 设置背景色
+ * @param string|null $color 颜色名
+ * @throws \InvalidArgumentException
+ * @api
+ */
+ public function setBackground($color = null)
+ {
+ if (null === $color) {
+ $this->background = null;
+
+ return;
+ }
+
+ if (!isset(static::$availableBackgroundColors[$color])) {
+ throw new \InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors))));
+ }
+
+ $this->background = static::$availableBackgroundColors[$color];
+ }
+
+ /**
+ * 设置字体格式
+ * @param string $option 格式名
+ * @throws \InvalidArgumentException When the option name isn't defined
+ * @api
+ */
+ public function setOption(string $option): void
+ {
+ if (!isset(static::$availableOptions[$option])) {
+ throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
+ }
+
+ if (!in_array(static::$availableOptions[$option], $this->options)) {
+ $this->options[] = static::$availableOptions[$option];
+ }
+ }
+
+ /**
+ * 重置字体格式
+ * @param string $option 格式名
+ * @throws \InvalidArgumentException
+ */
+ public function unsetOption(string $option): void
+ {
+ if (!isset(static::$availableOptions[$option])) {
+ throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
+ }
+
+ $pos = array_search(static::$availableOptions[$option], $this->options);
+ if (false !== $pos) {
+ unset($this->options[$pos]);
+ }
+ }
+
+ /**
+ * 批量设置字体格式
+ * @param array $options
+ */
+ public function setOptions(array $options)
+ {
+ $this->options = [];
+
+ foreach ($options as $option) {
+ $this->setOption($option);
+ }
+ }
+
+ /**
+ * 应用样式到文字
+ * @param string $text 文字
+ * @return string
+ */
+ public function apply(string $text): string
+ {
+ $setCodes = [];
+ $unsetCodes = [];
+
+ if (null !== $this->foreground) {
+ $setCodes[] = $this->foreground['set'];
+ $unsetCodes[] = $this->foreground['unset'];
+ }
+ if (null !== $this->background) {
+ $setCodes[] = $this->background['set'];
+ $unsetCodes[] = $this->background['unset'];
+ }
+ if (count($this->options)) {
+ foreach ($this->options as $option) {
+ $setCodes[] = $option['set'];
+ $unsetCodes[] = $option['unset'];
+ }
+ }
+
+ if (0 === count($setCodes)) {
+ return $text;
+ }
+
+ return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes));
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/output/question/Choice.php b/vendor/topthink/framework/src/think/console/output/question/Choice.php
new file mode 100755
index 0000000..1da1750
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/output/question/Choice.php
@@ -0,0 +1,163 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\question;
+
+use think\console\output\Question;
+
+class Choice extends Question
+{
+
+ private $choices;
+ private $multiselect = false;
+ private $prompt = ' > ';
+ private $errorMessage = 'Value "%s" is invalid';
+
+ /**
+ * 构造方法
+ * @param string $question 问题
+ * @param array $choices 选项
+ * @param mixed $default 默认答案
+ */
+ public function __construct($question, array $choices, $default = null)
+ {
+ parent::__construct($question, $default);
+
+ $this->choices = $choices;
+ $this->setValidator($this->getDefaultValidator());
+ $this->setAutocompleterValues($choices);
+ }
+
+ /**
+ * 可选项
+ * @return array
+ */
+ public function getChoices(): array
+ {
+ return $this->choices;
+ }
+
+ /**
+ * 设置可否多选
+ * @param bool $multiselect
+ * @return self
+ */
+ public function setMultiselect(bool $multiselect)
+ {
+ $this->multiselect = $multiselect;
+ $this->setValidator($this->getDefaultValidator());
+
+ return $this;
+ }
+
+ public function isMultiselect(): bool
+ {
+ return $this->multiselect;
+ }
+
+ /**
+ * 获取提示
+ * @return string
+ */
+ public function getPrompt(): string
+ {
+ return $this->prompt;
+ }
+
+ /**
+ * 设置提示
+ * @param string $prompt
+ * @return self
+ */
+ public function setPrompt(string $prompt)
+ {
+ $this->prompt = $prompt;
+
+ return $this;
+ }
+
+ /**
+ * 设置错误提示信息
+ * @param string $errorMessage
+ * @return self
+ */
+ public function setErrorMessage(string $errorMessage)
+ {
+ $this->errorMessage = $errorMessage;
+ $this->setValidator($this->getDefaultValidator());
+
+ return $this;
+ }
+
+ /**
+ * 获取默认的验证方法
+ * @return callable
+ */
+ private function getDefaultValidator()
+ {
+ $choices = $this->choices;
+ $errorMessage = $this->errorMessage;
+ $multiselect = $this->multiselect;
+ $isAssoc = $this->isAssoc($choices);
+
+ return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
+ // Collapse all spaces.
+ $selectedChoices = str_replace(' ', '', $selected);
+
+ if ($multiselect) {
+ // Check for a separated comma values
+ if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) {
+ throw new \InvalidArgumentException(sprintf($errorMessage, $selected));
+ }
+ $selectedChoices = explode(',', $selectedChoices);
+ } else {
+ $selectedChoices = [$selected];
+ }
+
+ $multiselectChoices = [];
+ foreach ($selectedChoices as $value) {
+ $results = [];
+ foreach ($choices as $key => $choice) {
+ if ($choice === $value) {
+ $results[] = $key;
+ }
+ }
+
+ if (count($results) > 1) {
+ throw new \InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results)));
+ }
+
+ $result = array_search($value, $choices);
+
+ if (!$isAssoc) {
+ if (!empty($result)) {
+ $result = $choices[$result];
+ } elseif (isset($choices[$value])) {
+ $result = $choices[$value];
+ }
+ } elseif (empty($result) && array_key_exists($value, $choices)) {
+ $result = $value;
+ }
+
+ if (false === $result) {
+ throw new \InvalidArgumentException(sprintf($errorMessage, $value));
+ }
+ array_push($multiselectChoices, $result);
+ }
+
+ if ($multiselect) {
+ return $multiselectChoices;
+ }
+
+ return current($multiselectChoices);
+ };
+ }
+}
diff --git a/vendor/topthink/framework/src/think/console/output/question/Confirmation.php b/vendor/topthink/framework/src/think/console/output/question/Confirmation.php
new file mode 100755
index 0000000..bf71b5d
--- /dev/null
+++ b/vendor/topthink/framework/src/think/console/output/question/Confirmation.php
@@ -0,0 +1,57 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\question;
+
+use think\console\output\Question;
+
+class Confirmation extends Question
+{
+
+ private $trueAnswerRegex;
+
+ /**
+ * 构造方法
+ * @param string $question 问题
+ * @param bool $default 默认答案
+ * @param string $trueAnswerRegex 验证正则
+ */
+ public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i')
+ {
+ parent::__construct($question, (bool) $default);
+
+ $this->trueAnswerRegex = $trueAnswerRegex;
+ $this->setNormalizer($this->getDefaultNormalizer());
+ }
+
+ /**
+ * 获取默认的答案回调
+ * @return callable
+ */
+ private function getDefaultNormalizer()
+ {
+ $default = $this->getDefault();
+ $regex = $this->trueAnswerRegex;
+
+ return function ($answer) use ($default, $regex) {
+ if (is_bool($answer)) {
+ return $answer;
+ }
+
+ $answerIsTrue = (bool) preg_match($regex, $answer);
+ if (false === $default) {
+ return $answer && $answerIsTrue;
+ }
+
+ return !$answer || $answerIsTrue;
+ };
+ }
+}
diff --git a/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php b/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php
new file mode 100755
index 0000000..da5e696
--- /dev/null
+++ b/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php
@@ -0,0 +1,88 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\contract;
+
+/**
+ * 缓存驱动接口
+ */
+interface CacheHandlerInterface
+{
+ /**
+ * 判断缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function has($name);
+
+ /**
+ * 读取缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get($name, $default = null);
+
+ /**
+ * 写入缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @param mixed $value 存储数据
+ * @param integer|\DateTime $expire 有效时间(秒)
+ * @return bool
+ */
+ public function set($name, $value, $expire = null);
+
+ /**
+ * 自增缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function inc(string $name, int $step = 1);
+
+ /**
+ * 自减缓存(针对数值缓存)
+ * @access public
+ * @param string $name 缓存变量名
+ * @param int $step 步长
+ * @return false|int
+ */
+ public function dec(string $name, int $step = 1);
+
+ /**
+ * 删除缓存
+ * @access public
+ * @param string $name 缓存变量名
+ * @return bool
+ */
+ public function delete($name);
+
+ /**
+ * 清除缓存
+ * @access public
+ * @return bool
+ */
+ public function clear();
+
+ /**
+ * 删除缓存标签
+ * @access public
+ * @param array $keys 缓存标识列表
+ * @return void
+ */
+ public function clearTag(array $keys);
+
+}
diff --git a/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php b/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php
new file mode 100755
index 0000000..896ac29
--- /dev/null
+++ b/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php
@@ -0,0 +1,28 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\contract;
+
+/**
+ * 日志驱动接口
+ */
+interface LogHandlerInterface
+{
+ /**
+ * 日志写入接口
+ * @access public
+ * @param array $log 日志信息
+ * @return bool
+ */
+ public function save(array $log): bool;
+
+}
diff --git a/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php b/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php
new file mode 100755
index 0000000..1f6f994
--- /dev/null
+++ b/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php
@@ -0,0 +1,99 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\contract;
+
+use Closure;
+use think\Collection;
+use think\db\Query;
+use think\Model;
+
+/**
+ * 模型关联接口
+ */
+interface ModelRelationInterface
+{
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联
+ * @param Closure $closure 闭包查询条件
+ * @return Collection
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null): Collection;
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包条件
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null): void;
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包条件
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null): void;
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 模型对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return integer
+ */
+ public function relationCount(Model $result, Closure $closure, string $aggregate = 'count', string $field = '*', string &$name = null);
+
+ /**
+ * 创建关联统计子查询
+ * @access public
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return string
+ */
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string;
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @return Query
+ */
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER'): Query;
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @return Query
+ */
+ public function hasWhere($where = [], $fields = null, string $joinType = ''): Query;
+}
diff --git a/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php b/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php
new file mode 100755
index 0000000..0b2e414
--- /dev/null
+++ b/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php
@@ -0,0 +1,23 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\contract;
+
+/**
+ * Session驱动接口
+ */
+interface SessionHandlerInterface
+{
+ public function read(string $sessionId): string;
+ public function delete(string $sessionId): bool;
+ public function write(string $sessionId, string $data): bool;
+}
diff --git a/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php b/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php
new file mode 100755
index 0000000..9be93d2
--- /dev/null
+++ b/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php
@@ -0,0 +1,61 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\contract;
+
+/**
+ * 视图驱动接口
+ */
+interface TemplateHandlerInterface
+{
+ /**
+ * 检测是否存在模板文件
+ * @access public
+ * @param string $template 模板文件或者模板规则
+ * @return bool
+ */
+ public function exists(string $template): bool;
+
+ /**
+ * 渲染模板文件
+ * @access public
+ * @param string $template 模板文件
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function fetch(string $template, array $data = []): void;
+
+ /**
+ * 渲染模板内容
+ * @access public
+ * @param string $content 模板内容
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function display(string $content, array $data = []): void;
+
+ /**
+ * 配置模板引擎
+ * @access private
+ * @param array $config 参数
+ * @return void
+ */
+ public function config(array $config): void;
+
+ /**
+ * 获取模板引擎配置
+ * @access public
+ * @param string $name 参数名
+ * @return void
+ */
+ public function getConfig(string $name);
+}
diff --git a/vendor/topthink/framework/src/think/event/AppInit.php b/vendor/topthink/framework/src/think/event/AppInit.php
new file mode 100755
index 0000000..dda820b
--- /dev/null
+++ b/vendor/topthink/framework/src/think/event/AppInit.php
@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\event;
+
+/**
+ * AppInit事件类
+ */
+class AppInit
+{}
diff --git a/vendor/topthink/framework/src/think/event/HttpEnd.php b/vendor/topthink/framework/src/think/event/HttpEnd.php
new file mode 100755
index 0000000..c40da57
--- /dev/null
+++ b/vendor/topthink/framework/src/think/event/HttpEnd.php
@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\event;
+
+/**
+ * HttpEnd事件类
+ */
+class HttpEnd
+{}
diff --git a/vendor/topthink/framework/src/think/event/HttpRun.php b/vendor/topthink/framework/src/think/event/HttpRun.php
new file mode 100755
index 0000000..ce67e93
--- /dev/null
+++ b/vendor/topthink/framework/src/think/event/HttpRun.php
@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\event;
+
+/**
+ * HttpRun事件类
+ */
+class HttpRun
+{}
diff --git a/vendor/topthink/framework/src/think/event/LogRecord.php b/vendor/topthink/framework/src/think/event/LogRecord.php
new file mode 100755
index 0000000..237468d
--- /dev/null
+++ b/vendor/topthink/framework/src/think/event/LogRecord.php
@@ -0,0 +1,29 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\event;
+
+/**
+ * LogRecord事件类
+ */
+class LogRecord
+{
+ /** @var string */
+ public $type;
+
+ /** @var string */
+ public $message;
+
+ public function __construct($type, $message)
+ {
+ $this->type = $type;
+ $this->message = $message;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/event/LogWrite.php b/vendor/topthink/framework/src/think/event/LogWrite.php
new file mode 100755
index 0000000..a787301
--- /dev/null
+++ b/vendor/topthink/framework/src/think/event/LogWrite.php
@@ -0,0 +1,31 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\event;
+
+/**
+ * LogWrite事件类
+ */
+class LogWrite
+{
+ /** @var string */
+ public $channel;
+
+ /** @var array */
+ public $log;
+
+ public function __construct($channel, $log)
+ {
+ $this->channel = $channel;
+ $this->log = $log;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/event/RouteLoaded.php b/vendor/topthink/framework/src/think/event/RouteLoaded.php
new file mode 100755
index 0000000..ace7992
--- /dev/null
+++ b/vendor/topthink/framework/src/think/event/RouteLoaded.php
@@ -0,0 +1,21 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\event;
+
+/**
+ * 路由加载完成事件
+ */
+class RouteLoaded
+{
+
+}
diff --git a/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php b/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php
new file mode 100755
index 0000000..c4cda77
--- /dev/null
+++ b/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php
@@ -0,0 +1,39 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\exception;
+
+use Psr\Container\NotFoundExceptionInterface;
+use RuntimeException;
+use Throwable;
+
+class ClassNotFoundException extends RuntimeException implements NotFoundExceptionInterface
+{
+ protected $class;
+
+ public function __construct(string $message, string $class = '', Throwable $previous = null)
+ {
+ $this->message = $message;
+ $this->class = $class;
+
+ parent::__construct($message, 0, $previous);
+ }
+
+ /**
+ * 获取类名
+ * @access public
+ * @return string
+ */
+ public function getClass()
+ {
+ return $this->class;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/exception/ErrorException.php b/vendor/topthink/framework/src/think/exception/ErrorException.php
new file mode 100755
index 0000000..d1a2378
--- /dev/null
+++ b/vendor/topthink/framework/src/think/exception/ErrorException.php
@@ -0,0 +1,57 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\exception;
+
+use think\Exception;
+
+/**
+ * ThinkPHP错误异常
+ * 主要用于封装 set_error_handler 和 register_shutdown_function 得到的错误
+ * 除开从 think\Exception 继承的功能
+ * 其他和PHP系统\ErrorException功能基本一样
+ */
+class ErrorException extends Exception
+{
+ /**
+ * 用于保存错误级别
+ * @var integer
+ */
+ protected $severity;
+
+ /**
+ * 错误异常构造函数
+ * @access public
+ * @param integer $severity 错误级别
+ * @param string $message 错误详细信息
+ * @param string $file 出错文件路径
+ * @param integer $line 出错行号
+ */
+ public function __construct(int $severity, string $message, string $file, int $line)
+ {
+ $this->severity = $severity;
+ $this->message = $message;
+ $this->file = $file;
+ $this->line = $line;
+ $this->code = 0;
+ }
+
+ /**
+ * 获取错误级别
+ * @access public
+ * @return integer 错误级别
+ */
+ final public function getSeverity()
+ {
+ return $this->severity;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/exception/FileException.php b/vendor/topthink/framework/src/think/exception/FileException.php
new file mode 100755
index 0000000..228a189
--- /dev/null
+++ b/vendor/topthink/framework/src/think/exception/FileException.php
@@ -0,0 +1,17 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\exception;
+
+class FileException extends \RuntimeException
+{
+}
diff --git a/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php b/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php
new file mode 100755
index 0000000..ee2bcad
--- /dev/null
+++ b/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace think\exception;
+
+use Psr\Container\NotFoundExceptionInterface;
+use RuntimeException;
+use Throwable;
+
+class FuncNotFoundException extends RuntimeException implements NotFoundExceptionInterface
+{
+ protected $func;
+
+ public function __construct(string $message, string $func = '', Throwable $previous = null)
+ {
+ $this->message = $message;
+ $this->func = $func;
+
+ parent::__construct($message, 0, $previous);
+ }
+
+ /**
+ * 获取方法名
+ * @access public
+ * @return string
+ */
+ public function getFunc()
+ {
+ return $this->func;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/exception/Handle.php b/vendor/topthink/framework/src/think/exception/Handle.php
new file mode 100755
index 0000000..1f783bc
--- /dev/null
+++ b/vendor/topthink/framework/src/think/exception/Handle.php
@@ -0,0 +1,332 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\exception;
+
+use Exception;
+use think\App;
+use think\console\Output;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\ModelNotFoundException;
+use think\Request;
+use think\Response;
+use Throwable;
+
+/**
+ * 系统异常处理类
+ */
+class Handle
+{
+ /** @var App */
+ protected $app;
+
+ protected $ignoreReport = [
+ HttpException::class,
+ HttpResponseException::class,
+ ModelNotFoundException::class,
+ DataNotFoundException::class,
+ ValidateException::class,
+ ];
+
+ protected $isJson = false;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * Report or log an exception.
+ *
+ * @access public
+ * @param Throwable $exception
+ * @return void
+ */
+ public function report(Throwable $exception): void
+ {
+ if (!$this->isIgnoreReport($exception)) {
+ // 收集异常数据
+ if ($this->app->isDebug()) {
+ $data = [
+ 'file' => $exception->getFile(),
+ 'line' => $exception->getLine(),
+ 'message' => $this->getMessage($exception),
+ 'code' => $this->getCode($exception),
+ ];
+ $log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]";
+ } else {
+ $data = [
+ 'code' => $this->getCode($exception),
+ 'message' => $this->getMessage($exception),
+ ];
+ $log = "[{$data['code']}]{$data['message']}";
+ }
+
+ if ($this->app->config->get('log.record_trace')) {
+ $log .= PHP_EOL . $exception->getTraceAsString();
+ }
+
+ try {
+ $this->app->log->record($log, 'error');
+ } catch (Exception $e) {}
+ }
+ }
+
+ protected function isIgnoreReport(Throwable $exception): bool
+ {
+ foreach ($this->ignoreReport as $class) {
+ if ($exception instanceof $class) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Render an exception into an HTTP response.
+ *
+ * @access public
+ * @param Request $request
+ * @param Throwable $e
+ * @return Response
+ */
+ public function render($request, Throwable $e): Response
+ {
+ $this->isJson = $request->isJson();
+ if ($e instanceof HttpResponseException) {
+ return $e->getResponse();
+ } elseif ($e instanceof HttpException) {
+ return $this->renderHttpException($e);
+ } else {
+ return $this->convertExceptionToResponse($e);
+ }
+ }
+
+ /**
+ * @access public
+ * @param Output $output
+ * @param Throwable $e
+ */
+ public function renderForConsole(Output $output, Throwable $e): void
+ {
+ if ($this->app->isDebug()) {
+ $output->setVerbosity(Output::VERBOSITY_DEBUG);
+ }
+
+ $output->renderException($e);
+ }
+
+ /**
+ * @access protected
+ * @param HttpException $e
+ * @return Response
+ */
+ protected function renderHttpException(HttpException $e): Response
+ {
+ $status = $e->getStatusCode();
+ $template = $this->app->config->get('app.http_exception_template');
+
+ if (!$this->app->isDebug() && !empty($template[$status])) {
+ return Response::create($template[$status], 'view', $status)->assign(['e' => $e]);
+ } else {
+ return $this->convertExceptionToResponse($e);
+ }
+ }
+
+ /**
+ * 收集异常数据
+ * @param Throwable $exception
+ * @return array
+ */
+ protected function convertExceptionToArray(Throwable $exception): array
+ {
+ if ($this->app->isDebug()) {
+ // 调试模式,获取详细的错误信息
+ $traces = [];
+ $nextException = $exception;
+ do {
+ $traces[] = [
+ 'name' => get_class($nextException),
+ 'file' => $nextException->getFile(),
+ 'line' => $nextException->getLine(),
+ 'code' => $this->getCode($nextException),
+ 'message' => $this->getMessage($nextException),
+ 'trace' => $nextException->getTrace(),
+ 'source' => $this->getSourceCode($nextException),
+ ];
+ } while ($nextException = $nextException->getPrevious());
+ $data = [
+ 'code' => $this->getCode($exception),
+ 'message' => $this->getMessage($exception),
+ 'traces' => $traces,
+ 'datas' => $this->getExtendData($exception),
+ 'tables' => [
+ 'GET Data' => $this->app->request->get(),
+ 'POST Data' => $this->app->request->post(),
+ 'Files' => $this->app->request->file(),
+ 'Cookies' => $this->app->request->cookie(),
+ 'Session' => $this->app->exists('session') ? $this->app->session->all() : [],
+ 'Server/Request Data' => $this->app->request->server(),
+ ],
+ ];
+ } else {
+ // 部署模式仅显示 Code 和 Message
+ $data = [
+ 'code' => $this->getCode($exception),
+ 'message' => $this->getMessage($exception),
+ ];
+
+ if (!$this->app->config->get('app.show_error_msg')) {
+ // 不显示详细错误信息
+ $data['message'] = $this->app->config->get('app.error_message');
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * @access protected
+ * @param Throwable $exception
+ * @return Response
+ */
+ protected function convertExceptionToResponse(Throwable $exception): Response
+ {
+ if (!$this->isJson) {
+ $response = Response::create($this->renderExceptionContent($exception));
+ } else {
+ $response = Response::create($this->convertExceptionToArray($exception), 'json');
+ }
+
+ if ($exception instanceof HttpException) {
+ $statusCode = $exception->getStatusCode();
+ $response->header($exception->getHeaders());
+ }
+
+ return $response->code($statusCode ?? 500);
+ }
+
+ protected function renderExceptionContent(Throwable $exception): string
+ {
+ ob_start();
+ $data = $this->convertExceptionToArray($exception);
+ extract($data);
+ include $this->app->config->get('app.exception_tmpl') ?: __DIR__ . '/../../tpl/think_exception.tpl';
+
+ return ob_get_clean();
+ }
+
+ /**
+ * 获取错误编码
+ * ErrorException则使用错误级别作为错误编码
+ * @access protected
+ * @param Throwable $exception
+ * @return integer 错误编码
+ */
+ protected function getCode(Throwable $exception)
+ {
+ $code = $exception->getCode();
+
+ if (!$code && $exception instanceof ErrorException) {
+ $code = $exception->getSeverity();
+ }
+
+ return $code;
+ }
+
+ /**
+ * 获取错误信息
+ * ErrorException则使用错误级别作为错误编码
+ * @access protected
+ * @param Throwable $exception
+ * @return string 错误信息
+ */
+ protected function getMessage(Throwable $exception): string
+ {
+ $message = $exception->getMessage();
+
+ if ($this->app->runningInConsole()) {
+ return $message;
+ }
+
+ $lang = $this->app->lang;
+
+ if (strpos($message, ':')) {
+ $name = strstr($message, ':', true);
+ $message = $lang->has($name) ? $lang->get($name) . strstr($message, ':') : $message;
+ } elseif (strpos($message, ',')) {
+ $name = strstr($message, ',', true);
+ $message = $lang->has($name) ? $lang->get($name) . ':' . substr(strstr($message, ','), 1) : $message;
+ } elseif ($lang->has($message)) {
+ $message = $lang->get($message);
+ }
+
+ return $message;
+ }
+
+ /**
+ * 获取出错文件内容
+ * 获取错误的前9行和后9行
+ * @access protected
+ * @param Throwable $exception
+ * @return array 错误文件内容
+ */
+ protected function getSourceCode(Throwable $exception): array
+ {
+ // 读取前9行和后9行
+ $line = $exception->getLine();
+ $first = ($line - 9 > 0) ? $line - 9 : 1;
+
+ try {
+ $contents = file($exception->getFile()) ?: [];
+ $source = [
+ 'first' => $first,
+ 'source' => array_slice($contents, $first - 1, 19),
+ ];
+ } catch (Exception $e) {
+ $source = [];
+ }
+
+ return $source;
+ }
+
+ /**
+ * 获取异常扩展信息
+ * 用于非调试模式html返回类型显示
+ * @access protected
+ * @param Throwable $exception
+ * @return array 异常类定义的扩展数据
+ */
+ protected function getExtendData(Throwable $exception): array
+ {
+ $data = [];
+
+ if ($exception instanceof \think\Exception) {
+ $data = $exception->getData();
+ }
+
+ return $data;
+ }
+
+ /**
+ * 获取常量列表
+ * @access protected
+ * @return array 常量列表
+ */
+ protected function getConst(): array
+ {
+ $const = get_defined_constants(true);
+
+ return $const['user'] ?? [];
+ }
+}
diff --git a/vendor/topthink/framework/src/think/exception/HttpException.php b/vendor/topthink/framework/src/think/exception/HttpException.php
new file mode 100755
index 0000000..45302e5
--- /dev/null
+++ b/vendor/topthink/framework/src/think/exception/HttpException.php
@@ -0,0 +1,42 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\exception;
+
+use Exception;
+
+/**
+ * HTTP异常
+ */
+class HttpException extends \RuntimeException
+{
+ private $statusCode;
+ private $headers;
+
+ public function __construct(int $statusCode, string $message = '', Exception $previous = null, array $headers = [], $code = 0)
+ {
+ $this->statusCode = $statusCode;
+ $this->headers = $headers;
+
+ parent::__construct($message, $code, $previous);
+ }
+
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/exception/HttpResponseException.php b/vendor/topthink/framework/src/think/exception/HttpResponseException.php
new file mode 100755
index 0000000..607813d
--- /dev/null
+++ b/vendor/topthink/framework/src/think/exception/HttpResponseException.php
@@ -0,0 +1,37 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\exception;
+
+use think\Response;
+
+/**
+ * HTTP响应异常
+ */
+class HttpResponseException extends \RuntimeException
+{
+ /**
+ * @var Response
+ */
+ protected $response;
+
+ public function __construct(Response $response)
+ {
+ $this->response = $response;
+ }
+
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php b/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php
new file mode 100755
index 0000000..8ccd6f6
--- /dev/null
+++ b/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php
@@ -0,0 +1,22 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+namespace think\exception;
+
+use Psr\Cache\InvalidArgumentException as Psr6CacheInvalidArgumentInterface;
+use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface;
+
+/**
+ * 非法数据异常
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInvalidArgumentInterface, SimpleCacheInvalidArgumentInterface
+{
+}
diff --git a/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php b/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php
new file mode 100755
index 0000000..7a2ee87
--- /dev/null
+++ b/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php
@@ -0,0 +1,26 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\exception;
+
+/**
+ * 路由未定义异常
+ */
+class RouteNotFoundException extends HttpException
+{
+
+ public function __construct()
+ {
+ parent::__construct(404, 'Route Not Found');
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/exception/ValidateException.php b/vendor/topthink/framework/src/think/exception/ValidateException.php
new file mode 100755
index 0000000..89b4e4d
--- /dev/null
+++ b/vendor/topthink/framework/src/think/exception/ValidateException.php
@@ -0,0 +1,37 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\exception;
+
+/**
+ * 数据验证异常
+ */
+class ValidateException extends \RuntimeException
+{
+ protected $error;
+
+ public function __construct($error)
+ {
+ $this->error = $error;
+ $this->message = is_array($error) ? implode(PHP_EOL, $error) : $error;
+ }
+
+ /**
+ * 获取验证错误信息
+ * @access public
+ * @return array|string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/App.php b/vendor/topthink/framework/src/think/facade/App.php
new file mode 100755
index 0000000..e9f8105
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/App.php
@@ -0,0 +1,59 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\App
+ * @package think\facade
+ * @mixin \think\App
+ * @method static \think\Service|null register(\think\Service|string $service, bool $force = false) 注册服务
+ * @method static mixed bootService(\think\Service $service) 执行服务
+ * @method static \think\Service|null getService(string|\think\Service $service) 获取服务
+ * @method static \think\App debug(bool $debug = true) 开启应用调试模式
+ * @method static bool isDebug() 是否为调试模式
+ * @method static \think\App setNamespace(string $namespace) 设置应用命名空间
+ * @method static string getNamespace() 获取应用类库命名空间
+ * @method static string version() 获取框架版本
+ * @method static string getRootPath() 获取应用根目录
+ * @method static string getBasePath() 获取应用基础目录
+ * @method static string getAppPath() 获取当前应用目录
+ * @method static mixed setAppPath(string $path) 设置应用目录
+ * @method static string getRuntimePath() 获取应用运行时目录
+ * @method static void setRuntimePath(string $path) 设置runtime目录
+ * @method static string getThinkPath() 获取核心框架目录
+ * @method static string getConfigPath() 获取应用配置目录
+ * @method static string getConfigExt() 获取配置后缀
+ * @method static float getBeginTime() 获取应用开启时间
+ * @method static integer getBeginMem() 获取应用初始内存占用
+ * @method static \think\App initialize() 初始化应用
+ * @method static bool initialized() 是否初始化过
+ * @method static void loadLangPack(string $langset) 加载语言包
+ * @method static void boot() 引导应用
+ * @method static void loadEvent(array $event) 注册应用事件
+ * @method static string parseClass(string $layer, string $name) 解析应用类的类名
+ * @method static bool runningInConsole() 是否运行在命令行下
+ */
+class App extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'app';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Cache.php b/vendor/topthink/framework/src/think/facade/Cache.php
new file mode 100755
index 0000000..aac105d
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Cache.php
@@ -0,0 +1,48 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\cache\Driver;
+use think\cache\TagSet;
+use think\Facade;
+
+/**
+ * @see \think\Cache
+ * @package think\facade
+ * @mixin \think\Cache
+ * @method static string|null getDefaultDriver() 默认驱动
+ * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取缓存配置
+ * @method static array getStoreConfig(string $store, string $name = null, null $default = null) 获取驱动配置
+ * @method static Driver store(string $name = null) 连接或者切换缓存
+ * @method static bool clear() 清空缓冲池
+ * @method static mixed get(string $key, mixed $default = null) 读取缓存
+ * @method static bool set(string $key, mixed $value, int|\DateTime $ttl = null) 写入缓存
+ * @method static bool delete(string $key) 删除缓存
+ * @method static iterable getMultiple(iterable $keys, mixed $default = null) 读取缓存
+ * @method static bool setMultiple(iterable $values, null|int|\DateInterval $ttl = null) 写入缓存
+ * @method static bool deleteMultiple(iterable $keys) 删除缓存
+ * @method static bool has(string $key) 判断缓存是否存在
+ * @method static TagSet tag(string|array $name) 缓存标签
+ */
+class Cache extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'cache';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Config.php b/vendor/topthink/framework/src/think/facade/Config.php
new file mode 100755
index 0000000..4ce73dd
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Config.php
@@ -0,0 +1,37 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\Config
+ * @package think\facade
+ * @mixin \think\Config
+ * @method static array load(string $file, string $name = '') 加载配置文件(多种格式)
+ * @method static bool has(string $name) 检测配置是否存在
+ * @method static mixed get(string $name = null, mixed $default = null) 获取配置参数 为空则获取所有配置
+ * @method static array set(array $config, string $name = null) 设置配置参数 name为数组则为批量设置
+ */
+class Config extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'config';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Console.php b/vendor/topthink/framework/src/think/facade/Console.php
new file mode 100755
index 0000000..30dd935
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Console.php
@@ -0,0 +1,56 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Definition as InputDefinition;
+use think\console\Output;
+use think\console\output\driver\Buffer;
+use think\Facade;
+
+/**
+ * Class Console
+ * @package think\facade
+ * @mixin \think\Console
+ * @method static Output|Buffer call(string $command, array $parameters = [], string $driver = 'buffer')
+ * @method static int run() 执行当前的指令
+ * @method static int doRun(Input $input, Output $output) 执行指令
+ * @method static void setDefinition(InputDefinition $definition) 设置输入参数定义
+ * @method static InputDefinition The InputDefinition instance getDefinition() 获取输入参数定义
+ * @method static string A help message. getHelp() Gets the help message.
+ * @method static void setCatchExceptions(bool $boolean) 是否捕获异常
+ * @method static void setAutoExit(bool $boolean) 是否自动退出
+ * @method static string getLongVersion() 获取完整的版本号
+ * @method static void addCommands(array $commands) 添加指令集
+ * @method static Command|void addCommand(string|Command $command, string $name = '') 添加一个指令
+ * @method static Command getCommand(string $name) 获取指令
+ * @method static bool hasCommand(string $name) 某个指令是否存在
+ * @method static array getNamespaces() 获取所有的命名空间
+ * @method static string findNamespace(string $namespace) 查找注册命名空间中的名称或缩写。
+ * @method static Command find(string $name) 查找指令
+ * @method static Command[] all(string $namespace = null) 获取所有的指令
+ * @method static string extractNamespace(string $name, int $limit = 0) 返回命名空间部分
+ */
+class Console extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'console';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Cookie.php b/vendor/topthink/framework/src/think/facade/Cookie.php
new file mode 100755
index 0000000..960f4a3
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Cookie.php
@@ -0,0 +1,40 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\Cookie
+ * @package think\facade
+ * @mixin \think\Cookie
+ * @method static mixed get(mixed $name = '', string $default = null) 获取cookie
+ * @method static bool has(string $name) 是否存在Cookie参数
+ * @method static void set(string $name, string $value, mixed $option = null) Cookie 设置
+ * @method static void forever(string $name, string $value = '', mixed $option = null) 永久保存Cookie数据
+ * @method static void delete(string $name) Cookie删除
+ * @method static array getCookie() 获取cookie保存数据
+ * @method static void save() 保存Cookie
+ */
+class Cookie extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'cookie';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Env.php b/vendor/topthink/framework/src/think/facade/Env.php
new file mode 100755
index 0000000..bed2538
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Env.php
@@ -0,0 +1,44 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\Env
+ * @package think\facade
+ * @mixin \think\Env
+ * @method static void load(string $file) 读取环境变量定义文件
+ * @method static mixed get(string $name = null, mixed $default = null) 获取环境变量值
+ * @method static void set(string|array $env, mixed $value = null) 设置环境变量值
+ * @method static bool has(string $name) 检测是否存在环境变量
+ * @method static void __set(string $name, mixed $value) 设置环境变量
+ * @method static mixed __get(string $name) 获取环境变量
+ * @method static bool __isset(string $name) 检测是否存在环境变量
+ * @method static void offsetSet($name, $value)
+ * @method static bool offsetExists($name)
+ * @method static mixed offsetUnset($name)
+ * @method static mixed offsetGet($name)
+ */
+class Env extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'env';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Event.php b/vendor/topthink/framework/src/think/facade/Event.php
new file mode 100755
index 0000000..c09d816
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Event.php
@@ -0,0 +1,42 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\Event
+ * @package think\facade
+ * @mixin \think\Event
+ * @method static \think\Event listenEvents(array $events) 批量注册事件监听
+ * @method static \think\Event listen(string $event, mixed $listener, bool $first = false) 注册事件监听
+ * @method static bool hasListener(string $event) 是否存在事件监听
+ * @method static void remove(string $event) 移除事件监听
+ * @method static \think\Event bind(array $events) 指定事件别名标识 便于调用
+ * @method static \think\Event subscribe(mixed $subscriber) 注册事件订阅者
+ * @method static \think\Event observe(string|object $observer, null|string $prefix = '') 自动注册事件观察者
+ * @method static mixed trigger(string|object $event, mixed $params = null, bool $once = false) 触发事件
+ * @method static mixed until($event, $params = null) 触发事件(只获取一个有效返回值)
+ */
+class Event extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'event';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Lang.php b/vendor/topthink/framework/src/think/facade/Lang.php
new file mode 100755
index 0000000..b460fe2
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Lang.php
@@ -0,0 +1,41 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\Lang
+ * @package think\facade
+ * @mixin \think\Lang
+ * @method static void setLangSet(string $lang) 设置当前语言
+ * @method static string getLangSet() 获取当前语言
+ * @method static string defaultLangSet() 获取默认语言
+ * @method static array load(string|array $file, string $range = '') 加载语言定义(不区分大小写)
+ * @method static bool has(string|null $name, string $range = '') 判断是否存在语言定义(不区分大小写)
+ * @method static mixed get(string|null $name = null, array $vars = [], string $range = '') 获取语言定义(不区分大小写)
+ * @method static string detect(\think\Request $request) 自动侦测设置获取语言选择
+ * @method static void saveToCookie(\think\Cookie $cookie) 保存当前语言到Cookie
+ */
+class Lang extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'lang';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Log.php b/vendor/topthink/framework/src/think/facade/Log.php
new file mode 100755
index 0000000..7c43d37
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Log.php
@@ -0,0 +1,58 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+use think\log\Channel;
+use think\log\ChannelSet;
+
+/**
+ * @see \think\Log
+ * @package think\facade
+ * @mixin \think\Log
+ * @method static string|null getDefaultDriver() 默认驱动
+ * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取日志配置
+ * @method static array getChannelConfig(string $channel, null $name = null, null $default = null) 获取渠道配置
+ * @method static Channel|ChannelSet channel(string|array $name = null) driver() 的别名
+ * @method static mixed createDriver(string $name)
+ * @method static \think\Log clear(string|array $channel = '*') 清空日志信息
+ * @method static \think\Log close(string|array $channel = '*') 关闭本次请求日志写入
+ * @method static array getLog(string $channel = null) 获取日志信息
+ * @method static bool save() 保存日志信息
+ * @method static \think\Log record(mixed $msg, string $type = 'info', array $context = [], bool $lazy = true) 记录日志信息
+ * @method static \think\Log write(mixed $msg, string $type = 'info', array $context = []) 实时写入日志信息
+ * @method static Event listen($listener) 注册日志写入事件监听
+ * @method static void log(string $level, mixed $message, array $context = []) 记录日志信息
+ * @method static void emergency(mixed $message, array $context = []) 记录emergency信息
+ * @method static void alert(mixed $message, array $context = []) 记录警报信息
+ * @method static void critical(mixed $message, array $context = []) 记录紧急情况
+ * @method static void error(mixed $message, array $context = []) 记录错误信息
+ * @method static void warning(mixed $message, array $context = []) 记录warning信息
+ * @method static void notice(mixed $message, array $context = []) 记录notice信息
+ * @method static void info(mixed $message, array $context = []) 记录一般信息
+ * @method static void debug(mixed $message, array $context = []) 记录调试信息
+ * @method static void sql(mixed $message, array $context = []) 记录sql信息
+ * @method static mixed __call($method, $parameters)
+ */
+class Log extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'log';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Middleware.php b/vendor/topthink/framework/src/think/facade/Middleware.php
new file mode 100755
index 0000000..4203f82
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Middleware.php
@@ -0,0 +1,42 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\Middleware
+ * @package think\facade
+ * @mixin \think\Middleware
+ * @method static void import(array $middlewares = [], string $type = 'global') 导入中间件
+ * @method static void add(mixed $middleware, string $type = 'global') 注册中间件
+ * @method static void route(mixed $middleware) 注册路由中间件
+ * @method static void controller(mixed $middleware) 注册控制器中间件
+ * @method static mixed unshift(mixed $middleware, string $type = 'global') 注册中间件到开始位置
+ * @method static array all(string $type = 'global') 获取注册的中间件
+ * @method static Pipeline pipeline(string $type = 'global') 调度管道
+ * @method static mixed end(\think\Response $response) 结束调度
+ * @method static \think\Response handleException(\think\Request $passable, \Throwable $e) 异常处理
+ */
+class Middleware extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'middleware';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Request.php b/vendor/topthink/framework/src/think/facade/Request.php
new file mode 100755
index 0000000..6531f46
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Request.php
@@ -0,0 +1,134 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+use think\file\UploadedFile;
+use think\route\Rule;
+
+/**
+ * @see \think\Request
+ * @package think\facade
+ * @mixin \think\Request
+ * @method static \think\Request setDomain(string $domain) 设置当前包含协议的域名
+ * @method static string domain(bool $port = false) 获取当前包含协议的域名
+ * @method static string rootDomain() 获取当前根域名
+ * @method static \think\Request setSubDomain(string $domain) 设置当前泛域名的值
+ * @method static string subDomain() 获取当前子域名
+ * @method static \think\Request setPanDomain(string $domain) 设置当前泛域名的值
+ * @method static string panDomain() 获取当前泛域名的值
+ * @method static \think\Request setUrl(string $url) 设置当前完整URL 包括QUERY_STRING
+ * @method static string url(bool $complete = false) 获取当前完整URL 包括QUERY_STRING
+ * @method static \think\Request setBaseUrl(string $url) 设置当前URL 不含QUERY_STRING
+ * @method static string baseUrl(bool $complete = false) 获取当前URL 不含QUERY_STRING
+ * @method static string baseFile(bool $complete = false) 获取当前执行的文件 SCRIPT_NAME
+ * @method static \think\Request setRoot(string $url) 设置URL访问根地址
+ * @method static string root(bool $complete = false) 获取URL访问根地址
+ * @method static string rootUrl() 获取URL访问根目录
+ * @method static \think\Request setPathinfo(string $pathinfo) 设置当前请求的pathinfo
+ * @method static string pathinfo() 获取当前请求URL的pathinfo信息(含URL后缀)
+ * @method static string ext() 当前URL的访问后缀
+ * @method static integer|float time(bool $float = false) 获取当前请求的时间
+ * @method static string type() 当前请求的资源类型
+ * @method static void mimeType(string|array $type, string $val = '') 设置资源类型
+ * @method static \think\Request setMethod(string $method) 设置请求类型
+ * @method static string method(bool $origin = false) 当前的请求类型
+ * @method static bool isGet() 是否为GET请求
+ * @method static bool isPost() 是否为POST请求
+ * @method static bool isPut() 是否为PUT请求
+ * @method static bool isDelete() 是否为DELTE请求
+ * @method static bool isHead() 是否为HEAD请求
+ * @method static bool isPatch() 是否为PATCH请求
+ * @method static bool isOptions() 是否为OPTIONS请求
+ * @method static bool isCli() 是否为cli
+ * @method static bool isCgi() 是否为cgi
+ * @method static mixed param(string|array $name = '', mixed $default = null, string|array $filter = '') 获取当前请求的参数
+ * @method static \think\Request setRule(Rule $rule) 设置路由变量
+ * @method static Rule|null rule() 获取当前路由对象
+ * @method static \think\Request setRoute(array $route) 设置路由变量
+ * @method static mixed route(string|array $name = '', mixed $default = null, string|array $filter = '') 获取路由参数
+ * @method static mixed get(string|array $name = '', mixed $default = null, string|array $filter = '') 获取GET参数
+ * @method static mixed middleware(mixed $name, mixed $default = null) 获取中间件传递的参数
+ * @method static mixed post(string|array $name = '', mixed $default = null, string|array $filter = '') 获取POST参数
+ * @method static mixed put(string|array $name = '', mixed $default = null, string|array $filter = '') 获取PUT参数
+ * @method static mixed delete(mixed $name = '', mixed $default = null, string|array $filter = '') 设置获取DELETE参数
+ * @method static mixed patch(mixed $name = '', mixed $default = null, string|array $filter = '') 设置获取PATCH参数
+ * @method static mixed request(string|array $name = '', mixed $default = null, string|array $filter = '') 获取request变量
+ * @method static mixed env(string $name = '', string $default = null) 获取环境变量
+ * @method static mixed session(string $name = '', string $default = null) 获取session数据
+ * @method static mixed cookie(mixed $name = '', string $default = null, string|array $filter = '') 获取cookie参数
+ * @method static mixed server(string $name = '', string $default = '') 获取server参数
+ * @method static null|array|UploadedFile file(string $name = '') 获取上传的文件信息
+ * @method static string|array header(string $name = '', string $default = null) 设置或者获取当前的Header
+ * @method static mixed input(array $data = [], string|false $name = '', mixed $default = null, string|array $filter = '') 获取变量 支持过滤和默认值
+ * @method static mixed filter(mixed $filter = null) 设置或获取当前的过滤规则
+ * @method static mixed filterValue(mixed &$value, mixed $key, array $filters) 递归过滤给定的值
+ * @method static bool has(string $name, string $type = 'param', bool $checkEmpty = false) 是否存在某个请求参数
+ * @method static array only(array $name, mixed $data = 'param', string|array $filter = '') 获取指定的参数
+ * @method static mixed except(array $name, string $type = 'param') 排除指定参数获取
+ * @method static bool isSsl() 当前是否ssl
+ * @method static bool isJson() 当前是否JSON请求
+ * @method static bool isAjax(bool $ajax = false) 当前是否Ajax请求
+ * @method static bool isPjax(bool $pjax = false) 当前是否Pjax请求
+ * @method static string ip() 获取客户端IP地址
+ * @method static boolean isValidIP(string $ip, string $type = '') 检测是否是合法的IP地址
+ * @method static string ip2bin(string $ip) 将IP地址转换为二进制字符串
+ * @method static bool isMobile() 检测是否使用手机访问
+ * @method static string scheme() 当前URL地址中的scheme参数
+ * @method static string query() 当前请求URL地址中的query参数
+ * @method static \think\Request setHost(string $host) 设置当前请求的host(包含端口)
+ * @method static string host(bool $strict = false) 当前请求的host
+ * @method static int port() 当前请求URL地址中的port参数
+ * @method static string protocol() 当前请求 SERVER_PROTOCOL
+ * @method static int remotePort() 当前请求 REMOTE_PORT
+ * @method static string contentType() 当前请求 HTTP_CONTENT_TYPE
+ * @method static string secureKey() 获取当前请求的安全Key
+ * @method static \think\Request setController(string $controller) 设置当前的控制器名
+ * @method static \think\Request setAction(string $action) 设置当前的操作名
+ * @method static string controller(bool $convert = false) 获取当前的控制器名
+ * @method static string action(bool $convert = false) 获取当前的操作名
+ * @method static string getContent() 设置或者获取当前请求的content
+ * @method static string getInput() 获取当前请求的php://input
+ * @method static string buildToken(string $name = '__token__', mixed $type = 'md5') 生成请求令牌
+ * @method static bool checkToken(string $token = '__token__', array $data = []) 检查请求令牌
+ * @method static \think\Request withMiddleware(array $middleware) 设置在中间件传递的数据
+ * @method static \think\Request withGet(array $get) 设置GET数据
+ * @method static \think\Request withPost(array $post) 设置POST数据
+ * @method static \think\Request withCookie(array $cookie) 设置COOKIE数据
+ * @method static \think\Request withSession(Session $session) 设置SESSION数据
+ * @method static \think\Request withServer(array $server) 设置SERVER数据
+ * @method static \think\Request withHeader(array $header) 设置HEADER数据
+ * @method static \think\Request withEnv(Env $env) 设置ENV数据
+ * @method static \think\Request withInput(string $input) 设置php://input数据
+ * @method static \think\Request withFiles(array $files) 设置文件上传数据
+ * @method static \think\Request withRoute(array $route) 设置ROUTE变量
+ * @method static mixed __set(string $name, mixed $value) 设置中间传递数据
+ * @method static mixed __get(string $name) 获取中间传递数据的值
+ * @method static boolean __isset(string $name) 检测中间传递数据的值
+ * @method static bool offsetExists($name)
+ * @method static mixed offsetGet($name)
+ * @method static mixed offsetSet($name, $value)
+ * @method static mixed offsetUnset($name)
+ */
+class Request extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'request';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Route.php b/vendor/topthink/framework/src/think/facade/Route.php
new file mode 100755
index 0000000..37504ce
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Route.php
@@ -0,0 +1,84 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+use think\route\Dispatch;
+use think\route\Domain;
+use think\route\Rule;
+use think\route\RuleGroup;
+use think\route\RuleItem;
+use think\route\RuleName;
+use think\route\Url as UrlBuild;
+
+/**
+ * @see \think\Route
+ * @package think\facade
+ * @mixin \think\Route
+ * @method static mixed config(string $name = null)
+ * @method static \think\Route lazy(bool $lazy = true) 设置路由域名及分组(包括资源路由)是否延迟解析
+ * @method static void setTestMode(bool $test) 设置路由为测试模式
+ * @method static bool isTest() 检查路由是否为测试模式
+ * @method static \think\Route mergeRuleRegex(bool $merge = true) 设置路由域名及分组(包括资源路由)是否合并解析
+ * @method static void setGroup(RuleGroup $group) 设置当前分组
+ * @method static RuleGroup getGroup(string $name = null) 获取指定标识的路由分组 不指定则获取当前分组
+ * @method static \think\Route pattern(array $pattern) 注册变量规则
+ * @method static \think\Route option(array $option) 注册路由参数
+ * @method static Domain domain(string|array $name, mixed $rule = null) 注册域名路由
+ * @method static array getDomains() 获取域名
+ * @method static RuleName getRuleName() 获取RuleName对象
+ * @method static \think\Route bind(string $bind, string $domain = null) 设置路由绑定
+ * @method static array getBind() 读取路由绑定信息
+ * @method static string|null getDomainBind(string $domain = null) 读取路由绑定
+ * @method static RuleItem[] getName(string $name = null, string $domain = null, string $method = '*') 读取路由标识
+ * @method static void import(array $name) 批量导入路由标识
+ * @method static void setName(string $name, RuleItem $ruleItem, bool $first = false) 注册路由标识
+ * @method static void setRule(string $rule, RuleItem $ruleItem = null) 保存路由规则
+ * @method static RuleItem[] getRule(string $rule) 读取路由
+ * @method static array getRuleList() 读取路由列表
+ * @method static void clear() 清空路由规则
+ * @method static RuleItem rule(string $rule, mixed $route = null, string $method = '*') 注册路由规则
+ * @method static \think\Route setCrossDomainRule(Rule $rule, string $method = '*') 设置跨域有效路由规则
+ * @method static RuleGroup group(string|\Closure $name, mixed $route = null) 注册路由分组
+ * @method static RuleItem any(string $rule, mixed $route) 注册路由
+ * @method static RuleItem get(string $rule, mixed $route) 注册GET路由
+ * @method static RuleItem post(string $rule, mixed $route) 注册POST路由
+ * @method static RuleItem put(string $rule, mixed $route) 注册PUT路由
+ * @method static RuleItem delete(string $rule, mixed $route) 注册DELETE路由
+ * @method static RuleItem patch(string $rule, mixed $route) 注册PATCH路由
+ * @method static RuleItem head(string $rule, mixed $route) 注册HEAD路由
+ * @method static RuleItem options(string $rule, mixed $route) 注册OPTIONS路由
+ * @method static Resource resource(string $rule, string $route) 注册资源路由
+ * @method static RuleItem view(string $rule, string $template = '', array $vars = []) 注册视图路由
+ * @method static RuleItem redirect(string $rule, string $route = '', int $status = 301) 注册重定向路由
+ * @method static \think\Route rest(string|array $name, array|bool $resource = []) rest方法定义和修改
+ * @method static array|null getRest(string $name = null) 获取rest方法定义的参数
+ * @method static RuleItem miss(string|\Closure $route, string $method = '*') 注册未匹配路由规则后的处理
+ * @method static Response dispatch(\think\Request $request, Closure|bool $withRoute = true) 路由调度
+ * @method static Dispatch|false check() 检测URL路由
+ * @method static Dispatch url(string $url) 默认URL解析
+ * @method static UrlBuild buildUrl(string $url = '', array $vars = []) URL生成 支持路由反射
+ * @method static RuleGroup __call(string $method, array $args) 设置全局的路由分组参数
+ */
+class Route extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'route';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Session.php b/vendor/topthink/framework/src/think/facade/Session.php
new file mode 100755
index 0000000..68bf993
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Session.php
@@ -0,0 +1,35 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\Session
+ * @package think\facade
+ * @mixin \think\Session
+ * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取Session配置
+ * @method static string|null getDefaultDriver() 默认驱动
+ */
+class Session extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'session';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/Validate.php b/vendor/topthink/framework/src/think/facade/Validate.php
new file mode 100755
index 0000000..6db6d34
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/Validate.php
@@ -0,0 +1,95 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\Validate
+ * @package think\facade
+ * @mixin \think\Validate
+ * @method static void setLang(\think\Lang $lang) 设置Lang对象
+ * @method static void setDb(\think\Db $db) 设置Db对象
+ * @method static void setRequest(\think\Request $request) 设置Request对象
+ * @method static \think\Validate rule(string|array $name, mixed $rule = '') 添加字段验证规则
+ * @method static \think\Validate extend(string $type, callable $callback = null, string $message = null) 注册验证(类型)规则
+ * @method static void setTypeMsg(string|array $type, string $msg = null) 设置验证规则的默认提示信息
+ * @method static Validate message(array $message) 设置提示信息
+ * @method static \think\Validate scene(string $name) 设置验证场景
+ * @method static bool hasScene(string $name) 判断是否存在某个验证场景
+ * @method static \think\Validate batch(bool $batch = true) 设置批量验证
+ * @method static \think\Validate failException(bool $fail = true) 设置验证失败后是否抛出异常
+ * @method static \think\Validate only(array $fields) 指定需要验证的字段列表
+ * @method static \think\Validate remove(string|array $field, mixed $rule = null) 移除某个字段的验证规则
+ * @method static \think\Validate append(string|array $field, mixed $rule = null) 追加某个字段的验证规则
+ * @method static bool check(array $data, array $rules = []) 数据自动验证
+ * @method static bool checkRule(mixed $value, mixed $rules) 根据验证规则验证数据
+ * @method static bool confirm(mixed $value, mixed $rule, array $data = [], string $field = '') 验证是否和某个字段的值一致
+ * @method static bool different(mixed $value, mixed $rule, array $data = []) 验证是否和某个字段的值是否不同
+ * @method static bool egt(mixed $value, mixed $rule, array $data = []) 验证是否大于等于某个值
+ * @method static bool gt(mixed $value, mixed $rule, array $data = []) 验证是否大于某个值
+ * @method static bool elt(mixed $value, mixed $rule, array $data = []) 验证是否小于等于某个值
+ * @method static bool lt(mixed $value, mixed $rule, array $data = []) 验证是否小于某个值
+ * @method static bool eq(mixed $value, mixed $rule) 验证是否等于某个值
+ * @method static bool must(mixed $value, mixed $rule = null) 必须验证
+ * @method static bool is(mixed $value, string $rule, array $data = []) 验证字段值是否为有效格式
+ * @method static bool token(mixed $value, mixed $rule, array $data) 验证表单令牌
+ * @method static bool activeUrl(mixed $value, mixed $rule = 'MX') 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型
+ * @method static bool ip(mixed $value, mixed $rule = 'ipv4') 验证是否有效IP
+ * @method static bool fileExt(mixed $file, mixed $rule) 验证上传文件后缀
+ * @method static bool fileMime(mixed $file, mixed $rule) 验证上传文件类型
+ * @method static bool fileSize(mixed $file, mixed $rule) 验证上传文件大小
+ * @method static bool image(mixed $file, mixed $rule) 验证图片的宽高及类型
+ * @method static bool dateFormat(mixed $value, mixed $rule) 验证时间和日期是否符合指定格式
+ * @method static bool unique(mixed $value, mixed $rule, array $data = [], string $field = '') 验证是否唯一
+ * @method static bool filter(mixed $value, mixed $rule) 使用filter_var方式验证
+ * @method static bool requireIf(mixed $value, mixed $rule, array $data = []) 验证某个字段等于某个值的时候必须
+ * @method static bool requireCallback(mixed $value, mixed $rule, array $data = []) 通过回调方法验证某个字段是否必须
+ * @method static bool requireWith(mixed $value, mixed $rule, array $data = []) 验证某个字段有值的情况下必须
+ * @method static bool requireWithout(mixed $value, mixed $rule, array $data = []) 验证某个字段没有值的情况下必须
+ * @method static bool in(mixed $value, mixed $rule) 验证是否在范围内
+ * @method static bool notIn(mixed $value, mixed $rule) 验证是否不在某个范围
+ * @method static bool between(mixed $value, mixed $rule) between验证数据
+ * @method static bool notBetween(mixed $value, mixed $rule) 使用notbetween验证数据
+ * @method static bool length(mixed $value, mixed $rule) 验证数据长度
+ * @method static bool max(mixed $value, mixed $rule) 验证数据最大长度
+ * @method static bool min(mixed $value, mixed $rule) 验证数据最小长度
+ * @method static bool after(mixed $value, mixed $rule, array $data = []) 验证日期
+ * @method static bool before(mixed $value, mixed $rule, array $data = []) 验证日期
+ * @method static bool afterWith(mixed $value, mixed $rule, array $data = []) 验证日期
+ * @method static bool beforeWith(mixed $value, mixed $rule, array $data = []) 验证日期
+ * @method static bool expire(mixed $value, mixed $rule) 验证有效期
+ * @method static bool allowIp(mixed $value, mixed $rule) 验证IP许可
+ * @method static bool denyIp(mixed $value, mixed $rule) 验证IP禁用
+ * @method static bool regex(mixed $value, mixed $rule) 使用正则验证数据
+ * @method static array|string getError() 获取错误信息
+ * @method static bool __call(string $method, array $args) 动态方法 直接调用is方法进行验证
+ */
+class Validate extends Facade
+{
+ /**
+ * 始终创建新的对象实例
+ * @var bool
+ */
+ protected static $alwaysNewInstance = true;
+
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'validate';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/facade/View.php b/vendor/topthink/framework/src/think/facade/View.php
new file mode 100755
index 0000000..acde3b5
--- /dev/null
+++ b/vendor/topthink/framework/src/think/facade/View.php
@@ -0,0 +1,42 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * @see \think\View
+ * @package think\facade
+ * @mixin \think\View
+ * @method static \think\View engine(string $type = null) 获取模板引擎
+ * @method static \think\View assign(string|array $name, mixed $value = null) 模板变量赋值
+ * @method static \think\View filter(\think\Callable $filter = null) 视图过滤
+ * @method static string fetch(string $template = '', array $vars = []) 解析和获取模板内容 用于输出
+ * @method static string display(string $content, array $vars = []) 渲染内容输出
+ * @method static mixed __set(string $name, mixed $value) 模板变量赋值
+ * @method static mixed __get(string $name) 取得模板显示变量的值
+ * @method static bool __isset(string $name) 检测模板变量是否设置
+ * @method static string|null getDefaultDriver() 默认驱动
+ */
+class View extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'view';
+ }
+}
diff --git a/vendor/topthink/framework/src/think/file/UploadedFile.php b/vendor/topthink/framework/src/think/file/UploadedFile.php
new file mode 100755
index 0000000..7dff766
--- /dev/null
+++ b/vendor/topthink/framework/src/think/file/UploadedFile.php
@@ -0,0 +1,143 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\file;
+
+use think\exception\FileException;
+use think\File;
+
+class UploadedFile extends File
+{
+
+ private $test = false;
+ private $originalName;
+ private $mimeType;
+ private $error;
+
+ public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, bool $test = false)
+ {
+ $this->originalName = $originalName;
+ $this->mimeType = $mimeType ?: 'application/octet-stream';
+ $this->test = $test;
+ $this->error = $error ?: UPLOAD_ERR_OK;
+
+ parent::__construct($path, UPLOAD_ERR_OK === $this->error);
+ }
+
+ public function isValid(): bool
+ {
+ $isOk = UPLOAD_ERR_OK === $this->error;
+
+ return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname());
+ }
+
+ /**
+ * 上传文件
+ * @access public
+ * @param string $directory 保存路径
+ * @param string|null $name 保存的文件名
+ * @return File
+ */
+ public function move(string $directory, string $name = null): File
+ {
+ if ($this->isValid()) {
+ if ($this->test) {
+ return parent::move($directory, $name);
+ }
+
+ $target = $this->getTargetFile($directory, $name);
+
+ set_error_handler(function ($type, $msg) use (&$error) {
+ $error = $msg;
+ });
+
+ $moved = move_uploaded_file($this->getPathname(), (string) $target);
+ restore_error_handler();
+ if (!$moved) {
+ throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
+ }
+
+ @chmod((string) $target, 0666 & ~umask());
+
+ return $target;
+ }
+
+ throw new FileException($this->getErrorMessage());
+ }
+
+ /**
+ * 获取错误信息
+ * @access public
+ * @return string
+ */
+ protected function getErrorMessage(): string
+ {
+ switch ($this->error) {
+ case 1:
+ case 2:
+ $message = 'upload File size exceeds the maximum value';
+ break;
+ case 3:
+ $message = 'only the portion of file is uploaded';
+ break;
+ case 4:
+ $message = 'no file to uploaded';
+ break;
+ case 6:
+ $message = 'upload temp dir not found';
+ break;
+ case 7:
+ $message = 'file write error';
+ break;
+ default:
+ $message = 'unknown upload error';
+ }
+
+ return $message;
+ }
+
+ /**
+ * 获取上传文件类型信息
+ * @return string
+ */
+ public function getOriginalMime(): string
+ {
+ return $this->mimeType;
+ }
+
+ /**
+ * 上传文件名
+ * @return string
+ */
+ public function getOriginalName(): string
+ {
+ return $this->originalName;
+ }
+
+ /**
+ * 获取上传文件扩展名
+ * @return string
+ */
+ public function getOriginalExtension(): string
+ {
+ return pathinfo($this->originalName, PATHINFO_EXTENSION);
+ }
+
+ /**
+ * 获取文件扩展名
+ * @return string
+ */
+ public function extension(): string
+ {
+ return $this->getOriginalExtension();
+ }
+}
diff --git a/vendor/topthink/framework/src/think/initializer/BootService.php b/vendor/topthink/framework/src/think/initializer/BootService.php
new file mode 100755
index 0000000..bab6d39
--- /dev/null
+++ b/vendor/topthink/framework/src/think/initializer/BootService.php
@@ -0,0 +1,26 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\initializer;
+
+use think\App;
+
+/**
+ * 启动系统服务
+ */
+class BootService
+{
+ public function init(App $app)
+ {
+ $app->boot();
+ }
+}
diff --git a/vendor/topthink/framework/src/think/initializer/Error.php b/vendor/topthink/framework/src/think/initializer/Error.php
new file mode 100755
index 0000000..201d947
--- /dev/null
+++ b/vendor/topthink/framework/src/think/initializer/Error.php
@@ -0,0 +1,117 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\initializer;
+
+use think\App;
+use think\console\Output as ConsoleOutput;
+use think\exception\ErrorException;
+use think\exception\Handle;
+use Throwable;
+
+/**
+ * 错误和异常处理
+ */
+class Error
+{
+ /** @var App */
+ protected $app;
+
+ /**
+ * 注册异常处理
+ * @access public
+ * @param App $app
+ * @return void
+ */
+ public function init(App $app)
+ {
+ $this->app = $app;
+ error_reporting(E_ALL);
+ set_error_handler([$this, 'appError']);
+ set_exception_handler([$this, 'appException']);
+ register_shutdown_function([$this, 'appShutdown']);
+ }
+
+ /**
+ * Exception Handler
+ * @access public
+ * @param \Throwable $e
+ */
+ public function appException(Throwable $e): void
+ {
+ $handler = $this->getExceptionHandler();
+
+ $handler->report($e);
+
+ if ($this->app->runningInConsole()) {
+ $handler->renderForConsole(new ConsoleOutput, $e);
+ } else {
+ $handler->render($this->app->request, $e)->send();
+ }
+ }
+
+ /**
+ * Error Handler
+ * @access public
+ * @param integer $errno 错误编号
+ * @param string $errstr 详细错误信息
+ * @param string $errfile 出错的文件
+ * @param integer $errline 出错行号
+ * @throws ErrorException
+ */
+ public function appError(int $errno, string $errstr, string $errfile = '', int $errline = 0): void
+ {
+ $exception = new ErrorException($errno, $errstr, $errfile, $errline);
+
+ if (error_reporting() & $errno) {
+ // 将错误信息托管至 think\exception\ErrorException
+ throw $exception;
+ }
+ }
+
+ /**
+ * Shutdown Handler
+ * @access public
+ */
+ public function appShutdown(): void
+ {
+ if (!is_null($error = error_get_last()) && $this->isFatal($error['type'])) {
+ // 将错误信息托管至think\ErrorException
+ $exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']);
+
+ $this->appException($exception);
+ }
+ }
+
+ /**
+ * 确定错误类型是否致命
+ *
+ * @access protected
+ * @param int $type
+ * @return bool
+ */
+ protected function isFatal(int $type): bool
+ {
+ return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]);
+ }
+
+ /**
+ * Get an instance of the exception handler.
+ *
+ * @access protected
+ * @return Handle
+ */
+ protected function getExceptionHandler()
+ {
+ return $this->app->make(Handle::class);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/initializer/RegisterService.php b/vendor/topthink/framework/src/think/initializer/RegisterService.php
new file mode 100755
index 0000000..b682a0b
--- /dev/null
+++ b/vendor/topthink/framework/src/think/initializer/RegisterService.php
@@ -0,0 +1,48 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\initializer;
+
+use think\App;
+use think\service\ModelService;
+use think\service\PaginatorService;
+use think\service\ValidateService;
+
+/**
+ * 注册系统服务
+ */
+class RegisterService
+{
+
+ protected $services = [
+ PaginatorService::class,
+ ValidateService::class,
+ ModelService::class,
+ ];
+
+ public function init(App $app)
+ {
+ $file = $app->getRootPath() . 'vendor/services.php';
+
+ $services = $this->services;
+
+ if (is_file($file)) {
+ $services = array_merge($services, include $file);
+ }
+
+ foreach ($services as $service) {
+ if (class_exists($service)) {
+ $app->register($service);
+ }
+ }
+ }
+}
diff --git a/vendor/topthink/framework/src/think/log/Channel.php b/vendor/topthink/framework/src/think/log/Channel.php
new file mode 100755
index 0000000..1de96f1
--- /dev/null
+++ b/vendor/topthink/framework/src/think/log/Channel.php
@@ -0,0 +1,286 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\log;
+
+use Psr\Log\LoggerInterface;
+use think\contract\LogHandlerInterface;
+use think\Event;
+use think\event\LogRecord;
+use think\event\LogWrite;
+
+class Channel implements LoggerInterface
+{
+ protected $name;
+ protected $logger;
+ protected $event;
+
+ protected $lazy = true;
+ /**
+ * 日志信息
+ * @var array
+ */
+ protected $log = [];
+
+ /**
+ * 关闭日志
+ * @var array
+ */
+ protected $close = false;
+
+ /**
+ * 允许写入类型
+ * @var array
+ */
+ protected $allow = [];
+
+ public function __construct(string $name, LogHandlerInterface $logger, array $allow, bool $lazy = true, Event $event = null)
+ {
+ $this->name = $name;
+ $this->logger = $logger;
+ $this->allow = $allow;
+ $this->lazy = $lazy;
+ $this->event = $event;
+ }
+
+ /**
+ * 关闭通道
+ */
+ public function close()
+ {
+ $this->clear();
+ $this->close = true;
+ }
+
+ /**
+ * 清空日志
+ */
+ public function clear()
+ {
+ $this->log = [];
+ }
+
+ /**
+ * 记录日志信息
+ * @access public
+ * @param mixed $msg 日志信息
+ * @param string $type 日志级别
+ * @param array $context 替换内容
+ * @param bool $lazy
+ * @return $this
+ */
+ public function record($msg, string $type = 'info', array $context = [], bool $lazy = true)
+ {
+ if ($this->close || (!empty($this->allow) && !in_array($type, $this->allow))) {
+ return $this;
+ }
+
+ if (is_string($msg) && !empty($context)) {
+ $replace = [];
+ foreach ($context as $key => $val) {
+ $replace['{' . $key . '}'] = $val;
+ }
+
+ $msg = strtr($msg, $replace);
+ }
+
+ if (!empty($msg) || 0 === $msg) {
+ $this->log[$type][] = $msg;
+ if ($this->event) {
+ $this->event->trigger(new LogRecord($type, $msg));
+ }
+ }
+
+ if (!$this->lazy || !$lazy) {
+ $this->save();
+ }
+
+ return $this;
+ }
+
+ /**
+ * 实时写入日志信息
+ * @access public
+ * @param mixed $msg 调试信息
+ * @param string $type 日志级别
+ * @param array $context 替换内容
+ * @return $this
+ */
+ public function write($msg, string $type = 'info', array $context = [])
+ {
+ return $this->record($msg, $type, $context, false);
+ }
+
+ /**
+ * 获取日志信息
+ * @return array
+ */
+ public function getLog(): array
+ {
+ return $this->log;
+ }
+
+ /**
+ * 保存日志
+ * @return bool
+ */
+ public function save(): bool
+ {
+ $log = $this->log;
+ if ($this->event) {
+ $event = new LogWrite($this->name, $log);
+ $this->event->trigger($event);
+ $log = $event->log;
+ }
+
+ if ($this->logger->save($log)) {
+ $this->clear();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function emergency($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function log($level, $message, array $context = [])
+ {
+ $this->record($message, $level, $context);
+ }
+
+ public function __call($method, $parameters)
+ {
+ $this->log($method, ...$parameters);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/log/ChannelSet.php b/vendor/topthink/framework/src/think/log/ChannelSet.php
new file mode 100755
index 0000000..6dcb0bd
--- /dev/null
+++ b/vendor/topthink/framework/src/think/log/ChannelSet.php
@@ -0,0 +1,39 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\log;
+
+use think\Log;
+
+/**
+ * Class ChannelSet
+ * @package think\log
+ * @mixin Channel
+ */
+class ChannelSet
+{
+ protected $log;
+ protected $channels;
+
+ public function __construct(Log $log, array $channels)
+ {
+ $this->log = $log;
+ $this->channels = $channels;
+ }
+
+ public function __call($method, $arguments)
+ {
+ foreach ($this->channels as $channel) {
+ $this->log->channel($channel)->{$method}(...$arguments);
+ }
+ }
+}
diff --git a/vendor/topthink/framework/src/think/log/driver/File.php b/vendor/topthink/framework/src/think/log/driver/File.php
new file mode 100755
index 0000000..1f726b0
--- /dev/null
+++ b/vendor/topthink/framework/src/think/log/driver/File.php
@@ -0,0 +1,207 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\log\driver;
+
+use think\App;
+use think\contract\LogHandlerInterface;
+
+/**
+ * 本地化调试输出到文件
+ */
+class File implements LogHandlerInterface
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [
+ 'time_format' => 'c',
+ 'single' => false,
+ 'file_size' => 2097152,
+ 'path' => '',
+ 'apart_level' => [],
+ 'max_files' => 0,
+ 'json' => false,
+ 'json_options' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
+ 'format' => '[%s][%s] %s',
+ ];
+
+ // 实例化并传入参数
+ public function __construct(App $app, $config = [])
+ {
+ if (is_array($config)) {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ if (empty($this->config['format'])) {
+ $this->config['format'] = '[%s][%s] %s';
+ }
+
+ if (empty($this->config['path'])) {
+ $this->config['path'] = $app->getRuntimePath() . 'log';
+ }
+
+ if (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) {
+ $this->config['path'] .= DIRECTORY_SEPARATOR;
+ }
+ }
+
+ /**
+ * 日志写入接口
+ * @access public
+ * @param array $log 日志信息
+ * @return bool
+ */
+ public function save(array $log): bool
+ {
+ $destination = $this->getMasterLogFile();
+
+ $path = dirname($destination);
+ !is_dir($path) && mkdir($path, 0755, true);
+
+ $info = [];
+
+ // 日志信息封装
+ $time = \DateTime::createFromFormat('0.u00 U', microtime())->setTimezone(new \DateTimeZone(date_default_timezone_get()))->format($this->config['time_format']);
+
+ foreach ($log as $type => $val) {
+ $message = [];
+ foreach ($val as $msg) {
+ if (!is_string($msg)) {
+ $msg = var_export($msg, true);
+ }
+
+ $message[] = $this->config['json'] ?
+ json_encode(['time' => $time, 'type' => $type, 'msg' => $msg], $this->config['json_options']) :
+ sprintf($this->config['format'], $time, $type, $msg);
+ }
+
+ if (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level'])) {
+ // 独立记录的日志级别
+ $filename = $this->getApartLevelFile($path, $type);
+ $this->write($message, $filename);
+ continue;
+ }
+
+ $info[$type] = $message;
+ }
+
+ if ($info) {
+ return $this->write($info, $destination);
+ }
+
+ return true;
+ }
+
+ /**
+ * 日志写入
+ * @access protected
+ * @param array $message 日志信息
+ * @param string $destination 日志文件
+ * @return bool
+ */
+ protected function write(array $message, string $destination): bool
+ {
+ // 检测日志文件大小,超过配置大小则备份日志文件重新生成
+ $this->checkLogSize($destination);
+
+ $info = [];
+
+ foreach ($message as $type => $msg) {
+ $info[$type] = is_array($msg) ? implode(PHP_EOL, $msg) : $msg;
+ }
+
+ $message = implode(PHP_EOL, $info) . PHP_EOL;
+
+ return error_log($message, 3, $destination);
+ }
+
+ /**
+ * 获取主日志文件名
+ * @access public
+ * @return string
+ */
+ protected function getMasterLogFile(): string
+ {
+
+ if ($this->config['max_files']) {
+ $files = glob($this->config['path'] . '*.log');
+
+ try {
+ if (count($files) > $this->config['max_files']) {
+ set_error_handler(function ($errno, $errstr, $errfile, $errline) {});
+ unlink($files[0]);
+ restore_error_handler();
+ }
+ } catch (\Exception $e) {
+ //
+ }
+ }
+
+ if ($this->config['single']) {
+ $name = is_string($this->config['single']) ? $this->config['single'] : 'single';
+ $destination = $this->config['path'] . $name . '.log';
+ } else {
+
+ if ($this->config['max_files']) {
+ $filename = date('Ymd') . '.log';
+ } else {
+ $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . '.log';
+ }
+
+ $destination = $this->config['path'] . $filename;
+ }
+
+ return $destination;
+ }
+
+ /**
+ * 获取独立日志文件名
+ * @access public
+ * @param string $path 日志目录
+ * @param string $type 日志类型
+ * @return string
+ */
+ protected function getApartLevelFile(string $path, string $type): string
+ {
+
+ if ($this->config['single']) {
+ $name = is_string($this->config['single']) ? $this->config['single'] : 'single';
+
+ $name .= '_' . $type;
+ } elseif ($this->config['max_files']) {
+ $name = date('Ymd') . '_' . $type;
+ } else {
+ $name = date('d') . '_' . $type;
+ }
+
+ return $path . DIRECTORY_SEPARATOR . $name . '.log';
+ }
+
+ /**
+ * 检查日志文件大小并自动生成备份文件
+ * @access protected
+ * @param string $destination 日志文件
+ * @return void
+ */
+ protected function checkLogSize(string $destination): void
+ {
+ if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) {
+ try {
+ rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination));
+ } catch (\Exception $e) {
+ //
+ }
+ }
+ }
+}
diff --git a/vendor/topthink/framework/src/think/log/driver/Socket.php b/vendor/topthink/framework/src/think/log/driver/Socket.php
new file mode 100755
index 0000000..2cfb943
--- /dev/null
+++ b/vendor/topthink/framework/src/think/log/driver/Socket.php
@@ -0,0 +1,311 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: luofei614 <weibo.com/luofei614>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\log\driver;
+
+use Psr\Container\NotFoundExceptionInterface;
+use think\App;
+use think\contract\LogHandlerInterface;
+
+/**
+ * github: https://github.com/luofei614/SocketLog
+ * @author luofei614<weibo.com/luofei614>
+ */
+class Socket implements LogHandlerInterface
+{
+ protected $app;
+
+ protected $config = [
+ // socket服务器地址
+ 'host' => 'localhost',
+ // socket服务器端口
+ 'port' => 1116,
+ // 是否显示加载的文件列表
+ 'show_included_files' => false,
+ // 日志强制记录到配置的client_id
+ 'force_client_ids' => [],
+ // 限制允许读取日志的client_id
+ 'allow_client_ids' => [],
+ // 调试开关
+ 'debug' => false,
+ // 输出到浏览器时默认展开的日志级别
+ 'expand_level' => ['debug'],
+ // 日志头渲染回调
+ 'format_head' => null,
+ // curl opt
+ 'curl_opt' => [
+ CURLOPT_CONNECTTIMEOUT => 1,
+ CURLOPT_TIMEOUT => 10,
+ ],
+ ];
+
+ protected $css = [
+ 'sql' => 'color:#009bb4;',
+ 'sql_warn' => 'color:#009bb4;font-size:14px;',
+ 'error' => 'color:#f4006b;font-size:14px;',
+ 'page' => 'color:#40e2ff;background:#171717;',
+ 'big' => 'font-size:20px;color:red;',
+ ];
+
+ protected $allowForceClientIds = []; //配置强制推送且被授权的client_id
+
+ protected $clientArg = [];
+
+ /**
+ * 架构函数
+ * @access public
+ * @param App $app
+ * @param array $config 缓存参数
+ */
+ public function __construct(App $app, array $config = [])
+ {
+ $this->app = $app;
+
+ if (!empty($config)) {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ if (!isset($config['debug'])) {
+ $this->config['debug'] = $app->isDebug();
+ }
+ }
+
+ /**
+ * 调试输出接口
+ * @access public
+ * @param array $log 日志信息
+ * @return bool
+ */
+ public function save(array $log = []): bool
+ {
+ if (!$this->check()) {
+ return false;
+ }
+
+ $trace = [];
+
+ if ($this->config['debug']) {
+ if ($this->app->exists('request')) {
+ $currentUri = $this->app->request->url(true);
+ } else {
+ $currentUri = 'cmd:' . implode(' ', $_SERVER['argv'] ?? []);
+ }
+
+ if (!empty($this->config['format_head'])) {
+ try {
+ $currentUri = $this->app->invoke($this->config['format_head'], [$currentUri]);
+ } catch (NotFoundExceptionInterface $notFoundException) {
+ // Ignore exception
+ }
+ }
+
+ // 基本信息
+ $trace[] = [
+ 'type' => 'group',
+ 'msg' => $currentUri,
+ 'css' => $this->css['page'],
+ ];
+ }
+
+ $expandLevel = array_flip($this->config['expand_level']);
+
+ foreach ($log as $type => $val) {
+ $trace[] = [
+ 'type' => isset($expandLevel[$type]) ? 'group' : 'groupCollapsed',
+ 'msg' => '[ ' . $type . ' ]',
+ 'css' => $this->css[$type] ?? '',
+ ];
+
+ foreach ($val as $msg) {
+ if (!is_string($msg)) {
+ $msg = var_export($msg, true);
+ }
+ $trace[] = [
+ 'type' => 'log',
+ 'msg' => $msg,
+ 'css' => '',
+ ];
+ }
+
+ $trace[] = [
+ 'type' => 'groupEnd',
+ 'msg' => '',
+ 'css' => '',
+ ];
+ }
+
+ if ($this->config['show_included_files']) {
+ $trace[] = [
+ 'type' => 'groupCollapsed',
+ 'msg' => '[ file ]',
+ 'css' => '',
+ ];
+
+ $trace[] = [
+ 'type' => 'log',
+ 'msg' => implode("\n", get_included_files()),
+ 'css' => '',
+ ];
+
+ $trace[] = [
+ 'type' => 'groupEnd',
+ 'msg' => '',
+ 'css' => '',
+ ];
+ }
+
+ $trace[] = [
+ 'type' => 'groupEnd',
+ 'msg' => '',
+ 'css' => '',
+ ];
+
+ $tabid = $this->getClientArg('tabid');
+
+ if (!$clientId = $this->getClientArg('client_id')) {
+ $clientId = '';
+ }
+
+ if (!empty($this->allowForceClientIds)) {
+ //强制推送到多个client_id
+ foreach ($this->allowForceClientIds as $forceClientId) {
+ $clientId = $forceClientId;
+ $this->sendToClient($tabid, $clientId, $trace, $forceClientId);
+ }
+ } else {
+ $this->sendToClient($tabid, $clientId, $trace, '');
+ }
+
+ return true;
+ }
+
+ /**
+ * 发送给指定客户端
+ * @access protected
+ * @author Zjmainstay
+ * @param $tabid
+ * @param $clientId
+ * @param $logs
+ * @param $forceClientId
+ */
+ protected function sendToClient($tabid, $clientId, $logs, $forceClientId)
+ {
+ $logs = [
+ 'tabid' => $tabid,
+ 'client_id' => $clientId,
+ 'logs' => $logs,
+ 'force_client_id' => $forceClientId,
+ ];
+
+ $msg = json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR);
+ $address = '/' . $clientId; //将client_id作为地址, server端通过地址判断将日志发布给谁
+
+ $this->send($this->config['host'], $this->config['port'], $msg, $address);
+ }
+
+ /**
+ * 检测客户授权
+ * @access protected
+ * @return bool
+ */
+ protected function check()
+ {
+ $tabid = $this->getClientArg('tabid');
+
+ //是否记录日志的检查
+ if (!$tabid && !$this->config['force_client_ids']) {
+ return false;
+ }
+
+ //用户认证
+ $allowClientIds = $this->config['allow_client_ids'];
+
+ if (!empty($allowClientIds)) {
+ //通过数组交集得出授权强制推送的client_id
+ $this->allowForceClientIds = array_intersect($allowClientIds, $this->config['force_client_ids']);
+ if (!$tabid && count($this->allowForceClientIds)) {
+ return true;
+ }
+
+ $clientId = $this->getClientArg('client_id');
+ if (!in_array($clientId, $allowClientIds)) {
+ return false;
+ }
+ } else {
+ $this->allowForceClientIds = $this->config['force_client_ids'];
+ }
+
+ return true;
+ }
+
+ /**
+ * 获取客户参数
+ * @access protected
+ * @param string $name
+ * @return string
+ */
+ protected function getClientArg(string $name)
+ {
+ if (!$this->app->exists('request')) {
+ return '';
+ }
+
+ if (empty($this->clientArg)) {
+ if (empty($socketLog = $this->app->request->header('socketlog'))) {
+ if (empty($socketLog = $this->app->request->header('User-Agent'))) {
+ return '';
+ }
+ }
+
+ if (!preg_match('/SocketLog\((.*?)\)/', $socketLog, $match)) {
+ $this->clientArg = ['tabid' => null, 'client_id' => null];
+ return '';
+ }
+ parse_str($match[1] ?? '', $this->clientArg);
+ }
+
+ if (isset($this->clientArg[$name])) {
+ return $this->clientArg[$name];
+ }
+
+ return '';
+ }
+
+ /**
+ * @access protected
+ * @param string $host - $host of socket server
+ * @param int $port - $port of socket server
+ * @param string $message - 发送的消息
+ * @param string $address - 地址
+ * @return bool
+ */
+ protected function send($host, $port, $message = '', $address = '/')
+ {
+ $url = 'http://' . $host . ':' . $port . $address;
+ $ch = curl_init();
+
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $message);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->config['curl_opt'][CURLOPT_CONNECTTIMEOUT] ?? 1);
+ curl_setopt($ch, CURLOPT_TIMEOUT, $this->config['curl_opt'][CURLOPT_TIMEOUT] ?? 10);
+
+ $headers = [
+ "Content-Type: application/json;charset=UTF-8",
+ ];
+
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置header
+
+ return curl_exec($ch);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php b/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php
new file mode 100755
index 0000000..b7ab842
--- /dev/null
+++ b/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php
@@ -0,0 +1,63 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\middleware;
+
+use Closure;
+use think\Config;
+use think\Request;
+use think\Response;
+
+/**
+ * 跨域请求支持
+ */
+class AllowCrossDomain
+{
+ protected $cookieDomain;
+
+ protected $header = [
+ 'Access-Control-Allow-Credentials' => 'true',
+ 'Access-Control-Max-Age' => 1800,
+ 'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE, OPTIONS',
+ 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',
+ ];
+
+ public function __construct(Config $config)
+ {
+ $this->cookieDomain = $config->get('cookie.domain', '');
+ }
+
+ /**
+ * 允许跨域请求
+ * @access public
+ * @param Request $request
+ * @param Closure $next
+ * @param array $header
+ * @return Response
+ */
+ public function handle($request, Closure $next, ? array $header = [])
+ {
+ $header = !empty($header) ? array_merge($this->header, $header) : $this->header;
+
+ if (!isset($header['Access-Control-Allow-Origin'])) {
+ $origin = $request->header('origin');
+
+ if ($origin && ('' == $this->cookieDomain || strpos($origin, $this->cookieDomain))) {
+ $header['Access-Control-Allow-Origin'] = $origin;
+ } else {
+ $header['Access-Control-Allow-Origin'] = '*';
+ }
+ }
+
+ return $next($request)->header($header);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php b/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php
new file mode 100755
index 0000000..b114351
--- /dev/null
+++ b/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php
@@ -0,0 +1,183 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\middleware;
+
+use Closure;
+use think\Cache;
+use think\Config;
+use think\Request;
+use think\Response;
+
+/**
+ * 请求缓存处理
+ */
+class CheckRequestCache
+{
+ /**
+ * 缓存对象
+ * @var Cache
+ */
+ protected $cache;
+
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [
+ // 请求缓存规则 true为自动规则
+ 'request_cache_key' => true,
+ // 请求缓存有效期
+ 'request_cache_expire' => null,
+ // 全局请求缓存排除规则
+ 'request_cache_except' => [],
+ // 请求缓存的Tag
+ 'request_cache_tag' => '',
+ ];
+
+ public function __construct(Cache $cache, Config $config)
+ {
+ $this->cache = $cache;
+ $this->config = array_merge($this->config, $config->get('route'));
+ }
+
+ /**
+ * 设置当前地址的请求缓存
+ * @access public
+ * @param Request $request
+ * @param Closure $next
+ * @param mixed $cache
+ * @return Response
+ */
+ public function handle($request, Closure $next, $cache = null)
+ {
+ if ($request->isGet() && false !== $cache) {
+ if (false === $this->config['request_cache_key']) {
+ // 关闭当前缓存
+ $cache = false;
+ }
+
+ $cache = $cache ?? $this->getRequestCache($request);
+
+ if ($cache) {
+ if (is_array($cache)) {
+ [$key, $expire, $tag] = array_pad($cache, 3, null);
+ } else {
+ $key = md5($request->url(true));
+ $expire = $cache;
+ $tag = null;
+ }
+
+ $key = $this->parseCacheKey($request, $key);
+
+ if (strtotime($request->server('HTTP_IF_MODIFIED_SINCE', '')) + $expire > $request->server('REQUEST_TIME')) {
+ // 读取缓存
+ return Response::create()->code(304);
+ } elseif (($hit = $this->cache->get($key)) !== null) {
+ [$content, $header, $when] = $hit;
+ if (null === $expire || $when + $expire > $request->server('REQUEST_TIME')) {
+ return Response::create($content)->header($header);
+ }
+ }
+ }
+ }
+
+ $response = $next($request);
+
+ if (isset($key) && 200 == $response->getCode() && $response->isAllowCache()) {
+ $header = $response->getHeader();
+ $header['Cache-Control'] = 'max-age=' . $expire . ',must-revalidate';
+ $header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT';
+ $header['Expires'] = gmdate('D, d M Y H:i:s', time() + $expire) . ' GMT';
+
+ $this->cache->tag($tag)->set($key, [$response->getContent(), $header, time()], $expire);
+ }
+
+ return $response;
+ }
+
+ /**
+ * 读取当前地址的请求缓存信息
+ * @access protected
+ * @param Request $request
+ * @return mixed
+ */
+ protected function getRequestCache($request)
+ {
+ $key = $this->config['request_cache_key'];
+ $expire = $this->config['request_cache_expire'];
+ $except = $this->config['request_cache_except'];
+ $tag = $this->config['request_cache_tag'];
+
+ foreach ($except as $rule) {
+ if (0 === stripos($request->url(), $rule)) {
+ return;
+ }
+ }
+
+ return [$key, $expire, $tag];
+ }
+
+ /**
+ * 读取当前地址的请求缓存信息
+ * @access protected
+ * @param Request $request
+ * @param mixed $key
+ * @return null|string
+ */
+ protected function parseCacheKey($request, $key)
+ {
+ if ($key instanceof \Closure) {
+ $key = call_user_func($key, $request);
+ }
+
+ if (false === $key) {
+ // 关闭当前缓存
+ return;
+ }
+
+ if (true === $key) {
+ // 自动缓存功能
+ $key = '__URL__';
+ } elseif (strpos($key, '|')) {
+ [$key, $fun] = explode('|', $key);
+ }
+
+ // 特殊规则替换
+ if (false !== strpos($key, '__')) {
+ $key = str_replace(['__CONTROLLER__', '__ACTION__', '__URL__'], [$request->controller(), $request->action(), md5($request->url(true))], $key);
+ }
+
+ if (false !== strpos($key, ':')) {
+ $param = $request->param();
+
+ foreach ($param as $item => $val) {
+ if (is_string($val) && false !== strpos($key, ':' . $item)) {
+ $key = str_replace(':' . $item, (string) $val, $key);
+ }
+ }
+ } elseif (strpos($key, ']')) {
+ if ('[' . $request->ext() . ']' == $key) {
+ // 缓存某个后缀的请求
+ $key = md5($request->url());
+ } else {
+ return;
+ }
+ }
+
+ if (isset($fun)) {
+ $key = $fun($key);
+ }
+
+ return $key;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php b/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php
new file mode 100755
index 0000000..efbb77b
--- /dev/null
+++ b/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php
@@ -0,0 +1,45 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\middleware;
+
+use Closure;
+use think\exception\ValidateException;
+use think\Request;
+use think\Response;
+
+/**
+ * 表单令牌支持
+ */
+class FormTokenCheck
+{
+
+ /**
+ * 表单令牌检测
+ * @access public
+ * @param Request $request
+ * @param Closure $next
+ * @param string $token 表单令牌Token名称
+ * @return Response
+ */
+ public function handle(Request $request, Closure $next, string $token = null)
+ {
+ $check = $request->checkToken($token ?: '__token__');
+
+ if (false === $check) {
+ throw new ValidateException('invalid token');
+ }
+
+ return $next($request);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/middleware/LoadLangPack.php b/vendor/topthink/framework/src/think/middleware/LoadLangPack.php
new file mode 100755
index 0000000..af0324b
--- /dev/null
+++ b/vendor/topthink/framework/src/think/middleware/LoadLangPack.php
@@ -0,0 +1,118 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\middleware;
+
+use Closure;
+use think\App;
+use think\Config;
+use think\Cookie;
+use think\Lang;
+use think\Request;
+use think\Response;
+
+/**
+ * 多语言加载
+ */
+class LoadLangPack
+{
+ protected $app;
+ protected $lang;
+ protected $config;
+
+ public function __construct(App $app, Lang $lang, Config $config)
+ {
+ $this->app = $app;
+ $this->lang = $lang;
+ $this->config = $lang->getConfig();
+ }
+
+ /**
+ * 路由初始化(路由规则注册)
+ * @access public
+ * @param Request $request
+ * @param Closure $next
+ * @return Response
+ */
+ public function handle($request, Closure $next)
+ {
+ // 自动侦测当前语言
+ $langset = $this->detect($request);
+
+ if ($this->lang->defaultLangSet() != $langset) {
+ $this->lang->switchLangSet($langset);
+ }
+
+ $this->saveToCookie($this->app->cookie, $langset);
+
+ return $next($request);
+ }
+
+ /**
+ * 自动侦测设置获取语言选择
+ * @access protected
+ * @param Request $request
+ * @return string
+ */
+ protected function detect(Request $request): string
+ {
+ // 自动侦测设置获取语言选择
+ $langSet = '';
+
+ if ($request->get($this->config['detect_var'])) {
+ // url中设置了语言变量
+ $langSet = $request->get($this->config['detect_var']);
+ } elseif ($request->header($this->config['header_var'])) {
+ // Header中设置了语言变量
+ $langSet = $request->header($this->config['header_var']);
+ } elseif ($request->cookie($this->config['cookie_var'])) {
+ // Cookie中设置了语言变量
+ $langSet = $request->cookie($this->config['cookie_var']);
+ } elseif ($request->server('HTTP_ACCEPT_LANGUAGE')) {
+ // 自动侦测浏览器语言
+ $langSet = $request->server('HTTP_ACCEPT_LANGUAGE');
+ }
+
+ if (preg_match('/^([a-z\d\-]+)/i', $langSet, $matches)) {
+ $langSet = strtolower($matches[1]);
+ if (isset($this->config['accept_language'][$langSet])) {
+ $langSet = $this->config['accept_language'][$langSet];
+ }
+ } else {
+ $langSet = $this->lang->getLangSet();
+ }
+
+ if (empty($this->config['allow_lang_list']) || in_array($langSet, $this->config['allow_lang_list'])) {
+ // 合法的语言
+ $this->lang->setLangSet($langSet);
+ } else {
+ $langSet = $this->lang->getLangSet();
+ }
+
+ return $langSet;
+ }
+
+ /**
+ * 保存当前语言到Cookie
+ * @access protected
+ * @param Cookie $cookie Cookie对象
+ * @param string $langSet 语言
+ * @return void
+ */
+ protected function saveToCookie(Cookie $cookie, string $langSet)
+ {
+ if ($this->config['use_cookie']) {
+ $cookie->set($this->config['cookie_var'], $langSet);
+ }
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/middleware/SessionInit.php b/vendor/topthink/framework/src/think/middleware/SessionInit.php
new file mode 100755
index 0000000..3cb2fad
--- /dev/null
+++ b/vendor/topthink/framework/src/think/middleware/SessionInit.php
@@ -0,0 +1,80 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\middleware;
+
+use Closure;
+use think\App;
+use think\Request;
+use think\Response;
+use think\Session;
+
+/**
+ * Session初始化
+ */
+class SessionInit
+{
+
+ /** @var App */
+ protected $app;
+
+ /** @var Session */
+ protected $session;
+
+ public function __construct(App $app, Session $session)
+ {
+ $this->app = $app;
+ $this->session = $session;
+ }
+
+ /**
+ * Session初始化
+ * @access public
+ * @param Request $request
+ * @param Closure $next
+ * @return Response
+ */
+ public function handle($request, Closure $next)
+ {
+ // Session初始化
+ $varSessionId = $this->app->config->get('session.var_session_id');
+ $cookieName = $this->session->getName();
+
+ if ($varSessionId && $request->request($varSessionId)) {
+ $sessionId = $request->request($varSessionId);
+ } else {
+ $sessionId = $request->cookie($cookieName);
+ }
+
+ if ($sessionId) {
+ $this->session->setId($sessionId);
+ }
+
+ $this->session->init();
+
+ $request->withSession($this->session);
+
+ /** @var Response $response */
+ $response = $next($request);
+
+ $response->setSession($this->session);
+
+ $this->app->cookie->set($cookieName, $this->session->getId());
+
+ return $response;
+ }
+
+ public function end(Response $response)
+ {
+ $this->session->save();
+ }
+}
diff --git a/vendor/topthink/framework/src/think/response/File.php b/vendor/topthink/framework/src/think/response/File.php
new file mode 100755
index 0000000..1e45f2f
--- /dev/null
+++ b/vendor/topthink/framework/src/think/response/File.php
@@ -0,0 +1,160 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\response;
+
+use think\Exception;
+use think\Response;
+
+/**
+ * File Response
+ */
+class File extends Response
+{
+ protected $expire = 360;
+ protected $name;
+ protected $mimeType;
+ protected $isContent = false;
+ protected $force = true;
+
+ public function __construct($data = '', int $code = 200)
+ {
+ $this->init($data, $code);
+ }
+
+ /**
+ * 处理数据
+ * @access protected
+ * @param mixed $data 要处理的数据
+ * @return mixed
+ * @throws \Exception
+ */
+ protected function output($data)
+ {
+ if (!$this->isContent && !is_file($data)) {
+ throw new Exception('file not exists:' . $data);
+ }
+
+ while (ob_get_level() > 0) {
+ ob_end_clean();
+ }
+
+ if (!empty($this->name)) {
+ $name = $this->name;
+ } else {
+ $name = !$this->isContent ? pathinfo($data, PATHINFO_BASENAME) : '';
+ }
+
+ if ($this->isContent) {
+ $mimeType = $this->mimeType;
+ $size = strlen($data);
+ } else {
+ $mimeType = $this->getMimeType($data);
+ $size = filesize($data);
+ }
+
+ $this->header['Pragma'] = 'public';
+ $this->header['Content-Type'] = $mimeType ?: 'application/octet-stream';
+ $this->header['Cache-control'] = 'max-age=' . $this->expire;
+ $this->header['Content-Disposition'] = ($this->force ? 'attachment; ' : '') . 'filename="' . $name . '"';
+ $this->header['Content-Length'] = $size;
+ $this->header['Content-Transfer-Encoding'] = 'binary';
+ $this->header['Expires'] = gmdate("D, d M Y H:i:s", time() + $this->expire) . ' GMT';
+
+ $this->lastModified(gmdate('D, d M Y H:i:s', time()) . ' GMT');
+
+ return $this->isContent ? $data : file_get_contents($data);
+ }
+
+ /**
+ * 设置是否为内容 必须配合mimeType方法使用
+ * @access public
+ * @param bool $content
+ * @return $this
+ */
+ public function isContent(bool $content = true)
+ {
+ $this->isContent = $content;
+ return $this;
+ }
+
+ /**
+ * 设置有效期
+ * @access public
+ * @param integer $expire 有效期
+ * @return $this
+ */
+ public function expire(int $expire)
+ {
+ $this->expire = $expire;
+ return $this;
+ }
+
+ /**
+ * 设置文件类型
+ * @access public
+ * @param string $filename 文件名
+ * @return $this
+ */
+ public function mimeType(string $mimeType)
+ {
+ $this->mimeType = $mimeType;
+ return $this;
+ }
+
+ /**
+ * 设置文件强制下载
+ * @access public
+ * @param bool $force 强制浏览器下载
+ * @return $this
+ */
+ public function force(bool $force)
+ {
+ $this->force = $force;
+ return $this;
+ }
+
+ /**
+ * 获取文件类型信息
+ * @access public
+ * @param string $filename 文件名
+ * @return string
+ */
+ protected function getMimeType(string $filename): string
+ {
+ if (!empty($this->mimeType)) {
+ return $this->mimeType;
+ }
+
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
+
+ return finfo_file($finfo, $filename);
+ }
+
+ /**
+ * 设置下载文件的显示名称
+ * @access public
+ * @param string $filename 文件名
+ * @param bool $extension 后缀自动识别
+ * @return $this
+ */
+ public function name(string $filename, bool $extension = true)
+ {
+ $this->name = $filename;
+
+ if ($extension && false === strpos($filename, '.')) {
+ $this->name .= '.' . pathinfo($this->data, PATHINFO_EXTENSION);
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/response/Html.php b/vendor/topthink/framework/src/think/response/Html.php
new file mode 100755
index 0000000..c158f78
--- /dev/null
+++ b/vendor/topthink/framework/src/think/response/Html.php
@@ -0,0 +1,34 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\response;
+
+use think\Cookie;
+use think\Response;
+
+/**
+ * Html Response
+ */
+class Html extends Response
+{
+ /**
+ * 输出type
+ * @var string
+ */
+ protected $contentType = 'text/html';
+
+ public function __construct(Cookie $cookie, $data = '', int $code = 200)
+ {
+ $this->init($data, $code);
+ $this->cookie = $cookie;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/response/Json.php b/vendor/topthink/framework/src/think/response/Json.php
new file mode 100755
index 0000000..a84501f
--- /dev/null
+++ b/vendor/topthink/framework/src/think/response/Json.php
@@ -0,0 +1,62 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\response;
+
+use think\Cookie;
+use think\Response;
+
+/**
+ * Json Response
+ */
+class Json extends Response
+{
+ // 输出参数
+ protected $options = [
+ 'json_encode_param' => JSON_UNESCAPED_UNICODE,
+ ];
+
+ protected $contentType = 'application/json';
+
+ public function __construct(Cookie $cookie, $data = '', int $code = 200)
+ {
+ $this->init($data, $code);
+ $this->cookie = $cookie;
+ }
+
+ /**
+ * 处理数据
+ * @access protected
+ * @param mixed $data 要处理的数据
+ * @return string
+ * @throws \Exception
+ */
+ protected function output($data): string
+ {
+ try {
+ // 返回JSON数据格式到客户端 包含状态信息
+ $data = json_encode($data, $this->options['json_encode_param']);
+
+ if (false === $data) {
+ throw new \InvalidArgumentException(json_last_error_msg());
+ }
+
+ return $data;
+ } catch (\Exception $e) {
+ if ($e->getPrevious()) {
+ throw $e->getPrevious();
+ }
+ throw $e;
+ }
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/response/Jsonp.php b/vendor/topthink/framework/src/think/response/Jsonp.php
new file mode 100755
index 0000000..81d3a06
--- /dev/null
+++ b/vendor/topthink/framework/src/think/response/Jsonp.php
@@ -0,0 +1,74 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\response;
+
+use think\Cookie;
+use think\Request;
+use think\Response;
+
+/**
+ * Jsonp Response
+ */
+class Jsonp extends Response
+{
+ // 输出参数
+ protected $options = [
+ 'var_jsonp_handler' => 'callback',
+ 'default_jsonp_handler' => 'jsonpReturn',
+ 'json_encode_param' => JSON_UNESCAPED_UNICODE,
+ ];
+
+ protected $contentType = 'application/javascript';
+
+ protected $request;
+
+ public function __construct(Cookie $cookie, Request $request, $data = '', int $code = 200)
+ {
+ $this->init($data, $code);
+
+ $this->cookie = $cookie;
+ $this->request = $request;
+ }
+
+ /**
+ * 处理数据
+ * @access protected
+ * @param mixed $data 要处理的数据
+ * @return string
+ * @throws \Exception
+ */
+ protected function output($data): string
+ {
+ try {
+ // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取<xiaobo.sun@qq.com>]
+ $varJsonpHandler = $this->request->param($this->options['var_jsonp_handler'], "");
+ $handler = !empty($varJsonpHandler) ? $varJsonpHandler : $this->options['default_jsonp_handler'];
+
+ $data = json_encode($data, $this->options['json_encode_param']);
+
+ if (false === $data) {
+ throw new \InvalidArgumentException(json_last_error_msg());
+ }
+
+ $data = $handler . '(' . $data . ');';
+
+ return $data;
+ } catch (\Exception $e) {
+ if ($e->getPrevious()) {
+ throw $e->getPrevious();
+ }
+ throw $e;
+ }
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/response/Redirect.php b/vendor/topthink/framework/src/think/response/Redirect.php
new file mode 100755
index 0000000..ee067be
--- /dev/null
+++ b/vendor/topthink/framework/src/think/response/Redirect.php
@@ -0,0 +1,98 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\response;
+
+use think\Cookie;
+use think\Request;
+use think\Response;
+use think\Session;
+
+/**
+ * Redirect Response
+ */
+class Redirect extends Response
+{
+
+ protected $request;
+
+ public function __construct(Cookie $cookie, Request $request, Session $session, $data = '', int $code = 302)
+ {
+ $this->init((string) $data, $code);
+
+ $this->cookie = $cookie;
+ $this->request = $request;
+ $this->session = $session;
+
+ $this->cacheControl('no-cache,must-revalidate');
+ }
+
+ /**
+ * 处理数据
+ * @access protected
+ * @param mixed $data 要处理的数据
+ * @return string
+ */
+ protected function output($data): string
+ {
+ $this->header['Location'] = $data;
+
+ return '';
+ }
+
+ /**
+ * 重定向传值(通过Session)
+ * @access protected
+ * @param string|array $name 变量名或者数组
+ * @param mixed $value 值
+ * @return $this
+ */
+ public function with($name, $value = null)
+ {
+ if (is_array($name)) {
+ foreach ($name as $key => $val) {
+ $this->session->flash($key, $val);
+ }
+ } else {
+ $this->session->flash($name, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 记住当前url后跳转
+ * @access public
+ * @return $this
+ */
+ public function remember($complete = false)
+ {
+ $this->session->set('redirect_url', $this->request->url($complete));
+
+ return $this;
+ }
+
+ /**
+ * 跳转到上次记住的url
+ * @access public
+ * @return $this
+ */
+ public function restore()
+ {
+ if ($this->session->has('redirect_url')) {
+ $this->data = $this->session->get('redirect_url');
+ $this->session->delete('redirect_url');
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/response/View.php b/vendor/topthink/framework/src/think/response/View.php
new file mode 100755
index 0000000..2c116c7
--- /dev/null
+++ b/vendor/topthink/framework/src/think/response/View.php
@@ -0,0 +1,151 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\response;
+
+use think\Cookie;
+use think\Response;
+use think\View as BaseView;
+
+/**
+ * View Response
+ */
+class View extends Response
+{
+ /**
+ * 输出参数
+ * @var array
+ */
+ protected $options = [];
+
+ /**
+ * 输出变量
+ * @var array
+ */
+ protected $vars = [];
+
+ /**
+ * 输出过滤
+ * @var mixed
+ */
+ protected $filter;
+
+ /**
+ * 输出type
+ * @var string
+ */
+ protected $contentType = 'text/html';
+
+ /**
+ * View对象
+ * @var BaseView
+ */
+ protected $view;
+
+ /**
+ * 是否内容渲染
+ * @var bool
+ */
+ protected $isContent = false;
+
+ public function __construct(Cookie $cookie, BaseView $view, $data = '', int $code = 200)
+ {
+ $this->init($data, $code);
+
+ $this->cookie = $cookie;
+ $this->view = $view;
+ }
+
+ /**
+ * 设置是否为内容渲染
+ * @access public
+ * @param bool $content
+ * @return $this
+ */
+ public function isContent(bool $content = true)
+ {
+ $this->isContent = $content;
+ return $this;
+ }
+
+ /**
+ * 处理数据
+ * @access protected
+ * @param mixed $data 要处理的数据
+ * @return string
+ */
+ protected function output($data): string
+ {
+ // 渲染模板输出
+ $this->view->filter($this->filter);
+ return $this->isContent ?
+ $this->view->display($data, $this->vars) :
+ $this->view->fetch($data, $this->vars);
+ }
+
+ /**
+ * 获取视图变量
+ * @access public
+ * @param string $name 模板变量
+ * @return mixed
+ */
+ public function getVars(string $name = null)
+ {
+ if (is_null($name)) {
+ return $this->vars;
+ } else {
+ return $this->vars[$name] ?? null;
+ }
+ }
+
+ /**
+ * 模板变量赋值
+ * @access public
+ * @param string|array $name 模板变量
+ * @param mixed $value 变量值
+ * @return $this
+ */
+ public function assign($name, $value = null)
+ {
+ if (is_array($name)) {
+ $this->vars = array_merge($this->vars, $name);
+ } else {
+ $this->vars[$name] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 视图内容过滤
+ * @access public
+ * @param callable $filter
+ * @return $this
+ */
+ public function filter(callable $filter = null)
+ {
+ $this->filter = $filter;
+ return $this;
+ }
+
+ /**
+ * 检查模板是否存在
+ * @access public
+ * @param string $name 模板名
+ * @return bool
+ */
+ public function exists(string $name): bool
+ {
+ return $this->view->exists($name);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/response/Xml.php b/vendor/topthink/framework/src/think/response/Xml.php
new file mode 100755
index 0000000..bddbb48
--- /dev/null
+++ b/vendor/topthink/framework/src/think/response/Xml.php
@@ -0,0 +1,127 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\response;
+
+use think\Collection;
+use think\Cookie;
+use think\Model;
+use think\Response;
+
+/**
+ * XML Response
+ */
+class Xml extends Response
+{
+ // 输出参数
+ protected $options = [
+ // 根节点名
+ 'root_node' => 'think',
+ // 根节点属性
+ 'root_attr' => '',
+ //数字索引的子节点名
+ 'item_node' => 'item',
+ // 数字索引子节点key转换的属性名
+ 'item_key' => 'id',
+ // 数据编码
+ 'encoding' => 'utf-8',
+ ];
+
+ protected $contentType = 'text/xml';
+
+ public function __construct(Cookie $cookie, $data = '', int $code = 200)
+ {
+ $this->init($data, $code);
+ $this->cookie = $cookie;
+ }
+
+ /**
+ * 处理数据
+ * @access protected
+ * @param mixed $data 要处理的数据
+ * @return mixed
+ */
+ protected function output($data): string
+ {
+ if (is_string($data)) {
+ if (0 !== strpos($data, '<?xml')) {
+ $encoding = $this->options['encoding'];
+ $xml = "<?xml version=\"1.0\" encoding=\"{$encoding}\"?>";
+ $data = $xml . $data;
+ }
+ return $data;
+ }
+
+ // XML数据转换
+ return $this->xmlEncode($data, $this->options['root_node'], $this->options['item_node'], $this->options['root_attr'], $this->options['item_key'], $this->options['encoding']);
+ }
+
+ /**
+ * XML编码
+ * @access protected
+ * @param mixed $data 数据
+ * @param string $root 根节点名
+ * @param string $item 数字索引的子节点名
+ * @param mixed $attr 根节点属性
+ * @param string $id 数字索引子节点key转换的属性名
+ * @param string $encoding 数据编码
+ * @return string
+ */
+ protected function xmlEncode($data, string $root, string $item, $attr, string $id, string $encoding): string
+ {
+ if (is_array($attr)) {
+ $array = [];
+ foreach ($attr as $key => $value) {
+ $array[] = "{$key}=\"{$value}\"";
+ }
+ $attr = implode(' ', $array);
+ }
+
+ $attr = trim($attr);
+ $attr = empty($attr) ? '' : " {$attr}";
+ $xml = "<?xml version=\"1.0\" encoding=\"{$encoding}\"?>";
+ $xml .= "<{$root}{$attr}>";
+ $xml .= $this->dataToXml($data, $item, $id);
+ $xml .= "</{$root}>";
+
+ return $xml;
+ }
+
+ /**
+ * 数据XML编码
+ * @access protected
+ * @param mixed $data 数据
+ * @param string $item 数字索引时的节点名称
+ * @param string $id 数字索引key转换为的属性名
+ * @return string
+ */
+ protected function dataToXml($data, string $item, string $id): string
+ {
+ $xml = $attr = '';
+
+ if ($data instanceof Collection || $data instanceof Model) {
+ $data = $data->toArray();
+ }
+
+ foreach ($data as $key => $val) {
+ if (is_numeric($key)) {
+ $id && $attr = " {$id}=\"{$key}\"";
+ $key = $item;
+ }
+ $xml .= "<{$key}{$attr}>";
+ $xml .= (is_array($val) || is_object($val)) ? $this->dataToXml($val, $item, $id) : $val;
+ $xml .= "</{$key}>";
+ }
+
+ return $xml;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/route/Dispatch.php b/vendor/topthink/framework/src/think/route/Dispatch.php
new file mode 100755
index 0000000..58666b5
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/Dispatch.php
@@ -0,0 +1,264 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route;
+
+use Psr\Http\Message\ResponseInterface;
+use think\App;
+use think\Container;
+use think\Request;
+use think\Response;
+use think\Validate;
+
+/**
+ * 路由调度基础类
+ */
+abstract class Dispatch
+{
+ /**
+ * 应用对象
+ * @var \think\App
+ */
+ protected $app;
+
+ /**
+ * 请求对象
+ * @var Request
+ */
+ protected $request;
+
+ /**
+ * 路由规则
+ * @var Rule
+ */
+ protected $rule;
+
+ /**
+ * 调度信息
+ * @var mixed
+ */
+ protected $dispatch;
+
+ /**
+ * 路由变量
+ * @var array
+ */
+ protected $param;
+
+ public function __construct(Request $request, Rule $rule, $dispatch, array $param = [])
+ {
+ $this->request = $request;
+ $this->rule = $rule;
+ $this->dispatch = $dispatch;
+ $this->param = $param;
+ }
+
+ public function init(App $app)
+ {
+ $this->app = $app;
+
+ // 执行路由后置操作
+ $this->doRouteAfter();
+ }
+
+ /**
+ * 执行路由调度
+ * @access public
+ * @return mixed
+ */
+ public function run(): Response
+ {
+ if ($this->rule instanceof RuleItem && $this->request->method() == 'OPTIONS' && $this->rule->isAutoOptions()) {
+ $rules = $this->rule->getRouter()->getRule($this->rule->getRule());
+ $allow = [];
+ foreach ($rules as $item) {
+ $allow[] = strtoupper($item->getMethod());
+ }
+
+ return Response::create('', 'html', 204)->header(['Allow' => implode(', ', $allow)]);
+ }
+
+ $data = $this->exec();
+ return $this->autoResponse($data);
+ }
+
+ protected function autoResponse($data): Response
+ {
+ if ($data instanceof Response) {
+ $response = $data;
+ } elseif ($data instanceof ResponseInterface) {
+ $response = Response::create((string) $data->getBody(), 'html', $data->getStatusCode());
+
+ foreach ($data->getHeaders() as $header => $values) {
+ $response->header([$header => implode(", ", $values)]);
+ }
+ } elseif (!is_null($data)) {
+ // 默认自动识别响应输出类型
+ $type = $this->request->isJson() ? 'json' : 'html';
+ $response = Response::create($data, $type);
+ } else {
+ $data = ob_get_clean();
+
+ $content = false === $data ? '' : $data;
+ $status = '' === $content && $this->request->isJson() ? 204 : 200;
+ $response = Response::create($content, 'html', $status);
+ }
+
+ return $response;
+ }
+
+ /**
+ * 检查路由后置操作
+ * @access protected
+ * @return void
+ */
+ protected function doRouteAfter(): void
+ {
+ $option = $this->rule->getOption();
+
+ // 添加中间件
+ if (!empty($option['middleware'])) {
+ $this->app->middleware->import($option['middleware'], 'route');
+ }
+
+ if (!empty($option['append'])) {
+ $this->param = array_merge($this->param, $option['append']);
+ }
+
+ // 绑定模型数据
+ if (!empty($option['model'])) {
+ $this->createBindModel($option['model'], $this->param);
+ }
+
+ // 记录当前请求的路由规则
+ $this->request->setRule($this->rule);
+
+ // 记录路由变量
+ $this->request->setRoute($this->param);
+
+ // 数据自动验证
+ if (isset($option['validate'])) {
+ $this->autoValidate($option['validate']);
+ }
+ }
+
+ /**
+ * 路由绑定模型实例
+ * @access protected
+ * @param array $bindModel 绑定模型
+ * @param array $matches 路由变量
+ * @return void
+ */
+ protected function createBindModel(array $bindModel, array $matches): void
+ {
+ foreach ($bindModel as $key => $val) {
+ if ($val instanceof \Closure) {
+ $result = $this->app->invokeFunction($val, $matches);
+ } else {
+ $fields = explode('&', $key);
+
+ if (is_array($val)) {
+ [$model, $exception] = $val;
+ } else {
+ $model = $val;
+ $exception = true;
+ }
+
+ $where = [];
+ $match = true;
+
+ foreach ($fields as $field) {
+ if (!isset($matches[$field])) {
+ $match = false;
+ break;
+ } else {
+ $where[] = [$field, '=', $matches[$field]];
+ }
+ }
+
+ if ($match) {
+ $result = $model::where($where)->failException($exception)->find();
+ }
+ }
+
+ if (!empty($result)) {
+ // 注入容器
+ $this->app->instance(get_class($result), $result);
+ }
+ }
+ }
+
+ /**
+ * 验证数据
+ * @access protected
+ * @param array $option
+ * @return void
+ * @throws \think\exception\ValidateException
+ */
+ protected function autoValidate(array $option): void
+ {
+ [$validate, $scene, $message, $batch] = $option;
+
+ if (is_array($validate)) {
+ // 指定验证规则
+ $v = new Validate();
+ $v->rule($validate);
+ } else {
+ // 调用验证器
+ $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
+
+ $v = new $class();
+
+ if (!empty($scene)) {
+ $v->scene($scene);
+ }
+ }
+
+ /** @var Validate $v */
+ $v->message($message)
+ ->batch($batch)
+ ->failException(true)
+ ->check($this->request->param());
+ }
+
+ public function getDispatch()
+ {
+ return $this->dispatch;
+ }
+
+ public function getParam(): array
+ {
+ return $this->param;
+ }
+
+ abstract public function exec();
+
+ public function __sleep()
+ {
+ return ['rule', 'dispatch', 'param', 'controller', 'actionName'];
+ }
+
+ public function __wakeup()
+ {
+ $this->app = Container::pull('app');
+ $this->request = $this->app->request;
+ }
+
+ public function __debugInfo()
+ {
+ return [
+ 'dispatch' => $this->dispatch,
+ 'param' => $this->param,
+ 'rule' => $this->rule,
+ ];
+ }
+}
diff --git a/vendor/topthink/framework/src/think/route/Domain.php b/vendor/topthink/framework/src/think/route/Domain.php
new file mode 100755
index 0000000..84f1d46
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/Domain.php
@@ -0,0 +1,183 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route;
+
+use think\helper\Str;
+use think\Request;
+use think\Route;
+use think\route\dispatch\Callback as CallbackDispatch;
+use think\route\dispatch\Controller as ControllerDispatch;
+
+/**
+ * 域名路由
+ */
+class Domain extends RuleGroup
+{
+ /**
+ * 架构函数
+ * @access public
+ * @param Route $router 路由对象
+ * @param string $name 路由域名
+ * @param mixed $rule 域名路由
+ */
+ public function __construct(Route $router, string $name = null, $rule = null)
+ {
+ $this->router = $router;
+ $this->domain = $name;
+ $this->rule = $rule;
+ }
+
+ /**
+ * 检测域名路由
+ * @access public
+ * @param Request $request 请求对象
+ * @param string $url 访问地址
+ * @param bool $completeMatch 路由是否完全匹配
+ * @return Dispatch|false
+ */
+ public function check(Request $request, string $url, bool $completeMatch = false)
+ {
+ // 检测URL绑定
+ $result = $this->checkUrlBind($request, $url);
+
+ if (!empty($this->option['append'])) {
+ $request->setRoute($this->option['append']);
+ unset($this->option['append']);
+ }
+
+ if (false !== $result) {
+ return $result;
+ }
+
+ return parent::check($request, $url, $completeMatch);
+ }
+
+ /**
+ * 设置路由绑定
+ * @access public
+ * @param string $bind 绑定信息
+ * @return $this
+ */
+ public function bind(string $bind)
+ {
+ $this->router->bind($bind, $this->domain);
+
+ return $this;
+ }
+
+ /**
+ * 检测URL绑定
+ * @access private
+ * @param Request $request
+ * @param string $url URL地址
+ * @return Dispatch|false
+ */
+ private function checkUrlBind(Request $request, string $url)
+ {
+ $bind = $this->router->getDomainBind($this->domain);
+
+ if ($bind) {
+ $this->parseBindAppendParam($bind);
+
+ // 如果有URL绑定 则进行绑定检测
+ $type = substr($bind, 0, 1);
+ $bind = substr($bind, 1);
+
+ $bindTo = [
+ '\\' => 'bindToClass',
+ '@' => 'bindToController',
+ ':' => 'bindToNamespace',
+ ];
+
+ if (isset($bindTo[$type])) {
+ return $this->{$bindTo[$type]}($request, $url, $bind);
+ }
+ }
+
+ return false;
+ }
+
+ protected function parseBindAppendParam(string &$bind): void
+ {
+ if (false !== strpos($bind, '?')) {
+ [$bind, $query] = explode('?', $bind);
+ parse_str($query, $vars);
+ $this->append($vars);
+ }
+ }
+
+ /**
+ * 绑定到类
+ * @access protected
+ * @param Request $request
+ * @param string $url URL地址
+ * @param string $class 类名(带命名空间)
+ * @return CallbackDispatch
+ */
+ protected function bindToClass(Request $request, string $url, string $class): CallbackDispatch
+ {
+ $array = explode('|', $url, 2);
+ $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action');
+ $param = [];
+
+ if (!empty($array[1])) {
+ $this->parseUrlParams($array[1], $param);
+ }
+
+ return new CallbackDispatch($request, $this, [$class, $action], $param);
+ }
+
+ /**
+ * 绑定到命名空间
+ * @access protected
+ * @param Request $request
+ * @param string $url URL地址
+ * @param string $namespace 命名空间
+ * @return CallbackDispatch
+ */
+ protected function bindToNamespace(Request $request, string $url, string $namespace): CallbackDispatch
+ {
+ $array = explode('|', $url, 3);
+ $class = !empty($array[0]) ? $array[0] : $this->router->config('default_controller');
+ $method = !empty($array[1]) ? $array[1] : $this->router->config('default_action');
+ $param = [];
+
+ if (!empty($array[2])) {
+ $this->parseUrlParams($array[2], $param);
+ }
+
+ return new CallbackDispatch($request, $this, [$namespace . '\\' . Str::studly($class), $method], $param);
+ }
+
+ /**
+ * 绑定到控制器
+ * @access protected
+ * @param Request $request
+ * @param string $url URL地址
+ * @param string $controller 控制器名
+ * @return ControllerDispatch
+ */
+ protected function bindToController(Request $request, string $url, string $controller): ControllerDispatch
+ {
+ $array = explode('|', $url, 2);
+ $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action');
+ $param = [];
+
+ if (!empty($array[1])) {
+ $this->parseUrlParams($array[1], $param);
+ }
+
+ return new ControllerDispatch($request, $this, $controller . '/' . $action, $param);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/route/Resource.php b/vendor/topthink/framework/src/think/route/Resource.php
new file mode 100755
index 0000000..5185cdc
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/Resource.php
@@ -0,0 +1,251 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route;
+
+use think\Route;
+
+/**
+ * 资源路由类
+ */
+class Resource extends RuleGroup
+{
+ /**
+ * 资源路由名称
+ * @var string
+ */
+ protected $resource;
+
+ /**
+ * 资源路由地址
+ * @var string
+ */
+ protected $route;
+
+ /**
+ * REST方法定义
+ * @var array
+ */
+ protected $rest = [];
+
+ /**
+ * 模型绑定
+ * @var array
+ */
+ protected $model = [];
+
+ /**
+ * 数据验证
+ * @var array
+ */
+ protected $validate = [];
+
+ /**
+ * 中间件
+ * @var array
+ */
+ protected $middleware = [];
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Route $router 路由对象
+ * @param RuleGroup $parent 上级对象
+ * @param string $name 资源名称
+ * @param string $route 路由地址
+ * @param array $rest 资源定义
+ */
+ public function __construct(Route $router, RuleGroup $parent = null, string $name = '', string $route = '', array $rest = [])
+ {
+ $name = ltrim($name, '/');
+ $this->router = $router;
+ $this->parent = $parent;
+ $this->resource = $name;
+ $this->route = $route;
+ $this->name = strpos($name, '.') ? strstr($name, '.', true) : $name;
+
+ $this->setFullName();
+
+ // 资源路由默认为完整匹配
+ $this->option['complete_match'] = true;
+
+ $this->rest = $rest;
+
+ if ($this->parent) {
+ $this->domain = $this->parent->getDomain();
+ $this->parent->addRuleItem($this);
+ }
+
+ if ($router->isTest()) {
+ $this->buildResourceRule();
+ }
+ }
+
+ /**
+ * 生成资源路由规则
+ * @access protected
+ * @return void
+ */
+ protected function buildResourceRule(): void
+ {
+ $rule = $this->resource;
+ $option = $this->option;
+ $origin = $this->router->getGroup();
+ $this->router->setGroup($this);
+
+ if (strpos($rule, '.')) {
+ // 注册嵌套资源路由
+ $array = explode('.', $rule);
+ $last = array_pop($array);
+ $item = [];
+
+ foreach ($array as $val) {
+ $item[] = $val . '/<' . ($option['var'][$val] ?? $val . '_id') . '>';
+ }
+
+ $rule = implode('/', $item) . '/' . $last;
+ }
+
+ $prefix = substr($rule, strlen($this->name) + 1);
+
+ // 注册资源路由
+ foreach ($this->rest as $key => $val) {
+ if ((isset($option['only']) && !in_array($key, $option['only']))
+ || (isset($option['except']) && in_array($key, $option['except']))) {
+ continue;
+ }
+
+ if (isset($last) && strpos($val[1], '<id>') && isset($option['var'][$last])) {
+ $val[1] = str_replace('<id>', '<' . $option['var'][$last] . '>', $val[1]);
+ } elseif (strpos($val[1], '<id>') && isset($option['var'][$rule])) {
+ $val[1] = str_replace('<id>', '<' . $option['var'][$rule] . '>', $val[1]);
+ }
+
+ $ruleItem = $this->addRule(trim($prefix . $val[1], '/'), $this->route . '/' . $val[2], $val[0]);
+
+ foreach (['model', 'validate', 'middleware', 'pattern'] as $name) {
+ if (isset($this->$name[$key])) {
+ call_user_func_array([$ruleItem, $name], (array) $this->$name[$key]);
+ }
+
+ }
+ }
+
+ $this->router->setGroup($origin);
+ }
+
+ /**
+ * 设置资源允许
+ * @access public
+ * @param array $only 资源允许
+ * @return $this
+ */
+ public function only(array $only)
+ {
+ return $this->setOption('only', $only);
+ }
+
+ /**
+ * 设置资源排除
+ * @access public
+ * @param array $except 排除资源
+ * @return $this
+ */
+ public function except(array $except)
+ {
+ return $this->setOption('except', $except);
+ }
+
+ /**
+ * 设置资源路由的变量
+ * @access public
+ * @param array $vars 资源变量
+ * @return $this
+ */
+ public function vars(array $vars)
+ {
+ return $this->setOption('var', $vars);
+ }
+
+ /**
+ * 绑定资源验证
+ * @access public
+ * @param array|string $name 资源类型或者验证信息
+ * @param array|string $validate 验证信息
+ * @return $this
+ */
+ public function withValidate($name, $validate = [])
+ {
+ if (is_array($name)) {
+ $this->validate = array_merge($this->validate, $name);
+ } else {
+ $this->validate[$name] = $validate;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 绑定资源模型
+ * @access public
+ * @param array|string $name 资源类型或者模型绑定
+ * @param array|string $model 模型绑定
+ * @return $this
+ */
+ public function withModel($name, $model = [])
+ {
+ if (is_array($name)) {
+ $this->model = array_merge($this->model, $name);
+ } else {
+ $this->model[$name] = $model;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 绑定资源中间件
+ * @access public
+ * @param array|string $name 资源类型或者中间件定义
+ * @param array|string $middleware 中间件定义
+ * @return $this
+ */
+ public function withMiddleware($name, $middleware = [])
+ {
+ if (is_array($name)) {
+ $this->middleware = array_merge($this->middleware, $name);
+ } else {
+ $this->middleware[$name] = $middleware;
+ }
+
+ return $this;
+ }
+
+ /**
+ * rest方法定义和修改
+ * @access public
+ * @param array|string $name 方法名称
+ * @param array|bool $resource 资源
+ * @return $this
+ */
+ public function rest($name, $resource = [])
+ {
+ if (is_array($name)) {
+ $this->rest = $resource ? $name : array_merge($this->rest, $name);
+ } else {
+ $this->rest[$name] = $resource;
+ }
+
+ return $this;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/route/Rule.php b/vendor/topthink/framework/src/think/route/Rule.php
new file mode 100755
index 0000000..2d864cd
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/Rule.php
@@ -0,0 +1,923 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route;
+
+use Closure;
+use think\Container;
+use think\middleware\AllowCrossDomain;
+use think\middleware\CheckRequestCache;
+use think\middleware\FormTokenCheck;
+use think\Request;
+use think\Route;
+use think\route\dispatch\Callback as CallbackDispatch;
+use think\route\dispatch\Controller as ControllerDispatch;
+
+/**
+ * 路由规则基础类
+ */
+abstract class Rule
+{
+ /**
+ * 路由标识
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * 所在域名
+ * @var string
+ */
+ protected $domain;
+
+ /**
+ * 路由对象
+ * @var Route
+ */
+ protected $router;
+
+ /**
+ * 路由所属分组
+ * @var RuleGroup
+ */
+ protected $parent;
+
+ /**
+ * 路由规则
+ * @var mixed
+ */
+ protected $rule;
+
+ /**
+ * 路由地址
+ * @var string|Closure
+ */
+ protected $route;
+
+ /**
+ * 请求类型
+ * @var string
+ */
+ protected $method;
+
+ /**
+ * 路由变量
+ * @var array
+ */
+ protected $vars = [];
+
+ /**
+ * 路由参数
+ * @var array
+ */
+ protected $option = [];
+
+ /**
+ * 路由变量规则
+ * @var array
+ */
+ protected $pattern = [];
+
+ /**
+ * 需要和分组合并的路由参数
+ * @var array
+ */
+ protected $mergeOptions = ['model', 'append', 'middleware'];
+
+ abstract public function check(Request $request, string $url, bool $completeMatch = false);
+
+ /**
+ * 设置路由参数
+ * @access public
+ * @param array $option 参数
+ * @return $this
+ */
+ public function option(array $option)
+ {
+ $this->option = array_merge($this->option, $option);
+
+ return $this;
+ }
+
+ /**
+ * 设置单个路由参数
+ * @access public
+ * @param string $name 参数名
+ * @param mixed $value 值
+ * @return $this
+ */
+ public function setOption(string $name, $value)
+ {
+ $this->option[$name] = $value;
+
+ return $this;
+ }
+
+ /**
+ * 注册变量规则
+ * @access public
+ * @param array $pattern 变量规则
+ * @return $this
+ */
+ public function pattern(array $pattern)
+ {
+ $this->pattern = array_merge($this->pattern, $pattern);
+
+ return $this;
+ }
+
+ /**
+ * 设置标识
+ * @access public
+ * @param string $name 标识名
+ * @return $this
+ */
+ public function name(string $name)
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * 获取路由对象
+ * @access public
+ * @return Route
+ */
+ public function getRouter(): Route
+ {
+ return $this->router;
+ }
+
+ /**
+ * 获取Name
+ * @access public
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name ?: '';
+ }
+
+ /**
+ * 获取当前路由规则
+ * @access public
+ * @return mixed
+ */
+ public function getRule()
+ {
+ return $this->rule;
+ }
+
+ /**
+ * 获取当前路由地址
+ * @access public
+ * @return mixed
+ */
+ public function getRoute()
+ {
+ return $this->route;
+ }
+
+ /**
+ * 获取当前路由的变量
+ * @access public
+ * @return array
+ */
+ public function getVars(): array
+ {
+ return $this->vars;
+ }
+
+ /**
+ * 获取Parent对象
+ * @access public
+ * @return $this|null
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * 获取路由所在域名
+ * @access public
+ * @return string
+ */
+ public function getDomain(): string
+ {
+ return $this->domain ?: $this->parent->getDomain();
+ }
+
+ /**
+ * 获取路由参数
+ * @access public
+ * @param string $name 变量名
+ * @return mixed
+ */
+ public function config(string $name = '')
+ {
+ return $this->router->config($name);
+ }
+
+ /**
+ * 获取变量规则定义
+ * @access public
+ * @param string $name 变量名
+ * @return mixed
+ */
+ public function getPattern(string $name = '')
+ {
+ $pattern = $this->pattern;
+
+ if ($this->parent) {
+ $pattern = array_merge($this->parent->getPattern(), $pattern);
+ }
+
+ if ('' === $name) {
+ return $pattern;
+ }
+
+ return $pattern[$name] ?? null;
+ }
+
+ /**
+ * 获取路由参数定义
+ * @access public
+ * @param string $name 参数名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getOption(string $name = '', $default = null)
+ {
+ $option = $this->option;
+
+ if ($this->parent) {
+ $parentOption = $this->parent->getOption();
+
+ // 合并分组参数
+ foreach ($this->mergeOptions as $item) {
+ if (isset($parentOption[$item]) && isset($option[$item])) {
+ $option[$item] = array_merge($parentOption[$item], $option[$item]);
+ }
+ }
+
+ $option = array_merge($parentOption, $option);
+ }
+
+ if ('' === $name) {
+ return $option;
+ }
+
+ return $option[$name] ?? $default;
+ }
+
+ /**
+ * 获取当前路由的请求类型
+ * @access public
+ * @return string
+ */
+ public function getMethod(): string
+ {
+ return strtolower($this->method);
+ }
+
+ /**
+ * 设置路由请求类型
+ * @access public
+ * @param string $method 请求类型
+ * @return $this
+ */
+ public function method(string $method)
+ {
+ return $this->setOption('method', strtolower($method));
+ }
+
+ /**
+ * 检查后缀
+ * @access public
+ * @param string $ext URL后缀
+ * @return $this
+ */
+ public function ext(string $ext = '')
+ {
+ return $this->setOption('ext', $ext);
+ }
+
+ /**
+ * 检查禁止后缀
+ * @access public
+ * @param string $ext URL后缀
+ * @return $this
+ */
+ public function denyExt(string $ext = '')
+ {
+ return $this->setOption('deny_ext', $ext);
+ }
+
+ /**
+ * 检查域名
+ * @access public
+ * @param string $domain 域名
+ * @return $this
+ */
+ public function domain(string $domain)
+ {
+ $this->domain = $domain;
+ return $this->setOption('domain', $domain);
+ }
+
+ /**
+ * 设置参数过滤检查
+ * @access public
+ * @param array $filter 参数过滤
+ * @return $this
+ */
+ public function filter(array $filter)
+ {
+ $this->option['filter'] = $filter;
+
+ return $this;
+ }
+
+ /**
+ * 绑定模型
+ * @access public
+ * @param array|string|Closure $var 路由变量名 多个使用 & 分割
+ * @param string|Closure $model 绑定模型类
+ * @param bool $exception 是否抛出异常
+ * @return $this
+ */
+ public function model($var, $model = null, bool $exception = true)
+ {
+ if ($var instanceof Closure) {
+ $this->option['model'][] = $var;
+ } elseif (is_array($var)) {
+ $this->option['model'] = $var;
+ } elseif (is_null($model)) {
+ $this->option['model']['id'] = [$var, true];
+ } else {
+ $this->option['model'][$var] = [$model, $exception];
+ }
+
+ return $this;
+ }
+
+ /**
+ * 附加路由隐式参数
+ * @access public
+ * @param array $append 追加参数
+ * @return $this
+ */
+ public function append(array $append = [])
+ {
+ $this->option['append'] = $append;
+
+ return $this;
+ }
+
+ /**
+ * 绑定验证
+ * @access public
+ * @param mixed $validate 验证器类
+ * @param string $scene 验证场景
+ * @param array $message 验证提示
+ * @param bool $batch 批量验证
+ * @return $this
+ */
+ public function validate($validate, string $scene = null, array $message = [], bool $batch = false)
+ {
+ $this->option['validate'] = [$validate, $scene, $message, $batch];
+
+ return $this;
+ }
+
+ /**
+ * 指定路由中间件
+ * @access public
+ * @param string|array|Closure $middleware 中间件
+ * @param mixed $params 参数
+ * @return $this
+ */
+ public function middleware($middleware, ...$params)
+ {
+ if (empty($params) && is_array($middleware)) {
+ $this->option['middleware'] = $middleware;
+ } else {
+ foreach ((array) $middleware as $item) {
+ $this->option['middleware'][] = [$item, $params];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 允许跨域
+ * @access public
+ * @param array $header 自定义Header
+ * @return $this
+ */
+ public function allowCrossDomain(array $header = [])
+ {
+ return $this->middleware(AllowCrossDomain::class, $header);
+ }
+
+ /**
+ * 表单令牌验证
+ * @access public
+ * @param string $token 表单令牌token名称
+ * @return $this
+ */
+ public function token(string $token = '__token__')
+ {
+ return $this->middleware(FormTokenCheck::class, $token);
+ }
+
+ /**
+ * 设置路由缓存
+ * @access public
+ * @param array|string $cache 缓存
+ * @return $this
+ */
+ public function cache($cache)
+ {
+ return $this->middleware(CheckRequestCache::class, $cache);
+ }
+
+ /**
+ * 检查URL分隔符
+ * @access public
+ * @param string $depr URL分隔符
+ * @return $this
+ */
+ public function depr(string $depr)
+ {
+ return $this->setOption('param_depr', $depr);
+ }
+
+ /**
+ * 设置需要合并的路由参数
+ * @access public
+ * @param array $option 路由参数
+ * @return $this
+ */
+ public function mergeOptions(array $option = [])
+ {
+ $this->mergeOptions = array_merge($this->mergeOptions, $option);
+ return $this;
+ }
+
+ /**
+ * 检查是否为HTTPS请求
+ * @access public
+ * @param bool $https 是否为HTTPS
+ * @return $this
+ */
+ public function https(bool $https = true)
+ {
+ return $this->setOption('https', $https);
+ }
+
+ /**
+ * 检查是否为JSON请求
+ * @access public
+ * @param bool $json 是否为JSON
+ * @return $this
+ */
+ public function json(bool $json = true)
+ {
+ return $this->setOption('json', $json);
+ }
+
+ /**
+ * 检查是否为AJAX请求
+ * @access public
+ * @param bool $ajax 是否为AJAX
+ * @return $this
+ */
+ public function ajax(bool $ajax = true)
+ {
+ return $this->setOption('ajax', $ajax);
+ }
+
+ /**
+ * 检查是否为PJAX请求
+ * @access public
+ * @param bool $pjax 是否为PJAX
+ * @return $this
+ */
+ public function pjax(bool $pjax = true)
+ {
+ return $this->setOption('pjax', $pjax);
+ }
+
+ /**
+ * 路由到一个模板地址 需要额外传入的模板变量
+ * @access public
+ * @param array $view 视图
+ * @return $this
+ */
+ public function view(array $view = [])
+ {
+ return $this->setOption('view', $view);
+ }
+
+ /**
+ * 通过闭包检查路由是否匹配
+ * @access public
+ * @param callable $match 闭包
+ * @return $this
+ */
+ public function match(callable $match)
+ {
+ return $this->setOption('match', $match);
+ }
+
+ /**
+ * 设置路由完整匹配
+ * @access public
+ * @param bool $match 是否完整匹配
+ * @return $this
+ */
+ public function completeMatch(bool $match = true)
+ {
+ return $this->setOption('complete_match', $match);
+ }
+
+ /**
+ * 是否去除URL最后的斜线
+ * @access public
+ * @param bool $remove 是否去除最后斜线
+ * @return $this
+ */
+ public function removeSlash(bool $remove = true)
+ {
+ return $this->setOption('remove_slash', $remove);
+ }
+
+ /**
+ * 设置路由规则全局有效
+ * @access public
+ * @return $this
+ */
+ public function crossDomainRule()
+ {
+ if ($this instanceof RuleGroup) {
+ $method = '*';
+ } else {
+ $method = $this->method;
+ }
+
+ $this->router->setCrossDomainRule($this, $method);
+
+ return $this;
+ }
+
+ /**
+ * 解析匹配到的规则路由
+ * @access public
+ * @param Request $request 请求对象
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @param string $url URL地址
+ * @param array $option 路由参数
+ * @param array $matches 匹配的变量
+ * @return Dispatch
+ */
+ public function parseRule(Request $request, string $rule, $route, string $url, array $option = [], array $matches = []): Dispatch
+ {
+ if (is_string($route) && isset($option['prefix'])) {
+ // 路由地址前缀
+ $route = $option['prefix'] . $route;
+ }
+
+ // 替换路由地址中的变量
+ $extraParams = true;
+ $search = $replace = [];
+ $depr = $this->router->config('pathinfo_depr');
+ foreach ($matches as $key => $value) {
+ $search[] = '<' . $key . '>';
+ $replace[] = $value;
+
+ $search[] = ':' . $key;
+ $replace[] = $value;
+
+ if (strpos($value, $depr)) {
+ $extraParams = false;
+ }
+ }
+
+ if (is_string($route)) {
+ $route = str_replace($search, $replace, $route);
+ }
+
+ // 解析额外参数
+ if ($extraParams) {
+ $count = substr_count($rule, '/');
+ $url = array_slice(explode('|', $url), $count + 1);
+ $this->parseUrlParams(implode('|', $url), $matches);
+ }
+
+ $this->vars = $matches;
+
+ // 发起路由调度
+ return $this->dispatch($request, $route, $option);
+ }
+
+ /**
+ * 发起路由调度
+ * @access protected
+ * @param Request $request Request对象
+ * @param mixed $route 路由地址
+ * @param array $option 路由参数
+ * @return Dispatch
+ */
+ protected function dispatch(Request $request, $route, array $option): Dispatch
+ {
+ if (is_subclass_of($route, Dispatch::class)) {
+ $result = new $route($request, $this, $route, $this->vars);
+ } elseif ($route instanceof Closure) {
+ // 执行闭包
+ $result = new CallbackDispatch($request, $this, $route, $this->vars);
+ } elseif (false !== strpos($route, '@') || false !== strpos($route, '::') || false !== strpos($route, '\\')) {
+ // 路由到类的方法
+ $route = str_replace('::', '@', $route);
+ $result = $this->dispatchMethod($request, $route);
+ } else {
+ // 路由到控制器/操作
+ $result = $this->dispatchController($request, $route);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 解析URL地址为 模块/控制器/操作
+ * @access protected
+ * @param Request $request Request对象
+ * @param string $route 路由地址
+ * @return CallbackDispatch
+ */
+ protected function dispatchMethod(Request $request, string $route): CallbackDispatch
+ {
+ $path = $this->parseUrlPath($route);
+
+ $route = str_replace('/', '@', implode('/', $path));
+ $method = strpos($route, '@') ? explode('@', $route) : $route;
+
+ return new CallbackDispatch($request, $this, $method, $this->vars);
+ }
+
+ /**
+ * 解析URL地址为 模块/控制器/操作
+ * @access protected
+ * @param Request $request Request对象
+ * @param string $route 路由地址
+ * @return ControllerDispatch
+ */
+ protected function dispatchController(Request $request, string $route): ControllerDispatch
+ {
+ $path = $this->parseUrlPath($route);
+
+ $action = array_pop($path);
+ $controller = !empty($path) ? array_pop($path) : null;
+
+ // 路由到模块/控制器/操作
+ return new ControllerDispatch($request, $this, [$controller, $action], $this->vars);
+ }
+
+ /**
+ * 路由检查
+ * @access protected
+ * @param array $option 路由参数
+ * @param Request $request Request对象
+ * @return bool
+ */
+ protected function checkOption(array $option, Request $request): bool
+ {
+ // 检查当前路由是否匹配
+ if (isset($option['match']) && is_callable($option['match'])) {
+ if (false === $option['match']($this, $request)) {
+ return false;
+ }
+ }
+
+ // 请求类型检测
+ if (!empty($option['method'])) {
+ if (is_string($option['method']) && false === stripos($option['method'], $request->method())) {
+ return false;
+ }
+ }
+
+ // AJAX PJAX 请求检查
+ foreach (['ajax', 'pjax', 'json'] as $item) {
+ if (isset($option[$item])) {
+ $call = 'is' . $item;
+ if ($option[$item] && !$request->$call() || !$option[$item] && $request->$call()) {
+ return false;
+ }
+ }
+ }
+
+ // 伪静态后缀检测
+ if ($request->url() != '/' && ((isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|'))
+ || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')))) {
+ return false;
+ }
+
+ // 域名检查
+ if ((isset($option['domain']) && !in_array($option['domain'], [$request->host(true), $request->subDomain()]))) {
+ return false;
+ }
+
+ // HTTPS检查
+ if ((isset($option['https']) && $option['https'] && !$request->isSsl())
+ || (isset($option['https']) && !$option['https'] && $request->isSsl())) {
+ return false;
+ }
+
+ // 请求参数检查
+ if (isset($option['filter'])) {
+ foreach ($option['filter'] as $name => $value) {
+ if ($request->param($name, '', null) != $value) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 解析URL地址中的参数Request对象
+ * @access protected
+ * @param string $rule 路由规则
+ * @param array $var 变量
+ * @return void
+ */
+ protected function parseUrlParams(string $url, array &$var = []): void
+ {
+ if ($url) {
+ preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) {
+ $var[$match[1]] = strip_tags($match[2]);
+ }, $url);
+ }
+ }
+
+ /**
+ * 解析URL的pathinfo参数
+ * @access public
+ * @param string $url URL地址
+ * @return array
+ */
+ public function parseUrlPath(string $url): array
+ {
+ // 分隔符替换 确保路由定义使用统一的分隔符
+ $url = str_replace('|', '/', $url);
+ $url = trim($url, '/');
+
+ if (strpos($url, '/')) {
+ // [控制器/操作]
+ $path = explode('/', $url);
+ } else {
+ $path = [$url];
+ }
+
+ return $path;
+ }
+
+ /**
+ * 生成路由的正则规则
+ * @access protected
+ * @param string $rule 路由规则
+ * @param array $match 匹配的变量
+ * @param array $pattern 路由变量规则
+ * @param array $option 路由参数
+ * @param bool $completeMatch 路由是否完全匹配
+ * @param string $suffix 路由正则变量后缀
+ * @return string
+ */
+ protected function buildRuleRegex(string $rule, array $match, array $pattern = [], array $option = [], bool $completeMatch = false, string $suffix = ''): string
+ {
+ foreach ($match as $name) {
+ $value = $this->buildNameRegex($name, $pattern, $suffix);
+ if ($value) {
+ $origin[] = $name;
+ $replace[] = $value;
+ }
+ }
+
+ // 是否区分 / 地址访问
+ if ('/' != $rule) {
+ if (!empty($option['remove_slash'])) {
+ $rule = rtrim($rule, '/');
+ } elseif (substr($rule, -1) == '/') {
+ $rule = rtrim($rule, '/');
+ $hasSlash = true;
+ }
+ }
+
+ $regex = isset($replace) ? str_replace($origin, $replace, $rule) : $rule;
+ $regex = str_replace([')?/', ')?-'], [')/', ')-'], $regex);
+
+ if (isset($hasSlash)) {
+ $regex .= '/';
+ }
+
+ return $regex . ($completeMatch ? '$' : '');
+ }
+
+ /**
+ * 生成路由变量的正则规则
+ * @access protected
+ * @param string $name 路由变量
+ * @param array $pattern 变量规则
+ * @param string $suffix 路由正则变量后缀
+ * @return string
+ */
+ protected function buildNameRegex(string $name, array $pattern, string $suffix): string
+ {
+ $optional = '';
+ $slash = substr($name, 0, 1);
+
+ if (in_array($slash, ['/', '-'])) {
+ $prefix = $slash;
+ $name = substr($name, 1);
+ $slash = substr($name, 0, 1);
+ } else {
+ $prefix = '';
+ }
+
+ if ('<' != $slash) {
+ return '';
+ }
+
+ if (strpos($name, '?')) {
+ $name = substr($name, 1, -2);
+ $optional = '?';
+ } elseif (strpos($name, '>')) {
+ $name = substr($name, 1, -1);
+ }
+
+ if (isset($pattern[$name])) {
+ $nameRule = $pattern[$name];
+ if (0 === strpos($nameRule, '/') && '/' == substr($nameRule, -1)) {
+ $nameRule = substr($nameRule, 1, -1);
+ }
+ } else {
+ $nameRule = $this->router->config('default_route_pattern');
+ }
+
+ return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional;
+ }
+
+ /**
+ * 设置路由参数
+ * @access public
+ * @param string $method 方法名
+ * @param array $args 调用参数
+ * @return $this
+ */
+ public function __call($method, $args)
+ {
+ if (count($args) > 1) {
+ $args[0] = $args;
+ }
+ array_unshift($args, $method);
+
+ return call_user_func_array([$this, 'setOption'], $args);
+ }
+
+ public function __sleep()
+ {
+ return ['name', 'rule', 'route', 'method', 'vars', 'option', 'pattern'];
+ }
+
+ public function __wakeup()
+ {
+ $this->router = Container::pull('route');
+ }
+
+ public function __debugInfo()
+ {
+ return [
+ 'name' => $this->name,
+ 'rule' => $this->rule,
+ 'route' => $this->route,
+ 'method' => $this->method,
+ 'vars' => $this->vars,
+ 'option' => $this->option,
+ 'pattern' => $this->pattern,
+ ];
+ }
+}
diff --git a/vendor/topthink/framework/src/think/route/RuleGroup.php b/vendor/topthink/framework/src/think/route/RuleGroup.php
new file mode 100755
index 0000000..cd9ddbd
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/RuleGroup.php
@@ -0,0 +1,523 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route;
+
+use Closure;
+use think\Container;
+use think\Exception;
+use think\Request;
+use think\Route;
+
+/**
+ * 路由分组类
+ */
+class RuleGroup extends Rule
+{
+ /**
+ * 分组路由(包括子分组)
+ * @var array
+ */
+ protected $rules = [];
+
+ /**
+ * 分组路由规则
+ * @var mixed
+ */
+ protected $rule;
+
+ /**
+ * MISS路由
+ * @var RuleItem
+ */
+ protected $miss;
+
+ /**
+ * 完整名称
+ * @var string
+ */
+ protected $fullName;
+
+ /**
+ * 分组别名
+ * @var string
+ */
+ protected $alias;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Route $router 路由对象
+ * @param RuleGroup $parent 上级对象
+ * @param string $name 分组名称
+ * @param mixed $rule 分组路由
+ */
+ public function __construct(Route $router, RuleGroup $parent = null, string $name = '', $rule = null)
+ {
+ $this->router = $router;
+ $this->parent = $parent;
+ $this->rule = $rule;
+ $this->name = trim($name, '/');
+
+ $this->setFullName();
+
+ if ($this->parent) {
+ $this->domain = $this->parent->getDomain();
+ $this->parent->addRuleItem($this);
+ }
+
+ if ($router->isTest()) {
+ $this->lazy(false);
+ }
+ }
+
+ /**
+ * 设置分组的路由规则
+ * @access public
+ * @return void
+ */
+ protected function setFullName(): void
+ {
+ if (false !== strpos($this->name, ':')) {
+ $this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name);
+ }
+
+ if ($this->parent && $this->parent->getFullName()) {
+ $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : '');
+ } else {
+ $this->fullName = $this->name;
+ }
+
+ if ($this->name) {
+ $this->router->getRuleName()->setGroup($this->name, $this);
+ }
+ }
+
+ /**
+ * 获取所属域名
+ * @access public
+ * @return string
+ */
+ public function getDomain(): string
+ {
+ return $this->domain ?: '-';
+ }
+
+ /**
+ * 获取分组别名
+ * @access public
+ * @return string
+ */
+ public function getAlias(): string
+ {
+ return $this->alias ?: '';
+ }
+
+ /**
+ * 检测分组路由
+ * @access public
+ * @param Request $request 请求对象
+ * @param string $url 访问地址
+ * @param bool $completeMatch 路由是否完全匹配
+ * @return Dispatch|false
+ */
+ public function check(Request $request, string $url, bool $completeMatch = false)
+ {
+ // 检查分组有效性
+ if (!$this->checkOption($this->option, $request) || !$this->checkUrl($url)) {
+ return false;
+ }
+
+ // 解析分组路由
+ if ($this instanceof Resource) {
+ $this->buildResourceRule();
+ } else {
+ $this->parseGroupRule($this->rule);
+ }
+
+ // 获取当前路由规则
+ $method = strtolower($request->method());
+ $rules = $this->getRules($method);
+ $option = $this->getOption();
+
+ if (isset($option['complete_match'])) {
+ $completeMatch = $option['complete_match'];
+ }
+
+ if (!empty($option['merge_rule_regex'])) {
+ // 合并路由正则规则进行路由匹配检查
+ $result = $this->checkMergeRuleRegex($request, $rules, $url, $completeMatch);
+
+ if (false !== $result) {
+ return $result;
+ }
+ }
+
+ // 检查分组路由
+ foreach ($rules as $key => $item) {
+ $result = $item[1]->check($request, $url, $completeMatch);
+
+ if (false !== $result) {
+ return $result;
+ }
+ }
+
+ if (!empty($option['dispatcher'])) {
+ $result = $this->parseRule($request, '', $option['dispatcher'], $url, $option);
+ } elseif ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) {
+ // 未匹配所有路由的路由规则处理
+ $result = $this->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption());
+ } else {
+ $result = false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * 分组URL匹配检查
+ * @access protected
+ * @param string $url URL
+ * @return bool
+ */
+ protected function checkUrl(string $url): bool
+ {
+ if ($this->fullName) {
+ $pos = strpos($this->fullName, '<');
+
+ if (false !== $pos) {
+ $str = substr($this->fullName, 0, $pos);
+ } else {
+ $str = $this->fullName;
+ }
+
+ if ($str && 0 !== stripos(str_replace('|', '/', $url), $str)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 设置路由分组别名
+ * @access public
+ * @param string $alias 路由分组别名
+ * @return $this
+ */
+ public function alias(string $alias)
+ {
+ $this->alias = $alias;
+ $this->router->getRuleName()->setGroup($alias, $this);
+
+ return $this;
+ }
+
+ /**
+ * 延迟解析分组的路由规则
+ * @access public
+ * @param bool $lazy 路由是否延迟解析
+ * @return $this
+ */
+ public function lazy(bool $lazy = true)
+ {
+ if (!$lazy) {
+ $this->parseGroupRule($this->rule);
+ $this->rule = null;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 解析分组和域名的路由规则及绑定
+ * @access public
+ * @param mixed $rule 路由规则
+ * @return void
+ */
+ public function parseGroupRule($rule): void
+ {
+ if (is_string($rule) && is_subclass_of($rule, Dispatch::class)) {
+ $this->dispatcher($rule);
+ return;
+ }
+
+ $origin = $this->router->getGroup();
+ $this->router->setGroup($this);
+
+ if ($rule instanceof \Closure) {
+ Container::getInstance()->invokeFunction($rule);
+ } elseif (is_string($rule) && $rule) {
+ $this->router->bind($rule, $this->domain);
+ }
+
+ $this->router->setGroup($origin);
+ }
+
+ /**
+ * 检测分组路由
+ * @access public
+ * @param Request $request 请求对象
+ * @param array $rules 路由规则
+ * @param string $url 访问地址
+ * @param bool $completeMatch 路由是否完全匹配
+ * @return Dispatch|false
+ */
+ protected function checkMergeRuleRegex(Request $request, array &$rules, string $url, bool $completeMatch)
+ {
+ $depr = $this->router->config('pathinfo_depr');
+ $url = $depr . str_replace('|', $depr, $url);
+ $regex = [];
+ $items = [];
+
+ foreach ($rules as $key => $val) {
+ $item = $val[1];
+ if ($item instanceof RuleItem) {
+ $rule = $depr . str_replace('/', $depr, $item->getRule());
+ if ($depr == $rule && $depr != $url) {
+ unset($rules[$key]);
+ continue;
+ }
+
+ $complete = $item->getOption('complete_match', $completeMatch);
+
+ if (false === strpos($rule, '<')) {
+ if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) {
+ return $item->checkRule($request, $url, []);
+ }
+
+ unset($rules[$key]);
+ continue;
+ }
+
+ $slash = preg_quote('/-' . $depr, '/');
+
+ if ($matchRule = preg_split('/[' . $slash . ']<\w+\??>/', $rule, 2)) {
+ if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) {
+ unset($rules[$key]);
+ continue;
+ }
+ }
+
+ if (preg_match_all('/[' . $slash . ']?<?\w+\??>?/', $rule, $matches)) {
+ unset($rules[$key]);
+ $pattern = array_merge($this->getPattern(), $item->getPattern());
+ $option = array_merge($this->getOption(), $item->getOption());
+
+ $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key);
+ $items[$key] = $item;
+ }
+ }
+ }
+
+ if (empty($regex)) {
+ return false;
+ }
+
+ try {
+ $result = preg_match('~^(?:' . implode('|', $regex) . ')~u', $url, $match);
+ } catch (\Exception $e) {
+ throw new Exception('route pattern error');
+ }
+
+ if ($result) {
+ $var = [];
+ foreach ($match as $key => $val) {
+ if (is_string($key) && '' !== $val) {
+ [$name, $pos] = explode('_THINK_', $key);
+
+ $var[$name] = $val;
+ }
+ }
+
+ if (!isset($pos)) {
+ foreach ($regex as $key => $item) {
+ if (0 === strpos(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) {
+ $pos = $key;
+ break;
+ }
+ }
+ }
+
+ $rule = $items[$pos]->getRule();
+ $array = $this->router->getRule($rule);
+
+ foreach ($array as $item) {
+ if (in_array($item->getMethod(), ['*', strtolower($request->method())])) {
+ $result = $item->checkRule($request, $url, $var);
+
+ if (false !== $result) {
+ return $result;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 获取分组的MISS路由
+ * @access public
+ * @return RuleItem|null
+ */
+ public function getMissRule(): ? RuleItem
+ {
+ return $this->miss;
+ }
+
+ /**
+ * 注册MISS路由
+ * @access public
+ * @param string|Closure $route 路由地址
+ * @param string $method 请求类型
+ * @return RuleItem
+ */
+ public function miss($route, string $method = '*') : RuleItem
+ {
+ // 创建路由规则实例
+ $ruleItem = new RuleItem($this->router, $this, null, '', $route, strtolower($method));
+
+ $ruleItem->setMiss();
+ $this->miss = $ruleItem;
+
+ return $ruleItem;
+ }
+
+ /**
+ * 添加分组下的路由规则
+ * @access public
+ * @param string $rule 路由规则
+ * @param mixed $route 路由地址
+ * @param string $method 请求类型
+ * @return RuleItem
+ */
+ public function addRule(string $rule, $route = null, string $method = '*'): RuleItem
+ {
+ // 读取路由标识
+ if (is_string($route)) {
+ $name = $route;
+ } else {
+ $name = null;
+ }
+
+ $method = strtolower($method);
+
+ if ('' === $rule || '/' === $rule) {
+ $rule .= '$';
+ }
+
+ // 创建路由规则实例
+ $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method);
+
+ $this->addRuleItem($ruleItem, $method);
+
+ return $ruleItem;
+ }
+
+ /**
+ * 注册分组下的路由规则
+ * @access public
+ * @param Rule $rule 路由规则
+ * @param string $method 请求类型
+ * @return $this
+ */
+ public function addRuleItem(Rule $rule, string $method = '*')
+ {
+ if (strpos($method, '|')) {
+ $rule->method($method);
+ $method = '*';
+ }
+
+ $this->rules[] = [$method, $rule];
+
+ if ($rule instanceof RuleItem && 'options' != $method) {
+ $this->rules[] = ['options', $rule->setAutoOptions()];
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置分组的路由前缀
+ * @access public
+ * @param string $prefix 路由前缀
+ * @return $this
+ */
+ public function prefix(string $prefix)
+ {
+ if ($this->parent && $this->parent->getOption('prefix')) {
+ $prefix = $this->parent->getOption('prefix') . $prefix;
+ }
+
+ return $this->setOption('prefix', $prefix);
+ }
+
+ /**
+ * 合并分组的路由规则正则
+ * @access public
+ * @param bool $merge 是否合并
+ * @return $this
+ */
+ public function mergeRuleRegex(bool $merge = true)
+ {
+ return $this->setOption('merge_rule_regex', $merge);
+ }
+
+ /**
+ * 设置分组的Dispatch调度
+ * @access public
+ * @param string $dispatch 调度类
+ * @return $this
+ */
+ public function dispatcher(string $dispatch)
+ {
+ return $this->setOption('dispatcher', $dispatch);
+ }
+
+ /**
+ * 获取完整分组Name
+ * @access public
+ * @return string
+ */
+ public function getFullName(): string
+ {
+ return $this->fullName ?: '';
+ }
+
+ /**
+ * 获取分组的路由规则
+ * @access public
+ * @param string $method 请求类型
+ * @return array
+ */
+ public function getRules(string $method = ''): array
+ {
+ if ('' === $method) {
+ return $this->rules;
+ }
+
+ return array_filter($this->rules, function ($item) use ($method) {
+ return $method == $item[0] || '*' == $item[0];
+ });
+ }
+
+ /**
+ * 清空分组下的路由规则
+ * @access public
+ * @return void
+ */
+ public function clear(): void
+ {
+ $this->rules = [];
+ }
+}
diff --git a/vendor/topthink/framework/src/think/route/RuleItem.php b/vendor/topthink/framework/src/think/route/RuleItem.php
new file mode 100755
index 0000000..e4e3246
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/RuleItem.php
@@ -0,0 +1,330 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route;
+
+use think\Exception;
+use think\Request;
+use think\Route;
+
+/**
+ * 路由规则类
+ */
+class RuleItem extends Rule
+{
+ /**
+ * 是否为MISS规则
+ * @var bool
+ */
+ protected $miss = false;
+
+ /**
+ * 是否为额外自动注册的OPTIONS规则
+ * @var bool
+ */
+ protected $autoOption = false;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Route $router 路由实例
+ * @param RuleGroup $parent 上级对象
+ * @param string $name 路由标识
+ * @param string $rule 路由规则
+ * @param string|\Closure $route 路由地址
+ * @param string $method 请求类型
+ */
+ public function __construct(Route $router, RuleGroup $parent, string $name = null, string $rule = '', $route = null, string $method = '*')
+ {
+ $this->router = $router;
+ $this->parent = $parent;
+ $this->name = $name;
+ $this->route = $route;
+ $this->method = $method;
+
+ $this->setRule($rule);
+
+ $this->router->setRule($this->rule, $this);
+ }
+
+ /**
+ * 设置当前路由规则为MISS路由
+ * @access public
+ * @return $this
+ */
+ public function setMiss()
+ {
+ $this->miss = true;
+ return $this;
+ }
+
+ /**
+ * 判断当前路由规则是否为MISS路由
+ * @access public
+ * @return bool
+ */
+ public function isMiss(): bool
+ {
+ return $this->miss;
+ }
+
+ /**
+ * 设置当前路由为自动注册OPTIONS
+ * @access public
+ * @return $this
+ */
+ public function setAutoOptions()
+ {
+ $this->autoOption = true;
+ return $this;
+ }
+
+ /**
+ * 判断当前路由规则是否为自动注册的OPTIONS路由
+ * @access public
+ * @return bool
+ */
+ public function isAutoOptions(): bool
+ {
+ return $this->autoOption;
+ }
+
+ /**
+ * 获取当前路由的URL后缀
+ * @access public
+ * @return string|null
+ */
+ public function getSuffix()
+ {
+ if (isset($this->option['ext'])) {
+ $suffix = $this->option['ext'];
+ } elseif ($this->parent->getOption('ext')) {
+ $suffix = $this->parent->getOption('ext');
+ } else {
+ $suffix = null;
+ }
+
+ return $suffix;
+ }
+
+ /**
+ * 路由规则预处理
+ * @access public
+ * @param string $rule 路由规则
+ * @return void
+ */
+ public function setRule(string $rule): void
+ {
+ if ('$' == substr($rule, -1, 1)) {
+ // 是否完整匹配
+ $rule = substr($rule, 0, -1);
+
+ $this->option['complete_match'] = true;
+ }
+
+ $rule = '/' != $rule ? ltrim($rule, '/') : '';
+
+ if ($this->parent && $prefix = $this->parent->getFullName()) {
+ $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : '');
+ }
+
+ if (false !== strpos($rule, ':')) {
+ $this->rule = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $rule);
+ } else {
+ $this->rule = $rule;
+ }
+
+ // 生成路由标识的快捷访问
+ $this->setRuleName();
+ }
+
+ /**
+ * 设置别名
+ * @access public
+ * @param string $name
+ * @return $this
+ */
+ public function name(string $name)
+ {
+ $this->name = $name;
+ $this->setRuleName(true);
+
+ return $this;
+ }
+
+ /**
+ * 设置路由标识 用于URL反解生成
+ * @access protected
+ * @param bool $first 是否插入开头
+ * @return void
+ */
+ protected function setRuleName(bool $first = false): void
+ {
+ if ($this->name) {
+ $this->router->setName($this->name, $this, $first);
+ }
+ }
+
+ /**
+ * 检测路由
+ * @access public
+ * @param Request $request 请求对象
+ * @param string $url 访问地址
+ * @param array $match 匹配路由变量
+ * @param bool $completeMatch 路由是否完全匹配
+ * @return Dispatch|false
+ */
+ public function checkRule(Request $request, string $url, $match = null, bool $completeMatch = false)
+ {
+ // 检查参数有效性
+ if (!$this->checkOption($this->option, $request)) {
+ return false;
+ }
+
+ // 合并分组参数
+ $option = $this->getOption();
+ $pattern = $this->getPattern();
+ $url = $this->urlSuffixCheck($request, $url, $option);
+
+ if (is_null($match)) {
+ $match = $this->checkMatch($url, $option, $pattern, $completeMatch);
+ }
+
+ if (false !== $match) {
+ return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match);
+ }
+
+ return false;
+ }
+
+ /**
+ * 检测路由(含路由匹配)
+ * @access public
+ * @param Request $request 请求对象
+ * @param string $url 访问地址
+ * @param bool $completeMatch 路由是否完全匹配
+ * @return Dispatch|false
+ */
+ public function check(Request $request, string $url, bool $completeMatch = false)
+ {
+ return $this->checkRule($request, $url, null, $completeMatch);
+ }
+
+ /**
+ * URL后缀及Slash检查
+ * @access protected
+ * @param Request $request 请求对象
+ * @param string $url 访问地址
+ * @param array $option 路由参数
+ * @return string
+ */
+ protected function urlSuffixCheck(Request $request, string $url, array $option = []): string
+ {
+ // 是否区分 / 地址访问
+ if (!empty($option['remove_slash']) && '/' != $this->rule) {
+ $this->rule = rtrim($this->rule, '/');
+ $url = rtrim($url, '|');
+ }
+
+ if (isset($option['ext'])) {
+ // 路由ext参数 优先于系统配置的URL伪静态后缀参数
+ $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url);
+ }
+
+ return $url;
+ }
+
+ /**
+ * 检测URL和规则路由是否匹配
+ * @access private
+ * @param string $url URL地址
+ * @param array $option 路由参数
+ * @param array $pattern 变量规则
+ * @param bool $completeMatch 是否完全匹配
+ * @return array|false
+ */
+ private function checkMatch(string $url, array $option, array $pattern, bool $completeMatch)
+ {
+ if (isset($option['complete_match'])) {
+ $completeMatch = $option['complete_match'];
+ }
+
+ $depr = $this->router->config('pathinfo_depr');
+
+ // 检查完整规则定义
+ if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . ($completeMatch ? '$' : '') . '/', str_replace('|', $depr, $url))) {
+ return false;
+ }
+
+ $var = [];
+ $url = $depr . str_replace('|', $depr, $url);
+ $rule = $depr . str_replace('/', $depr, $this->rule);
+
+ if ($depr == $rule && $depr != $url) {
+ return false;
+ }
+
+ if (false === strpos($rule, '<')) {
+ if (0 === strcasecmp($rule, $url) || (!$completeMatch && 0 === strncasecmp($rule . $depr, $url . $depr, strlen($rule . $depr)))) {
+ return $var;
+ }
+ return false;
+ }
+
+ $slash = preg_quote('/-' . $depr, '/');
+
+ if ($matchRule = preg_split('/[' . $slash . ']?<\w+\??>/', $rule, 2)) {
+ if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) {
+ return false;
+ }
+ }
+
+ if (preg_match_all('/[' . $slash . ']?<?\w+\??>?/', $rule, $matches)) {
+ $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch);
+
+ try {
+ if (!preg_match('~^' . $regex . '~u', $url, $match)) {
+ return false;
+ }
+ } catch (\Exception $e) {
+ throw new Exception('route pattern error');
+ }
+
+ foreach ($match as $key => $val) {
+ if (is_string($key)) {
+ $var[$key] = $val;
+ }
+ }
+ }
+
+ // 成功匹配后返回URL中的动态变量数组
+ return $var;
+ }
+
+ /**
+ * 设置路由所属分组(用于注解路由)
+ * @access public
+ * @param string $name 分组名称或者标识
+ * @return $this
+ */
+ public function group(string $name)
+ {
+ $group = $this->router->getRuleName()->getGroup($name);
+
+ if ($group) {
+ $this->parent = $group;
+ $this->setRule($this->rule);
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/topthink/framework/src/think/route/RuleName.php b/vendor/topthink/framework/src/think/route/RuleName.php
new file mode 100755
index 0000000..0684367
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/RuleName.php
@@ -0,0 +1,211 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route;
+
+/**
+ * 路由标识管理类
+ */
+class RuleName
+{
+ /**
+ * 路由标识
+ * @var array
+ */
+ protected $item = [];
+
+ /**
+ * 路由规则
+ * @var array
+ */
+ protected $rule = [];
+
+ /**
+ * 路由分组
+ * @var array
+ */
+ protected $group = [];
+
+ /**
+ * 注册路由标识
+ * @access public
+ * @param string $name 路由标识
+ * @param RuleItem $ruleItem 路由规则
+ * @param bool $first 是否优先
+ * @return void
+ */
+ public function setName(string $name, RuleItem $ruleItem, bool $first = false): void
+ {
+ $name = strtolower($name);
+ $item = $this->getRuleItemInfo($ruleItem);
+ if ($first && isset($this->item[$name])) {
+ array_unshift($this->item[$name], $item);
+ } else {
+ $this->item[$name][] = $item;
+ }
+ }
+
+ /**
+ * 注册路由分组标识
+ * @access public
+ * @param string $name 路由分组标识
+ * @param RuleGroup $group 路由分组
+ * @return void
+ */
+ public function setGroup(string $name, RuleGroup $group): void
+ {
+ $this->group[strtolower($name)] = $group;
+ }
+
+ /**
+ * 注册路由规则
+ * @access public
+ * @param string $rule 路由规则
+ * @param RuleItem $ruleItem 路由
+ * @return void
+ */
+ public function setRule(string $rule, RuleItem $ruleItem): void
+ {
+ $route = $ruleItem->getRoute();
+
+ if (is_string($route)) {
+ $this->rule[$rule][$route] = $ruleItem;
+ } else {
+ $this->rule[$rule][] = $ruleItem;
+ }
+ }
+
+ /**
+ * 根据路由规则获取路由对象(列表)
+ * @access public
+ * @param string $rule 路由标识
+ * @return RuleItem[]
+ */
+ public function getRule(string $rule): array
+ {
+ return $this->rule[$rule] ?? [];
+ }
+
+ /**
+ * 根据路由分组标识获取分组
+ * @access public
+ * @param string $name 路由分组标识
+ * @return RuleGroup|null
+ */
+ public function getGroup(string $name)
+ {
+ return $this->group[strtolower($name)] ?? null;
+ }
+
+ /**
+ * 清空路由规则
+ * @access public
+ * @return void
+ */
+ public function clear(): void
+ {
+ $this->item = [];
+ $this->rule = [];
+ }
+
+ /**
+ * 获取全部路由列表
+ * @access public
+ * @return array
+ */
+ public function getRuleList(): array
+ {
+ $list = [];
+
+ foreach ($this->rule as $rule => $rules) {
+ foreach ($rules as $item) {
+ $val = [];
+
+ foreach (['method', 'rule', 'name', 'route', 'domain', 'pattern', 'option'] as $param) {
+ $call = 'get' . $param;
+ $val[$param] = $item->$call();
+ }
+
+ if ($item->isMiss()) {
+ $val['rule'] .= '<MISS>';
+ }
+
+ $list[] = $val;
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * 导入路由标识
+ * @access public
+ * @param array $item 路由标识
+ * @return void
+ */
+ public function import(array $item): void
+ {
+ $this->item = $item;
+ }
+
+ /**
+ * 根据路由标识获取路由信息(用于URL生成)
+ * @access public
+ * @param string $name 路由标识
+ * @param string $domain 域名
+ * @param string $method 请求类型
+ * @return array
+ */
+ public function getName(string $name = null, string $domain = null, string $method = '*'): array
+ {
+ if (is_null($name)) {
+ return $this->item;
+ }
+
+ $name = strtolower($name);
+ $method = strtolower($method);
+ $result = [];
+
+ if (isset($this->item[$name])) {
+ if (is_null($domain)) {
+ $result = $this->item[$name];
+ } else {
+ foreach ($this->item[$name] as $item) {
+ $itemDomain = $item['domain'];
+ $itemMethod = $item['method'];
+
+ if (($itemDomain == $domain || '-' == $itemDomain) && ('*' == $itemMethod || '*' == $method || $method == $itemMethod)) {
+ $result[] = $item;
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 获取路由信息
+ * @access protected
+ * @param RuleItem $item 路由规则
+ * @return array
+ */
+ protected function getRuleItemInfo(RuleItem $item): array
+ {
+ return [
+ 'rule' => $item->getRule(),
+ 'domain' => $item->getDomain(),
+ 'method' => $item->getMethod(),
+ 'suffix' => $item->getSuffix(),
+ ];
+ }
+}
diff --git a/vendor/topthink/framework/src/think/route/Url.php b/vendor/topthink/framework/src/think/route/Url.php
new file mode 100755
index 0000000..b3f8b9f
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/Url.php
@@ -0,0 +1,517 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route;
+
+use think\App;
+use think\Route;
+
+/**
+ * 路由地址生成
+ */
+class Url
+{
+ /**
+ * 应用对象
+ * @var App
+ */
+ protected $app;
+
+ /**
+ * 路由对象
+ * @var Route
+ */
+ protected $route;
+
+ /**
+ * URL变量
+ * @var array
+ */
+ protected $vars = [];
+
+ /**
+ * 路由URL
+ * @var string
+ */
+ protected $url;
+
+ /**
+ * URL 根地址
+ * @var string
+ */
+ protected $root = '';
+
+ /**
+ * HTTPS
+ * @var bool
+ */
+ protected $https;
+
+ /**
+ * URL后缀
+ * @var string|bool
+ */
+ protected $suffix = true;
+
+ /**
+ * URL域名
+ * @var string|bool
+ */
+ protected $domain = false;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param string $url URL地址
+ * @param array $vars 参数
+ */
+ public function __construct(Route $route, App $app, string $url = '', array $vars = [])
+ {
+ $this->route = $route;
+ $this->app = $app;
+ $this->url = $url;
+ $this->vars = $vars;
+ }
+
+ /**
+ * 设置URL参数
+ * @access public
+ * @param array $vars URL参数
+ * @return $this
+ */
+ public function vars(array $vars = [])
+ {
+ $this->vars = $vars;
+ return $this;
+ }
+
+ /**
+ * 设置URL后缀
+ * @access public
+ * @param string|bool $suffix URL后缀
+ * @return $this
+ */
+ public function suffix($suffix)
+ {
+ $this->suffix = $suffix;
+ return $this;
+ }
+
+ /**
+ * 设置URL域名(或者子域名)
+ * @access public
+ * @param string|bool $domain URL域名
+ * @return $this
+ */
+ public function domain($domain)
+ {
+ $this->domain = $domain;
+ return $this;
+ }
+
+ /**
+ * 设置URL 根地址
+ * @access public
+ * @param string $root URL root
+ * @return $this
+ */
+ public function root(string $root)
+ {
+ $this->root = $root;
+ return $this;
+ }
+
+ /**
+ * 设置是否使用HTTPS
+ * @access public
+ * @param bool $https
+ * @return $this
+ */
+ public function https(bool $https = true)
+ {
+ $this->https = $https;
+ return $this;
+ }
+
+ /**
+ * 检测域名
+ * @access protected
+ * @param string $url URL
+ * @param string|true $domain 域名
+ * @return string
+ */
+ protected function parseDomain(string &$url, $domain): string
+ {
+ if (!$domain) {
+ return '';
+ }
+
+ $request = $this->app->request;
+ $rootDomain = $request->rootDomain();
+
+ if (true === $domain) {
+ // 自动判断域名
+ $domain = $request->host();
+ $domains = $this->route->getDomains();
+
+ if (!empty($domains)) {
+ $routeDomain = array_keys($domains);
+ foreach ($routeDomain as $domainPrefix) {
+ if (0 === strpos($domainPrefix, '*.') && strpos($domain, ltrim($domainPrefix, '*.')) !== false) {
+ foreach ($domains as $key => $rule) {
+ $rule = is_array($rule) ? $rule[0] : $rule;
+ if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) {
+ $url = ltrim($url, $rule);
+ $domain = $key;
+
+ // 生成对应子域名
+ if (!empty($rootDomain)) {
+ $domain .= $rootDomain;
+ }
+ break;
+ } elseif (false !== strpos($key, '*')) {
+ if (!empty($rootDomain)) {
+ $domain .= $rootDomain;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+ } elseif (false === strpos($domain, '.') && 0 !== strpos($domain, $rootDomain)) {
+ $domain .= '.' . $rootDomain;
+ }
+
+ if (false !== strpos($domain, '://')) {
+ $scheme = '';
+ } else {
+ $scheme = $this->https || $request->isSsl() ? 'https://' : 'http://';
+ }
+
+ return $scheme . $domain;
+ }
+
+ /**
+ * 解析URL后缀
+ * @access protected
+ * @param string|bool $suffix 后缀
+ * @return string
+ */
+ protected function parseSuffix($suffix): string
+ {
+ if ($suffix) {
+ $suffix = true === $suffix ? $this->route->config('url_html_suffix') : $suffix;
+
+ if (is_string($suffix) && $pos = strpos($suffix, '|')) {
+ $suffix = substr($suffix, 0, $pos);
+ }
+ }
+
+ return (empty($suffix) || 0 === strpos($suffix, '.')) ? (string) $suffix : '.' . $suffix;
+ }
+
+ /**
+ * 直接解析URL地址
+ * @access protected
+ * @param string $url URL
+ * @param string|bool $domain Domain
+ * @return string
+ */
+ protected function parseUrl(string $url, &$domain): string
+ {
+ $request = $this->app->request;
+
+ if (0 === strpos($url, '/')) {
+ // 直接作为路由地址解析
+ $url = substr($url, 1);
+ } elseif (false !== strpos($url, '\\')) {
+ // 解析到类
+ $url = ltrim(str_replace('\\', '/', $url), '/');
+ } elseif (0 === strpos($url, '@')) {
+ // 解析到控制器
+ $url = substr($url, 1);
+ } elseif ('' === $url) {
+ $url = $request->controller() . '/' . $request->action();
+ } else {
+ $controller = $request->controller();
+
+ $path = explode('/', $url);
+ $action = array_pop($path);
+ $controller = empty($path) ? $controller : array_pop($path);
+
+ $url = $controller . '/' . $action;
+ }
+
+ return $url;
+ }
+
+ /**
+ * 分析路由规则中的变量
+ * @access protected
+ * @param string $rule 路由规则
+ * @return array
+ */
+ protected function parseVar(string $rule): array
+ {
+ // 提取路由规则中的变量
+ $var = [];
+
+ if (preg_match_all('/<\w+\??>/', $rule, $matches)) {
+ foreach ($matches[0] as $name) {
+ $optional = false;
+
+ if (strpos($name, '?')) {
+ $name = substr($name, 1, -2);
+ $optional = true;
+ } else {
+ $name = substr($name, 1, -1);
+ }
+
+ $var[$name] = $optional ? 2 : 1;
+ }
+ }
+
+ return $var;
+ }
+
+ /**
+ * 匹配路由地址
+ * @access protected
+ * @param array $rule 路由规则
+ * @param array $vars 路由变量
+ * @param mixed $allowDomain 允许域名
+ * @return array
+ */
+ protected function getRuleUrl(array $rule, array &$vars = [], $allowDomain = ''): array
+ {
+ $request = $this->app->request;
+ if (is_string($allowDomain) && false === strpos($allowDomain, '.')) {
+ $allowDomain .= '.' . $request->rootDomain();
+ }
+ $port = $request->port();
+
+ foreach ($rule as $item) {
+ $url = $item['rule'];
+ $pattern = $this->parseVar($url);
+ $domain = $item['domain'];
+ $suffix = $item['suffix'];
+
+ if ('-' == $domain) {
+ $domain = is_string($allowDomain) ? $allowDomain : $request->host(true);
+ }
+
+ if (is_string($allowDomain) && $domain != $allowDomain) {
+ continue;
+ }
+
+ if ($port && !in_array($port, [80, 443])) {
+ $domain .= ':' . $port;
+ }
+
+ if (empty($pattern)) {
+ return [rtrim($url, '?-'), $domain, $suffix];
+ }
+
+ $type = $this->route->config('url_common_param');
+ $keys = [];
+
+ foreach ($pattern as $key => $val) {
+ if (isset($vars[$key])) {
+ $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? (string) $vars[$key] : urlencode((string) $vars[$key]), $url);
+ $keys[] = $key;
+ $url = str_replace(['/?', '-?'], ['/', '-'], $url);
+ $result = [rtrim($url, '?-'), $domain, $suffix];
+ } elseif (2 == $val) {
+ $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url);
+ $url = str_replace(['/?', '-?'], ['/', '-'], $url);
+ $result = [rtrim($url, '?-'), $domain, $suffix];
+ } else {
+ $result = null;
+ $keys = [];
+ break;
+ }
+ }
+
+ $vars = array_diff_key($vars, array_flip($keys));
+
+ if (isset($result)) {
+ return $result;
+ }
+ }
+
+ return [];
+ }
+
+ /**
+ * 生成URL地址
+ * @access public
+ * @return string
+ */
+ public function build()
+ {
+ // 解析URL
+ $url = $this->url;
+ $suffix = $this->suffix;
+ $domain = $this->domain;
+ $request = $this->app->request;
+ $vars = $this->vars;
+
+ if (0 === strpos($url, '[') && $pos = strpos($url, ']')) {
+ // [name] 表示使用路由命名标识生成URL
+ $name = substr($url, 1, $pos - 1);
+ $url = 'name' . substr($url, $pos + 1);
+ }
+
+ if (false === strpos($url, '://') && 0 !== strpos($url, '/')) {
+ $info = parse_url($url);
+ $url = !empty($info['path']) ? $info['path'] : '';
+
+ if (isset($info['fragment'])) {
+ // 解析锚点
+ $anchor = $info['fragment'];
+
+ if (false !== strpos($anchor, '?')) {
+ // 解析参数
+ [$anchor, $info['query']] = explode('?', $anchor, 2);
+ }
+
+ if (false !== strpos($anchor, '@')) {
+ // 解析域名
+ [$anchor, $domain] = explode('@', $anchor, 2);
+ }
+ } elseif (strpos($url, '@') && false === strpos($url, '\\')) {
+ // 解析域名
+ [$url, $domain] = explode('@', $url, 2);
+ }
+ }
+
+ if ($url) {
+ $checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '');
+ $checkDomain = $domain && is_string($domain) ? $domain : null;
+
+ $rule = $this->route->getName($checkName, $checkDomain);
+
+ if (empty($rule) && isset($info['query'])) {
+ $rule = $this->route->getName($url, $checkDomain);
+ // 解析地址里面参数 合并到vars
+ parse_str($info['query'], $params);
+ $vars = array_merge($params, $vars);
+ unset($info['query']);
+ }
+ }
+
+ if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) {
+ // 匹配路由命名标识
+ $url = $match[0];
+
+ if ($domain && !empty($match[1])) {
+ $domain = $match[1];
+ }
+
+ if (!is_null($match[2])) {
+ $suffix = $match[2];
+ }
+ } elseif (!empty($rule) && isset($name)) {
+ throw new \InvalidArgumentException('route name not exists:' . $name);
+ } else {
+ // 检测URL绑定
+ $bind = $this->route->getDomainBind($domain && is_string($domain) ? $domain : null);
+
+ if ($bind && 0 === strpos($url, $bind)) {
+ $url = substr($url, strlen($bind) + 1);
+ } else {
+ $binds = $this->route->getBind();
+
+ foreach ($binds as $key => $val) {
+ if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) {
+ $url = substr($url, strlen($val) + 1);
+ $domain = $key;
+ break;
+ }
+ }
+ }
+
+ // 路由标识不存在 直接解析
+ $url = $this->parseUrl($url, $domain);
+
+ if (isset($info['query'])) {
+ // 解析地址里面参数 合并到vars
+ parse_str($info['query'], $params);
+ $vars = array_merge($params, $vars);
+ }
+ }
+
+ // 还原URL分隔符
+ $depr = $this->route->config('pathinfo_depr');
+ $url = str_replace('/', $depr, $url);
+
+ $file = $request->baseFile();
+ if ($file && 0 !== strpos($request->url(), $file)) {
+ $file = str_replace('\\', '/', dirname($file));
+ }
+
+ $url = rtrim($file, '/') . '/' . $url;
+
+ // URL后缀
+ if ('/' == substr($url, -1) || '' == $url) {
+ $suffix = '';
+ } else {
+ $suffix = $this->parseSuffix($suffix);
+ }
+
+ // 锚点
+ $anchor = !empty($anchor) ? '#' . $anchor : '';
+
+ // 参数组装
+ if (!empty($vars)) {
+ // 添加参数
+ if ($this->route->config('url_common_param')) {
+ $vars = http_build_query($vars);
+ $url .= $suffix . ($vars ? '?' . $vars : '') . $anchor;
+ } else {
+ foreach ($vars as $var => $val) {
+ $val = (string) $val;
+ if ('' !== $val) {
+ $url .= $depr . $var . $depr . urlencode($val);
+ }
+ }
+
+ $url .= $suffix . $anchor;
+ }
+ } else {
+ $url .= $suffix . $anchor;
+ }
+
+ // 检测域名
+ $domain = $this->parseDomain($url, $domain);
+
+ // URL组装
+ return $domain . rtrim($this->root, '/') . '/' . ltrim($url, '/');
+ }
+
+ public function __toString()
+ {
+ return $this->build();
+ }
+
+ public function __debugInfo()
+ {
+ return [
+ 'url' => $this->url,
+ 'vars' => $this->vars,
+ 'suffix' => $this->suffix,
+ 'domain' => $this->domain,
+ ];
+ }
+}
diff --git a/vendor/topthink/framework/src/think/route/dispatch/Callback.php b/vendor/topthink/framework/src/think/route/dispatch/Callback.php
new file mode 100755
index 0000000..2044ef8
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/dispatch/Callback.php
@@ -0,0 +1,30 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route\dispatch;
+
+use think\route\Dispatch;
+
+/**
+ * Callback Dispatcher
+ */
+class Callback extends Dispatch
+{
+ public function exec()
+ {
+ // 执行回调方法
+ $vars = array_merge($this->request->param(), $this->param);
+
+ return $this->app->invoke($this->dispatch, $vars);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/route/dispatch/Controller.php b/vendor/topthink/framework/src/think/route/dispatch/Controller.php
new file mode 100755
index 0000000..f18512c
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/dispatch/Controller.php
@@ -0,0 +1,194 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route\dispatch;
+
+use ReflectionClass;
+use ReflectionException;
+use ReflectionMethod;
+use think\App;
+use think\exception\ClassNotFoundException;
+use think\exception\HttpException;
+use think\helper\Str;
+use think\route\Dispatch;
+
+/**
+ * Controller Dispatcher
+ */
+class Controller extends Dispatch
+{
+ /**
+ * 控制器名
+ * @var string
+ */
+ protected $controller;
+
+ /**
+ * 操作名
+ * @var string
+ */
+ protected $actionName;
+
+ public function init(App $app)
+ {
+ parent::init($app);
+
+ $result = $this->dispatch;
+
+ if (is_string($result)) {
+ $result = explode('/', $result);
+ }
+
+ // 获取控制器名
+ $controller = strip_tags($result[0] ?: $this->rule->config('default_controller'));
+
+ if (strpos($controller, '.')) {
+ $pos = strrpos($controller, '.');
+ $this->controller = substr($controller, 0, $pos) . '.' . Str::studly(substr($controller, $pos + 1));
+ } else {
+ $this->controller = Str::studly($controller);
+ }
+
+ // 获取操作名
+ $this->actionName = strip_tags($result[1] ?: $this->rule->config('default_action'));
+
+ // 设置当前请求的控制器、操作
+ $this->request
+ ->setController($this->controller)
+ ->setAction($this->actionName);
+ }
+
+ public function exec()
+ {
+ try {
+ // 实例化控制器
+ $instance = $this->controller($this->controller);
+ } catch (ClassNotFoundException $e) {
+ throw new HttpException(404, 'controller not exists:' . $e->getClass());
+ }
+
+ // 注册控制器中间件
+ $this->registerControllerMiddleware($instance);
+
+ return $this->app->middleware->pipeline('controller')
+ ->send($this->request)
+ ->then(function () use ($instance) {
+ // 获取当前操作名
+ $suffix = $this->rule->config('action_suffix');
+ $action = $this->actionName . $suffix;
+
+ if (is_callable([$instance, $action])) {
+ $vars = $this->request->param();
+ try {
+ $reflect = new ReflectionMethod($instance, $action);
+ // 严格获取当前操作方法名
+ $actionName = $reflect->getName();
+ if ($suffix) {
+ $actionName = substr($actionName, 0, -strlen($suffix));
+ }
+
+ $this->request->setAction($actionName);
+ } catch (ReflectionException $e) {
+ $reflect = new ReflectionMethod($instance, '__call');
+ $vars = [$action, $vars];
+ $this->request->setAction($action);
+ }
+ } else {
+ // 操作不存在
+ throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
+ }
+
+ $data = $this->app->invokeReflectMethod($instance, $reflect, $vars);
+
+ return $this->autoResponse($data);
+ });
+ }
+
+ protected function parseActions($actions)
+ {
+ return array_map(function ($item) {
+ return strtolower($item);
+ }, is_string($actions) ? explode(",", $actions) : $actions);
+ }
+
+ /**
+ * 使用反射机制注册控制器中间件
+ * @access public
+ * @param object $controller 控制器实例
+ * @return void
+ */
+ protected function registerControllerMiddleware($controller): void
+ {
+ $class = new ReflectionClass($controller);
+
+ if ($class->hasProperty('middleware')) {
+ $reflectionProperty = $class->getProperty('middleware');
+ $reflectionProperty->setAccessible(true);
+
+ $middlewares = $reflectionProperty->getValue($controller);
+ $action = $this->request->action(true);
+
+ foreach ($middlewares as $key => $val) {
+ if (!is_int($key)) {
+ $middleware = $key;
+ $options = $val;
+ } elseif (isset($val['middleware'])) {
+ $middleware = $val['middleware'];
+ $options = $val['options'] ?? [];
+ } else {
+ $middleware = $val;
+ $options = [];
+ }
+
+ if (isset($options['only']) && !in_array($action, $this->parseActions($options['only']))) {
+ continue;
+ } elseif (isset($options['except']) && in_array($action, $this->parseActions($options['except']))) {
+ continue;
+ }
+
+ if (is_string($middleware) && strpos($middleware, ':')) {
+ $middleware = explode(':', $middleware);
+ if (count($middleware) > 1) {
+ $middleware = [$middleware[0], array_slice($middleware, 1)];
+ }
+ }
+
+ $this->app->middleware->controller($middleware);
+ }
+ }
+ }
+
+ /**
+ * 实例化访问控制器
+ * @access public
+ * @param string $name 资源地址
+ * @return object
+ * @throws ClassNotFoundException
+ */
+ public function controller(string $name)
+ {
+ $suffix = $this->rule->config('controller_suffix') ? 'Controller' : '';
+
+ $controllerLayer = $this->rule->config('controller_layer') ?: 'controller';
+ $emptyController = $this->rule->config('empty_controller') ?: 'Error';
+
+ $class = $this->app->parseClass($controllerLayer, $name . $suffix);
+
+ if (class_exists($class)) {
+ return $this->app->make($class, [], true);
+ } elseif ($emptyController && class_exists($emptyClass = $this->app->parseClass($controllerLayer, $emptyController . $suffix))) {
+ return $this->app->make($emptyClass, [], true);
+ }
+
+ throw new ClassNotFoundException('class not exists:' . $class, $class);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/route/dispatch/Url.php b/vendor/topthink/framework/src/think/route/dispatch/Url.php
new file mode 100755
index 0000000..147f5cb
--- /dev/null
+++ b/vendor/topthink/framework/src/think/route/dispatch/Url.php
@@ -0,0 +1,118 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\route\dispatch;
+
+use think\exception\HttpException;
+use think\helper\Str;
+use think\Request;
+use think\route\Rule;
+
+/**
+ * Url Dispatcher
+ */
+class Url extends Controller
+{
+
+ public function __construct(Request $request, Rule $rule, $dispatch)
+ {
+ $this->request = $request;
+ $this->rule = $rule;
+ // 解析默认的URL规则
+ $dispatch = $this->parseUrl($dispatch);
+
+ parent::__construct($request, $rule, $dispatch, $this->param);
+ }
+
+ /**
+ * 解析URL地址
+ * @access protected
+ * @param string $url URL
+ * @return array
+ */
+ protected function parseUrl(string $url): array
+ {
+ $depr = $this->rule->config('pathinfo_depr');
+ $bind = $this->rule->getRouter()->getDomainBind();
+
+ if ($bind && preg_match('/^[a-z]/is', $bind)) {
+ $bind = str_replace('/', $depr, $bind);
+ // 如果有域名绑定
+ $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr);
+ }
+
+ $path = $this->rule->parseUrlPath($url);
+ if (empty($path)) {
+ return [null, null];
+ }
+
+ // 解析控制器
+ $controller = !empty($path) ? array_shift($path) : null;
+
+ if ($controller && !preg_match('/^[A-Za-z0-9][\w|\.]*$/', $controller)) {
+ throw new HttpException(404, 'controller not exists:' . $controller);
+ }
+
+ // 解析操作
+ $action = !empty($path) ? array_shift($path) : null;
+ $var = [];
+
+ // 解析额外参数
+ if ($path) {
+ preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) {
+ $var[$match[1]] = strip_tags($match[2]);
+ }, implode('|', $path));
+ }
+
+ $panDomain = $this->request->panDomain();
+ if ($panDomain && $key = array_search('*', $var)) {
+ // 泛域名赋值
+ $var[$key] = $panDomain;
+ }
+
+ // 设置当前请求的参数
+ $this->param = $var;
+
+ // 封装路由
+ $route = [$controller, $action];
+
+ if ($this->hasDefinedRoute($route)) {
+ throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url));
+ }
+
+ return $route;
+ }
+
+ /**
+ * 检查URL是否已经定义过路由
+ * @access protected
+ * @param array $route 路由信息
+ * @return bool
+ */
+ protected function hasDefinedRoute(array $route): bool
+ {
+ [$controller, $action] = $route;
+
+ // 检查地址是否被定义过路由
+ $name = strtolower(Str::studly($controller) . '/' . $action);
+
+ $host = $this->request->host(true);
+ $method = $this->request->method();
+
+ if ($this->rule->getRouter()->getName($name, $host, $method)) {
+ return true;
+ }
+
+ return false;
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/service/ModelService.php b/vendor/topthink/framework/src/think/service/ModelService.php
new file mode 100755
index 0000000..b517c4e
--- /dev/null
+++ b/vendor/topthink/framework/src/think/service/ModelService.php
@@ -0,0 +1,53 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\service;
+
+use think\Model;
+use think\Service;
+
+/**
+ * 模型服务类
+ */
+class ModelService extends Service
+{
+ public function boot()
+ {
+ Model::setDb($this->app->db);
+ Model::setEvent($this->app->event);
+ Model::setInvoker([$this->app, 'invoke']);
+ Model::maker(function (Model $model) {
+ $config = $this->app->config;
+
+ $isAutoWriteTimestamp = $model->getAutoWriteTimestamp();
+
+ if (is_null($isAutoWriteTimestamp)) {
+ // 自动写入时间戳
+ $model->isAutoWriteTimestamp($config->get('database.auto_timestamp', 'timestamp'));
+ }
+
+ $dateFormat = $model->getDateFormat();
+
+ if (is_null($dateFormat)) {
+ // 设置时间戳格式
+ $model->setDateFormat($config->get('database.datetime_format', 'Y-m-d H:i:s'));
+ }
+
+ $timeField = $config->get('database.datetime_field');
+ if (!empty($timeField)) {
+ [$createTime, $updateTime] = explode(',', $timeField);
+ $model->setTimeField($createTime, $updateTime);
+ }
+
+ });
+ }
+}
diff --git a/vendor/topthink/framework/src/think/service/PaginatorService.php b/vendor/topthink/framework/src/think/service/PaginatorService.php
new file mode 100755
index 0000000..a01977d
--- /dev/null
+++ b/vendor/topthink/framework/src/think/service/PaginatorService.php
@@ -0,0 +1,52 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\service;
+
+use think\Paginator;
+use think\paginator\driver\Bootstrap;
+use think\Service;
+
+/**
+ * 分页服务类
+ */
+class PaginatorService extends Service
+{
+ public function register()
+ {
+ if (!$this->app->bound(Paginator::class)) {
+ $this->app->bind(Paginator::class, Bootstrap::class);
+ }
+ }
+
+ public function boot()
+ {
+ Paginator::maker(function (...$args) {
+ return $this->app->make(Paginator::class, $args, true);
+ });
+
+ Paginator::currentPathResolver(function () {
+ return $this->app->request->baseUrl();
+ });
+
+ Paginator::currentPageResolver(function ($varPage = 'page') {
+
+ $page = $this->app->request->param($varPage);
+
+ if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) {
+ return (int) $page;
+ }
+
+ return 1;
+ });
+ }
+}
diff --git a/vendor/topthink/framework/src/think/service/ValidateService.php b/vendor/topthink/framework/src/think/service/ValidateService.php
new file mode 100755
index 0000000..94d7638
--- /dev/null
+++ b/vendor/topthink/framework/src/think/service/ValidateService.php
@@ -0,0 +1,31 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\service;
+
+use think\Service;
+use think\Validate;
+
+/**
+ * 验证服务类
+ */
+class ValidateService extends Service
+{
+ public function boot()
+ {
+ Validate::maker(function (Validate $validate) {
+ $validate->setLang($this->app->lang);
+ $validate->setDb($this->app->db);
+ $validate->setRequest($this->app->request);
+ });
+ }
+}
diff --git a/vendor/topthink/framework/src/think/session/Store.php b/vendor/topthink/framework/src/think/session/Store.php
new file mode 100755
index 0000000..49e1ba9
--- /dev/null
+++ b/vendor/topthink/framework/src/think/session/Store.php
@@ -0,0 +1,340 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\session;
+
+use think\contract\SessionHandlerInterface;
+use think\helper\Arr;
+
+class Store
+{
+
+ /**
+ * Session数据
+ * @var array
+ */
+ protected $data = [];
+
+ /**
+ * 是否初始化
+ * @var bool
+ */
+ protected $init = null;
+
+ /**
+ * 记录Session name
+ * @var string
+ */
+ protected $name = 'PHPSESSID';
+
+ /**
+ * 记录Session Id
+ * @var string
+ */
+ protected $id;
+
+ /**
+ * @var SessionHandlerInterface
+ */
+ protected $handler;
+
+ /** @var array */
+ protected $serialize = [];
+
+ public function __construct($name, SessionHandlerInterface $handler, array $serialize = null)
+ {
+ $this->name = $name;
+ $this->handler = $handler;
+
+ if (!empty($serialize)) {
+ $this->serialize = $serialize;
+ }
+
+ $this->setId();
+ }
+
+ /**
+ * 设置数据
+ * @access public
+ * @param array $data
+ * @return void
+ */
+ public function setData(array $data): void
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * session初始化
+ * @access public
+ * @return void
+ */
+ public function init(): void
+ {
+ // 读取缓存数据
+ $data = $this->handler->read($this->getId());
+
+ if (!empty($data)) {
+ $this->data = array_merge($this->data, $this->unserialize($data));
+ }
+
+ $this->init = true;
+ }
+
+ /**
+ * 设置SessionName
+ * @access public
+ * @param string $name session_name
+ * @return void
+ */
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * 获取sessionName
+ * @access public
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * session_id设置
+ * @access public
+ * @param string $id session_id
+ * @return void
+ */
+ public function setId($id = null): void
+ {
+ $this->id = is_string($id) && strlen($id) === 32 && ctype_alnum($id) ? $id : md5(microtime(true) . session_create_id());
+ }
+
+ /**
+ * 获取session_id
+ * @access public
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * 获取所有数据
+ * @return array
+ */
+ public function all(): array
+ {
+ return $this->data;
+ }
+
+ /**
+ * session设置
+ * @access public
+ * @param string $name session名称
+ * @param mixed $value session值
+ * @return void
+ */
+ public function set(string $name, $value): void
+ {
+ Arr::set($this->data, $name, $value);
+ }
+
+ /**
+ * session获取
+ * @access public
+ * @param string $name session名称
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function get(string $name, $default = null)
+ {
+ return Arr::get($this->data, $name, $default);
+ }
+
+ /**
+ * session获取并删除
+ * @access public
+ * @param string $name session名称
+ * @return mixed
+ */
+ public function pull(string $name)
+ {
+ return Arr::pull($this->data, $name);
+ }
+
+ /**
+ * 添加数据到一个session数组
+ * @access public
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function push(string $key, $value): void
+ {
+ $array = $this->get($key, []);
+
+ $array[] = $value;
+
+ $this->set($key, $array);
+ }
+
+ /**
+ * 判断session数据
+ * @access public
+ * @param string $name session名称
+ * @return bool
+ */
+ public function has(string $name): bool
+ {
+ return Arr::has($this->data, $name);
+ }
+
+ /**
+ * 删除session数据
+ * @access public
+ * @param string $name session名称
+ * @return void
+ */
+ public function delete(string $name): void
+ {
+ Arr::forget($this->data, $name);
+ }
+
+ /**
+ * 清空session数据
+ * @access public
+ * @return void
+ */
+ public function clear(): void
+ {
+ $this->data = [];
+ }
+
+ /**
+ * 销毁session
+ */
+ public function destroy(): void
+ {
+ $this->clear();
+
+ $this->regenerate(true);
+ }
+
+ /**
+ * 重新生成session id
+ * @param bool $destroy
+ */
+ public function regenerate(bool $destroy = false): void
+ {
+ if ($destroy) {
+ $this->handler->delete($this->getId());
+ }
+
+ $this->setId();
+ }
+
+ /**
+ * 保存session数据
+ * @access public
+ * @return void
+ */
+ public function save(): void
+ {
+ $this->clearFlashData();
+
+ $sessionId = $this->getId();
+
+ if (!empty($this->data)) {
+ $data = $this->serialize($this->data);
+
+ $this->handler->write($sessionId, $data);
+ } else {
+ $this->handler->delete($sessionId);
+ }
+
+ $this->init = false;
+ }
+
+ /**
+ * session设置 下一次请求有效
+ * @access public
+ * @param string $name session名称
+ * @param mixed $value session值
+ * @return void
+ */
+ public function flash(string $name, $value): void
+ {
+ $this->set($name, $value);
+ $this->push('__flash__.__next__', $name);
+ $this->set('__flash__.__current__', Arr::except($this->get('__flash__.__current__', []), $name));
+ }
+
+ /**
+ * 将本次闪存数据推迟到下次请求
+ *
+ * @return void
+ */
+ public function reflash(): void
+ {
+ $keys = $this->get('__flash__.__current__', []);
+ $values = array_unique(array_merge($this->get('__flash__.__next__', []), $keys));
+ $this->set('__flash__.__next__', $values);
+ $this->set('__flash__.__current__', []);
+ }
+
+ /**
+ * 清空当前请求的session数据
+ * @access public
+ * @return void
+ */
+ public function clearFlashData(): void
+ {
+ Arr::forget($this->data, $this->get('__flash__.__current__', []));
+ if (!empty($next = $this->get('__flash__.__next__', []))) {
+ $this->set('__flash__.__current__', $next);
+ } else {
+ $this->delete('__flash__.__current__');
+ }
+ $this->delete('__flash__.__next__');
+ }
+
+ /**
+ * 序列化数据
+ * @access protected
+ * @param mixed $data
+ * @return string
+ */
+ protected function serialize($data): string
+ {
+ $serialize = $this->serialize[0] ?? 'serialize';
+
+ return $serialize($data);
+ }
+
+ /**
+ * 反序列化数据
+ * @access protected
+ * @param string $data
+ * @return array
+ */
+ protected function unserialize(string $data): array
+ {
+ $unserialize = $this->serialize[1] ?? 'unserialize';
+
+ return (array) $unserialize($data);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/session/driver/Cache.php b/vendor/topthink/framework/src/think/session/driver/Cache.php
new file mode 100755
index 0000000..4fabc79
--- /dev/null
+++ b/vendor/topthink/framework/src/think/session/driver/Cache.php
@@ -0,0 +1,50 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\session\driver;
+
+use Psr\SimpleCache\CacheInterface;
+use think\contract\SessionHandlerInterface;
+use think\helper\Arr;
+
+class Cache implements SessionHandlerInterface
+{
+
+ /** @var CacheInterface */
+ protected $handler;
+
+ /** @var integer */
+ protected $expire;
+
+ /** @var string */
+ protected $prefix;
+
+ public function __construct(\think\Cache $cache, array $config = [])
+ {
+ $this->handler = $cache->store(Arr::get($config, 'store'));
+ $this->expire = Arr::get($config, 'expire', 1440);
+ $this->prefix = Arr::get($config, 'prefix', '');
+ }
+
+ public function read(string $sessionId): string
+ {
+ return (string) $this->handler->get($this->prefix . $sessionId);
+ }
+
+ public function delete(string $sessionId): bool
+ {
+ return $this->handler->delete($this->prefix . $sessionId);
+ }
+
+ public function write(string $sessionId, string $data): bool
+ {
+ return $this->handler->set($this->prefix . $sessionId, $data, $this->expire);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/session/driver/File.php b/vendor/topthink/framework/src/think/session/driver/File.php
new file mode 100755
index 0000000..788f323
--- /dev/null
+++ b/vendor/topthink/framework/src/think/session/driver/File.php
@@ -0,0 +1,249 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\session\driver;
+
+use Closure;
+use Exception;
+use FilesystemIterator;
+use Generator;
+use SplFileInfo;
+use think\App;
+use think\contract\SessionHandlerInterface;
+
+/**
+ * Session 文件驱动
+ */
+class File implements SessionHandlerInterface
+{
+ protected $config = [
+ 'path' => '',
+ 'expire' => 1440,
+ 'prefix' => '',
+ 'data_compress' => false,
+ 'gc_probability' => 1,
+ 'gc_divisor' => 100,
+ ];
+
+ public function __construct(App $app, array $config = [])
+ {
+ $this->config = array_merge($this->config, $config);
+
+ if (empty($this->config['path'])) {
+ $this->config['path'] = $app->getRuntimePath() . 'session' . DIRECTORY_SEPARATOR;
+ } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) {
+ $this->config['path'] .= DIRECTORY_SEPARATOR;
+ }
+
+ $this->init();
+ }
+
+ /**
+ * 打开Session
+ * @access protected
+ * @throws Exception
+ */
+ protected function init(): void
+ {
+ try {
+ !is_dir($this->config['path']) && mkdir($this->config['path'], 0755, true);
+ } catch (\Exception $e) {
+ // 写入失败
+ }
+
+ // 垃圾回收
+ if (random_int(1, $this->config['gc_divisor']) <= $this->config['gc_probability']) {
+ $this->gc();
+ }
+ }
+
+ /**
+ * Session 垃圾回收
+ * @access public
+ * @return void
+ */
+ public function gc(): void
+ {
+ $lifetime = $this->config['expire'];
+ $now = time();
+
+ $files = $this->findFiles($this->config['path'], function (SplFileInfo $item) use ($lifetime, $now) {
+ return $now - $lifetime > $item->getMTime();
+ });
+
+ foreach ($files as $file) {
+ $this->unlink($file->getPathname());
+ }
+ }
+
+ /**
+ * 查找文件
+ * @param string $root
+ * @param Closure $filter
+ * @return Generator
+ */
+ protected function findFiles(string $root, Closure $filter)
+ {
+ $items = new FilesystemIterator($root);
+
+ /** @var SplFileInfo $item */
+ foreach ($items as $item) {
+ if ($item->isDir() && !$item->isLink()) {
+ yield from $this->findFiles($item->getPathname(), $filter);
+ } else {
+ if ($filter($item)) {
+ yield $item;
+ }
+ }
+ }
+ }
+
+ /**
+ * 取得变量的存储文件名
+ * @access protected
+ * @param string $name 缓存变量名
+ * @param bool $auto 是否自动创建目录
+ * @return string
+ */
+ protected function getFileName(string $name, bool $auto = false): string
+ {
+ if ($this->config['prefix']) {
+ // 使用子目录
+ $name = $this->config['prefix'] . DIRECTORY_SEPARATOR . 'sess_' . $name;
+ } else {
+ $name = 'sess_' . $name;
+ }
+
+ $filename = $this->config['path'] . $name;
+ $dir = dirname($filename);
+
+ if ($auto && !is_dir($dir)) {
+ try {
+ mkdir($dir, 0755, true);
+ } catch (\Exception $e) {
+ // 创建失败
+ }
+ }
+
+ return $filename;
+ }
+
+ /**
+ * 读取Session
+ * @access public
+ * @param string $sessID
+ * @return string
+ */
+ public function read(string $sessID): string
+ {
+ $filename = $this->getFileName($sessID);
+
+ if (is_file($filename) && filemtime($filename) >= time() - $this->config['expire']) {
+ $content = $this->readFile($filename);
+
+ if ($this->config['data_compress'] && function_exists('gzcompress')) {
+ //启用数据压缩
+ $content = (string) gzuncompress($content);
+ }
+
+ return $content;
+ }
+
+ return '';
+ }
+
+ /**
+ * 写文件(加锁)
+ * @param $path
+ * @param $content
+ * @return bool
+ */
+ protected function writeFile($path, $content): bool
+ {
+ return (bool) file_put_contents($path, $content, LOCK_EX);
+ }
+
+ /**
+ * 读取文件内容(加锁)
+ * @param $path
+ * @return string
+ */
+ protected function readFile($path): string
+ {
+ $contents = '';
+
+ $handle = fopen($path, 'rb');
+
+ if ($handle) {
+ try {
+ if (flock($handle, LOCK_SH)) {
+ clearstatcache(true, $path);
+
+ $contents = fread($handle, filesize($path) ?: 1);
+
+ flock($handle, LOCK_UN);
+ }
+ } finally {
+ fclose($handle);
+ }
+ }
+
+ return $contents;
+ }
+
+ /**
+ * 写入Session
+ * @access public
+ * @param string $sessID
+ * @param string $sessData
+ * @return bool
+ */
+ public function write(string $sessID, string $sessData): bool
+ {
+ $filename = $this->getFileName($sessID, true);
+ $data = $sessData;
+
+ if ($this->config['data_compress'] && function_exists('gzcompress')) {
+ //数据压缩
+ $data = gzcompress($data, 3);
+ }
+
+ return $this->writeFile($filename, $data);
+ }
+
+ /**
+ * 删除Session
+ * @access public
+ * @param string $sessID
+ * @return bool
+ */
+ public function delete(string $sessID): bool
+ {
+ try {
+ return $this->unlink($this->getFileName($sessID));
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * 判断文件是否存在后,删除
+ * @access private
+ * @param string $file
+ * @return bool
+ */
+ private function unlink(string $file): bool
+ {
+ return is_file($file) && unlink($file);
+ }
+
+}
diff --git a/vendor/topthink/framework/src/think/validate/ValidateRule.php b/vendor/topthink/framework/src/think/validate/ValidateRule.php
new file mode 100755
index 0000000..18e5ea3
--- /dev/null
+++ b/vendor/topthink/framework/src/think/validate/ValidateRule.php
@@ -0,0 +1,172 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\validate;
+
+/**
+ * Class ValidateRule
+ * @package think\validate
+ * @method static ValidateRule confirm(mixed $rule, string $msg = '') 验证是否和某个字段的值一致
+ * @method static ValidateRule different(mixed $rule, string $msg = '') 验证是否和某个字段的值是否不同
+ * @method static ValidateRule egt(mixed $rule, string $msg = '') 验证是否大于等于某个值
+ * @method static ValidateRule gt(mixed $rule, string $msg = '') 验证是否大于某个值
+ * @method static ValidateRule elt(mixed $rule, string $msg = '') 验证是否小于等于某个值
+ * @method static ValidateRule lt(mixed $rule, string $msg = '') 验证是否小于某个值
+ * @method static ValidateRule eg(mixed $rule, string $msg = '') 验证是否等于某个值
+ * @method static ValidateRule in(mixed $rule, string $msg = '') 验证是否在范围内
+ * @method static ValidateRule notIn(mixed $rule, string $msg = '') 验证是否不在某个范围
+ * @method static ValidateRule between(mixed $rule, string $msg = '') 验证是否在某个区间
+ * @method static ValidateRule notBetween(mixed $rule, string $msg = '') 验证是否不在某个区间
+ * @method static ValidateRule length(mixed $rule, string $msg = '') 验证数据长度
+ * @method static ValidateRule max(mixed $rule, string $msg = '') 验证数据最大长度
+ * @method static ValidateRule min(mixed $rule, string $msg = '') 验证数据最小长度
+ * @method static ValidateRule after(mixed $rule, string $msg = '') 验证日期
+ * @method static ValidateRule before(mixed $rule, string $msg = '') 验证日期
+ * @method static ValidateRule expire(mixed $rule, string $msg = '') 验证有效期
+ * @method static ValidateRule allowIp(mixed $rule, string $msg = '') 验证IP许可
+ * @method static ValidateRule denyIp(mixed $rule, string $msg = '') 验证IP禁用
+ * @method static ValidateRule regex(mixed $rule, string $msg = '') 使用正则验证数据
+ * @method static ValidateRule token(mixed $rule='__token__', string $msg = '') 验证表单令牌
+ * @method static ValidateRule is(mixed $rule, string $msg = '') 验证字段值是否为有效格式
+ * @method static ValidateRule isRequire(mixed $rule = null, string $msg = '') 验证字段必须
+ * @method static ValidateRule isNumber(mixed $rule = null, string $msg = '') 验证字段值是否为数字
+ * @method static ValidateRule isArray(mixed $rule = null, string $msg = '') 验证字段值是否为数组
+ * @method static ValidateRule isInteger(mixed $rule = null, string $msg = '') 验证字段值是否为整形
+ * @method static ValidateRule isFloat(mixed $rule = null, string $msg = '') 验证字段值是否为浮点数
+ * @method static ValidateRule isMobile(mixed $rule = null, string $msg = '') 验证字段值是否为手机
+ * @method static ValidateRule isIdCard(mixed $rule = null, string $msg = '') 验证字段值是否为身份证号码
+ * @method static ValidateRule isChs(mixed $rule = null, string $msg = '') 验证字段值是否为中文
+ * @method static ValidateRule isChsDash(mixed $rule = null, string $msg = '') 验证字段值是否为中文字母及下划线
+ * @method static ValidateRule isChsAlpha(mixed $rule = null, string $msg = '') 验证字段值是否为中文和字母
+ * @method static ValidateRule isChsAlphaNum(mixed $rule = null, string $msg = '') 验证字段值是否为中文字母和数字
+ * @method static ValidateRule isDate(mixed $rule = null, string $msg = '') 验证字段值是否为有效格式
+ * @method static ValidateRule isBool(mixed $rule = null, string $msg = '') 验证字段值是否为布尔值
+ * @method static ValidateRule isAlpha(mixed $rule = null, string $msg = '') 验证字段值是否为字母
+ * @method static ValidateRule isAlphaDash(mixed $rule = null, string $msg = '') 验证字段值是否为字母和下划线
+ * @method static ValidateRule isAlphaNum(mixed $rule = null, string $msg = '') 验证字段值是否为字母和数字
+ * @method static ValidateRule isAccepted(mixed $rule = null, string $msg = '') 验证字段值是否为yes, on, 或是 1
+ * @method static ValidateRule isEmail(mixed $rule = null, string $msg = '') 验证字段值是否为有效邮箱格式
+ * @method static ValidateRule isUrl(mixed $rule = null, string $msg = '') 验证字段值是否为有效URL地址
+ * @method static ValidateRule activeUrl(mixed $rule, string $msg = '') 验证是否为合格的域名或者IP
+ * @method static ValidateRule ip(mixed $rule, string $msg = '') 验证是否有效IP
+ * @method static ValidateRule fileExt(mixed $rule, string $msg = '') 验证文件后缀
+ * @method static ValidateRule fileMime(mixed $rule, string $msg = '') 验证文件类型
+ * @method static ValidateRule fileSize(mixed $rule, string $msg = '') 验证文件大小
+ * @method static ValidateRule image(mixed $rule, string $msg = '') 验证图像文件
+ * @method static ValidateRule method(mixed $rule, string $msg = '') 验证请求类型
+ * @method static ValidateRule dateFormat(mixed $rule, string $msg = '') 验证时间和日期是否符合指定格式
+ * @method static ValidateRule unique(mixed $rule, string $msg = '') 验证是否唯一
+ * @method static ValidateRule behavior(mixed $rule, string $msg = '') 使用行为类验证
+ * @method static ValidateRule filter(mixed $rule, string $msg = '') 使用filter_var方式验证
+ * @method static ValidateRule requireIf(mixed $rule, string $msg = '') 验证某个字段等于某个值的时候必须
+ * @method static ValidateRule requireCallback(mixed $rule, string $msg = '') 通过回调方法验证某个字段是否必须
+ * @method static ValidateRule requireWith(mixed $rule, string $msg = '') 验证某个字段有值的情况下必须
+ * @method static ValidateRule must(mixed $rule = null, string $msg = '') 必须验证
+ */
+class ValidateRule
+{
+ // 验证字段的名称
+ protected $title;
+
+ // 当前验证规则
+ protected $rule = [];
+
+ // 验证提示信息
+ protected $message = [];
+
+ /**
+ * 添加验证因子
+ * @access protected
+ * @param string $name 验证名称
+ * @param mixed $rule 验证规则
+ * @param string $msg 提示信息
+ * @return $this
+ */
+ protected function addItem(string $name, $rule = null, string $msg = '')
+ {
+ if ($rule || 0 === $rule) {
+ $this->rule[$name] = $rule;
+ } else {
+ $this->rule[] = $name;
+ }
+
+ $this->message[] = $msg;
+
+ return $this;
+ }
+
+ /**
+ * 获取验证规则
+ * @access public
+ * @return array
+ */
+ public function getRule(): array
+ {
+ return $this->rule;
+ }
+
+ /**
+ * 获取验证字段名称
+ * @access public
+ * @return string
+ */
+ public function getTitle(): string
+ {
+ return $this->title ?: '';
+ }
+
+ /**
+ * 获取验证提示
+ * @access public
+ * @return array
+ */
+ public function getMsg(): array
+ {
+ return $this->message;
+ }
+
+ /**
+ * 设置验证字段名称
+ * @access public
+ * @return $this
+ */
+ public function title(string $title)
+ {
+ $this->title = $title;
+
+ return $this;
+ }
+
+ public function __call($method, $args)
+ {
+ if ('is' == strtolower(substr($method, 0, 2))) {
+ $method = substr($method, 2);
+ }
+
+ array_unshift($args, lcfirst($method));
+
+ return call_user_func_array([$this, 'addItem'], $args);
+ }
+
+ public static function __callStatic($method, $args)
+ {
+ $rule = new static();
+
+ if ('is' == strtolower(substr($method, 0, 2))) {
+ $method = substr($method, 2);
+ }
+
+ array_unshift($args, lcfirst($method));
+
+ return call_user_func_array([$rule, 'addItem'], $args);
+ }
+}
diff --git a/vendor/topthink/framework/src/think/view/driver/Php.php b/vendor/topthink/framework/src/think/view/driver/Php.php
new file mode 100755
index 0000000..9e6e54a
--- /dev/null
+++ b/vendor/topthink/framework/src/think/view/driver/Php.php
@@ -0,0 +1,191 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\view\driver;
+
+use RuntimeException;
+use think\App;
+use think\contract\TemplateHandlerInterface;
+use think\helper\Str;
+
+/**
+ * PHP原生模板驱动
+ */
+class Php implements TemplateHandlerInterface
+{
+ protected $template;
+ protected $content;
+ protected $app;
+
+ // 模板引擎参数
+ protected $config = [
+ // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法
+ 'auto_rule' => 1,
+ // 视图目录名
+ 'view_dir_name' => 'view',
+ // 应用模板路径
+ 'view_path' => '',
+ // 模板文件后缀
+ 'view_suffix' => 'php',
+ // 模板文件名分隔符
+ 'view_depr' => DIRECTORY_SEPARATOR,
+ ];
+
+ public function __construct(App $app, array $config = [])
+ {
+ $this->app = $app;
+ $this->config = array_merge($this->config, (array) $config);
+ }
+
+ /**
+ * 检测是否存在模板文件
+ * @access public
+ * @param string $template 模板文件或者模板规则
+ * @return bool
+ */
+ public function exists(string $template): bool
+ {
+ if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
+ // 获取模板文件名
+ $template = $this->parseTemplate($template);
+ }
+
+ return is_file($template);
+ }
+
+ /**
+ * 渲染模板文件
+ * @access public
+ * @param string $template 模板文件
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function fetch(string $template, array $data = []): void
+ {
+ if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
+ // 获取模板文件名
+ $template = $this->parseTemplate($template);
+ }
+
+ // 模板不存在 抛出异常
+ if (!is_file($template)) {
+ throw new RuntimeException('template not exists:' . $template);
+ }
+
+ $this->template = $template;
+
+ extract($data, EXTR_OVERWRITE);
+
+ include $this->template;
+ }
+
+ /**
+ * 渲染模板内容
+ * @access public
+ * @param string $content 模板内容
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function display(string $content, array $data = []): void
+ {
+ $this->content = $content;
+
+ extract($data, EXTR_OVERWRITE);
+ eval('?>' . $this->content);
+ }
+
+ /**
+ * 自动定位模板文件
+ * @access private
+ * @param string $template 模板文件规则
+ * @return string
+ */
+ private function parseTemplate(string $template): string
+ {
+ $request = $this->app->request;
+
+ // 获取视图根目录
+ if (strpos($template, '@')) {
+ // 跨应用调用
+ [$app, $template] = explode('@', $template);
+ }
+
+ if ($this->config['view_path'] && !isset($app)) {
+ $path = $this->config['view_path'];
+ } else {
+ $appName = isset($app) ? $app : $this->app->http->getName();
+ $view = $this->config['view_dir_name'];
+
+ if (is_dir($this->app->getAppPath() . $view)) {
+ $path = isset($app) ? $this->app->getBasePath() . ($appName ? $appName . DIRECTORY_SEPARATOR : '') . $view . DIRECTORY_SEPARATOR : $this->app->getAppPath() . $view . DIRECTORY_SEPARATOR;
+ } else {
+ $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . ($appName ? $appName . DIRECTORY_SEPARATOR : '');
+ }
+ }
+
+ $depr = $this->config['view_depr'];
+
+ if (0 !== strpos($template, '/')) {
+ $template = str_replace(['/', ':'], $depr, $template);
+ $controller = $request->controller();
+ if (strpos($controller, '.')) {
+ $pos = strrpos($controller, '.');
+ $controller = substr($controller, 0, $pos) . '.' . Str::snake(substr($controller, $pos + 1));
+ } else {
+ $controller = Str::snake($controller);
+ }
+
+ if ($controller) {
+ if ('' == $template) {
+ // 如果模板文件名为空 按照默认规则定位
+ if (2 == $this->config['auto_rule']) {
+ $template = $request->action(true);
+ } elseif (3 == $this->config['auto_rule']) {
+ $template = $request->action();
+ } else {
+ $template = Str::snake($request->action());
+ }
+
+ $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template;
+ } elseif (false === strpos($template, $depr)) {
+ $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template;
+ }
+ }
+ } else {
+ $template = str_replace(['/', ':'], $depr, substr($template, 1));
+ }
+
+ return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.');
+ }
+
+ /**
+ * 配置模板引擎
+ * @access private
+ * @param array $config 参数
+ * @return void
+ */
+ public function config(array $config): void
+ {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ /**
+ * 获取模板引擎配置
+ * @access public
+ * @param string $name 参数名
+ * @return mixed
+ */
+ public function getConfig(string $name)
+ {
+ return $this->config[$name] ?? null;
+ }
+}
diff --git a/vendor/topthink/framework/src/tpl/think_exception.tpl b/vendor/topthink/framework/src/tpl/think_exception.tpl
new file mode 100755
index 0000000..6ea9677
--- /dev/null
+++ b/vendor/topthink/framework/src/tpl/think_exception.tpl
@@ -0,0 +1,502 @@
+<?php
+/** @var array $traces */
+if (!function_exists('parse_padding')) {
+ function parse_padding($source)
+ {
+ $length = strlen(strval(count($source['source']) + $source['first']));
+ return 40 + ($length - 1) * 8;
+ }
+}
+
+if (!function_exists('parse_class')) {
+ function parse_class($name)
+ {
+ $names = explode('\\', $name);
+ return '<abbr title="'.$name.'">'.end($names).'</abbr>';
+ }
+}
+
+if (!function_exists('parse_file')) {
+ function parse_file($file, $line)
+ {
+ return '<a class="toggle" title="'."{$file} line {$line}".'">'.basename($file)." line {$line}".'</a>';
+ }
+}
+
+if (!function_exists('parse_args')) {
+ function parse_args($args)
+ {
+ $result = [];
+ foreach ($args as $key => $item) {
+ switch (true) {
+ case is_object($item):
+ $value = sprintf('<em>object</em>(%s)', parse_class(get_class($item)));
+ break;
+ case is_array($item):
+ if (count($item) > 3) {
+ $value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3)));
+ } else {
+ $value = sprintf('[%s]', parse_args($item));
+ }
+ break;
+ case is_string($item):
+ if (strlen($item) > 20) {
+ $value = sprintf(
+ '\'<a class="toggle" title="%s">%s...</a>\'',
+ htmlentities($item),
+ htmlentities(substr($item, 0, 20))
+ );
+ } else {
+ $value = sprintf("'%s'", htmlentities($item));
+ }
+ break;
+ case is_int($item):
+ case is_float($item):
+ $value = $item;
+ break;
+ case is_null($item):
+ $value = '<em>null</em>';
+ break;
+ case is_bool($item):
+ $value = '<em>' . ($item ? 'true' : 'false') . '</em>';
+ break;
+ case is_resource($item):
+ $value = '<em>resource</em>';
+ break;
+ default:
+ $value = htmlentities(str_replace("\n", '', var_export(strval($item), true)));
+ break;
+ }
+
+ $result[] = is_int($key) ? $value : "'{$key}' => {$value}";
+ }
+
+ return implode(', ', $result);
+ }
+}
+if (!function_exists('echo_value')) {
+ function echo_value($val)
+ {
+ if (is_array($val) || is_object($val)) {
+ echo htmlentities(json_encode($val, JSON_PRETTY_PRINT));
+ } elseif (is_bool($val)) {
+ echo $val ? 'true' : 'false';
+ } elseif (is_scalar($val)) {
+ echo htmlentities($val);
+ } else {
+ echo 'Resource';
+ }
+ }
+}
+?>
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>系统发生错误</title>
+ <meta name="robots" content="noindex,nofollow" />
+ <style>
+ /* Base */
+ body {
+ color: #333;
+ font: 16px Verdana, "Helvetica Neue", helvetica, Arial, 'Microsoft YaHei', sans-serif;
+ margin: 0;
+ padding: 0 20px 20px;
+ }
+ h1{
+ margin: 10px 0 0;
+ font-size: 28px;
+ font-weight: 500;
+ line-height: 32px;
+ }
+ h2{
+ color: #4288ce;
+ font-weight: 400;
+ padding: 6px 0;
+ margin: 6px 0 0;
+ font-size: 18px;
+ border-bottom: 1px solid #eee;
+ }
+ h3{
+ margin: 12px;
+ font-size: 16px;
+ font-weight: bold;
+ }
+ abbr{
+ cursor: help;
+ text-decoration: underline;
+ text-decoration-style: dotted;
+ }
+ a{
+ color: #868686;
+ cursor: pointer;
+ }
+ a:hover{
+ text-decoration: underline;
+ }
+ .line-error{
+ background: #f8cbcb;
+ }
+ .echo table {
+ width: 100%;
+ }
+ .echo pre {
+ padding: 16px;
+ overflow: auto;
+ font-size: 85%;
+ line-height: 1.45;
+ background-color: #f7f7f7;
+ border: 0;
+ border-radius: 3px;
+ font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ }
+ .echo pre > pre {
+ padding: 0;
+ margin: 0;
+ }
+ /* Exception Info */
+ .exception {
+ margin-top: 20px;
+ }
+ .exception .message{
+ padding: 12px;
+ border: 1px solid #ddd;
+ border-bottom: 0 none;
+ line-height: 18px;
+ font-size:16px;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ font-family: Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑",serif;
+ }
+ .exception .code{
+ float: left;
+ text-align: center;
+ color: #fff;
+ margin-right: 12px;
+ padding: 16px;
+ border-radius: 4px;
+ background: #999;
+ }
+ .exception .source-code{
+ padding: 6px;
+ border: 1px solid #ddd;
+
+ background: #f9f9f9;
+ overflow-x: auto;
+
+ }
+ .exception .source-code pre{
+ margin: 0;
+ }
+ .exception .source-code pre ol{
+ margin: 0;
+ color: #4288ce;
+ display: inline-block;
+ min-width: 100%;
+ box-sizing: border-box;
+ font-size:14px;
+ font-family: "Century Gothic",Consolas,"Liberation Mono",Courier,Verdana,serif;
+ padding-left: <?php echo (isset($source) && !empty($source)) ? parse_padding($source) : 40; ?>px;
+ }
+ .exception .source-code pre li{
+ border-left: 1px solid #ddd;
+ height: 18px;
+ line-height: 18px;
+ }
+ .exception .source-code pre code{
+ color: #333;
+ height: 100%;
+ display: inline-block;
+ border-left: 1px solid #fff;
+ font-size:14px;
+ font-family: Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑",serif;
+ }
+ .exception .trace{
+ padding: 6px;
+ border: 1px solid #ddd;
+ border-top: 0 none;
+ line-height: 16px;
+ font-size:14px;
+ font-family: Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑",serif;
+ }
+ .exception .trace h2:hover {
+ text-decoration: underline;
+ cursor: pointer;
+ }
+ .exception .trace ol{
+ margin: 12px;
+ }
+ .exception .trace ol li{
+ padding: 2px 4px;
+ }
+ .exception div:last-child{
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ }
+
+ /* Exception Variables */
+ .exception-var table{
+ width: 100%;
+ margin: 12px 0;
+ box-sizing: border-box;
+ table-layout:fixed;
+ word-wrap:break-word;
+ }
+ .exception-var table caption{
+ text-align: left;
+ font-size: 16px;
+ font-weight: bold;
+ padding: 6px 0;
+ }
+ .exception-var table caption small{
+ font-weight: 300;
+ display: inline-block;
+ margin-left: 10px;
+ color: #ccc;
+ }
+ .exception-var table tbody{
+ font-size: 13px;
+ font-family: Consolas, "Liberation Mono", Courier, "微软雅黑",serif;
+ }
+ .exception-var table td{
+ padding: 0 6px;
+ vertical-align: top;
+ word-break: break-all;
+ }
+ .exception-var table td:first-child{
+ width: 28%;
+ font-weight: bold;
+ white-space: nowrap;
+ }
+ .exception-var table td pre{
+ margin: 0;
+ }
+
+ /* Copyright Info */
+ .copyright{
+ margin-top: 24px;
+ padding: 12px 0;
+ border-top: 1px solid #eee;
+ }
+
+ /* SPAN elements with the classes below are added by prettyprint. */
+ pre.prettyprint .pln { color: #000 } /* plain text */
+ pre.prettyprint .str { color: #080 } /* string content */
+ pre.prettyprint .kwd { color: #008 } /* a keyword */
+ pre.prettyprint .com { color: #800 } /* a comment */
+ pre.prettyprint .typ { color: #606 } /* a type name */
+ pre.prettyprint .lit { color: #066 } /* a literal value */
+ /* punctuation, lisp open bracket, lisp close bracket */
+ pre.prettyprint .pun, pre.prettyprint .opn, pre.prettyprint .clo { color: #660 }
+ pre.prettyprint .tag { color: #008 } /* a markup tag name */
+ pre.prettyprint .atn { color: #606 } /* a markup attribute name */
+ pre.prettyprint .atv { color: #080 } /* a markup attribute value */
+ pre.prettyprint .dec, pre.prettyprint .var { color: #606 } /* a declaration; a variable name */
+ pre.prettyprint .fun { color: red } /* a function name */
+ </style>
+</head>
+<body>
+ <?php if (\think\facade\App::isDebug()) { ?>
+ <?php foreach ($traces as $index => $trace) { ?>
+ <div class="exception">
+ <div class="message">
+ <div class="info">
+ <div>
+ <h2><?php echo "#{$index} [{$trace['code']}]" . sprintf('%s in %s', parse_class($trace['name']), parse_file($trace['file'], $trace['line'])); ?></h2>
+ </div>
+ <div><h1><?php echo nl2br(htmlentities($trace['message'])); ?></h1></div>
+ </div>
+ </div>
+ <?php if (!empty($trace['source'])) { ?>
+ <div class="source-code">
+ <pre class="prettyprint lang-php"><ol start="<?php echo $trace['source']['first']; ?>"><?php foreach ((array) $trace['source']['source'] as $key => $value) { ?><li class="line-<?php echo "{$index}-"; echo $key + $trace['source']['first']; echo $trace['line'] === $key + $trace['source']['first'] ? ' line-error' : ''; ?>"><code><?php echo htmlentities($value); ?></code></li><?php } ?></ol></pre>
+ </div>
+ <?php }?>
+ <div class="trace">
+ <h2 data-expand="<?php echo 0 === $index ? '1' : '0'; ?>">Call Stack</h2>
+ <ol>
+ <li><?php echo sprintf('in %s', parse_file($trace['file'], $trace['line'])); ?></li>
+ <?php foreach ((array) $trace['trace'] as $value) { ?>
+ <li>
+ <?php
+ // Show Function
+ if ($value['function']) {
+ echo sprintf(
+ 'at %s%s%s(%s)',
+ isset($value['class']) ? parse_class($value['class']) : '',
+ isset($value['type']) ? $value['type'] : '',
+ $value['function'],
+ isset($value['args'])?parse_args($value['args']):''
+ );
+ }
+
+ // Show line
+ if (isset($value['file']) && isset($value['line'])) {
+ echo sprintf(' in %s', parse_file($value['file'], $value['line']));
+ }
+ ?>
+ </li>
+ <?php } ?>
+ </ol>
+ </div>
+ </div>
+ <?php } ?>
+ <?php } else { ?>
+ <div class="exception">
+ <div class="info"><h1><?php echo htmlentities($message); ?></h1></div>
+ </div>
+ <?php } ?>
+
+ <?php if (!empty($datas)) { ?>
+ <div class="exception-var">
+ <h2>Exception Datas</h2>
+ <?php foreach ((array) $datas as $label => $value) { ?>
+ <table>
+ <?php if (empty($value)) { ?>
+ <caption><?php echo $label; ?><small>empty</small></caption>
+ <?php } else { ?>
+ <caption><?php echo $label; ?></caption>
+ <tbody>
+ <?php foreach ((array) $value as $key => $val) { ?>
+ <tr>
+ <td><?php echo htmlentities($key); ?></td>
+ <td><?php echo_value($val); ?></td>
+ </tr>
+ <?php } ?>
+ </tbody>
+ <?php } ?>
+ </table>
+ <?php } ?>
+ </div>
+ <?php } ?>
+
+ <?php if (!empty($tables)) { ?>
+ <div class="exception-var">
+ <h2>Environment Variables</h2>
+ <?php foreach ((array) $tables as $label => $value) { ?>
+ <table>
+ <?php if (empty($value)) { ?>
+ <caption><?php echo $label; ?><small>empty</small></caption>
+ <?php } else { ?>
+ <caption><?php echo $label; ?></caption>
+ <tbody>
+ <?php foreach ((array) $value as $key => $val) { ?>
+ <tr>
+ <td><?php echo htmlentities($key); ?></td>
+ <td><?php echo_value($val); ?></td>
+ </tr>
+ <?php } ?>
+ </tbody>
+ <?php } ?>
+ </table>
+ <?php } ?>
+ </div>
+ <?php } ?>
+
+ <div class="copyright">
+ <a title="官方网站" href="http://www.thinkphp.cn">ThinkPHP</a>
+ <span>V<?php echo \think\facade\App::version(); ?></span>
+ <span>{ 十年磨一剑-为API开发设计的高性能框架 }</span>
+ <span>- <a title="官方手册" href="https://www.kancloud.cn/manual/thinkphp6_0/content">官方手册</a></span>
+ </div>
+ <?php if (\think\facade\App::isDebug()) { ?>
+ <script>
+ function $(selector, node){
+ var elements;
+
+ node = node || document;
+ if(document.querySelectorAll){
+ elements = node.querySelectorAll(selector);
+ } else {
+ switch(selector.substr(0, 1)){
+ case '#':
+ elements = [node.getElementById(selector.substr(1))];
+ break;
+ case '.':
+ if(document.getElementsByClassName){
+ elements = node.getElementsByClassName(selector.substr(1));
+ } else {
+ elements = get_elements_by_class(selector.substr(1), node);
+ }
+ break;
+ default:
+ elements = node.getElementsByTagName();
+ }
+ }
+ return elements;
+
+ function get_elements_by_class(search_class, node, tag) {
+ var elements = [], eles,
+ pattern = new RegExp('(^|\\s)' + search_class + '(\\s|$)');
+
+ node = node || document;
+ tag = tag || '*';
+
+ eles = node.getElementsByTagName(tag);
+ for(var i = 0; i < eles.length; i++) {
+ if(pattern.test(eles[i].className)) {
+ elements.push(eles[i])
+ }
+ }
+
+ return elements;
+ }
+ }
+
+ $.getScript = function(src, func){
+ var script = document.createElement('script');
+
+ script.async = 'async';
+ script.src = src;
+ script.onload = func || function(){};
+
+ $('head')[0].appendChild(script);
+ }
+
+ ;(function(){
+ var files = $('.toggle');
+ var ol = $('ol', $('.prettyprint')[0]);
+ var li = $('li', ol[0]);
+
+ // 短路径和长路径变换
+ for(var i = 0; i < files.length; i++){
+ files[i].ondblclick = function(){
+ var title = this.title;
+
+ this.title = this.innerHTML;
+ this.innerHTML = title;
+ }
+ }
+
+ (function () {
+ var expand = function (dom, expand) {
+ var ol = $('ol', dom.parentNode)[0];
+ expand = undefined === expand ? dom.attributes['data-expand'].value === '0' : undefined;
+ if (expand) {
+ dom.attributes['data-expand'].value = '1';
+ ol.style.display = 'none';
+ dom.innerText = 'Call Stack (展开)';
+ } else {
+ dom.attributes['data-expand'].value = '0';
+ ol.style.display = 'block';
+ dom.innerText = 'Call Stack (折叠)';
+ }
+ };
+ var traces = $('.trace');
+ for (var i = 0; i < traces.length; i ++) {
+ var h2 = $('h2', traces[i])[0];
+ expand(h2);
+ h2.onclick = function () {
+ expand(this);
+ };
+ }
+ })();
+
+ $.getScript('//cdn.bootcdn.net/ajax/libs/prettify/r298/prettify.min.js', function(){
+ prettyPrint();
+ });
+ })();
+ </script>
+ <?php } ?>
+</body>
+</html>
diff --git a/vendor/topthink/framework/tests/AppTest.php b/vendor/topthink/framework/tests/AppTest.php
new file mode 100755
index 0000000..6b86015
--- /dev/null
+++ b/vendor/topthink/framework/tests/AppTest.php
@@ -0,0 +1,215 @@
+<?php
+
+namespace think\tests;
+
+use Mockery as m;
+use org\bovigo\vfs\vfsStream;
+use org\bovigo\vfs\vfsStreamDirectory;
+use PHPUnit\Framework\TestCase;
+use stdClass;
+use think\App;
+use think\Env;
+use think\Event;
+use think\event\AppInit;
+use think\exception\ClassNotFoundException;
+use think\Service;
+
+class SomeService extends Service
+{
+ public $bind = [
+ 'some' => 'class',
+ ];
+
+ public function register()
+ {
+
+ }
+
+ public function boot()
+ {
+
+ }
+}
+
+/**
+ * @property array initializers
+ */
+class AppTest extends TestCase
+{
+ /** @var App */
+ protected $app;
+
+ protected function setUp()
+ {
+ $this->app = new App();
+ }
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ public function testService()
+ {
+ $this->app->register(stdClass::class);
+
+ $this->assertInstanceOf(stdClass::class, $this->app->getService(stdClass::class));
+
+ $service = m::mock(SomeService::class);
+
+ $service->shouldReceive('register')->once();
+
+ $this->app->register($service);
+
+ $this->assertEquals($service, $this->app->getService(SomeService::class));
+
+ $service2 = m::mock(SomeService::class);
+
+ $service2->shouldReceive('register')->once();
+
+ $this->app->register($service2);
+
+ $this->assertEquals($service, $this->app->getService(SomeService::class));
+
+ $this->app->register($service2, true);
+
+ $this->assertEquals($service2, $this->app->getService(SomeService::class));
+
+ $service->shouldReceive('boot')->once();
+ $service2->shouldReceive('boot')->once();
+
+ $this->app->boot();
+ }
+
+ public function testDebug()
+ {
+ $this->app->debug(false);
+
+ $this->assertFalse($this->app->isDebug());
+
+ $this->app->debug(true);
+
+ $this->assertTrue($this->app->isDebug());
+ }
+
+ public function testNamespace()
+ {
+ $namespace = 'test';
+
+ $this->app->setNamespace($namespace);
+
+ $this->assertEquals($namespace, $this->app->getNamespace());
+ }
+
+ public function testVersion()
+ {
+ $this->assertEquals(App::VERSION, $this->app->version());
+ }
+
+ public function testPath()
+ {
+ $rootPath = __DIR__ . DIRECTORY_SEPARATOR;
+
+ $app = new App($rootPath);
+
+ $this->assertEquals($rootPath, $app->getRootPath());
+
+ $this->assertEquals(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR, $app->getThinkPath());
+
+ $this->assertEquals($rootPath . 'app' . DIRECTORY_SEPARATOR, $app->getAppPath());
+
+ $appPath = $rootPath . 'app' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR;
+ $app->setAppPath($appPath);
+ $this->assertEquals($appPath, $app->getAppPath());
+
+ $this->assertEquals($rootPath . 'app' . DIRECTORY_SEPARATOR, $app->getBasePath());
+
+ $this->assertEquals($rootPath . 'config' . DIRECTORY_SEPARATOR, $app->getConfigPath());
+
+ $this->assertEquals($rootPath . 'runtime' . DIRECTORY_SEPARATOR, $app->getRuntimePath());
+
+ $runtimePath = $rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR;
+ $app->setRuntimePath($runtimePath);
+ $this->assertEquals($runtimePath, $app->getRuntimePath());
+ }
+
+ /**
+ * @param vfsStreamDirectory $root
+ * @param bool $debug
+ * @return App
+ */
+ protected function prepareAppForInitialize(vfsStreamDirectory $root, $debug = true)
+ {
+ $rootPath = $root->url() . DIRECTORY_SEPARATOR;
+
+ $app = new App($rootPath);
+
+ $initializer = m::mock();
+ $initializer->shouldReceive('init')->once()->with($app);
+
+ $app->instance($initializer->mockery_getName(), $initializer);
+
+ (function () use ($initializer) {
+ $this->initializers = [$initializer->mockery_getName()];
+ })->call($app);
+
+ $env = m::mock(Env::class);
+ $env->shouldReceive('load')->once()->with($rootPath . '.env');
+ $env->shouldReceive('get')->once()->with('config_ext', '.php')->andReturn('.php');
+ $env->shouldReceive('get')->once()->with('app_debug')->andReturn($debug);
+
+ $event = m::mock(Event::class);
+ $event->shouldReceive('trigger')->once()->with(AppInit::class);
+ $event->shouldReceive('bind')->once()->with([]);
+ $event->shouldReceive('listenEvents')->once()->with([]);
+ $event->shouldReceive('subscribe')->once()->with([]);
+
+ $app->instance('env', $env);
+ $app->instance('event', $event);
+
+ return $app;
+ }
+
+ public function testInitialize()
+ {
+ $root = vfsStream::setup('rootDir', null, [
+ '.env' => '',
+ 'app' => [
+ 'common.php' => '',
+ 'event.php' => '<?php return ["bind"=>[],"listen"=>[],"subscribe"=>[]];',
+ 'provider.php' => '<?php return [];',
+ ],
+ 'config' => [
+ 'app.php' => '<?php return [];',
+ ],
+ ]);
+
+ $app = $this->prepareAppForInitialize($root, true);
+
+ $app->debug(false);
+
+ $app->initialize();
+
+ $this->assertIsInt($app->getBeginMem());
+ $this->assertIsFloat($app->getBeginTime());
+
+ $this->assertTrue($app->initialized());
+ }
+
+ public function testFactory()
+ {
+ $this->assertInstanceOf(stdClass::class, App::factory(stdClass::class));
+
+ $this->expectException(ClassNotFoundException::class);
+
+ App::factory('SomeClass');
+ }
+
+ public function testParseClass()
+ {
+ $this->assertEquals('app\\controller\\SomeClass', $this->app->parseClass('controller', 'some_class'));
+ $this->app->setNamespace('app2');
+ $this->assertEquals('app2\\controller\\SomeClass', $this->app->parseClass('controller', 'some_class'));
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/CacheTest.php b/vendor/topthink/framework/tests/CacheTest.php
new file mode 100755
index 0000000..5b5a13c
--- /dev/null
+++ b/vendor/topthink/framework/tests/CacheTest.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace think\tests;
+
+use InvalidArgumentException;
+use Mockery as m;
+use Mockery\MockInterface;
+use org\bovigo\vfs\vfsStream;
+use PHPUnit\Framework\TestCase;
+use think\App;
+use think\Cache;
+use think\Config;
+use think\Container;
+
+class CacheTest extends TestCase
+{
+ /** @var App|MockInterface */
+ protected $app;
+
+ /** @var Cache|MockInterface */
+ protected $cache;
+
+ /** @var Config|MockInterface */
+ protected $config;
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ protected function setUp()
+ {
+ $this->app = m::mock(App::class)->makePartial();
+
+ Container::setInstance($this->app);
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class)->makePartial();
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+
+ $this->cache = new Cache($this->app);
+ }
+
+ public function testGetConfig()
+ {
+ $config = [
+ 'default' => 'file',
+ ];
+
+ $this->config->shouldReceive('get')->with('cache')->andReturn($config);
+
+ $this->assertEquals($config, $this->cache->getConfig());
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->cache->getStoreConfig('foo');
+ }
+
+ public function testCacheManagerInstances()
+ {
+ $this->config->shouldReceive('get')->with("cache.stores.single", null)->andReturn(['type' => 'file']);
+
+ $channel1 = $this->cache->store('single');
+ $channel2 = $this->cache->store('single');
+
+ $this->assertSame($channel1, $channel2);
+ }
+
+ public function testFileCache()
+ {
+ $root = vfsStream::setup();
+
+ $this->config->shouldReceive('get')->with("cache.default", null)->andReturn('file');
+
+ $this->config->shouldReceive('get')->with("cache.stores.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]);
+
+ $this->cache->set('foo', 5);
+ $this->cache->inc('foo');
+ $this->assertEquals(6, $this->cache->get('foo'));
+ $this->cache->dec('foo', 2);
+ $this->assertEquals(4, $this->cache->get('foo'));
+
+ $this->cache->set('bar', true);
+ $this->assertTrue($this->cache->get('bar'));
+
+ $this->cache->set('baz', null);
+ $this->assertNull($this->cache->get('baz'));
+
+ $this->assertTrue($this->cache->has('baz'));
+ $this->cache->delete('baz');
+ $this->assertFalse($this->cache->has('baz'));
+ $this->assertNull($this->cache->get('baz'));
+ $this->assertFalse($this->cache->get('baz', false));
+
+ $this->assertTrue($root->hasChildren());
+ $this->cache->clear();
+ $this->assertFalse($root->hasChildren());
+
+ //tags
+ $this->cache->tag('foo')->set('bar', 'foobar');
+ $this->assertEquals('foobar', $this->cache->get('bar'));
+ $this->cache->tag('foo')->clear();
+ $this->assertFalse($this->cache->has('bar'));
+
+ //multiple
+ $this->cache->setMultiple(['foo' => ['foobar', 'bar'], 'foobar' => ['foo', 'bar']]);
+ $this->assertEquals(['foo' => ['foobar', 'bar'], 'foobar' => ['foo', 'bar']], $this->cache->getMultiple(['foo', 'foobar']));
+ $this->assertTrue($this->cache->deleteMultiple(['foo', 'foobar']));
+ }
+
+ public function testRedisCache()
+ {
+ if (extension_loaded('redis')) {
+ return;
+ }
+ $this->config->shouldReceive('get')->with("cache.default", null)->andReturn('redis');
+ $this->config->shouldReceive('get')->with("cache.stores.redis", null)->andReturn(['type' => 'redis']);
+
+ $redis = m::mock('overload:\Predis\Client');
+
+ $redis->shouldReceive("set")->once()->with('foo', 5)->andReturnTrue();
+ $redis->shouldReceive("incrby")->once()->with('foo', 1)->andReturnTrue();
+ $redis->shouldReceive("decrby")->once()->with('foo', 2)->andReturnTrue();
+ $redis->shouldReceive("get")->once()->with('foo')->andReturn('6');
+ $redis->shouldReceive("get")->once()->with('foo')->andReturn('4');
+ $redis->shouldReceive("set")->once()->with('bar', serialize(true))->andReturnTrue();
+ $redis->shouldReceive("set")->once()->with('baz', serialize(null))->andReturnTrue();
+ $redis->shouldReceive("del")->once()->with('baz')->andReturnTrue();
+ $redis->shouldReceive("flushDB")->once()->andReturnTrue();
+ $redis->shouldReceive("set")->once()->with('bar', serialize('foobar'))->andReturnTrue();
+ $redis->shouldReceive("sAdd")->once()->with('tag:' . md5('foo'), 'bar')->andReturnTrue();
+ $redis->shouldReceive("sMembers")->once()->with('tag:' . md5('foo'))->andReturn(['bar']);
+ $redis->shouldReceive("del")->once()->with(['bar'])->andReturnTrue();
+ $redis->shouldReceive("del")->once()->with('tag:' . md5('foo'))->andReturnTrue();
+
+ $this->cache->set('foo', 5);
+ $this->cache->inc('foo');
+ $this->assertEquals(6, $this->cache->get('foo'));
+ $this->cache->dec('foo', 2);
+ $this->assertEquals(4, $this->cache->get('foo'));
+
+ $this->cache->set('bar', true);
+ $this->cache->set('baz', null);
+ $this->cache->delete('baz');
+ $this->cache->clear();
+
+ //tags
+ $this->cache->tag('foo')->set('bar', 'foobar');
+ $this->cache->tag('foo')->clear();
+ }
+}
diff --git a/vendor/topthink/framework/tests/ConfigTest.php b/vendor/topthink/framework/tests/ConfigTest.php
new file mode 100755
index 0000000..271a34f
--- /dev/null
+++ b/vendor/topthink/framework/tests/ConfigTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace think\tests;
+
+use org\bovigo\vfs\vfsStream;
+use PHPUnit\Framework\TestCase;
+use think\Config;
+
+class ConfigTest extends TestCase
+{
+ public function testLoad()
+ {
+ $root = vfsStream::setup();
+ $file = vfsStream::newFile('test.php')->setContent("<?php return ['key1'=> 'value1','key2'=>'value2'];");
+ $root->addChild($file);
+
+ $config = new Config();
+
+ $config->load($file->url(), 'test');
+
+ $this->assertEquals('value1', $config->get('test.key1'));
+ $this->assertEquals('value2', $config->get('test.key2'));
+
+ $this->assertSame(['key1' => 'value1', 'key2' => 'value2'], $config->get('test'));
+ }
+
+ public function testSetAndGet()
+ {
+ $config = new Config();
+
+ $config->set([
+ 'key1' => 'value1',
+ 'key2' => [
+ 'key3' => 'value3',
+ ],
+ ], 'test');
+
+ $this->assertTrue($config->has('test.key1'));
+ $this->assertEquals('value1', $config->get('test.key1'));
+ $this->assertEquals('value3', $config->get('test.key2.key3'));
+
+ $this->assertEquals(['key3' => 'value3'], $config->get('test.key2'));
+ $this->assertFalse($config->has('test.key3'));
+ $this->assertEquals('none', $config->get('test.key3', 'none'));
+ }
+}
diff --git a/vendor/topthink/framework/tests/ContainerTest.php b/vendor/topthink/framework/tests/ContainerTest.php
new file mode 100755
index 0000000..e27deb0
--- /dev/null
+++ b/vendor/topthink/framework/tests/ContainerTest.php
@@ -0,0 +1,314 @@
+<?php
+
+namespace think\tests;
+
+use PHPUnit\Framework\TestCase;
+use ReflectionMethod;
+use stdClass;
+use think\Container;
+use think\exception\ClassNotFoundException;
+use think\exception\FuncNotFoundException;
+
+class Taylor
+{
+ public $name;
+
+ public function __construct($name)
+ {
+ $this->name = $name;
+ }
+
+ public function some(Container $container)
+ {
+ }
+
+ protected function protectionFun()
+ {
+ return true;
+ }
+
+ public static function test(Container $container)
+ {
+ return $container;
+ }
+
+ public static function __make()
+ {
+ return new self('Taylor');
+ }
+}
+
+class SomeClass
+{
+ public $container;
+
+ public $count = 0;
+
+ public function __construct(Container $container)
+ {
+ $this->container = $container;
+ }
+}
+
+class ContainerTest extends TestCase
+{
+ protected function tearDown(): void
+ {
+ Container::setInstance(null);
+ }
+
+ public function testClosureResolution()
+ {
+ $container = new Container;
+
+ Container::setInstance($container);
+
+ $container->bind('name', function () {
+ return 'Taylor';
+ });
+
+ $this->assertEquals('Taylor', $container->make('name'));
+
+ $this->assertEquals('Taylor', Container::pull('name'));
+ }
+
+ public function testGet()
+ {
+ $container = new Container;
+
+ $this->expectException(ClassNotFoundException::class);
+ $this->expectExceptionMessage('class not exists: name');
+ $container->get('name');
+
+ $container->bind('name', function () {
+ return 'Taylor';
+ });
+
+ $this->assertSame('Taylor', $container->get('name'));
+ }
+
+ public function testExist()
+ {
+ $container = new Container;
+
+ $container->bind('name', function () {
+ return 'Taylor';
+ });
+
+ $this->assertFalse($container->exists("name"));
+
+ $container->make('name');
+
+ $this->assertTrue($container->exists('name'));
+ }
+
+ public function testInstance()
+ {
+ $container = new Container;
+
+ $container->bind('name', function () {
+ return 'Taylor';
+ });
+
+ $this->assertEquals('Taylor', $container->get('name'));
+
+ $container->bind('name2', Taylor::class);
+
+ $object = new stdClass();
+
+ $this->assertFalse($container->exists('name2'));
+
+ $container->instance('name2', $object);
+
+ $this->assertTrue($container->exists('name2'));
+
+ $this->assertTrue($container->exists(Taylor::class));
+
+ $this->assertEquals($object, $container->make(Taylor::class));
+
+ unset($container->name1);
+
+ $this->assertFalse($container->exists('name1'));
+
+ $container->delete('name2');
+
+ $this->assertFalse($container->exists('name2'));
+
+ foreach ($container as $class => $instance) {
+
+ }
+ }
+
+ public function testBind()
+ {
+ $container = new Container;
+
+ $object = new stdClass();
+
+ $container->bind(['name' => Taylor::class]);
+
+ $container->bind('name2', $object);
+
+ $container->bind('name3', Taylor::class);
+ $container->bind('name3', Taylor::class);
+
+ $container->name4 = $object;
+
+ $container['name5'] = $object;
+
+ $this->assertTrue(isset($container->name4));
+
+ $this->assertTrue(isset($container['name5']));
+
+ $this->assertInstanceOf(Taylor::class, $container->get('name'));
+
+ $this->assertSame($object, $container->get('name2'));
+
+ $this->assertSame($object, $container->name4);
+
+ $this->assertSame($object, $container['name5']);
+
+ $this->assertInstanceOf(Taylor::class, $container->get('name3'));
+
+ unset($container['name']);
+
+ $this->assertFalse(isset($container['name']));
+
+ unset($container->name3);
+
+ $this->assertFalse(isset($container->name3));
+ }
+
+ public function testAutoConcreteResolution()
+ {
+ $container = new Container;
+
+ $taylor = $container->make(Taylor::class);
+
+ $this->assertInstanceOf(Taylor::class, $taylor);
+ $this->assertAttributeSame('Taylor', 'name', $taylor);
+ }
+
+ public function testGetAndSetInstance()
+ {
+ $this->assertInstanceOf(Container::class, Container::getInstance());
+
+ $object = new stdClass();
+
+ Container::setInstance($object);
+
+ $this->assertSame($object, Container::getInstance());
+
+ Container::setInstance(function () {
+ return $this;
+ });
+
+ $this->assertSame($this, Container::getInstance());
+ }
+
+ public function testResolving()
+ {
+ $container = new Container();
+ $container->bind(Container::class, $container);
+
+ $container->resolving(function (SomeClass $taylor, Container $container) {
+ $taylor->count++;
+ });
+ $container->resolving(SomeClass::class, function (SomeClass $taylor, Container $container) {
+ $taylor->count++;
+ });
+
+ /** @var SomeClass $someClass */
+ $someClass = $container->invokeClass(SomeClass::class);
+ $this->assertEquals(2, $someClass->count);
+ }
+
+ public function testInvokeFunctionWithoutMethodThrowsException()
+ {
+ $this->expectException(FuncNotFoundException::class);
+ $this->expectExceptionMessage('function not exists: ContainerTestCallStub()');
+ $container = new Container();
+ $container->invokeFunction('ContainerTestCallStub', []);
+ }
+
+ public function testInvokeProtectionMethod()
+ {
+ $container = new Container();
+ $this->assertTrue($container->invokeMethod([Taylor::class, 'protectionFun'], [], true));
+ }
+
+ public function testInvoke()
+ {
+ $container = new Container();
+
+ Container::setInstance($container);
+
+ $container->bind(Container::class, $container);
+
+ $stub = $this->createMock(Taylor::class);
+
+ $stub->expects($this->once())->method('some')->with($container)->will($this->returnSelf());
+
+ $container->invokeMethod([$stub, 'some']);
+
+ $this->assertEquals('48', $container->invoke('ord', ['0']));
+
+ $this->assertSame($container, $container->invoke(Taylor::class . '::test', []));
+
+ $this->assertSame($container, $container->invokeMethod(Taylor::class . '::test'));
+
+ $reflect = new ReflectionMethod($container, 'exists');
+
+ $this->assertTrue($container->invokeReflectMethod($container, $reflect, [Container::class]));
+
+ $this->assertSame($container, $container->invoke(function (Container $container) {
+ return $container;
+ }));
+
+ $this->assertSame($container, $container->invoke(Taylor::class . '::test'));
+
+ $object = $container->invokeClass(SomeClass::class);
+ $this->assertInstanceOf(SomeClass::class, $object);
+ $this->assertSame($container, $object->container);
+
+ $stdClass = new stdClass();
+
+ $container->invoke(function (Container $container, stdClass $stdObject, $key1, $lowKey, $key2 = 'default') use ($stdClass) {
+ $this->assertEquals('value1', $key1);
+ $this->assertEquals('default', $key2);
+ $this->assertEquals('value2', $lowKey);
+ $this->assertSame($stdClass, $stdObject);
+ return $container;
+ }, ['some' => $stdClass, 'key1' => 'value1', 'low_key' => 'value2']);
+ }
+
+ public function testInvokeMethodNotExists()
+ {
+ $container = $this->resolveContainer();
+ $this->expectException(FuncNotFoundException::class);
+
+ $container->invokeMethod([SomeClass::class, 'any']);
+ }
+
+ public function testInvokeClassNotExists()
+ {
+ $container = new Container();
+
+ Container::setInstance($container);
+
+ $container->bind(Container::class, $container);
+
+ $this->expectExceptionObject(new ClassNotFoundException('class not exists: SomeClass'));
+
+ $container->invokeClass('SomeClass');
+ }
+
+ protected function resolveContainer()
+ {
+ $container = new Container();
+
+ Container::setInstance($container);
+ return $container;
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/DbTest.php b/vendor/topthink/framework/tests/DbTest.php
new file mode 100755
index 0000000..3bd0c1e
--- /dev/null
+++ b/vendor/topthink/framework/tests/DbTest.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace think\tests;
+
+use Mockery as m;
+use PHPUnit\Framework\TestCase;
+use think\Cache;
+use think\cache\Driver;
+use think\Config;
+use think\Db;
+use think\Event;
+use think\Log;
+
+class DbTest extends TestCase
+{
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ public function testMake()
+ {
+ $event = m::mock(Event::class);
+ $config = m::mock(Config::class);
+ $log = m::mock(Log::class);
+ $cache = m::mock(Cache::class);
+ $store = m::mock(Driver::class);
+
+ $config->shouldReceive('get')->with('database.cache_store', null)->andReturn(null);
+ $cache->shouldReceive('store')->with(null)->andReturn($store);
+
+ $db = Db::__make($event, $config, $log, $cache);
+
+ $config->shouldReceive('get')->with('database.foo', null)->andReturn('foo');
+ $this->assertEquals('foo', $db->getConfig('foo'));
+
+ $config->shouldReceive('get')->with('database', [])->andReturn([]);
+ $this->assertEquals([], $db->getConfig());
+
+ $callback = function () {
+ };
+ $event->shouldReceive('listen')->with('db.some', $callback);
+ $db->event('some', $callback);
+
+ $event->shouldReceive('trigger')->with('db.some', null, false);
+ $db->trigger('some');
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/DispatchTest.php b/vendor/topthink/framework/tests/DispatchTest.php
new file mode 100755
index 0000000..2b9e413
--- /dev/null
+++ b/vendor/topthink/framework/tests/DispatchTest.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace think\tests;
+
+use GuzzleHttp\Psr7\Response;
+use Mockery;
+use PHPUnit\Framework\TestCase;
+use think\Request;
+use think\route\Dispatch;
+use think\route\Rule;
+
+class DispatchTest extends TestCase
+{
+ public function testPsr7Response()
+ {
+ $request = Mockery::mock(Request::class);
+ $rule = Mockery::mock(Rule::class);
+ $dispatch = new class($request, $rule, '') extends Dispatch {
+ public function exec()
+ {
+ return new Response(200, ['framework' => ['tp', 'thinkphp'], 'psr' => 'psr-7'], '123');
+ }
+ };
+
+ $response = $dispatch->run();
+
+ $this->assertInstanceOf(\think\Response::class, $response);
+ $this->assertEquals('123', $response->getContent());
+ $this->assertEquals('tp, thinkphp', $response->getHeader('framework'));
+ $this->assertEquals('psr-7', $response->getHeader('psr'));
+ }
+}
diff --git a/vendor/topthink/framework/tests/EnvTest.php b/vendor/topthink/framework/tests/EnvTest.php
new file mode 100755
index 0000000..9b3b615
--- /dev/null
+++ b/vendor/topthink/framework/tests/EnvTest.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace think\tests;
+
+use org\bovigo\vfs\vfsStream;
+use PHPUnit\Framework\TestCase;
+use think\Env;
+use think\Exception;
+
+class EnvTest extends TestCase
+{
+ public function testEnvFile()
+ {
+ $root = vfsStream::setup();
+ $envFile = vfsStream::newFile('.env')->setContent("key1=value1\nkey2=value2");
+ $root->addChild($envFile);
+
+ $env = new Env();
+
+ $env->load($envFile->url());
+
+ $this->assertEquals('value1', $env->get('key1'));
+ $this->assertEquals('value2', $env->get('key2'));
+ }
+
+ public function testServerEnv()
+ {
+ $env = new Env();
+
+ $this->assertEquals('value2', $env->get('key2', 'value2'));
+
+ putenv('PHP_KEY7=value7');
+ putenv('PHP_KEY8=false');
+ putenv('PHP_KEY9=true');
+
+ $this->assertEquals('value7', $env->get('key7'));
+ $this->assertFalse($env->get('KEY8'));
+ $this->assertTrue($env->get('key9'));
+ }
+
+ public function testSetEnv()
+ {
+ $env = new Env();
+
+ $env->set([
+ 'key1' => 'value1',
+ 'key2' => [
+ 'key1' => 'value1-2',
+ ],
+ ]);
+
+ $env->set('key3', 'value3');
+
+ $env->key4 = 'value4';
+
+ $env['key5'] = 'value5';
+
+ $this->assertEquals('value1', $env->get('key1'));
+ $this->assertEquals('value1-2', $env->get('key2.key1'));
+
+ $this->assertEquals('value3', $env->get('key3'));
+
+ $this->assertEquals('value4', $env->key4);
+
+ $this->assertEquals('value5', $env['key5']);
+
+ $this->expectException(Exception::class);
+
+ unset($env['key5']);
+ }
+
+ public function testHasEnv()
+ {
+ $env = new Env();
+ $env->set(['foo' => 'bar']);
+ $this->assertTrue($env->has('foo'));
+ $this->assertTrue(isset($env->foo));
+ $this->assertTrue($env->offsetExists('foo'));
+ }
+}
diff --git a/vendor/topthink/framework/tests/EventTest.php b/vendor/topthink/framework/tests/EventTest.php
new file mode 100755
index 0000000..ded5a36
--- /dev/null
+++ b/vendor/topthink/framework/tests/EventTest.php
@@ -0,0 +1,134 @@
+<?php
+
+namespace think\tests;
+
+use Mockery as m;
+use Mockery\MockInterface;
+use PHPUnit\Framework\TestCase;
+use think\App;
+use think\Config;
+use think\Container;
+use think\Event;
+
+class EventTest extends TestCase
+{
+ /** @var App|MockInterface */
+ protected $app;
+
+ /** @var Event|MockInterface */
+ protected $event;
+
+ /** @var Config|MockInterface */
+ protected $config;
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ protected function setUp()
+ {
+ $this->app = m::mock(App::class)->makePartial();
+
+ Container::setInstance($this->app);
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class)->makePartial();
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+
+ $this->event = new Event($this->app);
+ }
+
+ public function testBasic()
+ {
+ $this->event->bind(['foo' => 'baz']);
+
+ $this->event->listen('foo', function ($bar) {
+ $this->assertEquals('bar', $bar);
+ });
+
+ $this->assertTrue($this->event->hasListener('foo'));
+
+ $this->event->trigger('baz', 'bar');
+
+ $this->event->remove('foo');
+
+ $this->assertFalse($this->event->hasListener('foo'));
+ }
+
+ public function testOnceEvent()
+ {
+ $this->event->listen('AppInit', function ($bar) {
+ $this->assertEquals('bar', $bar);
+ return 'foo';
+ });
+
+ $this->assertEquals('foo', $this->event->trigger('AppInit', 'bar', true));
+ $this->assertEquals(['foo'], $this->event->trigger('AppInit', 'bar'));
+ }
+
+ public function testClassListener()
+ {
+ $listener = m::mock("overload:SomeListener", TestListener::class);
+
+ $listener->shouldReceive('handle')->andReturnTrue();
+
+ $this->event->listen('some', "SomeListener");
+
+ $this->assertTrue($this->event->until('some'));
+ }
+
+ public function testSubscribe()
+ {
+ $listener = m::mock("overload:SomeListener", TestListener::class);
+
+ $listener->shouldReceive('subscribe')->andReturnUsing(function (Event $event) use ($listener) {
+
+ $listener->shouldReceive('onBar')->once()->andReturnFalse();
+
+ $event->listenEvents(['SomeListener::onBar' => [[$listener, 'onBar']]]);
+ });
+
+ $this->event->subscribe('SomeListener');
+
+ $this->assertTrue($this->event->hasListener('SomeListener::onBar'));
+
+ $this->event->trigger('SomeListener::onBar');
+ }
+
+ public function testAutoObserve()
+ {
+ $listener = m::mock("overload:SomeListener", TestListener::class);
+
+ $listener->shouldReceive('onBar')->once();
+
+ $this->app->shouldReceive('make')->with('SomeListener')->andReturn($listener);
+
+ $this->event->observe('SomeListener');
+
+ $this->event->trigger('bar');
+ }
+
+}
+
+class TestListener
+{
+ public function handle()
+ {
+
+ }
+
+ public function onBar()
+ {
+
+ }
+
+ public function onFoo()
+ {
+
+ }
+
+ public function subscribe()
+ {
+
+ }
+}
diff --git a/vendor/topthink/framework/tests/HttpTest.php b/vendor/topthink/framework/tests/HttpTest.php
new file mode 100755
index 0000000..c3e0abd
--- /dev/null
+++ b/vendor/topthink/framework/tests/HttpTest.php
@@ -0,0 +1,155 @@
+<?php
+
+namespace think\tests;
+
+use Mockery as m;
+use Mockery\MockInterface;
+use org\bovigo\vfs\vfsStream;
+use PHPUnit\Framework\TestCase;
+use think\App;
+use think\Console;
+use think\Event;
+use think\event\HttpEnd;
+use think\Exception;
+use think\exception\Handle;
+use think\Http;
+use think\Log;
+use think\Request;
+use think\Response;
+use think\Route;
+
+class HttpTest extends TestCase
+{
+ /** @var App|MockInterface */
+ protected $app;
+
+ /** @var Http|MockInterface */
+ protected $http;
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ protected function setUp()
+ {
+ $this->app = m::mock(App::class)->makePartial();
+
+ $this->http = m::mock(Http::class, [$this->app])->shouldAllowMockingProtectedMethods()->makePartial();
+ }
+
+ protected function prepareApp($request, $response)
+ {
+ $this->app->shouldReceive('instance')->once()->with('request', $request);
+ $this->app->shouldReceive('initialized')->once()->andReturnFalse();
+ $this->app->shouldReceive('initialize')->once();
+ $this->app->shouldReceive('get')->with('request')->andReturn($request);
+
+ $route = m::mock(Route::class);
+
+ $route->shouldReceive('dispatch')->withArgs(function ($req, $withRoute) use ($request) {
+ if ($withRoute) {
+ $withRoute();
+ }
+ return $req === $request;
+ })->andReturn($response);
+
+ $route->shouldReceive('config')->with('route_annotation')->andReturn(true);
+
+ $this->app->shouldReceive('get')->with('route')->andReturn($route);
+
+ $console = m::mock(Console::class);
+
+ $console->shouldReceive('call');
+
+ $this->app->shouldReceive('get')->with('console')->andReturn($console);
+ }
+
+ public function testRun()
+ {
+ $root = vfsStream::setup('rootDir', null, [
+ 'app' => [
+ 'controller' => [],
+ 'middleware.php' => '<?php return [];',
+ ],
+ 'route' => [
+ 'route.php' => '<?php return [];',
+ ],
+ ]);
+
+ $this->app->shouldReceive('getBasePath')->andReturn($root->getChild('app')->url() . DIRECTORY_SEPARATOR);
+ $this->app->shouldReceive('getRootPath')->andReturn($root->url() . DIRECTORY_SEPARATOR);
+
+ $request = m::mock(Request::class)->makePartial();
+ $response = m::mock(Response::class)->makePartial();
+
+ $this->prepareApp($request, $response);
+
+ $this->assertEquals($response, $this->http->run($request));
+ }
+
+ public function multiAppRunProvider()
+ {
+ $request1 = m::mock(Request::class)->makePartial();
+ $request1->shouldReceive('subDomain')->andReturn('www');
+ $request1->shouldReceive('host')->andReturn('www.domain.com');
+
+ $request2 = m::mock(Request::class)->makePartial();
+ $request2->shouldReceive('subDomain')->andReturn('app2');
+ $request2->shouldReceive('host')->andReturn('app2.domain.com');
+
+ $request3 = m::mock(Request::class)->makePartial();
+ $request3->shouldReceive('pathinfo')->andReturn('some1/a/b/c');
+
+ $request4 = m::mock(Request::class)->makePartial();
+ $request4->shouldReceive('pathinfo')->andReturn('app3/a/b/c');
+
+ $request5 = m::mock(Request::class)->makePartial();
+ $request5->shouldReceive('pathinfo')->andReturn('some2/a/b/c');
+
+ return [
+ [$request1, true, 'app1'],
+ [$request2, true, 'app2'],
+ [$request3, true, 'app3'],
+ [$request4, true, null],
+ [$request5, true, 'some2', 'path'],
+ [$request1, false, 'some3'],
+ ];
+ }
+
+ public function testRunWithException()
+ {
+ $request = m::mock(Request::class);
+ $response = m::mock(Response::class);
+
+ $this->app->shouldReceive('instance')->once()->with('request', $request);
+ $this->app->shouldReceive('initialize')->once();
+
+ $exception = new Exception();
+
+ $this->http->shouldReceive('runWithRequest')->once()->with($request)->andThrow($exception);
+
+ $handle = m::mock(Handle::class);
+
+ $handle->shouldReceive('report')->once()->with($exception);
+ $handle->shouldReceive('render')->once()->with($request, $exception)->andReturn($response);
+
+ $this->app->shouldReceive('make')->with(Handle::class)->andReturn($handle);
+
+ $this->assertEquals($response, $this->http->run($request));
+ }
+
+ public function testEnd()
+ {
+ $response = m::mock(Response::class);
+ $event = m::mock(Event::class);
+ $event->shouldReceive('trigger')->once()->with(HttpEnd::class, $response);
+ $this->app->shouldReceive('get')->once()->with('event')->andReturn($event);
+ $log = m::mock(Log::class);
+ $log->shouldReceive('save')->once();
+ $this->app->shouldReceive('get')->once()->with('log')->andReturn($log);
+
+ $this->http->end($response);
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/InteractsWithApp.php b/vendor/topthink/framework/tests/InteractsWithApp.php
new file mode 100755
index 0000000..f4fcf73
--- /dev/null
+++ b/vendor/topthink/framework/tests/InteractsWithApp.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace think\tests;
+
+use Mockery as m;
+use Mockery\MockInterface;
+use think\App;
+use think\Config;
+use think\Container;
+
+trait InteractsWithApp
+{
+ /** @var App|MockInterface */
+ protected $app;
+
+ /** @var Config|MockInterface */
+ protected $config;
+
+ protected function prepareApp()
+ {
+ $this->app = m::mock(App::class)->makePartial();
+ Container::setInstance($this->app);
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->app->shouldReceive('isDebug')->andReturnTrue();
+ $this->config = m::mock(Config::class)->makePartial();
+ $this->config->shouldReceive('get')->with('app.show_error_msg')->andReturnTrue();
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+ $this->app->shouldReceive('runningInConsole')->andReturn(false);
+ }
+}
diff --git a/vendor/topthink/framework/tests/LogTest.php b/vendor/topthink/framework/tests/LogTest.php
new file mode 100755
index 0000000..981110f
--- /dev/null
+++ b/vendor/topthink/framework/tests/LogTest.php
@@ -0,0 +1,130 @@
+<?php
+
+namespace think\tests;
+
+use InvalidArgumentException;
+use Mockery as m;
+use Mockery\MockInterface;
+use org\bovigo\vfs\vfsStream;
+use PHPUnit\Framework\TestCase;
+use think\Log;
+use think\log\ChannelSet;
+
+class LogTest extends TestCase
+{
+ use InteractsWithApp;
+
+ /** @var Log|MockInterface */
+ protected $log;
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ protected function setUp()
+ {
+ $this->prepareApp();
+
+ $this->log = new Log($this->app);
+ }
+
+ public function testGetConfig()
+ {
+ $config = [
+ 'default' => 'file',
+ ];
+
+ $this->config->shouldReceive('get')->with('log')->andReturn($config);
+
+ $this->assertEquals($config, $this->log->getConfig());
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->log->getChannelConfig('foo');
+ }
+
+ public function testChannel()
+ {
+ $this->assertInstanceOf(ChannelSet::class, $this->log->channel(['file', 'mail']));
+ }
+
+ public function testLogManagerInstances()
+ {
+ $this->config->shouldReceive('get')->with("log.channels.single", null)->andReturn(['type' => 'file']);
+
+ $channel1 = $this->log->channel('single');
+ $channel2 = $this->log->channel('single');
+
+ $this->assertSame($channel1, $channel2);
+ }
+
+ public function testFileLog()
+ {
+ $root = vfsStream::setup();
+
+ $this->config->shouldReceive('get')->with("log.default", null)->andReturn('file');
+
+ $this->config->shouldReceive('get')->with("log.channels.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]);
+
+ $this->log->info('foo');
+
+ $this->assertEquals($this->log->getLog(), ['info' => ['foo']]);
+
+ $this->log->clear();
+
+ $this->assertEmpty($this->log->getLog());
+
+ $this->log->error('foo');
+ $this->assertArrayHasKey('error', $this->log->getLog());
+
+ $this->log->emergency('foo');
+ $this->assertArrayHasKey('emergency', $this->log->getLog());
+
+ $this->log->alert('foo');
+ $this->assertArrayHasKey('alert', $this->log->getLog());
+
+ $this->log->critical('foo');
+ $this->assertArrayHasKey('critical', $this->log->getLog());
+
+ $this->log->warning('foo');
+ $this->assertArrayHasKey('warning', $this->log->getLog());
+
+ $this->log->notice('foo');
+ $this->assertArrayHasKey('notice', $this->log->getLog());
+
+ $this->log->debug('foo');
+ $this->assertArrayHasKey('debug', $this->log->getLog());
+
+ $this->log->sql('foo');
+ $this->assertArrayHasKey('sql', $this->log->getLog());
+
+ $this->log->custom('foo');
+ $this->assertArrayHasKey('custom', $this->log->getLog());
+
+ $this->log->write('foo');
+ $this->assertTrue($root->hasChildren());
+ $this->assertEmpty($this->log->getLog());
+
+ $this->log->close();
+
+ $this->log->info('foo');
+
+ $this->assertEmpty($this->log->getLog());
+ }
+
+ public function testSave()
+ {
+ $root = vfsStream::setup();
+
+ $this->config->shouldReceive('get')->with("log.default", null)->andReturn('file');
+
+ $this->config->shouldReceive('get')->with("log.channels.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]);
+
+ $this->log->info('foo');
+
+ $this->log->save();
+
+ $this->assertTrue($root->hasChildren());
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/MiddlewareTest.php b/vendor/topthink/framework/tests/MiddlewareTest.php
new file mode 100755
index 0000000..aa53059
--- /dev/null
+++ b/vendor/topthink/framework/tests/MiddlewareTest.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace think\tests;
+
+use Mockery as m;
+use Mockery\MockInterface;
+use PHPUnit\Framework\TestCase;
+use think\Exception;
+use think\exception\Handle;
+use think\Middleware;
+use think\Pipeline;
+use think\Request;
+use think\Response;
+
+class MiddlewareTest extends TestCase
+{
+ use InteractsWithApp;
+
+ /** @var Middleware|MockInterface */
+ protected $middleware;
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ protected function setUp()
+ {
+ $this->prepareApp();
+
+ $this->middleware = new Middleware($this->app);
+ }
+
+ public function testSetMiddleware()
+ {
+ $this->middleware->add('BarMiddleware', 'bar');
+
+ $this->assertEquals(1, count($this->middleware->all('bar')));
+
+ $this->middleware->controller('BarMiddleware');
+ $this->assertEquals(1, count($this->middleware->all('controller')));
+
+ $this->middleware->import(['FooMiddleware']);
+ $this->assertEquals(1, count($this->middleware->all()));
+
+ $this->middleware->unshift(['BazMiddleware', 'baz']);
+ $this->assertEquals(2, count($this->middleware->all()));
+ $this->assertEquals([['BazMiddleware', 'handle'], 'baz'], $this->middleware->all()[0]);
+
+ $this->config->shouldReceive('get')->with('middleware.alias', [])->andReturn(['foo' => ['FooMiddleware', 'FarMiddleware']]);
+
+ $this->middleware->add('foo');
+ $this->assertEquals(3, count($this->middleware->all()));
+ $this->middleware->add(function () {
+ });
+ $this->middleware->add(function () {
+ });
+ $this->assertEquals(5, count($this->middleware->all()));
+ }
+
+ public function testPipelineAndEnd()
+ {
+ $bar = m::mock("overload:BarMiddleware");
+ $foo = m::mock("overload:FooMiddleware", Foo::class);
+
+ $request = m::mock(Request::class);
+ $response = m::mock(Response::class);
+
+ $e = new Exception();
+
+ $handle = m::mock(Handle::class);
+ $handle->shouldReceive('report')->with($e)->andReturnNull();
+ $handle->shouldReceive('render')->with($request, $e)->andReturn($response);
+
+ $foo->shouldReceive('handle')->once()->andReturnUsing(function ($request, $next) {
+ return $next($request);
+ });
+ $bar->shouldReceive('handle')->once()->andReturnUsing(function ($request, $next) use ($e) {
+ $next($request);
+ throw $e;
+ });
+
+ $foo->shouldReceive('end')->once()->with($response)->andReturnNull();
+
+ $this->app->shouldReceive('make')->with(Handle::class)->andReturn($handle);
+
+ $this->config->shouldReceive('get')->once()->with('middleware.priority', [])->andReturn(['FooMiddleware', 'BarMiddleware']);
+
+ $this->middleware->import([function ($request, $next) {
+ return $next($request);
+ }, 'BarMiddleware', 'FooMiddleware']);
+
+ $this->assertInstanceOf(Pipeline::class, $pipeline = $this->middleware->pipeline());
+
+ $pipeline->send($request)->then(function ($request) use ($e, $response) {
+ throw $e;
+ });
+
+ $this->middleware->end($response);
+ }
+}
+
+class Foo
+{
+ public function end(Response $response)
+ {
+ }
+}
diff --git a/vendor/topthink/framework/tests/RouteTest.php b/vendor/topthink/framework/tests/RouteTest.php
new file mode 100755
index 0000000..6a7f8ee
--- /dev/null
+++ b/vendor/topthink/framework/tests/RouteTest.php
@@ -0,0 +1,291 @@
+<?php
+
+namespace think\tests;
+
+use Closure;
+use Mockery as m;
+use Mockery\MockInterface;
+use PHPUnit\Framework\TestCase;
+use think\helper\Str;
+use think\Request;
+use think\response\Redirect;
+use think\response\View;
+use think\Route;
+
+class RouteTest extends TestCase
+{
+ use InteractsWithApp;
+
+ /** @var Route|MockInterface */
+ protected $route;
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ protected function setUp()
+ {
+ $this->prepareApp();
+ $this->route = new Route($this->app);
+ }
+
+ /**
+ * @param $path
+ * @param string $method
+ * @param string $host
+ * @return m\Mock|Request
+ */
+ protected function makeRequest($path, $method = 'GET', $host = 'localhost')
+ {
+ $request = m::mock(Request::class)->makePartial();
+ $request->shouldReceive('host')->andReturn($host);
+ $request->shouldReceive('pathinfo')->andReturn($path);
+ $request->shouldReceive('url')->andReturn('/' . $path);
+ $request->shouldReceive('method')->andReturn(strtoupper($method));
+ return $request;
+ }
+
+ public function testSimpleRequest()
+ {
+ $this->route->get('foo', function () {
+ return 'get-foo';
+ });
+
+ $this->route->put('foo', function () {
+ return 'put-foo';
+ });
+
+ $this->route->group(function () {
+ $this->route->post('foo', function () {
+ return 'post-foo';
+ });
+ });
+
+ $request = $this->makeRequest('foo', 'post');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(200, $response->getCode());
+ $this->assertEquals('post-foo', $response->getContent());
+
+ $request = $this->makeRequest('foo', 'get');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(200, $response->getCode());
+ $this->assertEquals('get-foo', $response->getContent());
+ }
+
+ public function testOptionsRequest()
+ {
+ $this->route->get('foo', function () {
+ return 'get-foo';
+ });
+
+ $this->route->put('foo', function () {
+ return 'put-foo';
+ });
+
+ $this->route->group(function () {
+ $this->route->post('foo', function () {
+ return 'post-foo';
+ });
+ });
+ $this->route->group('abc', function () {
+ $this->route->post('foo/:id', function () {
+ return 'post-abc-foo';
+ });
+ });
+
+ $this->route->post('foo/:id', function () {
+ return 'post-abc-foo';
+ });
+
+ $this->route->resource('bar', 'SomeClass');
+
+ $request = $this->makeRequest('foo', 'options');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(204, $response->getCode());
+ $this->assertEquals('GET, PUT, POST', $response->getHeader('Allow'));
+
+ $request = $this->makeRequest('bar', 'options');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(204, $response->getCode());
+ $this->assertEquals('GET, POST', $response->getHeader('Allow'));
+
+ $request = $this->makeRequest('bar/1', 'options');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(204, $response->getCode());
+ $this->assertEquals('GET, PUT, DELETE', $response->getHeader('Allow'));
+
+ $request = $this->makeRequest('xxxx', 'options');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(204, $response->getCode());
+ $this->assertEquals('GET, POST, PUT, DELETE', $response->getHeader('Allow'));
+ }
+
+ public function testAllowCrossDomain()
+ {
+ $this->route->get('foo', function () {
+ return 'get-foo';
+ })->allowCrossDomain(['some' => 'bar']);
+
+ $request = $this->makeRequest('foo', 'get');
+ $response = $this->route->dispatch($request);
+
+ $this->assertEquals('bar', $response->getHeader('some'));
+ $this->assertArrayHasKey('Access-Control-Allow-Credentials', $response->getHeader());
+
+ $request = $this->makeRequest('foo2', 'options');
+ $response = $this->route->dispatch($request);
+
+ $this->assertEquals(204, $response->getCode());
+ $this->assertArrayHasKey('Access-Control-Allow-Credentials', $response->getHeader());
+ $this->assertEquals('GET, POST, PUT, DELETE', $response->getHeader('Allow'));
+ }
+
+ public function testControllerDispatch()
+ {
+ $this->route->get('foo', 'foo/bar');
+
+ $controller = m::Mock(\stdClass::class);
+
+ $this->app->shouldReceive('parseClass')->with('controller', 'Foo')->andReturn($controller->mockery_getName());
+ $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
+
+ $controller->shouldReceive('bar')->andReturn('bar');
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals('bar', $response->getContent());
+ }
+
+ public function testEmptyControllerDispatch()
+ {
+ $this->route->get('foo', 'foo/bar');
+
+ $controller = m::Mock(\stdClass::class);
+
+ $this->app->shouldReceive('parseClass')->with('controller', 'Error')->andReturn($controller->mockery_getName());
+ $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
+
+ $controller->shouldReceive('bar')->andReturn('bar');
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals('bar', $response->getContent());
+ }
+
+ protected function createMiddleware($times = 1)
+ {
+ $middleware = m::mock(Str::random(5));
+ $middleware->shouldReceive('handle')->times($times)->andReturnUsing(function ($request, Closure $next) {
+ return $next($request);
+ });
+ $this->app->shouldReceive('make')->with($middleware->mockery_getName())->andReturn($middleware);
+
+ return $middleware;
+ }
+
+ public function testControllerWithMiddleware()
+ {
+ $this->route->get('foo', 'foo/bar');
+
+ $controller = m::mock(FooClass::class);
+
+ $controller->middleware = [
+ $this->createMiddleware()->mockery_getName() . ":params1:params2",
+ $this->createMiddleware(0)->mockery_getName() => ['except' => 'bar'],
+ $this->createMiddleware()->mockery_getName() => ['only' => 'bar'],
+ [
+ 'middleware' => [$this->createMiddleware()->mockery_getName(), [new \stdClass()]],
+ 'options' => ['only' => 'bar'],
+ ],
+ ];
+
+ $this->app->shouldReceive('parseClass')->with('controller', 'Foo')->andReturn($controller->mockery_getName());
+ $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
+
+ $controller->shouldReceive('bar')->once()->andReturn('bar');
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals('bar', $response->getContent());
+ }
+
+ public function testUrlDispatch()
+ {
+ $controller = m::mock(FooClass::class);
+ $controller->shouldReceive('index')->andReturn('bar');
+
+ $this->app->shouldReceive('parseClass')->once()->with('controller', 'Foo')
+ ->andReturn($controller->mockery_getName());
+ $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals('bar', $response->getContent());
+ }
+
+ public function testRedirectDispatch()
+ {
+ $this->route->redirect('foo', 'http://localhost', 302);
+
+ $request = $this->makeRequest('foo');
+ $this->app->shouldReceive('make')->with(Request::class)->andReturn($request);
+ $response = $this->route->dispatch($request);
+
+ $this->assertInstanceOf(Redirect::class, $response);
+ $this->assertEquals(302, $response->getCode());
+ $this->assertEquals('http://localhost', $response->getData());
+ }
+
+ public function testViewDispatch()
+ {
+ $this->route->view('foo', 'index/hello', ['city' => 'shanghai']);
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+
+ $this->assertInstanceOf(View::class, $response);
+ $this->assertEquals(['city' => 'shanghai'], $response->getVars());
+ $this->assertEquals('index/hello', $response->getData());
+ }
+
+ public function testResponseDispatch()
+ {
+ $this->route->get('hello/:name', response()
+ ->data('Hello,ThinkPHP')
+ ->code(200)
+ ->contentType('text/plain'));
+
+ $request = $this->makeRequest('hello/some');
+ $response = $this->route->dispatch($request);
+
+ $this->assertEquals('Hello,ThinkPHP', $response->getContent());
+ $this->assertEquals(200, $response->getCode());
+ }
+
+ public function testDomainBindResponse()
+ {
+ $this->route->domain('test', function () {
+ $this->route->get('/', function () {
+ return 'Hello,ThinkPHP';
+ });
+ });
+
+ $request = $this->makeRequest('', 'get', 'test.domain.com');
+ $response = $this->route->dispatch($request);
+
+ $this->assertEquals('Hello,ThinkPHP', $response->getContent());
+ $this->assertEquals(200, $response->getCode());
+ }
+
+}
+
+class FooClass
+{
+ public $middleware = [];
+
+ public function bar()
+ {
+
+ }
+}
diff --git a/vendor/topthink/framework/tests/SessionTest.php b/vendor/topthink/framework/tests/SessionTest.php
new file mode 100755
index 0000000..b3b48a7
--- /dev/null
+++ b/vendor/topthink/framework/tests/SessionTest.php
@@ -0,0 +1,225 @@
+<?php
+
+namespace think\tests;
+
+use Mockery as m;
+use Mockery\MockInterface;
+use org\bovigo\vfs\vfsStream;
+use PHPUnit\Framework\TestCase;
+use think\App;
+use think\cache\Driver;
+use think\Config;
+use think\Container;
+use think\contract\SessionHandlerInterface;
+use think\helper\Str;
+use think\Session;
+use think\session\driver\Cache;
+use think\session\driver\File;
+
+class SessionTest extends TestCase
+{
+ /** @var App|MockInterface */
+ protected $app;
+
+ /** @var Session|MockInterface */
+ protected $session;
+
+ /** @var Config|MockInterface */
+ protected $config;
+
+ protected $handler;
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ protected function setUp()
+ {
+ $this->app = m::mock(App::class)->makePartial();
+ Container::setInstance($this->app);
+
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class)->makePartial();
+
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+ $handlerClass = "\\think\\session\\driver\\Test" . Str::random(10);
+ $this->config->shouldReceive("get")->with("session.type", "file")->andReturn($handlerClass);
+ $this->session = new Session($this->app);
+
+ $this->handler = m::mock('overload:' . $handlerClass, SessionHandlerInterface::class);
+ }
+
+ public function testLoadData()
+ {
+ $data = [
+ "bar" => 'foo',
+ ];
+
+ $id = md5(uniqid());
+
+ $this->handler->shouldReceive("read")->once()->with($id)->andReturn(serialize($data));
+
+ $this->session->setId($id);
+ $this->session->init();
+
+ $this->assertEquals('foo', $this->session->get('bar'));
+ $this->assertTrue($this->session->has('bar'));
+ $this->assertFalse($this->session->has('foo'));
+
+ $this->session->set('foo', 'bar');
+ $this->assertTrue($this->session->has('foo'));
+
+ $this->assertEquals('bar', $this->session->pull('foo'));
+ $this->assertFalse($this->session->has('foo'));
+ }
+
+ public function testSave()
+ {
+
+ $id = md5(uniqid());
+
+ $this->handler->shouldReceive('read')->once()->with($id)->andReturn("");
+
+ $this->handler->shouldReceive('write')->once()->with($id, serialize([
+ "bar" => 'foo',
+ ]))->andReturnTrue();
+
+ $this->session->setId($id);
+ $this->session->init();
+
+ $this->session->set('bar', 'foo');
+
+ $this->session->save();
+ }
+
+ public function testFlash()
+ {
+ $this->session->flash('foo', 'bar');
+ $this->session->flash('bar', 0);
+ $this->session->flash('baz', true);
+
+ $this->assertTrue($this->session->has('foo'));
+ $this->assertEquals('bar', $this->session->get('foo'));
+ $this->assertEquals(0, $this->session->get('bar'));
+ $this->assertTrue($this->session->get('baz'));
+
+ $this->session->clearFlashData();
+
+ $this->assertTrue($this->session->has('foo'));
+ $this->assertEquals('bar', $this->session->get('foo'));
+ $this->assertEquals(0, $this->session->get('bar'));
+
+ $this->session->clearFlashData();
+
+ $this->assertFalse($this->session->has('foo'));
+ $this->assertNull($this->session->get('foo'));
+
+ $this->session->flash('foo', 'bar');
+ $this->assertTrue($this->session->has('foo'));
+ $this->session->clearFlashData();
+ $this->session->reflash();
+ $this->session->clearFlashData();
+
+ $this->assertTrue($this->session->has('foo'));
+ }
+
+ public function testClear()
+ {
+ $this->session->set('bar', 'foo');
+ $this->assertEquals('foo', $this->session->get('bar'));
+ $this->session->clear();
+ $this->assertFalse($this->session->has('foo'));
+ }
+
+ public function testSetName()
+ {
+ $this->session->setName('foo');
+ $this->assertEquals('foo', $this->session->getName());
+ }
+
+ public function testDestroy()
+ {
+ $id = md5(uniqid());
+
+ $this->handler->shouldReceive('read')->once()->with($id)->andReturn("");
+ $this->handler->shouldReceive('delete')->once()->with($id)->andReturnTrue();
+
+ $this->session->setId($id);
+ $this->session->init();
+
+ $this->session->set('bar', 'foo');
+
+ $this->session->destroy();
+
+ $this->assertFalse($this->session->has('bar'));
+
+ $this->assertNotEquals($id, $this->session->getId());
+ }
+
+ public function testFileHandler()
+ {
+ $root = vfsStream::setup();
+
+ vfsStream::newFile('bar')
+ ->at($root)
+ ->lastModified(time());
+
+ vfsStream::newFile('bar')
+ ->at(vfsStream::newDirectory("foo")->at($root))
+ ->lastModified(100);
+
+ $this->assertTrue($root->hasChild("bar"));
+ $this->assertTrue($root->hasChild("foo/bar"));
+
+ $handler = new TestFileHandle($this->app, [
+ 'path' => $root->url(),
+ 'gc_probability' => 1,
+ 'gc_divisor' => 1,
+ ]);
+
+ $this->assertTrue($root->hasChild("bar"));
+ $this->assertFalse($root->hasChild("foo/bar"));
+
+ $id = md5(uniqid());
+ $handler->write($id, "bar");
+
+ $this->assertTrue($root->hasChild("sess_{$id}"));
+
+ $this->assertEquals("bar", $handler->read($id));
+
+ $handler->delete($id);
+
+ $this->assertFalse($root->hasChild("sess_{$id}"));
+ }
+
+ public function testCacheHandler()
+ {
+ $id = md5(uniqid());
+
+ $cache = m::mock(\think\Cache::class);
+
+ $store = m::mock(Driver::class);
+
+ $cache->shouldReceive('store')->once()->with('redis')->andReturn($store);
+
+ $handler = new Cache($cache, ['store' => 'redis']);
+
+ $store->shouldReceive("set")->with($id, "bar", 1440)->once()->andReturnTrue();
+ $handler->write($id, "bar");
+
+ $store->shouldReceive("get")->with($id)->once()->andReturn("bar");
+ $this->assertEquals("bar", $handler->read($id));
+
+ $store->shouldReceive("delete")->with($id)->once()->andReturnTrue();
+ $handler->delete($id);
+ }
+}
+
+class TestFileHandle extends File
+{
+ protected function writeFile($path, $content): bool
+ {
+ return (bool) file_put_contents($path, $content);
+ }
+}
diff --git a/vendor/topthink/framework/tests/ViewTest.php b/vendor/topthink/framework/tests/ViewTest.php
new file mode 100755
index 0000000..e413510
--- /dev/null
+++ b/vendor/topthink/framework/tests/ViewTest.php
@@ -0,0 +1,127 @@
+<?php
+
+namespace think\tests;
+
+use Mockery\MockInterface;
+use PHPUnit\Framework\TestCase;
+use think\App;
+use think\Config;
+use think\Container;
+use think\contract\TemplateHandlerInterface;
+use think\View;
+use Mockery as m;
+
+class ViewTest extends TestCase
+{
+ /** @var App|MockInterface */
+ protected $app;
+
+ /** @var View|MockInterface */
+ protected $view;
+
+ /** @var Config|MockInterface */
+ protected $config;
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ protected function setUp()
+ {
+ $this->app = m::mock(App::class)->makePartial();
+ Container::setInstance($this->app);
+
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class)->makePartial();
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+
+ $this->view = new View($this->app);
+ }
+
+ public function testAssignData()
+ {
+ $this->view->assign('foo', 'bar');
+ $this->view->assign(['baz' => 'boom']);
+ $this->view->qux = "corge";
+
+ $this->assertEquals('bar', $this->view->foo);
+ $this->assertEquals('boom', $this->view->baz);
+ $this->assertEquals('corge', $this->view->qux);
+ $this->assertTrue(isset($this->view->qux));
+ }
+
+ public function testRender()
+ {
+ $this->config->shouldReceive("get")->with("view.type", 'php')->andReturn(TestTemplate::class);
+
+ $this->view->filter(function ($content) {
+ return $content;
+ });
+
+ $this->assertEquals("fetch", $this->view->fetch('foo'));
+ $this->assertEquals("display", $this->view->display('foo'));
+ }
+
+}
+
+class TestTemplate implements TemplateHandlerInterface
+{
+
+ /**
+ * 检测是否存在模板文件
+ * @access public
+ * @param string $template 模板文件或者模板规则
+ * @return bool
+ */
+ public function exists(string $template): bool
+ {
+ return true;
+ }
+
+ /**
+ * 渲染模板文件
+ * @access public
+ * @param string $template 模板文件
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function fetch(string $template, array $data = []): void
+ {
+ echo "fetch";
+ }
+
+ /**
+ * 渲染模板内容
+ * @access public
+ * @param string $content 模板内容
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function display(string $content, array $data = []): void
+ {
+ echo "display";
+ }
+
+ /**
+ * 配置模板引擎
+ * @access private
+ * @param array $config 参数
+ * @return void
+ */
+ public function config(array $config): void
+ {
+ // TODO: Implement config() method.
+ }
+
+ /**
+ * 获取模板引擎配置
+ * @access public
+ * @param string $name 参数名
+ * @return void
+ */
+ public function getConfig(string $name)
+ {
+ // TODO: Implement getConfig() method.
+ }
+}
diff --git a/vendor/topthink/framework/tests/bootstrap.php b/vendor/topthink/framework/tests/bootstrap.php
new file mode 100755
index 0000000..3459061
--- /dev/null
+++ b/vendor/topthink/framework/tests/bootstrap.php
@@ -0,0 +1,3 @@
+<?php
+
+include __DIR__.'/../src/helper.php';
diff --git a/vendor/topthink/think-captcha/.gitignore b/vendor/topthink/think-captcha/.gitignore
new file mode 100755
index 0000000..85d49cb
--- /dev/null
+++ b/vendor/topthink/think-captcha/.gitignore
@@ -0,0 +1,3 @@
+/vendor/
+/composer.lock
+.idea
\ No newline at end of file
diff --git a/vendor/topthink/think-captcha/LICENSE b/vendor/topthink/think-captcha/LICENSE
new file mode 100755
index 0000000..835ce60
--- /dev/null
+++ b/vendor/topthink/think-captcha/LICENSE
@@ -0,0 +1,32 @@
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn)
+All rights reserved。
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+Apache Licence是著名的非盈利开源组织Apache采用的协议。
+该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
+允许代码修改,再作为开源或商业软件发布。需要满足
+的条件:
+1. 需要给代码的用户一份Apache Licence ;
+2. 如果你修改了代码,需要在被修改的文件中说明;
+3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
+带有原来代码中的协议,商标,专利声明和其他原来作者规
+定需要包含的说明;
+4. 如果再发布的产品中包含一个Notice文件,则在Notice文
+件中需要带有本协议内容。你可以在Notice中增加自己的
+许可,但不可以表现为对Apache Licence构成更改。
+具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/topthink/think-captcha/README.md b/vendor/topthink/think-captcha/README.md
new file mode 100755
index 0000000..cf1a06a
--- /dev/null
+++ b/vendor/topthink/think-captcha/README.md
@@ -0,0 +1,57 @@
+# think-captcha
+
+thinkphp6 验证码类库
+
+## 安装
+> composer require topthink/think-captcha
+
+
+
+## 使用
+
+### 在控制器中输出验证码
+
+在控制器的操作方法中使用
+
+~~~
+public function captcha($id = '')
+{
+ return captcha($id);
+}
+~~~
+然后注册对应的路由来输出验证码
+
+
+### 模板里输出验证码
+
+首先要在你应用的路由定义文件中,注册一个验证码路由规则。
+
+~~~
+\think\facade\Route::get('captcha/[:id]', "\\think\\captcha\\CaptchaController@index");
+~~~
+
+然后就可以在模板文件中使用
+~~~
+<div>{:captcha_img()}</div>
+~~~
+或者
+~~~
+<div><img src="{:captcha_src()}" alt="captcha" /></div>
+~~~
+> 上面两种的最终效果是一样的
+
+
+### 控制器里验证
+
+使用TP的内置验证功能即可
+~~~
+$this->validate($data,[
+ 'captcha|验证码'=>'require|captcha'
+]);
+~~~
+或者手动验证
+~~~
+if(!captcha_check($captcha)){
+ //验证失败
+};
+~~~
\ No newline at end of file
diff --git a/vendor/topthink/think-captcha/assets/bgs/1.jpg b/vendor/topthink/think-captcha/assets/bgs/1.jpg
new file mode 100755
index 0000000..d417136
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/bgs/1.jpg
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/bgs/2.jpg b/vendor/topthink/think-captcha/assets/bgs/2.jpg
new file mode 100755
index 0000000..56640bd
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/bgs/2.jpg
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/bgs/3.jpg b/vendor/topthink/think-captcha/assets/bgs/3.jpg
new file mode 100755
index 0000000..83e5bd9
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/bgs/3.jpg
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/bgs/4.jpg b/vendor/topthink/think-captcha/assets/bgs/4.jpg
new file mode 100755
index 0000000..97a3721
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/bgs/4.jpg
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/bgs/5.jpg b/vendor/topthink/think-captcha/assets/bgs/5.jpg
new file mode 100755
index 0000000..220a17a
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/bgs/5.jpg
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/bgs/6.jpg b/vendor/topthink/think-captcha/assets/bgs/6.jpg
new file mode 100755
index 0000000..be53ea0
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/bgs/6.jpg
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/bgs/7.jpg b/vendor/topthink/think-captcha/assets/bgs/7.jpg
new file mode 100755
index 0000000..fbf537f
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/bgs/7.jpg
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/bgs/8.jpg b/vendor/topthink/think-captcha/assets/bgs/8.jpg
new file mode 100755
index 0000000..e10cf28
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/bgs/8.jpg
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/ttfs/1.ttf b/vendor/topthink/think-captcha/assets/ttfs/1.ttf
new file mode 100755
index 0000000..9eae6f2
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/ttfs/1.ttf
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/ttfs/2.ttf b/vendor/topthink/think-captcha/assets/ttfs/2.ttf
new file mode 100755
index 0000000..6386c6b
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/ttfs/2.ttf
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/ttfs/3.ttf b/vendor/topthink/think-captcha/assets/ttfs/3.ttf
new file mode 100755
index 0000000..678a491
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/ttfs/3.ttf
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/ttfs/4.ttf b/vendor/topthink/think-captcha/assets/ttfs/4.ttf
new file mode 100755
index 0000000..db43334
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/ttfs/4.ttf
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/ttfs/5.ttf b/vendor/topthink/think-captcha/assets/ttfs/5.ttf
new file mode 100755
index 0000000..8c082c8
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/ttfs/5.ttf
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/ttfs/6.ttf b/vendor/topthink/think-captcha/assets/ttfs/6.ttf
new file mode 100755
index 0000000..45a038b
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/ttfs/6.ttf
Binary files differ
diff --git a/vendor/topthink/think-captcha/assets/zhttfs/1.ttf b/vendor/topthink/think-captcha/assets/zhttfs/1.ttf
new file mode 100755
index 0000000..1c14f7f
--- /dev/null
+++ b/vendor/topthink/think-captcha/assets/zhttfs/1.ttf
Binary files differ
diff --git a/vendor/topthink/think-captcha/composer.json b/vendor/topthink/think-captcha/composer.json
new file mode 100755
index 0000000..8946486
--- /dev/null
+++ b/vendor/topthink/think-captcha/composer.json
@@ -0,0 +1,30 @@
+{
+ "name": "topthink/think-captcha",
+ "description": "captcha package for thinkphp",
+ "authors": [
+ {
+ "name": "yunwuxin",
+ "email": "448901948@qq.com"
+ }
+ ],
+ "license": "Apache-2.0",
+ "require": {
+ "topthink/framework": "^6.0.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\captcha\\": "src/"
+ },
+ "files": [
+ "src/helper.php"
+ ]
+ },
+ "extra": {
+ "think": {
+ "services": [
+ "think\\captcha\\CaptchaService"
+ ]
+ }
+ },
+ "minimum-stability": "dev"
+}
diff --git a/vendor/topthink/think-captcha/src/Captcha.php b/vendor/topthink/think-captcha/src/Captcha.php
new file mode 100755
index 0000000..ea396ad
--- /dev/null
+++ b/vendor/topthink/think-captcha/src/Captcha.php
@@ -0,0 +1,339 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\captcha;
+
+use Exception;
+use think\Config;
+use think\Response;
+use think\Session;
+
+class Captcha
+{
+ private $im = null; // 验证码图片实例
+ private $color = null; // 验证码字体颜色
+
+ /**
+ * @var Config|null
+ */
+ private $config = null;
+
+ /**
+ * @var Session|null
+ */
+ private $session = null;
+
+ // 验证码字符集合
+ protected $codeSet = '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY';
+ // 验证码过期时间(s)
+ protected $expire = 1800;
+ // 使用中文验证码
+ protected $useZh = false;
+ // 中文验证码字符串
+ protected $zhSet = '们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书术状厂须离再目海交权且儿青才证低越际八试规斯近注办布门铁需走议县兵固除般引齿千胜细影济白格效置推空配刀叶率述今选养德话查差半敌始片施响收华觉备名红续均药标记难存测士身紧液派准斤角降维板许破述技消底床田势端感往神便贺村构照容非搞亚磨族火段算适讲按值美态黄易彪服早班麦削信排台声该击素张密害侯草何树肥继右属市严径螺检左页抗苏显苦英快称坏移约巴材省黑武培著河帝仅针怎植京助升王眼她抓含苗副杂普谈围食射源例致酸旧却充足短划剂宣环落首尺波承粉践府鱼随考刻靠够满夫失包住促枝局菌杆周护岩师举曲春元超负砂封换太模贫减阳扬江析亩木言球朝医校古呢稻宋听唯输滑站另卫字鼓刚写刘微略范供阿块某功套友限项余倒卷创律雨让骨远帮初皮播优占死毒圈伟季训控激找叫云互跟裂粮粒母练塞钢顶策双留误础吸阻故寸盾晚丝女散焊功株亲院冷彻弹错散商视艺灭版烈零室轻血倍缺厘泵察绝富城冲喷壤简否柱李望盘磁雄似困巩益洲脱投送奴侧润盖挥距触星松送获兴独官混纪依未突架宽冬章湿偏纹吃执阀矿寨责熟稳夺硬价努翻奇甲预职评读背协损棉侵灰虽矛厚罗泥辟告卵箱掌氧恩爱停曾溶营终纲孟钱待尽俄缩沙退陈讨奋械载胞幼哪剥迫旋征槽倒握担仍呀鲜吧卡粗介钻逐弱脚怕盐末阴丰雾冠丙街莱贝辐肠付吉渗瑞惊顿挤秒悬姆烂森糖圣凹陶词迟蚕亿矩康遵牧遭幅园腔订香肉弟屋敏恢忘编印蜂急拿扩伤飞露核缘游振操央伍域甚迅辉异序免纸夜乡久隶缸夹念兰映沟乙吗儒杀汽磷艰晶插埃燃欢铁补咱芽永瓦倾阵碳演威附牙芽永瓦斜灌欧献顺猪洋腐请透司危括脉宜笑若尾束壮暴企菜穗楚汉愈绿拖牛份染既秋遍锻玉夏疗尖殖井费州访吹荣铜沿替滚客召旱悟刺脑措贯藏敢令隙炉壳硫煤迎铸粘探临薄旬善福纵择礼愿伏残雷延烟句纯渐耕跑泽慢栽鲁赤繁境潮横掉锥希池败船假亮谓托伙哲怀割摆贡呈劲财仪沉炼麻罪祖息车穿货销齐鼠抽画饲龙库守筑房歌寒喜哥洗蚀废纳腹乎录镜妇恶脂庄擦险赞钟摇典柄辩竹谷卖乱虚桥奥伯赶垂途额壁网截野遗静谋弄挂课镇妄盛耐援扎虑键归符庆聚绕摩忙舞遇索顾胶羊湖钉仁音迹碎伸灯避泛亡答勇频皇柳哈揭甘诺概宪浓岛袭谁洪谢炮浇斑讯懂灵蛋闭孩释乳巨徒私银伊景坦累匀霉杜乐勒隔弯绩招绍胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗碱殊岗挖氏刃剧堆赫荷胸衡勤膜篇登驻案刊秧缓凸役剪川雪链渔啦脸户洛孢勃盟买杨宗焦赛旗滤硅炭股坐蒸凝竟陷枪黎救冒暗洞犯筒您宋弧爆谬涂味津臂障褐陆啊健尊豆拔莫抵桑坡缝警挑污冰柬嘴啥饭塑寄赵喊垫丹渡耳刨虎笔稀昆浪萨茶滴浅拥穴覆伦娘吨浸袖珠雌妈紫戏塔锤震岁貌洁剖牢锋疑霸闪埔猛诉刷狠忽灾闹乔唐漏闻沈熔氯荒茎男凡抢像浆旁玻亦忠唱蒙予纷捕锁尤乘乌智淡允叛畜俘摸锈扫毕璃宝芯爷鉴秘净蒋钙肩腾枯抛轨堂拌爸循诱祝励肯酒绳穷塘燥泡袋朗喂铝软渠颗惯贸粪综墙趋彼届墨碍启逆卸航衣孙龄岭骗休借';
+ // 使用背景图片
+ protected $useImgBg = false;
+ // 验证码字体大小(px)
+ protected $fontSize = 25;
+ // 是否画混淆曲线
+ protected $useCurve = true;
+ // 是否添加杂点
+ protected $useNoise = true;
+ // 验证码图片高度
+ protected $imageH = 0;
+ // 验证码图片宽度
+ protected $imageW = 0;
+ // 验证码位数
+ protected $length = 5;
+ // 验证码字体,不设置随机获取
+ protected $fontttf = '';
+ // 背景颜色
+ protected $bg = [243, 251, 254];
+ //算术验证码
+ protected $math = false;
+
+ /**
+ * 架构方法 设置参数
+ * @access public
+ * @param Config $config
+ * @param Session $session
+ */
+ public function __construct(Config $config, Session $session)
+ {
+ $this->config = $config;
+ $this->session = $session;
+ }
+
+ /**
+ * 配置验证码
+ * @param string|null $config
+ */
+ protected function configure(string $config = null): void
+ {
+ if (is_null($config)) {
+ $config = $this->config->get('captcha', []);
+ } else {
+ $config = $this->config->get('captcha.' . $config, []);
+ }
+
+ foreach ($config as $key => $val) {
+ if (property_exists($this, $key)) {
+ $this->{$key} = $val;
+ }
+ }
+ }
+
+ /**
+ * 创建验证码
+ * @return array
+ * @throws Exception
+ */
+ protected function generate(): array
+ {
+ $bag = '';
+
+ if ($this->math) {
+ $this->useZh = false;
+
+ $x = random_int(10, 30);
+ $y = random_int(1, 9);
+ $bag = "{$x} + {$y} = ";
+ $key = $x + $y;
+ $key .= '';
+ } else {
+ if ($this->useZh) {
+ $characters = preg_split('/(?<!^)(?!$)/u', $this->zhSet);
+ } else {
+ $characters = str_split($this->codeSet);
+ }
+
+ for ($i = 0; $i < $this->length; $i++) {
+ $bag .= $characters[rand(0, count($characters) - 1)];
+ }
+
+ $key = mb_strtolower($bag, 'UTF-8');
+ }
+
+ $hash = password_hash($key, PASSWORD_BCRYPT, ['cost' => 10]);
+
+ $this->session->set('captcha', [
+ 'key' => $hash,
+ ]);
+
+ return [
+ 'value' => $bag,
+ 'key' => $hash,
+ ];
+ }
+
+ /**
+ * 验证验证码是否正确
+ * @access public
+ * @param string $code 用户验证码
+ * @return bool 用户验证码是否正确
+ */
+ public function check(string $code): bool
+ {
+ if (!$this->session->has('captcha')) {
+ return false;
+ }
+
+ $key = $this->session->get('captcha.key');
+
+ $code = mb_strtolower($code, 'UTF-8');
+
+ $res = password_verify($code, $key);
+
+ if ($res) {
+ $this->session->delete('captcha');
+ }
+
+ return $res;
+ }
+
+ /**
+ * 输出验证码并把验证码的值保存的session中
+ * @access public
+ * @param null|string $config
+ * @param bool $api
+ * @return Response
+ */
+ public function create(string $config = null, bool $api = false): Response
+ {
+ $this->configure($config);
+
+ $generator = $this->generate();
+
+ // 图片宽(px)
+ $this->imageW || $this->imageW = $this->length * $this->fontSize * 1.5 + $this->length * $this->fontSize / 2;
+ // 图片高(px)
+ $this->imageH || $this->imageH = $this->fontSize * 2.5;
+ // 建立一幅 $this->imageW x $this->imageH 的图像
+ $this->im = imagecreate($this->imageW, $this->imageH);
+ // 设置背景
+ imagecolorallocate($this->im, $this->bg[0], $this->bg[1], $this->bg[2]);
+
+ // 验证码字体随机颜色
+ $this->color = imagecolorallocate($this->im, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150));
+
+ // 验证码使用随机字体
+ $ttfPath = __DIR__ . '/../assets/' . ($this->useZh ? 'zhttfs' : 'ttfs') . '/';
+
+ if (empty($this->fontttf)) {
+ $dir = dir($ttfPath);
+ $ttfs = [];
+ while (false !== ($file = $dir->read())) {
+ if ('.' != $file[0] && substr($file, -4) == '.ttf') {
+ $ttfs[] = $file;
+ }
+ }
+ $dir->close();
+ $this->fontttf = $ttfs[array_rand($ttfs)];
+ }
+
+ $fontttf = $ttfPath . $this->fontttf;
+
+ if ($this->useImgBg) {
+ $this->background();
+ }
+
+ if ($this->useNoise) {
+ // 绘杂点
+ $this->writeNoise();
+ }
+ if ($this->useCurve) {
+ // 绘干扰线
+ $this->writeCurve();
+ }
+
+ // 绘验证码
+ $text = $this->useZh ? preg_split('/(?<!^)(?!$)/u', $generator['value']) : str_split($generator['value']); // 验证码
+
+ foreach ($text as $index => $char) {
+
+ $x = $this->fontSize * ($index + 1) * mt_rand(1.2, 1.6) * ($this->math ? 1 : 1.5);
+ $y = $this->fontSize + mt_rand(10, 20);
+ $angle = $this->math ? 0 : mt_rand(-40, 40);
+
+ imagettftext($this->im, $this->fontSize, $angle, $x, $y, $this->color, $fontttf, $char);
+ }
+
+ ob_start();
+ // 输出图像
+ imagepng($this->im);
+ $content = ob_get_clean();
+ imagedestroy($this->im);
+
+ return response($content, 200, ['Content-Length' => strlen($content)])->contentType('image/png');
+ }
+
+ /**
+ * 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数)
+ *
+ * 高中的数学公式咋都忘了涅,写出来
+ * 正弦型函数解析式:y=Asin(ωx+φ)+b
+ * 各常数值对函数图像的影响:
+ * A:决定峰值(即纵向拉伸压缩的倍数)
+ * b:表示波形在Y轴的位置关系或纵向移动距离(上加下减)
+ * φ:决定波形与X轴位置关系或横向移动距离(左加右减)
+ * ω:决定周期(最小正周期T=2π/∣ω∣)
+ *
+ */
+ protected function writeCurve(): void
+ {
+ $px = $py = 0;
+
+ // 曲线前部分
+ $A = mt_rand(1, $this->imageH / 2); // 振幅
+ $b = mt_rand(-$this->imageH / 4, $this->imageH / 4); // Y轴方向偏移量
+ $f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量
+ $T = mt_rand($this->imageH, $this->imageW * 2); // 周期
+ $w = (2 * M_PI) / $T;
+
+ $px1 = 0; // 曲线横坐标起始位置
+ $px2 = mt_rand($this->imageW / 2, $this->imageW * 0.8); // 曲线横坐标结束位置
+
+ for ($px = $px1; $px <= $px2; $px = $px + 1) {
+ if (0 != $w) {
+ $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b
+ $i = (int) ($this->fontSize / 5);
+ while ($i > 0) {
+ imagesetpixel($this->im, $px + $i, $py + $i, $this->color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出(不用这while循环)性能要好很多
+ $i--;
+ }
+ }
+ }
+
+ // 曲线后部分
+ $A = mt_rand(1, $this->imageH / 2); // 振幅
+ $f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量
+ $T = mt_rand($this->imageH, $this->imageW * 2); // 周期
+ $w = (2 * M_PI) / $T;
+ $b = $py - $A * sin($w * $px + $f) - $this->imageH / 2;
+ $px1 = $px2;
+ $px2 = $this->imageW;
+
+ for ($px = $px1; $px <= $px2; $px = $px + 1) {
+ if (0 != $w) {
+ $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b
+ $i = (int) ($this->fontSize / 5);
+ while ($i > 0) {
+ imagesetpixel($this->im, $px + $i, $py + $i, $this->color);
+ $i--;
+ }
+ }
+ }
+ }
+
+ /**
+ * 画杂点
+ * 往图片上写不同颜色的字母或数字
+ */
+ protected function writeNoise(): void
+ {
+ $codeSet = '2345678abcdefhijkmnpqrstuvwxyz';
+ for ($i = 0; $i < 10; $i++) {
+ //杂点颜色
+ $noiseColor = imagecolorallocate($this->im, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225));
+ for ($j = 0; $j < 5; $j++) {
+ // 绘杂点
+ imagestring($this->im, 5, mt_rand(-10, $this->imageW), mt_rand(-10, $this->imageH), $codeSet[mt_rand(0, 29)], $noiseColor);
+ }
+ }
+ }
+
+ /**
+ * 绘制背景图片
+ * 注:如果验证码输出图片比较大,将占用比较多的系统资源
+ */
+ protected function background(): void
+ {
+ $path = __DIR__ . '/../assets/bgs/';
+ $dir = dir($path);
+
+ $bgs = [];
+ while (false !== ($file = $dir->read())) {
+ if ('.' != $file[0] && substr($file, -4) == '.jpg') {
+ $bgs[] = $path . $file;
+ }
+ }
+ $dir->close();
+
+ $gb = $bgs[array_rand($bgs)];
+
+ list($width, $height) = @getimagesize($gb);
+ // Resample
+ $bgImage = @imagecreatefromjpeg($gb);
+ @imagecopyresampled($this->im, $bgImage, 0, 0, 0, 0, $this->imageW, $this->imageH, $width, $height);
+ @imagedestroy($bgImage);
+ }
+
+}
diff --git a/vendor/topthink/think-captcha/src/CaptchaController.php b/vendor/topthink/think-captcha/src/CaptchaController.php
new file mode 100755
index 0000000..2c3cf59
--- /dev/null
+++ b/vendor/topthink/think-captcha/src/CaptchaController.php
@@ -0,0 +1,20 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\captcha;
+
+class CaptchaController
+{
+ public function index(Captcha $captcha, $config = null)
+ {
+ return $captcha->create($config);
+ }
+}
diff --git a/vendor/topthink/think-captcha/src/CaptchaService.php b/vendor/topthink/think-captcha/src/CaptchaService.php
new file mode 100755
index 0000000..27f0ff0
--- /dev/null
+++ b/vendor/topthink/think-captcha/src/CaptchaService.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace think\captcha;
+
+use think\Route;
+use think\Service;
+use think\Validate;
+
+class CaptchaService extends Service
+{
+ public function boot(Route $route)
+ {
+ $route->get('captcha/[:config]', "\\think\\captcha\\CaptchaController@index");
+
+ Validate::maker(function ($validate) {
+ $validate->extend('captcha', function ($value) {
+ return captcha_check($value);
+ }, ':attribute错误!');
+ });
+ }
+}
diff --git a/vendor/topthink/think-captcha/src/facade/Captcha.php b/vendor/topthink/think-captcha/src/facade/Captcha.php
new file mode 100755
index 0000000..cd9f793
--- /dev/null
+++ b/vendor/topthink/think-captcha/src/facade/Captcha.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace think\captcha\facade;
+
+use think\Facade;
+
+/**
+ * Class Captcha
+ * @package think\captcha\facade
+ * @mixin \think\captcha\Captcha
+ */
+class Captcha extends Facade
+{
+ protected static function getFacadeClass()
+ {
+ return \think\captcha\Captcha::class;
+ }
+}
diff --git a/vendor/topthink/think-captcha/src/helper.php b/vendor/topthink/think-captcha/src/helper.php
new file mode 100755
index 0000000..e3c3ec2
--- /dev/null
+++ b/vendor/topthink/think-captcha/src/helper.php
@@ -0,0 +1,52 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+use think\captcha\facade\Captcha;
+use think\facade\Route;
+use think\Response;
+
+/**
+ * @param string $config
+ * @return \think\Response
+ */
+function captcha($config = null): Response
+{
+ return Captcha::create($config);
+}
+
+/**
+ * @param $config
+ * @return string
+ */
+function captcha_src($config = null): string
+{
+ return Route::buildUrl('/captcha' . ($config ? "/{$config}" : ''));
+}
+
+/**
+ * @param $id
+ * @return string
+ */
+function captcha_img($id = ''): string
+{
+ $src = captcha_src($id);
+
+ return "<img src='{$src}' alt='captcha' onclick='this.src=\"{$src}?s=\"+Math.random();' />";
+}
+
+/**
+ * @param string $value
+ * @return bool
+ */
+function captcha_check($value)
+{
+ return Captcha::check($value);
+}
diff --git a/vendor/topthink/think-factory/.gitignore b/vendor/topthink/think-factory/.gitignore
new file mode 100755
index 0000000..1ff7691
--- /dev/null
+++ b/vendor/topthink/think-factory/.gitignore
@@ -0,0 +1,4 @@
+# Created by .ignore support plugin (hsz.mobi)
+vendor
+composer.lock
+.idea
\ No newline at end of file
diff --git a/vendor/topthink/think-factory/LICENSE b/vendor/topthink/think-factory/LICENSE
new file mode 100755
index 0000000..8dada3e
--- /dev/null
+++ b/vendor/topthink/think-factory/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/topthink/think-factory/composer.json b/vendor/topthink/think-factory/composer.json
new file mode 100755
index 0000000..a764082
--- /dev/null
+++ b/vendor/topthink/think-factory/composer.json
@@ -0,0 +1,22 @@
+{
+ "name": "topthink/think-factory",
+ "type": "library",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "yunwuxin",
+ "email": "448901948@qq.com"
+ }
+ ],
+ "require": {
+ "topthink/think-helper": "^3.0.0",
+ "topthink/framework": "^6.0.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\": "src"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
+}
diff --git a/vendor/topthink/think-factory/src/Factory.php b/vendor/topthink/think-factory/src/Factory.php
new file mode 100755
index 0000000..18d0a10
--- /dev/null
+++ b/vendor/topthink/think-factory/src/Factory.php
@@ -0,0 +1,115 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use InvalidArgumentException;
+use think\helper\Str;
+
+abstract class Factory
+{
+ protected $app;
+
+ /**
+ * 驱动
+ * @var array
+ */
+ protected $drivers = [];
+
+ /**
+ * 驱动的命名空间
+ * @var string
+ */
+ protected $namespace = "";
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * 获取驱动实例
+ * @param null $driver
+ * @return mixed
+ */
+ public function driver($driver = null)
+ {
+ $driver = $driver ?: $this->getDefaultDriver();
+
+ if (is_null($driver)) {
+ throw new InvalidArgumentException(sprintf(
+ 'Unable to resolve NULL driver for [%s].', static::class
+ ));
+ }
+
+ if (!isset($this->drivers[$driver])) {
+ $this->drivers[$driver] = $this->createDriver($driver);
+ }
+
+ return $this->drivers[$driver];
+ }
+
+ /**
+ * 获取驱动类
+ * @param $driver
+ * @return string
+ */
+ protected function resolveClass($driver)
+ {
+ if ($this->namespace || false !== strpos($driver, '\\')) {
+ $class = false !== strpos($driver, '\\') ? $driver : $this->namespace . Str::studly($driver);
+
+ if (class_exists($class)) {
+ return $class;
+ }
+ }
+
+ throw new InvalidArgumentException("Driver [$driver] not supported.");
+ }
+
+ /**
+ * 创建驱动
+ *
+ * @param string $driver
+ * @return mixed
+ *
+ */
+ protected function createDriver($driver)
+ {
+ $method = 'create' . Str::studly($driver) . 'Driver';
+
+ if (method_exists($this, $method)) {
+ return $this->$method();
+ }
+
+ $class = $this->resolveClass($driver);
+
+ return $this->app->make($class);
+ }
+
+ /**
+ * 默认驱动
+ * @return string
+ */
+ abstract public function getDefaultDriver();
+
+ /**
+ * 动态调用
+ * @param string $method
+ * @param array $parameters
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ return $this->driver()->$method(...$parameters);
+ }
+}
diff --git a/vendor/topthink/think-filesystem/.gitignore b/vendor/topthink/think-filesystem/.gitignore
new file mode 100755
index 0000000..25ad74d
--- /dev/null
+++ b/vendor/topthink/think-filesystem/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+/.idea
+/vendor
diff --git a/vendor/topthink/think-filesystem/README.md b/vendor/topthink/think-filesystem/README.md
new file mode 100755
index 0000000..90977dd
--- /dev/null
+++ b/vendor/topthink/think-filesystem/README.md
@@ -0,0 +1,5 @@
+# think-filesystem for ThinkPHP6.1
+
+## 安装
+
+> composer require topthink/think-filesystem
diff --git a/vendor/topthink/think-filesystem/composer.json b/vendor/topthink/think-filesystem/composer.json
new file mode 100755
index 0000000..8c4f508
--- /dev/null
+++ b/vendor/topthink/think-filesystem/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "topthink/think-filesystem",
+ "description": "The ThinkPHP6.1 Filesystem Package",
+ "type": "library",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "yunwuxin",
+ "email": "448901948@qq.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "think\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "think\\tests\\": "tests/"
+ }
+ },
+ "require": {
+ "topthink/framework": "^6.1|^8.0",
+ "league/flysystem": "^2.0"
+ },
+ "require-dev": {
+ "mikey179/vfsstream": "^1.6",
+ "mockery/mockery": "^1.2",
+ "phpunit/phpunit": "^8.0"
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
+}
diff --git a/vendor/topthink/think-filesystem/phpunit.xml.dist b/vendor/topthink/think-filesystem/phpunit.xml.dist
new file mode 100755
index 0000000..e20a133
--- /dev/null
+++ b/vendor/topthink/think-filesystem/phpunit.xml.dist
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ beStrictAboutTestsThatDoNotTestAnything="false"
+ bootstrap="tests/bootstrap.php"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnError="false"
+ stopOnFailure="false"
+ verbose="true"
+>
+ <testsuites>
+ <testsuite name="ThinkPHP Test Suite">
+ <directory suffix="Test.php">./tests</directory>
+ </testsuite>
+ </testsuites>
+ <filter>
+ <whitelist processUncoveredFilesFromWhitelist="true">
+ <directory suffix=".php">./src/think</directory>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/vendor/topthink/think-filesystem/src/Filesystem.php b/vendor/topthink/think-filesystem/src/Filesystem.php
new file mode 100755
index 0000000..0aee929
--- /dev/null
+++ b/vendor/topthink/think-filesystem/src/Filesystem.php
@@ -0,0 +1,89 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use InvalidArgumentException;
+use think\filesystem\Driver;
+use think\filesystem\driver\Local;
+use think\helper\Arr;
+
+/**
+ * Class Filesystem
+ * @package think
+ * @mixin Driver
+ * @mixin Local
+ */
+class Filesystem extends Manager
+{
+ protected $namespace = '\\think\\filesystem\\driver\\';
+
+ /**
+ * @param null|string $name
+ * @return Driver
+ */
+ public function disk(string $name = null): Driver
+ {
+ return $this->driver($name);
+ }
+
+ protected function resolveType(string $name)
+ {
+ return $this->getDiskConfig($name, 'type', 'local');
+ }
+
+ protected function resolveConfig(string $name)
+ {
+ return $this->getDiskConfig($name);
+ }
+
+ /**
+ * 获取缓存配置
+ * @access public
+ * @param null|string $name 名称
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getConfig(string $name = null, $default = null)
+ {
+ if (!is_null($name)) {
+ return $this->app->config->get('filesystem.' . $name, $default);
+ }
+
+ return $this->app->config->get('filesystem');
+ }
+
+ /**
+ * 获取磁盘配置
+ * @param string $disk
+ * @param null $name
+ * @param null $default
+ * @return array
+ */
+ public function getDiskConfig($disk, $name = null, $default = null)
+ {
+ if ($config = $this->getConfig("disks.{$disk}")) {
+ return Arr::get($config, $name, $default);
+ }
+
+ throw new InvalidArgumentException("Disk [$disk] not found.");
+ }
+
+ /**
+ * 默认驱动
+ * @return string|null
+ */
+ public function getDefaultDriver()
+ {
+ return $this->getConfig('default');
+ }
+}
diff --git a/vendor/topthink/think-filesystem/src/facade/Filesystem.php b/vendor/topthink/think-filesystem/src/facade/Filesystem.php
new file mode 100755
index 0000000..0e32c2c
--- /dev/null
+++ b/vendor/topthink/think-filesystem/src/facade/Filesystem.php
@@ -0,0 +1,33 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\facade;
+
+use think\Facade;
+use think\filesystem\Driver;
+
+/**
+ * Class Filesystem
+ * @package think\facade
+ * @mixin \think\Filesystem
+ * @method static Driver disk(string $name = null) , null|string
+ * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取缓存配置
+ * @method static array getDiskConfig(string $disk, null $name = null, null $default = null) 获取磁盘配置
+ * @method static string|null getDefaultDriver() 默认驱动
+ */
+class Filesystem extends Facade
+{
+ protected static function getFacadeClass()
+ {
+ return \think\Filesystem::class;
+ }
+}
diff --git a/vendor/topthink/think-filesystem/src/filesystem/Driver.php b/vendor/topthink/think-filesystem/src/filesystem/Driver.php
new file mode 100755
index 0000000..e804141
--- /dev/null
+++ b/vendor/topthink/think-filesystem/src/filesystem/Driver.php
@@ -0,0 +1,130 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\filesystem;
+
+use League\Flysystem\Filesystem;
+use League\Flysystem\FilesystemAdapter;
+use League\Flysystem\UnableToSetVisibility;
+use League\Flysystem\UnableToWriteFile;
+use RuntimeException;
+use think\Cache;
+use think\File;
+
+/**
+ * Class Driver
+ * @package think\filesystem
+ * @mixin Filesystem
+ */
+abstract class Driver
+{
+
+ /** @var Cache */
+ protected $cache;
+
+ /** @var Filesystem */
+ protected $filesystem;
+
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [];
+
+ public function __construct(Cache $cache, array $config)
+ {
+ $this->cache = $cache;
+ $this->config = array_merge($this->config, $config);
+
+ $adapter = $this->createAdapter();
+ $this->filesystem = $this->createFilesystem($adapter);
+ }
+
+ abstract protected function createAdapter(): FilesystemAdapter;
+
+ protected function createFilesystem(FilesystemAdapter $adapter): Filesystem
+ {
+ $config = array_intersect_key($this->config, array_flip(['visibility', 'disable_asserts', 'url']));
+
+ return new Filesystem($adapter, $config);
+ }
+
+ /**
+ * 获取文件完整路径
+ * @param string $path
+ * @return string
+ */
+ public function path(string $path): string
+ {
+ return $path;
+ }
+
+ protected function concatPathToUrl($url, $path)
+ {
+ return rtrim($url, '/') . '/' . ltrim($path, '/');
+ }
+
+ public function url(string $path): string
+ {
+ throw new RuntimeException('This driver does not support retrieving URLs.');
+ }
+
+ /**
+ * 保存文件
+ * @param string $path 路径
+ * @param File $file 文件
+ * @param null|string|\Closure $rule 文件名规则
+ * @param array $options 参数
+ * @return bool|string
+ */
+ public function putFile(string $path, File $file, $rule = null, array $options = [])
+ {
+ return $this->putFileAs($path, $file, $file->hashName($rule), $options);
+ }
+
+ /**
+ * 指定文件名保存文件
+ * @param string $path 路径
+ * @param File $file 文件
+ * @param string $name 文件名
+ * @param array $options 参数
+ * @return bool|string
+ */
+ public function putFileAs(string $path, File $file, string $name, array $options = [])
+ {
+ $stream = fopen($file->getRealPath(), 'r');
+ $path = trim($path . '/' . $name, '/');
+
+ $result = $this->put($path, $stream, $options);
+
+ if (is_resource($stream)) {
+ fclose($stream);
+ }
+
+ return $result ? $path : false;
+ }
+
+ protected function put(string $path, $contents, array $options = [])
+ {
+ try {
+ $this->writeStream($path, $contents, $options);
+ } catch (UnableToWriteFile|UnableToSetVisibility $e) {
+ return false;
+ }
+ return true;
+ }
+
+ public function __call($method, $parameters)
+ {
+ return $this->filesystem->$method(...$parameters);
+ }
+}
diff --git a/vendor/topthink/think-filesystem/src/filesystem/driver/Local.php b/vendor/topthink/think-filesystem/src/filesystem/driver/Local.php
new file mode 100755
index 0000000..3b1b64d
--- /dev/null
+++ b/vendor/topthink/think-filesystem/src/filesystem/driver/Local.php
@@ -0,0 +1,98 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\filesystem\driver;
+
+use League\Flysystem\FilesystemAdapter;
+use League\Flysystem\Local\LocalFilesystemAdapter;
+use League\Flysystem\PathNormalizer;
+use League\Flysystem\PathPrefixer;
+use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
+use League\Flysystem\Visibility;
+use League\Flysystem\WhitespacePathNormalizer;
+use think\filesystem\Driver;
+
+class Local extends Driver
+{
+ /**
+ * 配置参数
+ * @var array
+ */
+ protected $config = [
+ 'root' => '',
+ ];
+
+ /**
+ * @var PathPrefixer
+ */
+ protected $prefixer;
+
+ /**
+ * @var PathNormalizer
+ */
+ protected $normalizer;
+
+ protected function createAdapter(): FilesystemAdapter
+ {
+ $visibility = PortableVisibilityConverter::fromArray(
+ $this->config['permissions'] ?? [],
+ $this->config['visibility'] ?? Visibility::PRIVATE
+ );
+
+ $links = ($this->config['links'] ?? null) === 'skip'
+ ? LocalFilesystemAdapter::SKIP_LINKS
+ : LocalFilesystemAdapter::DISALLOW_LINKS;
+
+ return new LocalFilesystemAdapter(
+ $this->config['root'],
+ $visibility,
+ $this->config['lock'] ?? LOCK_EX,
+ $links
+ );
+ }
+
+ protected function prefixer()
+ {
+ if (!$this->prefixer) {
+ $this->prefixer = new PathPrefixer($this->config['root'], DIRECTORY_SEPARATOR);
+ }
+ return $this->prefixer;
+ }
+
+ protected function normalizer()
+ {
+ if (!$this->normalizer) {
+ $this->normalizer = new WhitespacePathNormalizer();
+ }
+ return $this->normalizer;
+ }
+
+ /**
+ * 获取文件访问地址
+ * @param string $path 文件路径
+ * @return string
+ */
+ public function url(string $path): string
+ {
+ $path = $this->normalizer()->normalizePath($path);
+
+ if (isset($this->config['url'])) {
+ return $this->concatPathToUrl($this->config['url'], $path);
+ }
+ return parent::url($path);
+ }
+
+ public function path(string $path): string
+ {
+ return $this->prefixer()->prefixPath($path);
+ }
+}
diff --git a/vendor/topthink/think-filesystem/tests/FilesystemTest.php b/vendor/topthink/think-filesystem/tests/FilesystemTest.php
new file mode 100755
index 0000000..cc9d94e
--- /dev/null
+++ b/vendor/topthink/think-filesystem/tests/FilesystemTest.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace think\tests;
+
+use Mockery as m;
+use Mockery\MockInterface;
+use org\bovigo\vfs\vfsStream;
+use org\bovigo\vfs\vfsStreamDirectory;
+use PHPUnit\Framework\TestCase;
+use think\App;
+use think\Config;
+use think\Container;
+use think\Filesystem;
+use think\filesystem\driver\Local;
+
+class FilesystemTest extends TestCase
+{
+ /** @var App|MockInterface */
+ protected $app;
+
+ /** @var Filesystem */
+ protected $filesystem;
+
+ /** @var Config|MockInterface */
+ protected $config;
+
+ /** @var vfsStreamDirectory */
+ protected $root;
+
+ protected function setUp(): void
+ {
+ $this->app = m::mock(App::class)->makePartial();
+ Container::setInstance($this->app);
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class);
+ $this->config->shouldReceive('get')->with('filesystem.default', null)->andReturn('local');
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+ $this->filesystem = new Filesystem($this->app);
+
+ $this->root = vfsStream::setup('rootDir');
+ }
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ public function testDisk()
+ {
+ $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([
+ 'type' => 'local',
+ 'root' => $this->root->url(),
+ ]);
+
+ $this->config->shouldReceive('get')->with('filesystem.disks.foo', null)->andReturn([
+ 'type' => 'local',
+ 'root' => $this->root->url(),
+ ]);
+
+ $this->assertInstanceOf(Local::class, $this->filesystem->disk());
+
+ $this->assertInstanceOf(Local::class, $this->filesystem->disk('foo'));
+ }
+
+}
+
diff --git a/vendor/topthink/think-filesystem/tests/bootstrap.php b/vendor/topthink/think-filesystem/tests/bootstrap.php
new file mode 100755
index 0000000..a4abe2d
--- /dev/null
+++ b/vendor/topthink/think-filesystem/tests/bootstrap.php
@@ -0,0 +1,2 @@
+<?php
+
diff --git a/vendor/topthink/think-helper/.gitignore b/vendor/topthink/think-helper/.gitignore
new file mode 100755
index 0000000..d851bdb
--- /dev/null
+++ b/vendor/topthink/think-helper/.gitignore
@@ -0,0 +1,3 @@
+/vendor/
+/.idea/
+composer.lock
\ No newline at end of file
diff --git a/vendor/topthink/think-helper/LICENSE b/vendor/topthink/think-helper/LICENSE
new file mode 100755
index 0000000..8dada3e
--- /dev/null
+++ b/vendor/topthink/think-helper/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/topthink/think-helper/README.md b/vendor/topthink/think-helper/README.md
new file mode 100755
index 0000000..7baf8f7
--- /dev/null
+++ b/vendor/topthink/think-helper/README.md
@@ -0,0 +1,33 @@
+# thinkphp6 常用的一些扩展类库
+
+基于PHP7.1+
+
+> 以下类库都在`\\think\\helper`命名空间下
+
+## Str
+
+> 字符串操作
+
+```
+// 检查字符串中是否包含某些字符串
+Str::contains($haystack, $needles)
+
+// 检查字符串是否以某些字符串结尾
+Str::endsWith($haystack, $needles)
+
+// 获取指定长度的随机字母数字组合的字符串
+Str::random($length = 16)
+
+// 字符串转小写
+Str::lower($value)
+
+// 字符串转大写
+Str::upper($value)
+
+// 获取字符串的长度
+Str::length($value)
+
+// 截取字符串
+Str::substr($string, $start, $length = null)
+
+```
\ No newline at end of file
diff --git a/vendor/topthink/think-helper/composer.json b/vendor/topthink/think-helper/composer.json
new file mode 100755
index 0000000..b68c43b
--- /dev/null
+++ b/vendor/topthink/think-helper/composer.json
@@ -0,0 +1,22 @@
+{
+ "name": "topthink/think-helper",
+ "description": "The ThinkPHP6 Helper Package",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "yunwuxin",
+ "email": "448901948@qq.com"
+ }
+ ],
+ "require": {
+ "php": ">=7.1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\": "src"
+ },
+ "files": [
+ "src/helper.php"
+ ]
+ }
+}
diff --git a/vendor/topthink/think-helper/src/Collection.php b/vendor/topthink/think-helper/src/Collection.php
new file mode 100755
index 0000000..f3d0a83
--- /dev/null
+++ b/vendor/topthink/think-helper/src/Collection.php
@@ -0,0 +1,651 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ArrayAccess;
+use ArrayIterator;
+use Countable;
+use IteratorAggregate;
+use JsonSerializable;
+use think\contract\Arrayable;
+use think\contract\Jsonable;
+use think\helper\Arr;
+
+/**
+ * 数据集管理类
+ */
+class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Arrayable, Jsonable
+{
+ /**
+ * 数据集数据
+ * @var array
+ */
+ protected $items = [];
+
+ public function __construct($items = [])
+ {
+ $this->items = $this->convertToArray($items);
+ }
+
+ public static function make($items = [])
+ {
+ return new static($items);
+ }
+
+ /**
+ * 是否为空
+ * @access public
+ * @return bool
+ */
+ public function isEmpty(): bool
+ {
+ return empty($this->items);
+ }
+
+ public function toArray(): array
+ {
+ return array_map(function ($value) {
+ return $value instanceof Arrayable ? $value->toArray() : $value;
+ }, $this->items);
+ }
+
+ public function all(): array
+ {
+ return $this->items;
+ }
+
+ /**
+ * 合并数组
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @return static
+ */
+ public function merge($items)
+ {
+ return new static(array_merge($this->items, $this->convertToArray($items)));
+ }
+
+ /**
+ * 按指定键整理数据
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @param string $indexKey 键名
+ * @return array
+ */
+ public function dictionary($items = null, string &$indexKey = null)
+ {
+ if ($items instanceof self) {
+ $items = $items->all();
+ }
+
+ $items = is_null($items) ? $this->items : $items;
+
+ if ($items && empty($indexKey)) {
+ $indexKey = is_array($items[0]) ? 'id' : $items[0]->getPk();
+ }
+
+ if (isset($indexKey) && is_string($indexKey)) {
+ return array_column($items, null, $indexKey);
+ }
+
+ return $items;
+ }
+
+ /**
+ * 比较数组,返回差集
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @param string $indexKey 指定比较的键名
+ * @return static
+ */
+ public function diff($items, string $indexKey = null)
+ {
+ if ($this->isEmpty() || is_scalar($this->items[0])) {
+ return new static(array_diff($this->items, $this->convertToArray($items)));
+ }
+
+ $diff = [];
+ $dictionary = $this->dictionary($items, $indexKey);
+
+ if (is_string($indexKey)) {
+ foreach ($this->items as $item) {
+ if (!isset($dictionary[$item[$indexKey]])) {
+ $diff[] = $item;
+ }
+ }
+ }
+
+ return new static($diff);
+ }
+
+ /**
+ * 比较数组,返回交集
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @param string $indexKey 指定比较的键名
+ * @return static
+ */
+ public function intersect($items, string $indexKey = null)
+ {
+ if ($this->isEmpty() || is_scalar($this->items[0])) {
+ return new static(array_diff($this->items, $this->convertToArray($items)));
+ }
+
+ $intersect = [];
+ $dictionary = $this->dictionary($items, $indexKey);
+
+ if (is_string($indexKey)) {
+ foreach ($this->items as $item) {
+ if (isset($dictionary[$item[$indexKey]])) {
+ $intersect[] = $item;
+ }
+ }
+ }
+
+ return new static($intersect);
+ }
+
+ /**
+ * 交换数组中的键和值
+ *
+ * @access public
+ * @return static
+ */
+ public function flip()
+ {
+ return new static(array_flip($this->items));
+ }
+
+ /**
+ * 返回数组中所有的键名
+ *
+ * @access public
+ * @return static
+ */
+ public function keys()
+ {
+ return new static(array_keys($this->items));
+ }
+
+ /**
+ * 返回数组中所有的值组成的新 Collection 实例
+ * @access public
+ * @return static
+ */
+ public function values()
+ {
+ return new static(array_values($this->items));
+ }
+
+ /**
+ * 删除数组的最后一个元素(出栈)
+ *
+ * @access public
+ * @return mixed
+ */
+ public function pop()
+ {
+ return array_pop($this->items);
+ }
+
+ /**
+ * 通过使用用户自定义函数,以字符串返回数组
+ *
+ * @access public
+ * @param callable $callback 调用方法
+ * @param mixed $initial
+ * @return mixed
+ */
+ public function reduce(callable $callback, $initial = null)
+ {
+ return array_reduce($this->items, $callback, $initial);
+ }
+
+ /**
+ * 以相反的顺序返回数组。
+ *
+ * @access public
+ * @return static
+ */
+ public function reverse()
+ {
+ return new static(array_reverse($this->items));
+ }
+
+ /**
+ * 删除数组中首个元素,并返回被删除元素的值
+ *
+ * @access public
+ * @return mixed
+ */
+ public function shift()
+ {
+ return array_shift($this->items);
+ }
+
+ /**
+ * 在数组结尾插入一个元素
+ * @access public
+ * @param mixed $value 元素
+ * @param string $key KEY
+ * @return void
+ */
+ public function push($value, string $key = null): void
+ {
+ if (is_null($key)) {
+ $this->items[] = $value;
+ } else {
+ $this->items[$key] = $value;
+ }
+ }
+
+ /**
+ * 把一个数组分割为新的数组块.
+ *
+ * @access public
+ * @param int $size 块大小
+ * @param bool $preserveKeys
+ * @return static
+ */
+ public function chunk(int $size, bool $preserveKeys = false)
+ {
+ $chunks = [];
+
+ foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) {
+ $chunks[] = new static($chunk);
+ }
+
+ return new static($chunks);
+ }
+
+ /**
+ * 在数组开头插入一个元素
+ * @access public
+ * @param mixed $value 元素
+ * @param string $key KEY
+ * @return void
+ */
+ public function unshift($value, string $key = null): void
+ {
+ if (is_null($key)) {
+ array_unshift($this->items, $value);
+ } else {
+ $this->items = [$key => $value] + $this->items;
+ }
+ }
+
+ /**
+ * 给每个元素执行个回调
+ *
+ * @access public
+ * @param callable $callback 回调
+ * @return $this
+ */
+ public function each(callable $callback)
+ {
+ foreach ($this->items as $key => $item) {
+ $result = $callback($item, $key);
+
+ if (false === $result) {
+ break;
+ } elseif (!is_object($item)) {
+ $this->items[$key] = $result;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 用回调函数处理数组中的元素
+ * @access public
+ * @param callable|null $callback 回调
+ * @return static
+ */
+ public function map(callable $callback)
+ {
+ return new static(array_map($callback, $this->items));
+ }
+
+ /**
+ * 用回调函数过滤数组中的元素
+ * @access public
+ * @param callable|null $callback 回调
+ * @return static
+ */
+ public function filter(callable $callback = null)
+ {
+ if ($callback) {
+ return new static(array_filter($this->items, $callback));
+ }
+
+ return new static(array_filter($this->items));
+ }
+
+ /**
+ * 根据字段条件过滤数组中的元素
+ * @access public
+ * @param string $field 字段名
+ * @param mixed $operator 操作符
+ * @param mixed $value 数据
+ * @return static
+ */
+ public function where(string $field, $operator, $value = null)
+ {
+ if (is_null($value)) {
+ $value = $operator;
+ $operator = '=';
+ }
+
+ return $this->filter(function ($data) use ($field, $operator, $value) {
+ if (strpos($field, '.')) {
+ list($field, $relation) = explode('.', $field);
+
+ $result = $data[$field][$relation] ?? null;
+ } else {
+ $result = $data[$field] ?? null;
+ }
+
+ switch (strtolower($operator)) {
+ case '===':
+ return $result === $value;
+ case '!==':
+ return $result !== $value;
+ case '!=':
+ case '<>':
+ return $result != $value;
+ case '>':
+ return $result > $value;
+ case '>=':
+ return $result >= $value;
+ case '<':
+ return $result < $value;
+ case '<=':
+ return $result <= $value;
+ case 'like':
+ return is_string($result) && false !== strpos($result, $value);
+ case 'not like':
+ return is_string($result) && false === strpos($result, $value);
+ case 'in':
+ return is_scalar($result) && in_array($result, $value, true);
+ case 'not in':
+ return is_scalar($result) && !in_array($result, $value, true);
+ case 'between':
+ list($min, $max) = is_string($value) ? explode(',', $value) : $value;
+ return is_scalar($result) && $result >= $min && $result <= $max;
+ case 'not between':
+ list($min, $max) = is_string($value) ? explode(',', $value) : $value;
+ return is_scalar($result) && $result > $max || $result < $min;
+ case '==':
+ case '=':
+ default:
+ return $result == $value;
+ }
+ });
+ }
+
+ /**
+ * LIKE过滤
+ * @access public
+ * @param string $field 字段名
+ * @param string $value 数据
+ * @return static
+ */
+ public function whereLike(string $field, string $value)
+ {
+ return $this->where($field, 'like', $value);
+ }
+
+ /**
+ * NOT LIKE过滤
+ * @access public
+ * @param string $field 字段名
+ * @param string $value 数据
+ * @return static
+ */
+ public function whereNotLike(string $field, string $value)
+ {
+ return $this->where($field, 'not like', $value);
+ }
+
+ /**
+ * IN过滤
+ * @access public
+ * @param string $field 字段名
+ * @param array $value 数据
+ * @return static
+ */
+ public function whereIn(string $field, array $value)
+ {
+ return $this->where($field, 'in', $value);
+ }
+
+ /**
+ * NOT IN过滤
+ * @access public
+ * @param string $field 字段名
+ * @param array $value 数据
+ * @return static
+ */
+ public function whereNotIn(string $field, array $value)
+ {
+ return $this->where($field, 'not in', $value);
+ }
+
+ /**
+ * BETWEEN 过滤
+ * @access public
+ * @param string $field 字段名
+ * @param mixed $value 数据
+ * @return static
+ */
+ public function whereBetween(string $field, $value)
+ {
+ return $this->where($field, 'between', $value);
+ }
+
+ /**
+ * NOT BETWEEN 过滤
+ * @access public
+ * @param string $field 字段名
+ * @param mixed $value 数据
+ * @return static
+ */
+ public function whereNotBetween(string $field, $value)
+ {
+ return $this->where($field, 'not between', $value);
+ }
+
+ /**
+ * 返回数据中指定的一列
+ * @access public
+ * @param string $columnKey 键名
+ * @param string $indexKey 作为索引值的列
+ * @return array
+ */
+ public function column(string $columnKey, string $indexKey = null)
+ {
+ return array_column($this->items, $columnKey, $indexKey);
+ }
+
+ /**
+ * 对数组排序
+ *
+ * @access public
+ * @param callable|null $callback 回调
+ * @return static
+ */
+ public function sort(callable $callback = null)
+ {
+ $items = $this->items;
+
+ $callback = $callback ?: function ($a, $b) {
+ return $a == $b ? 0 : (($a < $b) ? -1 : 1);
+ };
+
+ uasort($items, $callback);
+
+ return new static($items);
+ }
+
+ /**
+ * 指定字段排序
+ * @access public
+ * @param string $field 排序字段
+ * @param string $order 排序
+ * @return $this
+ */
+ public function order(string $field, string $order = null)
+ {
+ return $this->sort(function ($a, $b) use ($field, $order) {
+ $fieldA = $a[$field] ?? null;
+ $fieldB = $b[$field] ?? null;
+
+ return 'desc' == strtolower($order) ? strcmp($fieldB, $fieldA) : strcmp($fieldA, $fieldB);
+ });
+ }
+
+ /**
+ * 将数组打乱
+ *
+ * @access public
+ * @return static
+ */
+ public function shuffle()
+ {
+ $items = $this->items;
+
+ shuffle($items);
+
+ return new static($items);
+ }
+
+ /**
+ * 获取最后一个单元数据
+ *
+ * @access public
+ * @param callable|null $callback
+ * @param null $default
+ * @return mixed
+ */
+ public function first(callable $callback = null, $default = null)
+ {
+ return Arr::first($this->items, $callback, $default);
+ }
+
+ /**
+ * 获取第一个单元数据
+ *
+ * @access public
+ * @param callable|null $callback
+ * @param null $default
+ * @return mixed
+ */
+ public function last(callable $callback = null, $default = null)
+ {
+ return Arr::last($this->items, $callback, $default);
+ }
+
+ /**
+ * 截取数组
+ *
+ * @access public
+ * @param int $offset 起始位置
+ * @param int $length 截取长度
+ * @param bool $preserveKeys preserveKeys
+ * @return static
+ */
+ public function slice(int $offset, int $length = null, bool $preserveKeys = false)
+ {
+ return new static(array_slice($this->items, $offset, $length, $preserveKeys));
+ }
+
+ // ArrayAccess
+ public function offsetExists($offset)
+ {
+ return array_key_exists($offset, $this->items);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->items[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ if (is_null($offset)) {
+ $this->items[] = $value;
+ } else {
+ $this->items[$offset] = $value;
+ }
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->items[$offset]);
+ }
+
+ //Countable
+ public function count()
+ {
+ return count($this->items);
+ }
+
+ //IteratorAggregate
+ public function getIterator()
+ {
+ return new ArrayIterator($this->items);
+ }
+
+ //JsonSerializable
+ public function jsonSerialize()
+ {
+ return $this->toArray();
+ }
+
+ /**
+ * 转换当前数据集为JSON字符串
+ * @access public
+ * @param integer $options json参数
+ * @return string
+ */
+ public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
+ {
+ return json_encode($this->toArray(), $options);
+ }
+
+ public function __toString()
+ {
+ return $this->toJson();
+ }
+
+ /**
+ * 转换成数组
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @return array
+ */
+ protected function convertToArray($items): array
+ {
+ if ($items instanceof self) {
+ return $items->all();
+ }
+
+ return (array) $items;
+ }
+}
diff --git a/vendor/topthink/think-helper/src/contract/Arrayable.php b/vendor/topthink/think-helper/src/contract/Arrayable.php
new file mode 100755
index 0000000..7c6b992
--- /dev/null
+++ b/vendor/topthink/think-helper/src/contract/Arrayable.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace think\contract;
+
+interface Arrayable
+{
+ public function toArray(): array;
+}
diff --git a/vendor/topthink/think-helper/src/contract/Jsonable.php b/vendor/topthink/think-helper/src/contract/Jsonable.php
new file mode 100755
index 0000000..d86122d
--- /dev/null
+++ b/vendor/topthink/think-helper/src/contract/Jsonable.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace think\contract;
+
+interface Jsonable
+{
+ public function toJson(int $options = JSON_UNESCAPED_UNICODE): string;
+}
diff --git a/vendor/topthink/think-helper/src/helper.php b/vendor/topthink/think-helper/src/helper.php
new file mode 100755
index 0000000..0a14735
--- /dev/null
+++ b/vendor/topthink/think-helper/src/helper.php
@@ -0,0 +1,279 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+use think\Collection;
+use think\helper\Arr;
+
+if (!function_exists('throw_if')) {
+ /**
+ * 按条件抛异常
+ *
+ * @param mixed $condition
+ * @param Throwable|string $exception
+ * @param array ...$parameters
+ * @return mixed
+ *
+ * @throws Throwable
+ */
+ function throw_if($condition, $exception, ...$parameters)
+ {
+ if ($condition) {
+ throw (is_string($exception) ? new $exception(...$parameters) : $exception);
+ }
+
+ return $condition;
+ }
+}
+
+if (!function_exists('throw_unless')) {
+ /**
+ * 按条件抛异常
+ *
+ * @param mixed $condition
+ * @param Throwable|string $exception
+ * @param array ...$parameters
+ * @return mixed
+ * @throws Throwable
+ */
+ function throw_unless($condition, $exception, ...$parameters)
+ {
+ if (!$condition) {
+ throw (is_string($exception) ? new $exception(...$parameters) : $exception);
+ }
+
+ return $condition;
+ }
+}
+
+if (!function_exists('tap')) {
+ /**
+ * 对一个值调用给定的闭包,然后返回该值
+ *
+ * @param mixed $value
+ * @param callable|null $callback
+ * @return mixed
+ */
+ function tap($value, $callback = null)
+ {
+ if (is_null($callback)) {
+ return $value;
+ }
+
+ $callback($value);
+
+ return $value;
+ }
+}
+
+if (!function_exists('value')) {
+ /**
+ * Return the default value of the given value.
+ *
+ * @param mixed $value
+ * @return mixed
+ */
+ function value($value)
+ {
+ return $value instanceof Closure ? $value() : $value;
+ }
+}
+
+if (!function_exists('collect')) {
+ /**
+ * Create a collection from the given value.
+ *
+ * @param mixed $value
+ * @return Collection
+ */
+ function collect($value = null)
+ {
+ return new Collection($value);
+ }
+}
+
+if (!function_exists('data_fill')) {
+ /**
+ * Fill in data where it's missing.
+ *
+ * @param mixed $target
+ * @param string|array $key
+ * @param mixed $value
+ * @return mixed
+ */
+ function data_fill(&$target, $key, $value)
+ {
+ return data_set($target, $key, $value, false);
+ }
+}
+
+if (!function_exists('data_get')) {
+ /**
+ * Get an item from an array or object using "dot" notation.
+ *
+ * @param mixed $target
+ * @param string|array|int $key
+ * @param mixed $default
+ * @return mixed
+ */
+ function data_get($target, $key, $default = null)
+ {
+ if (is_null($key)) {
+ return $target;
+ }
+
+ $key = is_array($key) ? $key : explode('.', $key);
+
+ while (!is_null($segment = array_shift($key))) {
+ if ('*' === $segment) {
+ if ($target instanceof Collection) {
+ $target = $target->all();
+ } elseif (!is_array($target)) {
+ return value($default);
+ }
+
+ $result = [];
+
+ foreach ($target as $item) {
+ $result[] = data_get($item, $key);
+ }
+
+ return in_array('*', $key) ? Arr::collapse($result) : $result;
+ }
+
+ if (Arr::accessible($target) && Arr::exists($target, $segment)) {
+ $target = $target[$segment];
+ } elseif (is_object($target) && isset($target->{$segment})) {
+ $target = $target->{$segment};
+ } else {
+ return value($default);
+ }
+ }
+
+ return $target;
+ }
+}
+
+if (!function_exists('data_set')) {
+ /**
+ * Set an item on an array or object using dot notation.
+ *
+ * @param mixed $target
+ * @param string|array $key
+ * @param mixed $value
+ * @param bool $overwrite
+ * @return mixed
+ */
+ function data_set(&$target, $key, $value, $overwrite = true)
+ {
+ $segments = is_array($key) ? $key : explode('.', $key);
+
+ if (($segment = array_shift($segments)) === '*') {
+ if (!Arr::accessible($target)) {
+ $target = [];
+ }
+
+ if ($segments) {
+ foreach ($target as &$inner) {
+ data_set($inner, $segments, $value, $overwrite);
+ }
+ } elseif ($overwrite) {
+ foreach ($target as &$inner) {
+ $inner = $value;
+ }
+ }
+ } elseif (Arr::accessible($target)) {
+ if ($segments) {
+ if (!Arr::exists($target, $segment)) {
+ $target[$segment] = [];
+ }
+
+ data_set($target[$segment], $segments, $value, $overwrite);
+ } elseif ($overwrite || !Arr::exists($target, $segment)) {
+ $target[$segment] = $value;
+ }
+ } elseif (is_object($target)) {
+ if ($segments) {
+ if (!isset($target->{$segment})) {
+ $target->{$segment} = [];
+ }
+
+ data_set($target->{$segment}, $segments, $value, $overwrite);
+ } elseif ($overwrite || !isset($target->{$segment})) {
+ $target->{$segment} = $value;
+ }
+ } else {
+ $target = [];
+
+ if ($segments) {
+ data_set($target[$segment], $segments, $value, $overwrite);
+ } elseif ($overwrite) {
+ $target[$segment] = $value;
+ }
+ }
+
+ return $target;
+ }
+}
+
+if (!function_exists('trait_uses_recursive')) {
+ /**
+ * 获取一个trait里所有引用到的trait
+ *
+ * @param string $trait Trait
+ * @return array
+ */
+ function trait_uses_recursive(string $trait): array
+ {
+ $traits = class_uses($trait);
+ foreach ($traits as $trait) {
+ $traits += trait_uses_recursive($trait);
+ }
+
+ return $traits;
+ }
+}
+
+if (!function_exists('class_basename')) {
+ /**
+ * 获取类名(不包含命名空间)
+ *
+ * @param mixed $class 类名
+ * @return string
+ */
+ function class_basename($class): string
+ {
+ $class = is_object($class) ? get_class($class) : $class;
+ return basename(str_replace('\\', '/', $class));
+ }
+}
+
+if (!function_exists('class_uses_recursive')) {
+ /**
+ *获取一个类里所有用到的trait,包括父类的
+ *
+ * @param mixed $class 类名
+ * @return array
+ */
+ function class_uses_recursive($class): array
+ {
+ if (is_object($class)) {
+ $class = get_class($class);
+ }
+
+ $results = [];
+ $classes = array_merge([$class => $class], class_parents($class));
+ foreach ($classes as $class) {
+ $results += trait_uses_recursive($class);
+ }
+
+ return array_unique($results);
+ }
+}
diff --git a/vendor/topthink/think-helper/src/helper/Arr.php b/vendor/topthink/think-helper/src/helper/Arr.php
new file mode 100755
index 0000000..ed4d6a9
--- /dev/null
+++ b/vendor/topthink/think-helper/src/helper/Arr.php
@@ -0,0 +1,634 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\helper;
+
+use ArrayAccess;
+use InvalidArgumentException;
+use think\Collection;
+
+class Arr
+{
+
+ /**
+ * Determine whether the given value is array accessible.
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ public static function accessible($value)
+ {
+ return is_array($value) || $value instanceof ArrayAccess;
+ }
+
+ /**
+ * Add an element to an array using "dot" notation if it doesn't exist.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $value
+ * @return array
+ */
+ public static function add($array, $key, $value)
+ {
+ if (is_null(static::get($array, $key))) {
+ static::set($array, $key, $value);
+ }
+
+ return $array;
+ }
+
+ /**
+ * Collapse an array of arrays into a single array.
+ *
+ * @param array $array
+ * @return array
+ */
+ public static function collapse($array)
+ {
+ $results = [];
+
+ foreach ($array as $values) {
+ if ($values instanceof Collection) {
+ $values = $values->all();
+ } elseif (!is_array($values)) {
+ continue;
+ }
+
+ $results = array_merge($results, $values);
+ }
+
+ return $results;
+ }
+
+ /**
+ * Cross join the given arrays, returning all possible permutations.
+ *
+ * @param array ...$arrays
+ * @return array
+ */
+ public static function crossJoin(...$arrays)
+ {
+ $results = [[]];
+
+ foreach ($arrays as $index => $array) {
+ $append = [];
+
+ foreach ($results as $product) {
+ foreach ($array as $item) {
+ $product[$index] = $item;
+
+ $append[] = $product;
+ }
+ }
+
+ $results = $append;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Divide an array into two arrays. One with keys and the other with values.
+ *
+ * @param array $array
+ * @return array
+ */
+ public static function divide($array)
+ {
+ return [array_keys($array), array_values($array)];
+ }
+
+ /**
+ * Flatten a multi-dimensional associative array with dots.
+ *
+ * @param array $array
+ * @param string $prepend
+ * @return array
+ */
+ public static function dot($array, $prepend = '')
+ {
+ $results = [];
+
+ foreach ($array as $key => $value) {
+ if (is_array($value) && !empty($value)) {
+ $results = array_merge($results, static::dot($value, $prepend . $key . '.'));
+ } else {
+ $results[$prepend . $key] = $value;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Get all of the given array except for a specified array of keys.
+ *
+ * @param array $array
+ * @param array|string $keys
+ * @return array
+ */
+ public static function except($array, $keys)
+ {
+ static::forget($array, $keys);
+
+ return $array;
+ }
+
+ /**
+ * Determine if the given key exists in the provided array.
+ *
+ * @param \ArrayAccess|array $array
+ * @param string|int $key
+ * @return bool
+ */
+ public static function exists($array, $key)
+ {
+ if ($array instanceof ArrayAccess) {
+ return $array->offsetExists($key);
+ }
+
+ return array_key_exists($key, $array);
+ }
+
+ /**
+ * Return the first element in an array passing a given truth test.
+ *
+ * @param array $array
+ * @param callable|null $callback
+ * @param mixed $default
+ * @return mixed
+ */
+ public static function first($array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ if (empty($array)) {
+ return value($default);
+ }
+
+ foreach ($array as $item) {
+ return $item;
+ }
+ }
+
+ foreach ($array as $key => $value) {
+ if (call_user_func($callback, $value, $key)) {
+ return $value;
+ }
+ }
+
+ return value($default);
+ }
+
+ /**
+ * Return the last element in an array passing a given truth test.
+ *
+ * @param array $array
+ * @param callable|null $callback
+ * @param mixed $default
+ * @return mixed
+ */
+ public static function last($array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ return empty($array) ? value($default) : end($array);
+ }
+
+ return static::first(array_reverse($array, true), $callback, $default);
+ }
+
+ /**
+ * Flatten a multi-dimensional array into a single level.
+ *
+ * @param array $array
+ * @param int $depth
+ * @return array
+ */
+ public static function flatten($array, $depth = INF)
+ {
+ $result = [];
+
+ foreach ($array as $item) {
+ $item = $item instanceof Collection ? $item->all() : $item;
+
+ if (!is_array($item)) {
+ $result[] = $item;
+ } elseif ($depth === 1) {
+ $result = array_merge($result, array_values($item));
+ } else {
+ $result = array_merge($result, static::flatten($item, $depth - 1));
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Remove one or many array items from a given array using "dot" notation.
+ *
+ * @param array $array
+ * @param array|string $keys
+ * @return void
+ */
+ public static function forget(&$array, $keys)
+ {
+ $original = &$array;
+
+ $keys = (array) $keys;
+
+ if (count($keys) === 0) {
+ return;
+ }
+
+ foreach ($keys as $key) {
+ // if the exact key exists in the top-level, remove it
+ if (static::exists($array, $key)) {
+ unset($array[$key]);
+
+ continue;
+ }
+
+ $parts = explode('.', $key);
+
+ // clean up before each pass
+ $array = &$original;
+
+ while (count($parts) > 1) {
+ $part = array_shift($parts);
+
+ if (isset($array[$part]) && is_array($array[$part])) {
+ $array = &$array[$part];
+ } else {
+ continue 2;
+ }
+ }
+
+ unset($array[array_shift($parts)]);
+ }
+ }
+
+ /**
+ * Get an item from an array using "dot" notation.
+ *
+ * @param \ArrayAccess|array $array
+ * @param string $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public static function get($array, $key, $default = null)
+ {
+ if (!static::accessible($array)) {
+ return value($default);
+ }
+
+ if (is_null($key)) {
+ return $array;
+ }
+
+ if (static::exists($array, $key)) {
+ return $array[$key];
+ }
+
+ if (strpos($key, '.') === false) {
+ return $array[$key] ?? value($default);
+ }
+
+ foreach (explode('.', $key) as $segment) {
+ if (static::accessible($array) && static::exists($array, $segment)) {
+ $array = $array[$segment];
+ } else {
+ return value($default);
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Check if an item or items exist in an array using "dot" notation.
+ *
+ * @param \ArrayAccess|array $array
+ * @param string|array $keys
+ * @return bool
+ */
+ public static function has($array, $keys)
+ {
+ $keys = (array) $keys;
+
+ if (!$array || $keys === []) {
+ return false;
+ }
+
+ foreach ($keys as $key) {
+ $subKeyArray = $array;
+
+ if (static::exists($array, $key)) {
+ continue;
+ }
+
+ foreach (explode('.', $key) as $segment) {
+ if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) {
+ $subKeyArray = $subKeyArray[$segment];
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if an array is associative.
+ *
+ * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
+ *
+ * @param array $array
+ * @return bool
+ */
+ public static function isAssoc(array $array)
+ {
+ $keys = array_keys($array);
+
+ return array_keys($keys) !== $keys;
+ }
+
+ /**
+ * Get a subset of the items from the given array.
+ *
+ * @param array $array
+ * @param array|string $keys
+ * @return array
+ */
+ public static function only($array, $keys)
+ {
+ return array_intersect_key($array, array_flip((array) $keys));
+ }
+
+ /**
+ * Pluck an array of values from an array.
+ *
+ * @param array $array
+ * @param string|array $value
+ * @param string|array|null $key
+ * @return array
+ */
+ public static function pluck($array, $value, $key = null)
+ {
+ $results = [];
+
+ [$value, $key] = static::explodePluckParameters($value, $key);
+
+ foreach ($array as $item) {
+ $itemValue = data_get($item, $value);
+
+ // If the key is "null", we will just append the value to the array and keep
+ // looping. Otherwise we will key the array using the value of the key we
+ // received from the developer. Then we'll return the final array form.
+ if (is_null($key)) {
+ $results[] = $itemValue;
+ } else {
+ $itemKey = data_get($item, $key);
+
+ if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
+ $itemKey = (string) $itemKey;
+ }
+
+ $results[$itemKey] = $itemValue;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Explode the "value" and "key" arguments passed to "pluck".
+ *
+ * @param string|array $value
+ * @param string|array|null $key
+ * @return array
+ */
+ protected static function explodePluckParameters($value, $key)
+ {
+ $value = is_string($value) ? explode('.', $value) : $value;
+
+ $key = is_null($key) || is_array($key) ? $key : explode('.', $key);
+
+ return [$value, $key];
+ }
+
+ /**
+ * Push an item onto the beginning of an array.
+ *
+ * @param array $array
+ * @param mixed $value
+ * @param mixed $key
+ * @return array
+ */
+ public static function prepend($array, $value, $key = null)
+ {
+ if (is_null($key)) {
+ array_unshift($array, $value);
+ } else {
+ $array = [$key => $value] + $array;
+ }
+
+ return $array;
+ }
+
+ /**
+ * Get a value from the array, and remove it.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public static function pull(&$array, $key, $default = null)
+ {
+ $value = static::get($array, $key, $default);
+
+ static::forget($array, $key);
+
+ return $value;
+ }
+
+ /**
+ * Get one or a specified number of random values from an array.
+ *
+ * @param array $array
+ * @param int|null $number
+ * @return mixed
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function random($array, $number = null)
+ {
+ $requested = is_null($number) ? 1 : $number;
+
+ $count = count($array);
+
+ if ($requested > $count) {
+ throw new InvalidArgumentException(
+ "You requested {$requested} items, but there are only {$count} items available."
+ );
+ }
+
+ if (is_null($number)) {
+ return $array[array_rand($array)];
+ }
+
+ if ((int) $number === 0) {
+ return [];
+ }
+
+ $keys = array_rand($array, $number);
+
+ $results = [];
+
+ foreach ((array) $keys as $key) {
+ $results[] = $array[$key];
+ }
+
+ return $results;
+ }
+
+ /**
+ * Set an array item to a given value using "dot" notation.
+ *
+ * If no key is given to the method, the entire array will be replaced.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $value
+ * @return array
+ */
+ public static function set(&$array, $key, $value)
+ {
+ if (is_null($key)) {
+ return $array = $value;
+ }
+
+ $keys = explode('.', $key);
+
+ while (count($keys) > 1) {
+ $key = array_shift($keys);
+
+ // If the key doesn't exist at this depth, we will just create an empty array
+ // to hold the next value, allowing us to create the arrays to hold final
+ // values at the correct depth. Then we'll keep digging into the array.
+ if (!isset($array[$key]) || !is_array($array[$key])) {
+ $array[$key] = [];
+ }
+
+ $array = &$array[$key];
+ }
+
+ $array[array_shift($keys)] = $value;
+
+ return $array;
+ }
+
+ /**
+ * Shuffle the given array and return the result.
+ *
+ * @param array $array
+ * @param int|null $seed
+ * @return array
+ */
+ public static function shuffle($array, $seed = null)
+ {
+ if (is_null($seed)) {
+ shuffle($array);
+ } else {
+ srand($seed);
+
+ usort($array, function () {
+ return rand(-1, 1);
+ });
+ }
+
+ return $array;
+ }
+
+ /**
+ * Sort the array using the given callback or "dot" notation.
+ *
+ * @param array $array
+ * @param callable|string|null $callback
+ * @return array
+ */
+ public static function sort($array, $callback = null)
+ {
+ return Collection::make($array)->sort($callback)->all();
+ }
+
+ /**
+ * Recursively sort an array by keys and values.
+ *
+ * @param array $array
+ * @return array
+ */
+ public static function sortRecursive($array)
+ {
+ foreach ($array as &$value) {
+ if (is_array($value)) {
+ $value = static::sortRecursive($value);
+ }
+ }
+
+ if (static::isAssoc($array)) {
+ ksort($array);
+ } else {
+ sort($array);
+ }
+
+ return $array;
+ }
+
+ /**
+ * Convert the array into a query string.
+ *
+ * @param array $array
+ * @return string
+ */
+ public static function query($array)
+ {
+ return http_build_query($array, null, '&', PHP_QUERY_RFC3986);
+ }
+
+ /**
+ * Filter the array using the given callback.
+ *
+ * @param array $array
+ * @param callable $callback
+ * @return array
+ */
+ public static function where($array, callable $callback)
+ {
+ return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
+ }
+
+ /**
+ * If the given value is not an array and not null, wrap it in one.
+ *
+ * @param mixed $value
+ * @return array
+ */
+ public static function wrap($value)
+ {
+ if (is_null($value)) {
+ return [];
+ }
+
+ return is_array($value) ? $value : [$value];
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-helper/src/helper/Str.php b/vendor/topthink/think-helper/src/helper/Str.php
new file mode 100755
index 0000000..7391fbd
--- /dev/null
+++ b/vendor/topthink/think-helper/src/helper/Str.php
@@ -0,0 +1,234 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\helper;
+
+class Str
+{
+
+ protected static $snakeCache = [];
+
+ protected static $camelCache = [];
+
+ protected static $studlyCache = [];
+
+ /**
+ * 检查字符串中是否包含某些字符串
+ * @param string $haystack
+ * @param string|array $needles
+ * @return bool
+ */
+ public static function contains(string $haystack, $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if ('' != $needle && mb_strpos($haystack, $needle) !== false) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 检查字符串是否以某些字符串结尾
+ *
+ * @param string $haystack
+ * @param string|array $needles
+ * @return bool
+ */
+ public static function endsWith(string $haystack, $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if ((string) $needle === static::substr($haystack, -static::length($needle))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 检查字符串是否以某些字符串开头
+ *
+ * @param string $haystack
+ * @param string|array $needles
+ * @return bool
+ */
+ public static function startsWith(string $haystack, $needles): bool
+ {
+ foreach ((array) $needles as $needle) {
+ if ('' != $needle && mb_strpos($haystack, $needle) === 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 获取指定长度的随机字母数字组合的字符串
+ *
+ * @param int $length
+ * @param int $type
+ * @param string $addChars
+ * @return string
+ */
+ public static function random(int $length = 6, int $type = null, string $addChars = ''): string
+ {
+ $str = '';
+ switch ($type) {
+ case 0:
+ $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' . $addChars;
+ break;
+ case 1:
+ $chars = str_repeat('0123456789', 3);
+ break;
+ case 2:
+ $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . $addChars;
+ break;
+ case 3:
+ $chars = 'abcdefghijklmnopqrstuvwxyz' . $addChars;
+ break;
+ case 4:
+ $chars = "们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书" . $addChars;
+ break;
+ default:
+ $chars = 'ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789' . $addChars;
+ break;
+ }
+ if ($length > 10) {
+ $chars = $type == 1 ? str_repeat($chars, $length) : str_repeat($chars, 5);
+ }
+ if ($type != 4) {
+ $chars = str_shuffle($chars);
+ $str = substr($chars, 0, $length);
+ } else {
+ for ($i = 0; $i < $length; $i++) {
+ $str .= mb_substr($chars, floor(mt_rand(0, mb_strlen($chars, 'utf-8') - 1)), 1);
+ }
+ }
+ return $str;
+ }
+
+ /**
+ * 字符串转小写
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function lower(string $value): string
+ {
+ return mb_strtolower($value, 'UTF-8');
+ }
+
+ /**
+ * 字符串转大写
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function upper(string $value): string
+ {
+ return mb_strtoupper($value, 'UTF-8');
+ }
+
+ /**
+ * 获取字符串的长度
+ *
+ * @param string $value
+ * @return int
+ */
+ public static function length(string $value): int
+ {
+ return mb_strlen($value);
+ }
+
+ /**
+ * 截取字符串
+ *
+ * @param string $string
+ * @param int $start
+ * @param int|null $length
+ * @return string
+ */
+ public static function substr(string $string, int $start, int $length = null): string
+ {
+ return mb_substr($string, $start, $length, 'UTF-8');
+ }
+
+ /**
+ * 驼峰转下划线
+ *
+ * @param string $value
+ * @param string $delimiter
+ * @return string
+ */
+ public static function snake(string $value, string $delimiter = '_'): string
+ {
+ $key = $value;
+
+ if (isset(static::$snakeCache[$key][$delimiter])) {
+ return static::$snakeCache[$key][$delimiter];
+ }
+
+ if (!ctype_lower($value)) {
+ $value = preg_replace('/\s+/u', '', $value);
+
+ $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value));
+ }
+
+ return static::$snakeCache[$key][$delimiter] = $value;
+ }
+
+ /**
+ * 下划线转驼峰(首字母小写)
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function camel(string $value): string
+ {
+ if (isset(static::$camelCache[$value])) {
+ return static::$camelCache[$value];
+ }
+
+ return static::$camelCache[$value] = lcfirst(static::studly($value));
+ }
+
+ /**
+ * 下划线转驼峰(首字母大写)
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function studly(string $value): string
+ {
+ $key = $value;
+
+ if (isset(static::$studlyCache[$key])) {
+ return static::$studlyCache[$key];
+ }
+
+ $value = ucwords(str_replace(['-', '_'], ' ', $value));
+
+ return static::$studlyCache[$key] = str_replace(' ', '', $value);
+ }
+
+ /**
+ * 转为首字母大写的标题格式
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function title(string $value): string
+ {
+ return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
+ }
+}
diff --git a/vendor/topthink/think-image/.gitignore b/vendor/topthink/think-image/.gitignore
new file mode 100755
index 0000000..828149d
--- /dev/null
+++ b/vendor/topthink/think-image/.gitignore
@@ -0,0 +1,4 @@
+/vendor/
+/thinkphp/
+/composer.lock
+/.idea/
\ No newline at end of file
diff --git a/vendor/topthink/think-image/.travis.yml b/vendor/topthink/think-image/.travis.yml
new file mode 100755
index 0000000..28ef8fb
--- /dev/null
+++ b/vendor/topthink/think-image/.travis.yml
@@ -0,0 +1,22 @@
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - hhvm
+
+matrix:
+ allow_failures:
+ - php: 7.0
+ - php: hhvm
+
+before_script:
+ - composer self-update
+ - composer install --prefer-source --no-interaction --dev
+
+script: phpunit --coverage-clover=coverage.xml --configuration=phpunit.xml
+
+after_success:
+ - bash <(curl -s https://codecov.io/bash)
\ No newline at end of file
diff --git a/vendor/topthink/think-image/LICENSE b/vendor/topthink/think-image/LICENSE
new file mode 100755
index 0000000..8dada3e
--- /dev/null
+++ b/vendor/topthink/think-image/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/topthink/think-image/README.md b/vendor/topthink/think-image/README.md
new file mode 100755
index 0000000..866a0b4
--- /dev/null
+++ b/vendor/topthink/think-image/README.md
@@ -0,0 +1,29 @@
+# The ThinkPHP5 Image Package
+
+[](https://travis-ci.org/top-think/think-image)
+[](https://codecov.io/github/top-think/think-image)
+[](https://github.com/top-think/think-image/releases)
+[](https://github.com/top-think/think-image/releases/latest)
+[](https://github.com/top-think/think-image/releases/latest)
+[](https://packagist.org/packages/topthink/think-image)
+[](https://packagist.org/packages/topthink/think-image)
+
+## 安装
+
+> composer require topthink/think-image
+
+## 使用
+
+~~~
+$image = \think\Image::open('./image.jpg');
+或者
+$image = \think\Image::open(request()->file('image'));
+
+
+$image->crop(...)
+ ->thumb(...)
+ ->water(...)
+ ->text(....)
+ ->save(..);
+
+~~~
\ No newline at end of file
diff --git a/vendor/topthink/think-image/composer.json b/vendor/topthink/think-image/composer.json
new file mode 100755
index 0000000..276d029
--- /dev/null
+++ b/vendor/topthink/think-image/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "topthink/think-image",
+ "description": "The ThinkPHP5 Image Package",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "yunwuxin",
+ "email": "448901948@qq.com"
+ }
+ ],
+ "require": {
+ "ext-gd": "*"
+ },
+ "require-dev": {
+ "topthink/framework": "^5.0",
+ "phpunit/phpunit": "4.8.*"
+ },
+ "config": {
+ "preferred-install": "dist"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\": "src"
+ }
+ }
+}
diff --git a/vendor/topthink/think-image/phpunit.xml b/vendor/topthink/think-image/phpunit.xml
new file mode 100755
index 0000000..ee93855
--- /dev/null
+++ b/vendor/topthink/think-image/phpunit.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ bootstrap="tests/autoload.php"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ syntaxCheck="false">
+ <testsuites>
+ <testsuite name="Package Test Suite">
+ <directory suffix=".php">./tests/</directory>
+ </testsuite>
+ </testsuites>
+ <listeners>
+ <listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener" />
+ </listeners>
+</phpunit>
diff --git a/vendor/topthink/think-image/src/Image.php b/vendor/topthink/think-image/src/Image.php
new file mode 100755
index 0000000..e28a13d
--- /dev/null
+++ b/vendor/topthink/think-image/src/Image.php
@@ -0,0 +1,610 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\image\Exception as ImageException;
+use think\image\gif\Gif;
+
+class Image
+{
+
+ /* 缩略图相关常量定义 */
+ const THUMB_SCALING = 1; //常量,标识缩略图等比例缩放类型
+ const THUMB_FILLED = 2; //常量,标识缩略图缩放后填充类型
+ const THUMB_CENTER = 3; //常量,标识缩略图居中裁剪类型
+ const THUMB_NORTHWEST = 4; //常量,标识缩略图左上角裁剪类型
+ const THUMB_SOUTHEAST = 5; //常量,标识缩略图右下角裁剪类型
+ const THUMB_FIXED = 6; //常量,标识缩略图固定尺寸缩放类型
+ /* 水印相关常量定义 */
+ const WATER_NORTHWEST = 1; //常量,标识左上角水印
+ const WATER_NORTH = 2; //常量,标识上居中水印
+ const WATER_NORTHEAST = 3; //常量,标识右上角水印
+ const WATER_WEST = 4; //常量,标识左居中水印
+ const WATER_CENTER = 5; //常量,标识居中水印
+ const WATER_EAST = 6; //常量,标识右居中水印
+ const WATER_SOUTHWEST = 7; //常量,标识左下角水印
+ const WATER_SOUTH = 8; //常量,标识下居中水印
+ const WATER_SOUTHEAST = 9; //常量,标识右下角水印
+ /* 翻转相关常量定义 */
+ const FLIP_X = 1; //X轴翻转
+ const FLIP_Y = 2; //Y轴翻转
+
+ /**
+ * 图像资源对象
+ *
+ * @var resource
+ */
+ protected $im;
+
+ /** @var Gif */
+ protected $gif;
+
+ /**
+ * 图像信息,包括 width, height, type, mime, size
+ *
+ * @var array
+ */
+ protected $info;
+
+ protected function __construct(\SplFileInfo $file)
+ {
+ //获取图像信息
+ $info = @getimagesize($file->getPathname());
+
+ //检测图像合法性
+ if (false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))) {
+ throw new ImageException('Illegal image file');
+ }
+
+ //设置图像信息
+ $this->info = [
+ 'width' => $info[0],
+ 'height' => $info[1],
+ 'type' => image_type_to_extension($info[2], false),
+ 'mime' => $info['mime'],
+ ];
+
+ //打开图像
+ if ('gif' == $this->info['type']) {
+ $this->gif = new Gif($file->getPathname());
+ $this->im = @imagecreatefromstring($this->gif->image());
+ } else {
+ $fun = "imagecreatefrom{$this->info['type']}";
+ $this->im = @$fun($file->getPathname());
+ }
+
+ if (empty($this->im)) {
+ throw new ImageException('Failed to create image resources!');
+ }
+
+ }
+
+ /**
+ * 打开一个图片文件
+ * @param \SplFileInfo|string $file
+ * @return Image
+ */
+ public static function open($file)
+ {
+ if (is_string($file)) {
+ $file = new \SplFileInfo($file);
+ }
+ if (!$file->isFile()) {
+ throw new ImageException('image file not exist');
+ }
+ return new self($file);
+ }
+
+ /**
+ * 保存图像
+ * @param string $pathname 图像保存路径名称
+ * @param null|string $type 图像类型
+ * @param int $quality 图像质量
+ * @param bool $interlace 是否对JPEG类型图像设置隔行扫描
+ * @return $this
+ */
+ public function save($pathname, $type = null, $quality = 80, $interlace = true)
+ {
+ //自动获取图像类型
+ if (is_null($type)) {
+ $type = $this->info['type'];
+ } else {
+ $type = strtolower($type);
+ }
+ //保存图像
+ if ('jpeg' == $type || 'jpg' == $type) {
+ //JPEG图像设置隔行扫描
+ imageinterlace($this->im, $interlace);
+ imagejpeg($this->im, $pathname, $quality);
+ } elseif ('gif' == $type && !empty($this->gif)) {
+ $this->gif->save($pathname);
+ } elseif ('png' == $type) {
+ //设定保存完整的 alpha 通道信息
+ imagesavealpha($this->im, true);
+ //ImagePNG生成图像的质量范围从0到9的
+ imagepng($this->im, $pathname, min((int) ($quality / 10), 9));
+ } else {
+ $fun = 'image' . $type;
+ $fun($this->im, $pathname);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 返回图像宽度
+ * @return int 图像宽度
+ */
+ public function width()
+ {
+ return $this->info['width'];
+ }
+
+ /**
+ * 返回图像高度
+ * @return int 图像高度
+ */
+ public function height()
+ {
+ return $this->info['height'];
+ }
+
+ /**
+ * 返回图像类型
+ * @return string 图像类型
+ */
+ public function type()
+ {
+ return $this->info['type'];
+ }
+
+ /**
+ * 返回图像MIME类型
+ * @return string 图像MIME类型
+ */
+ public function mime()
+ {
+ return $this->info['mime'];
+ }
+
+ /**
+ * 返回图像尺寸数组 0 - 图像宽度,1 - 图像高度
+ * @return array 图像尺寸
+ */
+ public function size()
+ {
+ return [$this->info['width'], $this->info['height']];
+ }
+
+ /**
+ * 旋转图像
+ * @param int $degrees 顺时针旋转的度数
+ * @return $this
+ */
+ public function rotate($degrees = 90)
+ {
+ do {
+ $img = imagerotate($this->im, -$degrees, imagecolorallocatealpha($this->im, 0, 0, 0, 127));
+ imagedestroy($this->im);
+ $this->im = $img;
+ } while (!empty($this->gif) && $this->gifNext());
+
+ $this->info['width'] = imagesx($this->im);
+ $this->info['height'] = imagesy($this->im);
+
+ return $this;
+ }
+
+ /**
+ * 翻转图像
+ * @param integer $direction 翻转轴,X或者Y
+ * @return $this
+ */
+ public function flip($direction = self::FLIP_X)
+ {
+ //原图宽度和高度
+ $w = $this->info['width'];
+ $h = $this->info['height'];
+
+ do {
+
+ $img = imagecreatetruecolor($w, $h);
+
+ switch ($direction) {
+ case self::FLIP_X:
+ for ($y = 0; $y < $h; $y++) {
+ imagecopy($img, $this->im, 0, $h - $y - 1, 0, $y, $w, 1);
+ }
+ break;
+ case self::FLIP_Y:
+ for ($x = 0; $x < $w; $x++) {
+ imagecopy($img, $this->im, $w - $x - 1, 0, $x, 0, 1, $h);
+ }
+ break;
+ default:
+ throw new ImageException('不支持的翻转类型');
+ }
+
+ imagedestroy($this->im);
+ $this->im = $img;
+
+ } while (!empty($this->gif) && $this->gifNext());
+
+ return $this;
+ }
+
+ /**
+ * 裁剪图像
+ *
+ * @param integer $w 裁剪区域宽度
+ * @param integer $h 裁剪区域高度
+ * @param integer $x 裁剪区域x坐标
+ * @param integer $y 裁剪区域y坐标
+ * @param integer $width 图像保存宽度
+ * @param integer $height 图像保存高度
+ *
+ * @return $this
+ */
+ public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null)
+ {
+ //设置保存尺寸
+ empty($width) && $width = $w;
+ empty($height) && $height = $h;
+ do {
+ //创建新图像
+ $img = imagecreatetruecolor($width, $height);
+ // 调整默认颜色
+ $color = imagecolorallocate($img, 255, 255, 255);
+ imagefill($img, 0, 0, $color);
+ //裁剪
+ imagecopyresampled($img, $this->im, 0, 0, $x, $y, $width, $height, $w, $h);
+ imagedestroy($this->im); //销毁原图
+ //设置新图像
+ $this->im = $img;
+ } while (!empty($this->gif) && $this->gifNext());
+ $this->info['width'] = (int) $width;
+ $this->info['height'] = (int) $height;
+ return $this;
+ }
+
+ /**
+ * 生成缩略图
+ *
+ * @param integer $width 缩略图最大宽度
+ * @param integer $height 缩略图最大高度
+ * @param int $type 缩略图裁剪类型
+ *
+ * @return $this
+ */
+ public function thumb($width, $height, $type = self::THUMB_SCALING)
+ {
+ //原图宽度和高度
+ $w = $this->info['width'];
+ $h = $this->info['height'];
+ /* 计算缩略图生成的必要参数 */
+ switch ($type) {
+ /* 等比例缩放 */
+ case self::THUMB_SCALING:
+ //原图尺寸小于缩略图尺寸则不进行缩略
+ if ($w < $width && $h < $height) {
+ return $this;
+ }
+ //计算缩放比例
+ $scale = min($width / $w, $height / $h);
+ //设置缩略图的坐标及宽度和高度
+ $x = $y = 0;
+ $width = $w * $scale;
+ $height = $h * $scale;
+ break;
+ /* 居中裁剪 */
+ case self::THUMB_CENTER:
+ //计算缩放比例
+ $scale = max($width / $w, $height / $h);
+ //设置缩略图的坐标及宽度和高度
+ $w = $width / $scale;
+ $h = $height / $scale;
+ $x = ($this->info['width'] - $w) / 2;
+ $y = ($this->info['height'] - $h) / 2;
+ break;
+ /* 左上角裁剪 */
+ case self::THUMB_NORTHWEST:
+ //计算缩放比例
+ $scale = max($width / $w, $height / $h);
+ //设置缩略图的坐标及宽度和高度
+ $x = $y = 0;
+ $w = $width / $scale;
+ $h = $height / $scale;
+ break;
+ /* 右下角裁剪 */
+ case self::THUMB_SOUTHEAST:
+ //计算缩放比例
+ $scale = max($width / $w, $height / $h);
+ //设置缩略图的坐标及宽度和高度
+ $w = $width / $scale;
+ $h = $height / $scale;
+ $x = $this->info['width'] - $w;
+ $y = $this->info['height'] - $h;
+ break;
+ /* 填充 */
+ case self::THUMB_FILLED:
+ //计算缩放比例
+ if ($w < $width && $h < $height) {
+ $scale = 1;
+ } else {
+ $scale = min($width / $w, $height / $h);
+ }
+ //设置缩略图的坐标及宽度和高度
+ $neww = $w * $scale;
+ $newh = $h * $scale;
+ $x = $this->info['width'] - $w;
+ $y = $this->info['height'] - $h;
+ $posx = ($width - $w * $scale) / 2;
+ $posy = ($height - $h * $scale) / 2;
+ do {
+ //创建新图像
+ $img = imagecreatetruecolor($width, $height);
+ // 调整默认颜色
+ $color = imagecolorallocate($img, 255, 255, 255);
+ imagefill($img, 0, 0, $color);
+ //裁剪
+ imagecopyresampled($img, $this->im, $posx, $posy, $x, $y, $neww, $newh, $w, $h);
+ imagedestroy($this->im); //销毁原图
+ $this->im = $img;
+ } while (!empty($this->gif) && $this->gifNext());
+ $this->info['width'] = (int) $width;
+ $this->info['height'] = (int) $height;
+ return $this;
+ /* 固定 */
+ case self::THUMB_FIXED:
+ $x = $y = 0;
+ break;
+ default:
+ throw new ImageException('不支持的缩略图裁剪类型');
+ }
+ /* 裁剪图像 */
+ return $this->crop($w, $h, $x, $y, $width, $height);
+ }
+
+ /**
+ * 添加水印
+ *
+ * @param string $source 水印图片路径
+ * @param int $locate 水印位置
+ * @param int $alpha 透明度
+ * @return $this
+ */
+ public function water($source, $locate = self::WATER_SOUTHEAST, $alpha = 100)
+ {
+ if (!is_file($source)) {
+ throw new ImageException('水印图像不存在');
+ }
+ //获取水印图像信息
+ $info = getimagesize($source);
+ if (false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))) {
+ throw new ImageException('非法水印文件');
+ }
+ //创建水印图像资源
+ $fun = 'imagecreatefrom' . image_type_to_extension($info[2], false);
+ $water = $fun($source);
+ //设定水印图像的混色模式
+ imagealphablending($water, true);
+ /* 设定水印位置 */
+ switch ($locate) {
+ /* 右下角水印 */
+ case self::WATER_SOUTHEAST:
+ $x = $this->info['width'] - $info[0];
+ $y = $this->info['height'] - $info[1];
+ break;
+ /* 左下角水印 */
+ case self::WATER_SOUTHWEST:
+ $x = 0;
+ $y = $this->info['height'] - $info[1];
+ break;
+ /* 左上角水印 */
+ case self::WATER_NORTHWEST:
+ $x = $y = 0;
+ break;
+ /* 右上角水印 */
+ case self::WATER_NORTHEAST:
+ $x = $this->info['width'] - $info[0];
+ $y = 0;
+ break;
+ /* 居中水印 */
+ case self::WATER_CENTER:
+ $x = ($this->info['width'] - $info[0]) / 2;
+ $y = ($this->info['height'] - $info[1]) / 2;
+ break;
+ /* 下居中水印 */
+ case self::WATER_SOUTH:
+ $x = ($this->info['width'] - $info[0]) / 2;
+ $y = $this->info['height'] - $info[1];
+ break;
+ /* 右居中水印 */
+ case self::WATER_EAST:
+ $x = $this->info['width'] - $info[0];
+ $y = ($this->info['height'] - $info[1]) / 2;
+ break;
+ /* 上居中水印 */
+ case self::WATER_NORTH:
+ $x = ($this->info['width'] - $info[0]) / 2;
+ $y = 0;
+ break;
+ /* 左居中水印 */
+ case self::WATER_WEST:
+ $x = 0;
+ $y = ($this->info['height'] - $info[1]) / 2;
+ break;
+ default:
+ /* 自定义水印坐标 */
+ if (is_array($locate)) {
+ list($x, $y) = $locate;
+ } else {
+ throw new ImageException('不支持的水印位置类型');
+ }
+ }
+ do {
+ //添加水印
+ $src = imagecreatetruecolor($info[0], $info[1]);
+ // 调整默认颜色
+ $color = imagecolorallocate($src, 255, 255, 255);
+ imagefill($src, 0, 0, $color);
+ imagecopy($src, $this->im, 0, 0, $x, $y, $info[0], $info[1]);
+ imagecopy($src, $water, 0, 0, 0, 0, $info[0], $info[1]);
+ imagecopymerge($this->im, $src, $x, $y, 0, 0, $info[0], $info[1], $alpha);
+ //销毁零时图片资源
+ imagedestroy($src);
+ } while (!empty($this->gif) && $this->gifNext());
+ //销毁水印资源
+ imagedestroy($water);
+ return $this;
+ }
+
+ /**
+ * 图像添加文字
+ *
+ * @param string $text 添加的文字
+ * @param string $font 字体路径
+ * @param integer $size 字号
+ * @param string $color 文字颜色
+ * @param int $locate 文字写入位置
+ * @param integer $offset 文字相对当前位置的偏移量
+ * @param integer $angle 文字倾斜角度
+ *
+ * @return $this
+ * @throws ImageException
+ */
+ public function text($text, $font, $size, $color = '#00000000',
+ $locate = self::WATER_SOUTHEAST, $offset = 0, $angle = 0) {
+
+ if (!is_file($font)) {
+ throw new ImageException("不存在的字体文件:{$font}");
+ }
+ //获取文字信息
+ $info = imagettfbbox($size, $angle, $font, $text);
+ $minx = min($info[0], $info[2], $info[4], $info[6]);
+ $maxx = max($info[0], $info[2], $info[4], $info[6]);
+ $miny = min($info[1], $info[3], $info[5], $info[7]);
+ $maxy = max($info[1], $info[3], $info[5], $info[7]);
+ /* 计算文字初始坐标和尺寸 */
+ $x = $minx;
+ $y = abs($miny);
+ $w = $maxx - $minx;
+ $h = $maxy - $miny;
+ /* 设定文字位置 */
+ switch ($locate) {
+ /* 右下角文字 */
+ case self::WATER_SOUTHEAST:
+ $x += $this->info['width'] - $w;
+ $y += $this->info['height'] - $h;
+ break;
+ /* 左下角文字 */
+ case self::WATER_SOUTHWEST:
+ $y += $this->info['height'] - $h;
+ break;
+ /* 左上角文字 */
+ case self::WATER_NORTHWEST:
+ // 起始坐标即为左上角坐标,无需调整
+ break;
+ /* 右上角文字 */
+ case self::WATER_NORTHEAST:
+ $x += $this->info['width'] - $w;
+ break;
+ /* 居中文字 */
+ case self::WATER_CENTER:
+ $x += ($this->info['width'] - $w) / 2;
+ $y += ($this->info['height'] - $h) / 2;
+ break;
+ /* 下居中文字 */
+ case self::WATER_SOUTH:
+ $x += ($this->info['width'] - $w) / 2;
+ $y += $this->info['height'] - $h;
+ break;
+ /* 右居中文字 */
+ case self::WATER_EAST:
+ $x += $this->info['width'] - $w;
+ $y += ($this->info['height'] - $h) / 2;
+ break;
+ /* 上居中文字 */
+ case self::WATER_NORTH:
+ $x += ($this->info['width'] - $w) / 2;
+ break;
+ /* 左居中文字 */
+ case self::WATER_WEST:
+ $y += ($this->info['height'] - $h) / 2;
+ break;
+ default:
+ /* 自定义文字坐标 */
+ if (is_array($locate)) {
+ list($posx, $posy) = $locate;
+ $x += $posx;
+ $y += $posy;
+ } else {
+ throw new ImageException('不支持的文字位置类型');
+ }
+ }
+ /* 设置偏移量 */
+ if (is_array($offset)) {
+ $offset = array_map('intval', $offset);
+ list($ox, $oy) = $offset;
+ } else {
+ $offset = intval($offset);
+ $ox = $oy = $offset;
+ }
+ /* 设置颜色 */
+ if (is_string($color) && 0 === strpos($color, '#')) {
+ $color = str_split(substr($color, 1), 2);
+ $color = array_map('hexdec', $color);
+ if (empty($color[3]) || $color[3] > 127) {
+ $color[3] = 0;
+ }
+ } elseif (!is_array($color)) {
+ throw new ImageException('错误的颜色值');
+ }
+ do {
+ /* 写入文字 */
+ $col = imagecolorallocatealpha($this->im, $color[0], $color[1], $color[2], $color[3]);
+ imagettftext($this->im, $size, $angle, $x + $ox, $y + $oy, $col, $font, $text);
+ } while (!empty($this->gif) && $this->gifNext());
+ return $this;
+ }
+
+ /**
+ * 切换到GIF的下一帧并保存当前帧
+ */
+ protected function gifNext()
+ {
+ ob_start();
+ ob_implicit_flush(0);
+ imagegif($this->im);
+ $img = ob_get_clean();
+ $this->gif->image($img);
+ $next = $this->gif->nextImage();
+ if ($next) {
+ imagedestroy($this->im);
+ $this->im = imagecreatefromstring($next);
+ return $next;
+ } else {
+ imagedestroy($this->im);
+ $this->im = imagecreatefromstring($this->gif->image());
+ return false;
+ }
+ }
+
+ /**
+ * 析构方法,用于销毁图像资源
+ */
+ public function __destruct()
+ {
+ empty($this->im) || imagedestroy($this->im);
+ }
+
+}
diff --git a/vendor/topthink/think-image/src/image/Exception.php b/vendor/topthink/think-image/src/image/Exception.php
new file mode 100755
index 0000000..2ebafd8
--- /dev/null
+++ b/vendor/topthink/think-image/src/image/Exception.php
@@ -0,0 +1,18 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\image;
+
+
+class Exception extends \RuntimeException
+{
+
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-image/src/image/gif/Decoder.php b/vendor/topthink/think-image/src/image/gif/Decoder.php
new file mode 100755
index 0000000..a563092
--- /dev/null
+++ b/vendor/topthink/think-image/src/image/gif/Decoder.php
@@ -0,0 +1,207 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\image\gif;
+
+
+class Decoder
+{
+ public $GIF_buffer = [];
+ public $GIF_arrays = [];
+ public $GIF_delays = [];
+ public $GIF_stream = "";
+ public $GIF_string = "";
+ public $GIF_bfseek = 0;
+ public $GIF_screen = [];
+ public $GIF_global = [];
+ public $GIF_sorted;
+ public $GIF_colorS;
+ public $GIF_colorC;
+ public $GIF_colorF;
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFDecoder ( $GIF_pointer )
+ ::
+ */
+ public function __construct($GIF_pointer)
+ {
+ $this->GIF_stream = $GIF_pointer;
+ $this->getByte(6); // GIF89a
+ $this->getByte(7); // Logical Screen Descriptor
+ $this->GIF_screen = $this->GIF_buffer;
+ $this->GIF_colorF = $this->GIF_buffer[4] & 0x80 ? 1 : 0;
+ $this->GIF_sorted = $this->GIF_buffer[4] & 0x08 ? 1 : 0;
+ $this->GIF_colorC = $this->GIF_buffer[4] & 0x07;
+ $this->GIF_colorS = 2 << $this->GIF_colorC;
+ if (1 == $this->GIF_colorF) {
+ $this->getByte(3 * $this->GIF_colorS);
+ $this->GIF_global = $this->GIF_buffer;
+ }
+
+ for ($cycle = 1; $cycle;) {
+ if ($this->getByte(1)) {
+ switch ($this->GIF_buffer[0]) {
+ case 0x21:
+ $this->readExtensions();
+ break;
+ case 0x2C:
+ $this->readDescriptor();
+ break;
+ case 0x3B:
+ $cycle = 0;
+ break;
+ }
+ } else {
+ $cycle = 0;
+ }
+ }
+ }
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFReadExtension ( )
+ ::
+ */
+ public function readExtensions()
+ {
+ $this->getByte(1);
+ for (; ;) {
+ $this->getByte(1);
+ if (($u = $this->GIF_buffer[0]) == 0x00) {
+ break;
+ }
+ $this->getByte($u);
+ /*
+ * 07.05.2007.
+ * Implemented a new line for a new function
+ * to determine the originaly delays between
+ * frames.
+ *
+ */
+ if (4 == $u) {
+ $this->GIF_delays[] = ($this->GIF_buffer[1] | $this->GIF_buffer[2] << 8);
+ }
+ }
+ }
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFReadExtension ( )
+ ::
+ */
+ public function readDescriptor()
+ {
+ $this->getByte(9);
+ $GIF_screen = $this->GIF_buffer;
+ $GIF_colorF = $this->GIF_buffer[8] & 0x80 ? 1 : 0;
+ if ($GIF_colorF) {
+ $GIF_code = $this->GIF_buffer[8] & 0x07;
+ $GIF_sort = $this->GIF_buffer[8] & 0x20 ? 1 : 0;
+ } else {
+ $GIF_code = $this->GIF_colorC;
+ $GIF_sort = $this->GIF_sorted;
+ }
+ $GIF_size = 2 << $GIF_code;
+ $this->GIF_screen[4] &= 0x70;
+ $this->GIF_screen[4] |= 0x80;
+ $this->GIF_screen[4] |= $GIF_code;
+ if ($GIF_sort) {
+ $this->GIF_screen[4] |= 0x08;
+ }
+ $this->GIF_string = "GIF87a";
+ $this->putByte($this->GIF_screen);
+ if (1 == $GIF_colorF) {
+ $this->getByte(3 * $GIF_size);
+ $this->putByte($this->GIF_buffer);
+ } else {
+ $this->putByte($this->GIF_global);
+ }
+ $this->GIF_string .= chr(0x2C);
+ $GIF_screen[8] &= 0x40;
+ $this->putByte($GIF_screen);
+ $this->getByte(1);
+ $this->putByte($this->GIF_buffer);
+ for (; ;) {
+ $this->getByte(1);
+ $this->putByte($this->GIF_buffer);
+ if (($u = $this->GIF_buffer[0]) == 0x00) {
+ break;
+ }
+ $this->getByte($u);
+ $this->putByte($this->GIF_buffer);
+ }
+ $this->GIF_string .= chr(0x3B);
+ /*
+ Add frames into $GIF_stream array...
+ */
+ $this->GIF_arrays[] = $this->GIF_string;
+ }
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFGetByte ( $len )
+ ::
+ */
+ public function getByte($len)
+ {
+ $this->GIF_buffer = [];
+ for ($i = 0; $i < $len; $i++) {
+ if ($this->GIF_bfseek > strlen($this->GIF_stream)) {
+ return 0;
+ }
+ $this->GIF_buffer[] = ord($this->GIF_stream{$this->GIF_bfseek++});
+ }
+ return 1;
+ }
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFPutByte ( $bytes )
+ ::
+ */
+ public function putByte($bytes)
+ {
+ for ($i = 0; $i < count($bytes); $i++) {
+ $this->GIF_string .= chr($bytes[$i]);
+ }
+ }
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: PUBLIC FUNCTIONS
+ ::
+ ::
+ :: GIFGetFrames ( )
+ ::
+ */
+ public function getFrames()
+ {
+ return ($this->GIF_arrays);
+ }
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFGetDelays ( )
+ ::
+ */
+ public function getDelays()
+ {
+ return ($this->GIF_delays);
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-image/src/image/gif/Encoder.php b/vendor/topthink/think-image/src/image/gif/Encoder.php
new file mode 100755
index 0000000..688f7a0
--- /dev/null
+++ b/vendor/topthink/think-image/src/image/gif/Encoder.php
@@ -0,0 +1,222 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\image\gif;
+
+class Encoder
+{
+ public $GIF = "GIF89a"; /* GIF header 6 bytes */
+ public $VER = "GIFEncoder V2.05"; /* Encoder version */
+ public $BUF = [];
+ public $LOP = 0;
+ public $DIS = 2;
+ public $COL = -1;
+ public $IMG = -1;
+ public $ERR = [
+ 'ERR00' => "Does not supported function for only one image!",
+ 'ERR01' => "Source is not a GIF image!",
+ 'ERR02' => "Unintelligible flag ",
+ 'ERR03' => "Does not make animation from animated GIF source",
+ ];
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFEncoder...
+ ::
+ */
+ public function __construct(
+ $GIF_src, $GIF_dly, $GIF_lop, $GIF_dis,
+ $GIF_red, $GIF_grn, $GIF_blu, $GIF_mod
+ )
+ {
+ if (!is_array($GIF_src)) {
+ printf("%s: %s", $this->VER, $this->ERR['ERR00']);
+ exit(0);
+ }
+ $this->LOP = ($GIF_lop > -1) ? $GIF_lop : 0;
+ $this->DIS = ($GIF_dis > -1) ? (($GIF_dis < 3) ? $GIF_dis : 3) : 2;
+ $this->COL = ($GIF_red > -1 && $GIF_grn > -1 && $GIF_blu > -1) ?
+ ($GIF_red | ($GIF_grn << 8) | ($GIF_blu << 16)) : -1;
+ for ($i = 0; $i < count($GIF_src); $i++) {
+ if (strtolower($GIF_mod) == "url") {
+ $this->BUF[] = fread(fopen($GIF_src[$i], "rb"), filesize($GIF_src[$i]));
+ } else if (strtolower($GIF_mod) == "bin") {
+ $this->BUF[] = $GIF_src[$i];
+ } else {
+ printf("%s: %s ( %s )!", $this->VER, $this->ERR['ERR02'], $GIF_mod);
+ exit(0);
+ }
+ if (substr($this->BUF[$i], 0, 6) != "GIF87a" && substr($this->BUF[$i], 0, 6) != "GIF89a") {
+ printf("%s: %d %s", $this->VER, $i, $this->ERR['ERR01']);
+ exit(0);
+ }
+ for ($j = (13 + 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07))), $k = true; $k; $j++) {
+ switch ($this->BUF[$i]{$j}) {
+ case "!":
+ if ((substr($this->BUF[$i], ($j + 3), 8)) == "NETSCAPE") {
+ printf("%s: %s ( %s source )!", $this->VER, $this->ERR['ERR03'], ($i + 1));
+ exit(0);
+ }
+ break;
+ case ";":
+ $k = false;
+ break;
+ }
+ }
+ }
+ $this->addHeader();
+ for ($i = 0; $i < count($this->BUF); $i++) {
+ $this->addFrames($i, $GIF_dly[$i]);
+ }
+ $this->addFooter();
+ }
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFAddHeader...
+ ::
+ */
+ public function addHeader()
+ {
+ if (ord($this->BUF[0]{10}) & 0x80) {
+ $cmap = 3 * (2 << (ord($this->BUF[0]{10}) & 0x07));
+ $this->GIF .= substr($this->BUF[0], 6, 7);
+ $this->GIF .= substr($this->BUF[0], 13, $cmap);
+ $this->GIF .= "!\377\13NETSCAPE2.0\3\1" . $this->word($this->LOP) . "\0";
+ }
+ }
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFAddFrames...
+ ::
+ */
+ public function addFrames($i, $d)
+ {
+ $Locals_img = '';
+ $Locals_str = 13 + 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07));
+ $Locals_end = strlen($this->BUF[$i]) - $Locals_str - 1;
+ $Locals_tmp = substr($this->BUF[$i], $Locals_str, $Locals_end);
+ $Global_len = 2 << (ord($this->BUF[0]{10}) & 0x07);
+ $Locals_len = 2 << (ord($this->BUF[$i]{10}) & 0x07);
+ $Global_rgb = substr($this->BUF[0], 13,
+ 3 * (2 << (ord($this->BUF[0]{10}) & 0x07)));
+ $Locals_rgb = substr($this->BUF[$i], 13,
+ 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07)));
+ $Locals_ext = "!\xF9\x04" . chr(($this->DIS << 2) + 0) .
+ chr(($d >> 0) & 0xFF) . chr(($d >> 8) & 0xFF) . "\x0\x0";
+ if ($this->COL > -1 && ord($this->BUF[$i]{10}) & 0x80) {
+ for ($j = 0; $j < (2 << (ord($this->BUF[$i]{10}) & 0x07)); $j++) {
+ if (
+ ord($Locals_rgb{3 * $j + 0}) == (($this->COL >> 16) & 0xFF) &&
+ ord($Locals_rgb{3 * $j + 1}) == (($this->COL >> 8) & 0xFF) &&
+ ord($Locals_rgb{3 * $j + 2}) == (($this->COL >> 0) & 0xFF)
+ ) {
+ $Locals_ext = "!\xF9\x04" . chr(($this->DIS << 2) + 1) .
+ chr(($d >> 0) & 0xFF) . chr(($d >> 8) & 0xFF) . chr($j) . "\x0";
+ break;
+ }
+ }
+ }
+ switch ($Locals_tmp{0}) {
+ case "!":
+ /**
+ * @var string $Locals_img ;
+ */
+ $Locals_img = substr($Locals_tmp, 8, 10);
+ $Locals_tmp = substr($Locals_tmp, 18, strlen($Locals_tmp) - 18);
+ break;
+ case ",":
+ $Locals_img = substr($Locals_tmp, 0, 10);
+ $Locals_tmp = substr($Locals_tmp, 10, strlen($Locals_tmp) - 10);
+ break;
+ }
+ if (ord($this->BUF[$i]{10}) & 0x80 && $this->IMG > -1) {
+ if ($Global_len == $Locals_len) {
+ if ($this->blockCompare($Global_rgb, $Locals_rgb, $Global_len)) {
+ $this->GIF .= ($Locals_ext . $Locals_img . $Locals_tmp);
+ } else {
+ $byte = ord($Locals_img{9});
+ $byte |= 0x80;
+ $byte &= 0xF8;
+ $byte |= (ord($this->BUF[0]{10}) & 0x07);
+ $Locals_img{9} = chr($byte);
+ $this->GIF .= ($Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp);
+ }
+ } else {
+ $byte = ord($Locals_img{9});
+ $byte |= 0x80;
+ $byte &= 0xF8;
+ $byte |= (ord($this->BUF[$i]{10}) & 0x07);
+ $Locals_img{9} = chr($byte);
+ $this->GIF .= ($Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp);
+ }
+ } else {
+ $this->GIF .= ($Locals_ext . $Locals_img . $Locals_tmp);
+ }
+ $this->IMG = 1;
+ }
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFAddFooter...
+ ::
+ */
+ public function addFooter()
+ {
+ $this->GIF .= ";";
+ }
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFBlockCompare...
+ ::
+ */
+ public function blockCompare($GlobalBlock, $LocalBlock, $Len)
+ {
+ for ($i = 0; $i < $Len; $i++) {
+ if (
+ $GlobalBlock{3 * $i + 0} != $LocalBlock{3 * $i + 0} ||
+ $GlobalBlock{3 * $i + 1} != $LocalBlock{3 * $i + 1} ||
+ $GlobalBlock{3 * $i + 2} != $LocalBlock{3 * $i + 2}
+ ) {
+ return (0);
+ }
+ }
+ return (1);
+ }
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GIFWord...
+ ::
+ */
+ public function word($int)
+ {
+ return (chr($int & 0xFF) . chr(($int >> 8) & 0xFF));
+ }
+
+ /*
+ :::::::::::::::::::::::::::::::::::::::::::::::::::
+ ::
+ :: GetAnimation...
+ ::
+ */
+ public function getAnimation()
+ {
+ return ($this->GIF);
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-image/src/image/gif/Gif.php b/vendor/topthink/think-image/src/image/gif/Gif.php
new file mode 100755
index 0000000..b890915
--- /dev/null
+++ b/vendor/topthink/think-image/src/image/gif/Gif.php
@@ -0,0 +1,88 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\image\gif;
+
+class Gif
+{
+ /**
+ * GIF帧列表
+ *
+ * @var array
+ */
+ private $frames = [];
+ /**
+ * 每帧等待时间列表
+ *
+ * @var array
+ */
+ private $delays = [];
+
+ /**
+ * 构造方法,用于解码GIF图片
+ *
+ * @param string $src GIF图片数据
+ * @param string $mod 图片数据类型
+ * @throws \Exception
+ */
+ public function __construct($src = null, $mod = 'url')
+ {
+ if (!is_null($src)) {
+ if ('url' == $mod && is_file($src)) {
+ $src = file_get_contents($src);
+ }
+ /* 解码GIF图片 */
+ try {
+ $de = new Decoder($src);
+ $this->frames = $de->getFrames();
+ $this->delays = $de->getDelays();
+ } catch (\Exception $e) {
+ throw new \Exception("解码GIF图片出错");
+ }
+ }
+ }
+
+ /**
+ * 设置或获取当前帧的数据
+ *
+ * @param string $stream 二进制数据流
+ * @return mixed 获取到的数据
+ */
+ public function image($stream = null)
+ {
+ if (is_null($stream)) {
+ $current = current($this->frames);
+ return false === $current ? reset($this->frames) : $current;
+ }
+ $this->frames[key($this->frames)] = $stream;
+ }
+
+ /**
+ * 将当前帧移动到下一帧
+ *
+ * @return string 当前帧数据
+ */
+ public function nextImage()
+ {
+ return next($this->frames);
+ }
+
+ /**
+ * 编码并保存当前GIF图片
+ *
+ * @param string $pathname 图片名称
+ */
+ public function save($pathname)
+ {
+ $gif = new Encoder($this->frames, $this->delays, 0, 2, 0, 0, 0, 'bin');
+ file_put_contents($pathname, $gif->getAnimation());
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-image/tests/CropTest.php b/vendor/topthink/think-image/tests/CropTest.php
new file mode 100755
index 0000000..26a0531
--- /dev/null
+++ b/vendor/topthink/think-image/tests/CropTest.php
@@ -0,0 +1,67 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace tests;
+
+use think\Image;
+
+class CropTest extends TestCase
+{
+ public function testJpeg()
+ {
+ $pathname = TEST_PATH . 'tmp/crop.jpg';
+ $image = Image::open($this->getJpeg());
+
+ $image->crop(200, 200, 100, 100, 300, 300)->save($pathname);
+
+ $this->assertEquals(300, $image->width());
+ $this->assertEquals(300, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+
+ public function testPng()
+ {
+ $pathname = TEST_PATH . 'tmp/crop.png';
+ $image = Image::open($this->getPng());
+
+ $image->crop(200, 200, 100, 100, 300, 300)->save($pathname);
+
+ $this->assertEquals(300, $image->width());
+ $this->assertEquals(300, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+
+ public function testGif()
+ {
+ $pathname = TEST_PATH . 'tmp/crop.gif';
+ $image = Image::open($this->getGif());
+
+ $image->crop(200, 200, 100, 100, 300, 300)->save($pathname);
+
+ $this->assertEquals(300, $image->width());
+ $this->assertEquals(300, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-image/tests/FlipTest.php b/vendor/topthink/think-image/tests/FlipTest.php
new file mode 100755
index 0000000..bf7a5e2
--- /dev/null
+++ b/vendor/topthink/think-image/tests/FlipTest.php
@@ -0,0 +1,43 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace tests;
+
+use think\Image;
+
+class FlipTest extends TestCase
+{
+ public function testJpeg()
+ {
+ $pathname = TEST_PATH . 'tmp/flip.jpg';
+ $image = Image::open($this->getJpeg());
+ $image->flip()->save($pathname);
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+
+
+ public function testGif()
+ {
+ $pathname = TEST_PATH . 'tmp/flip.gif';
+ $image = Image::open($this->getGif());
+ $image->flip(Image::FLIP_Y)->save($pathname);
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-image/tests/InfoTest.php b/vendor/topthink/think-image/tests/InfoTest.php
new file mode 100755
index 0000000..22132ca
--- /dev/null
+++ b/vendor/topthink/think-image/tests/InfoTest.php
@@ -0,0 +1,60 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace tests;
+
+use think\Image;
+
+class InfoTest extends TestCase
+{
+
+ public function testOpen()
+ {
+ $this->setExpectedException("\\think\\image\\Exception");
+ Image::open('');
+ }
+
+ public function testIllegal()
+ {
+ $this->setExpectedException("\\think\\image\\Exception", 'Illegal image file');
+ Image::open(TEST_PATH . 'images/test.bmp');
+ }
+
+ public function testJpeg()
+ {
+ $image = Image::open($this->getJpeg());
+ $this->assertEquals(800, $image->width());
+ $this->assertEquals(600, $image->height());
+ $this->assertEquals('jpeg', $image->type());
+ $this->assertEquals('image/jpeg', $image->mime());
+ $this->assertEquals([800, 600], $image->size());
+ }
+
+
+ public function testPng()
+ {
+ $image = Image::open($this->getPng());
+ $this->assertEquals(800, $image->width());
+ $this->assertEquals(600, $image->height());
+ $this->assertEquals('png', $image->type());
+ $this->assertEquals('image/png', $image->mime());
+ $this->assertEquals([800, 600], $image->size());
+ }
+
+ public function testGif()
+ {
+ $image = Image::open($this->getGif());
+ $this->assertEquals(380, $image->width());
+ $this->assertEquals(216, $image->height());
+ $this->assertEquals('gif', $image->type());
+ $this->assertEquals('image/gif', $image->mime());
+ $this->assertEquals([380, 216], $image->size());
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-image/tests/RotateTest.php b/vendor/topthink/think-image/tests/RotateTest.php
new file mode 100755
index 0000000..56572d2
--- /dev/null
+++ b/vendor/topthink/think-image/tests/RotateTest.php
@@ -0,0 +1,42 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace tests;
+
+use think\Image;
+
+class RotateTest extends TestCase
+{
+ public function testJpeg()
+ {
+ $pathname = TEST_PATH . 'tmp/rotate.jpg';
+ $image = Image::open($this->getJpeg());
+ $image->rotate(90)->save($pathname);
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+
+ public function testGif()
+ {
+ $pathname = TEST_PATH . 'tmp/rotate.gif';
+ $image = Image::open($this->getGif());
+ $image->rotate(90)->save($pathname);
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-image/tests/TestCase.php b/vendor/topthink/think-image/tests/TestCase.php
new file mode 100755
index 0000000..89ba1b4
--- /dev/null
+++ b/vendor/topthink/think-image/tests/TestCase.php
@@ -0,0 +1,33 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace tests;
+
+use think\File;
+
+abstract class TestCase extends \PHPUnit_Framework_TestCase
+{
+
+ protected function getJpeg()
+ {
+ return new File(TEST_PATH . 'images/test.jpg');
+ }
+
+ protected function getPng()
+ {
+ return new File(TEST_PATH . 'images/test.png');
+ }
+
+ protected function getGif()
+ {
+ return new File(TEST_PATH . 'images/test.gif');
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-image/tests/TextTest.php b/vendor/topthink/think-image/tests/TextTest.php
new file mode 100755
index 0000000..04506a2
--- /dev/null
+++ b/vendor/topthink/think-image/tests/TextTest.php
@@ -0,0 +1,58 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace tests;
+
+use think\Image;
+
+class TextTest extends TestCase
+{
+ public function testJpeg()
+ {
+ $pathname = TEST_PATH . 'tmp/text.jpg';
+ $image = Image::open($this->getJpeg());
+
+ $image->text('test', TEST_PATH . 'images/test.ttf', 12)->save($pathname);
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+
+ public function testPng()
+ {
+ $pathname = TEST_PATH . 'tmp/text.png';
+ $image = Image::open($this->getPng());
+
+ $image->text('test', TEST_PATH . 'images/test.ttf', 12)->save($pathname);
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+
+ public function testGif()
+ {
+ $pathname = TEST_PATH . 'tmp/text.gif';
+ $image = Image::open($this->getGif());
+
+ $image->text('test', TEST_PATH . 'images/test.ttf', 12)->save($pathname);
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-image/tests/ThumbTest.php b/vendor/topthink/think-image/tests/ThumbTest.php
new file mode 100755
index 0000000..07113c8
--- /dev/null
+++ b/vendor/topthink/think-image/tests/ThumbTest.php
@@ -0,0 +1,284 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace tests;
+
+use think\Image;
+
+class ThumbTest extends TestCase
+{
+ public function testJpeg()
+ {
+ $pathname = TEST_PATH . 'tmp/thumb.jpg';
+
+ //1
+ $image = Image::open($this->getJpeg());
+
+ $image->thumb(200, 200, Image::THUMB_CENTER)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //2
+ $image = Image::open($this->getJpeg());
+
+ $image->thumb(200, 200, Image::THUMB_SCALING)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(150, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //3
+ $image = Image::open($this->getJpeg());
+
+ $image->thumb(200, 200, Image::THUMB_FILLED)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //4
+ $image = Image::open($this->getJpeg());
+
+ $image->thumb(200, 200, Image::THUMB_NORTHWEST)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //5
+ $image = Image::open($this->getJpeg());
+
+ $image->thumb(200, 200, Image::THUMB_SOUTHEAST)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //6
+ $image = Image::open($this->getJpeg());
+
+ $image->thumb(200, 200, Image::THUMB_FIXED)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+
+
+ public function testPng()
+ {
+ $pathname = TEST_PATH . 'tmp/thumb.png';
+
+ //1
+ $image = Image::open($this->getPng());
+
+ $image->thumb(200, 200, Image::THUMB_CENTER)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //2
+ $image = Image::open($this->getPng());
+
+ $image->thumb(200, 200, Image::THUMB_SCALING)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(150, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //3
+ $image = Image::open($this->getPng());
+
+ $image->thumb(200, 200, Image::THUMB_FILLED)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //4
+ $image = Image::open($this->getPng());
+
+ $image->thumb(200, 200, Image::THUMB_NORTHWEST)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //5
+ $image = Image::open($this->getPng());
+
+ $image->thumb(200, 200, Image::THUMB_SOUTHEAST)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //6
+ $image = Image::open($this->getPng());
+
+ $image->thumb(200, 200, Image::THUMB_FIXED)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+
+ public function testGif()
+ {
+ $pathname = TEST_PATH . 'tmp/thumb.gif';
+
+ //1
+ $image = Image::open($this->getGif());
+
+ $image->thumb(200, 200, Image::THUMB_CENTER)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //2
+ $image = Image::open($this->getGif());
+
+ $image->thumb(200, 200, Image::THUMB_SCALING)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(113, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //3
+ $image = Image::open($this->getGif());
+
+ $image->thumb(200, 200, Image::THUMB_FILLED)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //4
+ $image = Image::open($this->getGif());
+
+ $image->thumb(200, 200, Image::THUMB_NORTHWEST)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //5
+ $image = Image::open($this->getGif());
+
+ $image->thumb(200, 200, Image::THUMB_SOUTHEAST)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+
+ //6
+ $image = Image::open($this->getGif());
+
+ $image->thumb(200, 200, Image::THUMB_FIXED)->save($pathname);
+
+ $this->assertEquals(200, $image->width());
+ $this->assertEquals(200, $image->height());
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-image/tests/WaterTest.php b/vendor/topthink/think-image/tests/WaterTest.php
new file mode 100755
index 0000000..b6a2bcc
--- /dev/null
+++ b/vendor/topthink/think-image/tests/WaterTest.php
@@ -0,0 +1,58 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace tests;
+
+use think\Image;
+
+class WaterTest extends TestCase
+{
+ public function testJpeg()
+ {
+ $pathname = TEST_PATH . 'tmp/water.jpg';
+ $image = Image::open($this->getJpeg());
+
+ $image->water(TEST_PATH . 'images/test.gif')->save($pathname);
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+
+ public function testPng()
+ {
+ $pathname = TEST_PATH . 'tmp/water.png';
+ $image = Image::open($this->getPng());
+
+ $image->water(TEST_PATH . 'images/test.gif')->save($pathname);
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+
+ public function testGif()
+ {
+ $pathname = TEST_PATH . 'tmp/water.gif';
+ $image = Image::open($this->getGif());
+
+ $image->water(TEST_PATH . 'images/test.jpg')->save($pathname);
+
+ $file = new \SplFileInfo($pathname);
+
+ $this->assertTrue($file->isFile());
+
+ @unlink($pathname);
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-image/tests/autoload.php b/vendor/topthink/think-image/tests/autoload.php
new file mode 100755
index 0000000..f2e8aae
--- /dev/null
+++ b/vendor/topthink/think-image/tests/autoload.php
@@ -0,0 +1,15 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+define('TEST_PATH', __DIR__ . '/');
+// 加载框架基础文件
+require __DIR__ . '/../thinkphp/base.php';
+\think\Loader::addNamespace('tests', TEST_PATH);
+\think\Loader::addNamespace('think', __DIR__ . '/../src/');
\ No newline at end of file
diff --git a/vendor/topthink/think-image/tests/images/test.bmp b/vendor/topthink/think-image/tests/images/test.bmp
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/vendor/topthink/think-image/tests/images/test.bmp
diff --git a/vendor/topthink/think-image/tests/images/test.gif b/vendor/topthink/think-image/tests/images/test.gif
new file mode 100755
index 0000000..c6d5472
--- /dev/null
+++ b/vendor/topthink/think-image/tests/images/test.gif
Binary files differ
diff --git a/vendor/topthink/think-image/tests/images/test.jpg b/vendor/topthink/think-image/tests/images/test.jpg
new file mode 100755
index 0000000..4bb6549
--- /dev/null
+++ b/vendor/topthink/think-image/tests/images/test.jpg
Binary files differ
diff --git a/vendor/topthink/think-image/tests/images/test.png b/vendor/topthink/think-image/tests/images/test.png
new file mode 100755
index 0000000..f4830e3
--- /dev/null
+++ b/vendor/topthink/think-image/tests/images/test.png
Binary files differ
diff --git a/vendor/topthink/think-image/tests/images/test.ttf b/vendor/topthink/think-image/tests/images/test.ttf
new file mode 100755
index 0000000..4f985c8
--- /dev/null
+++ b/vendor/topthink/think-image/tests/images/test.ttf
Binary files differ
diff --git a/vendor/topthink/think-image/tests/tmp/.gitignore b/vendor/topthink/think-image/tests/tmp/.gitignore
new file mode 100755
index 0000000..c96a04f
--- /dev/null
+++ b/vendor/topthink/think-image/tests/tmp/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/vendor/topthink/think-migration/.gitignore b/vendor/topthink/think-migration/.gitignore
new file mode 100755
index 0000000..30babff
--- /dev/null
+++ b/vendor/topthink/think-migration/.gitignore
@@ -0,0 +1,3 @@
+/vendor/
+.idea
+composer.lock
diff --git a/vendor/topthink/think-migration/LICENSE b/vendor/topthink/think-migration/LICENSE
new file mode 100755
index 0000000..2c76a5a
--- /dev/null
+++ b/vendor/topthink/think-migration/LICENSE
@@ -0,0 +1,32 @@
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn)
+All rights reserved。
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+Apache Licence是著名的非盈利开源组织Apache采用的协议。
+该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
+允许代码修改,再作为开源或商业软件发布。需要满足
+的条件:
+1. 需要给代码的用户一份Apache Licence ;
+2. 如果你修改了代码,需要在被修改的文件中说明;
+3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
+带有原来代码中的协议,商标,专利声明和其他原来作者规
+定需要包含的说明;
+4. 如果再发布的产品中包含一个Notice文件,则在Notice文
+件中需要带有本协议内容。你可以在Notice中增加自己的
+许可,但不可以表现为对Apache Licence构成更改。
+具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/vendor/topthink/think-migration/README.md b/vendor/topthink/think-migration/README.md
new file mode 100755
index 0000000..39e6584
--- /dev/null
+++ b/vendor/topthink/think-migration/README.md
@@ -0,0 +1,6 @@
+# thinkphp6 数据库迁移工具
+
+## 安装
+~~~
+composer require topthink/think-migration
+~~~
diff --git a/vendor/topthink/think-migration/composer.json b/vendor/topthink/think-migration/composer.json
new file mode 100755
index 0000000..78af31f
--- /dev/null
+++ b/vendor/topthink/think-migration/composer.json
@@ -0,0 +1,34 @@
+{
+ "name": "topthink/think-migration",
+ "authors": [
+ {
+ "name": "yunwuxin",
+ "email": "448901948@qq.com"
+ }
+ ],
+ "license": "Apache-2.0",
+ "autoload": {
+ "psr-4": {
+ "Phinx\\": "phinx/src/Phinx",
+ "think\\migration\\": "src"
+ }
+ },
+ "extra": {
+ "think": {
+ "services": [
+ "think\\migration\\Service"
+ ]
+ }
+ },
+ "require": {
+ "topthink/framework": "^6.0",
+ "topthink/think-helper": "^3.0.3"
+ },
+ "minimum-stability": "dev",
+ "suggest": {
+ "fzaninotto/faker": "Required to use the factory builder (^1.8)."
+ },
+ "require-dev": {
+ "fzaninotto/faker": "^1.8"
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/CHANGELOG.md b/vendor/topthink/think-migration/phinx/CHANGELOG.md
new file mode 100755
index 0000000..df1c2e4
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/CHANGELOG.md
@@ -0,0 +1,370 @@
+# Version History
+
+**0.6.5** (Thursday, 27 October 2016)
+
+* Documentation updates
+* Pull requests
+ * [#831](https://github.com/robmorgan/phinx/pull/831) Typos
+ * [#929](https://github.com/robmorgan/phinx/pull/929) Support glob brace for seed paths
+ * [#949](https://github.com/robmorgan/phinx/pull/949) Fix for Config::getMigrationBaseClassName
+ * [#958](https://github.com/robmorgan/phinx/pull/958) Allow console input to be used within adapters
+
+**0.6.4** (Wednesday, 27th July 2016)
+
+* Documentation updates
+* Pull requests
+ * [#909](https://github.com/robmorgan/phinx/pull/909) Declare test class properties
+ * [#910](https://github.com/robmorgan/phinx/pull/910), [#916](https://github.com/robmorgan/phinx/pull/916) Remove unused variables
+ * [#912](https://github.com/robmorgan/phinx/pull/912) ConfigInterface usage consistency
+ * [#914](https://github.com/robmorgan/phinx/pull/914) Set return values and @return documentation
+ * [#918](https://github.com/robmorgan/phinx/pull/918) Docblock correction for Phinx\Migration\Manager::executeSeed()
+ * [#921](https://github.com/robmorgan/phinx/pull/921) Add Phinx\Wrapper\TextWrapper::getSeed()
+* Bug fixes
+ * [#908](https://github.com/robmorgan/phinx/pull/908) Fix setting options for Column, ForeignKey and Index
+ * [#922](https://github.com/robmorgan/phinx/pull/922) SQLite adapter drops table on changeColumn if there's a foreign key
+
+**0.6.3** (Monday, 18th July 2016)
+
+* New features
+ * [#707](https://github.com/robmorgan/phinx/pull/707/files) Add arguments for timestamps columns names
+* Documentation cleanup
+* Bug fixes
+ * [#884](https://github.com/robmorgan/phinx/pull/884) Only rollback 1 migration when only 2 migrations exist
+ * Input and Output are now correctly supplied to migration template creation classes
+
+**0.6.2** (Thursday, 23rd June 2016)
+
+* Fix breakpoint support for Postgres
+* HHVM now passes all tests
+
+**0.6.1** (Tuesday, 21st June 2016)
+
+* Fix rollback when only 1 migration
+
+**0.6.0** (Tuesday, 21st June 2016)
+
+* Backward incompatibility - see [UPGRADE_0.6](UPGRADE_0.6.md) document
+* Introduce Input and Output access to migrations and template creation
+* New breakpoint command
+* Moved version history to this CHANGELOG.md document
+* More tests
+
+**0.5.5** (Friday, 17th May 2016)
+
+* Fix support for running multiple seeders
+* Bug fix for migration template source - defaults and command line
+* Bug fixes
+
+**0.5.4** (Monday, 25th April 2016)
+
+* Added support for running multiple seeders
+* Use `GLOB_BRACE` when finding migrations only if its available
+* Added support for MySQL `VARBINARY` column type
+* Minor bug fixes
+
+**0.5.3** (Monday, 7th March 2016)
+
+* Critical fix: allow `migration_name` to be `null`. Introduced in 0.5.2
+* Status command now shows migration start and end times
+* Bug fix for rolling back by date
+* Documentation improvements
+
+**0.5.2** (Tuesday, 1st March 2016)
+
+* Status command now includes missing migration names
+* Added support for Postgres table comments
+* Added `insert()` for the TablePrefixAdapter
+* Fixed the migration verbosity flag
+* Added MySQL 5.7 JSON support
+* Added support for MySQL `FULLTEXT` indexes
+* Postgres now supports `BIGSERIAL` for primary keys
+* Added support for MySQL index limits
+* Initial support for multiple migration paths (using glob)
+* Documentation improvements
+* Unit test enhancements
+
+**0.5.1** (Wednesday, 30th December 2015)
+
+* **PHP 5.3 is no longer supported!**
+* Add support for Symfony 3.0 components
+* Ensure that the `status` command returns the correct exit code
+* Allow `$version` to be passed into templates
+* Support for MySQL `YEAR` column type
+* Multiple documentation updates and corrections
+
+**0.5.0** (Monday, 30th November 2015)
+
+* Support for seeding data after database creation
+* The migration and seed directories are now nested under `db` by default
+* Moved `Phinx\Migration\Util` to `Phinx\Util\Util`
+* All `insert()` methods now have a slightly different method signature
+* Fixed key/insert operations for MySQL
+* Introduced `AdapterInterface::hasIndexByName()`
+* Improved `dropForeignKey()` handling for SQLite
+* Added support for the MySQL `binary` datatype. BLOBs now use the proper type.
+* The status command shows a count of pending migrations in JSON output
+* We are now testing against PHP 7
+
+**0.4.6** (Friday, 11th September 2015)
+
+* You can now set custom migration templates in the config files
+* Support for MySQL unsigned booleans
+* Support for Postgres `smallint` column types
+* Support for `AFTER` when using `changeColumn()` with MySQL
+* Support for `precision` and `scale` when using the Postgres `decimal` type
+* Fixed a bug where duplicate migration names could be used
+* The schema table is now created with a primary key
+* Fixed issues when using the MySQL `STRICT_TRANS_TABLE` mode
+* Improved the docs in the default migration template
+* Made Box PHAR ignore the bundled `phinx.yml` configuration file
+* Updated Box installer URL
+* Internal code improvements
+* Documentation improvements
+
+**0.4.5** (Tuesday, 1st September 2015)
+
+* The rollback command now supports a date argument
+* Fixed DBLIB DSN strings for Microsoft SQL Server
+* Postgres support for `jsonb` columns added
+* The `addTimestamps()` helper method no longer updates the `created_at` column
+* Fix for Postgres named foreign keys
+* Unit test improvements (including strict warnings)
+* Documentation improvements
+
+**0.4.4** (Sunday, 14th June 2015)
+
+* The `change` method is now the default
+* Added a generic adapter insert method. Warning: The implementation will change!
+* Updated Symfony depdencies to ~2.7
+* Support for MySQL `BLOB` column types
+* SQLite migration fixes
+* Documentation improvements
+
+**0.4.3** (Monday, 23rd Feburary 2015)
+
+* Postgres bugfix for modifying column DEFAULTs
+* MySQL bugfix for setting column INTEGER lengths
+* SQLite bugfix for creating multiple indexes with similar names
+
+**0.4.2.1** (Saturday, 7th Feburary 2015)
+
+* Proper release, updated docs
+
+**0.4.2** (Friday, 6th Feburary 2015)
+
+* Postgres support for `json` columns added
+* MySQL support for `enum` and `set` columns added
+* Allow setting `identity` option on columns
+* Template configuration and generation made more extensible
+* Created a base class for `ProxyAdapter` and `TablePrefixAdapter`
+* Switched to PSR-4
+
+**0.4.1** (Tuesday, 23rd December 2014)
+
+* MySQL support for reserved words in hasColumn and getColumns methods
+* Better MySQL Adapter test coverage and performance fixes
+* Updated dependent Symfony components to 2.6.x
+
+**0.4.0** (Sunday, 14th December 2014)
+
+* Adding initial support for running Phinx via a web interface
+* Support for table prefixes and suffixes
+* Bugfix for foreign key options
+* MySQL keeps column default when renaming columns
+* MySQL support for tiny/medium and longtext columns added
+* Changed SQL Server binary columns to varbinary
+* MySQL supports table comments
+* Postgres supports column comments
+* Empty strings are now supported for default column values
+* Booleans are now supported for default column values
+* Fixed SQL Server default constraint error when changing column types
+* Migration timestamps are now created in UTC
+* Locked Symfony Components to 2.5.0
+* Support for custom migration base classes
+* Cleaned up source code formatting
+* Migrations have access to the output stream
+* Support for custom PDO connections when a PHP config
+* Added support for Postgres UUID type
+* Fixed issue with Postgres dropping foreign keys
+
+**0.3.8** (Sunday, 5th October 2014)
+
+* Added new CHAR & Geospatial column types
+* Added MySQL unix socket support
+* Added precision & scale support for SQL Server
+* Several bug fixes for SQLite
+* Improved error messages
+* Overall code optimizations
+* Optimizations to MySQL hasTable method
+
+**0.3.7** (Tuesday, 12th August 2014)
+
+* Smarter configuration file support
+* Support for Postgres Schemas
+* Fixed charset support for Microsoft SQL Server
+* Fix for Unique indexes in all adapters
+* Improvements for MySQL foreign key migration syntax
+* Allow MySQL column types with extra info
+* Fixed SQLite autoincrement behaviour
+* PHPDoc improvements
+* Documentation improvements
+* Unit test improvements
+* Removing primary_key as a type
+
+**0.3.6** (Sunday, 29th June 2014)
+
+* Add custom adapter support
+* Fix PHP 5.3 compatibility for SQL Server
+
+**0.3.5** (Saturday, 21st June 2014)
+
+* Added Microsoft SQL Server support
+* Removed Primary Key column type
+* Cleaned up and optimized many methods
+* Updated Symfony dependencies to v2.5.0
+* PHPDoc improvements
+
+**0.3.4** (Sunday, 27th April 2014)
+
+* Added support MySQL unsigned integer, biginteger, float and decimal types
+* Added JSON output support for the status command
+* Fix a bug where Postgres couldnt rollback foreign keys
+* Moved Phinx type references to interface constants
+* Fixed a bug with SQLite in-memory databases
+
+**0.3.3** (Saturday, 22nd March 2014)
+
+* Added support for JSON configuration
+* Named index support for all adapters (thanks @archer308)
+* Updated Composer dependencies
+* Fix for SQLite Integer Type
+* Fix for MySQL port option
+
+**0.3.2** (Monday, 24th February 2014)
+
+* Adding better Postgres type support
+
+**0.3.1** (Sunday, 23rd February 2014)
+
+* Adding MySQL charset support to the YAML config
+* Removing trailing spaces
+
+**0.3.0** (Sunday, 2nd February 2014)
+
+* PSR-2 support
+* Method to add timestamps easily to tables
+* Support for column comments in the Postgres adapter
+* Fixes for MySQL driver options
+* Fixes for MySQL biginteger type
+
+**0.2.9** (Saturday, 16th November 2013)
+
+* Added SQLite Support
+* Improving the unit tests, especially on Windows
+
+**0.2.8** (Sunday, 25th August 2013)
+
+* Added PostgresSQL Support
+
+**0.2.7** (Saturday, 24th August 2013)
+
+* Critical fix for a token parsing bug
+* Removed legacy build system
+* Improving docs
+
+**0.2.6** (Saturday, 24th August 2013)
+
+* Added support for environment vars in config files
+* Added support for environment vars to set the Phinx Env
+* Improving docs
+* Fixed a bug with column names in indexes
+* Changes for developers in regards to the unit tests
+
+**0.2.5** (Sunday, 26th May 2013)
+
+* Added support for Box Phar Archive Packaging
+* Added support for MYSQL_ATTR driver options
+* Fixed a bug where foreign keys cannot be removed
+* Added support for MySQL table collation
+* Updated Composer dependencies
+* Removed verbosity options, now relies on Symfony instead
+* Improved unit tests
+
+**0.2.4** (Saturday, 20th April 2013)
+
+* The Rollback command supports the verbosity parameter
+* The Rollback command has more detailed output
+* Table::dropForeignKey now returns the table instance
+
+**0.2.3** (Saturday, 6th April 2013)
+
+* Fixed a reporting bug when Phinx couldn't connect to a database
+* Added support for the MySQL 'ON UPDATE' function
+* Phinx timestamp is now mapped to MySQL timestamp instead of datetime
+* Fixed a docs typo for the minimum PHP version
+* Added UTF8 support for migrations
+* Changed regex to handle migration names differently
+* Added support for custom MySQL table engines such as MyISAM
+* Added the change method to the migration template
+
+**0.2.2** (Sunday, 3rd March 2013)
+
+* Added a new verbosity parameter to see more output when migrating
+* Support for PHP config files
+
+**0.2.1** (Sunday, 3rd March 2013)
+
+* Broken Release. Do not use!
+* Unit tests no longer rely on the default phinx.yml file
+* Running migrate for the first time does not give php warnings
+* `default_migration_table` is now actually supported
+* Updated docblocks to 2013.
+
+**0.2.0** (Sunday, 13th January 2013)
+
+* First Birthday Release
+* Added Reversible Migrations
+* Removed options parameter from AdapterInterface::hasColumn()
+
+**0.1.7** (Tuesday, 8th January 2013)
+
+* Improved documentation on the YAML configuration file
+* Removed options parameter from AdapterInterface::dropIndex()
+
+**0.1.6** (Sunday, 9th December 2012)
+
+* Added foreign key support
+* Removed PEAR support
+* Support for auto_increment on custom id columns
+* Bugfix for column default value 0
+* Documentation improvements
+
+**0.1.5** (Sunday, 4th November 2012)
+
+* Added a test command
+* Added transactions for adapters that support it
+* Changing the Table API to use pending column methods
+* Fixed a bug when defining multiple indexes on a table
+
+**0.1.4** (Sunday, 21st October 2012)
+
+* Documentation Improvements
+
+**0.1.3** (Saturday, 20th October 2012)
+
+* Fixed broken composer support
+
+**0.1.2** (Saturday, 20th October 2012)
+
+* Added composer support
+* Now forces migrations to be in CamelCase format
+* Now specifies the database name when migrating
+* Creates the internal log table using its API instead of raw SQL
+
+**0.1.1** (Wednesday, 13th June 2012)
+
+* First point release. Ready for limited production use.
+
+**0.1.0** (Friday, 13th January 2012)
+
+* Initial public release.
diff --git a/vendor/topthink/think-migration/phinx/CONTRIBUTING.md b/vendor/topthink/think-migration/phinx/CONTRIBUTING.md
new file mode 100755
index 0000000..73b54ac
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/CONTRIBUTING.md
@@ -0,0 +1,66 @@
+# How to contribute to Phinx
+
+Phinx relies heavily on external contributions in order to make it the best database migration
+tool possible. Without the support of our 115+ contributors we wouldn't be where we are today!
+We encourage anyone to submit documentation enhancements and code.
+
+Issues, feature requests and bugs should be submitted using the Github issue tool:
+https://github.com/robmorgan/phinx/issues.
+
+This document briefly outlines the requirements to contribute code to Phinx.
+
+## Considerations
+
+Before you submit your pull request take a moment to answer the following questions.
+
+Answering '**YES**' to all questions will increase the likelihood of your PR being accepted!
+
+* Have I implemented my feature for as many database adapters as possible?
+* Does my new feature improve Phinx's performance or keep it consistent?
+* Does my feature fit within the database migration space?
+* Is the code entirely my own and free from any commercial licensing?
+* Am I happy to release my code under the MIT license?
+* Is my code formatted using the [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) coding standard?
+
+**Note:** We accept bug fixes much faster into our development branch than features.
+
+## Getting Started
+
+Great, so you want to contribute. Let's get started:
+
+1. Start by forking Phinx on GitHub: https://github.com/robmorgan/phinx
+
+1. Clone your repository to a local directory on your development box.
+
+1. If you do not have Composer set up already, install it:
+
+ ```
+ curl -sS https://getcomposer.org/installer | php
+ ```
+
+1. Change to your Phinx clone directory and pull the necessary dependencies:
+
+ ```
+ php composer.phar install
+ ```
+
+1. Copy the `phpunit.xml.dist` template to `phpunit.xml` and change the configuration to suit your environment. If you are not using any particular adapter you can disable it in the `phpunit.xml` file.
+
+1. Run the unit tests locally to ensure they pass:
+
+ ```
+ php vendor/bin/phpunit --config phpunit.xml
+ ```
+
+1. Write the code and unit tests for your bug fix or feature.
+
+1. Add any relevant documentation.
+
+1. Run the unit tests again and ensure they pass.
+
+1. Open a pull request on the Github project page. Ensure the code is being merged into the latest development branch (e.g: `0.5.x-dev`) and not `master`.
+
+## Documentation
+
+The Phinx documentation is stored in the **docs** directory using the [RestructedText](http://docutils.sourceforge.net/rst.html) format. All documentation merged to `master` is automatically published to the Phinx documentation site available
+at: http://docs.phinx.org. Keep this in mind when submitting your PR, or ask someone to merge the development branch back down to master.
diff --git a/vendor/topthink/think-migration/phinx/LICENSE b/vendor/topthink/think-migration/phinx/LICENSE
new file mode 100755
index 0000000..26ade90
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/LICENSE
@@ -0,0 +1,9 @@
+(The MIT license)
+
+Copyright (c) 2014 Rob Morgan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/vendor/topthink/think-migration/phinx/README.md b/vendor/topthink/think-migration/phinx/README.md
new file mode 100755
index 0000000..ed1badc
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/README.md
@@ -0,0 +1,128 @@
+# [Phinx](https://phinx.org): Simple PHP Database Migrations
+
+[](https://travis-ci.org/robmorgan/phinx)
+[](https://ci.appveyor.com/project/robmorgan/phinx)
+[](https://scrutinizer-ci.com/g/robmorgan/phinx/)
+[](https://packagist.org/packages/robmorgan/phinx)
+[](https://packagist.org/packages/robmorgan/phinx)
+
+Phinx makes it ridiculously easy to manage the database migrations for your PHP app. In less than 5 minutes you can install Phinx and create your first database migration. Phinx is just about migrations without all the bloat of a database ORM system or framework.
+
+**Check out http://docs.phinx.org for the comprehensive documentation.**
+
+
+
+### Features
+
+* Write database migrations using database agnostic PHP code.
+* Migrate up and down.
+* Migrate on deployment.
+* Seed data after database creation.
+* Get going in less than 5 minutes.
+* Stop worrying about the state of your database.
+* Take advantage of SCM features such as branching.
+* Integrate with any app.
+
+### Supported Adapters
+
+Phinx natively supports the following database adapters:
+
+* MySQL
+* PostgreSQL
+* SQLite
+* Microsoft SQL Server
+
+## Install & Run
+
+### Composer
+
+The fastest way to install Phinx is to add it to your project using Composer (http://getcomposer.org/).
+
+1. Install Composer:
+
+ ```
+ curl -sS https://getcomposer.org/installer | php
+ ```
+
+1. Require Phinx as a dependency using Composer:
+
+ ```
+ php composer.phar require robmorgan/phinx
+ ```
+
+1. Install Phinx:
+
+ ```
+ php composer.phar install
+ ```
+
+1. Execute Phinx:
+
+ ```
+ php vendor/bin/phinx
+ ```
+
+### As a Phar
+
+You can also use the Box application to build Phinx as a Phar archive (https://box-project.github.io/box2/).
+
+1. Clone Phinx from GitHub
+
+ ```
+ git clone git://github.com/robmorgan/phinx.git
+ cd phinx
+ ```
+
+1. Install Composer
+
+ ```
+ curl -s https://getcomposer.org/installer | php
+ ```
+
+1. Install the Phinx dependencies
+
+ ```
+ php composer.phar install
+ ```
+
+1. Install Box:
+
+ ```
+ curl -LSs https://box-project.github.io/box2/installer.php | php
+ ```
+
+1. Create a Phar archive
+
+ ```
+ php box.phar build
+ ```
+
+## Documentation
+
+Check out http://docs.phinx.org for the comprehensive documentation.
+
+## Contributing
+
+Please read the [CONTRIBUTING](CONTRIBUTING.md) document.
+
+## News & Updates
+
+Follow Rob (@\_rjm\_) on Twitter to stay up to date (http://twitter.com/_rjm_)
+
+## Misc
+
+### Version History
+
+Please read the [CHANGELOG](CHANGELOG.md) document.
+
+### License
+
+(The MIT license)
+
+Copyright (c) 2016 Rob Morgan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterFactory.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterFactory.php
new file mode 100755
index 0000000..90674f9
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterFactory.php
@@ -0,0 +1,181 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Migration
+ */
+namespace Phinx\Db\Adapter;
+
+/**
+ * Adapter factory and registry.
+ *
+ * Used for registering adapters and creating instances of adapters.
+ *
+ * @author Woody Gilk <woody.gilk@gmail.com>
+ */
+class AdapterFactory
+{
+ /**
+ * @var AdapterFactory
+ */
+ protected static $instance;
+
+ /**
+ * Get the factory singleton instance.
+ *
+ * @return AdapterFactory
+ */
+ public static function instance()
+ {
+ if (!static::$instance) {
+ static::$instance = new static();
+ }
+ return static::$instance;
+ }
+
+ /**
+ * Class map of database adapters, indexed by PDO::ATTR_DRIVER_NAME.
+ *
+ * @var array
+ */
+ protected $adapters = array(
+ 'mysql' => 'Phinx\Db\Adapter\MysqlAdapter',
+ 'pgsql' => 'Phinx\Db\Adapter\PostgresAdapter',
+ 'sqlite' => 'Phinx\Db\Adapter\SQLiteAdapter',
+ 'sqlsrv' => 'Phinx\Db\Adapter\SqlServerAdapter',
+ );
+
+ /**
+ * Class map of adapters wrappers, indexed by name.
+ *
+ * @var array
+ */
+ protected $wrappers = array(
+ 'prefix' => 'Phinx\Db\Adapter\TablePrefixAdapter',
+ 'proxy' => 'Phinx\Db\Adapter\ProxyAdapter',
+ );
+
+ /**
+ * Add or replace an adapter with a fully qualified class name.
+ *
+ * @throws \RuntimeException
+ * @param string $name
+ * @param string $class
+ * @return $this
+ */
+ public function registerAdapter($name, $class)
+ {
+ if (!is_subclass_of($class, 'Phinx\Db\Adapter\AdapterInterface')) {
+ throw new \RuntimeException(sprintf(
+ 'Adapter class "%s" must implement Phinx\\Db\\Adapter\\AdapterInterface',
+ $class
+ ));
+ }
+ $this->adapters[$name] = $class;
+ return $this;
+ }
+
+ /**
+ * Get an adapter class by name.
+ *
+ * @throws \RuntimeException
+ * @param string $name
+ * @return string
+ */
+ protected function getClass($name)
+ {
+ if (empty($this->adapters[$name])) {
+ throw new \RuntimeException(sprintf(
+ 'Adapter "%s" has not been registered',
+ $name
+ ));
+ }
+ return $this->adapters[$name];
+ }
+
+ /**
+ * Get an adapter instance by name.
+ *
+ * @param string $name
+ * @param array $options
+ * @return AdapterInterface
+ */
+ public function getAdapter($name, array $options)
+ {
+ $class = $this->getClass($name);
+ return new $class($options);
+ }
+
+ /**
+ * Add or replace a wrapper with a fully qualified class name.
+ *
+ * @throws \RuntimeException
+ * @param string $name
+ * @param string $class
+ * @return $this
+ */
+ public function registerWrapper($name, $class)
+ {
+ if (!is_subclass_of($class, 'Phinx\Db\Adapter\WrapperInterface')) {
+ throw new \RuntimeException(sprintf(
+ 'Wrapper class "%s" must be implement Phinx\\Db\\Adapter\\WrapperInterface',
+ $class
+ ));
+ }
+ $this->wrappers[$name] = $class;
+ return $this;
+ }
+
+ /**
+ * Get a wrapper class by name.
+ *
+ * @throws \RuntimeException
+ * @param string $name
+ * @return string
+ */
+ protected function getWrapperClass($name)
+ {
+ if (empty($this->wrappers[$name])) {
+ throw new \RuntimeException(sprintf(
+ 'Wrapper "%s" has not been registered',
+ $name
+ ));
+ }
+ return $this->wrappers[$name];
+ }
+
+ /**
+ * Get a wrapper instance by name.
+ *
+ * @param string $name
+ * @param AdapterInterface $adapter
+ * @return AdapterInterface
+ */
+ public function getWrapper($name, AdapterInterface $adapter)
+ {
+ $class = $this->getWrapperClass($name);
+ return new $class($adapter);
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterInterface.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterInterface.php
new file mode 100755
index 0000000..f2e9fe1
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterInterface.php
@@ -0,0 +1,510 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace Phinx\Db\Adapter;
+
+use think\console\Input as InputInterface;
+use think\console\Output as OutputInterface;
+use Phinx\Db\Table;
+use Phinx\Db\Table\Column;
+use Phinx\Db\Table\Index;
+use Phinx\Db\Table\ForeignKey;
+use Phinx\Migration\MigrationInterface;
+
+/**
+ * Adapter Interface.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+interface AdapterInterface
+{
+ const PHINX_TYPE_STRING = 'string';
+ const PHINX_TYPE_CHAR = 'char';
+ const PHINX_TYPE_TEXT = 'text';
+ const PHINX_TYPE_INTEGER = 'integer';
+ const PHINX_TYPE_BIG_INTEGER = 'biginteger';
+ const PHINX_TYPE_FLOAT = 'float';
+ const PHINX_TYPE_DECIMAL = 'decimal';
+ const PHINX_TYPE_DATETIME = 'datetime';
+ const PHINX_TYPE_TIMESTAMP = 'timestamp';
+ const PHINX_TYPE_TIME = 'time';
+ const PHINX_TYPE_DATE = 'date';
+ const PHINX_TYPE_BINARY = 'binary';
+ const PHINX_TYPE_VARBINARY = 'varbinary';
+ const PHINX_TYPE_BLOB = 'blob';
+ const PHINX_TYPE_BOOLEAN = 'boolean';
+ const PHINX_TYPE_JSON = 'json';
+ const PHINX_TYPE_JSONB = 'jsonb';
+ const PHINX_TYPE_UUID = 'uuid';
+ const PHINX_TYPE_FILESTREAM = 'filestream';
+
+ // Geospatial database types
+ const PHINX_TYPE_GEOMETRY = 'geometry';
+ const PHINX_TYPE_POINT = 'point';
+ const PHINX_TYPE_LINESTRING = 'linestring';
+ const PHINX_TYPE_POLYGON = 'polygon';
+
+ // only for mysql so far
+ const PHINX_TYPE_ENUM = 'enum';
+ const PHINX_TYPE_SET = 'set';
+
+ /**
+ * Get all migrated version numbers.
+ *
+ * @return array
+ */
+ public function getVersions();
+
+ /**
+ * Get all migration log entries, indexed by version number.
+ *
+ * @return array
+ */
+ public function getVersionLog();
+
+ /**
+ * Set adapter configuration options.
+ *
+ * @param array $options
+ * @return AdapterInterface
+ */
+ public function setOptions(array $options);
+
+ /**
+ * Get all adapter options.
+ *
+ * @return array
+ */
+ public function getOptions();
+
+ /**
+ * Check if an option has been set.
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public function hasOption($name);
+
+ /**
+ * Get a single adapter option, or null if the option does not exist.
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getOption($name);
+
+ /**
+ * Sets the console input.
+ *
+ * @param InputInterface $input Input
+ * @return AdapterInterface
+ */
+ public function setInput(InputInterface $input);
+
+ /**
+ * Gets the console input.
+ *
+ * @return InputInterface
+ */
+ public function getInput();
+
+ /**
+ * Sets the console output.
+ *
+ * @param OutputInterface $output Output
+ * @return AdapterInterface
+ */
+ public function setOutput(OutputInterface $output);
+
+ /**
+ * Gets the console output.
+ *
+ * @return OutputInterface
+ */
+ public function getOutput();
+
+ /**
+ * Records a migration being run.
+ *
+ * @param MigrationInterface $migration Migration
+ * @param string $direction Direction
+ * @param int $startTime Start Time
+ * @param int $endTime End Time
+ * @return AdapterInterface
+ */
+ public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime);
+
+ /**
+ * Toggle a migration breakpoint.
+ *
+ * @param MigrationInterface $migration
+ *
+ * @return AdapterInterface
+ */
+ public function toggleBreakpoint(MigrationInterface $migration);
+
+ /**
+ * Reset all migration breakpoints.
+ *
+ * @return int The number of breakpoints reset
+ */
+ public function resetAllBreakpoints();
+
+ /**
+ * Does the schema table exist?
+ *
+ * @deprecated use hasTable instead.
+ * @return boolean
+ */
+ public function hasSchemaTable();
+
+ /**
+ * Creates the schema table.
+ *
+ * @return void
+ */
+ public function createSchemaTable();
+
+ /**
+ * Returns the adapter type.
+ *
+ * @return string
+ */
+ public function getAdapterType();
+
+ /**
+ * Initializes the database connection.
+ *
+ * @throws \RuntimeException When the requested database driver is not installed.
+ * @return void
+ */
+ public function connect();
+
+ /**
+ * Closes the database connection.
+ *
+ * @return void
+ */
+ public function disconnect();
+
+ /**
+ * Does the adapter support transactions?
+ *
+ * @return boolean
+ */
+ public function hasTransactions();
+
+ /**
+ * Begin a transaction.
+ *
+ * @return void
+ */
+ public function beginTransaction();
+
+ /**
+ * Commit a transaction.
+ *
+ * @return void
+ */
+ public function commitTransaction();
+
+ /**
+ * Rollback a transaction.
+ *
+ * @return void
+ */
+ public function rollbackTransaction();
+
+ /**
+ * Executes a SQL statement and returns the number of affected rows.
+ *
+ * @param string $sql SQL
+ * @return int
+ */
+ public function execute($sql);
+
+ /**
+ * Executes a SQL statement and returns the result as an array.
+ *
+ * @param string $sql SQL
+ * @return array
+ */
+ public function query($sql);
+
+ /**
+ * Executes a query and returns only one row as an array.
+ *
+ * @param string $sql SQL
+ * @return array
+ */
+ public function fetchRow($sql);
+
+ /**
+ * Executes a query and returns an array of rows.
+ *
+ * @param string $sql SQL
+ * @return array
+ */
+ public function fetchAll($sql);
+
+ /**
+ * Inserts data into a table.
+ *
+ * @param Table $table where to insert data
+ * @param array $row
+ * @return void
+ */
+ public function insert(Table $table, $row);
+
+ /**
+ * Quotes a table name for use in a query.
+ *
+ * @param string $tableName Table Name
+ * @return string
+ */
+ public function quoteTableName($tableName);
+
+ /**
+ * Quotes a column name for use in a query.
+ *
+ * @param string $columnName Table Name
+ * @return string
+ */
+ public function quoteColumnName($columnName);
+
+ /**
+ * Checks to see if a table exists.
+ *
+ * @param string $tableName Table Name
+ * @return boolean
+ */
+ public function hasTable($tableName);
+
+ /**
+ * Creates the specified database table.
+ *
+ * @param Table $table Table
+ * @return void
+ */
+ public function createTable(Table $table);
+
+ /**
+ * Renames the specified database table.
+ *
+ * @param string $tableName Table Name
+ * @param string $newName New Name
+ * @return void
+ */
+ public function renameTable($tableName, $newName);
+
+ /**
+ * Drops the specified database table.
+ *
+ * @param string $tableName Table Name
+ * @return void
+ */
+ public function dropTable($tableName);
+
+ /**
+ * Returns table columns
+ *
+ * @param string $tableName Table Name
+ * @return Column[]
+ */
+ public function getColumns($tableName);
+
+ /**
+ * Checks to see if a column exists.
+ *
+ * @param string $tableName Table Name
+ * @param string $columnName Column Name
+ * @return boolean
+ */
+ public function hasColumn($tableName, $columnName);
+
+ /**
+ * Adds the specified column to a database table.
+ *
+ * @param Table $table Table
+ * @param Column $column Column
+ * @return void
+ */
+ public function addColumn(Table $table, Column $column);
+
+ /**
+ * Renames the specified column.
+ *
+ * @param string $tableName Table Name
+ * @param string $columnName Column Name
+ * @param string $newColumnName New Column Name
+ * @return void
+ */
+ public function renameColumn($tableName, $columnName, $newColumnName);
+
+ /**
+ * Change a table column type.
+ *
+ * @param string $tableName Table Name
+ * @param string $columnName Column Name
+ * @param Column $newColumn New Column
+ * @return Table
+ */
+ public function changeColumn($tableName, $columnName, Column $newColumn);
+
+ /**
+ * Drops the specified column.
+ *
+ * @param string $tableName Table Name
+ * @param string $columnName Column Name
+ * @return void
+ */
+ public function dropColumn($tableName, $columnName);
+
+ /**
+ * Checks to see if an index exists.
+ *
+ * @param string $tableName Table Name
+ * @param mixed $columns Column(s)
+ * @return boolean
+ */
+ public function hasIndex($tableName, $columns);
+
+ /**
+ * Checks to see if an index specified by name exists.
+ *
+ * @param string $tableName Table Name
+ * @param string $indexName
+ * @return boolean
+ */
+ public function hasIndexByName($tableName, $indexName);
+
+ /**
+ * Adds the specified index to a database table.
+ *
+ * @param Table $table Table
+ * @param Index $index Index
+ * @return void
+ */
+ public function addIndex(Table $table, Index $index);
+
+ /**
+ * Drops the specified index from a database table.
+ *
+ * @param string $tableName
+ * @param mixed $columns Column(s)
+ * @return void
+ */
+ public function dropIndex($tableName, $columns);
+
+ /**
+ * Drops the index specified by name from a database table.
+ *
+ * @param string $tableName
+ * @param string $indexName
+ * @return void
+ */
+ public function dropIndexByName($tableName, $indexName);
+
+ /**
+ * Checks to see if a foreign key exists.
+ *
+ * @param string $tableName
+ * @param string[] $columns Column(s)
+ * @param string $constraint Constraint name
+ * @return boolean
+ */
+ public function hasForeignKey($tableName, $columns, $constraint = null);
+
+ /**
+ * Adds the specified foreign key to a database table.
+ *
+ * @param Table $table
+ * @param ForeignKey $foreignKey
+ * @return void
+ */
+ public function addForeignKey(Table $table, ForeignKey $foreignKey);
+
+ /**
+ * Drops the specified foreign key from a database table.
+ *
+ * @param string $tableName
+ * @param string[] $columns Column(s)
+ * @param string $constraint Constraint name
+ * @return void
+ */
+ public function dropForeignKey($tableName, $columns, $constraint = null);
+
+ /**
+ * Returns an array of the supported Phinx column types.
+ *
+ * @return array
+ */
+ public function getColumnTypes();
+
+ /**
+ * Checks that the given column is of a supported type.
+ *
+ * @param Column $column
+ * @return boolean
+ */
+ public function isValidColumnType(Column $column);
+
+ /**
+ * Converts the Phinx logical type to the adapter's SQL type.
+ *
+ * @param string $type
+ * @param integer $limit
+ * @return string
+ */
+ public function getSqlType($type, $limit = null);
+
+ /**
+ * Creates a new database.
+ *
+ * @param string $name Database Name
+ * @param array $options Options
+ * @return void
+ */
+ public function createDatabase($name, $options = array());
+
+ /**
+ * Checks to see if a database exists.
+ *
+ * @param string $name Database Name
+ * @return boolean
+ */
+ public function hasDatabase($name);
+
+ /**
+ * Drops the specified database.
+ *
+ * @param string $name Database Name
+ * @return void
+ */
+ public function dropDatabase($name);
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php
new file mode 100755
index 0000000..ae5ec39
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php
@@ -0,0 +1,507 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace Phinx\Db\Adapter;
+
+use Phinx\Db\Table;
+use Phinx\Db\Table\Column;
+use Phinx\Db\Table\Index;
+use Phinx\Db\Table\ForeignKey;
+use Phinx\Migration\MigrationInterface;
+use think\console\Input as InputInterface;
+use think\console\Output as OutputInterface;
+
+/**
+ * Adapter Wrapper.
+ *
+ * Proxy commands through to another adapter, allowing modification of
+ * parameters during calls.
+ *
+ * @author Woody Gilk <woody.gilk@gmail.com>
+ */
+abstract class AdapterWrapper implements AdapterInterface, WrapperInterface
+{
+ /**
+ * @var AdapterInterface
+ */
+ protected $adapter;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(AdapterInterface $adapter)
+ {
+ $this->setAdapter($adapter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setAdapter(AdapterInterface $adapter)
+ {
+ $this->adapter = $adapter;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAdapter()
+ {
+ return $this->adapter;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setOptions(array $options)
+ {
+ $this->adapter->setOptions($options);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOptions()
+ {
+ return $this->adapter->getOptions();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasOption($name)
+ {
+ return $this->adapter->hasOption($name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOption($name)
+ {
+ return $this->adapter->getOption($name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setInput(InputInterface $input)
+ {
+ $this->adapter->setInput($input);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInput()
+ {
+ return $this->adapter->getInput();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setOutput(OutputInterface $output)
+ {
+ $this->adapter->setOutput($output);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOutput()
+ {
+ return $this->adapter->getOutput();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function connect()
+ {
+ return $this->getAdapter()->connect();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function disconnect()
+ {
+ return $this->getAdapter()->disconnect();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($sql)
+ {
+ return $this->getAdapter()->execute($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function query($sql)
+ {
+ return $this->getAdapter()->query($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function insert(Table $table, $row)
+ {
+ return $this->getAdapter()->insert($table, $row);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetchRow($sql)
+ {
+ return $this->getAdapter()->fetchRow($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetchAll($sql)
+ {
+ return $this->getAdapter()->fetchAll($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getVersions()
+ {
+ return $this->getAdapter()->getVersions();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getVersionLog()
+ {
+ return $this->getAdapter()->getVersionLog();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime)
+ {
+ $this->getAdapter()->migrated($migration, $direction, $startTime, $endTime);
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function toggleBreakpoint(MigrationInterface $migration)
+ {
+ $this->getAdapter()->toggleBreakpoint($migration);
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function resetAllBreakpoints()
+ {
+ return $this->getAdapter()->resetAllBreakpoints();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasSchemaTable()
+ {
+ return $this->getAdapter()->hasSchemaTable();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createSchemaTable()
+ {
+ return $this->getAdapter()->createSchemaTable();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumnTypes()
+ {
+ return $this->getAdapter()->getColumnTypes();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isValidColumnType(Column $column)
+ {
+ return $this->getAdapter()->isValidColumnType($column);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasTransactions()
+ {
+ return $this->getAdapter()->hasTransactions();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function beginTransaction()
+ {
+ return $this->getAdapter()->beginTransaction();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function commitTransaction()
+ {
+ return $this->getAdapter()->commitTransaction();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rollbackTransaction()
+ {
+ return $this->getAdapter()->rollbackTransaction();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function quoteTableName($tableName)
+ {
+ return $this->getAdapter()->quoteTableName($tableName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function quoteColumnName($columnName)
+ {
+ return $this->getAdapter()->quoteColumnName($columnName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasTable($tableName)
+ {
+ return $this->getAdapter()->hasTable($tableName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createTable(Table $table)
+ {
+ return $this->getAdapter()->createTable($table);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameTable($tableName, $newTableName)
+ {
+ return $this->getAdapter()->renameTable($tableName, $newTableName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropTable($tableName)
+ {
+ return $this->getAdapter()->dropTable($tableName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns($tableName)
+ {
+ return $this->getAdapter()->getColumns($tableName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasColumn($tableName, $columnName)
+ {
+ return $this->getAdapter()->hasColumn($tableName, $columnName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addColumn(Table $table, Column $column)
+ {
+ return $this->getAdapter()->addColumn($table, $column);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameColumn($tableName, $columnName, $newColumnName)
+ {
+ return $this->getAdapter()->renameColumn($tableName, $columnName, $newColumnName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function changeColumn($tableName, $columnName, Column $newColumn)
+ {
+ return $this->getAdapter()->changeColumn($tableName, $columnName, $newColumn);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropColumn($tableName, $columnName)
+ {
+ return $this->getAdapter()->dropColumn($tableName, $columnName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasIndex($tableName, $columns)
+ {
+ return $this->getAdapter()->hasIndex($tableName, $columns);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasIndexByName($tableName, $indexName)
+ {
+ return $this->getAdapter()->hasIndexByName($tableName, $indexName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addIndex(Table $table, Index $index)
+ {
+ return $this->getAdapter()->addIndex($table, $index);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndex($tableName, $columns, $options = array())
+ {
+ return $this->getAdapter()->dropIndex($tableName, $columns, $options);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndexByName($tableName, $indexName)
+ {
+ return $this->getAdapter()->dropIndexByName($tableName, $indexName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasForeignKey($tableName, $columns, $constraint = null)
+ {
+ return $this->getAdapter()->hasForeignKey($tableName, $columns, $constraint);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addForeignKey(Table $table, ForeignKey $foreignKey)
+ {
+ return $this->getAdapter()->addForeignKey($table, $foreignKey);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropForeignKey($tableName, $columns, $constraint = null)
+ {
+ return $this->getAdapter()->dropForeignKey($tableName, $columns, $constraint);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSqlType($type, $limit = null)
+ {
+ return $this->getAdapter()->getSqlType($type, $limit);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createDatabase($name, $options = array())
+ {
+ return $this->getAdapter()->createDatabase($name, $options);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasDatabase($name)
+ {
+ return $this->getAdapter()->hasDatabase($name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropDatabase($name)
+ {
+ return $this->getAdapter()->dropDatabase($name);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function castToBool($value)
+ {
+ return $this->getAdapter()->castToBool($value);
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php
new file mode 100755
index 0000000..f882fb5
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php
@@ -0,0 +1,1145 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace Phinx\Db\Adapter;
+
+use Phinx\Db\Table;
+use Phinx\Db\Table\Column;
+use Phinx\Db\Table\Index;
+use Phinx\Db\Table\ForeignKey;
+
+/**
+ * Phinx MySQL Adapter.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+class MysqlAdapter extends PdoAdapter implements AdapterInterface
+{
+
+ protected $signedColumnTypes = array('integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true, 'boolean' => true);
+
+ const TEXT_TINY = 255;
+ const TEXT_SMALL = 255; /* deprecated, alias of TEXT_TINY */
+ const TEXT_REGULAR = 65535;
+ const TEXT_MEDIUM = 16777215;
+ const TEXT_LONG = 4294967295;
+
+ // According to https://dev.mysql.com/doc/refman/5.0/en/blob.html BLOB sizes are the same as TEXT
+ const BLOB_TINY = 255;
+ const BLOB_SMALL = 255; /* deprecated, alias of BLOB_TINY */
+ const BLOB_REGULAR = 65535;
+ const BLOB_MEDIUM = 16777215;
+ const BLOB_LONG = 4294967295;
+
+ const INT_TINY = 255;
+ const INT_SMALL = 65535;
+ const INT_MEDIUM = 16777215;
+ const INT_REGULAR = 4294967295;
+ const INT_BIG = 18446744073709551615;
+
+ const TYPE_YEAR = 'year';
+
+ /**
+ * {@inheritdoc}
+ */
+ public function connect()
+ {
+ if (null === $this->connection) {
+ if (!class_exists('PDO') || !in_array('mysql', \PDO::getAvailableDrivers(), true)) {
+ // @codeCoverageIgnoreStart
+ throw new \RuntimeException('You need to enable the PDO_Mysql extension for Phinx to run properly.');
+ // @codeCoverageIgnoreEnd
+ }
+
+ $db = null;
+ $options = $this->getOptions();
+
+ $dsn = 'mysql:';
+
+ if (!empty($options['unix_socket'])) {
+ // use socket connection
+ $dsn .= 'unix_socket=' . $options['unix_socket'];
+ } else {
+ // use network connection
+ $dsn .= 'host=' . $options['host'];
+ if (!empty($options['port'])) {
+ $dsn .= ';port=' . $options['port'];
+ }
+ }
+
+ $dsn .= ';dbname=' . $options['name'];
+
+ // charset support
+ if (!empty($options['charset'])) {
+ $dsn .= ';charset=' . $options['charset'];
+ }
+
+ $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION);
+
+ // support arbitrary \PDO::MYSQL_ATTR_* driver options and pass them to PDO
+ // http://php.net/manual/en/ref.pdo-mysql.php#pdo-mysql.constants
+ foreach ($options as $key => $option) {
+ if (strpos($key, 'mysql_attr_') === 0) {
+ $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
+ }
+ }
+
+ try {
+ $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
+ } catch (\PDOException $exception) {
+ throw new \InvalidArgumentException(sprintf(
+ 'There was a problem connecting to the database: %s',
+ $exception->getMessage()
+ ));
+ }
+
+ $this->setConnection($db);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function disconnect()
+ {
+ $this->connection = null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasTransactions()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function beginTransaction()
+ {
+ $this->execute('START TRANSACTION');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function commitTransaction()
+ {
+ $this->execute('COMMIT');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rollbackTransaction()
+ {
+ $this->execute('ROLLBACK');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function quoteTableName($tableName)
+ {
+ return str_replace('.', '`.`', $this->quoteColumnName($tableName));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function quoteColumnName($columnName)
+ {
+ return '`' . str_replace('`', '``', $columnName) . '`';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasTable($tableName)
+ {
+ $options = $this->getOptions();
+
+ $exists = $this->fetchRow(sprintf(
+ "SELECT TABLE_NAME
+ FROM INFORMATION_SCHEMA.TABLES
+ WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'",
+ $options['name'], $tableName
+ ));
+
+ return !empty($exists);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createTable(Table $table)
+ {
+ $this->startCommandTimer();
+
+ // This method is based on the MySQL docs here: http://dev.mysql.com/doc/refman/5.1/en/create-index.html
+ $defaultOptions = array(
+ 'engine' => 'InnoDB',
+ 'collation' => 'utf8_general_ci'
+ );
+ $options = array_merge($defaultOptions, $table->getOptions());
+
+ // Add the default primary key
+ $columns = $table->getPendingColumns();
+
+ if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
+ $options['id'] = 'id';
+ }
+
+ if (isset($options['id']) && is_string($options['id'])) {
+ // Handle id => "field_name" to support AUTO_INCREMENT
+ $column = new Column();
+ $column->setName($options['id'])
+ ->setType('integer')
+ ->setSigned(isset($options['signed']) ? $options['signed'] : true)
+ ->setIdentity(true);
+
+ array_unshift($columns, $column);
+ $options['primary_key'] = $options['id'];
+ }
+
+ // TODO - process table options like collation etc
+
+ // process table engine (default to InnoDB)
+ $optionsStr = 'ENGINE = InnoDB';
+ if (isset($options['engine'])) {
+ $optionsStr = sprintf('ENGINE = %s', $options['engine']);
+ }
+
+ // process table collation
+ if (isset($options['collation'])) {
+ $charset = explode('_', $options['collation']);
+ $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]);
+ $optionsStr .= sprintf(' COLLATE %s', $options['collation']);
+ }
+
+ // set the table comment
+ if (isset($options['comment'])) {
+ $optionsStr .= sprintf(" COMMENT=%s ", $this->getConnection()->quote($options['comment']));
+ }
+
+ $sql = 'CREATE TABLE ';
+ $sql .= $this->quoteTableName($table->getName()) . ' (';
+ foreach ($columns as $column) {
+ $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
+ }
+
+ // set the primary key(s)
+ if (isset($options['primary_key'])) {
+ $sql = rtrim($sql);
+ $sql .= ' PRIMARY KEY (';
+ if (is_string($options['primary_key'])) { // handle primary_key => 'id'
+ $sql .= $this->quoteColumnName($options['primary_key']);
+ } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
+ // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the
+ // anonymous function, but for now just hard-code the adapter quotes
+ $sql .= implode(
+ ',',
+ array_map(
+ function ($v) {
+ return '`' . $v . '`';
+ },
+ $options['primary_key']
+ )
+ );
+ }
+ $sql .= ')';
+ } else {
+ $sql = substr(rtrim($sql), 0, -1); // no primary keys
+ }
+
+ // set the indexes
+ $indexes = $table->getIndexes();
+ if (!empty($indexes)) {
+ foreach ($indexes as $index) {
+ $sql .= ', ' . $this->getIndexSqlDefinition($index);
+ }
+ }
+
+ // set the foreign keys
+ $foreignKeys = $table->getForeignKeys();
+ if (!empty($foreignKeys)) {
+ foreach ($foreignKeys as $foreignKey) {
+ $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey);
+ }
+ }
+
+ $sql .= ') ' . $optionsStr;
+ $sql = rtrim($sql) . ';';
+
+ // execute the sql
+ $this->writeCommand('createTable', array($table->getName()));
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameTable($tableName, $newTableName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('renameTable', array($tableName, $newTableName));
+ $this->execute(sprintf('RENAME TABLE %s TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName)));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropTable($tableName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropTable', array($tableName));
+ $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns($tableName)
+ {
+ $columns = array();
+ $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
+ foreach ($rows as $columnInfo) {
+
+ $phinxType = $this->getPhinxType($columnInfo['Type']);
+
+ $column = new Column();
+ $column->setName($columnInfo['Field'])
+ ->setNull($columnInfo['Null'] !== 'NO')
+ ->setDefault($columnInfo['Default'])
+ ->setType($phinxType['name'])
+ ->setLimit($phinxType['limit']);
+
+ if ($columnInfo['Extra'] === 'auto_increment') {
+ $column->setIdentity(true);
+ }
+
+ $columns[] = $column;
+ }
+
+ return $columns;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasColumn($tableName, $columnName)
+ {
+ $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
+ foreach ($rows as $column) {
+ if (strcasecmp($column['Field'], $columnName) === 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the defintion for a `DEFAULT` statement.
+ *
+ * @param mixed $default
+ * @return string
+ */
+ protected function getDefaultValueDefinition($default)
+ {
+ if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
+ $default = $this->getConnection()->quote($default);
+ } elseif (is_bool($default)) {
+ $default = $this->castToBool($default);
+ }
+ return isset($default) ? ' DEFAULT ' . $default : '';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addColumn(Table $table, Column $column)
+ {
+ $this->startCommandTimer();
+ $sql = sprintf(
+ 'ALTER TABLE %s ADD %s %s',
+ $this->quoteTableName($table->getName()),
+ $this->quoteColumnName($column->getName()),
+ $this->getColumnSqlDefinition($column)
+ );
+
+ if ($column->getAfter()) {
+ $sql .= ' AFTER ' . $this->quoteColumnName($column->getAfter());
+ }
+
+ $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType()));
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameColumn($tableName, $columnName, $newColumnName)
+ {
+ $this->startCommandTimer();
+ $rows = $this->fetchAll(sprintf('DESCRIBE %s', $this->quoteTableName($tableName)));
+ foreach ($rows as $row) {
+ if (strcasecmp($row['Field'], $columnName) === 0) {
+ $null = ($row['Null'] == 'NO') ? 'NOT NULL' : 'NULL';
+ $extra = ' ' . strtoupper($row['Extra']);
+ if (!is_null($row['Default'])) {
+ $extra .= $this->getDefaultValueDefinition($row['Default']);
+ }
+ $definition = $row['Type'] . ' ' . $null . $extra;
+
+ $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName));
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s CHANGE COLUMN %s %s %s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($columnName),
+ $this->quoteColumnName($newColumnName),
+ $definition
+ )
+ );
+ $this->endCommandTimer();
+ return;
+ }
+ }
+
+ throw new \InvalidArgumentException(sprintf(
+ 'The specified column doesn\'t exist: '
+ . $columnName
+ ));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function changeColumn($tableName, $columnName, Column $newColumn)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType()));
+ $after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : '';
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s CHANGE %s %s %s%s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($columnName),
+ $this->quoteColumnName($newColumn->getName()),
+ $this->getColumnSqlDefinition($newColumn),
+ $after
+ )
+ );
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropColumn($tableName, $columnName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropColumn', array($tableName, $columnName));
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s DROP COLUMN %s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($columnName)
+ )
+ );
+ $this->endCommandTimer();
+ }
+
+ /**
+ * Get an array of indexes from a particular table.
+ *
+ * @param string $tableName Table Name
+ * @return array
+ */
+ protected function getIndexes($tableName)
+ {
+ $indexes = array();
+ $rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName)));
+ foreach ($rows as $row) {
+ if (!isset($indexes[$row['Key_name']])) {
+ $indexes[$row['Key_name']] = array('columns' => array());
+ }
+ $indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']);
+ }
+ return $indexes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasIndex($tableName, $columns)
+ {
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+
+ $columns = array_map('strtolower', $columns);
+ $indexes = $this->getIndexes($tableName);
+
+ foreach ($indexes as $index) {
+ if ($columns == $index['columns']) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasIndexByName($tableName, $indexName)
+ {
+ $indexes = $this->getIndexes($tableName);
+
+ foreach ($indexes as $name => $index) {
+ if ($name === $indexName) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addIndex(Table $table, Index $index)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('addIndex', array($table->getName(), $index->getColumns()));
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s ADD %s',
+ $this->quoteTableName($table->getName()),
+ $this->getIndexSqlDefinition($index)
+ )
+ );
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndex($tableName, $columns)
+ {
+ $this->startCommandTimer();
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+
+ $this->writeCommand('dropIndex', array($tableName, $columns));
+ $indexes = $this->getIndexes($tableName);
+ $columns = array_map('strtolower', $columns);
+
+ foreach ($indexes as $indexName => $index) {
+ if ($columns == $index['columns']) {
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s DROP INDEX %s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($indexName)
+ )
+ );
+ $this->endCommandTimer();
+ return;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndexByName($tableName, $indexName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropIndexByName', array($tableName, $indexName));
+ $indexes = $this->getIndexes($tableName);
+
+ foreach ($indexes as $name => $index) {
+ //$a = array_diff($columns, $index['columns']);
+ if ($name === $indexName) {
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s DROP INDEX %s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($indexName)
+ )
+ );
+ $this->endCommandTimer();
+ return;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasForeignKey($tableName, $columns, $constraint = null)
+ {
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+ $foreignKeys = $this->getForeignKeys($tableName);
+ if ($constraint) {
+ if (isset($foreignKeys[$constraint])) {
+ return !empty($foreignKeys[$constraint]);
+ }
+ return false;
+ } else {
+ foreach ($foreignKeys as $key) {
+ $a = array_diff($columns, $key['columns']);
+ if ($columns == $key['columns']) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Get an array of foreign keys from a particular table.
+ *
+ * @param string $tableName Table Name
+ * @return array
+ */
+ protected function getForeignKeys($tableName)
+ {
+ $foreignKeys = array();
+ $rows = $this->fetchAll(sprintf(
+ "SELECT
+ CONSTRAINT_NAME,
+ TABLE_NAME,
+ COLUMN_NAME,
+ REFERENCED_TABLE_NAME,
+ REFERENCED_COLUMN_NAME
+ FROM information_schema.KEY_COLUMN_USAGE
+ WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
+ AND REFERENCED_TABLE_NAME IS NOT NULL
+ AND TABLE_NAME = '%s'
+ ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
+ $tableName
+ ));
+ foreach ($rows as $row) {
+ $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME'];
+ $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME'];
+ $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME'];
+ $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
+ }
+ return $foreignKeys;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addForeignKey(Table $table, ForeignKey $foreignKey)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns()));
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s ADD %s',
+ $this->quoteTableName($table->getName()),
+ $this->getForeignKeySqlDefinition($foreignKey)
+ )
+ );
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropForeignKey($tableName, $columns, $constraint = null)
+ {
+ $this->startCommandTimer();
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+
+ $this->writeCommand('dropForeignKey', array($tableName, $columns));
+
+ if ($constraint) {
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s DROP FOREIGN KEY %s',
+ $this->quoteTableName($tableName),
+ $constraint
+ )
+ );
+ $this->endCommandTimer();
+ return;
+ } else {
+ foreach ($columns as $column) {
+ $rows = $this->fetchAll(sprintf(
+ "SELECT
+ CONSTRAINT_NAME
+ FROM information_schema.KEY_COLUMN_USAGE
+ WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
+ AND REFERENCED_TABLE_NAME IS NOT NULL
+ AND TABLE_NAME = '%s'
+ AND COLUMN_NAME = '%s'
+ ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
+ $tableName,
+ $column
+ ));
+ foreach ($rows as $row) {
+ $this->dropForeignKey($tableName, $columns, $row['CONSTRAINT_NAME']);
+ }
+ }
+ }
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSqlType($type, $limit = null)
+ {
+ switch ($type) {
+ case static::PHINX_TYPE_STRING:
+ return array('name' => 'varchar', 'limit' => $limit ? $limit : 255);
+ break;
+ case static::PHINX_TYPE_CHAR:
+ return array('name' => 'char', 'limit' => $limit ? $limit : 255);
+ break;
+ case static::PHINX_TYPE_TEXT:
+ if ($limit) {
+ $sizes = array(
+ // Order matters! Size must always be tested from longest to shortest!
+ 'longtext' => static::TEXT_LONG,
+ 'mediumtext' => static::TEXT_MEDIUM,
+ 'text' => static::TEXT_REGULAR,
+ 'tinytext' => static::TEXT_SMALL,
+ );
+ foreach ($sizes as $name => $length) {
+ if ($limit >= $length) {
+ return array('name' => $name);
+ }
+ }
+ }
+ return array('name' => 'text');
+ break;
+ case static::PHINX_TYPE_BINARY:
+ return array('name' => 'binary', 'limit' => $limit ? $limit : 255);
+ break;
+ case static::PHINX_TYPE_VARBINARY:
+ return array('name' => 'varbinary', 'limit' => $limit ? $limit : 255);
+ break;
+ case static::PHINX_TYPE_BLOB:
+ if ($limit) {
+ $sizes = array(
+ // Order matters! Size must always be tested from longest to shortest!
+ 'longblob' => static::BLOB_LONG,
+ 'mediumblob' => static::BLOB_MEDIUM,
+ 'blob' => static::BLOB_REGULAR,
+ 'tinyblob' => static::BLOB_SMALL,
+ );
+ foreach ($sizes as $name => $length) {
+ if ($limit >= $length) {
+ return array('name' => $name);
+ }
+ }
+ }
+ return array('name' => 'blob');
+ break;
+ case static::PHINX_TYPE_INTEGER:
+ if ($limit && $limit >= static::INT_TINY) {
+ $sizes = array(
+ // Order matters! Size must always be tested from longest to shortest!
+ 'bigint' => static::INT_BIG,
+ 'int' => static::INT_REGULAR,
+ 'mediumint' => static::INT_MEDIUM,
+ 'smallint' => static::INT_SMALL,
+ 'tinyint' => static::INT_TINY,
+ );
+ $limits = array(
+ 'int' => 11,
+ 'bigint' => 20,
+ );
+ foreach ($sizes as $name => $length) {
+ if ($limit >= $length) {
+ $def = array('name' => $name);
+ if (isset($limits[$name])) {
+ $def['limit'] = $limits[$name];
+ }
+ return $def;
+ }
+ }
+ } elseif (!$limit) {
+ $limit = 11;
+ }
+ return array('name' => 'int', 'limit' => $limit);
+ break;
+ case static::PHINX_TYPE_BIG_INTEGER:
+ return array('name' => 'bigint', 'limit' => 20);
+ break;
+ case static::PHINX_TYPE_FLOAT:
+ return array('name' => 'float');
+ break;
+ case static::PHINX_TYPE_DECIMAL:
+ return array('name' => 'decimal');
+ break;
+ case static::PHINX_TYPE_DATETIME:
+ return array('name' => 'datetime');
+ break;
+ case static::PHINX_TYPE_TIMESTAMP:
+ return array('name' => 'timestamp');
+ break;
+ case static::PHINX_TYPE_TIME:
+ return array('name' => 'time');
+ break;
+ case static::PHINX_TYPE_DATE:
+ return array('name' => 'date');
+ break;
+ case static::PHINX_TYPE_BOOLEAN:
+ return array('name' => 'tinyint', 'limit' => 1);
+ break;
+ case static::PHINX_TYPE_UUID:
+ return array('name' => 'char', 'limit' => 36);
+ // Geospatial database types
+ case static::PHINX_TYPE_GEOMETRY:
+ case static::PHINX_TYPE_POINT:
+ case static::PHINX_TYPE_LINESTRING:
+ case static::PHINX_TYPE_POLYGON:
+ return array('name' => $type);
+ case static::PHINX_TYPE_ENUM:
+ return array('name' => 'enum');
+ break;
+ case static::PHINX_TYPE_SET:
+ return array('name' => 'set');
+ break;
+ case static::TYPE_YEAR:
+ if (!$limit || in_array($limit, array(2, 4)))
+ $limit = 4;
+ return array('name' => 'year', 'limit' => $limit);
+ break;
+ case static::PHINX_TYPE_JSON:
+ return array('name' => 'json');
+ break;
+ default:
+ throw new \RuntimeException('The type: "' . $type . '" is not supported.');
+ }
+ }
+
+ /**
+ * Returns Phinx type by SQL type
+ *
+ * @param string $sqlTypeDef
+ * @throws \RuntimeException
+ * @internal param string $sqlType SQL type
+ * @returns string Phinx type
+ */
+ public function getPhinxType($sqlTypeDef)
+ {
+ if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) {
+ throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
+ } else {
+ $limit = null;
+ $precision = null;
+ $type = $matches[1];
+ if (count($matches) > 2) {
+ $limit = $matches[3] ? (int) $matches[3] : null;
+ }
+ if (count($matches) > 4) {
+ $precision = (int) $matches[5];
+ }
+ if ($type === 'tinyint' && $limit === 1) {
+ $type = static::PHINX_TYPE_BOOLEAN;
+ $limit = null;
+ }
+ switch ($type) {
+ case 'varchar':
+ $type = static::PHINX_TYPE_STRING;
+ if ($limit === 255) {
+ $limit = null;
+ }
+ break;
+ case 'char':
+ $type = static::PHINX_TYPE_CHAR;
+ if ($limit === 255) {
+ $limit = null;
+ }
+ if ($limit === 36) {
+ $type = static::PHINX_TYPE_UUID;
+ }
+ break;
+ case 'tinyint':
+ $type = static::PHINX_TYPE_INTEGER;
+ $limit = static::INT_TINY;
+ break;
+ case 'smallint':
+ $type = static::PHINX_TYPE_INTEGER;
+ $limit = static::INT_SMALL;
+ break;
+ case 'mediumint':
+ $type = static::PHINX_TYPE_INTEGER;
+ $limit = static::INT_MEDIUM;
+ break;
+ case 'int':
+ $type = static::PHINX_TYPE_INTEGER;
+ if ($limit === 11) {
+ $limit = null;
+ }
+ break;
+ case 'bigint':
+ if ($limit === 20) {
+ $limit = null;
+ }
+ $type = static::PHINX_TYPE_BIG_INTEGER;
+ break;
+ case 'blob':
+ $type = static::PHINX_TYPE_BINARY;
+ break;
+ case 'tinyblob':
+ $type = static::PHINX_TYPE_BINARY;
+ $limit = static::BLOB_TINY;
+ break;
+ case 'mediumblob':
+ $type = static::PHINX_TYPE_BINARY;
+ $limit = static::BLOB_MEDIUM;
+ break;
+ case 'longblob':
+ $type = static::PHINX_TYPE_BINARY;
+ $limit = static::BLOB_LONG;
+ break;
+ case 'tinytext':
+ $type = static::PHINX_TYPE_TEXT;
+ $limit = static::TEXT_TINY;
+ break;
+ case 'mediumtext':
+ $type = static::PHINX_TYPE_TEXT;
+ $limit = static::TEXT_MEDIUM;
+ break;
+ case 'longtext':
+ $type = static::PHINX_TYPE_TEXT;
+ $limit = static::TEXT_LONG;
+ break;
+ }
+
+ $this->getSqlType($type, $limit);
+
+ return array(
+ 'name' => $type,
+ 'limit' => $limit,
+ 'precision' => $precision
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createDatabase($name, $options = array())
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('createDatabase', array($name));
+ $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
+
+ if (isset($options['collation'])) {
+ $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation']));
+ } else {
+ $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset));
+ }
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasDatabase($name)
+ {
+ $rows = $this->fetchAll(
+ sprintf(
+ 'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'',
+ $name
+ )
+ );
+
+ foreach ($rows as $row) {
+ if (!empty($row)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropDatabase($name)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropDatabase', array($name));
+ $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * Gets the MySQL Column Definition for a Column object.
+ *
+ * @param Column $column Column
+ * @return string
+ */
+ protected function getColumnSqlDefinition(Column $column)
+ {
+ $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
+
+ $def = '';
+ $def .= strtoupper($sqlType['name']);
+ if ($column->getPrecision() && $column->getScale()) {
+ $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
+ } elseif (isset($sqlType['limit'])) {
+ $def .= '(' . $sqlType['limit'] . ')';
+ }
+ if (($values = $column->getValues()) && is_array($values)) {
+ $def .= "('" . implode("', '", $values) . "')";
+ }
+ $def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : '' ;
+ $def .= ($column->isNull() == false) ? ' NOT NULL' : ' NULL';
+ $def .= ($column->isIdentity()) ? ' AUTO_INCREMENT' : '';
+ $def .= $this->getDefaultValueDefinition($column->getDefault());
+
+ if ($column->getComment()) {
+ $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment());
+ }
+
+ if ($column->getUpdate()) {
+ $def .= ' ON UPDATE ' . $column->getUpdate();
+ }
+
+ return $def;
+ }
+
+ /**
+ * Gets the MySQL Index Definition for an Index object.
+ *
+ * @param Index $index Index
+ * @return string
+ */
+ protected function getIndexSqlDefinition(Index $index)
+ {
+ $def = '';
+ $limit = '';
+ if ($index->getLimit()) {
+ $limit = '(' . $index->getLimit() . ')';
+ }
+
+ if ($index->getType() == Index::UNIQUE) {
+ $def .= ' UNIQUE';
+ }
+
+ if ($index->getType() == Index::FULLTEXT) {
+ $def .= ' FULLTEXT';
+ }
+
+ $def .= ' KEY';
+
+ if (is_string($index->getName())) {
+ $def .= ' `' . $index->getName() . '`';
+ }
+
+ $def .= ' (`' . implode('`,`', $index->getColumns()) . '`' . $limit . ')';
+
+ return $def;
+ }
+
+ /**
+ * Gets the MySQL Foreign Key Definition for an ForeignKey object.
+ *
+ * @param ForeignKey $foreignKey
+ * @return string
+ */
+ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey)
+ {
+ $def = '';
+ if ($foreignKey->getConstraint()) {
+ $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint());
+ }
+ $columnNames = array();
+ foreach ($foreignKey->getColumns() as $column) {
+ $columnNames[] = $this->quoteColumnName($column);
+ }
+ $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
+ $refColumnNames = array();
+ foreach ($foreignKey->getReferencedColumns() as $column) {
+ $refColumnNames[] = $this->quoteColumnName($column);
+ }
+ $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
+ if ($foreignKey->getOnDelete()) {
+ $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
+ }
+ if ($foreignKey->getOnUpdate()) {
+ $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
+ }
+ return $def;
+ }
+
+ /**
+ * Describes a database table. This is a MySQL adapter specific method.
+ *
+ * @param string $tableName Table name
+ * @return array
+ */
+ public function describeTable($tableName)
+ {
+ $options = $this->getOptions();
+
+ // mysql specific
+ $sql = sprintf(
+ "SELECT *
+ FROM information_schema.tables
+ WHERE table_schema = '%s'
+ AND table_name = '%s'",
+ $options['name'],
+ $tableName
+ );
+
+ return $this->fetchRow($sql);
+ }
+
+ /**
+ * Returns MySQL column types (inherited and MySQL specified).
+ * @return array
+ */
+ public function getColumnTypes()
+ {
+ return array_merge(parent::getColumnTypes(), array('enum', 'set', 'year', 'json'));
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PdoAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PdoAdapter.php
new file mode 100755
index 0000000..9924c3a
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PdoAdapter.php
@@ -0,0 +1,587 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace Phinx\Db\Adapter;
+
+use think\console\Input as InputInterface;
+use think\console\Output as OutputInterface;
+use Phinx\Db\Table;
+use Phinx\Db\Table\Column;
+use Phinx\Migration\MigrationInterface;
+
+/**
+ * Phinx PDO Adapter.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+abstract class PdoAdapter implements AdapterInterface
+{
+ /**
+ * @var array
+ */
+ protected $options = array();
+
+ /**
+ * @var InputInterface
+ */
+ protected $input;
+
+ /**
+ * @var OutputInterface
+ */
+ protected $output;
+
+ /**
+ * @var string
+ */
+ protected $schemaTableName = 'migrations';
+
+ /**
+ * @var \PDO
+ */
+ protected $connection;
+
+ /**
+ * @var float
+ */
+ protected $commandStartTime;
+
+ /**
+ * Class Constructor.
+ *
+ * @param array $options Options
+ * @param InputInterface $input Input Interface
+ * @param OutputInterface $output Output Interface
+ */
+ public function __construct(array $options, InputInterface $input = null, OutputInterface $output = null)
+ {
+ $this->setOptions($options);
+ if (null !== $input) {
+ $this->setInput($input);
+ }
+ if (null !== $output) {
+ $this->setOutput($output);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setOptions(array $options)
+ {
+ $this->options = $options;
+
+ if (isset($options['default_migration_table'])) {
+ $this->setSchemaTableName($options['default_migration_table']);
+ }
+
+ if (isset($options['connection'])) {
+ $this->setConnection($options['connection']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasOption($name)
+ {
+ return isset($this->options[$name]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOption($name)
+ {
+ if (!$this->hasOption($name)) {
+ return;
+ }
+ return $this->options[$name];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setInput(InputInterface $input)
+ {
+ $this->input = $input;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setOutput(OutputInterface $output)
+ {
+ $this->output = $output;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOutput()
+ {
+ if (null === $this->output) {
+ $output = new OutputInterface('nothing');
+ $this->setOutput($output);
+ }
+ return $this->output;
+ }
+
+ /**
+ * Sets the schema table name.
+ *
+ * @param string $schemaTableName Schema Table Name
+ * @return PdoAdapter
+ */
+ public function setSchemaTableName($schemaTableName)
+ {
+ $this->schemaTableName = $schemaTableName;
+ return $this;
+ }
+
+ /**
+ * Gets the schema table name.
+ *
+ * @return string
+ */
+ public function getSchemaTableName()
+ {
+ return $this->schemaTableName;
+ }
+
+ /**
+ * Sets the database connection.
+ *
+ * @param \PDO $connection Connection
+ * @return AdapterInterface
+ */
+ public function setConnection(\PDO $connection)
+ {
+ $this->connection = $connection;
+
+ // Create the schema table if it doesn't already exist
+ if (!$this->hasSchemaTable()) {
+ $this->createSchemaTable();
+ } else {
+ $table = new Table($this->getSchemaTableName(), array(), $this);
+ if (!$table->hasColumn('migration_name')) {
+ $table
+ ->addColumn('migration_name', 'string',
+ array('limit' => 100, 'after' => 'version', 'default' => null, 'null' => true)
+ )
+ ->save();
+ }
+ if (!$table->hasColumn('breakpoint')) {
+ $table
+ ->addColumn('breakpoint', 'boolean', array('default' => false))
+ ->save();
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Gets the database connection
+ *
+ * @return \PDO
+ */
+ public function getConnection()
+ {
+ if (null === $this->connection) {
+ $this->connect();
+ }
+ return $this->connection;
+ }
+
+ /**
+ * Sets the command start time
+ *
+ * @param int $time
+ * @return AdapterInterface
+ */
+ public function setCommandStartTime($time)
+ {
+ $this->commandStartTime = $time;
+ return $this;
+ }
+
+ /**
+ * Gets the command start time
+ *
+ * @return int
+ */
+ public function getCommandStartTime()
+ {
+ return $this->commandStartTime;
+ }
+
+ /**
+ * Start timing a command.
+ *
+ * @return void
+ */
+ public function startCommandTimer()
+ {
+ $this->setCommandStartTime(microtime(true));
+ }
+
+ /**
+ * Stop timing the current command and write the elapsed time to the
+ * output.
+ *
+ * @return void
+ */
+ public function endCommandTimer()
+ {
+ $end = microtime(true);
+ if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) {
+ $this->getOutput()->writeln(' -> ' . sprintf('%.4fs', $end - $this->getCommandStartTime()));
+ }
+ }
+
+ /**
+ * Write a Phinx command to the output.
+ *
+ * @param string $command Command Name
+ * @param array $args Command Args
+ * @return void
+ */
+ public function writeCommand($command, $args = array())
+ {
+ if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) {
+ if (count($args)) {
+ $outArr = array();
+ foreach ($args as $arg) {
+ if (is_array($arg)) {
+ $arg = array_map(function ($value) {
+ return '\'' . $value . '\'';
+ }, $arg);
+ $outArr[] = '[' . implode(', ', $arg) . ']';
+ continue;
+ }
+
+ $outArr[] = '\'' . $arg . '\'';
+ }
+ $this->getOutput()->writeln(' -- ' . $command . '(' . implode(', ', $outArr) . ')');
+ return;
+ }
+ $this->getOutput()->writeln(' -- ' . $command);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function connect()
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function disconnect()
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($sql)
+ {
+ return $this->getConnection()->exec($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function query($sql)
+ {
+ return $this->getConnection()->query($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetchRow($sql)
+ {
+ $result = $this->query($sql);
+ return $result->fetch();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetchAll($sql)
+ {
+ $rows = array();
+ $result = $this->query($sql);
+ while ($row = $result->fetch()) {
+ $rows[] = $row;
+ }
+ return $rows;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function insert(Table $table, $row)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('insert', array($table->getName()));
+
+ $sql = sprintf(
+ "INSERT INTO %s ",
+ $this->quoteTableName($table->getName())
+ );
+
+ $columns = array_keys($row);
+ $sql .= "(". implode(', ', array_map(array($this, 'quoteColumnName'), $columns)) . ")";
+ $sql .= " VALUES (" . implode(', ', array_fill(0, count($columns), '?')) . ")";
+
+ $stmt = $this->getConnection()->prepare($sql);
+ $stmt->execute(array_values($row));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getVersions()
+ {
+ $rows = $this->getVersionLog();
+
+ return array_keys($rows);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getVersionLog()
+ {
+ $result = array();
+ $rows = $this->fetchAll(sprintf('SELECT * FROM %s ORDER BY version ASC', $this->getSchemaTableName()));
+ foreach ($rows as $version) {
+ $result[$version['version']] = $version;
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime)
+ {
+ if (strcasecmp($direction, MigrationInterface::UP) === 0) {
+ // up
+ $sql = sprintf(
+ "INSERT INTO %s (%s, %s, %s, %s, %s) VALUES ('%s', '%s', '%s', '%s', %s);",
+ $this->getSchemaTableName(),
+ $this->quoteColumnName('version'),
+ $this->quoteColumnName('migration_name'),
+ $this->quoteColumnName('start_time'),
+ $this->quoteColumnName('end_time'),
+ $this->quoteColumnName('breakpoint'),
+ $migration->getVersion(),
+ substr($migration->getName(), 0, 100),
+ $startTime,
+ $endTime,
+ $this->castToBool(false)
+ );
+
+ $this->query($sql);
+ } else {
+ // down
+ $sql = sprintf(
+ "DELETE FROM %s WHERE %s = '%s'",
+ $this->getSchemaTableName(),
+ $this->quoteColumnName('version'),
+ $migration->getVersion()
+ );
+
+ $this->query($sql);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function toggleBreakpoint(MigrationInterface $migration)
+ {
+ $this->query(
+ sprintf(
+ 'UPDATE %1$s SET %2$s = CASE %2$s WHEN %3$s THEN %4$s ELSE %3$s END WHERE %5$s = \'%6$s\';',
+ $this->getSchemaTableName(),
+ $this->quoteColumnName('breakpoint'),
+ $this->castToBool(true),
+ $this->castToBool(false),
+ $this->quoteColumnName('version'),
+ $migration->getVersion()
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function resetAllBreakpoints()
+ {
+ return $this->execute(
+ sprintf(
+ 'UPDATE %1$s SET %2$s = %3$s WHERE %2$s <> %3$s;',
+ $this->getSchemaTableName(),
+ $this->quoteColumnName('breakpoint'),
+ $this->castToBool(false)
+ )
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasSchemaTable()
+ {
+ return $this->hasTable($this->getSchemaTableName());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createSchemaTable()
+ {
+ try {
+ $options = array(
+ 'id' => false,
+ 'primary_key' => 'version'
+ );
+
+ $table = new Table($this->getSchemaTableName(), $options, $this);
+
+ if ($this->getConnection()->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'mysql'
+ && version_compare($this->getConnection()->getAttribute(\PDO::ATTR_SERVER_VERSION), '5.6.0', '>=')) {
+ $table->addColumn('version', 'biginteger', array('limit' => 14))
+ ->addColumn('migration_name', 'string', array('limit' => 100, 'default' => null, 'null' => true))
+ ->addColumn('start_time', 'timestamp', array('default' => 'CURRENT_TIMESTAMP'))
+ ->addColumn('end_time', 'timestamp', array('default' => 'CURRENT_TIMESTAMP'))
+ ->addColumn('breakpoint', 'boolean', array('default' => false))
+ ->save();
+ } else {
+ $table->addColumn('version', 'biginteger')
+ ->addColumn('migration_name', 'string', array('limit' => 100, 'default' => null, 'null' => true))
+ ->addColumn('start_time', 'timestamp')
+ ->addColumn('end_time', 'timestamp')
+ ->addColumn('breakpoint', 'boolean', array('default' => false))
+ ->save();
+ }
+ } catch (\Exception $exception) {
+ throw new \InvalidArgumentException('There was a problem creating the schema table: ' . $exception->getMessage());
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAdapterType()
+ {
+ return $this->getOption('adapter');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumnTypes()
+ {
+ return array(
+ 'string',
+ 'char',
+ 'text',
+ 'integer',
+ 'biginteger',
+ 'float',
+ 'decimal',
+ 'datetime',
+ 'timestamp',
+ 'time',
+ 'date',
+ 'blob',
+ 'binary',
+ 'varbinary',
+ 'boolean',
+ 'uuid',
+ // Geospatial data types
+ 'geometry',
+ 'point',
+ 'linestring',
+ 'polygon',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isValidColumnType(Column $column) {
+ return in_array($column->getType(), $this->getColumnTypes());
+ }
+
+ /**
+ * Cast a value to a boolean appropriate for the adapter.
+ *
+ * @param mixed $value The value to be cast
+ *
+ * @return mixed
+ */
+ public function castToBool($value)
+ {
+ return (bool) $value ? 1 : 0;
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php
new file mode 100755
index 0000000..3c506bd
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php
@@ -0,0 +1,1182 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace Phinx\Db\Adapter;
+
+use Phinx\Db\Table;
+use Phinx\Db\Table\Column;
+use Phinx\Db\Table\Index;
+use Phinx\Db\Table\ForeignKey;
+
+class PostgresAdapter extends PdoAdapter implements AdapterInterface
+{
+ const INT_SMALL = 65535;
+
+ /**
+ * Columns with comments
+ *
+ * @var array
+ */
+ protected $columnsWithComments = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function connect()
+ {
+ if (null === $this->connection) {
+ if (!class_exists('PDO') || !in_array('pgsql', \PDO::getAvailableDrivers(), true)) {
+ // @codeCoverageIgnoreStart
+ throw new \RuntimeException('You need to enable the PDO_Pgsql extension for Phinx to run properly.');
+ // @codeCoverageIgnoreEnd
+ }
+
+ $db = null;
+ $options = $this->getOptions();
+
+ // if port is specified use it, otherwise use the PostgreSQL default
+ if (isset($options['port'])) {
+ $dsn = 'pgsql:host=' . $options['host'] . ';port=' . $options['port'] . ';dbname=' . $options['name'];
+ } else {
+ $dsn = 'pgsql:host=' . $options['host'] . ';dbname=' . $options['name'];
+ }
+
+ try {
+ $db = new \PDO($dsn, $options['user'], $options['pass'], array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION));
+ } catch (\PDOException $exception) {
+ throw new \InvalidArgumentException(sprintf(
+ 'There was a problem connecting to the database: %s',
+ $exception->getMessage()
+ ));
+ }
+
+ $this->setConnection($db);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function disconnect()
+ {
+ $this->connection = null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasTransactions()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function beginTransaction()
+ {
+ $this->execute('BEGIN');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function commitTransaction()
+ {
+ $this->execute('COMMIT');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rollbackTransaction()
+ {
+ $this->execute('ROLLBACK');
+ }
+
+ /**
+ * Quotes a schema name for use in a query.
+ *
+ * @param string $schemaName Schema Name
+ * @return string
+ */
+ public function quoteSchemaName($schemaName)
+ {
+ return $this->quoteColumnName($schemaName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function quoteTableName($tableName)
+ {
+ return $this->quoteSchemaName($this->getSchemaName()) . '.' . $this->quoteColumnName($tableName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function quoteColumnName($columnName)
+ {
+ return '"'. $columnName . '"';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasTable($tableName)
+ {
+ $result = $this->getConnection()->query(
+ sprintf(
+ 'SELECT *
+ FROM information_schema.tables
+ WHERE table_schema = %s
+ AND lower(table_name) = lower(%s)',
+ $this->getConnection()->quote($this->getSchemaName()),
+ $this->getConnection()->quote($tableName)
+ )
+ );
+
+ return $result->rowCount() === 1;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createTable(Table $table)
+ {
+ $this->startCommandTimer();
+ $options = $table->getOptions();
+
+ // Add the default primary key
+ $columns = $table->getPendingColumns();
+ if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
+ $column = new Column();
+ $column->setName('id')
+ ->setType('integer')
+ ->setIdentity(true);
+
+ array_unshift($columns, $column);
+ $options['primary_key'] = 'id';
+
+ } elseif (isset($options['id']) && is_string($options['id'])) {
+ // Handle id => "field_name" to support AUTO_INCREMENT
+ $column = new Column();
+ $column->setName($options['id'])
+ ->setType('integer')
+ ->setIdentity(true);
+
+ array_unshift($columns, $column);
+ $options['primary_key'] = $options['id'];
+ }
+
+ // TODO - process table options like collation etc
+ $sql = 'CREATE TABLE ';
+ $sql .= $this->quoteTableName($table->getName()) . ' (';
+
+ $this->columnsWithComments = array();
+ foreach ($columns as $column) {
+ $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
+
+ // set column comments, if needed
+ if ($column->getComment()) {
+ $this->columnsWithComments[] = $column;
+ }
+ }
+
+ // set the primary key(s)
+ if (isset($options['primary_key'])) {
+ $sql = rtrim($sql);
+ $sql .= sprintf(' CONSTRAINT %s_pkey PRIMARY KEY (', $table->getName());
+ if (is_string($options['primary_key'])) { // handle primary_key => 'id'
+ $sql .= $this->quoteColumnName($options['primary_key']);
+ } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
+ // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function,
+ // but for now just hard-code the adapter quotes
+ $sql .= implode(
+ ',',
+ array_map(
+ function ($v) {
+ return '"' . $v . '"';
+ },
+ $options['primary_key']
+ )
+ );
+ }
+ $sql .= ')';
+ } else {
+ $sql = substr(rtrim($sql), 0, -1); // no primary keys
+ }
+
+ // set the foreign keys
+ $foreignKeys = $table->getForeignKeys();
+ if (!empty($foreignKeys)) {
+ foreach ($foreignKeys as $foreignKey) {
+ $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
+ }
+ }
+
+ $sql .= ');';
+
+ // process column comments
+ if (!empty($this->columnsWithComments)) {
+ foreach ($this->columnsWithComments as $column) {
+ $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
+ }
+ }
+
+ // set the indexes
+ $indexes = $table->getIndexes();
+ if (!empty($indexes)) {
+ foreach ($indexes as $index) {
+ $sql .= $this->getIndexSqlDefinition($index, $table->getName());
+ }
+ }
+
+ // execute the sql
+ $this->writeCommand('createTable', array($table->getName()));
+ $this->execute($sql);
+
+ // process table comments
+ if (isset($options['comment'])) {
+ $sql = sprintf(
+ 'COMMENT ON TABLE %s IS %s',
+ $this->quoteTableName($table->getName()),
+ $this->getConnection()->quote($options['comment'])
+ );
+ $this->execute($sql);
+ }
+
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameTable($tableName, $newTableName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('renameTable', array($tableName, $newTableName));
+ $sql = sprintf(
+ 'ALTER TABLE %s RENAME TO %s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($newTableName)
+ );
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropTable($tableName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropTable', array($tableName));
+ $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns($tableName)
+ {
+ $columns = array();
+ $sql = sprintf(
+ "SELECT column_name, data_type, is_identity, is_nullable,
+ column_default, character_maximum_length, numeric_precision, numeric_scale
+ FROM information_schema.columns
+ WHERE table_name ='%s'",
+ $tableName
+ );
+ $columnsInfo = $this->fetchAll($sql);
+
+ foreach ($columnsInfo as $columnInfo) {
+ $column = new Column();
+ $column->setName($columnInfo['column_name'])
+ ->setType($this->getPhinxType($columnInfo['data_type']))
+ ->setNull($columnInfo['is_nullable'] === 'YES')
+ ->setDefault($columnInfo['column_default'])
+ ->setIdentity($columnInfo['is_identity'] === 'YES')
+ ->setPrecision($columnInfo['numeric_precision'])
+ ->setScale($columnInfo['numeric_scale']);
+
+ if (preg_match('/\bwith time zone$/', $columnInfo['data_type'])) {
+ $column->setTimezone(true);
+ }
+
+ if (isset($columnInfo['character_maximum_length'])) {
+ $column->setLimit($columnInfo['character_maximum_length']);
+ }
+ $columns[] = $column;
+ }
+ return $columns;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasColumn($tableName, $columnName, $options = array())
+ {
+ $sql = sprintf("SELECT count(*)
+ FROM information_schema.columns
+ WHERE table_schema = '%s' AND table_name = '%s' AND column_name = '%s'",
+ $this->getSchemaName(),
+ $tableName,
+ $columnName
+ );
+
+ $result = $this->fetchRow($sql);
+ return $result['count'] > 0;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addColumn(Table $table, Column $column)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType()));
+ $sql = sprintf(
+ 'ALTER TABLE %s ADD %s %s',
+ $this->quoteTableName($table->getName()),
+ $this->quoteColumnName($column->getName()),
+ $this->getColumnSqlDefinition($column)
+ );
+
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameColumn($tableName, $columnName, $newColumnName)
+ {
+ $this->startCommandTimer();
+ $sql = sprintf(
+ "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS column_exists
+ FROM information_schema.columns
+ WHERE table_name ='%s' AND column_name = '%s'",
+ $tableName,
+ $columnName
+ );
+ $result = $this->fetchRow($sql);
+ if (!(bool) $result['column_exists']) {
+ throw new \InvalidArgumentException("The specified column does not exist: $columnName");
+ }
+ $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName));
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s RENAME COLUMN %s TO %s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($columnName),
+ $newColumnName
+ )
+ );
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function changeColumn($tableName, $columnName, Column $newColumn)
+ {
+ // TODO - is it possible to merge these 3 queries into less?
+ $this->startCommandTimer();
+ $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType()));
+ // change data type
+ $sql = sprintf(
+ 'ALTER TABLE %s ALTER COLUMN %s TYPE %s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($columnName),
+ $this->getColumnSqlDefinition($newColumn)
+ );
+ //NULL and DEFAULT cannot be set while changing column type
+ $sql = preg_replace('/ NOT NULL/', '', $sql);
+ $sql = preg_replace('/ NULL/', '', $sql);
+ //If it is set, DEFAULT is the last definition
+ $sql = preg_replace('/DEFAULT .*/', '', $sql);
+ $this->execute($sql);
+ // process null
+ $sql = sprintf(
+ 'ALTER TABLE %s ALTER COLUMN %s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($columnName)
+ );
+ if ($newColumn->isNull()) {
+ $sql .= ' DROP NOT NULL';
+ } else {
+ $sql .= ' SET NOT NULL';
+ }
+ $this->execute($sql);
+ if (!is_null($newColumn->getDefault())) {
+ //change default
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s ALTER COLUMN %s SET %s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($columnName),
+ $this->getDefaultValueDefinition($newColumn->getDefault())
+ )
+ );
+ }
+ else {
+ //drop default
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($columnName)
+ )
+ );
+ }
+ // rename column
+ if ($columnName !== $newColumn->getName()) {
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s RENAME COLUMN %s TO %s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($columnName),
+ $this->quoteColumnName($newColumn->getName())
+ )
+ );
+ }
+
+ // change column comment if needed
+ if ($newColumn->getComment()) {
+ $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
+ $this->execute($sql);
+ }
+
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropColumn($tableName, $columnName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropColumn', array($tableName, $columnName));
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s DROP COLUMN %s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($columnName)
+ )
+ );
+ $this->endCommandTimer();
+ }
+
+ /**
+ * Get an array of indexes from a particular table.
+ *
+ * @param string $tableName Table Name
+ * @return array
+ */
+ protected function getIndexes($tableName)
+ {
+ $indexes = array();
+ $sql = "SELECT
+ i.relname AS index_name,
+ a.attname AS column_name
+ FROM
+ pg_class t,
+ pg_class i,
+ pg_index ix,
+ pg_attribute a
+ WHERE
+ t.oid = ix.indrelid
+ AND i.oid = ix.indexrelid
+ AND a.attrelid = t.oid
+ AND a.attnum = ANY(ix.indkey)
+ AND t.relkind = 'r'
+ AND t.relname = '$tableName'
+ ORDER BY
+ t.relname,
+ i.relname;";
+ $rows = $this->fetchAll($sql);
+ foreach ($rows as $row) {
+ if (!isset($indexes[$row['index_name']])) {
+ $indexes[$row['index_name']] = array('columns' => array());
+ }
+ $indexes[$row['index_name']]['columns'][] = strtolower($row['column_name']);
+ }
+ return $indexes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasIndex($tableName, $columns)
+ {
+ if (is_string($columns)) {
+ $columns = array($columns);
+ }
+ $columns = array_map('strtolower', $columns);
+ $indexes = $this->getIndexes($tableName);
+ foreach ($indexes as $index) {
+ if (array_diff($index['columns'], $columns) === array_diff($columns, $index['columns'])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasIndexByName($tableName, $indexName)
+ {
+ $indexes = $this->getIndexes($tableName);
+ foreach ($indexes as $name => $index) {
+ if ($name === $indexName) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addIndex(Table $table, Index $index)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('addIndex', array($table->getName(), $index->getColumns()));
+ $sql = $this->getIndexSqlDefinition($index, $table->getName());
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndex($tableName, $columns)
+ {
+ $this->startCommandTimer();
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+
+ $this->writeCommand('dropIndex', array($tableName, $columns));
+ $indexes = $this->getIndexes($tableName);
+ $columns = array_map('strtolower', $columns);
+
+ foreach ($indexes as $indexName => $index) {
+ $a = array_diff($columns, $index['columns']);
+ if (empty($a)) {
+ $this->execute(
+ sprintf(
+ 'DROP INDEX IF EXISTS %s',
+ $this->quoteColumnName($indexName)
+ )
+ );
+ $this->endCommandTimer();
+ return;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndexByName($tableName, $indexName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropIndexByName', array($tableName, $indexName));
+ $sql = sprintf(
+ 'DROP INDEX IF EXISTS %s',
+ $indexName
+ );
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasForeignKey($tableName, $columns, $constraint = null)
+ {
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+ $foreignKeys = $this->getForeignKeys($tableName);
+ if ($constraint) {
+ if (isset($foreignKeys[$constraint])) {
+ return !empty($foreignKeys[$constraint]);
+ }
+ return false;
+ } else {
+ foreach ($foreignKeys as $key) {
+ $a = array_diff($columns, $key['columns']);
+ if (empty($a)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Get an array of foreign keys from a particular table.
+ *
+ * @param string $tableName Table Name
+ * @return array
+ */
+ protected function getForeignKeys($tableName)
+ {
+ $foreignKeys = array();
+ $rows = $this->fetchAll(sprintf(
+ "SELECT
+ tc.constraint_name,
+ tc.table_name, kcu.column_name,
+ ccu.table_name AS referenced_table_name,
+ ccu.column_name AS referenced_column_name
+ FROM
+ information_schema.table_constraints AS tc
+ JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
+ JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
+ WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
+ ORDER BY kcu.position_in_unique_constraint",
+ $tableName
+ ));
+ foreach ($rows as $row) {
+ $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
+ $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
+ $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
+ $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
+ }
+ return $foreignKeys;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addForeignKey(Table $table, ForeignKey $foreignKey)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns()));
+ $sql = sprintf(
+ 'ALTER TABLE %s ADD %s',
+ $this->quoteTableName($table->getName()),
+ $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
+ );
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropForeignKey($tableName, $columns, $constraint = null)
+ {
+ $this->startCommandTimer();
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+ $this->writeCommand('dropForeignKey', array($tableName, $columns));
+
+ if ($constraint) {
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s DROP CONSTRAINT %s',
+ $this->quoteTableName($tableName),
+ $constraint
+ )
+ );
+ } else {
+ foreach ($columns as $column) {
+ $rows = $this->fetchAll(sprintf(
+ "SELECT CONSTRAINT_NAME
+ FROM information_schema.KEY_COLUMN_USAGE
+ WHERE TABLE_SCHEMA = CURRENT_SCHEMA()
+ AND TABLE_NAME IS NOT NULL
+ AND TABLE_NAME = '%s'
+ AND COLUMN_NAME = '%s'
+ ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
+ $tableName,
+ $column
+ ));
+
+ foreach ($rows as $row) {
+ $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
+ }
+ }
+ }
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSqlType($type, $limit = null)
+ {
+ switch ($type) {
+ case static::PHINX_TYPE_INTEGER:
+ if ($limit && $limit == static::INT_SMALL) {
+ return array(
+ 'name' => 'smallint',
+ 'limit' => static::INT_SMALL
+ );
+ }
+ return array('name' => $type);
+ case static::PHINX_TYPE_TEXT:
+ case static::PHINX_TYPE_TIME:
+ case static::PHINX_TYPE_DATE:
+ case static::PHINX_TYPE_BOOLEAN:
+ case static::PHINX_TYPE_JSON:
+ case static::PHINX_TYPE_JSONB:
+ case static::PHINX_TYPE_UUID:
+ return array('name' => $type);
+ case static::PHINX_TYPE_DECIMAL:
+ return array('name' => $type, 'precision' => 18, 'scale' => 0);
+ case static::PHINX_TYPE_STRING:
+ return array('name' => 'character varying', 'limit' => 255);
+ case static::PHINX_TYPE_CHAR:
+ return array('name' => 'character', 'limit' => 255);
+ case static::PHINX_TYPE_BIG_INTEGER:
+ return array('name' => 'bigint');
+ case static::PHINX_TYPE_FLOAT:
+ return array('name' => 'real');
+ case static::PHINX_TYPE_DATETIME:
+ case static::PHINX_TYPE_TIMESTAMP:
+ return array('name' => 'timestamp');
+ case static::PHINX_TYPE_BLOB:
+ case static::PHINX_TYPE_BINARY:
+ return array('name' => 'bytea');
+ // Geospatial database types
+ // Spatial storage in Postgres is done via the PostGIS extension,
+ // which enables the use of the "geography" type in combination
+ // with SRID 4326.
+ case static::PHINX_TYPE_GEOMETRY:
+ return array('name' => 'geography', 'geometry', 4326);
+ break;
+ case static::PHINX_TYPE_POINT:
+ return array('name' => 'geography', 'point', 4326);
+ break;
+ case static::PHINX_TYPE_LINESTRING:
+ return array('name' => 'geography', 'linestring', 4326);
+ break;
+ case static::PHINX_TYPE_POLYGON:
+ return array('name' => 'geography', 'polygon', 4326);
+ break;
+ default:
+ if ($this->isArrayType($type)) {
+ return array('name' => $type);
+ }
+ // Return array type
+ throw new \RuntimeException('The type: "' . $type . '" is not supported');
+ }
+ }
+
+ /**
+ * Returns Phinx type by SQL type
+ *
+ * @param string $sqlType SQL type
+ * @returns string Phinx type
+ */
+ public function getPhinxType($sqlType)
+ {
+ switch ($sqlType) {
+ case 'character varying':
+ case 'varchar':
+ return static::PHINX_TYPE_STRING;
+ case 'character':
+ case 'char':
+ return static::PHINX_TYPE_CHAR;
+ case 'text':
+ return static::PHINX_TYPE_TEXT;
+ case 'json':
+ return static::PHINX_TYPE_JSON;
+ case 'jsonb':
+ return static::PHINX_TYPE_JSONB;
+ case 'smallint':
+ return array(
+ 'name' => 'smallint',
+ 'limit' => static::INT_SMALL
+ );
+ case 'int':
+ case 'int4':
+ case 'integer':
+ return static::PHINX_TYPE_INTEGER;
+ case 'decimal':
+ case 'numeric':
+ return static::PHINX_TYPE_DECIMAL;
+ case 'bigint':
+ case 'int8':
+ return static::PHINX_TYPE_BIG_INTEGER;
+ case 'real':
+ case 'float4':
+ return static::PHINX_TYPE_FLOAT;
+ case 'bytea':
+ return static::PHINX_TYPE_BINARY;
+ break;
+ case 'time':
+ case 'timetz':
+ case 'time with time zone':
+ case 'time without time zone':
+ return static::PHINX_TYPE_TIME;
+ case 'date':
+ return static::PHINX_TYPE_DATE;
+ case 'timestamp':
+ case 'timestamptz':
+ case 'timestamp with time zone':
+ case 'timestamp without time zone':
+ return static::PHINX_TYPE_DATETIME;
+ case 'bool':
+ case 'boolean':
+ return static::PHINX_TYPE_BOOLEAN;
+ case 'uuid':
+ return static::PHINX_TYPE_UUID;
+ default:
+ throw new \RuntimeException('The PostgreSQL type: "' . $sqlType . '" is not supported');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createDatabase($name, $options = array())
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('createDatabase', array($name));
+ $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
+ $this->execute(sprintf("CREATE DATABASE %s WITH ENCODING = '%s'", $name, $charset));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasDatabase($databaseName)
+ {
+ $sql = sprintf("SELECT count(*) FROM pg_database WHERE datname = '%s'", $databaseName);
+ $result = $this->fetchRow($sql);
+ return $result['count'] > 0;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropDatabase($name)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropDatabase', array($name));
+ $this->disconnect();
+ $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $name));
+ $this->connect();
+ $this->endCommandTimer();
+ }
+
+ /**
+ * Get the defintion for a `DEFAULT` statement.
+ *
+ * @param mixed $default
+ * @return string
+ */
+ protected function getDefaultValueDefinition($default)
+ {
+ if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
+ $default = $this->getConnection()->quote($default);
+ } elseif (is_bool($default)) {
+ $default = $this->castToBool($default);
+ }
+ return isset($default) ? 'DEFAULT ' . $default : '';
+ }
+
+ /**
+ * Gets the PostgreSQL Column Definition for a Column object.
+ *
+ * @param Column $column Column
+ * @return string
+ */
+ protected function getColumnSqlDefinition(Column $column)
+ {
+ $buffer = array();
+ if ($column->isIdentity()) {
+ $buffer[] = $column->getType() == 'biginteger' ? 'BIGSERIAL' : 'SERIAL';
+ } else {
+ $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
+ $buffer[] = strtoupper($sqlType['name']);
+ // integers cant have limits in postgres
+ if (static::PHINX_TYPE_DECIMAL === $sqlType['name'] && ($column->getPrecision() || $column->getScale())) {
+ $buffer[] = sprintf(
+ '(%s, %s)',
+ $column->getPrecision() ? $column->getPrecision() : $sqlType['precision'],
+ $column->getScale() ? $column->getScale() : $sqlType['scale']
+ );
+ } elseif (!in_array($sqlType['name'], array('integer', 'smallint'))) {
+ if ($column->getLimit() || isset($sqlType['limit'])) {
+ $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']);
+ }
+ }
+
+ $timeTypes = array(
+ 'time',
+ 'timestamp',
+ );
+ if (in_array($sqlType['name'], $timeTypes) && $column->isTimezone()) {
+ $buffer[] = strtoupper('with time zone');
+ }
+ }
+
+ $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
+
+ if (!is_null($column->getDefault())) {
+ $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
+ }
+
+ return implode(' ', $buffer);
+ }
+
+ /**
+ * Gets the PostgreSQL Column Comment Defininition for a column object.
+ *
+ * @param Column $column Column
+ * @param string $tableName Table name
+ * @return string
+ */
+ protected function getColumnCommentSqlDefinition(Column $column, $tableName)
+ {
+ // passing 'null' is to remove column comment
+ $comment = (strcasecmp($column->getComment(), 'NULL') !== 0)
+ ? $this->getConnection()->quote($column->getComment())
+ : 'NULL';
+
+ return sprintf(
+ 'COMMENT ON COLUMN %s.%s IS %s;',
+ $tableName,
+ $column->getName(),
+ $comment
+ );
+ }
+
+ /**
+ * Gets the PostgreSQL Index Definition for an Index object.
+ *
+ * @param Index $index Index
+ * @param string $tableName Table name
+ * @return string
+ */
+ protected function getIndexSqlDefinition(Index $index, $tableName)
+ {
+ if (is_string($index->getName())) {
+ $indexName = $index->getName();
+ } else {
+ $columnNames = $index->getColumns();
+ if (is_string($columnNames)) {
+ $columnNames = array($columnNames);
+ }
+ $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
+ }
+ $def = sprintf(
+ "CREATE %s INDEX %s ON %s (%s);",
+ ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
+ $indexName,
+ $this->quoteTableName($tableName),
+ implode(',', $index->getColumns())
+ );
+ return $def;
+ }
+
+ /**
+ * Gets the MySQL Foreign Key Definition for an ForeignKey object.
+ *
+ * @param ForeignKey $foreignKey
+ * @param string $tableName Table name
+ * @return string
+ */
+ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName)
+ {
+ $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns());
+ $def = ' CONSTRAINT "' . $constraintName . '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
+ $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
+ if ($foreignKey->getOnDelete()) {
+ $def .= " ON DELETE {$foreignKey->getOnDelete()}";
+ }
+ if ($foreignKey->getOnUpdate()) {
+ $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
+ }
+ return $def;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createSchemaTable()
+ {
+ // Create the public/custom schema if it doesn't already exist
+ if (false === $this->hasSchema($this->getSchemaName())) {
+ $this->createSchema($this->getSchemaName());
+ }
+
+ $this->fetchAll(sprintf('SET search_path TO %s', $this->getSchemaName()));
+
+ return parent::createSchemaTable();
+ }
+
+ /**
+ * Creates the specified schema.
+ *
+ * @param string $schemaName Schema Name
+ * @return void
+ */
+ public function createSchema($schemaName = 'public')
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('addSchema', array($schemaName));
+ $sql = sprintf('CREATE SCHEMA %s;', $this->quoteSchemaName($schemaName)); // from postgres 9.3 we can use "CREATE SCHEMA IF NOT EXISTS schema_name"
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * Checks to see if a schema exists.
+ *
+ * @param string $schemaName Schema Name
+ * @return boolean
+ */
+ public function hasSchema($schemaName)
+ {
+ $sql = sprintf(
+ "SELECT count(*)
+ FROM pg_namespace
+ WHERE nspname = '%s'",
+ $schemaName
+ );
+ $result = $this->fetchRow($sql);
+ return $result['count'] > 0;
+ }
+
+ /**
+ * Drops the specified schema table.
+ *
+ * @param string $schemaName Schema name
+ * @return void
+ */
+ public function dropSchema($schemaName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropSchema', array($schemaName));
+ $sql = sprintf("DROP SCHEMA IF EXISTS %s CASCADE;", $this->quoteSchemaName($schemaName));
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * Drops all schemas.
+ *
+ * @return void
+ */
+ public function dropAllSchemas()
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropAllSchemas');
+ foreach ($this->getAllSchemas() as $schema) {
+ $this->dropSchema($schema);
+ }
+ $this->endCommandTimer();
+ }
+
+ /**
+ * Returns schemas.
+ *
+ * @return array
+ */
+ public function getAllSchemas()
+ {
+ $sql = "SELECT schema_name
+ FROM information_schema.schemata
+ WHERE schema_name <> 'information_schema' AND schema_name !~ '^pg_'";
+ $items = $this->fetchAll($sql);
+ $schemaNames = array();
+ foreach ($items as $item) {
+ $schemaNames[] = $item['schema_name'];
+ }
+ return $schemaNames;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumnTypes()
+ {
+ return array_merge(parent::getColumnTypes(), array('json', 'jsonb'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isValidColumnType(Column $column)
+ {
+ // If not a standard column type, maybe it is array type?
+ return (parent::isValidColumnType($column) || $this->isArrayType($column->getType()));
+ }
+
+ /**
+ * Check if the given column is an array of a valid type.
+ *
+ * @param string $columnType
+ * @return bool
+ */
+ protected function isArrayType($columnType)
+ {
+ if (!preg_match('/^([a-z]+)(?:\[\]){1,}$/', $columnType, $matches)) {
+ return false;
+ }
+
+ $baseType = $matches[1];
+ return in_array($baseType, $this->getColumnTypes());
+ }
+
+ /**
+ * Gets the schema name.
+ *
+ * @return string
+ */
+ private function getSchemaName()
+ {
+ $options = $this->getOptions();
+ return empty($options['schema']) ? 'public' : $options['schema'];
+ }
+
+ /**
+ * Cast a value to a boolean appropriate for the adapter.
+ *
+ * @param mixed $value The value to be cast
+ *
+ * @return mixed
+ */
+ public function castToBool($value)
+ {
+ return (bool) $value ? 'TRUE' : 'FALSE';
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php
new file mode 100755
index 0000000..1236031
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php
@@ -0,0 +1,325 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace Phinx\Db\Adapter;
+
+use Phinx\Db\Table;
+use Phinx\Db\Table\Column;
+use Phinx\Db\Table\Index;
+use Phinx\Db\Table\ForeignKey;
+use Phinx\Migration\IrreversibleMigrationException;
+
+/**
+ * Phinx Proxy Adapter.
+ *
+ * Used for recording migration commands to automatically reverse them.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+class ProxyAdapter extends AdapterWrapper
+{
+ /**
+ * @var array
+ */
+ protected $commands;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAdapterType()
+ {
+ return 'ProxyAdapter';
+ }
+ /**
+ * {@inheritdoc}
+ */
+ public function createTable(Table $table)
+ {
+ $this->recordCommand('createTable', array($table->getName()));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameTable($tableName, $newTableName)
+ {
+ $this->recordCommand('renameTable', array($tableName, $newTableName));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropTable($tableName)
+ {
+ $this->recordCommand('dropTable', array($tableName));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addColumn(Table $table, Column $column)
+ {
+ $this->recordCommand('addColumn', array($table, $column));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameColumn($tableName, $columnName, $newColumnName)
+ {
+ $this->recordCommand('renameColumn', array($tableName, $columnName, $newColumnName));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function changeColumn($tableName, $columnName, Column $newColumn)
+ {
+ $this->recordCommand('changeColumn', array($tableName, $columnName, $newColumn));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropColumn($tableName, $columnName)
+ {
+ $this->recordCommand('dropColumn', array($tableName, $columnName));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addIndex(Table $table, Index $index)
+ {
+ $this->recordCommand('addIndex', array($table, $index));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndex($tableName, $columns, $options = array())
+ {
+ $this->recordCommand('dropIndex', array($tableName, $columns, $options));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndexByName($tableName, $indexName)
+ {
+ $this->recordCommand('dropIndexByName', array($tableName, $indexName));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addForeignKey(Table $table, ForeignKey $foreignKey)
+ {
+ $this->recordCommand('addForeignKey', array($table, $foreignKey));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropForeignKey($tableName, $columns, $constraint = null)
+ {
+ $this->recordCommand('dropForeignKey', array($columns, $constraint));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createDatabase($name, $options = array())
+ {
+ $this->recordCommand('createDatabase', array($name, $options));
+ }
+
+ /**
+ * Record a command for execution later.
+ *
+ * @param string $name Command Name
+ * @param array $arguments Command Arguments
+ * @return void
+ */
+ public function recordCommand($name, $arguments)
+ {
+ $this->commands[] = array(
+ 'name' => $name,
+ 'arguments' => $arguments
+ );
+ }
+
+ /**
+ * Sets an array of recorded commands.
+ *
+ * @param array $commands Commands
+ * @return ProxyAdapter
+ */
+ public function setCommands($commands)
+ {
+ $this->commands = $commands;
+ return $this;
+ }
+
+ /**
+ * Gets an array of the recorded commands.
+ *
+ * @return array
+ */
+ public function getCommands()
+ {
+ return $this->commands;
+ }
+
+ /**
+ * Gets an array of the recorded commands in reverse.
+ *
+ * @throws IrreversibleMigrationException if a command cannot be reversed.
+ * @return array
+ */
+ public function getInvertedCommands()
+ {
+ if (null === $this->getCommands()) {
+ return array();
+ }
+
+ $invCommands = array();
+ $supportedCommands = array(
+ 'createTable', 'renameTable', 'addColumn',
+ 'renameColumn', 'addIndex', 'addForeignKey'
+ );
+ foreach (array_reverse($this->getCommands()) as $command) {
+ if (!in_array($command['name'], $supportedCommands)) {
+ throw new IrreversibleMigrationException(sprintf(
+ 'Cannot reverse a "%s" command',
+ $command['name']
+ ));
+ }
+ $invertMethod = 'invert' . ucfirst($command['name']);
+ $invertedCommand = $this->$invertMethod($command['arguments']);
+ $invCommands[] = array(
+ 'name' => $invertedCommand['name'],
+ 'arguments' => $invertedCommand['arguments']
+ );
+ }
+
+ return $invCommands;
+ }
+
+ /**
+ * Execute the recorded commands.
+ *
+ * @return void
+ */
+ public function executeCommands()
+ {
+ $commands = $this->getCommands();
+ foreach ($commands as $command) {
+ call_user_func_array(array($this->getAdapter(), $command['name']), $command['arguments']);
+ }
+ }
+
+ /**
+ * Execute the recorded commands in reverse.
+ *
+ * @return void
+ */
+ public function executeInvertedCommands()
+ {
+ $commands = $this->getInvertedCommands();
+ foreach ($commands as $command) {
+ call_user_func_array(array($this->getAdapter(), $command['name']), $command['arguments']);
+ }
+ }
+
+ /**
+ * Returns the reverse of a createTable command.
+ *
+ * @param array $args Method Arguments
+ * @return array
+ */
+ public function invertCreateTable($args)
+ {
+ return array('name' => 'dropTable', 'arguments' => array($args[0]));
+ }
+
+ /**
+ * Returns the reverse of a renameTable command.
+ *
+ * @param array $args Method Arguments
+ * @return array
+ */
+ public function invertRenameTable($args)
+ {
+ return array('name' => 'renameTable', 'arguments' => array($args[1], $args[0]));
+ }
+
+ /**
+ * Returns the reverse of a addColumn command.
+ *
+ * @param array $args Method Arguments
+ * @return array
+ */
+ public function invertAddColumn($args)
+ {
+ return array('name' => 'dropColumn', 'arguments' => array($args[0]->getName(), $args[1]->getName()));
+ }
+
+ /**
+ * Returns the reverse of a renameColumn command.
+ *
+ * @param array $args Method Arguments
+ * @return array
+ */
+ public function invertRenameColumn($args)
+ {
+ return array('name' => 'renameColumn', 'arguments' => array($args[0], $args[2], $args[1]));
+ }
+
+ /**
+ * Returns the reverse of a addIndex command.
+ *
+ * @param array $args Method Arguments
+ * @return array
+ */
+ public function invertAddIndex($args)
+ {
+ return array('name' => 'dropIndex', 'arguments' => array($args[0]->getName(), $args[1]->getColumns()));
+ }
+
+ /**
+ * Returns the reverse of a addForeignKey command.
+ *
+ * @param array $args Method Arguments
+ * @return array
+ */
+ public function invertAddForeignKey($args)
+ {
+ return array('name' => 'dropForeignKey', 'arguments' => array($args[0]->getName(), $args[1]->getColumns()));
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php
new file mode 100755
index 0000000..4ff7b74
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php
@@ -0,0 +1,1136 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace Phinx\Db\Adapter;
+
+use Phinx\Db\Table;
+use Phinx\Db\Table\Column;
+use Phinx\Db\Table\Index;
+use Phinx\Db\Table\ForeignKey;
+
+/**
+ * Phinx SQLite Adapter.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ * @author Richard McIntyre <richard.mackstars@gmail.com>
+ */
+class SQLiteAdapter extends PdoAdapter implements AdapterInterface
+{
+ protected $definitionsWithLimits = array(
+ 'CHARACTER',
+ 'VARCHAR',
+ 'VARYING CHARACTER',
+ 'NCHAR',
+ 'NATIVE CHARACTER',
+ 'NVARCHAR'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ public function connect()
+ {
+ if (null === $this->connection) {
+ if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
+ // @codeCoverageIgnoreStart
+ throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
+ // @codeCoverageIgnoreEnd
+ }
+
+ $db = null;
+ $options = $this->getOptions();
+
+ // if port is specified use it, otherwise use the MySQL default
+ if (isset($options['memory'])) {
+ $dsn = 'sqlite::memory:';
+ } else {
+ $dsn = 'sqlite:' . $options['name'];
+ if (file_exists($options['name'] . '.sqlite3')) {
+ $dsn = 'sqlite:' . $options['name'] . '.sqlite3';
+ }
+ }
+
+ try {
+ $db = new \PDO($dsn);
+ } catch (\PDOException $exception) {
+ throw new \InvalidArgumentException(sprintf(
+ 'There was a problem connecting to the database: %s',
+ $exception->getMessage()
+ ));
+ }
+
+ $this->setConnection($db);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function disconnect()
+ {
+ $this->connection = null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasTransactions()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function beginTransaction()
+ {
+ $this->execute('BEGIN TRANSACTION');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function commitTransaction()
+ {
+ $this->execute('COMMIT');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rollbackTransaction()
+ {
+ $this->execute('ROLLBACK');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function quoteTableName($tableName)
+ {
+ return str_replace('.', '`.`', $this->quoteColumnName($tableName));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function quoteColumnName($columnName)
+ {
+ return '`' . str_replace('`', '``', $columnName) . '`';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasTable($tableName)
+ {
+ $tables = array();
+ $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName));
+ foreach ($rows as $row) {
+ $tables[] = strtolower($row[0]);
+ }
+
+ return in_array(strtolower($tableName), $tables);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createTable(Table $table)
+ {
+ $this->startCommandTimer();
+
+ // Add the default primary key
+ $columns = $table->getPendingColumns();
+ $options = $table->getOptions();
+ if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
+ $column = new Column();
+ $column->setName('id')
+ ->setType('integer')
+ ->setIdentity(true);
+
+ array_unshift($columns, $column);
+
+ } elseif (isset($options['id']) && is_string($options['id'])) {
+ // Handle id => "field_name" to support AUTO_INCREMENT
+ $column = new Column();
+ $column->setName($options['id'])
+ ->setType('integer')
+ ->setIdentity(true);
+
+ array_unshift($columns, $column);
+ }
+
+ $sql = 'CREATE TABLE ';
+ $sql .= $this->quoteTableName($table->getName()) . ' (';
+ foreach ($columns as $column) {
+ $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
+ }
+
+ // set the primary key(s)
+ if (isset($options['primary_key'])) {
+ $sql = rtrim($sql);
+ $sql .= ' PRIMARY KEY (';
+ if (is_string($options['primary_key'])) { // handle primary_key => 'id'
+ $sql .= $this->quoteColumnName($options['primary_key']);
+ } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
+ // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function,
+ // but for now just hard-code the adapter quotes
+ $sql .= implode(
+ ',',
+ array_map(
+ function ($v) {
+ return '`' . $v . '`';
+ },
+ $options['primary_key']
+ )
+ );
+ }
+ $sql .= ')';
+ } else {
+ $sql = substr(rtrim($sql), 0, -1); // no primary keys
+ }
+
+ // set the foreign keys
+ $foreignKeys = $table->getForeignKeys();
+ if (!empty($foreignKeys)) {
+ foreach ($foreignKeys as $foreignKey) {
+ $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey);
+ }
+ }
+
+ $sql = rtrim($sql) . ');';
+ // execute the sql
+ $this->writeCommand('createTable', array($table->getName()));
+ $this->execute($sql);
+ $this->endCommandTimer();
+
+ foreach ($table->getIndexes() as $index) {
+ $this->addIndex($table, $index);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameTable($tableName, $newTableName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('renameTable', array($tableName, $newTableName));
+ $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName)));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropTable($tableName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropTable', array($tableName));
+ $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns($tableName)
+ {
+ $columns = array();
+ $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
+
+ foreach ($rows as $columnInfo) {
+ $column = new Column();
+ $type = strtolower($columnInfo['type']);
+ $column->setName($columnInfo['name'])
+ ->setNull($columnInfo['notnull'] !== '1')
+ ->setDefault($columnInfo['dflt_value']);
+
+ $phinxType = $this->getPhinxType($type);
+ $column->setType($phinxType['name'])
+ ->setLimit($phinxType['limit']);
+
+ if ($columnInfo['pk'] == 1) {
+ $column->setIdentity(true);
+ }
+
+ $columns[] = $column;
+ }
+
+ return $columns;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasColumn($tableName, $columnName)
+ {
+ $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
+ foreach ($rows as $column) {
+ if (strcasecmp($column['name'], $columnName) === 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addColumn(Table $table, Column $column)
+ {
+ $this->startCommandTimer();
+
+ $sql = sprintf(
+ 'ALTER TABLE %s ADD COLUMN %s %s',
+ $this->quoteTableName($table->getName()),
+ $this->quoteColumnName($column->getName()),
+ $this->getColumnSqlDefinition($column)
+ );
+
+ $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType()));
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameColumn($tableName, $columnName, $newColumnName)
+ {
+ $tmpTableName = 'tmp_' . $tableName;
+
+ $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
+
+ $sql = '';
+ foreach ($rows as $table) {
+ if ($table['tbl_name'] === $tableName) {
+ $sql = $table['sql'];
+ }
+ }
+
+ $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
+ $selectColumns = array();
+ $writeColumns = array();
+ foreach ($columns as $column) {
+ $selectName = $column['name'];
+ $writeName = ($selectName == $columnName) ? $newColumnName : $selectName;
+ $selectColumns[] = $this->quoteColumnName($selectName);
+ $writeColumns[] = $this->quoteColumnName($writeName);
+ }
+
+ if (!in_array($this->quoteColumnName($columnName), $selectColumns)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The specified column doesn\'t exist: ' . $columnName
+ ));
+ }
+
+ $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
+
+ $sql = str_replace(
+ $this->quoteColumnName($columnName),
+ $this->quoteColumnName($newColumnName),
+ $sql
+ );
+ $this->execute($sql);
+
+ $sql = sprintf(
+ 'INSERT INTO %s(%s) SELECT %s FROM %s',
+ $tableName,
+ implode(', ', $writeColumns),
+ implode(', ', $selectColumns),
+ $tmpTableName
+ );
+
+ $this->execute($sql);
+
+ $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function changeColumn($tableName, $columnName, Column $newColumn)
+ {
+
+ // TODO: DRY this up....
+
+ $this->startCommandTimer();
+ $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType()));
+
+ $tmpTableName = 'tmp_' . $tableName;
+
+ $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
+
+ $sql = '';
+ foreach ($rows as $table) {
+ if ($table['tbl_name'] === $tableName) {
+ $sql = $table['sql'];
+ }
+ }
+
+ $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
+ $selectColumns = array();
+ $writeColumns = array();
+ foreach ($columns as $column) {
+ $selectName = $column['name'];
+ $writeName = ($selectName === $columnName) ? $newColumn->getName() : $selectName;
+ $selectColumns[] = $this->quoteColumnName($selectName);
+ $writeColumns[] = $this->quoteColumnName($writeName);
+ }
+
+ if (!in_array($this->quoteColumnName($columnName), $selectColumns)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The specified column doesn\'t exist: ' . $columnName
+ ));
+ }
+
+ $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
+
+ $sql = preg_replace(
+ sprintf("/%s[^,]+([,)])/", $this->quoteColumnName($columnName)),
+ sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
+ $sql,
+ 1
+ );
+
+ $this->execute($sql);
+
+ $sql = sprintf(
+ 'INSERT INTO %s(%s) SELECT %s FROM %s',
+ $tableName,
+ implode(', ', $writeColumns),
+ implode(', ', $selectColumns),
+ $tmpTableName
+ );
+
+ $this->execute($sql);
+ $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropColumn($tableName, $columnName)
+ {
+ // TODO: DRY this up....
+
+ $this->startCommandTimer();
+ $this->writeCommand('dropColumn', array($tableName, $columnName));
+
+ $tmpTableName = 'tmp_' . $tableName;
+
+ $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
+
+ $sql = '';
+ foreach ($rows as $table) {
+ if ($table['tbl_name'] === $tableName) {
+ $sql = $table['sql'];
+ }
+ }
+
+ $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
+ $columns = array();
+ $columnType = null;
+ foreach ($rows as $row) {
+ if ($row['name'] !== $columnName) {
+ $columns[] = $row['name'];
+ } else {
+ $found = true;
+ $columnType = $row['type'];
+ }
+ }
+
+ if (!isset($found)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The specified column doesn\'t exist: ' . $columnName
+ ));
+ }
+
+ $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));
+
+ $sql = preg_replace(
+ sprintf("/%s\s%s[^,)]*(,\s|\))/", preg_quote($this->quoteColumnName($columnName)), preg_quote($columnType)),
+ "",
+ $sql
+ );
+
+ if (substr($sql, -2) === ', ') {
+ $sql = substr($sql, 0, -2) . ')';
+ }
+
+ $this->execute($sql);
+
+ $sql = sprintf(
+ 'INSERT INTO %s(%s) SELECT %s FROM %s',
+ $tableName,
+ implode(', ', $columns),
+ implode(', ', $columns),
+ $tmpTableName
+ );
+
+ $this->execute($sql);
+ $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * Get an array of indexes from a particular table.
+ *
+ * @param string $tableName Table Name
+ * @return array
+ */
+ protected function getIndexes($tableName)
+ {
+ $indexes = array();
+ $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));
+
+ foreach ($rows as $row) {
+ $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
+ if (!isset($indexes[$tableName])) {
+ $indexes[$tableName] = array('index' => $row['name'], 'columns' => array());
+ }
+ foreach ($indexData as $indexItem) {
+ $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
+ }
+ }
+ return $indexes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasIndex($tableName, $columns)
+ {
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+
+ $columns = array_map('strtolower', $columns);
+ $indexes = $this->getIndexes($tableName);
+
+ foreach ($indexes as $index) {
+ $a = array_diff($columns, $index['columns']);
+ if (empty($a)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasIndexByName($tableName, $indexName)
+ {
+ $indexes = $this->getIndexes($tableName);
+
+ foreach ($indexes as $index) {
+ if ($indexName === $index['index']) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addIndex(Table $table, Index $index)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('addIndex', array($table->getName(), $index->getColumns()));
+ $indexColumnArray = array();
+ foreach ($index->getColumns() as $column) {
+ $indexColumnArray []= sprintf('`%s` ASC', $column);
+ }
+ $indexColumns = implode(',', $indexColumnArray);
+ $this->execute(
+ sprintf(
+ 'CREATE %s ON %s (%s)',
+ $this->getIndexSqlDefinition($table, $index),
+ $this->quoteTableName($table->getName()),
+ $indexColumns
+ )
+ );
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndex($tableName, $columns)
+ {
+ $this->startCommandTimer();
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+
+ $this->writeCommand('dropIndex', array($tableName, $columns));
+ $indexes = $this->getIndexes($tableName);
+ $columns = array_map('strtolower', $columns);
+
+ foreach ($indexes as $index) {
+ $a = array_diff($columns, $index['columns']);
+ if (empty($a)) {
+ $this->execute(
+ sprintf(
+ 'DROP INDEX %s',
+ $this->quoteColumnName($index['index'])
+ )
+ );
+ $this->endCommandTimer();
+ return;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndexByName($tableName, $indexName)
+ {
+ $this->startCommandTimer();
+
+ $this->writeCommand('dropIndexByName', array($tableName, $indexName));
+ $indexes = $this->getIndexes($tableName);
+
+ foreach ($indexes as $index) {
+ if ($indexName === $index['index']) {
+ $this->execute(
+ sprintf(
+ 'DROP INDEX %s',
+ $this->quoteColumnName($indexName)
+ )
+ );
+ $this->endCommandTimer();
+ return;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasForeignKey($tableName, $columns, $constraint = null)
+ {
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+ $foreignKeys = $this->getForeignKeys($tableName);
+
+ $a = array_diff($columns, $foreignKeys);
+ if (empty($a)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get an array of foreign keys from a particular table.
+ *
+ * @param string $tableName Table Name
+ * @return array
+ */
+ protected function getForeignKeys($tableName)
+ {
+ $foreignKeys = array();
+ $rows = $this->fetchAll(
+ "SELECT sql, tbl_name
+ FROM (
+ SELECT sql sql, type type, tbl_name tbl_name, name name
+ FROM sqlite_master
+ UNION ALL
+ SELECT sql, type, tbl_name, name
+ FROM sqlite_temp_master
+ )
+ WHERE type != 'meta'
+ AND sql NOTNULL
+ AND name NOT LIKE 'sqlite_%'
+ ORDER BY substr(type, 2, 1), name"
+ );
+
+ foreach ($rows as $row) {
+ if ($row['tbl_name'] === $tableName) {
+
+ if (strpos($row['sql'], 'REFERENCES') !== false) {
+ preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches);
+ foreach ($matches[1] as $match) {
+ $foreignKeys[] = $match;
+ }
+ }
+ }
+ }
+ return $foreignKeys;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addForeignKey(Table $table, ForeignKey $foreignKey)
+ {
+ // TODO: DRY this up....
+ $this->startCommandTimer();
+ $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns()));
+ $this->execute('pragma foreign_keys = ON');
+
+ $tmpTableName = 'tmp_' . $table->getName();
+ $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
+
+ $sql = '';
+ foreach ($rows as $row) {
+ if ($row['tbl_name'] === $table->getName()) {
+ $sql = $row['sql'];
+ }
+ }
+
+ $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($table->getName())));
+ $columns = array();
+ foreach ($rows as $column) {
+ $columns[] = $this->quoteColumnName($column['name']);
+ }
+
+ $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($table->getName()), $tmpTableName));
+
+ $sql = substr($sql, 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
+ $this->execute($sql);
+
+ $sql = sprintf(
+ 'INSERT INTO %s(%s) SELECT %s FROM %s',
+ $this->quoteTableName($table->getName()),
+ implode(', ', $columns),
+ implode(', ', $columns),
+ $this->quoteTableName($tmpTableName)
+ );
+
+ $this->execute($sql);
+ $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropForeignKey($tableName, $columns, $constraint = null)
+ {
+ // TODO: DRY this up....
+
+ $this->startCommandTimer();
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+
+ $this->writeCommand('dropForeignKey', array($tableName, $columns));
+
+ $tmpTableName = 'tmp_' . $tableName;
+
+ $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');
+
+ $sql = '';
+ foreach ($rows as $table) {
+ if ($table['tbl_name'] === $tableName) {
+ $sql = $table['sql'];
+ }
+ }
+
+ $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
+ $replaceColumns = array();
+ foreach ($rows as $row) {
+ if (!in_array($row['name'], $columns)) {
+ $replaceColumns[] = $row['name'];
+ } else {
+ $found = true;
+ }
+ }
+
+ if (!isset($found)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The specified column doesn\'t exist: '
+ ));
+ }
+
+ $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $tmpTableName));
+
+ foreach ($columns as $columnName) {
+ $search = sprintf(
+ "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
+ $this->quoteColumnName($columnName)
+ );
+ $sql = preg_replace($search, '', $sql, 1);
+ }
+
+ $this->execute($sql);
+
+ $sql = sprintf(
+ 'INSERT INTO %s(%s) SELECT %s FROM %s',
+ $tableName,
+ implode(', ', $columns),
+ implode(', ', $columns),
+ $tmpTableName
+ );
+
+ $this->execute($sql);
+ $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function insert(Table $table, $row)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('insert', array($table->getName()));
+
+ $sql = sprintf(
+ "INSERT INTO %s ",
+ $this->quoteTableName($table->getName())
+ );
+
+ $columns = array_keys($row);
+ $sql .= "(". implode(', ', array_map(array($this, 'quoteColumnName'), $columns)) . ")";
+ $sql .= " VALUES ";
+
+ $sql .= "(" . implode(', ', array_map(function ($value) {
+ if (is_numeric($value)) {
+ return $value;
+ }
+ return "'{$value}'";
+ }, $row)) . ")";
+
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSqlType($type, $limit = null)
+ {
+ switch ($type) {
+ case static::PHINX_TYPE_STRING:
+ return array('name' => 'varchar', 'limit' => 255);
+ break;
+ case static::PHINX_TYPE_CHAR:
+ return array('name' => 'char', 'limit' => 255);
+ break;
+ case static::PHINX_TYPE_TEXT:
+ return array('name' => 'text');
+ break;
+ case static::PHINX_TYPE_INTEGER:
+ return array('name' => 'integer');
+ break;
+ case static::PHINX_TYPE_BIG_INTEGER:
+ return array('name' => 'bigint');
+ break;
+ case static::PHINX_TYPE_FLOAT:
+ return array('name' => 'float');
+ break;
+ case static::PHINX_TYPE_DECIMAL:
+ return array('name' => 'decimal');
+ break;
+ case static::PHINX_TYPE_DATETIME:
+ return array('name' => 'datetime');
+ break;
+ case static::PHINX_TYPE_TIMESTAMP:
+ return array('name' => 'datetime');
+ break;
+ case static::PHINX_TYPE_TIME:
+ return array('name' => 'time');
+ break;
+ case static::PHINX_TYPE_DATE:
+ return array('name' => 'date');
+ break;
+ case static::PHINX_TYPE_BLOB:
+ case static::PHINX_TYPE_BINARY:
+ return array('name' => 'blob');
+ break;
+ case static::PHINX_TYPE_BOOLEAN:
+ return array('name' => 'boolean');
+ break;
+ case static::PHINX_TYPE_UUID:
+ return array('name' => 'char', 'limit' => 36);
+ case static::PHINX_TYPE_ENUM:
+ return array('name' => 'enum');
+ // Geospatial database types
+ // No specific data types exist in SQLite, instead all geospatial
+ // functionality is handled in the client. See also: SpatiaLite.
+ case static::PHINX_TYPE_GEOMETRY:
+ case static::PHINX_TYPE_POLYGON:
+ return array('name' => 'text');
+ return;
+ case static::PHINX_TYPE_LINESTRING:
+ return array('name' => 'varchar', 'limit' => 255);
+ break;
+ case static::PHINX_TYPE_POINT:
+ return array('name' => 'float');
+ default:
+ throw new \RuntimeException('The type: "' . $type . '" is not supported.');
+ }
+ }
+
+ /**
+ * Returns Phinx type by SQL type
+ *
+ * @param string $sqlTypeDef SQL type
+ * @returns string Phinx type
+ */
+ public function getPhinxType($sqlTypeDef)
+ {
+ if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) {
+ throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
+ } else {
+ $limit = null;
+ $precision = null;
+ $type = $matches[1];
+ if (count($matches) > 2) {
+ $limit = $matches[3] ? $matches[3] : null;
+ }
+ if (count($matches) > 4) {
+ $precision = $matches[5];
+ }
+ switch ($matches[1]) {
+ case 'varchar':
+ $type = static::PHINX_TYPE_STRING;
+ if ($limit === 255) {
+ $limit = null;
+ }
+ break;
+ case 'char':
+ $type = static::PHINX_TYPE_CHAR;
+ if ($limit === 255) {
+ $limit = null;
+ }
+ if ($limit === 36) {
+ $type = static::PHINX_TYPE_UUID;
+ }
+ break;
+ case 'int':
+ $type = static::PHINX_TYPE_INTEGER;
+ if ($limit === 11) {
+ $limit = null;
+ }
+ break;
+ case 'bigint':
+ if ($limit === 11) {
+ $limit = null;
+ }
+ $type = static::PHINX_TYPE_BIG_INTEGER;
+ break;
+ case 'blob':
+ $type = static::PHINX_TYPE_BINARY;
+ break;
+ }
+ if ($type === 'tinyint') {
+ if ($matches[3] === 1) {
+ $type = static::PHINX_TYPE_BOOLEAN;
+ $limit = null;
+ }
+ }
+
+ $this->getSqlType($type);
+
+ return array(
+ 'name' => $type,
+ 'limit' => $limit,
+ 'precision' => $precision
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createDatabase($name, $options = array())
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('createDatabase', array($name));
+ touch($name . '.sqlite3');
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasDatabase($name)
+ {
+ return is_file($name . '.sqlite3');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropDatabase($name)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropDatabase', array($name));
+ if (file_exists($name . '.sqlite3')) {
+ unlink($name . '.sqlite3');
+ }
+ $this->endCommandTimer();
+ }
+
+ /**
+ * Get the definition for a `DEFAULT` statement.
+ *
+ * @param mixed $default
+ * @return string
+ */
+ protected function getDefaultValueDefinition($default)
+ {
+ if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
+ $default = $this->getConnection()->quote($default);
+ } elseif (is_bool($default)) {
+ $default = $this->castToBool($default);
+ }
+ return isset($default) ? ' DEFAULT ' . $default : '';
+ }
+
+ /**
+ * Gets the SQLite Column Definition for a Column object.
+ *
+ * @param Column $column Column
+ * @return string
+ */
+ protected function getColumnSqlDefinition(Column $column)
+ {
+ $sqlType = $this->getSqlType($column->getType());
+ $def = '';
+ $def .= strtoupper($sqlType['name']);
+ if ($column->getPrecision() && $column->getScale()) {
+ $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
+ }
+ $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
+ if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
+ $def .= '(' . ($column->getLimit() ? $column->getLimit() : $sqlType['limit']) . ')';
+ }
+ if (($values = $column->getValues()) && is_array($values)) {
+ $def .= " CHECK({$column->getName()} IN ('" . implode("', '", $values) . "'))";
+ }
+
+ $default = $column->getDefault();
+
+ $def .= ($column->isNull() || is_null($default)) ? ' NULL' : ' NOT NULL';
+ $def .= $this->getDefaultValueDefinition($default);
+ $def .= ($column->isIdentity()) ? ' PRIMARY KEY AUTOINCREMENT' : '';
+
+ if ($column->getUpdate()) {
+ $def .= ' ON UPDATE ' . $column->getUpdate();
+ }
+
+ $def .= $this->getCommentDefinition($column);
+
+ return $def;
+ }
+
+ /**
+ * Gets the comment Definition for a Column object.
+ *
+ * @param Column $column Column
+ * @return string
+ */
+ protected function getCommentDefinition(Column $column)
+ {
+ if ($column->getComment()) {
+ return ' /* ' . $column->getComment() . ' */ ';
+ }
+ return '';
+ }
+
+ /**
+ * Gets the SQLite Index Definition for an Index object.
+ *
+ * @param Index $index Index
+ * @return string
+ */
+ protected function getIndexSqlDefinition(Table $table, Index $index)
+ {
+ if ($index->getType() === Index::UNIQUE) {
+ $def = 'UNIQUE INDEX';
+ } else {
+ $def = 'INDEX';
+ }
+ if (is_string($index->getName())) {
+ $indexName = $index->getName();
+ } else {
+ $indexName = $table->getName() . '_';
+ foreach ($index->getColumns() as $column) {
+ $indexName .= $column . '_';
+ }
+ $indexName .= 'index';
+ }
+ $def .= ' `' . $indexName . '`';
+ return $def;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumnTypes()
+ {
+ return array_merge(parent::getColumnTypes(), array('enum'));
+ }
+
+ /**
+ * Gets the SQLite Foreign Key Definition for an ForeignKey object.
+ *
+ * @param ForeignKey $foreignKey
+ * @return string
+ */
+ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey)
+ {
+ $def = '';
+ if ($foreignKey->getConstraint()) {
+ $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint());
+ } else {
+ $columnNames = array();
+ foreach ($foreignKey->getColumns() as $column) {
+ $columnNames[] = $this->quoteColumnName($column);
+ }
+ $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
+ $refColumnNames = array();
+ foreach ($foreignKey->getReferencedColumns() as $column) {
+ $refColumnNames[] = $this->quoteColumnName($column);
+ }
+ $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
+ if ($foreignKey->getOnDelete()) {
+ $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
+ }
+ if ($foreignKey->getOnUpdate()) {
+ $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
+ }
+ }
+ return $def;
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php
new file mode 100755
index 0000000..99f8a82
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php
@@ -0,0 +1,1165 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace Phinx\Db\Adapter;
+
+use Phinx\Db\Table;
+use Phinx\Db\Table\Column;
+use Phinx\Db\Table\Index;
+use Phinx\Db\Table\ForeignKey;
+
+/**
+ * Phinx SqlServer Adapter.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+class SqlServerAdapter extends PdoAdapter implements AdapterInterface
+{
+ protected $schema = 'dbo';
+
+ protected $signedColumnTypes = array('integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true);
+
+ /**
+ * {@inheritdoc}
+ */
+ public function connect()
+ {
+ if (null === $this->connection) {
+ if (!class_exists('PDO') || !in_array('sqlsrv', \PDO::getAvailableDrivers(), true)) {
+ // try our connection via freetds (Mac/Linux)
+ return $this->connectDblib();
+ }
+
+ $db = null;
+ $options = $this->getOptions();
+
+ // if port is specified use it, otherwise use the SqlServer default
+ if (empty($options['port'])) {
+ $dsn = 'sqlsrv:server=' . $options['host'] . ';database=' . $options['name'];
+ } else {
+ $dsn = 'sqlsrv:server=' . $options['host'] . ',' . $options['port'] . ';database=' . $options['name'];
+ }
+ $dsn .= ';MultipleActiveResultSets=false';
+
+ $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION);
+
+ // charset support
+ if (isset($options['charset'])) {
+ $driverOptions[\PDO::SQLSRV_ATTR_ENCODING] = $options['charset'];
+ }
+
+ // support arbitrary \PDO::SQLSRV_ATTR_* driver options and pass them to PDO
+ // http://php.net/manual/en/ref.pdo-sqlsrv.php#pdo-sqlsrv.constants
+ foreach ($options as $key => $option) {
+ if (strpos($key, 'sqlsrv_attr_') === 0) {
+ $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
+ }
+ }
+
+ try {
+ $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
+ } catch (\PDOException $exception) {
+ throw new \InvalidArgumentException(sprintf(
+ 'There was a problem connecting to the database: %s',
+ $exception->getMessage()
+ ));
+ }
+
+ $this->setConnection($db);
+ }
+ }
+
+ /**
+ * Connect to MSSQL using dblib/freetds.
+ *
+ * The "sqlsrv" driver is not available on Unix machines.
+ *
+ * @throws \InvalidArgumentException
+ */
+ protected function connectDblib()
+ {
+ if (!class_exists('PDO') || !in_array('dblib', \PDO::getAvailableDrivers(), true)) {
+ // @codeCoverageIgnoreStart
+ throw new \RuntimeException('You need to enable the PDO_Dblib extension for Phinx to run properly.');
+ // @codeCoverageIgnoreEnd
+ }
+
+ $options = $this->getOptions();
+
+ // if port is specified use it, otherwise use the SqlServer default
+ if (empty($options['port'])) {
+ $dsn = 'dblib:host=' . $options['host'] . ';dbname=' . $options['name'];
+ } else {
+ $dsn = 'dblib:host=' . $options['host'] . ':' . $options['port'] . ';dbname=' . $options['name'];
+ }
+
+ $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION);
+
+ try {
+ $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
+ } catch (\PDOException $exception) {
+ throw new \InvalidArgumentException(sprintf(
+ 'There was a problem connecting to the database: %s',
+ $exception->getMessage()
+ ));
+ }
+
+ $this->setConnection($db);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function disconnect()
+ {
+ $this->connection = null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasTransactions()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function beginTransaction()
+ {
+ $this->execute('BEGIN TRANSACTION');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function commitTransaction()
+ {
+ $this->execute('COMMIT TRANSACTION');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rollbackTransaction()
+ {
+ $this->execute('ROLLBACK TRANSACTION');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function quoteTableName($tableName)
+ {
+ return str_replace('.', '].[', $this->quoteColumnName($tableName));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function quoteColumnName($columnName)
+ {
+ return '[' . str_replace(']', '\]', $columnName) . ']';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasTable($tableName)
+ {
+ $result = $this->fetchRow(sprintf('SELECT count(*) as [count] FROM information_schema.tables WHERE table_name = \'%s\';', $tableName));
+ return $result['count'] > 0;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createTable(Table $table)
+ {
+ $this->startCommandTimer();
+
+ $options = $table->getOptions();
+
+ // Add the default primary key
+ $columns = $table->getPendingColumns();
+ if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
+ $column = new Column();
+ $column->setName('id')
+ ->setType('integer')
+ ->setIdentity(true);
+
+ array_unshift($columns, $column);
+ $options['primary_key'] = 'id';
+
+ } elseif (isset($options['id']) && is_string($options['id'])) {
+ // Handle id => "field_name" to support AUTO_INCREMENT
+ $column = new Column();
+ $column->setName($options['id'])
+ ->setType('integer')
+ ->setIdentity(true);
+
+ array_unshift($columns, $column);
+ $options['primary_key'] = $options['id'];
+ }
+
+ $sql = 'CREATE TABLE ';
+ $sql .= $this->quoteTableName($table->getName()) . ' (';
+ $sqlBuffer = array();
+ $columnsWithComments = array();
+ foreach ($columns as $column) {
+ $sqlBuffer[] = $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column);
+
+ // set column comments, if needed
+ if ($column->getComment()) {
+ $columnsWithComments[] = $column;
+ }
+ }
+
+ // set the primary key(s)
+ if (isset($options['primary_key'])) {
+ $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', $table->getName());
+ if (is_string($options['primary_key'])) { // handle primary_key => 'id'
+ $pkSql .= $this->quoteColumnName($options['primary_key']);
+ } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
+ // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function,
+ // but for now just hard-code the adapter quotes
+ $pkSql .= implode(
+ ',',
+ array_map(
+ function ($v) {
+ return '[' . $v . ']';
+ },
+ $options['primary_key']
+ )
+ );
+ }
+ $pkSql .= ')';
+ $sqlBuffer[] = $pkSql;
+ }
+
+ // set the foreign keys
+ $foreignKeys = $table->getForeignKeys();
+ if (!empty($foreignKeys)) {
+ foreach ($foreignKeys as $foreignKey) {
+ $sqlBuffer[] = $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
+ }
+ }
+
+ $sql .= implode(', ', $sqlBuffer);
+ $sql .= ');';
+
+ // process column comments
+ if (!empty($columnsWithComments)) {
+ foreach ($columnsWithComments as $column) {
+ $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
+ }
+ }
+
+ // set the indexes
+ $indexes = $table->getIndexes();
+ if (!empty($indexes)) {
+ foreach ($indexes as $index) {
+ $sql .= $this->getIndexSqlDefinition($index, $table->getName());
+ }
+ }
+
+ // execute the sql
+ $this->writeCommand('createTable', array($table->getName()));
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * Gets the SqlServer Column Comment Defininition for a column object.
+ *
+ * @param Column $column Column
+ * @param string $tableName Table name
+ *
+ * @return string
+ */
+ protected function getColumnCommentSqlDefinition(Column $column, $tableName)
+ {
+ // passing 'null' is to remove column comment
+ $currentComment = $this->getColumnComment($tableName, $column->getName());
+
+ $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) ? $this->getConnection()->quote($column->getComment()) : '\'\'';
+ $command = $currentComment === false ? 'sp_addextendedproperty' : 'sp_updateextendedproperty';
+ return sprintf(
+ "EXECUTE %s N'MS_Description', N%s, N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s';",
+ $command,
+ $comment,
+ $this->schema,
+ $tableName,
+ $column->getName()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameTable($tableName, $newTableName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('renameTable', array($tableName, $newTableName));
+ $this->execute(sprintf('EXEC sp_rename \'%s\', \'%s\'', $tableName, $newTableName));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropTable($tableName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropTable', array($tableName));
+ $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
+ $this->endCommandTimer();
+ }
+
+ public function getColumnComment($tableName, $columnName)
+ {
+ $sql = sprintf("SELECT cast(extended_properties.[value] as nvarchar(4000)) comment
+ FROM sys.schemas
+ INNER JOIN sys.tables
+ ON schemas.schema_id = tables.schema_id
+ INNER JOIN sys.columns
+ ON tables.object_id = columns.object_id
+ INNER JOIN sys.extended_properties
+ ON tables.object_id = extended_properties.major_id
+ AND columns.column_id = extended_properties.minor_id
+ AND extended_properties.name = 'MS_Description'
+ WHERE schemas.[name] = '%s' AND tables.[name] = '%s' AND columns.[name] = '%s'", $this->schema, $tableName, $columnName);
+ $row = $this->fetchRow($sql);
+
+ if ($row) {
+ return $row['comment'];
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns($tableName)
+ {
+ $columns = array();
+ $sql = sprintf(
+ "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type],
+ IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default],
+ CHARACTER_MAXIMUM_LENGTH AS [char_length],
+ NUMERIC_PRECISION AS [precision],
+ NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position],
+ COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity]
+ FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE TABLE_NAME = '%s'
+ ORDER BY ordinal_position",
+ $tableName
+ );
+ $rows = $this->fetchAll($sql);
+ foreach ($rows as $columnInfo) {
+ $column = new Column();
+ $column->setName($columnInfo['name'])
+ ->setType($this->getPhinxType($columnInfo['type']))
+ ->setNull($columnInfo['null'] !== 'NO')
+ ->setDefault($this->parseDefault($columnInfo['default']))
+ ->setIdentity($columnInfo['identity'] === '1')
+ ->setComment($this->getColumnComment($columnInfo['table_name'], $columnInfo['name']));
+
+ if (!empty($columnInfo['char_length'])) {
+ $column->setLimit($columnInfo['char_length']);
+ }
+
+ $columns[$columnInfo['name']] = $column;
+ }
+
+ return $columns;
+ }
+
+ protected function parseDefault($default)
+ {
+ $default = preg_replace(array("/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"), '$1', $default);
+
+ if (strtoupper($default) === 'NULL') {
+ $default = null;
+ } elseif (is_numeric($default)) {
+ $default = (int) $default;
+ }
+
+ return $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasColumn($tableName, $columnName, $options = array())
+ {
+ $sql = sprintf(
+ "SELECT count(*) as [count]
+ FROM information_schema.columns
+ WHERE table_name = '%s' AND column_name = '%s'",
+ $tableName,
+ $columnName
+ );
+ $result = $this->fetchRow($sql);
+
+ return $result['count'] > 0;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addColumn(Table $table, Column $column)
+ {
+ $this->startCommandTimer();
+ $sql = sprintf(
+ 'ALTER TABLE %s ADD %s %s',
+ $this->quoteTableName($table->getName()),
+ $this->quoteColumnName($column->getName()),
+ $this->getColumnSqlDefinition($column)
+ );
+
+ $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType()));
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameColumn($tableName, $columnName, $newColumnName)
+ {
+ $this->startCommandTimer();
+
+ if (!$this->hasColumn($tableName, $columnName)) {
+ throw new \InvalidArgumentException("The specified column does not exist: $columnName");
+ }
+ $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName));
+ $this->renameDefault($tableName, $columnName, $newColumnName);
+ $this->execute(
+ sprintf(
+ "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ",
+ $tableName,
+ $columnName,
+ $newColumnName
+ )
+ );
+ $this->endCommandTimer();
+ }
+
+ protected function renameDefault($tableName, $columnName, $newColumnName)
+ {
+ $oldConstraintName = "DF_{$tableName}_{$columnName}";
+ $newConstraintName = "DF_{$tableName}_{$newColumnName}";
+ $sql = <<<SQL
+IF (OBJECT_ID('$oldConstraintName', 'D') IS NOT NULL)
+BEGIN
+ EXECUTE sp_rename N'%s', N'%s', N'OBJECT'
+END
+SQL;
+ $this->execute(sprintf(
+ $sql,
+ $oldConstraintName,
+ $newConstraintName
+ ));
+ }
+
+ public function changeDefault($tableName, Column $newColumn)
+ {
+ $constraintName = "DF_{$tableName}_{$newColumn->getName()}";
+ $default = $newColumn->getDefault();
+
+ if ($default === null) {
+ $default = 'DEFAULT NULL';
+ } else {
+ $default = $this->getDefaultValueDefinition($default);
+ }
+
+ if (empty($default)) {
+ return;
+ }
+
+ $this->execute(sprintf(
+ 'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s',
+ $this->quoteTableName($tableName),
+ $constraintName,
+ $default,
+ $this->quoteColumnName($newColumn->getName())
+ ));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function changeColumn($tableName, $columnName, Column $newColumn)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType()));
+ $columns = $this->getColumns($tableName);
+ $changeDefault = $newColumn->getDefault() !== $columns[$columnName]->getDefault() || $newColumn->getType() !== $columns[$columnName]->getType();
+ if ($columnName !== $newColumn->getName()) {
+ $this->renameColumn($tableName, $columnName, $newColumn->getName());
+ }
+
+ if ($changeDefault) {
+ $this->dropDefaultConstraint($tableName, $newColumn->getName());
+ }
+
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s ALTER COLUMN %s %s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($newColumn->getName()),
+ $this->getColumnSqlDefinition($newColumn, false)
+ )
+ );
+ // change column comment if needed
+ if ($newColumn->getComment()) {
+ $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
+ $this->execute($sql);
+ }
+
+ if ($changeDefault) {
+ $this->changeDefault($tableName, $newColumn);
+ }
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropColumn($tableName, $columnName)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropColumn', array($tableName, $columnName));
+ $this->dropDefaultConstraint($tableName, $columnName);
+
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s DROP COLUMN %s',
+ $this->quoteTableName($tableName),
+ $this->quoteColumnName($columnName)
+ )
+ );
+ $this->endCommandTimer();
+ }
+
+ protected function dropDefaultConstraint($tableName, $columnName)
+ {
+ $defaultConstraint = $this->getDefaultConstraint($tableName, $columnName);
+
+ if (!$defaultConstraint) {
+ return;
+ }
+
+ $this->dropForeignKey($tableName, $columnName, $defaultConstraint);
+ }
+
+ protected function getDefaultConstraint($tableName, $columnName)
+ {
+ $sql = "SELECT
+ default_constraints.name
+FROM
+ sys.all_columns
+
+ INNER JOIN
+ sys.tables
+ ON all_columns.object_id = tables.object_id
+
+ INNER JOIN
+ sys.schemas
+ ON tables.schema_id = schemas.schema_id
+
+ INNER JOIN
+ sys.default_constraints
+ ON all_columns.default_object_id = default_constraints.object_id
+
+WHERE
+ schemas.name = 'dbo'
+ AND tables.name = '{$tableName}'
+ AND all_columns.name = '{$columnName}'";
+
+ $rows = $this->fetchAll($sql);
+ return empty($rows) ? false : $rows[0]['name'];
+ }
+
+ protected function getIndexColums($tableId, $indexId)
+ {
+ $sql = "SELECT AC.[name] AS [column_name]
+FROM sys.[index_columns] IC
+ INNER JOIN sys.[all_columns] AC ON IC.[column_id] = AC.[column_id]
+WHERE AC.[object_id] = {$tableId} AND IC.[index_id] = {$indexId} AND IC.[object_id] = {$tableId}
+ORDER BY IC.[key_ordinal];";
+
+ $rows = $this->fetchAll($sql);
+ $columns = array();
+ foreach($rows as $row) {
+ $columns[] = strtolower($row['column_name']);
+ }
+ return $columns;
+ }
+
+ /**
+ * Get an array of indexes from a particular table.
+ *
+ * @param string $tableName Table Name
+ * @return array
+ */
+ public function getIndexes($tableName)
+ {
+ $indexes = array();
+ $sql = "SELECT I.[name] AS [index_name], I.[index_id] as [index_id], T.[object_id] as [table_id]
+FROM sys.[tables] AS T
+ INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]
+WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP' AND T.[name] = '{$tableName}'
+ORDER BY T.[name], I.[index_id];";
+
+ $rows = $this->fetchAll($sql);
+ foreach ($rows as $row) {
+ $columns = $this->getIndexColums($row['table_id'], $row['index_id']);
+ $indexes[$row['index_name']] = array('columns' => $columns);
+ }
+
+ return $indexes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasIndex($tableName, $columns)
+ {
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+
+ $columns = array_map('strtolower', $columns);
+ $indexes = $this->getIndexes($tableName);
+
+ foreach ($indexes as $index) {
+ $a = array_diff($columns, $index['columns']);
+
+ if (empty($a)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasIndexByName($tableName, $indexName)
+ {
+ $indexes = $this->getIndexes($tableName);
+
+ foreach ($indexes as $name => $index) {
+ if ($name === $indexName) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addIndex(Table $table, Index $index)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('addIndex', array($table->getName(), $index->getColumns()));
+ $sql = $this->getIndexSqlDefinition($index, $table->getName());
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndex($tableName, $columns)
+ {
+ $this->startCommandTimer();
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+
+ $this->writeCommand('dropIndex', array($tableName, $columns));
+ $indexes = $this->getIndexes($tableName);
+ $columns = array_map('strtolower', $columns);
+
+ foreach ($indexes as $indexName => $index) {
+ $a = array_diff($columns, $index['columns']);
+ if (empty($a)) {
+ $this->execute(
+ sprintf(
+ 'DROP INDEX %s ON %s',
+ $this->quoteColumnName($indexName),
+ $this->quoteTableName($tableName)
+ )
+ );
+ $this->endCommandTimer();
+ return;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndexByName($tableName, $indexName)
+ {
+ $this->startCommandTimer();
+
+ $this->writeCommand('dropIndexByName', array($tableName, $indexName));
+ $indexes = $this->getIndexes($tableName);
+
+ foreach ($indexes as $name => $index) {
+ if ($name === $indexName) {
+ $this->execute(
+ sprintf(
+ 'DROP INDEX %s ON %s',
+ $this->quoteColumnName($indexName),
+ $this->quoteTableName($tableName)
+ )
+ );
+ $this->endCommandTimer();
+ return;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasForeignKey($tableName, $columns, $constraint = null)
+ {
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+ $foreignKeys = $this->getForeignKeys($tableName);
+ if ($constraint) {
+ if (isset($foreignKeys[$constraint])) {
+ return !empty($foreignKeys[$constraint]);
+ }
+ return false;
+ } else {
+ foreach ($foreignKeys as $key) {
+ $a = array_diff($columns, $key['columns']);
+ if (empty($a)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Get an array of foreign keys from a particular table.
+ *
+ * @param string $tableName Table Name
+ * @return array
+ */
+ protected function getForeignKeys($tableName)
+ {
+ $foreignKeys = array();
+ $rows = $this->fetchAll(sprintf(
+ "SELECT
+ tc.constraint_name,
+ tc.table_name, kcu.column_name,
+ ccu.table_name AS referenced_table_name,
+ ccu.column_name AS referenced_column_name
+ FROM
+ information_schema.table_constraints AS tc
+ JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
+ JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
+ WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
+ ORDER BY kcu.ordinal_position",
+ $tableName
+ ));
+ foreach ($rows as $row) {
+ $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
+ $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
+ $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
+ $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
+ }
+
+ return $foreignKeys;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addForeignKey(Table $table, ForeignKey $foreignKey)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns()));
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s ADD %s',
+ $this->quoteTableName($table->getName()),
+ $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
+ )
+ );
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropForeignKey($tableName, $columns, $constraint = null)
+ {
+ $this->startCommandTimer();
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+
+ $this->writeCommand('dropForeignKey', array($tableName, $columns));
+
+ if ($constraint) {
+ $this->execute(
+ sprintf(
+ 'ALTER TABLE %s DROP CONSTRAINT %s',
+ $this->quoteTableName($tableName),
+ $constraint
+ )
+ );
+ $this->endCommandTimer();
+ return;
+ } else {
+ foreach ($columns as $column) {
+ $rows = $this->fetchAll(sprintf(
+ "SELECT
+ tc.constraint_name,
+ tc.table_name, kcu.column_name,
+ ccu.table_name AS referenced_table_name,
+ ccu.column_name AS referenced_column_name
+ FROM
+ information_schema.table_constraints AS tc
+ JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
+ JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
+ WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s'
+ ORDER BY kcu.ordinal_position",
+ $tableName,
+ $column
+ ));
+ foreach ($rows as $row) {
+ $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
+ }
+ }
+ }
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSqlType($type, $limit = null)
+ {
+ switch ($type) {
+ case static::PHINX_TYPE_STRING:
+ return array('name' => 'nvarchar', 'limit' => 255);
+ break;
+ case static::PHINX_TYPE_CHAR:
+ return array('name' => 'nchar', 'limit' => 255);
+ break;
+ case static::PHINX_TYPE_TEXT:
+ return array('name' => 'ntext');
+ break;
+ case static::PHINX_TYPE_INTEGER:
+ return array('name' => 'int');
+ break;
+ case static::PHINX_TYPE_BIG_INTEGER:
+ return array('name' => 'bigint');
+ break;
+ case static::PHINX_TYPE_FLOAT:
+ return array('name' => 'float');
+ break;
+ case static::PHINX_TYPE_DECIMAL:
+ return array('name' => 'decimal');
+ break;
+ case static::PHINX_TYPE_DATETIME:
+ case static::PHINX_TYPE_TIMESTAMP:
+ return array('name' => 'datetime');
+ break;
+ case static::PHINX_TYPE_TIME:
+ return array('name' => 'time');
+ break;
+ case static::PHINX_TYPE_DATE:
+ return array('name' => 'date');
+ break;
+ case static::PHINX_TYPE_BLOB:
+ case static::PHINX_TYPE_BINARY:
+ return array('name' => 'varbinary');
+ break;
+ case static::PHINX_TYPE_BOOLEAN:
+ return array('name' => 'bit');
+ break;
+ case static::PHINX_TYPE_UUID:
+ return array('name' => 'uniqueidentifier');
+ case static::PHINX_TYPE_FILESTREAM:
+ return array('name' => 'varbinary', 'limit' => 'max');
+ // Geospatial database types
+ case static::PHINX_TYPE_GEOMETRY:
+ case static::PHINX_TYPE_POINT:
+ case static::PHINX_TYPE_LINESTRING:
+ case static::PHINX_TYPE_POLYGON:
+ // SQL Server stores all spatial data using a single data type.
+ // Specific types (point, polygon, etc) are set at insert time.
+ return array('name' => 'geography');
+ break;
+ default:
+ throw new \RuntimeException('The type: "' . $type . '" is not supported.');
+ }
+ }
+
+ /**
+ * Returns Phinx type by SQL type
+ *
+ * @param $sqlTypeDef
+ * @throws \RuntimeException
+ * @internal param string $sqlType SQL type
+ * @returns string Phinx type
+ */
+ public function getPhinxType($sqlType)
+ {
+ switch ($sqlType) {
+ case 'nvarchar':
+ case 'varchar':
+ return static::PHINX_TYPE_STRING;
+ case 'char':
+ case 'nchar':
+ return static::PHINX_TYPE_CHAR;
+ case 'text':
+ case 'ntext':
+ return static::PHINX_TYPE_TEXT;
+ case 'int':
+ case 'integer':
+ return static::PHINX_TYPE_INTEGER;
+ case 'decimal':
+ case 'numeric':
+ case 'money':
+ return static::PHINX_TYPE_DECIMAL;
+ case 'bigint':
+ return static::PHINX_TYPE_BIG_INTEGER;
+ case 'real':
+ case 'float':
+ return static::PHINX_TYPE_FLOAT;
+ case 'binary':
+ case 'image':
+ case 'varbinary':
+ return static::PHINX_TYPE_BINARY;
+ break;
+ case 'time':
+ return static::PHINX_TYPE_TIME;
+ case 'date':
+ return static::PHINX_TYPE_DATE;
+ case 'datetime':
+ case 'timestamp':
+ return static::PHINX_TYPE_DATETIME;
+ case 'bit':
+ return static::PHINX_TYPE_BOOLEAN;
+ case 'uniqueidentifier':
+ return static::PHINX_TYPE_UUID;
+ case 'filestream':
+ return static::PHINX_TYPE_FILESTREAM;
+ default:
+ throw new \RuntimeException('The SqlServer type: "' . $sqlType . '" is not supported');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createDatabase($name, $options = array())
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('createDatabase', array($name));
+
+ if (isset($options['collation'])) {
+ $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation']));
+ } else {
+ $this->execute(sprintf('CREATE DATABASE [%s]', $name));
+ }
+ $this->execute(sprintf('USE [%s]', $name));
+ $this->endCommandTimer();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasDatabase($name)
+ {
+ $result = $this->fetchRow(
+ sprintf(
+ 'SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = \'%s\'',
+ $name
+ )
+ );
+
+ return $result['count'] > 0;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropDatabase($name)
+ {
+ $this->startCommandTimer();
+ $this->writeCommand('dropDatabase', array($name));
+ $sql = <<<SQL
+USE master;
+IF EXISTS(select * from sys.databases where name=N'$name')
+ALTER DATABASE [$name] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
+DROP DATABASE [$name];
+SQL;
+ $this->execute($sql);
+ $this->endCommandTimer();
+ }
+
+ /**
+ * Get the defintion for a `DEFAULT` statement.
+ *
+ * @param mixed $default
+ * @return string
+ */
+ protected function getDefaultValueDefinition($default)
+ {
+ if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
+ $default = $this->getConnection()->quote($default);
+ } elseif (is_bool($default)) {
+ $default = $this->castToBool($default);
+ }
+ return isset($default) ? ' DEFAULT ' . $default : '';
+ }
+
+ /**
+ * Gets the SqlServer Column Definition for a Column object.
+ *
+ * @param Column $column Column
+ * @return string
+ */
+ protected function getColumnSqlDefinition(Column $column, $create = true)
+ {
+ $buffer = array();
+
+ $sqlType = $this->getSqlType($column->getType());
+ $buffer[] = strtoupper($sqlType['name']);
+ // integers cant have limits in SQlServer
+ $noLimits = array(
+ 'bigint',
+ 'int',
+ 'tinyint'
+ );
+ if (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) {
+ $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']);
+ }
+ if ($column->getPrecision() && $column->getScale()) {
+ $buffer[] = '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
+ }
+
+ $properties = $column->getProperties();
+ $buffer[] = $column->getType() === 'filestream' ? 'FILESTREAM' : '';
+ $buffer[] = isset($properties['rowguidcol']) ? 'ROWGUIDCOL' : '';
+
+ $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
+
+ if ($create === true) {
+ if ($column->getDefault() === null && $column->isNull()) {
+ $buffer[] = ' DEFAULT NULL';
+ } else {
+ $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
+ }
+ }
+
+ if ($column->isIdentity()) {
+ $buffer[] = 'IDENTITY(1, 1)';
+ }
+
+ return implode(' ', $buffer);
+ }
+
+ /**
+ * Gets the SqlServer Index Definition for an Index object.
+ *
+ * @param Index $index Index
+ * @return string
+ */
+ protected function getIndexSqlDefinition(Index $index, $tableName)
+ {
+ if (is_string($index->getName())) {
+ $indexName = $index->getName();
+ } else {
+ $columnNames = $index->getColumns();
+ if (is_string($columnNames)) {
+ $columnNames = array($columnNames);
+ }
+ $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
+ }
+ $def = sprintf(
+ "CREATE %s INDEX %s ON %s (%s);",
+ ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
+ $indexName,
+ $this->quoteTableName($tableName),
+ '[' . implode('],[', $index->getColumns()) . ']'
+ );
+
+ return $def;
+ }
+
+ /**
+ * Gets the SqlServer Foreign Key Definition for an ForeignKey object.
+ *
+ * @param ForeignKey $foreignKey
+ * @return string
+ */
+ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName)
+ {
+ $def = ' CONSTRAINT "';
+ $def .= $tableName . '_' . implode('_', $foreignKey->getColumns());
+ $def .= '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
+ $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
+ if ($foreignKey->getOnDelete()) {
+ $def .= " ON DELETE {$foreignKey->getOnDelete()}";
+ }
+ if ($foreignKey->getOnUpdate()) {
+ $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
+ }
+
+ return $def;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumnTypes()
+ {
+ return array_merge(parent::getColumnTypes(), array('filestream'));
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php
new file mode 100755
index 0000000..66a850c
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php
@@ -0,0 +1,272 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace Phinx\Db\Adapter;
+
+use Phinx\Db\Table;
+use Phinx\Db\Table\Column;
+use Phinx\Db\Table\Index;
+use Phinx\Db\Table\ForeignKey;
+
+/**
+ * Table prefix/suffix adapter.
+ *
+ * Used for inserting a prefix or suffix into table names.
+ *
+ * @author Samuel Fisher <sam@sfisher.co>
+ */
+class TablePrefixAdapter extends AdapterWrapper
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getAdapterType()
+ {
+ return 'TablePrefixAdapter';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasTable($tableName)
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ return parent::hasTable($adapterTableName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createTable(Table $table)
+ {
+ $adapterTable = clone $table;
+ $adapterTableName = $this->getAdapterTableName($table->getName());
+ $adapterTable->setName($adapterTableName);
+
+ foreach ($adapterTable->getForeignKeys() as $fk) {
+ $adapterReferenceTable = $fk->getReferencedTable();
+ $adapterReferenceTableName = $this->getAdapterTableName($adapterReferenceTable->getName());
+ $adapterReferenceTable->setName($adapterReferenceTableName);
+ }
+
+ return parent::createTable($adapterTable);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameTable($tableName, $newTableName)
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ $adapterNewTableName = $this->getAdapterTableName($newTableName);
+ return parent::renameTable($adapterTableName, $adapterNewTableName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropTable($tableName)
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ return parent::dropTable($adapterTableName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns($tableName)
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ return parent::getColumns($adapterTableName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasColumn($tableName, $columnName)
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ return parent::hasColumn($adapterTableName, $columnName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addColumn(Table $table, Column $column)
+ {
+ $adapterTable = clone $table;
+ $adapterTableName = $this->getAdapterTableName($table->getName());
+ $adapterTable->setName($adapterTableName);
+ return parent::addColumn($adapterTable, $column);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function renameColumn($tableName, $columnName, $newColumnName)
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ return parent::renameColumn($adapterTableName, $columnName, $newColumnName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function changeColumn($tableName, $columnName, Column $newColumn)
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ return parent::changeColumn($adapterTableName, $columnName, $newColumn);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropColumn($tableName, $columnName)
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ return parent::dropColumn($adapterTableName, $columnName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasIndex($tableName, $columns)
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ return parent::hasIndex($adapterTableName, $columns);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasIndexByName($tableName, $indexName)
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ return parent::hasIndexByName($adapterTableName, $indexName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addIndex(Table $table, Index $index)
+ {
+ $adapterTable = clone $table;
+ $adapterTableName = $this->getAdapterTableName($table->getName());
+ $adapterTable->setName($adapterTableName);
+ return parent::addIndex($adapterTable, $index);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndex($tableName, $columns, $options = array())
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ return parent::dropIndex($adapterTableName, $columns, $options);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropIndexByName($tableName, $indexName)
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ return parent::dropIndexByName($adapterTableName, $indexName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasForeignKey($tableName, $columns, $constraint = null)
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ return parent::hasForeignKey($adapterTableName, $columns, $constraint);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addForeignKey(Table $table, ForeignKey $foreignKey)
+ {
+ $adapterTable = clone $table;
+ $adapterTableName = $this->getAdapterTableName($table->getName());
+ $adapterTable->setName($adapterTableName);
+ return parent::addForeignKey($adapterTable, $foreignKey);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropForeignKey($tableName, $columns, $constraint = null)
+ {
+ $adapterTableName = $this->getAdapterTableName($tableName);
+ return parent::dropForeignKey($adapterTableName, $columns, $constraint);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function insert(Table $table, $row)
+ {
+ $adapterTable = clone $table;
+ $adapterTableName = $this->getAdapterTableName($table->getName());
+ $adapterTable->setName($adapterTableName);
+ return parent::insert($adapterTable, $row);
+ }
+
+ /**
+ * Gets the table prefix.
+ *
+ * @return string
+ */
+ public function getPrefix()
+ {
+ return (string) $this->getOption('table_prefix');
+ }
+
+ /**
+ * Gets the table suffix.
+ *
+ * @return string
+ */
+ public function getSuffix()
+ {
+ return (string) $this->getOption('table_suffix');
+ }
+
+ /**
+ * Applies the prefix and suffix to the table name.
+ *
+ * @param string $tableName
+ * @return string
+ */
+ public function getAdapterTableName($tableName)
+ {
+ return $this->getPrefix() . $tableName . $this->getSuffix();
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/WrapperInterface.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/WrapperInterface.php
new file mode 100755
index 0000000..b8181d4
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/WrapperInterface.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db\Adapter
+ */
+namespace Phinx\Db\Adapter;
+
+/**
+ * Wrapper Interface.
+ *
+ * @author Woody Gilk <woody.gilk@gmail.com>
+ */
+interface WrapperInterface
+{
+ /**
+ * Class constructor, must always wrap another adapter.
+ *
+ * @param AdapterInterface $adapter
+ */
+ public function __construct(AdapterInterface $adapter);
+
+ /**
+ * Sets the database adapter to proxy commands to.
+ *
+ * @param AdapterInterface $adapter
+ * @return AdapterInterface
+ */
+ public function setAdapter(AdapterInterface $adapter);
+
+ /**
+ * Gets the database adapter.
+ *
+ * @throws \RuntimeException if the adapter has not been set
+ * @return AdapterInterface
+ */
+ public function getAdapter();
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table.php
new file mode 100755
index 0000000..b0e7202
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table.php
@@ -0,0 +1,674 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db
+ */
+namespace Phinx\Db;
+
+use Phinx\Db\Table\Column;
+use Phinx\Db\Table\Index;
+use Phinx\Db\Table\ForeignKey;
+use Phinx\Db\Adapter\AdapterInterface;
+
+/**
+ *
+ * This object is based loosely on: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html.
+ */
+class Table
+{
+ /**
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * @var array
+ */
+ protected $options = array();
+
+ /**
+ * @var AdapterInterface
+ */
+ protected $adapter;
+
+ /**
+ * @var array
+ */
+ protected $columns = array();
+
+ /**
+ * @var array
+ */
+ protected $indexes = array();
+
+ /**
+ * @var ForeignKey[]
+ */
+ protected $foreignKeys = array();
+
+ /**
+ * @var array
+ */
+ protected $data = array();
+
+ /**
+ * Class Constuctor.
+ *
+ * @param string $name Table Name
+ * @param array $options Options
+ * @param AdapterInterface $adapter Database Adapter
+ */
+ public function __construct($name, $options = array(), AdapterInterface $adapter = null)
+ {
+ $this->setName($name);
+ $this->setOptions($options);
+
+ if (null !== $adapter) {
+ $this->setAdapter($adapter);
+ }
+ }
+
+ /**
+ * Sets the table name.
+ *
+ * @param string $name Table Name
+ * @return Table
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * Gets the table name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Sets the table options.
+ *
+ * @param array $options
+ * @return Table
+ */
+ public function setOptions($options)
+ {
+ $this->options = $options;
+ return $this;
+ }
+
+ /**
+ * Gets the table options.
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Sets the database adapter.
+ *
+ * @param AdapterInterface $adapter Database Adapter
+ * @return Table
+ */
+ public function setAdapter(AdapterInterface $adapter)
+ {
+ $this->adapter = $adapter;
+ return $this;
+ }
+
+ /**
+ * Gets the database adapter.
+ *
+ * @return AdapterInterface
+ */
+ public function getAdapter()
+ {
+ return $this->adapter;
+ }
+
+ /**
+ * Does the table exist?
+ *
+ * @return boolean
+ */
+ public function exists()
+ {
+ return $this->getAdapter()->hasTable($this->getName());
+ }
+
+ /**
+ * Drops the database table.
+ *
+ * @return void
+ */
+ public function drop()
+ {
+ $this->getAdapter()->dropTable($this->getName());
+ }
+
+ /**
+ * Renames the database table.
+ *
+ * @param string $newTableName New Table Name
+ * @return Table
+ */
+ public function rename($newTableName)
+ {
+ $this->getAdapter()->renameTable($this->getName(), $newTableName);
+ $this->setName($newTableName);
+ return $this;
+ }
+
+ /**
+ * Sets an array of columns waiting to be committed.
+ * Use setPendingColumns
+ *
+ * @deprecated
+ * @param array $columns Columns
+ * @return Table
+ */
+ public function setColumns($columns)
+ {
+ $this->setPendingColumns($columns);
+ }
+
+ /**
+ * Gets an array of the table columns.
+ *
+ * @return Column[]
+ */
+ public function getColumns()
+ {
+ return $this->getAdapter()->getColumns($this->getName());
+ }
+
+ /**
+ * Sets an array of columns waiting to be committed.
+ *
+ * @param array $columns Columns
+ * @return Table
+ */
+ public function setPendingColumns($columns)
+ {
+ $this->columns = $columns;
+ return $this;
+ }
+
+ /**
+ * Gets an array of columns waiting to be committed.
+ *
+ * @return Column[]
+ */
+ public function getPendingColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * Sets an array of columns waiting to be indexed.
+ *
+ * @param array $indexes Indexes
+ * @return Table
+ */
+ public function setIndexes($indexes)
+ {
+ $this->indexes = $indexes;
+ return $this;
+ }
+
+ /**
+ * Gets an array of indexes waiting to be committed.
+ *
+ * @return array
+ */
+ public function getIndexes()
+ {
+ return $this->indexes;
+ }
+
+ /**
+ * Sets an array of foreign keys waiting to be commited.
+ *
+ * @param ForeignKey[] $foreignKeys foreign keys
+ * @return Table
+ */
+ public function setForeignKeys($foreignKeys)
+ {
+ $this->foreignKeys = $foreignKeys;
+ return $this;
+ }
+
+ /**
+ * Gets an array of foreign keys waiting to be commited.
+ *
+ * @return array|ForeignKey[]
+ */
+ public function getForeignKeys()
+ {
+ return $this->foreignKeys;
+ }
+
+ /**
+ * Sets an array of data to be inserted.
+ *
+ * @param array $data Data
+ * @return Table
+ */
+ public function setData($data)
+ {
+ $this->data = $data;
+ return $this;
+ }
+
+ /**
+ * Gets the data waiting to be inserted.
+ *
+ * @return array
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Resets all of the pending table changes.
+ *
+ * @return void
+ */
+ public function reset()
+ {
+ $this->setPendingColumns(array());
+ $this->setIndexes(array());
+ $this->setForeignKeys(array());
+ $this->setData(array());
+ }
+
+ /**
+ * Add a table column.
+ *
+ * Type can be: string, text, integer, float, decimal, datetime, timestamp,
+ * time, date, binary, boolean.
+ *
+ * Valid options can be: limit, default, null, precision or scale.
+ *
+ * @param string|Column $columnName Column Name
+ * @param string $type Column Type
+ * @param array $options Column Options
+ * @throws \RuntimeException
+ * @throws \InvalidArgumentException
+ * @return Table
+ */
+ public function addColumn($columnName, $type = null, $options = array())
+ {
+ // we need an adapter set to add a column
+ if (null === $this->getAdapter()) {
+ throw new \RuntimeException('An adapter must be specified to add a column.');
+ }
+
+ // create a new column object if only strings were supplied
+ if (!$columnName instanceof Column) {
+ $column = new Column();
+ $column->setName($columnName);
+ $column->setType($type);
+ $column->setOptions($options); // map options to column methods
+ } else {
+ $column = $columnName;
+ }
+
+ // Delegate to Adapters to check column type
+ if (!$this->getAdapter()->isValidColumnType($column)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'An invalid column type "%s" was specified for column "%s".',
+ $column->getType(),
+ $column->getName()
+ ));
+ }
+
+ $this->columns[] = $column;
+ return $this;
+ }
+
+ /**
+ * Remove a table column.
+ *
+ * @param string $columnName Column Name
+ * @return Table
+ */
+ public function removeColumn($columnName)
+ {
+ $this->getAdapter()->dropColumn($this->getName(), $columnName);
+ return $this;
+ }
+
+ /**
+ * Rename a table column.
+ *
+ * @param string $oldName Old Column Name
+ * @param string $newName New Column Name
+ * @return Table
+ */
+ public function renameColumn($oldName, $newName)
+ {
+ $this->getAdapter()->renameColumn($this->getName(), $oldName, $newName);
+ return $this;
+ }
+
+ /**
+ * Change a table column type.
+ *
+ * @param string $columnName Column Name
+ * @param string|Column $newColumnType New Column Type
+ * @param array $options Options
+ * @return Table
+ */
+ public function changeColumn($columnName, $newColumnType, $options = array())
+ {
+ // create a column object if one wasn't supplied
+ if (!$newColumnType instanceof Column) {
+ $newColumn = new Column();
+ $newColumn->setType($newColumnType);
+ $newColumn->setOptions($options);
+ } else {
+ $newColumn = $newColumnType;
+ }
+
+ // if the name was omitted use the existing column name
+ if (null === $newColumn->getName() || strlen($newColumn->getName()) === 0) {
+ $newColumn->setName($columnName);
+ }
+
+ $this->getAdapter()->changeColumn($this->getName(), $columnName, $newColumn);
+ return $this;
+ }
+
+ /**
+ * Checks to see if a column exists.
+ *
+ * @param string $columnName Column Name
+ * @param array $options Options
+ * @return boolean
+ */
+ public function hasColumn($columnName, $options = array())
+ {
+ return $this->getAdapter()->hasColumn($this->getName(), $columnName, $options);
+ }
+
+ /**
+ * Add an index to a database table.
+ *
+ * In $options you can specific unique = true/false or name (index name).
+ *
+ * @param string|array|Index $columns Table Column(s)
+ * @param array $options Index Options
+ * @return Table
+ */
+ public function addIndex($columns, $options = array())
+ {
+ // create a new index object if strings or an array of strings were supplied
+ if (!$columns instanceof Index) {
+ $index = new Index();
+ if (is_string($columns)) {
+ $columns = array($columns); // str to array
+ }
+ $index->setColumns($columns);
+ $index->setOptions($options);
+ } else {
+ $index = $columns;
+ }
+
+ $this->indexes[] = $index;
+ return $this;
+ }
+
+ /**
+ * Removes the given index from a table.
+ *
+ * @param array $columns Columns
+ * @param array $options Options
+ * @return Table
+ */
+ public function removeIndex($columns, $options = array())
+ {
+ $this->getAdapter()->dropIndex($this->getName(), $columns, $options);
+ return $this;
+ }
+
+ /**
+ * Removes the given index identified by its name from a table.
+ *
+ * @param string $name Index name
+ * @return Table
+ */
+ public function removeIndexByName($name)
+ {
+ $this->getAdapter()->dropIndexByName($this->getName(), $name);
+ return $this;
+ }
+
+ /**
+ * Checks to see if an index exists.
+ *
+ * @param string|array $columns Columns
+ * @param array $options Options
+ * @return boolean
+ */
+ public function hasIndex($columns, $options = array())
+ {
+ return $this->getAdapter()->hasIndex($this->getName(), $columns, $options);
+ }
+
+ /**
+ * Add a foreign key to a database table.
+ *
+ * In $options you can specify on_delete|on_delete = cascade|no_action ..,
+ * on_update, constraint = constraint name.
+ *
+ * @param string|array $columns Columns
+ * @param string|Table $referencedTable Referenced Table
+ * @param string|array $referencedColumns Referenced Columns
+ * @param array $options Options
+ * @return Table
+ */
+ public function addForeignKey($columns, $referencedTable, $referencedColumns = array('id'), $options = array())
+ {
+ if (is_string($referencedColumns)) {
+ $referencedColumns = array($referencedColumns); // str to array
+ }
+ $fk = new ForeignKey();
+ if ($referencedTable instanceof Table) {
+ $fk->setReferencedTable($referencedTable);
+ } else {
+ $fk->setReferencedTable(new Table($referencedTable, array(), $this->adapter));
+ }
+ $fk->setColumns($columns)
+ ->setReferencedColumns($referencedColumns)
+ ->setOptions($options);
+ $this->foreignKeys[] = $fk;
+
+ return $this;
+ }
+
+ /**
+ * Removes the given foreign key from the table.
+ *
+ * @param string|array $columns Column(s)
+ * @param null|string $constraint Constraint names
+ * @return Table
+ */
+ public function dropForeignKey($columns, $constraint = null)
+ {
+ if (is_string($columns)) {
+ $columns = array($columns);
+ }
+ if ($constraint) {
+ $this->getAdapter()->dropForeignKey($this->getName(), array(), $constraint);
+ } else {
+ $this->getAdapter()->dropForeignKey($this->getName(), $columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Checks to see if a foreign key exists.
+ *
+ * @param string|array $columns Column(s)
+ * @param null|string $constraint Constraint names
+ * @return boolean
+ */
+ public function hasForeignKey($columns, $constraint = null)
+ {
+ return $this->getAdapter()->hasForeignKey($this->getName(), $columns, $constraint);
+ }
+
+ /**
+ * Add timestamp columns created_at and updated_at to the table.
+ *
+ * @param string $createdAtColumnName
+ * @param string $updatedAtColumnName
+ *
+ * @return Table
+ */
+ public function addTimestamps($createdAtColumnName = 'created_at', $updatedAtColumnName = 'updated_at')
+ {
+ $createdAtColumnName = is_null($createdAtColumnName) ? 'created_at' : $createdAtColumnName;
+ $updatedAtColumnName = is_null($updatedAtColumnName) ? 'updated_at' : $updatedAtColumnName;
+ $this->addColumn($createdAtColumnName, 'timestamp', array(
+ 'default' => 'CURRENT_TIMESTAMP',
+ 'update' => ''
+ ))
+ ->addColumn($updatedAtColumnName, 'timestamp', array(
+ 'null' => true,
+ 'default' => null
+ ));
+
+ return $this;
+ }
+
+ /**
+ * Insert data into the table.
+ *
+ * @param $data array of data in the form:
+ * array(
+ * array("col1" => "value1", "col2" => "anotherValue1"),
+ * array("col2" => "value2", "col2" => "anotherValue2"),
+ * )
+ * or array("col1" => "value1", "col2" => "anotherValue1")
+ *
+ * @return Table
+ */
+ public function insert($data)
+ {
+ // handle array of array situations
+ if (isset($data[0]) && is_array($data[0])) {
+ foreach ($data as $row) {
+ $this->data[] = $row;
+ }
+ return $this;
+ }
+ $this->data[] = $data;
+ return $this;
+ }
+
+ /**
+ * Creates a table from the object instance.
+ *
+ * @return void
+ */
+ public function create()
+ {
+ $this->getAdapter()->createTable($this);
+ $this->saveData();
+ $this->reset(); // reset pending changes
+ }
+
+ /**
+ * Updates a table from the object instance.
+ *
+ * @throws \RuntimeException
+ * @return void
+ */
+ public function update()
+ {
+ if (!$this->exists()) {
+ throw new \RuntimeException('Cannot update a table that doesn\'t exist!');
+ }
+
+ // update table
+ foreach ($this->getPendingColumns() as $column) {
+ $this->getAdapter()->addColumn($this, $column);
+ }
+
+ foreach ($this->getIndexes() as $index) {
+ $this->getAdapter()->addIndex($this, $index);
+ }
+
+ foreach ($this->getForeignKeys() as $foreignKey) {
+ $this->getAdapter()->addForeignKey($this, $foreignKey);
+ }
+
+ $this->saveData();
+ $this->reset(); // reset pending changes
+ }
+
+ /**
+ * Commit the pending data waiting for insertion.
+ *
+ * @return void
+ */
+ public function saveData()
+ {
+ foreach ($this->getData() as $row) {
+ $this->getAdapter()->insert($this, $row);
+ }
+ }
+
+ /**
+ * Commits the table changes.
+ *
+ * If the table doesn't exist it is created otherwise it is updated.
+ *
+ * @return void
+ */
+ public function save()
+ {
+ if ($this->exists()) {
+ $this->update(); // update the table
+ } else {
+ $this->create(); // create the table
+ }
+
+ $this->reset(); // reset pending changes
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Column.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Column.php
new file mode 100755
index 0000000..874341e
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Column.php
@@ -0,0 +1,550 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db
+ */
+namespace Phinx\Db\Table;
+
+/**
+ *
+ * This object is based loosely on: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html.
+ */
+class Column
+{
+ /**
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * @var integer
+ */
+ protected $limit = null;
+
+ /**
+ * @var boolean
+ */
+ protected $null = false;
+
+ /**
+ * @var mixed
+ */
+ protected $default = null;
+
+ /**
+ * @var boolean
+ */
+ protected $identity = false;
+
+ /**
+ * @var integer
+ */
+ protected $precision;
+
+ /**
+ * @var integer
+ */
+ protected $scale;
+
+ /**
+ * @var string
+ */
+ protected $after;
+
+ /**
+ * @var string
+ */
+ protected $update;
+
+ /**
+ * @var string
+ */
+ protected $comment;
+
+ /**
+ * @var boolean
+ */
+ protected $signed = true;
+
+ /**
+ * @var boolean
+ */
+ protected $timezone = false;
+
+ /**
+ * @var array
+ */
+ protected $properties = array();
+
+ /**
+ * @var array
+ */
+ protected $values;
+
+ /**
+ * Sets the column name.
+ *
+ * @param string $name
+ * @return $this
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * Gets the column name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Sets the column type.
+ *
+ * @param string $type
+ * @return $this
+ */
+ public function setType($type)
+ {
+ $this->type = $type;
+ return $this;
+ }
+
+ /**
+ * Gets the column type.
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Sets the column limit.
+ *
+ * @param integer $limit
+ * @return $this
+ */
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+ return $this;
+ }
+
+ /**
+ * Gets the column limit.
+ *
+ * @return integer
+ */
+ public function getLimit()
+ {
+ return $this->limit;
+ }
+
+ /**
+ * Sets whether the column allows nulls.
+ *
+ * @param boolean $null
+ * @return $this
+ */
+ public function setNull($null)
+ {
+ $this->null = (bool) $null;
+ return $this;
+ }
+
+ /**
+ * Gets whether the column allows nulls.
+ *
+ * @return boolean
+ */
+ public function getNull()
+ {
+ return $this->null;
+ }
+
+ /**
+ * Does the column allow nulls?
+ *
+ * @return boolean
+ */
+ public function isNull()
+ {
+ return $this->getNull();
+ }
+
+ /**
+ * Sets the default column value.
+ *
+ * @param mixed $default
+ * @return $this
+ */
+ public function setDefault($default)
+ {
+ $this->default = $default;
+ return $this;
+ }
+
+ /**
+ * Gets the default column value.
+ *
+ * @return mixed
+ */
+ public function getDefault()
+ {
+ return $this->default;
+ }
+
+ /**
+ * Sets whether or not the column is an identity column.
+ *
+ * @param boolean $identity
+ * @return $this
+ */
+ public function setIdentity($identity)
+ {
+ $this->identity = $identity;
+ return $this;
+ }
+
+ /**
+ * Gets whether or not the column is an identity column.
+ *
+ * @return boolean
+ */
+ public function getIdentity()
+ {
+ return $this->identity;
+ }
+
+ /**
+ * Is the column an identity column?
+ *
+ * @return boolean
+ */
+ public function isIdentity()
+ {
+ return $this->getIdentity();
+ }
+
+ /**
+ * Sets the name of the column to add this column after.
+ *
+ * @param string $after After
+ * @return $this
+ */
+ public function setAfter($after)
+ {
+ $this->after = $after;
+ return $this;
+ }
+
+ /**
+ * Returns the name of the column to add this column after.
+ *
+ * @return string
+ */
+ public function getAfter()
+ {
+ return $this->after;
+ }
+
+ /**
+ * Sets the 'ON UPDATE' mysql column function.
+ *
+ * @param string $update On Update function
+ * @return $this
+ */
+ public function setUpdate($update)
+ {
+ $this->update = $update;
+ return $this;
+ }
+
+ /**
+ * Returns the value of the ON UPDATE column function.
+ *
+ * @return string
+ */
+ public function getUpdate()
+ {
+ return $this->update;
+ }
+
+ /**
+ * Sets the column precision for decimal.
+ *
+ * @param integer $precision
+ * @return $this
+ */
+ public function setPrecision($precision)
+ {
+ $this->precision = $precision;
+ return $this;
+ }
+
+ /**
+ * Gets the column precision for decimal.
+ *
+ * @return integer
+ */
+ public function getPrecision()
+ {
+ return $this->precision;
+ }
+
+ /**
+ * Sets the column scale for decimal.
+ *
+ * @param integer $scale
+ * @return $this
+ */
+ public function setScale($scale)
+ {
+ $this->scale = $scale;
+ return $this;
+ }
+
+ /**
+ * Gets the column scale for decimal.
+ *
+ * @return integer
+ */
+ public function getScale()
+ {
+ return $this->scale;
+ }
+
+ /**
+ * Sets the column comment.
+ *
+ * @param string $comment
+ * @return $this
+ */
+ public function setComment($comment)
+ {
+ $this->comment = $comment;
+ return $this;
+ }
+
+ /**
+ * Gets the column comment.
+ *
+ * @return string
+ */
+ public function getComment()
+ {
+ return $this->comment;
+ }
+
+ /**
+ * Sets whether field should be signed.
+ *
+ * @param bool $signed
+ * @return $this
+ */
+ public function setSigned($signed)
+ {
+ $this->signed = (bool) $signed;
+ return $this;
+ }
+
+ /**
+ * Gets whether field should be signed.
+ *
+ * @return string
+ */
+ public function getSigned()
+ {
+ return $this->signed;
+ }
+
+ /**
+ * Should the column be signed?
+ *
+ * @return boolean
+ */
+ public function isSigned()
+ {
+ return $this->getSigned();
+ }
+
+ /**
+ * Sets whether the field should have a timezone identifier.
+ * Used for date/time columns only!
+ *
+ * @param bool $timezone
+ * @return $this
+ */
+ public function setTimezone($timezone)
+ {
+ $this->timezone = (bool) $timezone;
+ return $this;
+ }
+
+ /**
+ * Gets whether field has a timezone identifier.
+ *
+ * @return boolean
+ */
+ public function getTimezone()
+ {
+ return $this->timezone;
+ }
+
+ /**
+ * Should the column have a timezone?
+ *
+ * @return boolean
+ */
+ public function isTimezone()
+ {
+ return $this->getTimezone();
+ }
+
+ /**
+ * Sets field properties.
+ *
+ * @param array $properties
+ *
+ * @return $this
+ */
+ public function setProperties($properties)
+ {
+ $this->properties = $properties;
+ return $this;
+ }
+
+ /**
+ * Gets field properties
+ *
+ * @return array
+ */
+ public function getProperties()
+ {
+ return $this->properties;
+ }
+
+ /**
+ * Sets field values.
+ *
+ * @param mixed (array|string) $values
+ *
+ * @return $this
+ */
+ public function setValues($values)
+ {
+ if (!is_array($values)) {
+ $values = preg_split('/,\s*/', $values);
+ }
+ $this->values = $values;
+ return $this;
+ }
+
+ /**
+ * Gets field values
+ *
+ * @return string
+ */
+ public function getValues()
+ {
+ return $this->values;
+ }
+
+ /**
+ * Gets all allowed options. Each option must have a corresponding `setFoo` method.
+ *
+ * @return array
+ */
+ protected function getValidOptions()
+ {
+ return array(
+ 'limit',
+ 'default',
+ 'null',
+ 'identity',
+ 'precision',
+ 'scale',
+ 'after',
+ 'update',
+ 'comment',
+ 'signed',
+ 'timezone',
+ 'properties',
+ 'values',
+ );
+ }
+
+ /**
+ * Gets all aliased options. Each alias must reference a valid option.
+ *
+ * @return array
+ */
+ protected function getAliasedOptions()
+ {
+ return array(
+ 'length' => 'limit',
+ );
+ }
+
+ /**
+ * Utility method that maps an array of column options to this objects methods.
+ *
+ * @param array $options Options
+ * @return $this
+ */
+ public function setOptions($options)
+ {
+ $validOptions = $this->getValidOptions();
+ $aliasOptions = $this->getAliasedOptions();
+
+ foreach ($options as $option => $value) {
+ if (isset($aliasOptions[$option])) {
+ // proxy alias -> option
+ $option = $aliasOptions[$option];
+ }
+
+ if (!in_array($option, $validOptions, true)) {
+ throw new \RuntimeException(sprintf('"%s" is not a valid column option.', $option));
+ }
+
+ $method = 'set' . ucfirst($option);
+ $this->$method($value);
+ }
+ return $this;
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/ForeignKey.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/ForeignKey.php
new file mode 100755
index 0000000..3033503
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/ForeignKey.php
@@ -0,0 +1,252 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db
+ * @author Leonid Kuzmin <lndkuzmin@gmail.com>
+ */
+namespace Phinx\Db\Table;
+
+use Phinx\Db\Table;
+
+class ForeignKey
+{
+ const CASCADE = 'CASCADE';
+ const RESTRICT = 'RESTRICT';
+ const SET_NULL = 'SET NULL';
+ const NO_ACTION = 'NO ACTION';
+
+ /**
+ * @var array
+ */
+ protected $columns = array();
+
+ /**
+ * @var Table
+ */
+ protected $referencedTable;
+
+ /**
+ * @var array
+ */
+ protected $referencedColumns = array();
+
+ /**
+ * @var string
+ */
+ protected $onDelete;
+
+ /**
+ * @var string
+ */
+ protected $onUpdate;
+
+ /**
+ * @var string|boolean
+ */
+ protected $constraint;
+
+ /**
+ * Sets the foreign key columns.
+ *
+ * @param array|string $columns
+ * @return ForeignKey
+ */
+ public function setColumns($columns)
+ {
+ if (is_string($columns)) {
+ $columns = array($columns);
+ }
+ $this->columns = $columns;
+ return $this;
+ }
+
+ /**
+ * Gets the foreign key columns.
+ *
+ * @return array
+ */
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * Sets the foreign key referenced table.
+ *
+ * @param Table $table
+ * @return ForeignKey
+ */
+ public function setReferencedTable(Table $table)
+ {
+ $this->referencedTable = $table;
+ return $this;
+ }
+
+ /**
+ * Gets the foreign key referenced table.
+ *
+ * @return Table
+ */
+ public function getReferencedTable()
+ {
+ return $this->referencedTable;
+ }
+
+ /**
+ * Sets the foreign key referenced columns.
+ *
+ * @param array $referencedColumns
+ * @return ForeignKey
+ */
+ public function setReferencedColumns(array $referencedColumns)
+ {
+ $this->referencedColumns = $referencedColumns;
+ return $this;
+ }
+
+ /**
+ * Gets the foreign key referenced columns.
+ *
+ * @return array
+ */
+ public function getReferencedColumns()
+ {
+ return $this->referencedColumns;
+ }
+
+ /**
+ * Sets ON DELETE action for the foreign key.
+ *
+ * @param string $onDelete
+ * @return ForeignKey
+ */
+ public function setOnDelete($onDelete)
+ {
+ $this->onDelete = $this->normalizeAction($onDelete);
+ return $this;
+ }
+
+ /**
+ * Gets ON DELETE action for the foreign key.
+ *
+ * @return string
+ */
+ public function getOnDelete()
+ {
+ return $this->onDelete;
+ }
+
+ /**
+ * Gets ON UPDATE action for the foreign key.
+ *
+ * @return string
+ */
+ public function getOnUpdate()
+ {
+ return $this->onUpdate;
+ }
+
+ /**
+ * Sets ON UPDATE action for the foreign key.
+ *
+ * @param string $onUpdate
+ * @return ForeignKey
+ */
+ public function setOnUpdate($onUpdate)
+ {
+ $this->onUpdate = $this->normalizeAction($onUpdate);
+ return $this;
+ }
+
+ /**
+ * Sets constraint for the foreign key.
+ *
+ * @param string $constraint
+ * @return ForeignKey
+ */
+ public function setConstraint($constraint)
+ {
+ $this->constraint = $constraint;
+ return $this;
+ }
+
+ /**
+ * Gets constraint name for the foreign key.
+ *
+ * @return string
+ */
+ public function getConstraint()
+ {
+ return $this->constraint;
+ }
+
+ /**
+ * Utility method that maps an array of index options to this objects methods.
+ *
+ * @param array $options Options
+ * @throws \RuntimeException
+ * @throws \InvalidArgumentException
+ * @return ForeignKey
+ */
+ public function setOptions($options)
+ {
+ // Valid Options
+ $validOptions = array('delete', 'update', 'constraint');
+ foreach ($options as $option => $value) {
+ if (!in_array($option, $validOptions, true)) {
+ throw new \RuntimeException(sprintf('"%s" is not a valid foreign key option.', $option));
+ }
+
+ // handle $options['delete'] as $options['update']
+ if ('delete' === $option) {
+ $this->setOnDelete($value);
+ } elseif ('update' === $option) {
+ $this->setOnUpdate($value);
+ } else {
+ $method = 'set' . ucfirst($option);
+ $this->$method($value);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * From passed value checks if it's correct and fixes if needed
+ *
+ * @param string $action
+ * @throws \InvalidArgumentException
+ * @return string
+ */
+ protected function normalizeAction($action)
+ {
+ $constantName = 'static::' . str_replace(' ', '_', strtoupper(trim($action)));
+ if (!defined($constantName)) {
+ throw new \InvalidArgumentException('Unknown action passed: ' . $action);
+ }
+ return constant($constantName);
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Index.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Index.php
new file mode 100755
index 0000000..82404cb
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Index.php
@@ -0,0 +1,185 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Db
+ */
+namespace Phinx\Db\Table;
+
+class Index
+{
+ /**
+ * @var string
+ */
+ const UNIQUE = 'unique';
+
+ /**
+ * @var string
+ */
+ const INDEX = 'index';
+
+ /**
+ * @var string
+ */
+ const FULLTEXT = 'fulltext';
+
+ /**
+ * @var array
+ */
+ protected $columns;
+
+ /**
+ * @var string
+ */
+ protected $type = self::INDEX;
+
+ /**
+ * @var string
+ */
+ protected $name = null;
+
+ /**
+ * @var integer
+ */
+ protected $limit = null;
+
+ /**
+ * Sets the index columns.
+ *
+ * @param array $columns
+ * @return Index
+ */
+ public function setColumns($columns)
+ {
+ $this->columns = $columns;
+ return $this;
+ }
+
+ /**
+ * Gets the index columns.
+ *
+ * @return array
+ */
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * Sets the index type.
+ *
+ * @param string $type
+ * @return Index
+ */
+ public function setType($type)
+ {
+ $this->type = $type;
+ return $this;
+ }
+
+ /**
+ * Gets the index type.
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Sets the index name.
+ *
+ * @param string $name
+ * @return Index
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * Gets the index name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Sets the index limit.
+ *
+ * @param integer $limit
+ * @return Index
+ */
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+ return $this;
+ }
+
+ /**
+ * Gets the index limit.
+ *
+ * @return integer
+ */
+ public function getLimit()
+ {
+ return $this->limit;
+ }
+
+ /**
+ * Utility method that maps an array of index options to this objects methods.
+ *
+ * @param array $options Options
+ * @throws \RuntimeException
+ * @return Index
+ */
+ public function setOptions($options)
+ {
+ // Valid Options
+ $validOptions = array('type', 'unique', 'name', 'limit');
+ foreach ($options as $option => $value) {
+ if (!in_array($option, $validOptions, true)) {
+ throw new \RuntimeException(sprintf('"%s" is not a valid index option.', $option));
+ }
+
+ // handle $options['unique']
+ if (strcasecmp($option, self::UNIQUE) === 0) {
+ if ((bool) $value) {
+ $this->setType(self::UNIQUE);
+ }
+ continue;
+ }
+
+ $method = 'set' . ucfirst($option);
+ $this->$method($value);
+ }
+ return $this;
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractMigration.php b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractMigration.php
new file mode 100755
index 0000000..1598cfb
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractMigration.php
@@ -0,0 +1,273 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Migration
+ */
+namespace Phinx\Migration;
+
+use Phinx\Db\Table;
+use Phinx\Db\Adapter\AdapterInterface;
+use think\console\Input as InputInterface;
+use think\console\Output as OutputInterface;
+
+/**
+ * Abstract Migration Class.
+ *
+ * It is expected that the migrations you write extend from this class.
+ *
+ * This abstract class proxies the various database methods to your specified
+ * adapter.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+abstract class AbstractMigration implements MigrationInterface
+{
+ /**
+ * @var float
+ */
+ protected $version;
+
+ /**
+ * @var AdapterInterface
+ */
+ protected $adapter;
+
+ /**
+ * @var OutputInterface
+ */
+ protected $output;
+
+ /**
+ * @var InputInterface
+ */
+ protected $input;
+
+ /**
+ * Class Constructor.
+ *
+ * @param int $version Migration Version
+ * @param InputInterface|null $input
+ * @param OutputInterface|null $output
+ */
+ final public function __construct($version, InputInterface $input = null, OutputInterface $output = null)
+ {
+ $this->version = $version;
+ if (!is_null($input)){
+ $this->setInput($input);
+ }
+ if (!is_null($output)){
+ $this->setOutput($output);
+ }
+
+ $this->init();
+ }
+
+ /**
+ * Initialize method.
+ *
+ * @return void
+ */
+ protected function init()
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function up()
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function down()
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setAdapter(AdapterInterface $adapter)
+ {
+ $this->adapter = $adapter;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAdapter()
+ {
+ return $this->adapter;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setInput(InputInterface $input)
+ {
+ $this->input = $input;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setOutput(OutputInterface $output)
+ {
+ $this->output = $output;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOutput()
+ {
+ return $this->output;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return get_class($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setVersion($version)
+ {
+ $this->version = $version;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($sql)
+ {
+ return $this->getAdapter()->execute($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function query($sql)
+ {
+ return $this->getAdapter()->query($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetchRow($sql)
+ {
+ return $this->getAdapter()->fetchRow($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetchAll($sql)
+ {
+ return $this->getAdapter()->fetchAll($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function insert($table, $data)
+ {
+ // convert to table object
+ if (is_string($table)) {
+ $table = new Table($table, array(), $this->getAdapter());
+ }
+ return $table->insert($data)->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createDatabase($name, $options)
+ {
+ $this->getAdapter()->createDatabase($name, $options);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dropDatabase($name)
+ {
+ $this->getAdapter()->dropDatabase($name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasTable($tableName)
+ {
+ return $this->getAdapter()->hasTable($tableName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function table($tableName, $options = array())
+ {
+ return new Table($tableName, $options, $this->getAdapter());
+ }
+
+ /**
+ * A short-hand method to drop the given database table.
+ *
+ * @param string $tableName Table Name
+ * @return void
+ */
+ public function dropTable($tableName)
+ {
+ $this->table($tableName)->drop();
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractTemplateCreation.php b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractTemplateCreation.php
new file mode 100755
index 0000000..589a5f7
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractTemplateCreation.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Migration
+ */
+namespace Phinx\Migration;
+
+use think\console\Input as InputInterface;
+use think\console\Output as OutputInterface;
+
+abstract class AbstractTemplateCreation implements CreationInterface
+{
+ /**
+ * @var InputInterface
+ */
+ protected $input;
+
+ /**
+ * @var OutputInterface
+ */
+ protected $output;
+
+ /**
+ * Class Constructor.
+ *
+ * @param InputInterface|null $input
+ * @param OutputInterface|null $output
+ */
+ public function __construct(InputInterface $input = null, OutputInterface $output = null)
+ {
+ if (!is_null($input)) {
+ $this->setInput($input);
+ }
+ if (!is_null($output)) {
+ $this->setOutput($output);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setInput(InputInterface $input)
+ {
+ $this->input = $input;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOutput()
+ {
+ return $this->output;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setOutput(OutputInterface $output)
+ {
+ $this->output = $output;
+
+ return $this;
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/CreationInterface.php b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/CreationInterface.php
new file mode 100755
index 0000000..b60c889
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/CreationInterface.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Migration
+ */
+namespace Phinx\Migration;
+
+use think\console\Input as InputInterface;
+use think\console\Output as OutputInterface;
+
+/**
+ * Migration interface
+ *
+ * @author Richard Quadling <RQuadling@GMail.com>
+ */
+interface CreationInterface
+{
+ /**
+ * CreationInterface constructor.
+ *
+ * @param InputInterface|null $input
+ * @param OutputInterface|null $output
+ */
+ public function __construct(InputInterface $input = null, OutputInterface $output = null);
+
+ /**
+ * @param InputInterface $input
+ *
+ * @return CreationInterface
+ */
+ public function setInput(InputInterface $input);
+
+ /**
+ * @param OutputInterface $output
+ *
+ * @return CreationInterface
+ */
+ public function setOutput(OutputInterface $output);
+
+ /**
+ * @return InputInterface
+ */
+ public function getInput();
+
+ /**
+ * @return OutputInterface
+ */
+ public function getOutput();
+
+ /**
+ * Get the migration template.
+ *
+ * This will be the content that Phinx will amend to generate the migration file.
+ *
+ * @return string The content of the template for Phinx to amend.
+ */
+ public function getMigrationTemplate();
+
+ /**
+ * Post Migration Creation.
+ *
+ * Once the migration file has been created, this method will be called, allowing any additional
+ * processing, specific to the template to be performed.
+ *
+ * @param string $migrationFilename The name of the newly created migration.
+ * @param string $className The class name.
+ * @param string $baseClassName The name of the base class.
+ * @return void
+ */
+ public function postMigrationCreation($migrationFilename, $className, $baseClassName);
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/IrreversibleMigrationException.php b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/IrreversibleMigrationException.php
new file mode 100755
index 0000000..9c2a8d2
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/IrreversibleMigrationException.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Migration
+ */
+namespace Phinx\Migration;
+
+/**
+ * Exception class thrown when migrations cannot be reversed using the 'change'
+ * feature.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+class IrreversibleMigrationException extends \Exception
+{
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/Migration.template.php.dist b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/Migration.template.php.dist
new file mode 100755
index 0000000..80ac0c1
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/Migration.template.php.dist
@@ -0,0 +1,32 @@
+<?php
+
+use $useClassName;
+
+class $className extends $baseClassName
+{
+ /**
+ * Change Method.
+ *
+ * Write your reversible migrations using this method.
+ *
+ * More information on writing migrations is available here:
+ * http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
+ *
+ * The following commands can be used in this method and Phinx will
+ * automatically reverse them when rolling back:
+ *
+ * createTable
+ * renameTable
+ * addColumn
+ * renameColumn
+ * addIndex
+ * addForeignKey
+ *
+ * Remember to call "create()" or "update()" and NOT "save()" when working
+ * with the Table class.
+ */
+ public function change()
+ {
+
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/MigrationInterface.php b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/MigrationInterface.php
new file mode 100755
index 0000000..927be3a
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/MigrationInterface.php
@@ -0,0 +1,215 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Migration
+ */
+namespace Phinx\Migration;
+
+use Phinx\Db\Adapter\AdapterInterface;
+use Phinx\Db\Table;
+use think\console\Input as InputInterface;
+use think\console\Output as OutputInterface;
+
+/**
+ * Migration interface
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+interface MigrationInterface
+{
+ /**
+ * @var string
+ */
+ const CHANGE = 'change';
+
+ /**
+ * @var string
+ */
+ const UP = 'up';
+
+ /**
+ * @var string
+ */
+ const DOWN = 'down';
+
+ /**
+ * Migrate Up
+ *
+ * @return void
+ */
+ public function up();
+
+ /**
+ * Migrate Down
+ *
+ * @return void
+ */
+ public function down();
+
+ /**
+ * Sets the database adapter.
+ *
+ * @param AdapterInterface $adapter Database Adapter
+ * @return MigrationInterface
+ */
+ public function setAdapter(AdapterInterface $adapter);
+
+ /**
+ * Gets the database adapter.
+ *
+ * @return AdapterInterface
+ */
+ public function getAdapter();
+
+ /**
+ * Sets the input object to be used in migration object
+ *
+ * @param InputInterface $input
+ * @return MigrationInterface
+ */
+ public function setInput(InputInterface $input);
+
+ /**
+ * Gets the input object to be used in migration object
+ *
+ * @return InputInterface
+ */
+ public function getInput();
+
+ /**
+ * Sets the output object to be used in migration object
+ *
+ * @param OutputInterface $output
+ * @return MigrationInterface
+ */
+ public function setOutput(OutputInterface $output);
+
+ /**
+ * Gets the output object to be used in migration object
+ *
+ * @return OutputInterface
+ */
+ public function getOutput();
+
+ /**
+ * Gets the name.
+ *
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Sets the migration version number.
+ *
+ * @param float $version Version
+ * @return MigrationInterface
+ */
+ public function setVersion($version);
+
+ /**
+ * Gets the migration version number.
+ *
+ * @return float
+ */
+ public function getVersion();
+
+ /**
+ * Executes a SQL statement and returns the number of affected rows.
+ *
+ * @param string $sql SQL
+ * @return int
+ */
+ public function execute($sql);
+
+ /**
+ * Executes a SQL statement and returns the result as an array.
+ *
+ * @param string $sql SQL
+ * @return array
+ */
+ public function query($sql);
+
+ /**
+ * Executes a query and returns only one row as an array.
+ *
+ * @param string $sql SQL
+ * @return array
+ */
+ public function fetchRow($sql);
+
+ /**
+ * Executes a query and returns an array of rows.
+ *
+ * @param string $sql SQL
+ * @return array
+ */
+ public function fetchAll($sql);
+
+ /**
+ * Insert data into a table.
+ *
+ * @param string $tableName
+ * @param array $data
+ * @return void
+ */
+ public function insert($tableName, $data);
+
+ /**
+ * Create a new database.
+ *
+ * @param string $name Database Name
+ * @param array $options Options
+ * @return void
+ */
+ public function createDatabase($name, $options);
+
+ /**
+ * Drop a database.
+ *
+ * @param string $name Database Name
+ * @return void
+ */
+ public function dropDatabase($name);
+
+ /**
+ * Checks to see if a table exists.
+ *
+ * @param string $tableName Table Name
+ * @return boolean
+ */
+ public function hasTable($tableName);
+
+ /**
+ * Returns an instance of the <code>\Table</code> class.
+ *
+ * You can use this class to create and manipulate tables.
+ *
+ * @param string $tableName Table Name
+ * @param array $options Options
+ * @return Table
+ */
+ public function table($tableName, $options);
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Seed/AbstractSeed.php b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/AbstractSeed.php
new file mode 100755
index 0000000..4d631f5
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/AbstractSeed.php
@@ -0,0 +1,215 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Seed
+ */
+namespace Phinx\Seed;
+
+use Phinx\Db\Table;
+use Phinx\Db\Adapter\AdapterInterface;
+use think\console\Input as InputInterface;
+use think\console\Output as OutputInterface;
+
+/**
+ * Abstract Seed Class.
+ *
+ * It is expected that the seeds you write extend from this class.
+ *
+ * This abstract class proxies the various database methods to your specified
+ * adapter.
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+abstract class AbstractSeed implements SeedInterface
+{
+ /**
+ * @var AdapterInterface
+ */
+ protected $adapter;
+
+ /**
+ * @var InputInterface
+ */
+ protected $input;
+
+ /**
+ * @var OutputInterface
+ */
+ protected $output;
+
+ /**
+ * Class Constructor.
+ *
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ */
+ final public function __construct(InputInterface $input = null, OutputInterface $output = null)
+ {
+ if (!is_null($input)){
+ $this->setInput($input);
+ }
+ if (!is_null($output)){
+ $this->setOutput($output);
+ }
+
+ $this->init();
+ }
+
+ /**
+ * Initialize method.
+ *
+ * @return void
+ */
+ protected function init()
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setAdapter(AdapterInterface $adapter)
+ {
+ $this->adapter = $adapter;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAdapter()
+ {
+ return $this->adapter;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setInput(InputInterface $input)
+ {
+ $this->input = $input;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setOutput(OutputInterface $output)
+ {
+ $this->output = $output;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOutput()
+ {
+ return $this->output;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return get_class($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($sql)
+ {
+ return $this->getAdapter()->execute($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function query($sql)
+ {
+ return $this->getAdapter()->query($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetchRow($sql)
+ {
+ return $this->getAdapter()->fetchRow($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fetchAll($sql)
+ {
+ return $this->getAdapter()->fetchAll($sql);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function insert($table, $data)
+ {
+ // convert to table object
+ if (is_string($table)) {
+ $table = new Table($table, array(), $this->getAdapter());
+ }
+ return $table->insert($data)->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasTable($tableName)
+ {
+ return $this->getAdapter()->hasTable($tableName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function table($tableName, $options = array())
+ {
+ return new Table($tableName, $options, $this->getAdapter());
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Seed/Seed.template.php.dist b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/Seed.template.php.dist
new file mode 100755
index 0000000..698ae26
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/Seed.template.php.dist
@@ -0,0 +1,19 @@
+<?php
+
+use $useClassName;
+
+class $className extends $baseClassName
+{
+ /**
+ * Run Method.
+ *
+ * Write your database seeder using this method.
+ *
+ * More information on writing seeders is available here:
+ * http://docs.phinx.org/en/latest/seeding.html
+ */
+ public function run()
+ {
+
+ }
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Seed/SeedInterface.php b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/SeedInterface.php
new file mode 100755
index 0000000..86be8e0
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/SeedInterface.php
@@ -0,0 +1,166 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Seed
+ */
+namespace Phinx\Seed;
+
+use Phinx\Db\Adapter\AdapterInterface;
+use Phinx\Db\Table;
+use think\console\Input as InputInterface;
+use think\console\Output as OutputInterface;
+
+/**
+ * Seed interface
+ *
+ * @author Rob Morgan <robbym@gmail.com>
+ */
+interface SeedInterface
+{
+ /**
+ * @var string
+ */
+ const RUN = 'run';
+
+ /**
+ * Run the seeder.
+ *
+ * @return void
+ */
+ public function run();
+
+ /**
+ * Sets the database adapter.
+ *
+ * @param AdapterInterface $adapter Database Adapter
+ * @return MigrationInterface
+ */
+ public function setAdapter(AdapterInterface $adapter);
+
+ /**
+ * Gets the database adapter.
+ *
+ * @return AdapterInterface
+ */
+ public function getAdapter();
+
+ /**
+ * Sets the input object to be used in migration object
+ *
+ * @param InputInterface $input
+ * @return MigrationInterface
+ */
+ public function setInput(InputInterface $input);
+
+ /**
+ * Gets the input object to be used in migration object
+ *
+ * @return InputInterface
+ */
+ public function getInput();
+
+ /**
+ * Sets the output object to be used in migration object
+ *
+ * @param OutputInterface $output
+ * @return MigrationInterface
+ */
+ public function setOutput(OutputInterface $output);
+
+ /**
+ * Gets the output object to be used in migration object
+ *
+ * @return OutputInterface
+ */
+ public function getOutput();
+
+ /**
+ * Gets the name.
+ *
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Executes a SQL statement and returns the number of affected rows.
+ *
+ * @param string $sql SQL
+ * @return int
+ */
+ public function execute($sql);
+
+ /**
+ * Executes a SQL statement and returns the result as an array.
+ *
+ * @param string $sql SQL
+ * @return array
+ */
+ public function query($sql);
+
+ /**
+ * Executes a query and returns only one row as an array.
+ *
+ * @param string $sql SQL
+ * @return array
+ */
+ public function fetchRow($sql);
+
+ /**
+ * Executes a query and returns an array of rows.
+ *
+ * @param string $sql SQL
+ * @return array
+ */
+ public function fetchAll($sql);
+
+ /**
+ * Insert data into a table.
+ *
+ * @param string $tableName
+ * @param array $data
+ * @return void
+ */
+ public function insert($tableName, $data);
+
+ /**
+ * Checks to see if a table exists.
+ *
+ * @param string $tableName Table Name
+ * @return boolean
+ */
+ public function hasTable($tableName);
+
+ /**
+ * Returns an instance of the <code>\Table</code> class.
+ *
+ * You can use this class to create and manipulate tables.
+ *
+ * @param string $tableName Table Name
+ * @param array $options Options
+ * @return Table
+ */
+ public function table($tableName, $options);
+}
diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Util/Util.php b/vendor/topthink/think-migration/phinx/src/Phinx/Util/Util.php
new file mode 100755
index 0000000..9b8edf2
--- /dev/null
+++ b/vendor/topthink/think-migration/phinx/src/Phinx/Util/Util.php
@@ -0,0 +1,190 @@
+<?php
+/**
+ * Phinx
+ *
+ * (The MIT license)
+ * Copyright (c) 2015 Rob Morgan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated * documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * @package Phinx
+ * @subpackage Phinx\Util
+ */
+namespace Phinx\Util;
+
+class Util
+{
+ /**
+ * @var string
+ */
+ const DATE_FORMAT = 'YmdHis';
+
+ /**
+ * @var string
+ */
+ const MIGRATION_FILE_NAME_PATTERN = '/^\d+_([\w_]+).php$/i';
+
+ /**
+ * @var string
+ */
+ const SEED_FILE_NAME_PATTERN = '/^([A-Z][a-z0-9]+).php$/i';
+
+ /**
+ * Gets the current timestamp string, in UTC.
+ *
+ * @return string
+ */
+ public static function getCurrentTimestamp()
+ {
+ $dt = new \DateTime('now', new \DateTimeZone('UTC'));
+ return $dt->format(static::DATE_FORMAT);
+ }
+
+ /**
+ * Gets an array of all the existing migration class names.
+ *
+ * @return string
+ */
+ public static function getExistingMigrationClassNames($path)
+ {
+ $classNames = array();
+
+ if (!is_dir($path)) {
+ return $classNames;
+ }
+
+ // filter the files to only get the ones that match our naming scheme
+ $phpFiles = glob($path . DIRECTORY_SEPARATOR . '*.php');
+
+ foreach ($phpFiles as $filePath) {
+ if (preg_match('/([0-9]+)_([_a-z0-9]*).php/', basename($filePath))) {
+ $classNames[] = static::mapFileNameToClassName(basename($filePath));
+ }
+ }
+
+ return $classNames;
+ }
+
+ /**
+ * Get the version from the beginning of a file name.
+ *
+ * @param string $fileName File Name
+ * @return string
+ */
+ public static function getVersionFromFileName($fileName)
+ {
+ $matches = array();
+ preg_match('/^[0-9]+/', basename($fileName), $matches);
+ return $matches[0];
+ }
+
+ /**
+ * Turn migration names like 'CreateUserTable' into file names like
+ * '12345678901234_create_user_table.php' or 'LimitResourceNamesTo30Chars' into
+ * '12345678901234_limit_resource_names_to_30_chars.php'.
+ *
+ * @param string $className Class Name
+ * @return string
+ */
+ public static function mapClassNameToFileName($className)
+ {
+ $arr = preg_split('/(?=[A-Z])/', $className);
+ unset($arr[0]); // remove the first element ('')
+ $fileName = static::getCurrentTimestamp() . '_' . strtolower(implode('_', $arr)) . '.php';
+ return $fileName;
+ }
+
+ /**
+ * Turn file names like '12345678901234_create_user_table.php' into class
+ * names like 'CreateUserTable'.
+ *
+ * @param string $fileName File Name
+ * @return string
+ */
+ public static function mapFileNameToClassName($fileName)
+ {
+ $matches = array();
+ if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches)) {
+ $fileName = $matches[1];
+ }
+
+ return str_replace(' ', '', ucwords(str_replace('_', ' ', $fileName)));
+ }
+
+ /**
+ * Check if a migration class name is unique regardless of the
+ * timestamp.
+ *
+ * This method takes a class name and a path to a migrations directory.
+ *
+ * Migration class names must be in CamelCase format.
+ * e.g: CreateUserTable or AddIndexToPostsTable.
+ *
+ * Single words are not allowed on their own.
+ *
+ * @param string $className Class Name
+ * @param string $path Path
+ * @return boolean
+ */
+ public static function isUniqueMigrationClassName($className, $path)
+ {
+ $existingClassNames = static::getExistingMigrationClassNames($path);
+ return !(in_array($className, $existingClassNames));
+ }
+
+ /**
+ * Check if a migration/seed class name is valid.
+ *
+ * Migration & Seed class names must be in CamelCase format.
+ * e.g: CreateUserTable, AddIndexToPostsTable or UserSeeder.
+ *
+ * Single words are not allowed on their own.
+ *
+ * @param string $className Class Name
+ * @return boolean
+ */
+ public static function isValidPhinxClassName($className)
+ {
+ return (bool) preg_match('/^([A-Z][a-z0-9]+)+$/', $className);
+ }
+
+ /**
+ * Check if a migration file name is valid.
+ *
+ * @param string $fileName File Name
+ * @return boolean
+ */
+ public static function isValidMigrationFileName($fileName)
+ {
+ $matches = array();
+ return preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches);
+ }
+
+ /**
+ * Check if a seed file name is valid.
+ *
+ * @param string $fileName File Name
+ * @return boolean
+ */
+ public static function isValidSeedFileName($fileName)
+ {
+ $matches = array();
+ return preg_match(static::SEED_FILE_NAME_PATTERN, $fileName, $matches);
+ }
+}
diff --git a/vendor/topthink/think-migration/src/Command.php b/vendor/topthink/think-migration/src/Command.php
new file mode 100755
index 0000000..b4db21f
--- /dev/null
+++ b/vendor/topthink/think-migration/src/Command.php
@@ -0,0 +1,89 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\migration;
+
+use InvalidArgumentException;
+use Phinx\Db\Adapter\AdapterFactory;
+
+abstract class Command extends \think\console\Command
+{
+
+ public function getAdapter()
+ {
+ if (isset($this->adapter)) {
+ return $this->adapter;
+ }
+
+ $options = $this->getDbConfig();
+
+ $adapter = AdapterFactory::instance()->getAdapter($options['adapter'], $options);
+
+ if ($adapter->hasOption('table_prefix') || $adapter->hasOption('table_suffix')) {
+ $adapter = AdapterFactory::instance()->getWrapper('prefix', $adapter);
+ }
+
+ $this->adapter = $adapter;
+
+ return $adapter;
+ }
+
+ /**
+ * 获取数据库配置
+ * @return array
+ */
+ protected function getDbConfig(): array
+ {
+ $default = $this->app->config->get('database.default');
+
+ $config = $this->app->config->get("database.connections.{$default}");
+
+ if (0 == $config['deploy']) {
+ $dbConfig = [
+ 'adapter' => $config['type'],
+ 'host' => $config['hostname'],
+ 'name' => $config['database'],
+ 'user' => $config['username'],
+ 'pass' => $config['password'],
+ 'port' => $config['hostport'],
+ 'charset' => $config['charset'],
+ 'table_prefix' => $config['prefix'],
+ ];
+ } else {
+ $dbConfig = [
+ 'adapter' => explode(',', $config['type'])[0],
+ 'host' => explode(',', $config['hostname'])[0],
+ 'name' => explode(',', $config['database'])[0],
+ 'user' => explode(',', $config['username'])[0],
+ 'pass' => explode(',', $config['password'])[0],
+ 'port' => explode(',', $config['hostport'])[0],
+ 'charset' => explode(',', $config['charset'])[0],
+ 'table_prefix' => explode(',', $config['prefix'])[0],
+ ];
+ }
+
+ $table = $this->app->config->get('database.migration_table', 'migrations');
+
+ $dbConfig['default_migration_table'] = $dbConfig['table_prefix'] . $table;
+
+ return $dbConfig;
+ }
+
+ protected function verifyMigrationDirectory(string $path)
+ {
+ if (!is_dir($path)) {
+ throw new InvalidArgumentException(sprintf('Migration directory "%s" does not exist', $path));
+ }
+
+ if (!is_writable($path)) {
+ throw new InvalidArgumentException(sprintf('Migration directory "%s" is not writable', $path));
+ }
+ }
+}
diff --git a/vendor/topthink/think-migration/src/Creator.php b/vendor/topthink/think-migration/src/Creator.php
new file mode 100755
index 0000000..f4bc804
--- /dev/null
+++ b/vendor/topthink/think-migration/src/Creator.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace think\migration;
+
+use InvalidArgumentException;
+use Phinx\Util\Util;
+use RuntimeException;
+use think\App;
+
+class Creator
+{
+
+ protected $app;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ }
+
+ public function create(string $className)
+ {
+ $path = $this->ensureDirectory();
+
+ if (!Util::isValidPhinxClassName($className)) {
+ throw new InvalidArgumentException(sprintf('The migration class name "%s" is invalid. Please use CamelCase format.', $className));
+ }
+
+ if (!Util::isUniqueMigrationClassName($className, $path)) {
+ throw new InvalidArgumentException(sprintf('The migration class name "%s" already exists', $className));
+ }
+
+ // Compute the file path
+ $fileName = Util::mapClassNameToFileName($className);
+ $filePath = $path . DIRECTORY_SEPARATOR . $fileName;
+
+ if (is_file($filePath)) {
+ throw new InvalidArgumentException(sprintf('The file "%s" already exists', $filePath));
+ }
+
+ // Verify that the template creation class (or the aliased class) exists and that it implements the required interface.
+ $aliasedClassName = null;
+
+ // Load the alternative template if it is defined.
+ $contents = file_get_contents($this->getTemplate());
+
+ // inject the class names appropriate to this migration
+ $contents = strtr($contents, [
+ 'MigratorClass' => $className,
+ ]);
+
+ if (false === file_put_contents($filePath, $contents)) {
+ throw new RuntimeException(sprintf('The file "%s" could not be written to', $path));
+ }
+
+ return $filePath;
+ }
+
+ protected function ensureDirectory()
+ {
+ $path = $this->app->getRootPath() . 'database' . DIRECTORY_SEPARATOR . 'migrations';
+
+ if (!is_dir($path) && !mkdir($path, 0755, true)) {
+ throw new InvalidArgumentException(sprintf('directory "%s" does not exist', $path));
+ }
+
+ if (!is_writable($path)) {
+ throw new InvalidArgumentException(sprintf('directory "%s" is not writable', $path));
+ }
+
+ return $path;
+ }
+
+ protected function getTemplate()
+ {
+ return __DIR__ . '/command/stubs/migrate.stub';
+ }
+}
diff --git a/vendor/topthink/think-migration/src/Factory.php b/vendor/topthink/think-migration/src/Factory.php
new file mode 100755
index 0000000..976a999
--- /dev/null
+++ b/vendor/topthink/think-migration/src/Factory.php
@@ -0,0 +1,313 @@
+<?php
+
+namespace think\migration;
+
+use ArrayAccess;
+use Faker\Generator as Faker;
+
+class Factory implements ArrayAccess
+{
+
+ /**
+ * The model definitions in the container.
+ *
+ * @var array
+ */
+ protected $definitions = [];
+
+ /**
+ * The registered model states.
+ *
+ * @var array
+ */
+ protected $states = [];
+
+ /**
+ * The registered after making callbacks.
+ *
+ * @var array
+ */
+ protected $afterMaking = [];
+
+ /**
+ * The registered after creating callbacks.
+ *
+ * @var array
+ */
+ protected $afterCreating = [];
+
+ /**
+ * The Faker instance for the builder.
+ *
+ * @var Faker
+ */
+ protected $faker;
+
+ /**
+ * Create a new factory instance.
+ *
+ * @param Faker $faker
+ * @return void
+ */
+ public function __construct(Faker $faker)
+ {
+ $this->faker = $faker;
+ }
+
+ /**
+ * Define a class with a given short-name.
+ *
+ * @param string $class
+ * @param string $name
+ * @param callable $attributes
+ * @return $this
+ */
+ public function defineAs(string $class, string $name, callable $attributes)
+ {
+ return $this->define($class, $attributes, $name);
+ }
+
+ /**
+ * Define a class with a given set of attributes.
+ *
+ * @param string $class
+ * @param callable $attributes
+ * @param string $name
+ * @return $this
+ */
+ public function define(string $class, callable $attributes, string $name = 'default')
+ {
+ $this->definitions[$class][$name] = $attributes;
+
+ return $this;
+ }
+
+ /**
+ * Define a state with a given set of attributes.
+ *
+ * @param string $class
+ * @param string $state
+ * @param callable|array $attributes
+ * @return $this
+ */
+ public function state(string $class, string $state, $attributes)
+ {
+ $this->states[$class][$state] = $attributes;
+
+ return $this;
+ }
+
+ /**
+ * Define a callback to run after making a model.
+ *
+ * @param string $class
+ * @param callable $callback
+ * @param string $name
+ * @return $this
+ */
+ public function afterMaking(string $class, callable $callback, string $name = 'default')
+ {
+ $this->afterMaking[$class][$name][] = $callback;
+
+ return $this;
+ }
+
+ /**
+ * Define a callback to run after making a model with given state.
+ *
+ * @param string $class
+ * @param string $state
+ * @param callable $callback
+ * @return $this
+ */
+ public function afterMakingState(string $class, string $state, callable $callback)
+ {
+ return $this->afterMaking($class, $callback, $state);
+ }
+
+ /**
+ * Define a callback to run after creating a model.
+ *
+ * @param string $class
+ * @param callable $callback
+ * @param string $name
+ * @return $this
+ */
+ public function afterCreating(string $class, callable $callback, string $name = 'default')
+ {
+ $this->afterCreating[$class][$name][] = $callback;
+
+ return $this;
+ }
+
+ /**
+ * Define a callback to run after creating a model with given state.
+ *
+ * @param string $class
+ * @param string $state
+ * @param callable $callback
+ * @return $this
+ */
+ public function afterCreatingState(string $class, string $state, callable $callback)
+ {
+ return $this->afterCreating($class, $callback, $state);
+ }
+
+ /**
+ * Create an instance of the given model and persist it to the database.
+ *
+ * @param string $class
+ * @param array $attributes
+ * @return mixed
+ */
+ public function create(string $class, array $attributes = [])
+ {
+ return $this->of($class)->create($attributes);
+ }
+
+ /**
+ * Create an instance of the given model and type and persist it to the database.
+ *
+ * @param string $class
+ * @param string $name
+ * @param array $attributes
+ * @return mixed
+ */
+ public function createAs(string $class, string $name, array $attributes = [])
+ {
+ return $this->of($class, $name)->create($attributes);
+ }
+
+ /**
+ * Create an instance of the given model.
+ *
+ * @param string $class
+ * @param array $attributes
+ * @return mixed
+ */
+ public function make(string $class, array $attributes = [])
+ {
+ return $this->of($class)->make($attributes);
+ }
+
+ /**
+ * Create an instance of the given model and type.
+ *
+ * @param string $class
+ * @param string $name
+ * @param array $attributes
+ * @return mixed
+ */
+ public function makeAs(string $class, string $name, array $attributes = [])
+ {
+ return $this->of($class, $name)->make($attributes);
+ }
+
+ /**
+ * Get the raw attribute array for a given named model.
+ *
+ * @param string $class
+ * @param string $name
+ * @param array $attributes
+ * @return array
+ */
+ public function rawOf(string $class, string $name, array $attributes = [])
+ {
+ return $this->raw($class, $attributes, $name);
+ }
+
+ /**
+ * Get the raw attribute array for a given model.
+ *
+ * @param string $class
+ * @param array $attributes
+ * @param string $name
+ * @return array
+ */
+ public function raw(string $class, array $attributes = [], string $name = 'default')
+ {
+ return array_merge(
+ call_user_func($this->definitions[$class][$name], $this->faker), $attributes
+ );
+ }
+
+ /**
+ * Create a builder for the given model.
+ *
+ * @param string $class
+ * @param string $name
+ * @return FactoryBuilder
+ */
+ public function of(string $class, string $name = 'default')
+ {
+ return new FactoryBuilder(
+ $class, $name, $this->definitions, $this->states,
+ $this->afterMaking, $this->afterCreating, $this->faker
+ );
+ }
+
+ /**
+ * Load factories from path.
+ *
+ * @param string $path
+ * @return $this
+ */
+ public function load(string $path)
+ {
+ $factory = $this;
+
+ if (is_dir($path)) {
+ foreach (glob($path . '*.php') as $file) {
+ require $file;
+ }
+ }
+
+ return $factory;
+ }
+
+ /**
+ * Determine if the given offset exists.
+ *
+ * @param string $offset
+ * @return bool
+ */
+ public function offsetExists($offset)
+ {
+ return isset($this->definitions[$offset]);
+ }
+
+ /**
+ * Get the value of the given offset.
+ *
+ * @param string $offset
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ return $this->make($offset);
+ }
+
+ /**
+ * Set the given offset to the given value.
+ *
+ * @param string $offset
+ * @param callable $value
+ * @return void
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->define($offset, $value);
+ }
+
+ /**
+ * Unset the value at the given offset.
+ *
+ * @param string $offset
+ * @return void
+ */
+ public function offsetUnset($offset)
+ {
+ unset($this->definitions[$offset]);
+ }
+
+}
diff --git a/vendor/topthink/think-migration/src/FactoryBuilder.php b/vendor/topthink/think-migration/src/FactoryBuilder.php
new file mode 100755
index 0000000..a72b07e
--- /dev/null
+++ b/vendor/topthink/think-migration/src/FactoryBuilder.php
@@ -0,0 +1,437 @@
+<?php
+
+namespace think\migration;
+
+use Faker\Generator as Faker;
+use InvalidArgumentException;
+use think\Collection;
+use think\Model;
+
+class FactoryBuilder
+{
+
+ /**
+ * The model definitions in the container.
+ *
+ * @var array
+ */
+ protected $definitions;
+
+ /**
+ * The model being built.
+ *
+ * @var string
+ */
+ protected $class;
+
+ /**
+ * The name of the model being built.
+ *
+ * @var string
+ */
+ protected $name = 'default';
+
+ /**
+ * The database connection on which the model instance should be persisted.
+ *
+ * @var string
+ */
+ protected $connection;
+
+ /**
+ * The model states.
+ *
+ * @var array
+ */
+ protected $states;
+
+ /**
+ * The model after making callbacks.
+ *
+ * @var array
+ */
+ protected $afterMaking = [];
+
+ /**
+ * The model after creating callbacks.
+ *
+ * @var array
+ */
+ protected $afterCreating = [];
+
+ /**
+ * The states to apply.
+ *
+ * @var array
+ */
+ protected $activeStates = [];
+
+ /**
+ * The Faker instance for the builder.
+ *
+ * @var Faker
+ */
+ protected $faker;
+
+ /**
+ * The number of models to build.
+ *
+ * @var int|null
+ */
+ protected $amount = null;
+
+ /**
+ * Create an new builder instance.
+ *
+ * @param string $class
+ * @param string $name
+ * @param array $definitions
+ * @param array $states
+ * @param array $afterMaking
+ * @param array $afterCreating
+ * @param Faker $faker
+ * @return void
+ */
+ public function __construct($class, $name, array $definitions, array $states,
+ array $afterMaking, array $afterCreating, Faker $faker)
+ {
+ $this->name = $name;
+ $this->class = $class;
+ $this->faker = $faker;
+ $this->states = $states;
+ $this->definitions = $definitions;
+ $this->afterMaking = $afterMaking;
+ $this->afterCreating = $afterCreating;
+ }
+
+ /**
+ * Set the amount of models you wish to create / make.
+ *
+ * @param int $amount
+ * @return $this
+ */
+ public function times($amount)
+ {
+ $this->amount = $amount;
+
+ return $this;
+ }
+
+ /**
+ * Set the state to be applied to the model.
+ *
+ * @param string $state
+ * @return $this
+ */
+ public function state($state)
+ {
+ return $this->states([$state]);
+ }
+
+ /**
+ * Set the states to be applied to the model.
+ *
+ * @param array|mixed $states
+ * @return $this
+ */
+ public function states($states)
+ {
+ $this->activeStates = is_array($states) ? $states : func_get_args();
+
+ return $this;
+ }
+
+ /**
+ * Set the database connection on which the model instance should be persisted.
+ *
+ * @param string $name
+ * @return $this
+ */
+ public function connection($name)
+ {
+ $this->connection = $name;
+
+ return $this;
+ }
+
+ /**
+ * Create a model and persist it in the database if requested.
+ *
+ * @param array $attributes
+ * @return \Closure
+ */
+ public function lazy(array $attributes = [])
+ {
+ return function () use ($attributes) {
+ return $this->create($attributes);
+ };
+ }
+
+ /**
+ * Create a collection of models and persist them to the database.
+ *
+ * @param array $attributes
+ * @return mixed
+ */
+ public function create(array $attributes = [])
+ {
+ $results = $this->make($attributes);
+
+ if ($results instanceof Model) {
+ $this->store(new Collection([$results]));
+
+ $this->callAfterCreating(new Collection([$results]));
+ } else {
+ $this->store($results);
+
+ $this->callAfterCreating($results);
+ }
+
+ return $results;
+ }
+
+ /**
+ * Set the connection name on the results and store them.
+ *
+ * @param Collection $results
+ * @return void
+ */
+ protected function store($results)
+ {
+ $results->each(function (Model $model) {
+ $model->save();
+ });
+ }
+
+ /**
+ * Create a collection of models.
+ *
+ * @param array $attributes
+ * @return mixed
+ */
+ public function make(array $attributes = [])
+ {
+ if ($this->amount === null) {
+ return tap($this->makeInstance($attributes), function ($instance) {
+ $this->callAfterMaking(new Collection([$instance]));
+ });
+ }
+
+ if ($this->amount < 1) {
+ return (new $this->class)->toCollection();
+ }
+
+ $instances = (new $this->class)->toCollection(array_map(function () use ($attributes) {
+ return $this->makeInstance($attributes);
+ }, range(1, $this->amount)));
+
+ $this->callAfterMaking($instances);
+
+ return $instances;
+ }
+
+ /**
+ * Create an array of raw attribute arrays.
+ *
+ * @param array $attributes
+ * @return mixed
+ */
+ public function raw(array $attributes = [])
+ {
+ if ($this->amount === null) {
+ return $this->getRawAttributes($attributes);
+ }
+
+ if ($this->amount < 1) {
+ return [];
+ }
+
+ return array_map(function () use ($attributes) {
+ return $this->getRawAttributes($attributes);
+ }, range(1, $this->amount));
+ }
+
+ /**
+ * Get a raw attributes array for the model.
+ *
+ * @param array $attributes
+ * @return mixed
+ *
+ * @throws \InvalidArgumentException
+ */
+ protected function getRawAttributes(array $attributes = [])
+ {
+ if (!isset($this->definitions[$this->class][$this->name])) {
+ throw new InvalidArgumentException("Unable to locate factory with name [{$this->name}] [{$this->class}].");
+ }
+
+ $definition = call_user_func(
+ $this->definitions[$this->class][$this->name],
+ $this->faker, $attributes
+ );
+
+ return $this->expandAttributes(
+ array_merge($this->applyStates($definition, $attributes), $attributes)
+ );
+ }
+
+ /**
+ * Make an instance of the model with the given attributes.
+ *
+ * @param array $attributes
+ * @return Model
+ */
+ protected function makeInstance(array $attributes = [])
+ {
+ /** @var Model $model */
+ $model = new $this->class;
+
+ $model->setAttrs($this->getRawAttributes($attributes));
+
+ return $model;
+ }
+
+ /**
+ * Apply the active states to the model definition array.
+ *
+ * @param array $definition
+ * @param array $attributes
+ * @return array
+ */
+ protected function applyStates(array $definition, array $attributes = [])
+ {
+ foreach ($this->activeStates as $state) {
+ if (!isset($this->states[$this->class][$state])) {
+ if ($this->stateHasAfterCallback($state)) {
+ continue;
+ }
+
+ throw new InvalidArgumentException("Unable to locate [{$state}] state for [{$this->class}].");
+ }
+
+ $definition = array_merge(
+ $definition,
+ $this->stateAttributes($state, $attributes)
+ );
+ }
+
+ return $definition;
+ }
+
+ /**
+ * Get the state attributes.
+ *
+ * @param string $state
+ * @param array $attributes
+ * @return array
+ */
+ protected function stateAttributes($state, array $attributes)
+ {
+ $stateAttributes = $this->states[$this->class][$state];
+
+ if (!is_callable($stateAttributes)) {
+ return $stateAttributes;
+ }
+
+ return call_user_func(
+ $stateAttributes,
+ $this->faker, $attributes
+ );
+ }
+
+ /**
+ * Expand all attributes to their underlying values.
+ *
+ * @param array $attributes
+ * @return array
+ */
+ protected function expandAttributes(array $attributes)
+ {
+ foreach ($attributes as &$attribute) {
+ if (is_callable($attribute) && !is_string($attribute) && !is_array($attribute)) {
+ $attribute = $attribute($attributes);
+ }
+
+ if ($attribute instanceof static) {
+ $attribute = $attribute->create()->getKey();
+ }
+
+ if ($attribute instanceof Model) {
+ $attribute = $attribute->getKey();
+ }
+ }
+
+ return $attributes;
+ }
+
+ /**
+ * Run after making callbacks on a collection of models.
+ *
+ * @param Collection $models
+ * @return void
+ */
+ public function callAfterMaking($models)
+ {
+ $this->callAfter($this->afterMaking, $models);
+ }
+
+ /**
+ * Run after creating callbacks on a collection of models.
+ *
+ * @param Collection $models
+ * @return void
+ */
+ public function callAfterCreating($models)
+ {
+ $this->callAfter($this->afterCreating, $models);
+ }
+
+ /**
+ * Call after callbacks for each model and state.
+ *
+ * @param array $afterCallbacks
+ * @param Collection $models
+ * @return void
+ */
+ protected function callAfter(array $afterCallbacks, $models)
+ {
+ $states = array_merge([$this->name], $this->activeStates);
+
+ $models->each(function ($model) use ($states, $afterCallbacks) {
+ foreach ($states as $state) {
+ $this->callAfterCallbacks($afterCallbacks, $model, $state);
+ }
+ });
+ }
+
+ /**
+ * Call after callbacks for each model and state.
+ *
+ * @param array $afterCallbacks
+ * @param Model $model
+ * @param string $state
+ * @return void
+ */
+ protected function callAfterCallbacks(array $afterCallbacks, $model, $state)
+ {
+ if (!isset($afterCallbacks[$this->class][$state])) {
+ return;
+ }
+
+ foreach ($afterCallbacks[$this->class][$state] as $callback) {
+ $callback($model, $this->faker);
+ }
+ }
+
+ /**
+ * Determine if the given state has an "after" callback.
+ *
+ * @param string $state
+ * @return bool
+ */
+ protected function stateHasAfterCallback($state)
+ {
+ return isset($this->afterMaking[$this->class][$state]) ||
+ isset($this->afterCreating[$this->class][$state]);
+ }
+}
diff --git a/vendor/topthink/think-migration/src/Migrator.php b/vendor/topthink/think-migration/src/Migrator.php
new file mode 100755
index 0000000..1965953
--- /dev/null
+++ b/vendor/topthink/think-migration/src/Migrator.php
@@ -0,0 +1,27 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\migration;
+
+use Phinx\Migration\AbstractMigration;
+use think\migration\db\Table;
+
+class Migrator extends AbstractMigration
+{
+ /**
+ * @param string $tableName
+ * @param array $options
+ * @return Table
+ */
+ public function table($tableName, $options = [])
+ {
+ return new Table($tableName, $options, $this->getAdapter());
+ }
+}
diff --git a/vendor/topthink/think-migration/src/Seeder.php b/vendor/topthink/think-migration/src/Seeder.php
new file mode 100755
index 0000000..4b5f7f2
--- /dev/null
+++ b/vendor/topthink/think-migration/src/Seeder.php
@@ -0,0 +1,24 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\migration;
+
+use Phinx\Seed\AbstractSeed;
+
+class Seeder extends AbstractSeed
+{
+ /**
+ * @return Factory
+ */
+ public function factory()
+ {
+ return app(Factory::class);
+ }
+}
diff --git a/vendor/topthink/think-migration/src/Service.php b/vendor/topthink/think-migration/src/Service.php
new file mode 100755
index 0000000..614e9e0
--- /dev/null
+++ b/vendor/topthink/think-migration/src/Service.php
@@ -0,0 +1,51 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\migration;
+
+use Faker\Factory as FakerFactory;
+use Faker\Generator as FakerGenerator;
+use think\migration\command\factory\Create as FactoryCreate;
+use think\migration\command\migrate\Breakpoint as MigrateBreakpoint;
+use think\migration\command\migrate\Create as MigrateCreate;
+use think\migration\command\migrate\Rollback as MigrateRollback;
+use think\migration\command\migrate\Run as MigrateRun;
+use think\migration\command\migrate\Status as MigrateStatus;
+use think\migration\command\seed\Create as SeedCreate;
+use think\migration\command\seed\Run as SeedRun;
+
+class Service extends \think\Service
+{
+
+ public function boot()
+ {
+ $this->app->bind(FakerGenerator::class, function () {
+ return FakerFactory::create($this->app->config->get('app.faker_locale', 'zh_CN'));
+ });
+
+ $this->app->bind(Factory::class, function () {
+ return (new Factory($this->app->make(FakerGenerator::class)))->load($this->app->getRootPath() . 'database/factories/');
+ });
+
+ $this->app->bind('migration.creator', Creator::class);
+
+ $this->commands([
+ MigrateCreate::class,
+ MigrateRun::class,
+ MigrateRollback::class,
+ MigrateBreakpoint::class,
+ MigrateStatus::class,
+ SeedCreate::class,
+ SeedRun::class,
+ FactoryCreate::class,
+ ]);
+ }
+}
diff --git a/vendor/topthink/think-migration/src/command/Migrate.php b/vendor/topthink/think-migration/src/command/Migrate.php
new file mode 100755
index 0000000..1d97973
--- /dev/null
+++ b/vendor/topthink/think-migration/src/command/Migrate.php
@@ -0,0 +1,146 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\migration\command;
+
+use Phinx\Db\Adapter\AdapterFactory;
+use Phinx\Db\Adapter\ProxyAdapter;
+use Phinx\Migration\AbstractMigration;
+use Phinx\Migration\MigrationInterface;
+use Phinx\Util\Util;
+use think\migration\Command;
+use think\migration\Migrator;
+
+abstract class Migrate extends Command
+{
+ /**
+ * @var array
+ */
+ protected $migrations;
+
+ protected function getPath()
+ {
+ return $this->app->getRootPath() . 'database' . DIRECTORY_SEPARATOR . 'migrations';
+ }
+
+ protected function executeMigration(MigrationInterface $migration, $direction = MigrationInterface::UP)
+ {
+ $this->output->writeln('');
+ $this->output->writeln(' ==' . ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' . ' <comment>' . (MigrationInterface::UP === $direction ? 'migrating' : 'reverting') . '</comment>');
+
+ // Execute the migration and log the time elapsed.
+ $start = microtime(true);
+
+ $startTime = time();
+ $direction = (MigrationInterface::UP === $direction) ? MigrationInterface::UP : MigrationInterface::DOWN;
+ $migration->setAdapter($this->getAdapter());
+
+ // begin the transaction if the adapter supports it
+ if ($this->getAdapter()->hasTransactions()) {
+ $this->getAdapter()->beginTransaction();
+ }
+
+ // Run the migration
+ if (method_exists($migration, MigrationInterface::CHANGE)) {
+ if (MigrationInterface::DOWN === $direction) {
+ // Create an instance of the ProxyAdapter so we can record all
+ // of the migration commands for reverse playback
+ /** @var ProxyAdapter $proxyAdapter */
+ $proxyAdapter = AdapterFactory::instance()->getWrapper('proxy', $this->getAdapter());
+ $migration->setAdapter($proxyAdapter);
+ /** @noinspection PhpUndefinedMethodInspection */
+ $migration->change();
+ $proxyAdapter->executeInvertedCommands();
+ $migration->setAdapter($this->getAdapter());
+ } else {
+ /** @noinspection PhpUndefinedMethodInspection */
+ $migration->change();
+ }
+ } else {
+ $migration->{$direction}();
+ }
+
+ // commit the transaction if the adapter supports it
+ if ($this->getAdapter()->hasTransactions()) {
+ $this->getAdapter()->commitTransaction();
+ }
+
+ // Record it in the database
+ $this->getAdapter()
+ ->migrated($migration, $direction, date('Y-m-d H:i:s', $startTime), date('Y-m-d H:i:s', time()));
+
+ $end = microtime(true);
+
+ $this->output->writeln(' ==' . ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' . ' <comment>' . (MigrationInterface::UP === $direction ? 'migrated' : 'reverted') . ' ' . sprintf('%.4fs', $end - $start) . '</comment>');
+ }
+
+ protected function getVersionLog()
+ {
+ return $this->getAdapter()->getVersionLog();
+ }
+
+ protected function getVersions()
+ {
+ return $this->getAdapter()->getVersions();
+ }
+
+ protected function getMigrations()
+ {
+ if (null === $this->migrations) {
+ $phpFiles = glob($this->getPath() . DIRECTORY_SEPARATOR . '*.php', defined('GLOB_BRACE') ? GLOB_BRACE : 0);
+
+ // filter the files to only get the ones that match our naming scheme
+ $fileNames = [];
+ /** @var Migrator[] $versions */
+ $versions = [];
+
+ foreach ($phpFiles as $filePath) {
+ if (Util::isValidMigrationFileName(basename($filePath))) {
+ $version = Util::getVersionFromFileName(basename($filePath));
+
+ if (isset($versions[$version])) {
+ throw new \InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion()));
+ }
+
+ // convert the filename to a class name
+ $class = Util::mapFileNameToClassName(basename($filePath));
+
+ if (isset($fileNames[$class])) {
+ throw new \InvalidArgumentException(sprintf('Migration "%s" has the same name as "%s"', basename($filePath), $fileNames[$class]));
+ }
+
+ $fileNames[$class] = basename($filePath);
+
+ // load the migration file
+ /** @noinspection PhpIncludeInspection */
+ require_once $filePath;
+ if (!class_exists($class)) {
+ throw new \InvalidArgumentException(sprintf('Could not find class "%s" in file "%s"', $class, $filePath));
+ }
+
+ // instantiate it
+ $migration = new $class($version, $this->input, $this->output);
+
+ if (!($migration instanceof AbstractMigration)) {
+ throw new \InvalidArgumentException(sprintf('The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration', $class, $filePath));
+ }
+
+ $versions[$version] = $migration;
+ }
+ }
+
+ ksort($versions);
+ $this->migrations = $versions;
+ }
+
+ return $this->migrations;
+ }
+}
diff --git a/vendor/topthink/think-migration/src/command/Seed.php b/vendor/topthink/think-migration/src/command/Seed.php
new file mode 100755
index 0000000..e7a2c06
--- /dev/null
+++ b/vendor/topthink/think-migration/src/command/Seed.php
@@ -0,0 +1,73 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\migration\command;
+
+use InvalidArgumentException;
+use Phinx\Seed\AbstractSeed;
+use Phinx\Util\Util;
+use think\migration\Command;
+use think\migration\Seeder;
+
+abstract class Seed extends Command
+{
+
+ /**
+ * @var array
+ */
+ protected $seeds;
+
+ protected function getPath()
+ {
+ return $this->app->getRootPath() . 'database' . DIRECTORY_SEPARATOR . 'seeds';
+ }
+
+ public function getSeeds()
+ {
+ if (null === $this->seeds) {
+ $phpFiles = glob($this->getPath() . DIRECTORY_SEPARATOR . '*.php', defined('GLOB_BRACE') ? GLOB_BRACE : 0);
+
+ // filter the files to only get the ones that match our naming scheme
+ $fileNames = [];
+ /** @var Seeder[] $seeds */
+ $seeds = [];
+
+ foreach ($phpFiles as $filePath) {
+ if (Util::isValidSeedFileName(basename($filePath))) {
+ // convert the filename to a class name
+ $class = pathinfo($filePath, PATHINFO_FILENAME);
+ $fileNames[$class] = basename($filePath);
+
+ // load the seed file
+ /** @noinspection PhpIncludeInspection */
+ require_once $filePath;
+ if (!class_exists($class)) {
+ throw new InvalidArgumentException(sprintf('Could not find class "%s" in file "%s"', $class, $filePath));
+ }
+
+ // instantiate it
+ $seed = new $class($this->input, $this->output);
+
+ if (!($seed instanceof AbstractSeed)) {
+ throw new InvalidArgumentException(sprintf('The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed', $class, $filePath));
+ }
+
+ $seeds[$class] = $seed;
+ }
+ }
+
+ ksort($seeds);
+ $this->seeds = $seeds;
+ }
+
+ return $this->seeds;
+ }
+}
diff --git a/vendor/topthink/think-migration/src/command/factory/Create.php b/vendor/topthink/think-migration/src/command/factory/Create.php
new file mode 100755
index 0000000..a7b4684
--- /dev/null
+++ b/vendor/topthink/think-migration/src/command/factory/Create.php
@@ -0,0 +1,82 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\migration\command\factory;
+
+use InvalidArgumentException;
+use Phinx\Util\Util;
+use RuntimeException;
+use think\console\Command;
+use think\console\input\Argument as InputArgument;
+
+class Create extends Command
+{
+ protected function configure()
+ {
+ $this->setName('factory:create')
+ ->setDescription('Create a new model factory')
+ ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the model?');
+ }
+
+ public function handle()
+ {
+ $path = $this->getPath();
+
+ if (!file_exists($path)) {
+ mkdir($path, 0755, true);
+ }
+
+ if (!is_dir($path)) {
+ throw new InvalidArgumentException(sprintf('Factory directory "%s" does not exist', $path));
+ }
+
+ if (!is_writable($path)) {
+ throw new InvalidArgumentException(sprintf('Factory directory "%s" is not writable', $path));
+ }
+
+ $path = realpath($path);
+ $className = $this->input->getArgument('name');
+
+ if (!Util::isValidPhinxClassName($className)) {
+ throw new InvalidArgumentException(sprintf('The migration class name "%s" is invalid. Please use CamelCase format.', $className));
+ }
+
+ $filePath = $path . DIRECTORY_SEPARATOR . $className . '.php';
+
+ if (is_file($filePath)) {
+ throw new InvalidArgumentException(sprintf('The file "%s" already exists', $filePath));
+ }
+
+ // Load the alternative template if it is defined.
+ $contents = file_get_contents($this->getTemplate());
+
+ // inject the class names appropriate to this migration
+ $contents = strtr($contents, [
+ '"ModelClass"' => "\\app\\model\\" . $className . '::class',
+ ]);
+
+ if (false === file_put_contents($filePath, $contents)) {
+ throw new RuntimeException(sprintf('The file "%s" could not be written to', $path));
+ }
+
+ $this->output->writeln('<info>created</info> .' . str_replace(getcwd(), '', $filePath));
+ }
+
+ protected function getTemplate()
+ {
+ return __DIR__ . '/../stubs/factory.stub';
+ }
+
+ protected function getPath()
+ {
+ return $this->app->getRootPath() . 'database' . DIRECTORY_SEPARATOR . 'factories';
+ }
+}
diff --git a/vendor/topthink/think-migration/src/command/migrate/Breakpoint.php b/vendor/topthink/think-migration/src/command/migrate/Breakpoint.php
new file mode 100755
index 0000000..cfb2d32
--- /dev/null
+++ b/vendor/topthink/think-migration/src/command/migrate/Breakpoint.php
@@ -0,0 +1,92 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\migration\command\migrate;
+
+use think\console\Input;
+use think\console\input\Option as InputOption;
+use think\console\Output;
+use think\migration\command\Migrate;
+
+class Breakpoint extends Migrate
+{
+ protected function configure()
+ {
+ $this->setName('migrate:breakpoint')
+ ->setDescription('Manage breakpoints')
+ ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to set or clear a breakpoint against')
+ ->addOption('--remove-all', '-r', InputOption::VALUE_NONE, 'Remove all breakpoints')
+ ->setHelp(<<<EOT
+ The <info>breakpoint</info> command allows you to set or clear a breakpoint against a specific target to inhibit rollbacks beyond a certain target.
+If no target is supplied then the most recent migration will be used.
+You cannot specify un-migrated targets
+
+<info>php think migrate:breakpoint</info>
+<info>php think migrate:breakpoint -t 20110103081132</info>
+<info>php think migrate:breakpoint -r</info>
+EOT
+ );
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $version = $input->getOption('target');
+ $removeAll = $input->getOption('remove-all');
+
+ if ($version && $removeAll) {
+ throw new \InvalidArgumentException('Cannot toggle a breakpoint and remove all breakpoints at the same time.');
+ }
+
+ // Remove all breakpoints
+ if ($removeAll) {
+ $this->removeBreakpoints();
+ } else {
+ // Toggle the breakpoint.
+ $this->toggleBreakpoint($version);
+ }
+ }
+
+ protected function toggleBreakpoint($version)
+ {
+ $migrations = $this->getMigrations();
+ $versions = $this->getVersionLog();
+
+ if (empty($versions) || empty($migrations)) {
+ return;
+ }
+
+ if (null === $version) {
+ $lastVersion = end($versions);
+ $version = $lastVersion['version'];
+ }
+
+ if (0 != $version && !isset($migrations[$version])) {
+ $this->output->writeln(sprintf('<comment>warning</comment> %s is not a valid version', $version));
+ return;
+ }
+
+ $this->getAdapter()->toggleBreakpoint($migrations[$version]);
+
+ $versions = $this->getVersionLog();
+
+ $this->output->writeln(' Breakpoint ' . ($versions[$version]['breakpoint'] ? 'set' : 'cleared') . ' for <info>' . $version . '</info>' . ' <comment>' . $migrations[$version]->getName() . '</comment>');
+ }
+
+ /**
+ * Remove all breakpoints
+ *
+ * @return void
+ */
+ protected function removeBreakpoints()
+ {
+ $this->output->writeln(sprintf(' %d breakpoints cleared.', $this->getAdapter()->resetAllBreakpoints()));
+ }
+}
diff --git a/vendor/topthink/think-migration/src/command/migrate/Create.php b/vendor/topthink/think-migration/src/command/migrate/Create.php
new file mode 100755
index 0000000..8f96de7
--- /dev/null
+++ b/vendor/topthink/think-migration/src/command/migrate/Create.php
@@ -0,0 +1,55 @@
+<?php
+// +----------------------------------------------------------------------
+// | TopThink [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://www.topthink.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\migration\command\migrate;
+
+use InvalidArgumentException;
+use RuntimeException;
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument as InputArgument;
+use think\console\Output;
+use think\migration\Creator;
+
+class Create extends Command
+{
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this->setName('migrate:create')
+ ->setDescription('Create a new migration')
+ ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the migration?')
+ ->setHelp(sprintf('%sCreates a new database migration%s', PHP_EOL, PHP_EOL));
+ }
+
+ /**
+ * Create the new migration.
+ *
+ * @param Input $input
+ * @param Output $output
+ * @return void
+ * @throws InvalidArgumentException
+ * @throws RuntimeException
+ */
+ protected function execute(Input $input, Output $output)
+ {
+ /** @var Creator $creator */
+ $creator = $this->app->get('migration.creator');
+
+ $className = $input->getArgument('name');
+
+ $path = $creator->create($className);
+
+ $output->writeln('<info>created</info> .' . str_replace(getcwd(), '', realpath($path)));
+ }
+
+}
diff --git a/vendor/topthink/think-migration/src/command/migrate/Rollback.php b/vendor/topthink/think-migration/src/command/migrate/Rollback.php
new file mode 100755
index 0000000..e668ac7
--- /dev/null
+++ b/vendor/topthink/think-migration/src/command/migrate/Rollback.php
@@ -0,0 +1,146 @@
+<?php
+// +----------------------------------------------------------------------
+// | TopThink [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://www.topthink.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\migration\command\migrate;
+
+use Phinx\Migration\MigrationInterface;
+use think\console\input\Option as InputOption;
+use think\console\Input;
+use think\console\Output;
+use think\migration\command\Migrate;
+
+class Rollback extends Migrate
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this->setName('migrate:rollback')
+ ->setDescription('Rollback the last or to a specific migration')
+ ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to rollback to')
+ ->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to rollback to')
+ ->addOption('--force', '-f', InputOption::VALUE_NONE, 'Force rollback to ignore breakpoints')
+ ->setHelp(<<<EOT
+The <info>migrate:rollback</info> command reverts the last migration, or optionally up to a specific version
+
+<info>php think migrate:rollback</info>
+<info>php think migrate:rollback -t 20111018185412</info>
+<info>php think migrate:rollback -d 20111018</info>
+<info>php think migrate:rollback -v</info>
+
+EOT
+ );
+ }
+
+ /**
+ * Rollback the migration.
+ *
+ * @param Input $input
+ * @param Output $output
+ * @return void
+ */
+ protected function execute(Input $input, Output $output)
+ {
+ $version = $input->getOption('target');
+ $date = $input->getOption('date');
+ $force = !!$input->getOption('force');
+
+ // rollback the specified environment
+ $start = microtime(true);
+ if (null !== $date) {
+ $this->rollbackToDateTime(new \DateTime($date), $force);
+ } else {
+ $this->rollback($version, $force);
+ }
+ $end = microtime(true);
+
+ $output->writeln('');
+ $output->writeln('<comment>All Done. Took ' . sprintf('%.4fs', $end - $start) . '</comment>');
+ }
+
+ protected function rollback($version = null, $force = false)
+ {
+ $migrations = $this->getMigrations();
+ $versionLog = $this->getVersionLog();
+ $versions = array_keys($versionLog);
+
+ ksort($migrations);
+ sort($versions);
+
+ // Check we have at least 1 migration to revert
+ if (empty($versions) || $version == end($versions)) {
+ $this->output->writeln('<error>No migrations to rollback</error>');
+ return;
+ }
+
+ // If no target version was supplied, revert the last migration
+ if (null === $version) {
+ // Get the migration before the last run migration
+ $prev = count($versions) - 2;
+ $version = $prev < 0 ? 0 : $versions[$prev];
+ } else {
+ // Get the first migration number
+ $first = $versions[0];
+
+ // If the target version is before the first migration, revert all migrations
+ if ($version < $first) {
+ $version = 0;
+ }
+ }
+
+ // Check the target version exists
+ if (0 !== $version && !isset($migrations[$version])) {
+ $this->output->writeln("<error>Target version ($version) not found</error>");
+ return;
+ }
+
+ // Revert the migration(s)
+ krsort($migrations);
+ foreach ($migrations as $migration) {
+ if ($migration->getVersion() <= $version) {
+ break;
+ }
+
+ if (in_array($migration->getVersion(), $versions)) {
+ if (isset($versionLog[$migration->getVersion()]) && 0 != $versionLog[$migration->getVersion()]['breakpoint'] && !$force) {
+ $this->output->writeln('<error>Breakpoint reached. Further rollbacks inhibited.</error>');
+ break;
+ }
+ $this->executeMigration($migration, MigrationInterface::DOWN);
+ }
+ }
+ }
+
+ protected function rollbackToDateTime(\DateTime $dateTime, $force = false)
+ {
+ $versions = $this->getVersions();
+ $dateString = $dateTime->format('YmdHis');
+ sort($versions);
+
+ $earlierVersion = null;
+ $availableMigrations = array_filter($versions, function ($version) use ($dateString, &$earlierVersion) {
+ if ($version <= $dateString) {
+ $earlierVersion = $version;
+ }
+ return $version >= $dateString;
+ });
+
+ if (count($availableMigrations) > 0) {
+ if (is_null($earlierVersion)) {
+ $this->output->writeln('Rolling back all migrations');
+ $migration = 0;
+ } else {
+ $this->output->writeln('Rolling back to version ' . $earlierVersion);
+ $migration = $earlierVersion;
+ }
+ $this->rollback($migration, $force);
+ }
+ }
+}
diff --git a/vendor/topthink/think-migration/src/command/migrate/Run.php b/vendor/topthink/think-migration/src/command/migrate/Run.php
new file mode 100755
index 0000000..4714f02
--- /dev/null
+++ b/vendor/topthink/think-migration/src/command/migrate/Run.php
@@ -0,0 +1,140 @@
+<?php
+// +----------------------------------------------------------------------
+// | TopThink [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://www.topthink.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\migration\command\migrate;
+
+use Phinx\Migration\MigrationInterface;
+use think\console\Input;
+use think\console\input\Option as InputOption;
+use think\console\Output;
+use think\migration\command\Migrate;
+
+class Run extends Migrate
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this->setName('migrate:run')
+ ->setDescription('Migrate the database')
+ ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to migrate to')
+ ->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to migrate to')
+ ->setHelp(<<<EOT
+The <info>migrate:run</info> command runs all available migrations, optionally up to a specific version
+
+<info>php think migrate:run</info>
+<info>php think migrate:run -t 20110103081132</info>
+<info>php think migrate:run -d 20110103</info>
+<info>php think migrate:run -v</info>
+
+EOT
+ );
+ }
+
+ /**
+ * Migrate the database.
+ *
+ * @param Input $input
+ * @param Output $output
+ */
+ protected function execute(Input $input, Output $output)
+ {
+ $version = $input->getOption('target');
+ $date = $input->getOption('date');
+
+ // run the migrations
+ $start = microtime(true);
+ if (null !== $date) {
+ $this->migrateToDateTime(new \DateTime($date));
+ } else {
+ $this->migrate($version);
+ }
+ $end = microtime(true);
+
+ $output->writeln('');
+ $output->writeln('<comment>All Done. Took ' . sprintf('%.4fs', $end - $start) . '</comment>');
+ }
+
+ public function migrateToDateTime(\DateTime $dateTime)
+ {
+ $versions = array_keys($this->getMigrations());
+ $dateString = $dateTime->format('YmdHis');
+
+ $outstandingMigrations = array_filter($versions, function ($version) use ($dateString) {
+ return $version <= $dateString;
+ });
+
+ if (count($outstandingMigrations) > 0) {
+ $migration = max($outstandingMigrations);
+ $this->output->writeln('Migrating to version ' . $migration);
+ $this->migrate($migration);
+ }
+ }
+
+ protected function migrate($version = null)
+ {
+ $migrations = $this->getMigrations();
+ $versions = $this->getVersions();
+ $current = $this->getCurrentVersion();
+
+ if (empty($versions) && empty($migrations)) {
+ return;
+ }
+
+ if (null === $version) {
+ $version = max(array_merge($versions, array_keys($migrations)));
+ } else {
+ if (0 != $version && !isset($migrations[$version])) {
+ $this->output->writeln(sprintf('<comment>warning</comment> %s is not a valid version', $version));
+ return;
+ }
+ }
+
+ // are we migrating up or down?
+ $direction = $version > $current ? MigrationInterface::UP : MigrationInterface::DOWN;
+
+ if ($direction === MigrationInterface::DOWN) {
+ // run downs first
+ krsort($migrations);
+ foreach ($migrations as $migration) {
+ if ($migration->getVersion() <= $version) {
+ break;
+ }
+
+ if (in_array($migration->getVersion(), $versions)) {
+ $this->executeMigration($migration, MigrationInterface::DOWN);
+ }
+ }
+ }
+
+ ksort($migrations);
+ foreach ($migrations as $migration) {
+ if ($migration->getVersion() > $version) {
+ break;
+ }
+
+ if (!in_array($migration->getVersion(), $versions)) {
+ $this->executeMigration($migration, MigrationInterface::UP);
+ }
+ }
+ }
+
+ protected function getCurrentVersion()
+ {
+ $versions = $this->getVersions();
+ $version = 0;
+
+ if (!empty($versions)) {
+ $version = end($versions);
+ }
+
+ return $version;
+ }
+}
diff --git a/vendor/topthink/think-migration/src/command/migrate/Status.php b/vendor/topthink/think-migration/src/command/migrate/Status.php
new file mode 100755
index 0000000..ccf424a
--- /dev/null
+++ b/vendor/topthink/think-migration/src/command/migrate/Status.php
@@ -0,0 +1,126 @@
+<?php
+// +----------------------------------------------------------------------
+// | TopThink [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://www.topthink.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\migration\command\migrate;
+
+use think\console\input\Option as InputOption;
+use think\console\Input;
+use think\console\Output;
+use think\migration\command\Migrate;
+
+class Status extends Migrate
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this->setName('migrate:status')
+ ->setDescription('Show migration status')
+ ->addOption('--format', '-f', InputOption::VALUE_REQUIRED, 'The output format: text or json. Defaults to text.')
+ ->setHelp(<<<EOT
+The <info>migrate:status</info> command prints a list of all migrations, along with their current status
+
+<info>php think migrate:status</info>
+<info>php think migrate:status -f json</info>
+EOT
+ );
+ }
+
+ /**
+ * Show the migration status.
+ *
+ * @param Input $input
+ * @param Output $output
+ * @return integer 0 if all migrations are up, or an error code
+ */
+ protected function execute(Input $input, Output $output)
+ {
+ $format = $input->getOption('format');
+
+ if (null !== $format) {
+ $output->writeln('<info>using format</info> ' . $format);
+ }
+
+ // print the status
+ return $this->printStatus($format);
+ }
+
+ protected function printStatus($format = null)
+ {
+ $output = $this->output;
+ $migrations = [];
+ if (count($this->getMigrations())) {
+ // TODO - rewrite using Symfony Table Helper as we already have this library
+ // included and it will fix formatting issues (e.g drawing the lines)
+ $output->writeln('');
+ $output->writeln(' Status Migration ID Started Finished Migration Name ');
+ $output->writeln('----------------------------------------------------------------------------------');
+
+ $versions = $this->getVersionLog();
+ $maxNameLength = $versions ? max(array_map(function ($version) {
+ return strlen($version['migration_name']);
+ }, $versions)) : 0;
+
+ foreach ($this->getMigrations() as $migration) {
+ $version = array_key_exists($migration->getVersion(), $versions) ? $versions[$migration->getVersion()] : false;
+ if ($version) {
+ $status = ' <info>up</info> ';
+ } else {
+ $status = ' <error>down</error> ';
+ $version = [];
+ $version['start_time'] = $version['end_time'] = $version['breakpoint'] = '';
+ }
+ $maxNameLength = max($maxNameLength, strlen($migration->getName()));
+
+ $output->writeln(sprintf('%s %14.0f %19s %19s <comment>%s</comment>', $status, $migration->getVersion(), $version['start_time'], $version['end_time'], $migration->getName()));
+
+ if ($version && $version['breakpoint']) {
+ $output->writeln(' <error>BREAKPOINT SET</error>');
+ }
+
+ $migrations[] = [
+ 'migration_status' => trim(strip_tags($status)),
+ 'migration_id' => sprintf('%14.0f', $migration->getVersion()),
+ 'migration_name' => $migration->getName()
+ ];
+ unset($versions[$migration->getVersion()]);
+ }
+
+ if (count($versions)) {
+ foreach ($versions as $missing => $version) {
+ $output->writeln(sprintf(' <error>up</error> %14.0f %19s %19s <comment>%s</comment> <error>** MISSING **</error>', $missing, $version['start_time'], $version['end_time'], str_pad($version['migration_name'], $maxNameLength, ' ')));
+
+ if ($version && $version['breakpoint']) {
+ $output->writeln(' <error>BREAKPOINT SET</error>');
+ }
+ }
+ }
+ } else {
+ // there are no migrations
+ $output->writeln('');
+ $output->writeln('There are no available migrations. Try creating one using the <info>create</info> command.');
+ }
+
+ // write an empty line
+ $output->writeln('');
+ if ($format !== null) {
+ switch ($format) {
+ case 'json':
+ $output->writeln(json_encode([
+ 'pending_count' => count($this->getMigrations()),
+ 'migrations' => $migrations
+ ]));
+ break;
+ default:
+ $output->writeln('<info>Unsupported format: ' . $format . '</info>');
+ }
+ }
+ }
+}
diff --git a/vendor/topthink/think-migration/src/command/seed/Create.php b/vendor/topthink/think-migration/src/command/seed/Create.php
new file mode 100755
index 0000000..6873980
--- /dev/null
+++ b/vendor/topthink/think-migration/src/command/seed/Create.php
@@ -0,0 +1,83 @@
+<?php
+// +----------------------------------------------------------------------
+// | TopThink [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://www.topthink.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\migration\command\seed;
+
+use Phinx\Util\Util;
+use think\console\Input;
+use think\console\input\Argument as InputArgument;
+use think\console\Output;
+use think\migration\command\Seed;
+
+class Create extends Seed
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this->setName('seed:create')
+ ->setDescription('Create a new database seeder')
+ ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the seeder?')
+ ->setHelp(sprintf('%sCreates a new database seeder%s', PHP_EOL, PHP_EOL));
+ }
+
+ /**
+ * Create the new seeder.
+ *
+ * @param Input $input
+ * @param Output $output
+ * @return void
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ */
+ protected function execute(Input $input, Output $output)
+ {
+ $path = $this->getPath();
+
+ if (!file_exists($path)) {
+ mkdir($path, 0755, true);
+ }
+
+ $this->verifyMigrationDirectory($path);
+
+ $path = realpath($path);
+
+ $className = $input->getArgument('name');
+
+ if (!Util::isValidPhinxClassName($className)) {
+ throw new \InvalidArgumentException(sprintf('The seed class name "%s" is invalid. Please use CamelCase format', $className));
+ }
+
+ // Compute the file path
+ $filePath = $path . DIRECTORY_SEPARATOR . $className . '.php';
+
+ if (is_file($filePath)) {
+ throw new \InvalidArgumentException(sprintf('The file "%s" already exists', basename($filePath)));
+ }
+
+ // inject the class names appropriate to this seeder
+ $contents = file_get_contents($this->getTemplate());
+ $classes = [
+ 'SeederClass' => $className,
+ ];
+ $contents = strtr($contents, $classes);
+
+ if (false === file_put_contents($filePath, $contents)) {
+ throw new \RuntimeException(sprintf('The file "%s" could not be written to', $path));
+ }
+
+ $output->writeln('<info>created</info> .' . str_replace(getcwd(), '', $filePath));
+ }
+
+ protected function getTemplate()
+ {
+ return __DIR__ . '/../stubs/seed.stub';
+ }
+}
diff --git a/vendor/topthink/think-migration/src/command/seed/Run.php b/vendor/topthink/think-migration/src/command/seed/Run.php
new file mode 100755
index 0000000..a0ac8f9
--- /dev/null
+++ b/vendor/topthink/think-migration/src/command/seed/Run.php
@@ -0,0 +1,107 @@
+<?php
+// +----------------------------------------------------------------------
+// | TopThink [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://www.topthink.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\migration\command\seed;
+
+use Phinx\Seed\SeedInterface;
+use think\console\Input;
+use think\console\input\Option as InputOption;
+use think\console\Output;
+use think\migration\command\Seed;
+
+class Run extends Seed
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this->setName('seed:run')
+ ->setDescription('Run database seeders')
+ ->addOption('--seed', '-s', InputOption::VALUE_REQUIRED, 'What is the name of the seeder?')
+ ->setHelp(<<<EOT
+ The <info>seed:run</info> command runs all available or individual seeders
+
+<info>php think seed:run</info>
+<info>php think seed:run -s UserSeeder</info>
+<info>php think seed:run -v</info>
+
+EOT
+ );
+ }
+
+ /**
+ * Run database seeders.
+ *
+ * @param Input $input
+ * @param Output $output
+ * @return void
+ */
+ protected function execute(Input $input, Output $output)
+ {
+ $seed = $input->getOption('seed');
+
+ // run the seed(ers)
+ $start = microtime(true);
+ $this->seed($seed);
+ $end = microtime(true);
+
+ $output->writeln('');
+ $output->writeln('<comment>All Done. Took ' . sprintf('%.4fs', $end - $start) . '</comment>');
+ }
+
+ public function seed($seed = null)
+ {
+ $seeds = $this->getSeeds();
+
+ if (null === $seed) {
+ // run all seeders
+ foreach ($seeds as $seeder) {
+ if (array_key_exists($seeder->getName(), $seeds)) {
+ $this->executeSeed($seeder);
+ }
+ }
+ } else {
+ // run only one seeder
+ if (array_key_exists($seed, $seeds)) {
+ $this->executeSeed($seeds[$seed]);
+ } else {
+ throw new \InvalidArgumentException(sprintf('The seed class "%s" does not exist', $seed));
+ }
+ }
+ }
+
+ protected function executeSeed(SeedInterface $seed)
+ {
+ $this->output->writeln('');
+ $this->output->writeln(' ==' . ' <info>' . $seed->getName() . ':</info>' . ' <comment>seeding</comment>');
+
+ // Execute the seeder and log the time elapsed.
+ $start = microtime(true);
+ $seed->setAdapter($this->getAdapter());
+
+ // begin the transaction if the adapter supports it
+ if ($this->getAdapter()->hasTransactions()) {
+ $this->getAdapter()->beginTransaction();
+ }
+
+ // Run the seeder
+ if (method_exists($seed, SeedInterface::RUN)) {
+ $seed->run();
+ }
+
+ // commit the transaction if the adapter supports it
+ if ($this->getAdapter()->hasTransactions()) {
+ $this->getAdapter()->commitTransaction();
+ }
+ $end = microtime(true);
+
+ $this->output->writeln(' ==' . ' <info>' . $seed->getName() . ':</info>' . ' <comment>seeded' . ' ' . sprintf('%.4fs', $end - $start) . '</comment>');
+ }
+}
diff --git a/vendor/topthink/think-migration/src/command/stubs/factory.stub b/vendor/topthink/think-migration/src/command/stubs/factory.stub
new file mode 100755
index 0000000..eef91dc
--- /dev/null
+++ b/vendor/topthink/think-migration/src/command/stubs/factory.stub
@@ -0,0 +1,11 @@
+<?php
+
+use Faker\Generator as Faker;
+use think\migration\Factory;
+
+/** @var Factory $factory */
+$factory->define("ModelClass", function (Faker $faker) {
+ return [
+ //
+ ];
+});
diff --git a/vendor/topthink/think-migration/src/command/stubs/migrate.stub b/vendor/topthink/think-migration/src/command/stubs/migrate.stub
new file mode 100755
index 0000000..f11d29a
--- /dev/null
+++ b/vendor/topthink/think-migration/src/command/stubs/migrate.stub
@@ -0,0 +1,33 @@
+<?php
+
+use think\migration\Migrator;
+use think\migration\db\Column;
+
+class MigratorClass extends Migrator
+{
+ /**
+ * Change Method.
+ *
+ * Write your reversible migrations using this method.
+ *
+ * More information on writing migrations is available here:
+ * http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
+ *
+ * The following commands can be used in this method and Phinx will
+ * automatically reverse them when rolling back:
+ *
+ * createTable
+ * renameTable
+ * addColumn
+ * renameColumn
+ * addIndex
+ * addForeignKey
+ *
+ * Remember to call "create()" or "update()" and NOT "save()" when working
+ * with the Table class.
+ */
+ public function change()
+ {
+
+ }
+}
diff --git a/vendor/topthink/think-migration/src/command/stubs/seed.stub b/vendor/topthink/think-migration/src/command/stubs/seed.stub
new file mode 100755
index 0000000..8e1fa03
--- /dev/null
+++ b/vendor/topthink/think-migration/src/command/stubs/seed.stub
@@ -0,0 +1,19 @@
+<?php
+
+use think\migration\Seeder;
+
+class SeederClass extends Seeder
+{
+ /**
+ * Run Method.
+ *
+ * Write your database seeder using this method.
+ *
+ * More information on writing seeders is available here:
+ * http://docs.phinx.org/en/latest/seeding.html
+ */
+ public function run()
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-migration/src/db/Column.php b/vendor/topthink/think-migration/src/db/Column.php
new file mode 100755
index 0000000..3b5cb04
--- /dev/null
+++ b/vendor/topthink/think-migration/src/db/Column.php
@@ -0,0 +1,171 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\migration\db;
+
+use Phinx\Db\Adapter\AdapterInterface;
+use Phinx\Db\Adapter\MysqlAdapter;
+
+class Column extends \Phinx\Db\Table\Column
+{
+ protected $unique = false;
+
+ public function setNullable()
+ {
+ return $this->setNull(true);
+ }
+
+ public function setUnsigned()
+ {
+ return $this->setSigned(false);
+ }
+
+ public function setUnique()
+ {
+ $this->unique = true;
+ return $this;
+ }
+
+ public function getUnique()
+ {
+ return $this->unique;
+ }
+
+ public function isUnique()
+ {
+ return $this->getUnique();
+ }
+
+ public static function make($name, $type, $options = [])
+ {
+ $column = new self();
+ $column->setName($name);
+ $column->setType($type);
+ $column->setOptions($options);
+ return $column;
+ }
+
+ public static function bigInteger($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_BIG_INTEGER);
+ }
+
+ public static function binary($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_BLOB);
+ }
+
+ public static function boolean($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_BOOLEAN);
+ }
+
+ public static function char($name, $length = 255)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_CHAR, compact('length'));
+ }
+
+ public static function date($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_DATE);
+ }
+
+ public static function dateTime($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_DATETIME);
+ }
+
+ public static function decimal($name, $precision = 8, $scale = 2)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_DECIMAL, compact('precision', 'scale'));
+ }
+
+ public static function enum($name, array $values)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_ENUM, compact('values'));
+ }
+
+ public static function float($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_FLOAT);
+ }
+
+ public static function integer($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_INTEGER);
+ }
+
+ public static function json($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_JSON);
+ }
+
+ public static function jsonb($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_JSONB);
+ }
+
+ public static function longText($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_TEXT, ['length' => MysqlAdapter::TEXT_LONG]);
+ }
+
+ public static function mediumInteger($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_INTEGER, ['length' => MysqlAdapter::INT_MEDIUM]);
+ }
+
+ public static function mediumText($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_TEXT, ['length' => MysqlAdapter::TEXT_MEDIUM]);
+ }
+
+ public static function smallInteger($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_INTEGER, ['length' => MysqlAdapter::INT_SMALL]);
+ }
+
+ public static function string($name, $length = 255)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_STRING, compact('length'));
+ }
+
+ public static function text($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_TEXT);
+ }
+
+ public static function time($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_TIME);
+ }
+
+ public static function tinyInteger($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_INTEGER, ['length' => MysqlAdapter::INT_TINY]);
+ }
+
+ public static function unsignedInteger($name)
+ {
+ return self::integer($name)->setUnSigned();
+ }
+
+ public static function timestamp($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_TIMESTAMP);
+ }
+
+ public static function uuid($name)
+ {
+ return self::make($name, AdapterInterface::PHINX_TYPE_UUID);
+ }
+
+}
diff --git a/vendor/topthink/think-migration/src/db/Table.php b/vendor/topthink/think-migration/src/db/Table.php
new file mode 100755
index 0000000..cf4daa9
--- /dev/null
+++ b/vendor/topthink/think-migration/src/db/Table.php
@@ -0,0 +1,135 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\migration\db;
+
+use Phinx\Db\Table\Index;
+
+class Table extends \Phinx\Db\Table
+{
+ /**
+ * 设置id
+ * @param $id
+ * @return $this
+ */
+ public function setId($id)
+ {
+ $this->options['id'] = $id;
+ return $this;
+ }
+
+ /**
+ * 设置主键
+ * @param $key
+ * @return $this
+ */
+ public function setPrimaryKey($key)
+ {
+ $this->options['primary_key'] = $key;
+ return $this;
+ }
+
+ /**
+ * 设置引擎
+ * @param $engine
+ * @return $this
+ */
+ public function setEngine($engine)
+ {
+ $this->options['engine'] = $engine;
+ return $this;
+ }
+
+ /**
+ * 设置表注释
+ * @param $comment
+ * @return $this
+ */
+ public function setComment($comment)
+ {
+ $this->options['comment'] = $comment;
+ return $this;
+ }
+
+ /**
+ * 设置排序比对方法
+ * @param $collation
+ * @return $this
+ */
+ public function setCollation($collation)
+ {
+ $this->options['collation'] = $collation;
+ return $this;
+ }
+
+ public function addSoftDelete()
+ {
+ $this->addColumn(Column::timestamp('delete_time')->setNullable());
+ return $this;
+ }
+
+ public function addMorphs($name, $indexName = null)
+ {
+ $this->addColumn(Column::unsignedInteger("{$name}_id"));
+ $this->addColumn(Column::string("{$name}_type"));
+ $this->addIndex(["{$name}_id", "{$name}_type"], ['name' => $indexName]);
+ return $this;
+ }
+
+ public function addNullableMorphs($name, $indexName = null)
+ {
+ $this->addColumn(Column::unsignedInteger("{$name}_id")->setNullable());
+ $this->addColumn(Column::string("{$name}_type")->setNullable());
+ $this->addIndex(["{$name}_id", "{$name}_type"], ['name' => $indexName]);
+ return $this;
+ }
+
+ /**
+ * @param string $createdAtColumnName
+ * @param string $updatedAtColumnName
+ * @return \Phinx\Db\Table|Table
+ */
+ public function addTimestamps($createdAtColumnName = 'create_time', $updatedAtColumnName = 'update_time')
+ {
+ return parent::addTimestamps($createdAtColumnName, $updatedAtColumnName);
+ }
+
+ /**
+ * @param \Phinx\Db\Table\Column|string $columnName
+ * @param null $type
+ * @param array $options
+ * @return \Phinx\Db\Table|Table
+ */
+ public function addColumn($columnName, $type = null, $options = [])
+ {
+ if ($columnName instanceof Column && $columnName->getUnique()) {
+ $index = new Index();
+ $index->setColumns([$columnName->getName()]);
+ $index->setType(Index::UNIQUE);
+ $this->addIndex($index);
+ }
+ return parent::addColumn($columnName, $type, $options);
+ }
+
+ /**
+ * @param string $columnName
+ * @param null $newColumnType
+ * @param array $options
+ * @return \Phinx\Db\Table|Table
+ */
+ public function changeColumn($columnName, $newColumnType = null, $options = [])
+ {
+ if ($columnName instanceof \Phinx\Db\Table\Column) {
+ return parent::changeColumn($columnName->getName(), $columnName, $options);
+ }
+ return parent::changeColumn($columnName, $newColumnType, $options);
+ }
+}
diff --git a/vendor/topthink/think-migration/src/helper.php b/vendor/topthink/think-migration/src/helper.php
new file mode 100755
index 0000000..6dc47ab
--- /dev/null
+++ b/vendor/topthink/think-migration/src/helper.php
@@ -0,0 +1,40 @@
+<?php
+
+use think\migration\Factory;
+use think\migration\FactoryBuilder;
+
+if (!function_exists('factory')) {
+ /**
+ * Create a model factory builder for a given class, name, and amount.
+ *
+ * @param mixed class|class,name|class,amount|class,name,amount
+ * @return FactoryBuilder
+ */
+ function factory()
+ {
+ /** @var Factory $factory */
+ $factory = app(Factory::class);
+
+ $arguments = func_get_args();
+
+ if (isset($arguments[1]) && is_string($arguments[1])) {
+ return $factory->of($arguments[0], $arguments[1])->times($arguments[2] ?? null);
+ } elseif (isset($arguments[1])) {
+ return $factory->of($arguments[0])->times($arguments[1]);
+ }
+
+ return $factory->of($arguments[0]);
+ }
+}
+
+if (!function_exists('database_path')) {
+ /**
+ * 获取数据迁移脚本地址
+ * @param string $path
+ * @return string
+ */
+ function database_path($path = '')
+ {
+ return app()->getRootPath() . 'database' . DIRECTORY_SEPARATOR . $path;
+ }
+}
diff --git a/vendor/topthink/think-multi-app/LICENSE b/vendor/topthink/think-multi-app/LICENSE
new file mode 100755
index 0000000..261eeb9
--- /dev/null
+++ b/vendor/topthink/think-multi-app/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/topthink/think-multi-app/README.md b/vendor/topthink/think-multi-app/README.md
new file mode 100755
index 0000000..a746fa7
--- /dev/null
+++ b/vendor/topthink/think-multi-app/README.md
@@ -0,0 +1,14 @@
+# think-multi-app
+
+用于ThinkPHP6+的多应用支持
+
+## 安装
+
+~~~
+composer require topthink/think-multi-app
+~~~
+
+## 使用
+
+用法参考ThinkPHP6完全开发手册[多应用模式](https://www.kancloud.cn/manual/thinkphp6_0/1297876)章节。
+
diff --git a/vendor/topthink/think-multi-app/composer.json b/vendor/topthink/think-multi-app/composer.json
new file mode 100755
index 0000000..92d620e
--- /dev/null
+++ b/vendor/topthink/think-multi-app/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "topthink/think-multi-app",
+ "description": "thinkphp6 multi app support",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=7.1.0",
+ "topthink/framework": "^6.0.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\app\\": "src"
+ }
+ },
+ "extra": {
+ "think":{
+ "services":[
+ "think\\app\\Service"
+ ]
+ }
+ },
+ "minimum-stability": "dev"
+}
diff --git a/vendor/topthink/think-multi-app/src/MultiApp.php b/vendor/topthink/think-multi-app/src/MultiApp.php
new file mode 100755
index 0000000..b0ac260
--- /dev/null
+++ b/vendor/topthink/think-multi-app/src/MultiApp.php
@@ -0,0 +1,245 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\app;
+
+use Closure;
+use think\App;
+use think\exception\HttpException;
+use think\Request;
+use think\Response;
+
+/**
+ * 多应用模式支持
+ */
+class MultiApp
+{
+
+ /** @var App */
+ protected $app;
+
+ /**
+ * 应用名称
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * 应用名称
+ * @var string
+ */
+ protected $appName;
+
+ /**
+ * 应用路径
+ * @var string
+ */
+ protected $path;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ $this->name = $this->app->http->getName();
+ $this->path = $this->app->http->getPath();
+ }
+
+ /**
+ * 多应用解析
+ * @access public
+ * @param Request $request
+ * @param Closure $next
+ * @return Response
+ */
+ public function handle($request, Closure $next)
+ {
+ if (!$this->parseMultiApp()) {
+ return $next($request);
+ }
+
+ return $this->app->middleware->pipeline('app')
+ ->send($request)
+ ->then(function ($request) use ($next) {
+ return $next($request);
+ });
+ }
+
+ /**
+ * 获取路由目录
+ * @access protected
+ * @return string
+ */
+ protected function getRoutePath(): string
+ {
+ return $this->app->getAppPath() . 'route' . DIRECTORY_SEPARATOR;
+ }
+
+ /**
+ * 解析多应用
+ * @return bool
+ */
+ protected function parseMultiApp(): bool
+ {
+ $scriptName = $this->getScriptName();
+ $defaultApp = $this->app->config->get('app.default_app') ?: 'index';
+
+ if ($this->name || ($scriptName && !in_array($scriptName, ['index', 'router', 'think']))) {
+ $appName = $this->name ?: $scriptName;
+ $this->app->http->setBind();
+ } else {
+ // 自动多应用识别
+ $this->app->http->setBind(false);
+ $appName = null;
+ $this->appName = '';
+
+ $bind = $this->app->config->get('app.domain_bind', []);
+
+ if (!empty($bind)) {
+ // 获取当前子域名
+ $subDomain = $this->app->request->subDomain();
+ $domain = $this->app->request->host(true);
+
+ if (isset($bind[$domain])) {
+ $appName = $bind[$domain];
+ $this->app->http->setBind();
+ } elseif (isset($bind[$subDomain])) {
+ $appName = $bind[$subDomain];
+ $this->app->http->setBind();
+ } elseif (isset($bind['*'])) {
+ $appName = $bind['*'];
+ $this->app->http->setBind();
+ }
+ }
+
+ if (!$this->app->http->isBind()) {
+ $path = $this->app->request->pathinfo();
+ $map = $this->app->config->get('app.app_map', []);
+ $deny = $this->app->config->get('app.deny_app_list', []);
+ $name = current(explode('/', $path));
+
+ if (strpos($name, '.')) {
+ $name = strstr($name, '.', true);
+ }
+
+ if (isset($map[$name])) {
+ if ($map[$name] instanceof Closure) {
+ $result = call_user_func_array($map[$name], [$this->app]);
+ $appName = $result ?: $name;
+ } else {
+ $appName = $map[$name];
+ }
+ } elseif ($name && (false !== array_search($name, $map) || in_array($name, $deny))) {
+ throw new HttpException(404, 'app not exists:' . $name);
+ } elseif ($name && isset($map['*'])) {
+ $appName = $map['*'];
+ } else {
+ $appName = $name ?: $defaultApp;
+ $appPath = $this->path ?: $this->app->getBasePath() . $appName . DIRECTORY_SEPARATOR;
+
+ if (!is_dir($appPath)) {
+ $express = $this->app->config->get('app.app_express', false);
+ if ($express) {
+ $this->setApp($defaultApp);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ if ($name) {
+ $this->app->request->setRoot('/' . $name);
+ $this->app->request->setPathinfo(strpos($path, '/') ? ltrim(strstr($path, '/'), '/') : '');
+ }
+ }
+ }
+
+ $this->setApp($appName ?: $defaultApp);
+ return true;
+ }
+
+ /**
+ * 获取当前运行入口名称
+ * @access protected
+ * @codeCoverageIgnore
+ * @return string
+ */
+ protected function getScriptName(): string
+ {
+ if (isset($_SERVER['SCRIPT_FILENAME'])) {
+ $file = $_SERVER['SCRIPT_FILENAME'];
+ } elseif (isset($_SERVER['argv'][0])) {
+ $file = realpath($_SERVER['argv'][0]);
+ }
+
+ return isset($file) ? pathinfo($file, PATHINFO_FILENAME) : '';
+ }
+
+ /**
+ * 设置应用
+ * @param string $appName
+ */
+ protected function setApp(string $appName): void
+ {
+ $this->appName = $appName;
+ $this->app->http->name($appName);
+
+ $appPath = $this->path ?: $this->app->getBasePath() . $appName . DIRECTORY_SEPARATOR;
+
+ $this->app->setAppPath($appPath);
+ // 设置应用命名空间
+ $this->app->setNamespace($this->app->config->get('app.app_namespace') ?: 'app\\' . $appName);
+
+ if (is_dir($appPath)) {
+ $this->app->setRuntimePath($this->app->getRuntimePath() . $appName . DIRECTORY_SEPARATOR);
+ $this->app->http->setRoutePath($this->getRoutePath());
+
+ //加载应用
+ $this->loadApp($appName, $appPath);
+ }
+ }
+
+ /**
+ * 加载应用文件
+ * @param string $appName 应用名
+ * @return void
+ */
+ protected function loadApp(string $appName, string $appPath): void
+ {
+ if (is_file($appPath . 'common.php')) {
+ include_once $appPath . 'common.php';
+ }
+
+ $files = [];
+
+ $files = array_merge($files, glob($appPath . 'config' . DIRECTORY_SEPARATOR . '*' . $this->app->getConfigExt()));
+
+ foreach ($files as $file) {
+ $this->app->config->load($file, pathinfo($file, PATHINFO_FILENAME));
+ }
+
+ if (is_file($appPath . 'event.php')) {
+ $this->app->loadEvent(include $appPath . 'event.php');
+ }
+
+ if (is_file($appPath . 'middleware.php')) {
+ $this->app->middleware->import(include $appPath . 'middleware.php', 'app');
+ }
+
+ if (is_file($appPath . 'provider.php')) {
+ $this->app->bind(include $appPath . 'provider.php');
+ }
+
+ // 加载应用默认语言包
+ $this->app->loadLangPack($this->app->lang->defaultLangSet());
+ }
+
+}
diff --git a/vendor/topthink/think-multi-app/src/Service.php b/vendor/topthink/think-multi-app/src/Service.php
new file mode 100755
index 0000000..cdc90b4
--- /dev/null
+++ b/vendor/topthink/think-multi-app/src/Service.php
@@ -0,0 +1,32 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace think\app;
+
+use think\Service as BaseService;
+
+class Service extends BaseService
+{
+ public function boot()
+ {
+ $this->app->event->listen('HttpRun', function () {
+ $this->app->middleware->add(MultiApp::class);
+ });
+
+ $this->commands([
+ 'build' => command\Build::class,
+ 'clear' => command\Clear::class,
+ ]);
+
+ $this->app->bind([
+ 'think\route\Url' => Url::class,
+ ]);
+ }
+}
diff --git a/vendor/topthink/think-multi-app/src/Url.php b/vendor/topthink/think-multi-app/src/Url.php
new file mode 100755
index 0000000..7bd6057
--- /dev/null
+++ b/vendor/topthink/think-multi-app/src/Url.php
@@ -0,0 +1,232 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\app;
+
+use think\App;
+use think\Route;
+use think\route\Url as UrlBuild;
+
+/**
+ * 路由地址生成
+ */
+class Url extends UrlBuild
+{
+ /**
+ * 直接解析URL地址
+ * @access protected
+ * @param string $url URL
+ * @param string|bool $domain Domain
+ * @return string
+ */
+ protected function parseUrl(string $url, &$domain): string
+ {
+ $request = $this->app->request;
+
+ if (0 === strpos($url, '/')) {
+ // 直接作为路由地址解析
+ $url = substr($url, 1);
+ } elseif (false !== strpos($url, '\\')) {
+ // 解析到类
+ $url = ltrim(str_replace('\\', '/', $url), '/');
+ } elseif (0 === strpos($url, '@')) {
+ // 解析到控制器
+ $url = substr($url, 1);
+ } elseif ('' === $url) {
+ $url = $this->getAppName() . '/' . $request->controller() . '/' . $request->action();
+ } else {
+ // 解析到 应用/控制器/操作
+ $controller = $request->controller();
+ $app = $this->getAppName();
+ $path = explode('/', $url);
+ $action = array_pop($path);
+ $controller = empty($path) ? $controller : array_pop($path);
+ $app = empty($path) ? $app : array_pop($path);
+ $url = $controller . '/' . $action;
+ $bind = $this->app->config->get('app.domain_bind', []);
+
+ if ($key = array_search($this->app->http->getName(), $bind)) {
+ isset($bind[$_SERVER['SERVER_NAME']]) && $domain = $_SERVER['SERVER_NAME'];
+
+ $domain = is_bool($domain) ? $key : $domain;
+ } else {
+ $url = $app . '/' . $url;
+ }
+ }
+
+ return $url;
+ }
+
+ public function build()
+ {
+ // 解析URL
+ $url = $this->url;
+ $suffix = $this->suffix;
+ $domain = $this->domain;
+ $request = $this->app->request;
+ $vars = $this->vars;
+
+ if (0 === strpos($url, '[') && $pos = strpos($url, ']')) {
+ // [name] 表示使用路由命名标识生成URL
+ $name = substr($url, 1, $pos - 1);
+ $url = 'name' . substr($url, $pos + 1);
+ }
+
+ if (false === strpos($url, '://') && 0 !== strpos($url, '/')) {
+ $info = parse_url($url);
+ $url = !empty($info['path']) ? $info['path'] : '';
+
+ if (isset($info['fragment'])) {
+ // 解析锚点
+ $anchor = $info['fragment'];
+
+ if (false !== strpos($anchor, '?')) {
+ // 解析参数
+ list($anchor, $info['query']) = explode('?', $anchor, 2);
+ }
+
+ if (false !== strpos($anchor, '@')) {
+ // 解析域名
+ list($anchor, $domain) = explode('@', $anchor, 2);
+ }
+ } elseif (strpos($url, '@') && false === strpos($url, '\\')) {
+ // 解析域名
+ list($url, $domain) = explode('@', $url, 2);
+ }
+ }
+
+ if ($url) {
+ $checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '');
+ $checkDomain = $domain && is_string($domain) ? $domain : null;
+
+ $rule = $this->route->getName($checkName, $checkDomain);
+
+ if (empty($rule) && isset($info['query'])) {
+ $rule = $this->route->getName($url, $checkDomain);
+ // 解析地址里面参数 合并到vars
+ parse_str($info['query'], $params);
+ $vars = array_merge($params, $vars);
+ unset($info['query']);
+ }
+ }
+
+ if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) {
+ // 匹配路由命名标识
+ $url = $match[0];
+
+ if ($domain && !empty($match[1])) {
+ $domain = $match[1];
+ }
+
+ if (!is_null($match[2])) {
+ $suffix = $match[2];
+ }
+
+ if (!$this->app->http->isBind()) {
+ $app = $this->getAppName();
+ $url = $app . '/' . $url;
+ }
+ } elseif (!empty($rule) && isset($name)) {
+ throw new \InvalidArgumentException('route name not exists:' . $name);
+ } else {
+ // 检测URL绑定
+ $bind = $this->route->getDomainBind($domain && is_string($domain) ? $domain : null);
+
+ if ($bind && 0 === strpos($url, $bind)) {
+ $url = substr($url, strlen($bind) + 1);
+ } else {
+ $binds = $this->route->getBind();
+
+ foreach ($binds as $key => $val) {
+ if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) {
+ $url = substr($url, strlen($val) + 1);
+ $domain = $key;
+ break;
+ }
+ }
+ }
+
+ // 路由标识不存在 直接解析
+ $url = $this->parseUrl($url, $domain);
+
+ if (isset($info['query'])) {
+ // 解析地址里面参数 合并到vars
+ parse_str($info['query'], $params);
+ $vars = array_merge($params, $vars);
+ }
+ }
+
+ // 还原URL分隔符
+ $depr = $this->route->config('pathinfo_depr');
+ $url = str_replace('/', $depr, $url);
+
+ $file = $request->baseFile();
+ if ($file && 0 !== strpos($request->url(), $file)) {
+ $file = str_replace('\\', '/', dirname($file));
+ }
+
+ $url = rtrim($file, '/') . '/' . ltrim($url, '/');
+
+ // URL后缀
+ if ('/' == substr($url, -1) || '' == $url) {
+ $suffix = '';
+ } else {
+ $suffix = $this->parseSuffix($suffix);
+ }
+
+ // 锚点
+ $anchor = !empty($anchor) ? '#' . $anchor : '';
+
+ // 参数组装
+ if (!empty($vars)) {
+ // 添加参数
+ if ($this->route->config('url_common_param')) {
+ $vars = http_build_query($vars);
+ $url .= $suffix . '?' . $vars . $anchor;
+ } else {
+ foreach ($vars as $var => $val) {
+ $val = (string) $val;
+ if ('' !== $val) {
+ $url .= $depr . $var . $depr . urlencode($val);
+ }
+ }
+
+ $url .= $suffix . $anchor;
+ }
+ } else {
+ $url .= $suffix . $anchor;
+ }
+
+ // 检测域名
+ $domain = $this->parseDomain($url, $domain);
+
+ // URL组装
+ return $domain . rtrim($this->root, '/') . '/' . ltrim($url, '/');
+ }
+
+ /**
+ * 获取URL的应用名
+ * @access protected
+ * @return string
+ */
+ protected function getAppName()
+ {
+ $app = $this->app->http->getName();
+ $map = $this->app->config->get('app.app_map', []);
+
+ if ($key = array_search($app, $map)) {
+ $app = $key;
+ }
+
+ return $app;
+ }
+}
diff --git a/vendor/topthink/think-multi-app/src/command/Build.php b/vendor/topthink/think-multi-app/src/command/Build.php
new file mode 100755
index 0000000..65b2f87
--- /dev/null
+++ b/vendor/topthink/think-multi-app/src/command/Build.php
@@ -0,0 +1,180 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\app\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\Output;
+
+class Build extends Command
+{
+ /**
+ * 应用基础目录
+ * @var string
+ */
+ protected $basePath;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this->setName('build')
+ ->addArgument('app', Argument::OPTIONAL, 'app name .')
+ ->setDescription('Build App Dirs');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $this->basePath = $this->app->getBasePath();
+ $app = $input->getArgument('app') ?: '';
+
+ if (is_file($this->basePath . 'build.php')) {
+ $list = include $this->basePath . 'build.php';
+ } else {
+ $list = [
+ '__dir__' => ['controller', 'model', 'view'],
+ ];
+ }
+
+ $this->buildApp($app, $list);
+ $output->writeln("<info>Successed</info>");
+
+ }
+
+ /**
+ * 创建应用
+ * @access protected
+ * @param string $app 应用名
+ * @param array $list 目录结构
+ * @return void
+ */
+ protected function buildApp(string $app, array $list = []): void
+ {
+ if (!is_dir($this->basePath . $app)) {
+ // 创建应用目录
+ mkdir($this->basePath . $app);
+ }
+
+ $appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
+ $namespace = 'app' . ($app ? '\\' . $app : '');
+
+ // 创建配置文件和公共文件
+ $this->buildCommon($app);
+ // 创建模块的默认页面
+ $this->buildHello($app, $namespace);
+
+ foreach ($list as $path => $file) {
+ if ('__dir__' == $path) {
+ // 生成子目录
+ foreach ($file as $dir) {
+ $this->checkDirBuild($appPath . $dir);
+ }
+ } elseif ('__file__' == $path) {
+ // 生成(空白)文件
+ foreach ($file as $name) {
+ if (!is_file($appPath . $name)) {
+ file_put_contents($appPath . $name, 'php' == pathinfo($name, PATHINFO_EXTENSION) ? '<?php' . PHP_EOL : '');
+ }
+ }
+ } else {
+ // 生成相关MVC文件
+ foreach ($file as $val) {
+ $val = trim($val);
+ $filename = $appPath . $path . DIRECTORY_SEPARATOR . $val . '.php';
+ $space = $namespace . '\\' . $path;
+ $class = $val;
+ switch ($path) {
+ case 'controller': // 控制器
+ if ($this->app->config->get('route.controller_suffix')) {
+ $filename = $appPath . $path . DIRECTORY_SEPARATOR . $val . 'Controller.php';
+ $class = $val . 'Controller';
+ }
+ $content = "<?php" . PHP_EOL . "namespace {$space};" . PHP_EOL . PHP_EOL . "class {$class}" . PHP_EOL . "{" . PHP_EOL . PHP_EOL . "}";
+ break;
+ case 'model': // 模型
+ $content = "<?php" . PHP_EOL . "namespace {$space};" . PHP_EOL . PHP_EOL . "use think\Model;" . PHP_EOL . PHP_EOL . "class {$class} extends Model" . PHP_EOL . "{" . PHP_EOL . PHP_EOL . "}";
+ break;
+ case 'view': // 视图
+ $filename = $appPath . $path . DIRECTORY_SEPARATOR . $val . '.html';
+ $this->checkDirBuild(dirname($filename));
+ $content = '';
+ break;
+ default:
+ // 其他文件
+ $content = "<?php" . PHP_EOL . "namespace {$space};" . PHP_EOL . PHP_EOL . "class {$class}" . PHP_EOL . "{" . PHP_EOL . PHP_EOL . "}";
+ }
+
+ if (!is_file($filename)) {
+ file_put_contents($filename, $content);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 创建应用的欢迎页面
+ * @access protected
+ * @param string $app 目录
+ * @param string $namespace 类库命名空间
+ * @return void
+ */
+ protected function buildHello(string $app, string $namespace): void
+ {
+ $suffix = $this->app->config->get('route.controller_suffix') ? 'Controller' : '';
+ $filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . $suffix . '.php';
+
+ if (!is_file($filename)) {
+ $content = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'controller.stub');
+ $content = str_replace(['{%name%}', '{%app%}', '{%layer%}', '{%suffix%}'], [$app, $namespace, 'controller', $suffix], $content);
+ $this->checkDirBuild(dirname($filename));
+
+ file_put_contents($filename, $content);
+ }
+ }
+
+ /**
+ * 创建应用的公共文件
+ * @access protected
+ * @param string $app 目录
+ * @return void
+ */
+ protected function buildCommon(string $app): void
+ {
+ $appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '');
+
+ if (!is_file($appPath . 'common.php')) {
+ file_put_contents($appPath . 'common.php', "<?php" . PHP_EOL . "// 这是系统自动生成的公共文件" . PHP_EOL);
+ }
+
+ foreach (['event', 'middleware', 'common'] as $name) {
+ if (!is_file($appPath . $name . '.php')) {
+ file_put_contents($appPath . $name . '.php', "<?php" . PHP_EOL . "// 这是系统自动生成的{$name}定义文件" . PHP_EOL . "return [" . PHP_EOL . PHP_EOL . "];" . PHP_EOL);
+ }
+ }
+ }
+
+ /**
+ * 创建目录
+ * @access protected
+ * @param string $dirname 目录名称
+ * @return void
+ */
+ protected function checkDirBuild(string $dirname): void
+ {
+ if (!is_dir($dirname)) {
+ mkdir($dirname, 0755, true);
+ }
+ }
+}
diff --git a/vendor/topthink/think-multi-app/src/command/Clear.php b/vendor/topthink/think-multi-app/src/command/Clear.php
new file mode 100755
index 0000000..f5283a3
--- /dev/null
+++ b/vendor/topthink/think-multi-app/src/command/Clear.php
@@ -0,0 +1,66 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace think\app\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\input\Option;
+use think\console\Output;
+
+class Clear extends Command
+{
+ protected function configure()
+ {
+ // 指令配置
+ $this->setName('clear')
+ ->addArgument('app', Argument::OPTIONAL, 'app name .')
+ ->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file')
+ ->addOption('log', 'l', Option::VALUE_NONE, 'clear log file')
+ ->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir')
+ ->setDescription('Clear runtime file');
+ }
+
+ protected function execute(Input $input, Output $output)
+ {
+ $app = $input->getArgument('app') ?: '';
+ $runtimePath = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($app ? $app . DIRECTORY_SEPARATOR : '');
+
+ if ($input->getOption('cache')) {
+ $path = $runtimePath . 'cache';
+ } elseif ($input->getOption('log')) {
+ $path = $runtimePath . 'log';
+ } else {
+ $path = $runtimePath;
+ }
+
+ $rmdir = $input->getOption('dir') ? true : false;
+ $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir);
+
+ $output->writeln("<info>Clear Successed</info>");
+ }
+
+ protected function clear(string $path, bool $rmdir): void
+ {
+ $files = is_dir($path) ? scandir($path) : [];
+
+ foreach ($files as $file) {
+ if ('.' != $file && '..' != $file && is_dir($path . $file)) {
+ array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*'));
+ if ($rmdir) {
+ rmdir($path . $file);
+ }
+ } elseif ('.gitignore' != $file && is_file($path . $file)) {
+ unlink($path . $file);
+ }
+ }
+ }
+}
diff --git a/vendor/topthink/think-multi-app/src/command/stubs/controller.stub b/vendor/topthink/think-multi-app/src/command/stubs/controller.stub
new file mode 100755
index 0000000..263c4c6
--- /dev/null
+++ b/vendor/topthink/think-multi-app/src/command/stubs/controller.stub
@@ -0,0 +1,12 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%app%}\{%layer%};
+
+class Index{%suffix%}
+{
+ public function index()
+ {
+ return '您好!这是一个[{%name%}]示例应用';
+ }
+}
diff --git a/vendor/topthink/think-orm/.gitignore b/vendor/topthink/think-orm/.gitignore
new file mode 100755
index 0000000..82cfc4e
--- /dev/null
+++ b/vendor/topthink/think-orm/.gitignore
@@ -0,0 +1,3 @@
+.idea
+composer.lock
+vendor
diff --git a/vendor/topthink/think-orm/LICENSE b/vendor/topthink/think-orm/LICENSE
new file mode 100755
index 0000000..8dada3e
--- /dev/null
+++ b/vendor/topthink/think-orm/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/topthink/think-orm/README.md b/vendor/topthink/think-orm/README.md
new file mode 100755
index 0000000..c65afe9
--- /dev/null
+++ b/vendor/topthink/think-orm/README.md
@@ -0,0 +1,27 @@
+# ThinkORM
+
+基于PHP7.1+ 和PDO实现的ORM,支持多数据库,2.0版本主要特性包括:
+
+* 基于PDO和PHP强类型实现
+* 支持原生查询和查询构造器
+* 自动参数绑定和预查询
+* 简洁易用的查询功能
+* 强大灵活的模型用法
+* 支持预载入关联查询和延迟关联查询
+* 支持多数据库及动态切换
+* 支持`MongoDb`
+* 支持分布式及事务
+* 支持断点重连
+* 支持`JSON`查询
+* 支持数据库日志
+* 支持`PSR-16`缓存及`PSR-3`日志规范
+
+
+## 安装
+~~~
+composer require topthink/think-orm
+~~~
+
+## 文档
+
+详细参考 [ThinkORM开发指南](https://www.kancloud.cn/manual/think-orm/content)
diff --git a/vendor/topthink/think-orm/composer.json b/vendor/topthink/think-orm/composer.json
new file mode 100755
index 0000000..1a5b9e4
--- /dev/null
+++ b/vendor/topthink/think-orm/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "topthink/think-orm",
+ "description": "think orm",
+ "keywords": [
+ "orm",
+ "database"
+ ],
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=7.1.0",
+ "ext-json": "*",
+ "psr/simple-cache": "^1.0",
+ "psr/log": "~1.0",
+ "topthink/think-helper":"^3.1"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\": "src"
+ },
+ "files": []
+ }
+}
diff --git a/vendor/topthink/think-orm/src/DbManager.php b/vendor/topthink/think-orm/src/DbManager.php
new file mode 100755
index 0000000..414706a
--- /dev/null
+++ b/vendor/topthink/think-orm/src/DbManager.php
@@ -0,0 +1,396 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use InvalidArgumentException;
+use Psr\Log\LoggerInterface;
+use Psr\SimpleCache\CacheInterface;
+use think\db\BaseQuery;
+use think\db\ConnectionInterface;
+use think\db\Query;
+use think\db\Raw;
+
+/**
+ * Class DbManager
+ * @package think
+ * @mixin BaseQuery
+ * @mixin Query
+ */
+class DbManager
+{
+ /**
+ * 数据库连接实例
+ * @var array
+ */
+ protected $instance = [];
+
+ /**
+ * 数据库配置
+ * @var array
+ */
+ protected $config = [];
+
+ /**
+ * Event对象或者数组
+ * @var array|object
+ */
+ protected $event;
+
+ /**
+ * SQL监听
+ * @var array
+ */
+ protected $listen = [];
+
+ /**
+ * SQL日志
+ * @var array
+ */
+ protected $dbLog = [];
+
+ /**
+ * 查询次数
+ * @var int
+ */
+ protected $queryTimes = 0;
+
+ /**
+ * 查询缓存对象
+ * @var CacheInterface
+ */
+ protected $cache;
+
+ /**
+ * 查询日志对象
+ * @var LoggerInterface
+ */
+ protected $log;
+
+ /**
+ * 架构函数
+ * @access public
+ */
+ public function __construct()
+ {
+ $this->modelMaker();
+ }
+
+ /**
+ * 注入模型对象
+ * @access public
+ * @return void
+ */
+ protected function modelMaker()
+ {
+ $this->triggerSql();
+
+ Model::setDb($this);
+
+ if (is_object($this->event)) {
+ Model::setEvent($this->event);
+ }
+
+ Model::maker(function (Model $model) {
+ $isAutoWriteTimestamp = $model->getAutoWriteTimestamp();
+
+ if (is_null($isAutoWriteTimestamp)) {
+ // 自动写入时间戳
+ $model->isAutoWriteTimestamp($this->getConfig('auto_timestamp', true));
+ }
+
+ $dateFormat = $model->getDateFormat();
+
+ if (is_null($dateFormat)) {
+ // 设置时间戳格式
+ $model->setDateFormat($this->getConfig('datetime_format', 'Y-m-d H:i:s'));
+ }
+ });
+ }
+
+ /**
+ * 监听SQL
+ * @access protected
+ * @return void
+ */
+ protected function triggerSql(): void
+ {
+ // 监听SQL
+ $this->listen(function ($sql, $time, $master) {
+ if (0 === strpos($sql, 'CONNECT:')) {
+ $this->log($sql);
+ return;
+ }
+
+ // 记录SQL
+ if (is_bool($master)) {
+ // 分布式记录当前操作的主从
+ $master = $master ? 'master|' : 'slave|';
+ } else {
+ $master = '';
+ }
+
+ $this->log($sql . ' [ ' . $master . 'RunTime:' . $time . 's ]');
+ });
+ }
+
+ /**
+ * 初始化配置参数
+ * @access public
+ * @param array $config 连接配置
+ * @return void
+ */
+ public function setConfig($config): void
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * 设置缓存对象
+ * @access public
+ * @param CacheInterface $cache 缓存对象
+ * @return void
+ */
+ public function setCache(CacheInterface $cache): void
+ {
+ $this->cache = $cache;
+ }
+
+ /**
+ * 设置日志对象
+ * @access public
+ * @param LoggerInterface $log 日志对象
+ * @return void
+ */
+ public function setLog(LoggerInterface $log): void
+ {
+ $this->log = $log;
+ }
+
+ /**
+ * 记录SQL日志
+ * @access protected
+ * @param string $log SQL日志信息
+ * @param string $type 日志类型
+ * @return void
+ */
+ public function log(string $log, string $type = 'sql')
+ {
+ if ($this->log) {
+ $this->log->log($type, $log);
+ } else {
+ $this->dbLog[$type][] = $log;
+ }
+ }
+
+ /**
+ * 获得查询日志(没有设置日志对象使用)
+ * @access public
+ * @param bool $clear 是否清空
+ * @return array
+ */
+ public function getDbLog(bool $clear = false): array
+ {
+ $logs = $this->dbLog;
+ if ($clear) {
+ $this->dbLog = [];
+ }
+
+ return $logs;
+ }
+
+ /**
+ * 获取配置参数
+ * @access public
+ * @param string $name 配置参数
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function getConfig(string $name = '', $default = null)
+ {
+ if ('' === $name) {
+ return $this->config;
+ }
+
+ return $this->config[$name] ?? $default;
+ }
+
+ /**
+ * 创建/切换数据库连接查询
+ * @access public
+ * @param string|null $name 连接配置标识
+ * @param bool $force 强制重新连接
+ * @return ConnectionInterface
+ */
+ public function connect(string $name = null, bool $force = false)
+ {
+ return $this->instance($name, $force);
+ }
+
+ /**
+ * 创建数据库连接实例
+ * @access protected
+ * @param string|null $name 连接标识
+ * @param bool $force 强制重新连接
+ * @return ConnectionInterface
+ */
+ protected function instance(string $name = null, bool $force = false): ConnectionInterface
+ {
+ if (empty($name)) {
+ $name = $this->getConfig('default', 'mysql');
+ }
+
+ if ($force || !isset($this->instance[$name])) {
+ $this->instance[$name] = $this->createConnection($name);
+ }
+
+ return $this->instance[$name];
+ }
+
+ /**
+ * 获取连接配置
+ * @param string $name
+ * @return array
+ */
+ protected function getConnectionConfig(string $name): array
+ {
+ $connections = $this->getConfig('connections');
+ if (!isset($connections[$name])) {
+ throw new InvalidArgumentException('Undefined db config:' . $name);
+ }
+
+ return $connections[$name];
+ }
+
+ /**
+ * 创建连接
+ * @param $name
+ * @return ConnectionInterface
+ */
+ protected function createConnection(string $name): ConnectionInterface
+ {
+ $config = $this->getConnectionConfig($name);
+
+ $type = !empty($config['type']) ? $config['type'] : 'mysql';
+
+ if (false !== strpos($type, '\\')) {
+ $class = $type;
+ } else {
+ $class = '\\think\\db\\connector\\' . ucfirst($type);
+ }
+
+ /** @var ConnectionInterface $connection */
+ $connection = new $class($config);
+ $connection->setDb($this);
+
+ if ($this->cache) {
+ $connection->setCache($this->cache);
+ }
+
+ return $connection;
+ }
+
+ /**
+ * 使用表达式设置数据
+ * @access public
+ * @param string $value 表达式
+ * @return Raw
+ */
+ public function raw(string $value): Raw
+ {
+ return new Raw($value);
+ }
+
+ /**
+ * 更新查询次数
+ * @access public
+ * @return void
+ */
+ public function updateQueryTimes(): void
+ {
+ $this->queryTimes++;
+ }
+
+ /**
+ * 重置查询次数
+ * @access public
+ * @return void
+ */
+ public function clearQueryTimes(): void
+ {
+ $this->queryTimes = 0;
+ }
+
+ /**
+ * 获得查询次数
+ * @access public
+ * @return integer
+ */
+ public function getQueryTimes(): int
+ {
+ return $this->queryTimes;
+ }
+
+ /**
+ * 监听SQL执行
+ * @access public
+ * @param callable $callback 回调方法
+ * @return void
+ */
+ public function listen(callable $callback): void
+ {
+ $this->listen[] = $callback;
+ }
+
+ /**
+ * 获取监听SQL执行
+ * @access public
+ * @return array
+ */
+ public function getListen(): array
+ {
+ return $this->listen;
+ }
+
+ /**
+ * 注册回调方法
+ * @access public
+ * @param string $event 事件名
+ * @param callable $callback 回调方法
+ * @return void
+ */
+ public function event(string $event, callable $callback): void
+ {
+ $this->event[$event][] = $callback;
+ }
+
+ /**
+ * 触发事件
+ * @access public
+ * @param string $event 事件名
+ * @param mixed $params 传入参数
+ * @return mixed
+ */
+ public function trigger(string $event, $params = null)
+ {
+ if (isset($this->event[$event])) {
+ foreach ($this->event[$event] as $callback) {
+ call_user_func_array($callback, [$this]);
+ }
+ }
+ }
+
+ public function __call($method, $args)
+ {
+ return call_user_func_array([$this->connect(), $method], $args);
+ }
+}
diff --git a/vendor/topthink/think-orm/src/Model.php b/vendor/topthink/think-orm/src/Model.php
new file mode 100755
index 0000000..fc5dd90
--- /dev/null
+++ b/vendor/topthink/think-orm/src/Model.php
@@ -0,0 +1,1060 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ArrayAccess;
+use Closure;
+use JsonSerializable;
+use think\contract\Arrayable;
+use think\contract\Jsonable;
+use think\db\BaseQuery as Query;
+
+/**
+ * Class Model
+ * @package think
+ * @mixin Query
+ * @method void onAfterRead(Model $model) static after_read事件定义
+ * @method mixed onBeforeInsert(Model $model) static before_insert事件定义
+ * @method void onAfterInsert(Model $model) static after_insert事件定义
+ * @method mixed onBeforeUpdate(Model $model) static before_update事件定义
+ * @method void onAfterUpdate(Model $model) static after_update事件定义
+ * @method mixed onBeforeWrite(Model $model) static before_write事件定义
+ * @method void onAfterWrite(Model $model) static after_write事件定义
+ * @method mixed onBeforeDelete(Model $model) static before_write事件定义
+ * @method void onAfterDelete(Model $model) static after_delete事件定义
+ * @method void onBeforeRestore(Model $model) static before_restore事件定义
+ * @method void onAfterRestore(Model $model) static after_restore事件定义
+ */
+abstract class Model implements JsonSerializable, ArrayAccess, Arrayable, Jsonable
+{
+ use model\concern\Attribute;
+ use model\concern\RelationShip;
+ use model\concern\ModelEvent;
+ use model\concern\TimeStamp;
+ use model\concern\Conversion;
+
+ /**
+ * 数据是否存在
+ * @var bool
+ */
+ private $exists = false;
+
+ /**
+ * 是否强制更新所有数据
+ * @var bool
+ */
+ private $force = false;
+
+ /**
+ * 是否Replace
+ * @var bool
+ */
+ private $replace = false;
+
+ /**
+ * 数据表后缀
+ * @var string
+ */
+ protected $suffix;
+
+ /**
+ * 更新条件
+ * @var array
+ */
+ private $updateWhere;
+
+ /**
+ * 数据库配置
+ * @var string
+ */
+ protected $connection;
+
+ /**
+ * 模型名称
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * 主键值
+ * @var string
+ */
+ protected $key;
+
+ /**
+ * 数据表名称
+ * @var string
+ */
+ protected $table;
+
+ /**
+ * 初始化过的模型.
+ * @var array
+ */
+ protected static $initialized = [];
+
+ /**
+ * 软删除字段默认值
+ * @var mixed
+ */
+ protected $defaultSoftDelete;
+
+ /**
+ * 全局查询范围
+ * @var array
+ */
+ protected $globalScope = [];
+
+ /**
+ * 延迟保存信息
+ * @var bool
+ */
+ private $lazySave = false;
+
+ /**
+ * Db对象
+ * @var DbManager
+ */
+ protected static $db;
+
+ /**
+ * 容器对象的依赖注入方法
+ * @var callable
+ */
+ protected static $invoker;
+
+ /**
+ * 服务注入
+ * @var Closure[]
+ */
+ protected static $maker = [];
+
+ /**
+ * 方法注入
+ * @var Closure[][]
+ */
+ protected static $macro = [];
+
+ /**
+ * 设置服务注入
+ * @access public
+ * @param Closure $maker
+ * @return void
+ */
+ public static function maker(Closure $maker)
+ {
+ static::$maker[] = $maker;
+ }
+
+ /**
+ * 设置方法注入
+ * @access public
+ * @param string $method
+ * @param Closure $closure
+ * @return void
+ */
+ public static function macro(string $method, Closure $closure)
+ {
+ if (!isset(static::$macro[static::class])) {
+ static::$macro[static::class] = [];
+ }
+ static::$macro[static::class][$method] = $closure;
+ }
+
+ /**
+ * 设置Db对象
+ * @access public
+ * @param DbManager $db Db对象
+ * @return void
+ */
+ public static function setDb(DbManager $db)
+ {
+ self::$db = $db;
+ }
+
+ /**
+ * 设置容器对象的依赖注入方法
+ * @access public
+ * @param callable $callable 依赖注入方法
+ * @return void
+ */
+ public static function setInvoker(callable $callable): void
+ {
+ self::$invoker = $callable;
+ }
+
+ /**
+ * 调用反射执行模型方法 支持参数绑定
+ * @access public
+ * @param mixed $method
+ * @param array $vars 参数
+ * @return mixed
+ */
+ public function invoke($method, array $vars = [])
+ {
+ if (self::$invoker) {
+ $call = self::$invoker;
+ return $call($method instanceof Closure ? $method : Closure::fromCallable([$this, $method]), $vars);
+ }
+
+ return call_user_func_array($method instanceof Closure ? $method : [$this, $method], $vars);
+ }
+
+ /**
+ * 架构函数
+ * @access public
+ * @param array $data 数据
+ */
+ public function __construct(array $data = [])
+ {
+ $this->data = $data;
+
+ if (!empty($this->data)) {
+ // 废弃字段
+ foreach ((array) $this->disuse as $key) {
+ if (array_key_exists($key, $this->data)) {
+ unset($this->data[$key]);
+ }
+ }
+ }
+
+ // 记录原始数据
+ $this->origin = $this->data;
+
+ if (empty($this->name)) {
+ // 当前模型名
+ $name = str_replace('\\', '/', static::class);
+ $this->name = basename($name);
+ }
+
+ if (!empty(static::$maker)) {
+ foreach (static::$maker as $maker) {
+ call_user_func($maker, $this);
+ }
+ }
+
+ // 执行初始化操作
+ $this->initialize();
+ }
+
+ /**
+ * 获取当前模型名称
+ * @access public
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * 创建新的模型实例
+ * @access public
+ * @param array $data 数据
+ * @param mixed $where 更新条件
+ * @return Model
+ */
+ public function newInstance(array $data = [], $where = null): Model
+ {
+ $model = new static($data);
+
+ if ($this->connection) {
+ $model->setConnection($this->connection);
+ }
+
+ if ($this->suffix) {
+ $model->setSuffix($this->suffix);
+ }
+
+ if (empty($data)) {
+ return $model;
+ }
+
+ $model->exists(true);
+
+ $model->setUpdateWhere($where);
+
+ $model->trigger('AfterRead');
+
+ return $model;
+ }
+
+ /**
+ * 设置模型的更新条件
+ * @access protected
+ * @param mixed $where 更新条件
+ * @return void
+ */
+ protected function setUpdateWhere($where): void
+ {
+ $this->updateWhere = $where;
+ }
+
+ /**
+ * 设置当前模型的数据库连接
+ * @access public
+ * @param string $connection 数据表连接标识
+ * @return $this
+ */
+ public function setConnection(string $connection)
+ {
+ $this->connection = $connection;
+ return $this;
+ }
+
+ /**
+ * 获取当前模型的数据库连接标识
+ * @access public
+ * @return string
+ */
+ public function getConnection(): string
+ {
+ return $this->connection ?: '';
+ }
+
+ /**
+ * 设置当前模型数据表的后缀
+ * @access public
+ * @param string $suffix 数据表后缀
+ * @return $this
+ */
+ public function setSuffix(string $suffix)
+ {
+ $this->suffix = $suffix;
+ return $this;
+ }
+
+ /**
+ * 获取当前模型的数据表后缀
+ * @access public
+ * @return string
+ */
+ public function getSuffix(): string
+ {
+ return $this->suffix ?: '';
+ }
+
+ /**
+ * 获取当前模型的数据库查询对象
+ * @access public
+ * @param array $scope 设置不使用的全局查询范围
+ * @return Query
+ */
+ public function db($scope = []): Query
+ {
+ /** @var Query $query */
+ $query = self::$db->connect($this->connection)
+ ->name($this->name . $this->suffix)
+ ->pk($this->pk);
+
+ if (!empty($this->table)) {
+ $query->table($this->table . $this->suffix);
+ }
+
+ $query->model($this)
+ ->json($this->json, $this->jsonAssoc)
+ ->setFieldType(array_merge($this->schema, $this->jsonType));
+
+ // 软删除
+ if (property_exists($this, 'withTrashed') && !$this->withTrashed) {
+ $this->withNoTrashed($query);
+ }
+
+ // 全局作用域
+ if (is_array($scope)) {
+ $globalScope = array_diff($this->globalScope, $scope);
+ $query->scope($globalScope);
+ }
+
+ // 返回当前模型的数据库查询对象
+ return $query;
+ }
+
+ /**
+ * 初始化模型
+ * @access private
+ * @return void
+ */
+ private function initialize(): void
+ {
+ if (!isset(static::$initialized[static::class])) {
+ static::$initialized[static::class] = true;
+ static::init();
+ }
+ }
+
+ /**
+ * 初始化处理
+ * @access protected
+ * @return void
+ */
+ protected static function init()
+ {
+ }
+
+ protected function checkData(): void
+ {
+ }
+
+ protected function checkResult($result): void
+ {
+ }
+
+ /**
+ * 更新是否强制写入数据 而不做比较(亦可用于软删除的强制删除)
+ * @access public
+ * @param bool $force
+ * @return $this
+ */
+ public function force(bool $force = true)
+ {
+ $this->force = $force;
+ return $this;
+ }
+
+ /**
+ * 判断force
+ * @access public
+ * @return bool
+ */
+ public function isForce(): bool
+ {
+ return $this->force;
+ }
+
+ /**
+ * 新增数据是否使用Replace
+ * @access public
+ * @param bool $replace
+ * @return $this
+ */
+ public function replace(bool $replace = true)
+ {
+ $this->replace = $replace;
+ return $this;
+ }
+
+ /**
+ * 刷新模型数据
+ * @access public
+ * @param bool $relation 是否刷新关联数据
+ * @return $this
+ */
+ public function refresh(bool $relation = false)
+ {
+ if ($this->exists) {
+ $this->data = $this->db()->find($this->getKey())->getData();
+ $this->origin = $this->data;
+
+ if ($relation) {
+ $this->relation = [];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置数据是否存在
+ * @access public
+ * @param bool $exists
+ * @return $this
+ */
+ public function exists(bool $exists = true)
+ {
+ $this->exists = $exists;
+ return $this;
+ }
+
+ /**
+ * 判断数据是否存在数据库
+ * @access public
+ * @return bool
+ */
+ public function isExists(): bool
+ {
+ return $this->exists;
+ }
+
+ /**
+ * 判断模型是否为空
+ * @access public
+ * @return bool
+ */
+ public function isEmpty(): bool
+ {
+ return empty($this->data);
+ }
+
+ /**
+ * 延迟保存当前数据对象
+ * @access public
+ * @param array|bool $data 数据
+ * @return void
+ */
+ public function lazySave($data = []): void
+ {
+ if (false === $data) {
+ $this->lazySave = false;
+ } else {
+ if (is_array($data)) {
+ $this->setAttrs($data);
+ }
+
+ $this->lazySave = true;
+ }
+ }
+
+ /**
+ * 保存当前数据对象
+ * @access public
+ * @param array $data 数据
+ * @param string $sequence 自增序列名
+ * @return bool
+ */
+ public function save(array $data = [], string $sequence = null): bool
+ {
+ // 数据对象赋值
+ $this->setAttrs($data);
+
+ if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {
+ return false;
+ }
+
+ $result = $this->exists ? $this->updateData() : $this->insertData($sequence);
+
+ if (false === $result) {
+ return false;
+ }
+
+ // 写入回调
+ $this->trigger('AfterWrite');
+
+ // 重新记录原始数据
+ $this->origin = $this->data;
+ $this->set = [];
+ $this->lazySave = false;
+
+ return true;
+ }
+
+ /**
+ * 检查数据是否允许写入
+ * @access protected
+ * @return array
+ */
+ protected function checkAllowFields(): array
+ {
+ // 检测字段
+ if (empty($this->field)) {
+ if (!empty($this->schema)) {
+ $this->field = array_keys(array_merge($this->schema, $this->jsonType));
+ } else {
+ $query = $this->db();
+ $table = $this->table ? $this->table . $this->suffix : $query->getTable();
+
+ $this->field = $query->getConnection()->getTableFields($table);
+ }
+
+ return $this->field;
+ }
+
+ $field = $this->field;
+
+ if ($this->autoWriteTimestamp) {
+ array_push($field, $this->createTime, $this->updateTime);
+ }
+
+ if (!empty($this->disuse)) {
+ // 废弃字段
+ $field = array_diff($field, $this->disuse);
+ }
+
+ return $field;
+ }
+
+ /**
+ * 保存写入数据
+ * @access protected
+ * @return bool
+ */
+ protected function updateData(): bool
+ {
+ // 事件回调
+ if (false === $this->trigger('BeforeUpdate')) {
+ return false;
+ }
+
+ $this->checkData();
+
+ // 获取有更新的数据
+ $data = $this->getChangedData();
+
+ if (empty($data)) {
+ // 关联更新
+ if (!empty($this->relationWrite)) {
+ $this->autoRelationUpdate();
+ }
+
+ return true;
+ }
+
+ if ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) {
+ // 自动写入更新时间
+ $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime);
+ $this->data[$this->updateTime] = $data[$this->updateTime];
+ }
+
+ // 检查允许字段
+ $allowFields = $this->checkAllowFields();
+
+ foreach ($this->relationWrite as $name => $val) {
+ if (!is_array($val)) {
+ continue;
+ }
+
+ foreach ($val as $key) {
+ if (isset($data[$key])) {
+ unset($data[$key]);
+ }
+ }
+ }
+
+ // 模型更新
+ $db = $this->db();
+
+ $db->transaction(function () use ($data, $allowFields, $db) {
+ $this->key = null;
+ $where = $this->getWhere();
+
+ $result = $db->where($where)
+ ->strict(false)
+ ->cache(true)
+ ->setOption('key', $this->key)
+ ->field($allowFields)
+ ->update($data);
+
+ $this->checkResult($result);
+
+ // 关联更新
+ if (!empty($this->relationWrite)) {
+ $this->autoRelationUpdate();
+ }
+ });
+
+ // 更新回调
+ $this->trigger('AfterUpdate');
+
+ return true;
+ }
+
+ /**
+ * 新增写入数据
+ * @access protected
+ * @param string $sequence 自增名
+ * @return bool
+ */
+ protected function insertData(string $sequence = null): bool
+ {
+ // 时间戳自动写入
+ if ($this->autoWriteTimestamp) {
+ if ($this->createTime && !isset($this->data[$this->createTime])) {
+ $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime);
+ }
+
+ if ($this->updateTime && !isset($this->data[$this->updateTime])) {
+ $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime);
+ }
+ }
+
+ if (false === $this->trigger('BeforeInsert')) {
+ return false;
+ }
+
+ $this->checkData();
+
+ // 检查允许字段
+ $allowFields = $this->checkAllowFields();
+
+ $db = $this->db();
+
+ $db->transaction(function () use ($sequence, $allowFields, $db) {
+ $result = $db->strict(false)
+ ->field($allowFields)
+ ->replace($this->replace)
+ ->sequence($sequence)
+ ->insert($this->data, true);
+
+ // 获取自动增长主键
+ if ($result) {
+ $pk = $this->getPk();
+
+ if (is_string($pk) && (!isset($this->data[$pk]) || '' == $this->data[$pk])) {
+ $this->data[$pk] = $result;
+ }
+ }
+
+ // 关联写入
+ if (!empty($this->relationWrite)) {
+ $this->autoRelationInsert();
+ }
+ });
+
+ // 标记数据已经存在
+ $this->exists = true;
+
+ // 新增回调
+ $this->trigger('AfterInsert');
+
+ return true;
+ }
+
+ /**
+ * 获取当前的更新条件
+ * @access public
+ * @return mixed
+ */
+ public function getWhere()
+ {
+ $pk = $this->getPk();
+
+ if (is_string($pk) && isset($this->origin[$pk])) {
+ $where = [[$pk, '=', $this->origin[$pk]]];
+ $this->key = $this->origin[$pk];
+ } elseif (is_array($pk)) {
+ foreach ($pk as $field) {
+ if (isset($this->origin[$field])) {
+ $where[] = [$field, '=', $this->origin[$field]];
+ }
+ }
+ }
+
+ if (empty($where)) {
+ $where = empty($this->updateWhere) ? null : $this->updateWhere;
+ }
+
+ return $where;
+ }
+
+ /**
+ * 保存多个数据到当前数据对象
+ * @access public
+ * @param iterable $dataSet 数据
+ * @param boolean $replace 是否自动识别更新和写入
+ * @return Collection
+ * @throws \Exception
+ */
+ public function saveAll(iterable $dataSet, bool $replace = true): Collection
+ {
+ $db = $this->db();
+
+ $result = $db->transaction(function () use ($replace, $dataSet) {
+
+ $pk = $this->getPk();
+
+ if (is_string($pk) && $replace) {
+ $auto = true;
+ }
+
+ $result = [];
+
+ $suffix = $this->getSuffix();
+
+ foreach ($dataSet as $key => $data) {
+ if ($this->exists || (!empty($auto) && isset($data[$pk]))) {
+ $result[$key] = static::update($data, [], [], $suffix);
+ } else {
+ $result[$key] = static::create($data, $this->field, $this->replace, $suffix);
+ }
+ }
+
+ return $result;
+ });
+
+ return $this->toCollection($result);
+ }
+
+ /**
+ * 删除当前的记录
+ * @access public
+ * @return bool
+ */
+ public function delete(): bool
+ {
+ if (!$this->exists || $this->isEmpty() || false === $this->trigger('BeforeDelete')) {
+ return false;
+ }
+
+ // 读取更新条件
+ $where = $this->getWhere();
+
+ $db = $this->db();
+
+ $db->transaction(function () use ($where, $db) {
+ // 删除当前模型数据
+ $db->where($where)->delete();
+
+ // 关联删除
+ if (!empty($this->relationWrite)) {
+ $this->autoRelationDelete();
+ }
+ });
+
+ $this->trigger('AfterDelete');
+
+ $this->exists = false;
+ $this->lazySave = false;
+
+ return true;
+ }
+
+ /**
+ * 写入数据
+ * @access public
+ * @param array $data 数据数组
+ * @param array $allowField 允许字段
+ * @param bool $replace 使用Replace
+ * @param string $suffix 数据表后缀
+ * @return static
+ */
+ public static function create(array $data, array $allowField = [], bool $replace = false, string $suffix = ''): Model
+ {
+ $model = new static();
+
+ if (!empty($allowField)) {
+ $model->allowField($allowField);
+ }
+
+ if (!empty($suffix)) {
+ $model->setSuffix($suffix);
+ }
+
+ $model->replace($replace)->save($data);
+
+ return $model;
+ }
+
+ /**
+ * 更新数据
+ * @access public
+ * @param array $data 数据数组
+ * @param mixed $where 更新条件
+ * @param array $allowField 允许字段
+ * @param string $suffix 数据表后缀
+ * @return static
+ */
+ public static function update(array $data, $where = [], array $allowField = [], string $suffix = '')
+ {
+ $model = new static();
+
+ if (!empty($allowField)) {
+ $model->allowField($allowField);
+ }
+
+ if (!empty($where)) {
+ $model->setUpdateWhere($where);
+ }
+
+ if (!empty($suffix)) {
+ $model->setSuffix($suffix);
+ }
+
+ $model->exists(true)->save($data);
+
+ return $model;
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param mixed $data 主键列表 支持闭包查询条件
+ * @param bool $force 是否强制删除
+ * @return bool
+ */
+ public static function destroy($data, bool $force = false): bool
+ {
+ if (empty($data) && 0 !== $data) {
+ return false;
+ }
+
+ $model = new static();
+
+ $query = $model->db();
+
+ if (is_array($data) && key($data) !== 0) {
+ $query->where($data);
+ $data = null;
+ } elseif ($data instanceof \Closure) {
+ $data($query);
+ $data = null;
+ }
+
+ $resultSet = $query->select($data);
+
+ foreach ($resultSet as $result) {
+ $result->force($force)->delete();
+ }
+
+ return true;
+ }
+
+ /**
+ * 解序列化后处理
+ */
+ public function __wakeup()
+ {
+ $this->initialize();
+ }
+
+ /**
+ * 修改器 设置数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @param mixed $value 值
+ * @return void
+ */
+ public function __set(string $name, $value): void
+ {
+ $this->setAttr($name, $value);
+ }
+
+ /**
+ * 获取器 获取数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return mixed
+ */
+ public function __get(string $name)
+ {
+ return $this->getAttr($name);
+ }
+
+ /**
+ * 检测数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return bool
+ */
+ public function __isset(string $name): bool
+ {
+ return !is_null($this->getAttr($name));
+ }
+
+ /**
+ * 销毁数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return void
+ */
+ public function __unset(string $name): void
+ {
+ unset($this->data[$name], $this->relation[$name]);
+ }
+
+ // ArrayAccess
+ public function offsetSet($name, $value)
+ {
+ $this->setAttr($name, $value);
+ }
+
+ public function offsetExists($name): bool
+ {
+ return $this->__isset($name);
+ }
+
+ public function offsetUnset($name)
+ {
+ $this->__unset($name);
+ }
+
+ public function offsetGet($name)
+ {
+ return $this->getAttr($name);
+ }
+
+ /**
+ * 设置不使用的全局查询范围
+ * @access public
+ * @param array $scope 不启用的全局查询范围
+ * @return Query
+ */
+ public static function withoutGlobalScope(array $scope = null)
+ {
+ $model = new static();
+
+ return $model->db($scope);
+ }
+
+ /**
+ * 切换后缀进行查询
+ * @access public
+ * @param string $suffix 切换的表后缀
+ * @return Model
+ */
+ public static function suffix(string $suffix)
+ {
+ $model = new static();
+ $model->setSuffix($suffix);
+
+ return $model;
+ }
+
+ /**
+ * 切换数据库连接进行查询
+ * @access public
+ * @param string $connection 数据库连接标识
+ * @return Model
+ */
+ public static function connect(string $connection)
+ {
+ $model = new static();
+ $model->setConnection($connection);
+
+ return $model;
+ }
+
+ public function __call($method, $args)
+ {
+ if (isset(static::$macro[static::class][$method])) {
+ return call_user_func_array(static::$macro[static::class][$method]->bindTo($this, static::class), $args);
+ }
+
+ if ('withattr' == strtolower($method)) {
+ return call_user_func_array([$this, 'withAttribute'], $args);
+ }
+
+ return call_user_func_array([$this->db(), $method], $args);
+ }
+
+ public static function __callStatic($method, $args)
+ {
+ if (isset(static::$macro[static::class][$method])) {
+ return call_user_func_array(static::$macro[static::class][$method]->bindTo(null, static::class), $args);
+ }
+
+ $model = new static();
+
+ return call_user_func_array([$model->db(), $method], $args);
+ }
+
+ /**
+ * 析构方法
+ * @access public
+ */
+ public function __destruct()
+ {
+ if ($this->lazySave) {
+ $this->save();
+ }
+ }
+}
diff --git a/vendor/topthink/think-orm/src/Paginator.php b/vendor/topthink/think-orm/src/Paginator.php
new file mode 100755
index 0000000..f5d8006
--- /dev/null
+++ b/vendor/topthink/think-orm/src/Paginator.php
@@ -0,0 +1,518 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ArrayAccess;
+use ArrayIterator;
+use Closure;
+use Countable;
+use DomainException;
+use IteratorAggregate;
+use JsonSerializable;
+use think\paginator\driver\Bootstrap;
+use Traversable;
+
+/**
+ * 分页基础类
+ * @mixin Collection
+ */
+abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
+{
+ /**
+ * 是否简洁模式
+ * @var bool
+ */
+ protected $simple = false;
+
+ /**
+ * 数据集
+ * @var Collection
+ */
+ protected $items;
+
+ /**
+ * 当前页
+ * @var int
+ */
+ protected $currentPage;
+
+ /**
+ * 最后一页
+ * @var int
+ */
+ protected $lastPage;
+
+ /**
+ * 数据总数
+ * @var integer|null
+ */
+ protected $total;
+
+ /**
+ * 每页数量
+ * @var int
+ */
+ protected $listRows;
+
+ /**
+ * 是否有下一页
+ * @var bool
+ */
+ protected $hasMore;
+
+ /**
+ * 分页配置
+ * @var array
+ */
+ protected $options = [
+ 'var_page' => 'page',
+ 'path' => '/',
+ 'query' => [],
+ 'fragment' => '',
+ ];
+
+ /**
+ * 获取当前页码
+ * @var Closure
+ */
+ protected static $currentPageResolver;
+
+ /**
+ * 获取当前路径
+ * @var Closure
+ */
+ protected static $currentPathResolver;
+
+ /**
+ * @var Closure
+ */
+ protected static $maker;
+
+ public function __construct($items, int $listRows, int $currentPage = 1, int $total = null, bool $simple = false, array $options = [])
+ {
+ $this->options = array_merge($this->options, $options);
+
+ $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path'];
+
+ $this->simple = $simple;
+ $this->listRows = $listRows;
+
+ if (!$items instanceof Collection) {
+ $items = Collection::make($items);
+ }
+
+ if ($simple) {
+ $this->currentPage = $this->setCurrentPage($currentPage);
+ $this->hasMore = count($items) > ($this->listRows);
+ $items = $items->slice(0, $this->listRows);
+ } else {
+ $this->total = $total;
+ $this->lastPage = (int) ceil($total / $listRows);
+ $this->currentPage = $this->setCurrentPage($currentPage);
+ $this->hasMore = $this->currentPage < $this->lastPage;
+ }
+ $this->items = $items;
+ }
+
+ /**
+ * @access public
+ * @param mixed $items
+ * @param int $listRows
+ * @param int $currentPage
+ * @param int $total
+ * @param bool $simple
+ * @param array $options
+ * @return Paginator
+ */
+ public static function make($items, int $listRows, int $currentPage = 1, int $total = null, bool $simple = false, array $options = [])
+ {
+ if (isset(static::$maker)) {
+ return call_user_func(static::$maker, $items, $listRows, $currentPage, $total, $simple, $options);
+ }
+
+ return new Bootstrap($items, $listRows, $currentPage, $total, $simple, $options);
+ }
+
+ public static function maker(Closure $resolver)
+ {
+ static::$maker = $resolver;
+ }
+
+ protected function setCurrentPage(int $currentPage): int
+ {
+ if (!$this->simple && $currentPage > $this->lastPage) {
+ return $this->lastPage > 0 ? $this->lastPage : 1;
+ }
+
+ return $currentPage;
+ }
+
+ /**
+ * 获取页码对应的链接
+ *
+ * @access protected
+ * @param int $page
+ * @return string
+ */
+ protected function url(int $page): string
+ {
+ if ($page <= 0) {
+ $page = 1;
+ }
+
+ if (strpos($this->options['path'], '[PAGE]') === false) {
+ $parameters = [$this->options['var_page'] => $page];
+ $path = $this->options['path'];
+ } else {
+ $parameters = [];
+ $path = str_replace('[PAGE]', $page, $this->options['path']);
+ }
+
+ if (count($this->options['query']) > 0) {
+ $parameters = array_merge($this->options['query'], $parameters);
+ }
+
+ $url = $path;
+ if (!empty($parameters)) {
+ $url .= '?' . http_build_query($parameters, '', '&');
+ }
+
+ return $url . $this->buildFragment();
+ }
+
+ /**
+ * 自动获取当前页码
+ * @access public
+ * @param string $varPage
+ * @param int $default
+ * @return int
+ */
+ public static function getCurrentPage(string $varPage = 'page', int $default = 1): int
+ {
+ if (isset(static::$currentPageResolver)) {
+ return call_user_func(static::$currentPageResolver, $varPage);
+ }
+
+ return $default;
+ }
+
+ /**
+ * 设置获取当前页码闭包
+ * @param Closure $resolver
+ */
+ public static function currentPageResolver(Closure $resolver)
+ {
+ static::$currentPageResolver = $resolver;
+ }
+
+ /**
+ * 自动获取当前的path
+ * @access public
+ * @param string $default
+ * @return string
+ */
+ public static function getCurrentPath($default = '/'): string
+ {
+ if (isset(static::$currentPathResolver)) {
+ return call_user_func(static::$currentPathResolver);
+ }
+
+ return $default;
+ }
+
+ /**
+ * 设置获取当前路径闭包
+ * @param Closure $resolver
+ */
+ public static function currentPathResolver(Closure $resolver)
+ {
+ static::$currentPathResolver = $resolver;
+ }
+
+ /**
+ * 获取数据总条数
+ * @return int
+ */
+ public function total(): int
+ {
+ if ($this->simple) {
+ throw new DomainException('not support total');
+ }
+
+ return $this->total;
+ }
+
+ /**
+ * 获取每页数量
+ * @return int
+ */
+ public function listRows(): int
+ {
+ return $this->listRows;
+ }
+
+ /**
+ * 获取当前页页码
+ * @return int
+ */
+ public function currentPage(): int
+ {
+ return $this->currentPage;
+ }
+
+ /**
+ * 获取最后一页页码
+ * @return int
+ */
+ public function lastPage(): int
+ {
+ if ($this->simple) {
+ throw new DomainException('not support last');
+ }
+
+ return $this->lastPage;
+ }
+
+ /**
+ * 数据是否足够分页
+ * @access public
+ * @return bool
+ */
+ public function hasPages(): bool
+ {
+ return !(1 == $this->currentPage && !$this->hasMore);
+ }
+
+ /**
+ * 创建一组分页链接
+ *
+ * @access public
+ * @param int $start
+ * @param int $end
+ * @return array
+ */
+ public function getUrlRange(int $start, int $end): array
+ {
+ $urls = [];
+
+ for ($page = $start; $page <= $end; $page++) {
+ $urls[$page] = $this->url($page);
+ }
+
+ return $urls;
+ }
+
+ /**
+ * 设置URL锚点
+ *
+ * @access public
+ * @param string|null $fragment
+ * @return $this
+ */
+ public function fragment(string $fragment = null)
+ {
+ $this->options['fragment'] = $fragment;
+
+ return $this;
+ }
+
+ /**
+ * 添加URL参数
+ *
+ * @access public
+ * @param array $append
+ * @return $this
+ */
+ public function appends(array $append)
+ {
+ foreach ($append as $k => $v) {
+ if ($k !== $this->options['var_page']) {
+ $this->options['query'][$k] = $v;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 构造锚点字符串
+ *
+ * @access public
+ * @return string
+ */
+ protected function buildFragment(): string
+ {
+ return $this->options['fragment'] ? '#' . $this->options['fragment'] : '';
+ }
+
+ /**
+ * 渲染分页html
+ * @access public
+ * @return mixed
+ */
+ abstract public function render();
+
+ public function items()
+ {
+ return $this->items->all();
+ }
+
+ /**
+ * 获取数据集
+ *
+ * @return Collection|\think\model\Collection
+ */
+ public function getCollection()
+ {
+ return $this->items;
+ }
+
+ public function isEmpty(): bool
+ {
+ return $this->items->isEmpty();
+ }
+
+ /**
+ * 给每个元素执行个回调
+ *
+ * @access public
+ * @param callable $callback
+ * @return $this
+ */
+ public function each(callable $callback)
+ {
+ foreach ($this->items as $key => $item) {
+ $result = $callback($item, $key);
+
+ if (false === $result) {
+ break;
+ } elseif (!is_object($item)) {
+ $this->items[$key] = $result;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve an external iterator
+ * @access public
+ * @return Traversable An instance of an object implementing <b>Iterator</b> or
+ * <b>Traversable</b>
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->items->all());
+ }
+
+ /**
+ * Whether a offset exists
+ * @access public
+ * @param mixed $offset
+ * @return bool
+ */
+ public function offsetExists($offset)
+ {
+ return $this->items->offsetExists($offset);
+ }
+
+ /**
+ * Offset to retrieve
+ * @access public
+ * @param mixed $offset
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ return $this->items->offsetGet($offset);
+ }
+
+ /**
+ * Offset to set
+ * @access public
+ * @param mixed $offset
+ * @param mixed $value
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->items->offsetSet($offset, $value);
+ }
+
+ /**
+ * Offset to unset
+ * @access public
+ * @param mixed $offset
+ * @return void
+ * @since 5.0.0
+ */
+ public function offsetUnset($offset)
+ {
+ $this->items->offsetUnset($offset);
+ }
+
+ /**
+ * 统计数据集条数
+ * @return int
+ */
+ public function count(): int
+ {
+ return $this->items->count();
+ }
+
+ public function __toString()
+ {
+ return (string) $this->render();
+ }
+
+ /**
+ * 转换为数组
+ * @return array
+ */
+ public function toArray(): array
+ {
+ try {
+ $total = $this->total();
+ } catch (DomainException $e) {
+ $total = null;
+ }
+
+ return [
+ 'total' => $total,
+ 'per_page' => $this->listRows(),
+ 'current_page' => $this->currentPage(),
+ 'last_page' => $this->lastPage,
+ 'data' => $this->items->toArray(),
+ ];
+ }
+
+ /**
+ * Specify data which should be serialized to JSON
+ */
+ public function jsonSerialize()
+ {
+ return $this->toArray();
+ }
+
+ public function __call($name, $arguments)
+ {
+ $result = call_user_func_array([$this->items, $name], $arguments);
+
+ if ($result instanceof Collection) {
+ $this->items = $result;
+ return $this;
+ }
+
+ return $result;
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/BaseQuery.php b/vendor/topthink/think-orm/src/db/BaseQuery.php
new file mode 100755
index 0000000..65b2d2d
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/BaseQuery.php
@@ -0,0 +1,1282 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use think\Collection;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\DbException as Exception;
+use think\db\exception\ModelNotFoundException;
+use think\helper\Str;
+use think\Model;
+use think\Paginator;
+
+/**
+ * 数据查询基础类
+ */
+abstract class BaseQuery
+{
+ use concern\TimeFieldQuery;
+ use concern\AggregateQuery;
+ use concern\ModelRelationQuery;
+ use concern\ResultOperation;
+ use concern\Transaction;
+ use concern\WhereQuery;
+
+ /**
+ * 当前数据库连接对象
+ * @var Connection
+ */
+ protected $connection;
+
+ /**
+ * 当前数据表名称(不含前缀)
+ * @var string
+ */
+ protected $name = '';
+
+ /**
+ * 当前数据表主键
+ * @var string|array
+ */
+ protected $pk;
+
+ /**
+ * 当前数据表自增主键
+ * @var string
+ */
+ protected $autoinc;
+
+ /**
+ * 当前数据表前缀
+ * @var string
+ */
+ protected $prefix = '';
+
+ /**
+ * 当前查询参数
+ * @var array
+ */
+ protected $options = [];
+
+ /**
+ * 架构函数
+ * @access public
+ * @param ConnectionInterface $connection 数据库连接对象
+ */
+ public function __construct(ConnectionInterface $connection)
+ {
+ $this->connection = $connection;
+
+ $this->prefix = $this->connection->getConfig('prefix');
+ }
+
+ /**
+ * 利用__call方法实现一些特殊的Model方法
+ * @access public
+ * @param string $method 方法名称
+ * @param array $args 调用参数
+ * @return mixed
+ * @throws Exception
+ */
+ public function __call(string $method, array $args)
+ {
+ if (strtolower(substr($method, 0, 5)) == 'getby') {
+ // 根据某个字段获取记录
+ $field = Str::snake(substr($method, 5));
+ return $this->where($field, '=', $args[0])->find();
+ } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
+ // 根据某个字段获取记录的某个值
+ $name = Str::snake(substr($method, 10));
+ return $this->where($name, '=', $args[0])->value($args[1]);
+ } elseif (strtolower(substr($method, 0, 7)) == 'whereor') {
+ $name = Str::snake(substr($method, 7));
+ array_unshift($args, $name);
+ return call_user_func_array([$this, 'whereOr'], $args);
+ } elseif (strtolower(substr($method, 0, 5)) == 'where') {
+ $name = Str::snake(substr($method, 5));
+ array_unshift($args, $name);
+ return call_user_func_array([$this, 'where'], $args);
+ } elseif ($this->model && method_exists($this->model, 'scope' . $method)) {
+ // 动态调用命名范围
+ $method = 'scope' . $method;
+ array_unshift($args, $this);
+
+ call_user_func_array([$this->model, $method], $args);
+ return $this;
+ } else {
+ throw new Exception('method not exist:' . static::class . '->' . $method);
+ }
+ }
+
+ /**
+ * 创建一个新的查询对象
+ * @access public
+ * @return BaseQuery
+ */
+ public function newQuery(): BaseQuery
+ {
+ $query = new static($this->connection);
+
+ if ($this->model) {
+ $query->model($this->model);
+ }
+
+ if (isset($this->options['table'])) {
+ $query->table($this->options['table']);
+ } else {
+ $query->name($this->name);
+ }
+
+ if (isset($this->options['json'])) {
+ $query->json($this->options['json'], $this->options['json_assoc']);
+ }
+
+ if (isset($this->options['field_type'])) {
+ $query->setFieldType($this->options['field_type']);
+ }
+
+ return $query;
+ }
+
+ /**
+ * 获取当前的数据库Connection对象
+ * @access public
+ * @return ConnectionInterface
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ /**
+ * 指定当前数据表名(不含前缀)
+ * @access public
+ * @param string $name 不含前缀的数据表名字
+ * @return $this
+ */
+ public function name(string $name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * 获取当前的数据表名称
+ * @access public
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name ?: $this->model->getName();
+ }
+
+ /**
+ * 获取数据库的配置参数
+ * @access public
+ * @param string $name 参数名称
+ * @return mixed
+ */
+ public function getConfig(string $name = '')
+ {
+ return $this->connection->getConfig($name);
+ }
+
+ /**
+ * 得到当前或者指定名称的数据表
+ * @access public
+ * @param string $name 不含前缀的数据表名字
+ * @return mixed
+ */
+ public function getTable(string $name = '')
+ {
+ if (empty($name) && isset($this->options['table'])) {
+ return $this->options['table'];
+ }
+
+ $name = $name ?: $this->name;
+
+ return $this->prefix . Str::snake($name);
+ }
+
+ /**
+ * 设置字段类型信息
+ * @access public
+ * @param array $type 字段类型信息
+ * @return $this
+ */
+ public function setFieldType(array $type)
+ {
+ $this->options['field_type'] = $type;
+ return $this;
+ }
+
+ /**
+ * 获取最近一次查询的sql语句
+ * @access public
+ * @return string
+ */
+ public function getLastSql(): string
+ {
+ return $this->connection->getLastSql();
+ }
+
+ /**
+ * 获取返回或者影响的记录数
+ * @access public
+ * @return integer
+ */
+ public function getNumRows(): int
+ {
+ return $this->connection->getNumRows();
+ }
+
+ /**
+ * 获取最近插入的ID
+ * @access public
+ * @param string $sequence 自增序列名
+ * @return mixed
+ */
+ public function getLastInsID(string $sequence = null)
+ {
+ return $this->connection->getLastInsID($this, $sequence);
+ }
+
+ /**
+ * 得到某个字段的值
+ * @access public
+ * @param string $field 字段名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function value(string $field, $default = null)
+ {
+ return $this->connection->value($this, $field, $default);
+ }
+
+ /**
+ * 得到某个列的数组
+ * @access public
+ * @param string $field 字段名 多个字段用逗号分隔
+ * @param string $key 索引
+ * @return array
+ */
+ public function column(string $field, string $key = ''): array
+ {
+ return $this->connection->column($this, $field, $key);
+ }
+
+ /**
+ * 查询SQL组装 union
+ * @access public
+ * @param mixed $union UNION
+ * @param boolean $all 是否适用UNION ALL
+ * @return $this
+ */
+ public function union($union, bool $all = false)
+ {
+ $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION';
+
+ if (is_array($union)) {
+ $this->options['union'] = array_merge($this->options['union'], $union);
+ } else {
+ $this->options['union'][] = $union;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 查询SQL组装 union all
+ * @access public
+ * @param mixed $union UNION数据
+ * @return $this
+ */
+ public function unionAll($union)
+ {
+ return $this->union($union, true);
+ }
+
+ /**
+ * 指定查询字段
+ * @access public
+ * @param mixed $field 字段信息
+ * @return $this
+ */
+ public function field($field)
+ {
+ if (empty($field)) {
+ return $this;
+ } elseif ($field instanceof Raw) {
+ $this->options['field'][] = $field;
+ return $this;
+ }
+
+ if (is_string($field)) {
+ if (preg_match('/[\<\'\"\(]/', $field)) {
+ return $this->fieldRaw($field);
+ }
+
+ $field = array_map('trim', explode(',', $field));
+ }
+
+ if (true === $field) {
+ // 获取全部字段
+ $fields = $this->getTableFields();
+ $field = $fields ?: ['*'];
+ }
+
+ if (isset($this->options['field'])) {
+ $field = array_merge((array) $this->options['field'], $field);
+ }
+
+ $this->options['field'] = array_unique($field);
+
+ return $this;
+ }
+
+ /**
+ * 指定要排除的查询字段
+ * @access public
+ * @param array|string $field 要排除的字段
+ * @return $this
+ */
+ public function withoutField($field)
+ {
+ if (empty($field)) {
+ return $this;
+ }
+
+ if (is_string($field)) {
+ $field = array_map('trim', explode(',', $field));
+ }
+
+ // 字段排除
+ $fields = $this->getTableFields();
+ $field = $fields ? array_diff($fields, $field) : $field;
+
+ if (isset($this->options['field'])) {
+ $field = array_merge((array) $this->options['field'], $field);
+ }
+
+ $this->options['field'] = array_unique($field);
+
+ return $this;
+ }
+
+ /**
+ * 指定其它数据表的查询字段
+ * @access public
+ * @param mixed $field 字段信息
+ * @param string $tableName 数据表名
+ * @param string $prefix 字段前缀
+ * @param string $alias 别名前缀
+ * @return $this
+ */
+ public function tableField($field, string $tableName, string $prefix = '', string $alias = '')
+ {
+ if (empty($field)) {
+ return $this;
+ }
+
+ if (is_string($field)) {
+ $field = array_map('trim', explode(',', $field));
+ }
+
+ if (true === $field) {
+ // 获取全部字段
+ $fields = $this->getTableFields($tableName);
+ $field = $fields ?: ['*'];
+ }
+
+ // 添加统一的前缀
+ $prefix = $prefix ?: $tableName;
+ foreach ($field as $key => &$val) {
+ if (is_numeric($key) && $alias) {
+ $field[$prefix . '.' . $val] = $alias . $val;
+ unset($field[$key]);
+ } elseif (is_numeric($key)) {
+ $val = $prefix . '.' . $val;
+ }
+ }
+
+ if (isset($this->options['field'])) {
+ $field = array_merge((array) $this->options['field'], $field);
+ }
+
+ $this->options['field'] = array_unique($field);
+
+ return $this;
+ }
+
+ /**
+ * 设置数据
+ * @access public
+ * @param array $data 数据
+ * @return $this
+ */
+ public function data(array $data)
+ {
+ $this->options['data'] = $data;
+
+ return $this;
+ }
+
+ /**
+ * 去除查询参数
+ * @access public
+ * @param string $option 参数名 留空去除所有参数
+ * @return $this
+ */
+ public function removeOption(string $option = '')
+ {
+ if ('' === $option) {
+ $this->options = [];
+ $this->bind = [];
+ } elseif (isset($this->options[$option])) {
+ unset($this->options[$option]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 指定查询数量
+ * @access public
+ * @param int $offset 起始位置
+ * @param int $length 查询数量
+ * @return $this
+ */
+ public function limit(int $offset, int $length = null)
+ {
+ $this->options['limit'] = $offset . ($length ? ',' . $length : '');
+
+ return $this;
+ }
+
+ /**
+ * 指定分页
+ * @access public
+ * @param int $page 页数
+ * @param int $listRows 每页数量
+ * @return $this
+ */
+ public function page(int $page, int $listRows = null)
+ {
+ $this->options['page'] = [$page, $listRows];
+
+ return $this;
+ }
+
+ /**
+ * 指定当前操作的数据表
+ * @access public
+ * @param mixed $table 表名
+ * @return $this
+ */
+ public function table($table)
+ {
+ if (is_string($table)) {
+ if (strpos($table, ')')) {
+ // 子查询
+ } elseif (false === strpos($table, ',')) {
+ if (strpos($table, ' ')) {
+ [$item, $alias] = explode(' ', $table);
+ $table = [];
+ $this->alias([$item => $alias]);
+ $table[$item] = $alias;
+ }
+ } else {
+ $tables = explode(',', $table);
+ $table = [];
+
+ foreach ($tables as $item) {
+ $item = trim($item);
+ if (strpos($item, ' ')) {
+ [$item, $alias] = explode(' ', $item);
+ $this->alias([$item => $alias]);
+ $table[$item] = $alias;
+ } else {
+ $table[] = $item;
+ }
+ }
+ }
+ } elseif (is_array($table)) {
+ $tables = $table;
+ $table = [];
+
+ foreach ($tables as $key => $val) {
+ if (is_numeric($key)) {
+ $table[] = $val;
+ } else {
+ $this->alias([$key => $val]);
+ $table[$key] = $val;
+ }
+ }
+ }
+
+ $this->options['table'] = $table;
+
+ return $this;
+ }
+
+ /**
+ * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc'])
+ * @access public
+ * @param string|array|Raw $field 排序字段
+ * @param string $order 排序
+ * @return $this
+ */
+ public function order($field, string $order = '')
+ {
+ if (empty($field)) {
+ return $this;
+ } elseif ($field instanceof Raw) {
+ $this->options['order'][] = $field;
+ return $this;
+ }
+
+ if (is_string($field)) {
+ if (!empty($this->options['via'])) {
+ $field = $this->options['via'] . '.' . $field;
+ }
+ if (strpos($field, ',')) {
+ $field = array_map('trim', explode(',', $field));
+ } else {
+ $field = empty($order) ? $field : [$field => $order];
+ }
+ } elseif (!empty($this->options['via'])) {
+ foreach ($field as $key => $val) {
+ if (is_numeric($key)) {
+ $field[$key] = $this->options['via'] . '.' . $val;
+ } else {
+ $field[$this->options['via'] . '.' . $key] = $val;
+ unset($field[$key]);
+ }
+ }
+ }
+
+ if (!isset($this->options['order'])) {
+ $this->options['order'] = [];
+ }
+
+ if (is_array($field)) {
+ $this->options['order'] = array_merge($this->options['order'], $field);
+ } else {
+ $this->options['order'][] = $field;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 分页查询
+ * @access public
+ * @param int|array $listRows 每页数量 数组表示配置参数
+ * @param int|bool $simple 是否简洁模式或者总记录数
+ * @return Paginator
+ * @throws Exception
+ */
+ public function paginate($listRows = null, $simple = false): Paginator
+ {
+ if (is_int($simple)) {
+ $total = $simple;
+ $simple = false;
+ }
+
+ $defaultConfig = [
+ 'query' => [], //url额外参数
+ 'fragment' => '', //url锚点
+ 'var_page' => 'page', //分页变量
+ 'list_rows' => 15, //每页数量
+ ];
+
+ if (is_array($listRows)) {
+ $config = array_merge($defaultConfig, $listRows);
+ $listRows = intval($config['list_rows']);
+ } else {
+ $config = $defaultConfig;
+ $listRows = intval($listRows ?: $config['list_rows']);
+ }
+
+ $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']);
+
+ $page = $page < 1 ? 1 : $page;
+
+ $config['path'] = $config['path'] ?? Paginator::getCurrentPath();
+
+ if (!isset($total) && !$simple) {
+ $options = $this->getOptions();
+
+ unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
+
+ $bind = $this->bind;
+ $total = $this->count();
+ $results = $this->options($options)->bind($bind)->page($page, $listRows)->select();
+ } elseif ($simple) {
+ $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
+ $total = null;
+ } else {
+ $results = $this->page($page, $listRows)->select();
+ }
+
+ $this->removeOption('limit');
+ $this->removeOption('page');
+
+ return Paginator::make($results, $listRows, $page, $total, $simple, $config);
+ }
+
+ /**
+ * 根据数字类型字段进行分页查询(大数据)
+ * @access public
+ * @param int|array $listRows 每页数量或者分页配置
+ * @param string $key 分页索引键
+ * @param string $sort 索引键排序 asc|desc
+ * @return Paginator
+ * @throws Exception
+ */
+ public function paginateX($listRows = null, string $key = null, string $sort = null): Paginator
+ {
+ $defaultConfig = [
+ 'query' => [], //url额外参数
+ 'fragment' => '', //url锚点
+ 'var_page' => 'page', //分页变量
+ 'list_rows' => 15, //每页数量
+ ];
+
+ $config = is_array($listRows) ? array_merge($defaultConfig, $listRows) : $defaultConfig;
+ $listRows = is_int($listRows) ? $listRows : (int) $config['list_rows'];
+ $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']);
+ $page = $page < 1 ? 1 : $page;
+
+ $config['path'] = $config['path'] ?? Paginator::getCurrentPath();
+
+ $key = $key ?: $this->getPk();
+ $options = $this->getOptions();
+
+ if (is_null($sort)) {
+ $order = $options['order'] ?? '';
+ if (!empty($order)) {
+ $sort = $order[$key] ?? 'desc';
+ } else {
+ $this->order($key, 'desc');
+ $sort = 'desc';
+ }
+ } else {
+ $this->order($key, $sort);
+ }
+
+ $newOption = $options;
+ unset($newOption['field'], $newOption['page']);
+
+ $data = $this->newQuery()
+ ->options($newOption)
+ ->field($key)
+ ->where(true)
+ ->order($key, $sort)
+ ->limit(1)
+ ->find();
+
+ $result = $data[$key];
+
+ if (is_numeric($result)) {
+ $lastId = 'asc' == $sort ? ($result - 1) + ($page - 1) * $listRows : ($result + 1) - ($page - 1) * $listRows;
+ } else {
+ throw new Exception('not support type');
+ }
+
+ $results = $this->when($lastId, function ($query) use ($key, $sort, $lastId) {
+ $query->where($key, 'asc' == $sort ? '>' : '<', $lastId);
+ })
+ ->limit($listRows)
+ ->select();
+
+ $this->options($options);
+
+ return Paginator::make($results, $listRows, $page, null, true, $config);
+ }
+
+ /**
+ * 根据最后ID查询更多N个数据
+ * @access public
+ * @param int $limit LIMIT
+ * @param int|string $lastId LastId
+ * @param string $key 分页索引键 默认为主键
+ * @param string $sort 索引键排序 asc|desc
+ * @return array
+ * @throws Exception
+ */
+ public function more(int $limit, $lastId = null, string $key = null, string $sort = null): array
+ {
+ $key = $key ?: $this->getPk();
+
+ if (is_null($sort)) {
+ $order = $this->getOptions('order');
+ if (!empty($order)) {
+ $sort = $order[$key] ?? 'desc';
+ } else {
+ $this->order($key, 'desc');
+ $sort = 'desc';
+ }
+ } else {
+ $this->order($key, $sort);
+ }
+
+ $result = $this->when($lastId, function ($query) use ($key, $sort, $lastId) {
+ $query->where($key, 'asc' == $sort ? '>' : '<', $lastId);
+ })->limit($limit)->select();
+
+ $last = $result->last();
+
+ $result->first();
+
+ return [
+ 'data' => $result,
+ 'lastId' => $last[$key],
+ ];
+ }
+
+ /**
+ * 查询缓存
+ * @access public
+ * @param mixed $key 缓存key
+ * @param integer|\DateTime $expire 缓存有效期
+ * @param string|array $tag 缓存标签
+ * @return $this
+ */
+ public function cache($key = true, $expire = null, $tag = null)
+ {
+ if (false === $key || !$this->getConnection()->getCache()) {
+ return $this;
+ }
+
+ if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) {
+ $expire = $key;
+ $key = true;
+ }
+
+ $this->options['cache'] = [$key, $expire, $tag];
+
+ return $this;
+ }
+
+ /**
+ * 指定查询lock
+ * @access public
+ * @param bool|string $lock 是否lock
+ * @return $this
+ */
+ public function lock($lock = false)
+ {
+ $this->options['lock'] = $lock;
+
+ if ($lock) {
+ $this->options['master'] = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 指定数据表别名
+ * @access public
+ * @param array|string $alias 数据表别名
+ * @return $this
+ */
+ public function alias($alias)
+ {
+ if (is_array($alias)) {
+ $this->options['alias'] = $alias;
+ } else {
+ $table = $this->getTable();
+
+ $this->options['alias'][$table] = $alias;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置从主服务器读取数据
+ * @access public
+ * @param bool $readMaster 是否从主服务器读取
+ * @return $this
+ */
+ public function master(bool $readMaster = true)
+ {
+ $this->options['master'] = $readMaster;
+ return $this;
+ }
+
+ /**
+ * 设置是否严格检查字段名
+ * @access public
+ * @param bool $strict 是否严格检查字段
+ * @return $this
+ */
+ public function strict(bool $strict = true)
+ {
+ $this->options['strict'] = $strict;
+ return $this;
+ }
+
+ /**
+ * 设置自增序列名
+ * @access public
+ * @param string $sequence 自增序列名
+ * @return $this
+ */
+ public function sequence(string $sequence = null)
+ {
+ $this->options['sequence'] = $sequence;
+ return $this;
+ }
+
+ /**
+ * 设置JSON字段信息
+ * @access public
+ * @param array $json JSON字段
+ * @param bool $assoc 是否取出数组
+ * @return $this
+ */
+ public function json(array $json = [], bool $assoc = false)
+ {
+ $this->options['json'] = $json;
+ $this->options['json_assoc'] = $assoc;
+ return $this;
+ }
+
+ /**
+ * 指定数据表主键
+ * @access public
+ * @param string|array $pk 主键
+ * @return $this
+ */
+ public function pk($pk)
+ {
+ $this->pk = $pk;
+ return $this;
+ }
+
+ /**
+ * 查询参数批量赋值
+ * @access protected
+ * @param array $options 表达式参数
+ * @return $this
+ */
+ protected function options(array $options)
+ {
+ $this->options = $options;
+ return $this;
+ }
+
+ /**
+ * 获取当前的查询参数
+ * @access public
+ * @param string $name 参数名
+ * @return mixed
+ */
+ public function getOptions(string $name = '')
+ {
+ if ('' === $name) {
+ return $this->options;
+ }
+
+ return $this->options[$name] ?? null;
+ }
+
+ /**
+ * 设置当前的查询参数
+ * @access public
+ * @param string $option 参数名
+ * @param mixed $value 参数值
+ * @return $this
+ */
+ public function setOption(string $option, $value)
+ {
+ $this->options[$option] = $value;
+ return $this;
+ }
+
+ /**
+ * 设置当前字段添加的表别名
+ * @access public
+ * @param string $via 临时表别名
+ * @return $this
+ */
+ public function via(string $via = '')
+ {
+ $this->options['via'] = $via;
+
+ return $this;
+ }
+
+ /**
+ * 保存记录 自动判断insert或者update
+ * @access public
+ * @param array $data 数据
+ * @param bool $forceInsert 是否强制insert
+ * @return integer
+ */
+ public function save(array $data = [], bool $forceInsert = false)
+ {
+ if ($forceInsert) {
+ return $this->insert($data);
+ }
+
+ $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
+
+ if (!empty($this->options['where'])) {
+ $isUpdate = true;
+ } else {
+ $isUpdate = $this->parseUpdateData($this->options['data']);
+ }
+
+ return $isUpdate ? $this->update() : $this->insert();
+ }
+
+ /**
+ * 插入记录
+ * @access public
+ * @param array $data 数据
+ * @param boolean $getLastInsID 返回自增主键
+ * @return integer|string
+ */
+ public function insert(array $data = [], bool $getLastInsID = false)
+ {
+ if (!empty($data)) {
+ $this->options['data'] = $data;
+ }
+
+ return $this->connection->insert($this, $getLastInsID);
+ }
+
+ /**
+ * 插入记录并获取自增ID
+ * @access public
+ * @param array $data 数据
+ * @return integer|string
+ */
+ public function insertGetId(array $data)
+ {
+ return $this->insert($data, true);
+ }
+
+ /**
+ * 批量插入记录
+ * @access public
+ * @param array $dataSet 数据集
+ * @param integer $limit 每次写入数据限制
+ * @return integer
+ */
+ public function insertAll(array $dataSet = [], int $limit = 0): int
+ {
+ if (empty($dataSet)) {
+ $dataSet = $this->options['data'] ?? [];
+ }
+
+ if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) {
+ $limit = (int) $this->options['limit'];
+ }
+
+ return $this->connection->insertAll($this, $dataSet, $limit);
+ }
+
+ /**
+ * 通过Select方式插入记录
+ * @access public
+ * @param array $fields 要插入的数据表字段名
+ * @param string $table 要插入的数据表名
+ * @return integer
+ */
+ public function selectInsert(array $fields, string $table): int
+ {
+ return $this->connection->selectInsert($this, $fields, $table);
+ }
+
+ /**
+ * 更新记录
+ * @access public
+ * @param mixed $data 数据
+ * @return integer
+ * @throws Exception
+ */
+ public function update(array $data = []): int
+ {
+ if (!empty($data)) {
+ $this->options['data'] = array_merge($this->options['data'] ?? [], $data);
+ }
+
+ if (empty($this->options['where'])) {
+ $this->parseUpdateData($this->options['data']);
+ }
+
+ if (empty($this->options['where']) && $this->model) {
+ $this->where($this->model->getWhere());
+ }
+
+ if (empty($this->options['where'])) {
+ // 如果没有任何更新条件则不执行
+ throw new Exception('miss update condition');
+ }
+
+ return $this->connection->update($this);
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param mixed $data 表达式 true 表示强制删除
+ * @return int
+ * @throws Exception
+ */
+ public function delete($data = null): int
+ {
+ if (!is_null($data) && true !== $data) {
+ // AR模式分析主键条件
+ $this->parsePkWhere($data);
+ }
+
+ if (empty($this->options['where']) && $this->model) {
+ $this->where($this->model->getWhere());
+ }
+
+ if (true !== $data && empty($this->options['where'])) {
+ // 如果条件为空 不进行删除操作 除非设置 1=1
+ throw new Exception('delete without condition');
+ }
+
+ if (!empty($this->options['soft_delete'])) {
+ // 软删除
+ list($field, $condition) = $this->options['soft_delete'];
+ if ($condition) {
+ unset($this->options['soft_delete']);
+ $this->options['data'] = [$field => $condition];
+
+ return $this->connection->update($this);
+ }
+ }
+
+ $this->options['data'] = $data;
+
+ return $this->connection->delete($this);
+ }
+
+ /**
+ * 查找记录
+ * @access public
+ * @param mixed $data 数据
+ * @return Collection
+ * @throws Exception
+ * @throws ModelNotFoundException
+ * @throws DataNotFoundException
+ */
+ public function select($data = null): Collection
+ {
+ if (!is_null($data)) {
+ // 主键条件分析
+ $this->parsePkWhere($data);
+ }
+
+ $resultSet = $this->connection->select($this);
+
+ // 返回结果处理
+ if (!empty($this->options['fail']) && count($resultSet) == 0) {
+ $this->throwNotFound();
+ }
+
+ // 数据列表读取后的处理
+ if (!empty($this->model)) {
+ // 生成模型对象
+ $resultSet = $this->resultSetToModelCollection($resultSet);
+ } else {
+ $this->resultSet($resultSet);
+ }
+
+ return $resultSet;
+ }
+
+ /**
+ * 查找单条记录
+ * @access public
+ * @param mixed $data 查询数据
+ * @return array|Model|null
+ * @throws Exception
+ * @throws ModelNotFoundException
+ * @throws DataNotFoundException
+ */
+ public function find($data = null)
+ {
+ if (!is_null($data)) {
+ // AR模式分析主键条件
+ $this->parsePkWhere($data);
+ }
+
+ if (empty($this->options['where']) && empty($this->options['order'])) {
+ $result = [];
+ } else {
+ $result = $this->connection->find($this);
+ }
+
+ // 数据处理
+ if (empty($result)) {
+ return $this->resultToEmpty();
+ }
+
+ if (!empty($this->model)) {
+ // 返回模型对象
+ $this->resultToModel($result, $this->options);
+ } else {
+ $this->result($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 分析表达式(可用于查询或者写入操作)
+ * @access public
+ * @return array
+ */
+ public function parseOptions(): array
+ {
+ $options = $this->getOptions();
+
+ // 获取数据表
+ if (empty($options['table'])) {
+ $options['table'] = $this->getTable();
+ }
+
+ if (!isset($options['where'])) {
+ $options['where'] = [];
+ } elseif (isset($options['view'])) {
+ // 视图查询条件处理
+ $this->parseView($options);
+ }
+
+ if (!isset($options['field'])) {
+ $options['field'] = '*';
+ }
+
+ foreach (['data', 'order', 'join', 'union'] as $name) {
+ if (!isset($options[$name])) {
+ $options[$name] = [];
+ }
+ }
+
+ if (!isset($options['strict'])) {
+ $options['strict'] = $this->connection->getConfig('fields_strict');
+ }
+
+ foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) {
+ if (!isset($options[$name])) {
+ $options[$name] = false;
+ }
+ }
+
+ foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) {
+ if (!isset($options[$name])) {
+ $options[$name] = '';
+ }
+ }
+
+ if (isset($options['page'])) {
+ // 根据页数计算limit
+ [$page, $listRows] = $options['page'];
+ $page = $page > 0 ? $page : 1;
+ $listRows = $listRows ?: (is_numeric($options['limit']) ? $options['limit'] : 20);
+ $offset = $listRows * ($page - 1);
+ $options['limit'] = $offset . ',' . $listRows;
+ }
+
+ $this->options = $options;
+
+ return $options;
+ }
+
+ /**
+ * 分析数据是否存在更新条件
+ * @access public
+ * @param array $data 数据
+ * @return bool
+ * @throws Exception
+ */
+ public function parseUpdateData(&$data): bool
+ {
+ $pk = $this->getPk();
+ $isUpdate = false;
+ // 如果存在主键数据 则自动作为更新条件
+ if (is_string($pk) && isset($data[$pk])) {
+ $this->where($pk, '=', $data[$pk]);
+ $this->options['key'] = $data[$pk];
+ unset($data[$pk]);
+ $isUpdate = true;
+ } elseif (is_array($pk)) {
+ foreach ($pk as $field) {
+ if (isset($data[$field])) {
+ $this->where($field, '=', $data[$field]);
+ $isUpdate = true;
+ } else {
+ // 如果缺少复合主键数据则不执行
+ throw new Exception('miss complex primary data');
+ }
+ unset($data[$field]);
+ }
+ }
+
+ return $isUpdate;
+ }
+
+ /**
+ * 把主键值转换为查询条件 支持复合主键
+ * @access public
+ * @param array|string $data 主键数据
+ * @return void
+ * @throws Exception
+ */
+ public function parsePkWhere($data): void
+ {
+ $pk = $this->getPk();
+
+ if (is_string($pk)) {
+ // 获取数据表
+ if (empty($this->options['table'])) {
+ $this->options['table'] = $this->getTable();
+ }
+
+ $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table'];
+
+ if (!empty($this->options['alias'][$table])) {
+ $alias = $this->options['alias'][$table];
+ }
+
+ $key = isset($alias) ? $alias . '.' . $pk : $pk;
+ // 根据主键查询
+ if (is_array($data)) {
+ $this->where($key, 'in', $data);
+ } else {
+ $this->where($key, '=', $data);
+ $this->options['key'] = $data;
+ }
+ }
+ }
+
+ /**
+ * 获取模型的更新条件
+ * @access protected
+ * @param array $options 查询参数
+ */
+ protected function getModelUpdateCondition(array $options)
+ {
+ return $options['where']['AND'] ?? null;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/Builder.php b/vendor/topthink/think-orm/src/db/Builder.php
new file mode 100755
index 0000000..c807f82
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/Builder.php
@@ -0,0 +1,1303 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use Closure;
+use PDO;
+use think\db\exception\DbException as Exception;
+
+/**
+ * Db Builder
+ */
+abstract class Builder
+{
+ /**
+ * Connection对象
+ * @var ConnectionInterface
+ */
+ protected $connection;
+
+ /**
+ * 查询表达式映射
+ * @var array
+ */
+ protected $exp = ['NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME'];
+
+ /**
+ * 查询表达式解析
+ * @var array
+ */
+ protected $parser = [
+ 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='],
+ 'parseLike' => ['LIKE', 'NOT LIKE'],
+ 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'],
+ 'parseIn' => ['NOT IN', 'IN'],
+ 'parseExp' => ['EXP'],
+ 'parseNull' => ['NOT NULL', 'NULL'],
+ 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'],
+ 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'],
+ 'parseExists' => ['NOT EXISTS', 'EXISTS'],
+ 'parseColumn' => ['COLUMN'],
+ ];
+
+ /**
+ * SELECT SQL表达式
+ * @var string
+ */
+ protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * INSERT SQL表达式
+ * @var string
+ */
+ protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
+
+ /**
+ * INSERT ALL SQL表达式
+ * @var string
+ */
+ protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
+
+ /**
+ * UPDATE SQL表达式
+ * @var string
+ */
+ protected $updateSql = 'UPDATE%EXTRA% %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * DELETE SQL表达式
+ * @var string
+ */
+ protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * 架构函数
+ * @access public
+ * @param ConnectionInterface $connection 数据库连接对象实例
+ */
+ public function __construct(ConnectionInterface $connection)
+ {
+ $this->connection = $connection;
+ }
+
+ /**
+ * 获取当前的连接对象实例
+ * @access public
+ * @return ConnectionInterface
+ */
+ public function getConnection(): ConnectionInterface
+ {
+ return $this->connection;
+ }
+
+ /**
+ * 注册查询表达式解析
+ * @access public
+ * @param string $name 解析方法
+ * @param array $parser 匹配表达式数据
+ * @return $this
+ */
+ public function bindParser(string $name, array $parser)
+ {
+ $this->parser[$name] = $parser;
+ return $this;
+ }
+
+ /**
+ * 数据分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param array $data 数据
+ * @param array $fields 字段信息
+ * @param array $bind 参数绑定
+ * @return array
+ */
+ protected function parseData(Query $query, array $data = [], array $fields = [], array $bind = []): array
+ {
+ if (empty($data)) {
+ return [];
+ }
+
+ $options = $query->getOptions();
+
+ // 获取绑定信息
+ if (empty($bind)) {
+ $bind = $query->getFieldsBindType();
+ }
+
+ if (empty($fields)) {
+ if ('*' == $options['field']) {
+ $fields = array_keys($bind);
+ } else {
+ $fields = $options['field'];
+ }
+ }
+
+ $result = [];
+
+ foreach ($data as $key => $val) {
+ $item = $this->parseKey($query, $key, true);
+
+ if ($val instanceof Raw) {
+ $result[$item] = $this->parseRaw($query, $val);
+ continue;
+ } elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $query->getFieldType($key))) {
+ $val = json_encode($val);
+ }
+
+ if (false !== strpos($key, '->')) {
+ [$key, $name] = explode('->', $key, 2);
+ $item = $this->parseKey($query, $key);
+ $result[$item] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key . '->' . $name, $val, $bind) . ')';
+ } elseif (false === strpos($key, '.') && !in_array($key, $fields, true)) {
+ if ($options['strict']) {
+ throw new Exception('fields not exists:[' . $key . ']');
+ }
+ } elseif (is_null($val)) {
+ $result[$item] = 'NULL';
+ } elseif (is_array($val) && !empty($val) && is_string($val[0])) {
+ switch (strtoupper($val[0])) {
+ case 'INC':
+ $result[$item] = $item . ' + ' . floatval($val[1]);
+ break;
+ case 'DEC':
+ $result[$item] = $item . ' - ' . floatval($val[1]);
+ break;
+ }
+ } elseif (is_scalar($val)) {
+ // 过滤非标量数据
+ $result[$item] = $this->parseDataBind($query, $key, $val, $bind);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 数据绑定处理
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key 字段名
+ * @param mixed $data 数据
+ * @param array $bind 绑定数据
+ * @return string
+ */
+ protected function parseDataBind(Query $query, string $key, $data, array $bind = []): string
+ {
+ if ($data instanceof Raw) {
+ return $this->parseRaw($query, $data);
+ }
+
+ $name = $query->bindValue($data, $bind[$key] ?? PDO::PARAM_STR);
+
+ return ':' . $name;
+ }
+
+ /**
+ * 字段名分析
+ * @access public
+ * @param Query $query 查询对象
+ * @param mixed $key 字段名
+ * @param bool $strict 严格检测
+ * @return string
+ */
+ public function parseKey(Query $query, $key, bool $strict = false): string
+ {
+ return $key;
+ }
+
+ /**
+ * 查询额外参数分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $extra 额外参数
+ * @return string
+ */
+ protected function parseExtra(Query $query, string $extra): string
+ {
+ return preg_match('/^[\w]+$/i', $extra) ? ' ' . strtoupper($extra) : '';
+ }
+
+ /**
+ * field分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $fields 字段名
+ * @return string
+ */
+ protected function parseField(Query $query, $fields): string
+ {
+ if (is_array($fields)) {
+ // 支持 'field1'=>'field2' 这样的字段别名定义
+ $array = [];
+
+ foreach ($fields as $key => $field) {
+ if ($field instanceof Raw) {
+ $array[] = $this->parseRaw($query, $field);
+ } elseif (!is_numeric($key)) {
+ $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true);
+ } else {
+ $array[] = $this->parseKey($query, $field);
+ }
+ }
+
+ $fieldsStr = implode(',', $array);
+ } else {
+ $fieldsStr = '*';
+ }
+
+ return $fieldsStr;
+ }
+
+ /**
+ * table分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $tables 表名
+ * @return string
+ */
+ protected function parseTable(Query $query, $tables): string
+ {
+ $item = [];
+ $options = $query->getOptions();
+
+ foreach ((array) $tables as $key => $table) {
+ if ($table instanceof Raw) {
+ $item[] = $this->parseRaw($query, $table);
+ } elseif (!is_numeric($key)) {
+ $item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table);
+ } elseif (isset($options['alias'][$table])) {
+ $item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]);
+ } else {
+ $item[] = $this->parseKey($query, $table);
+ }
+ }
+
+ return implode(',', $item);
+ }
+
+ /**
+ * where分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $where 查询条件
+ * @return string
+ */
+ protected function parseWhere(Query $query, array $where): string
+ {
+ $options = $query->getOptions();
+ $whereStr = $this->buildWhere($query, $where);
+
+ if (!empty($options['soft_delete'])) {
+ // 附加软删除条件
+ [$field, $condition] = $options['soft_delete'];
+
+ $binds = $query->getFieldsBindType();
+ $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : '';
+ $whereStr = $whereStr . $this->parseWhereItem($query, $field, $condition, $binds);
+ }
+
+ return empty($whereStr) ? '' : ' WHERE ' . $whereStr;
+ }
+
+ /**
+ * 生成查询条件SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @param mixed $where 查询条件
+ * @return string
+ */
+ public function buildWhere(Query $query, array $where): string
+ {
+ if (empty($where)) {
+ $where = [];
+ }
+
+ $whereStr = '';
+
+ $binds = $query->getFieldsBindType();
+
+ foreach ($where as $logic => $val) {
+ $str = $this->parseWhereLogic($query, $logic, $val, $binds);
+
+ $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str);
+ }
+
+ return $whereStr;
+ }
+
+ /**
+ * 不同字段使用相同查询条件(AND)
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $logic Logic
+ * @param array $val 查询条件
+ * @param array $binds 参数绑定
+ * @return array
+ */
+ protected function parseWhereLogic(Query $query, string $logic, array $val, array $binds = []): array
+ {
+ $where = [];
+ foreach ($val as $value) {
+ if ($value instanceof Raw) {
+ $where[] = ' ' . $logic . ' ( ' . $this->parseRaw($query, $value) . ' )';
+ continue;
+ }
+
+ if (is_array($value)) {
+ if (key($value) !== 0) {
+ throw new Exception('where express error:' . var_export($value, true));
+ }
+ $field = array_shift($value);
+ } elseif (true === $value) {
+ $where[] = ' ' . $logic . ' 1 ';
+ continue;
+ } elseif (!($value instanceof Closure)) {
+ throw new Exception('where express error:' . var_export($value, true));
+ }
+
+ if ($value instanceof Closure) {
+ // 使用闭包查询
+ $whereClosureStr = $this->parseClosureWhere($query, $value, $logic);
+ if ($whereClosureStr) {
+ $where[] = $whereClosureStr;
+ }
+ } elseif (is_array($field)) {
+ $where[] = $this->parseMultiWhereField($query, $value, $field, $logic, $binds);
+ } elseif ($field instanceof Raw) {
+ $where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds);
+ } elseif (strpos($field, '|')) {
+ $where[] = $this->parseFieldsOr($query, $value, $field, $logic, $binds);
+ } elseif (strpos($field, '&')) {
+ $where[] = $this->parseFieldsAnd($query, $value, $field, $logic, $binds);
+ } else {
+ // 对字段使用表达式查询
+ $field = is_string($field) ? $field : '';
+ $where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds);
+ }
+ }
+
+ return $where;
+ }
+
+ /**
+ * 不同字段使用相同查询条件(AND)
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $value 查询条件
+ * @param string $field 查询字段
+ * @param string $logic Logic
+ * @param array $binds 参数绑定
+ * @return string
+ */
+ protected function parseFieldsAnd(Query $query, $value, string $field, string $logic, array $binds): string
+ {
+ $item = [];
+
+ foreach (explode('&', $field) as $k) {
+ $item[] = $this->parseWhereItem($query, $k, $value, $binds);
+ }
+
+ return ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )';
+ }
+
+ /**
+ * 不同字段使用相同查询条件(OR)
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $value 查询条件
+ * @param string $field 查询字段
+ * @param string $logic Logic
+ * @param array $binds 参数绑定
+ * @return string
+ */
+ protected function parseFieldsOr(Query $query, $value, string $field, string $logic, array $binds): string
+ {
+ $item = [];
+
+ foreach (explode('|', $field) as $k) {
+ $item[] = $this->parseWhereItem($query, $k, $value, $binds);
+ }
+
+ return ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )';
+ }
+
+ /**
+ * 闭包查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param Closure $value 查询条件
+ * @param string $logic Logic
+ * @return string
+ */
+ protected function parseClosureWhere(Query $query, Closure $value, string $logic): string
+ {
+ $newQuery = $query->newQuery();
+ $value($newQuery);
+ $whereClosure = $this->buildWhere($newQuery, $newQuery->getOptions('where') ?: []);
+
+ if (!empty($whereClosure)) {
+ $query->bind($newQuery->getBind(false));
+ $where = ' ' . $logic . ' ( ' . $whereClosure . ' )';
+ }
+
+ return $where ?? '';
+ }
+
+ /**
+ * 复合条件查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $value 查询条件
+ * @param mixed $field 查询字段
+ * @param string $logic Logic
+ * @param array $binds 参数绑定
+ * @return string
+ */
+ protected function parseMultiWhereField(Query $query, $value, $field, string $logic, array $binds): string
+ {
+ array_unshift($value, $field);
+
+ $where = [];
+ foreach ($value as $item) {
+ $where[] = $this->parseWhereItem($query, array_shift($item), $item, $binds);
+ }
+
+ return ' ' . $logic . ' ( ' . implode(' AND ', $where) . ' )';
+ }
+
+ /**
+ * where子单元分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $field 查询字段
+ * @param array $val 查询条件
+ * @param array $binds 参数绑定
+ * @return string
+ */
+ protected function parseWhereItem(Query $query, $field, array $val, array $binds = []): string
+ {
+ // 字段分析
+ $key = $field ? $this->parseKey($query, $field, true) : '';
+
+ list($exp, $value) = $val;
+
+ // 检测操作符
+ if (!is_string($exp)) {
+ throw new Exception('where express error:' . var_export($exp, true));
+ }
+
+ $exp = strtoupper($exp);
+ if (isset($this->exp[$exp])) {
+ $exp = $this->exp[$exp];
+ }
+
+ if (is_string($field) && 'LIKE' != $exp) {
+ $bindType = $binds[$field] ?? PDO::PARAM_STR;
+ } else {
+ $bindType = PDO::PARAM_STR;
+ }
+
+ if ($value instanceof Raw) {
+
+ } elseif (is_object($value) && method_exists($value, '__toString')) {
+ // 对象数据写入
+ $value = $value->__toString();
+ }
+
+ if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) {
+ if (is_string($value) && 0 === strpos($value, ':') && $query->isBind(substr($value, 1))) {
+ } else {
+ $name = $query->bindValue($value, $bindType);
+ $value = ':' . $name;
+ }
+ }
+
+ // 解析查询表达式
+ foreach ($this->parser as $fun => $parse) {
+ if (in_array($exp, $parse)) {
+ return $this->$fun($query, $key, $exp, $value, $field, $bindType, $val[2] ?? 'AND');
+ }
+ }
+
+ throw new Exception('where express error:' . $exp);
+ }
+
+ /**
+ * 模糊查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param array $value
+ * @param string $field
+ * @param integer $bindType
+ * @param string $logic
+ * @return string
+ */
+ protected function parseLike(Query $query, string $key, string $exp, $value, $field, int $bindType, string $logic): string
+ {
+ // 模糊匹配
+ if (is_array($value)) {
+ $array = [];
+ foreach ($value as $item) {
+ $name = $query->bindValue($item, PDO::PARAM_STR);
+ $array[] = $key . ' ' . $exp . ' :' . $name;
+ }
+
+ $whereStr = '(' . implode(' ' . strtoupper($logic) . ' ', $array) . ')';
+ } else {
+ $whereStr = $key . ' ' . $exp . ' ' . $value;
+ }
+
+ return $whereStr;
+ }
+
+ /**
+ * 表达式查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param array $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseExp(Query $query, string $key, string $exp, Raw $value, string $field, int $bindType): string
+ {
+ // 表达式查询
+ return '( ' . $key . ' ' . $this->parseRaw($query, $value) . ' )';
+ }
+
+ /**
+ * 表达式查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param array $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseColumn(Query $query, string $key, $exp, array $value, string $field, int $bindType): string
+ {
+ // 字段比较查询
+ [$op, $field] = $value;
+
+ if (!in_array(trim($op), ['=', '<>', '>', '>=', '<', '<='])) {
+ throw new Exception('where express error:' . var_export($value, true));
+ }
+
+ return '( ' . $key . ' ' . $op . ' ' . $this->parseKey($query, $field, true) . ' )';
+ }
+
+ /**
+ * Null查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string
+ {
+ // NULL 查询
+ return $key . ' IS ' . $exp;
+ }
+
+ /**
+ * 范围查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseBetween(Query $query, string $key, string $exp, $value, $field, int $bindType): string
+ {
+ // BETWEEN 查询
+ $data = is_array($value) ? $value : explode(',', $value);
+
+ $min = $query->bindValue($data[0], $bindType);
+ $max = $query->bindValue($data[1], $bindType);
+
+ return $key . ' ' . $exp . ' :' . $min . ' AND :' . $max . ' ';
+ }
+
+ /**
+ * Exists查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseExists(Query $query, string $key, string $exp, $value, string $field, int $bindType): string
+ {
+ // EXISTS 查询
+ if ($value instanceof Closure) {
+ $value = $this->parseClosure($query, $value, false);
+ } elseif ($value instanceof Raw) {
+ $value = $this->parseRaw($query, $value);
+ } else {
+ throw new Exception('where express error:' . $value);
+ }
+
+ return $exp . ' ( ' . $value . ' )';
+ }
+
+ /**
+ * 时间比较查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string
+ {
+ return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindType);
+ }
+
+ /**
+ * 大小比较查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseCompare(Query $query, string $key, string $exp, $value, $field, int $bindType): string
+ {
+ if (is_array($value)) {
+ throw new Exception('where express error:' . $exp . var_export($value, true));
+ }
+
+ // 比较运算
+ if ($value instanceof Closure) {
+ $value = $this->parseClosure($query, $value);
+ }
+
+ if ('=' == $exp && is_null($value)) {
+ return $key . ' IS NULL';
+ }
+
+ return $key . ' ' . $exp . ' ' . $value;
+ }
+
+ /**
+ * 时间范围查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseBetweenTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string
+ {
+ if (is_string($value)) {
+ $value = explode(',', $value);
+ }
+
+ return $key . ' ' . substr($exp, 0, -4)
+ . $this->parseDateTime($query, $value[0], $field, $bindType)
+ . ' AND '
+ . $this->parseDateTime($query, $value[1], $field, $bindType);
+
+ }
+
+ /**
+ * IN查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseIn(Query $query, string $key, string $exp, $value, $field, int $bindType): string
+ {
+ // IN 查询
+ if ($value instanceof Closure) {
+ $value = $this->parseClosure($query, $value, false);
+ } elseif ($value instanceof Raw) {
+ $value = $this->parseRaw($query, $value);
+ } else {
+ $value = array_unique(is_array($value) ? $value : explode(',', $value));
+ $array = [];
+
+ foreach ($value as $v) {
+ $name = $query->bindValue($v, $bindType);
+ $array[] = ':' . $name;
+ }
+
+ if (count($array) == 1) {
+ return $key . ('IN' == $exp ? ' = ' : ' <> ') . $array[0];
+ } else {
+ $zone = implode(',', $array);
+ $value = empty($zone) ? "''" : $zone;
+ }
+ }
+
+ return $key . ' ' . $exp . ' (' . $value . ')';
+ }
+
+ /**
+ * 闭包子查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param \Closure $call
+ * @param bool $show
+ * @return string
+ */
+ protected function parseClosure(Query $query, Closure $call, bool $show = true): string
+ {
+ $newQuery = $query->newQuery()->removeOption();
+ $call($newQuery);
+
+ return $newQuery->buildSql($show);
+ }
+
+ /**
+ * 日期时间条件解析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $value
+ * @param string $key
+ * @param integer $bindType
+ * @return string
+ */
+ protected function parseDateTime(Query $query, $value, string $key, int $bindType): string
+ {
+ $options = $query->getOptions();
+
+ // 获取时间字段类型
+ if (strpos($key, '.')) {
+ [$table, $key] = explode('.', $key);
+
+ if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) {
+ $table = $pos;
+ }
+ } else {
+ $table = $options['table'];
+ }
+
+ $type = $query->getFieldType($key);
+
+ if ($type) {
+ if (is_string($value)) {
+ $value = strtotime($value) ?: $value;
+ }
+
+ if (is_int($value)) {
+ if (preg_match('/(datetime|timestamp)/is', $type)) {
+ // 日期及时间戳类型
+ $value = date('Y-m-d H:i:s', $value);
+ } elseif (preg_match('/(date)/is', $type)) {
+ // 日期及时间戳类型
+ $value = date('Y-m-d', $value);
+ }
+ }
+ }
+
+ $name = $query->bindValue($value, $bindType);
+
+ return ':' . $name;
+ }
+
+ /**
+ * limit分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $limit
+ * @return string
+ */
+ protected function parseLimit(Query $query, string $limit): string
+ {
+ return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : '';
+ }
+
+ /**
+ * join分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param array $join
+ * @return string
+ */
+ protected function parseJoin(Query $query, array $join): string
+ {
+ $joinStr = '';
+
+ foreach ($join as $item) {
+ [$table, $type, $on] = $item;
+
+ if (strpos($on, '=')) {
+ [$val1, $val2] = explode('=', $on, 2);
+
+ $condition = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2);
+ } else {
+ $condition = $on;
+ }
+
+ $table = $this->parseTable($query, $table);
+
+ $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . $condition;
+ }
+
+ return $joinStr;
+ }
+
+ /**
+ * order分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param array $order
+ * @return string
+ */
+ protected function parseOrder(Query $query, array $order): string
+ {
+ $array = [];
+ foreach ($order as $key => $val) {
+ if ($val instanceof Raw) {
+ $array[] = $this->parseRaw($query, $val);
+ } elseif (is_array($val) && preg_match('/^[\w\.]+$/', $key)) {
+ $array[] = $this->parseOrderField($query, $key, $val);
+ } elseif ('[rand]' == $val) {
+ $array[] = $this->parseRand($query);
+ } elseif (is_string($val)) {
+ if (is_numeric($key)) {
+ list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' ');
+ } else {
+ $sort = $val;
+ }
+
+ if (preg_match('/^[\w\.]+$/', $key)) {
+ $sort = strtoupper($sort);
+ $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
+ $array[] = $this->parseKey($query, $key, true) . $sort;
+ } else {
+ throw new Exception('order express error:' . $key);
+ }
+ }
+ }
+
+ return empty($array) ? '' : ' ORDER BY ' . implode(',', $array);
+ }
+
+ /**
+ * 分析Raw对象
+ * @access protected
+ * @param Query $query 查询对象
+ * @param Raw $raw Raw对象
+ * @return string
+ */
+ protected function parseRaw(Query $query, Raw $raw): string
+ {
+ $sql = $raw->getValue();
+ $bind = $raw->getBind();
+
+ if ($bind) {
+ $query->bindParams($sql, $bind);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @param Query $query 查询对象
+ * @return string
+ */
+ protected function parseRand(Query $query): string
+ {
+ return '';
+ }
+
+ /**
+ * orderField分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param array $val
+ * @return string
+ */
+ protected function parseOrderField(Query $query, string $key, array $val): string
+ {
+ if (isset($val['sort'])) {
+ $sort = $val['sort'];
+ unset($val['sort']);
+ } else {
+ $sort = '';
+ }
+
+ $sort = strtoupper($sort);
+ $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
+ $bind = $query->getFieldsBindType();
+
+ foreach ($val as $item) {
+ $val[] = $this->parseDataBind($query, $key, $item, $bind);
+ }
+
+ return 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort;
+ }
+
+ /**
+ * group分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $group
+ * @return string
+ */
+ protected function parseGroup(Query $query, $group): string
+ {
+ if (empty($group)) {
+ return '';
+ }
+
+ if (is_string($group)) {
+ $group = explode(',', $group);
+ }
+
+ $val = [];
+ foreach ($group as $key) {
+ $val[] = $this->parseKey($query, $key);
+ }
+
+ return ' GROUP BY ' . implode(',', $val);
+ }
+
+ /**
+ * having分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $having
+ * @return string
+ */
+ protected function parseHaving(Query $query, string $having): string
+ {
+ return !empty($having) ? ' HAVING ' . $having : '';
+ }
+
+ /**
+ * comment分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $comment
+ * @return string
+ */
+ protected function parseComment(Query $query, string $comment): string
+ {
+ if (false !== strpos($comment, '*/')) {
+ $comment = strstr($comment, '*/', true);
+ }
+
+ return !empty($comment) ? ' /* ' . $comment . ' */' : '';
+ }
+
+ /**
+ * distinct分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $distinct
+ * @return string
+ */
+ protected function parseDistinct(Query $query, bool $distinct): string
+ {
+ return !empty($distinct) ? ' DISTINCT ' : '';
+ }
+
+ /**
+ * union分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param array $union
+ * @return string
+ */
+ protected function parseUnion(Query $query, array $union): string
+ {
+ if (empty($union)) {
+ return '';
+ }
+
+ $type = $union['type'];
+ unset($union['type']);
+
+ foreach ($union as $u) {
+ if ($u instanceof Closure) {
+ $sql[] = $type . ' ' . $this->parseClosure($query, $u);
+ } elseif (is_string($u)) {
+ $sql[] = $type . ' ( ' . $u . ' )';
+ }
+ }
+
+ return ' ' . implode(' ', $sql);
+ }
+
+ /**
+ * index分析,可在操作链中指定需要强制使用的索引
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $index
+ * @return string
+ */
+ protected function parseForce(Query $query, $index): string
+ {
+ if (empty($index)) {
+ return '';
+ }
+
+ if (is_array($index)) {
+ $index = join(',', $index);
+ }
+
+ return sprintf(" FORCE INDEX ( %s ) ", $index);
+ }
+
+ /**
+ * 设置锁机制
+ * @access protected
+ * @param Query $query 查询对象
+ * @param bool|string $lock
+ * @return string
+ */
+ protected function parseLock(Query $query, $lock = false): string
+ {
+ if (is_bool($lock)) {
+ return $lock ? ' FOR UPDATE ' : '';
+ }
+
+ if (is_string($lock) && !empty($lock)) {
+ return ' ' . trim($lock) . ' ';
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * 生成查询SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @param bool $one 是否仅获取一个记录
+ * @return string
+ */
+ public function select(Query $query, bool $one = false): string
+ {
+ $options = $query->getOptions();
+
+ return str_replace(
+ ['%TABLE%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'],
+ [
+ $this->parseTable($query, $options['table']),
+ $this->parseDistinct($query, $options['distinct']),
+ $this->parseExtra($query, $options['extra']),
+ $this->parseField($query, $options['field']),
+ $this->parseJoin($query, $options['join']),
+ $this->parseWhere($query, $options['where']),
+ $this->parseGroup($query, $options['group']),
+ $this->parseHaving($query, $options['having']),
+ $this->parseOrder($query, $options['order']),
+ $this->parseLimit($query, $one ? '1' : $options['limit']),
+ $this->parseUnion($query, $options['union']),
+ $this->parseLock($query, $options['lock']),
+ $this->parseComment($query, $options['comment']),
+ $this->parseForce($query, $options['force']),
+ ],
+ $this->selectSql);
+ }
+
+ /**
+ * 生成Insert SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @return string
+ */
+ public function insert(Query $query): string
+ {
+ $options = $query->getOptions();
+
+ // 分析并处理数据
+ $data = $this->parseData($query, $options['data']);
+ if (empty($data)) {
+ return '';
+ }
+
+ $fields = array_keys($data);
+ $values = array_values($data);
+
+ return str_replace(
+ ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'],
+ [
+ !empty($options['replace']) ? 'REPLACE' : 'INSERT',
+ $this->parseTable($query, $options['table']),
+ $this->parseExtra($query, $options['extra']),
+ implode(' , ', $fields),
+ implode(' , ', $values),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->insertSql);
+ }
+
+ /**
+ * 生成insertall SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @param array $dataSet 数据集
+ * @return string
+ */
+ public function insertAll(Query $query, array $dataSet): string
+ {
+ $options = $query->getOptions();
+
+ // 获取绑定信息
+ $bind = $query->getFieldsBindType();
+
+ // 获取合法的字段
+ if ('*' == $options['field']) {
+ $allowFields = array_keys($bind);
+ } else {
+ $allowFields = $options['field'];
+ }
+
+ $fields = [];
+ $values = [];
+
+ foreach ($dataSet as $k => $data) {
+ $data = $this->parseData($query, $data, $allowFields, $bind);
+
+ $values[] = 'SELECT ' . implode(',', array_values($data));
+
+ if (!isset($insertFields)) {
+ $insertFields = array_keys($data);
+ }
+ }
+
+ foreach ($insertFields as $field) {
+ $fields[] = $this->parseKey($query, $field);
+ }
+
+ return str_replace(
+ ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'],
+ [
+ !empty($options['replace']) ? 'REPLACE' : 'INSERT',
+ $this->parseTable($query, $options['table']),
+ $this->parseExtra($query, $options['extra']),
+ implode(' , ', $fields),
+ implode(' UNION ALL ', $values),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->insertAllSql);
+ }
+
+ /**
+ * 生成slect insert SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @param array $fields 数据
+ * @param string $table 数据表
+ * @return string
+ */
+ public function selectInsert(Query $query, array $fields, string $table): string
+ {
+ foreach ($fields as &$field) {
+ $field = $this->parseKey($query, $field, true);
+ }
+
+ return 'INSERT INTO ' . $this->parseTable($query, $table) . ' (' . implode(',', $fields) . ') ' . $this->select($query);
+ }
+
+ /**
+ * 生成update SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @return string
+ */
+ public function update(Query $query): string
+ {
+ $options = $query->getOptions();
+
+ $data = $this->parseData($query, $options['data']);
+
+ if (empty($data)) {
+ return '';
+ }
+
+ $set = [];
+ foreach ($data as $key => $val) {
+ $set[] = $key . ' = ' . $val;
+ }
+
+ return str_replace(
+ ['%TABLE%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
+ [
+ $this->parseTable($query, $options['table']),
+ $this->parseExtra($query, $options['extra']),
+ implode(' , ', $set),
+ $this->parseJoin($query, $options['join']),
+ $this->parseWhere($query, $options['where']),
+ $this->parseOrder($query, $options['order']),
+ $this->parseLimit($query, $options['limit']),
+ $this->parseLock($query, $options['lock']),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->updateSql);
+ }
+
+ /**
+ * 生成delete SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @return string
+ */
+ public function delete(Query $query): string
+ {
+ $options = $query->getOptions();
+
+ return str_replace(
+ ['%TABLE%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
+ [
+ $this->parseTable($query, $options['table']),
+ $this->parseExtra($query, $options['extra']),
+ !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '',
+ $this->parseJoin($query, $options['join']),
+ $this->parseWhere($query, $options['where']),
+ $this->parseOrder($query, $options['order']),
+ $this->parseLimit($query, $options['limit']),
+ $this->parseLock($query, $options['lock']),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->deleteSql);
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/CacheItem.php b/vendor/topthink/think-orm/src/db/CacheItem.php
new file mode 100755
index 0000000..839f384
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/CacheItem.php
@@ -0,0 +1,209 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use DateInterval;
+use DateTime;
+use DateTimeInterface;
+use think\db\exception\InvalidArgumentException;
+
+/**
+ * CacheItem实现类
+ */
+class CacheItem
+{
+ /**
+ * 缓存Key
+ * @var string
+ */
+ protected $key;
+
+ /**
+ * 缓存内容
+ * @var mixed
+ */
+ protected $value;
+
+ /**
+ * 过期时间
+ * @var int|DateTimeInterface
+ */
+ protected $expire;
+
+ /**
+ * 缓存tag
+ * @var string
+ */
+ protected $tag;
+
+ /**
+ * 缓存是否命中
+ * @var bool
+ */
+ protected $isHit = false;
+
+ public function __construct(string $key = null)
+ {
+ $this->key = $key;
+ }
+
+ /**
+ * 为此缓存项设置「键」
+ * @access public
+ * @param string $key
+ * @return $this
+ */
+ public function setKey(string $key)
+ {
+ $this->key = $key;
+ return $this;
+ }
+
+ /**
+ * 返回当前缓存项的「键」
+ * @access public
+ * @return string
+ */
+ public function getKey()
+ {
+ return $this->key;
+ }
+
+ /**
+ * 返回当前缓存项的有效期
+ * @access public
+ * @return DateTimeInterface|int|null
+ */
+ public function getExpire()
+ {
+ if ($this->expire instanceof DateTimeInterface) {
+ return $this->expire;
+ }
+
+ return $this->expire ? $this->expire - time() : null;
+ }
+
+ /**
+ * 获取缓存Tag
+ * @access public
+ * @return string|array
+ */
+ public function getTag()
+ {
+ return $this->tag;
+ }
+
+ /**
+ * 凭借此缓存项的「键」从缓存系统里面取出缓存项
+ * @access public
+ * @return mixed
+ */
+ public function get()
+ {
+ return $this->value;
+ }
+
+ /**
+ * 确认缓存项的检查是否命中
+ * @access public
+ * @return bool
+ */
+ public function isHit(): bool
+ {
+ return $this->isHit;
+ }
+
+ /**
+ * 为此缓存项设置「值」
+ * @access public
+ * @param mixed $value
+ * @return $this
+ */
+ public function set($value)
+ {
+ $this->value = $value;
+ $this->isHit = true;
+ return $this;
+ }
+
+ /**
+ * 为此缓存项设置所属标签
+ * @access public
+ * @param string|array $tag
+ * @return $this
+ */
+ public function tag($tag = null)
+ {
+ $this->tag = $tag;
+ return $this;
+ }
+
+ /**
+ * 设置缓存项的有效期
+ * @access public
+ * @param mixed $expire
+ * @return $this
+ */
+ public function expire($expire)
+ {
+ if (is_null($expire)) {
+ $this->expire = null;
+ } elseif (is_numeric($expire) || $expire instanceof DateInterval) {
+ $this->expiresAfter($expire);
+ } elseif ($expire instanceof DateTimeInterface) {
+ $this->expire = $expire;
+ } else {
+ throw new InvalidArgumentException('not support datetime');
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置缓存项的准确过期时间点
+ * @access public
+ * @param DateTimeInterface $expiration
+ * @return $this
+ */
+ public function expiresAt($expiration)
+ {
+ if ($expiration instanceof DateTimeInterface) {
+ $this->expire = $expiration;
+ } else {
+ throw new InvalidArgumentException('not support datetime');
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置缓存项的过期时间
+ * @access public
+ * @param int|DateInterval $timeInterval
+ * @return $this
+ * @throws InvalidArgumentException
+ */
+ public function expiresAfter($timeInterval)
+ {
+ if ($timeInterval instanceof DateInterval) {
+ $this->expire = (int) DateTime::createFromFormat('U', (string) time())->add($timeInterval)->format('U');
+ } elseif (is_numeric($timeInterval)) {
+ $this->expire = $timeInterval + time();
+ } else {
+ throw new InvalidArgumentException('not support datetime');
+ }
+
+ return $this;
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/Connection.php b/vendor/topthink/think-orm/src/db/Connection.php
new file mode 100755
index 0000000..cb6a912
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/Connection.php
@@ -0,0 +1,332 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use Psr\SimpleCache\CacheInterface;
+use think\DbManager;
+
+/**
+ * 数据库连接基础类
+ */
+abstract class Connection implements ConnectionInterface
+{
+
+ /**
+ * 当前SQL指令
+ * @var string
+ */
+ protected $queryStr = '';
+
+ /**
+ * 返回或者影响记录数
+ * @var int
+ */
+ protected $numRows = 0;
+
+ /**
+ * 事务指令数
+ * @var int
+ */
+ protected $transTimes = 0;
+
+ /**
+ * 错误信息
+ * @var string
+ */
+ protected $error = '';
+
+ /**
+ * 数据库连接ID 支持多个连接
+ * @var array
+ */
+ protected $links = [];
+
+ /**
+ * 当前连接ID
+ * @var object
+ */
+ protected $linkID;
+
+ /**
+ * 当前读连接ID
+ * @var object
+ */
+ protected $linkRead;
+
+ /**
+ * 当前写连接ID
+ * @var object
+ */
+ protected $linkWrite;
+
+ /**
+ * 数据表信息
+ * @var array
+ */
+ protected $info = [];
+
+ /**
+ * 查询开始时间
+ * @var float
+ */
+ protected $queryStartTime;
+
+ /**
+ * Builder对象
+ * @var Builder
+ */
+ protected $builder;
+
+ /**
+ * Db对象
+ * @var DbManager
+ */
+ protected $db;
+
+ /**
+ * 是否读取主库
+ * @var bool
+ */
+ protected $readMaster = false;
+
+ /**
+ * 数据库连接参数配置
+ * @var array
+ */
+ protected $config = [];
+
+ /**
+ * 缓存对象
+ * @var CacheInterface
+ */
+ protected $cache;
+
+ /**
+ * 架构函数 读取数据库配置信息
+ * @access public
+ * @param array $config 数据库配置数组
+ */
+ public function __construct(array $config = [])
+ {
+ if (!empty($config)) {
+ $this->config = array_merge($this->config, $config);
+ }
+
+ // 创建Builder对象
+ $class = $this->getBuilderClass();
+
+ $this->builder = new $class($this);
+ }
+
+ /**
+ * 获取当前的builder实例对象
+ * @access public
+ * @return Builder
+ */
+ public function getBuilder()
+ {
+ return $this->builder;
+ }
+
+
+ /**
+ * 创建查询对象
+ */
+ public function newQuery()
+ {
+ $class = $this->getQueryClass();
+
+ /** @var BaseQuery $query */
+ $query = new $class($this);
+
+ $timeRule = $this->db->getConfig('time_query_rule');
+ if (!empty($timeRule)) {
+ $query->timeRule($timeRule);
+ }
+
+ return $query;
+ }
+
+ /**
+ * 指定表名开始查询
+ * @param $table
+ * @return BaseQuery
+ */
+ public function table($table)
+ {
+ return $this->newQuery()->table($table);
+ }
+
+ /**
+ * 指定表名开始查询(不带前缀)
+ * @param $name
+ * @return BaseQuery
+ */
+ public function name($name)
+ {
+ return $this->newQuery()->name($name);
+ }
+
+ /**
+ * 设置当前的数据库Db对象
+ * @access public
+ * @param DbManager $db
+ * @return void
+ */
+ public function setDb(DbManager $db)
+ {
+ $this->db = $db;
+ }
+
+ /**
+ * 设置当前的缓存对象
+ * @access public
+ * @param CacheInterface $cache
+ * @return void
+ */
+ public function setCache(CacheInterface $cache)
+ {
+ $this->cache = $cache;
+ }
+
+ /**
+ * 获取当前的缓存对象
+ * @access public
+ * @return CacheInterface|null
+ */
+ public function getCache()
+ {
+ return $this->cache;
+ }
+
+ /**
+ * 获取数据库的配置参数
+ * @access public
+ * @param string $config 配置名称
+ * @return mixed
+ */
+ public function getConfig(string $config = '')
+ {
+ if ('' === $config) {
+ return $this->config;
+ }
+
+ return $this->config[$config] ?? null;
+ }
+
+ /**
+ * 数据库SQL监控
+ * @access protected
+ * @param string $sql 执行的SQL语句 留空自动获取
+ * @param bool $master 主从标记
+ * @return void
+ */
+ protected function trigger(string $sql = '', bool $master = false): void
+ {
+ $listen = $this->db->getListen();
+
+ if (!empty($listen)) {
+ $runtime = number_format((microtime(true) - $this->queryStartTime), 6);
+ $sql = $sql ?: $this->getLastsql();
+
+ if (empty($this->config['deploy'])) {
+ $master = null;
+ }
+
+ foreach ($listen as $callback) {
+ if (is_callable($callback)) {
+ $callback($sql, $runtime, $master);
+ }
+ }
+ }
+ }
+
+ /**
+ * 缓存数据
+ * @access protected
+ * @param CacheItem $cacheItem 缓存Item
+ */
+ protected function cacheData(CacheItem $cacheItem)
+ {
+ if ($cacheItem->getTag() && method_exists($this->cache, 'tag')) {
+ $this->cache->tag($cacheItem->getTag())->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire());
+ } else {
+ $this->cache->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire());
+ }
+ }
+
+ /**
+ * 分析缓存Key
+ * @access protected
+ * @param BaseQuery $query 查询对象
+ * @param string $method 查询方法
+ * @return string
+ */
+ protected function getCacheKey(BaseQuery $query, string $method = ''): string
+ {
+ if (!empty($query->getOptions('key')) && empty($method)) {
+ $key = 'think:' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $query->getOptions('key');
+ } else {
+ $key = $query->getQueryGuid();
+ }
+
+ return $key;
+ }
+
+ /**
+ * 分析缓存
+ * @access protected
+ * @param BaseQuery $query 查询对象
+ * @param array $cache 缓存信息
+ * @param string $method 查询方法
+ * @return CacheItem
+ */
+ protected function parseCache(BaseQuery $query, array $cache, string $method = ''): CacheItem
+ {
+ [$key, $expire, $tag] = $cache;
+
+ if ($key instanceof CacheItem) {
+ $cacheItem = $key;
+ } else {
+ if (true === $key) {
+ $key = $this->getCacheKey($query, $method);
+ }
+
+ $cacheItem = new CacheItem($key);
+ $cacheItem->expire($expire);
+ $cacheItem->tag($tag);
+ }
+
+ return $cacheItem;
+ }
+
+ /**
+ * 获取返回或者影响的记录数
+ * @access public
+ * @return integer
+ */
+ public function getNumRows(): int
+ {
+ return $this->numRows;
+ }
+
+ /**
+ * 析构方法
+ * @access public
+ */
+ public function __destruct()
+ {
+ // 关闭连接
+ $this->close();
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/ConnectionInterface.php b/vendor/topthink/think-orm/src/db/ConnectionInterface.php
new file mode 100755
index 0000000..207a424
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/ConnectionInterface.php
@@ -0,0 +1,190 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use Psr\SimpleCache\CacheInterface;
+use think\DbManager;
+
+/**
+ * Connection interface
+ */
+interface ConnectionInterface
+{
+ /**
+ * 获取当前连接器类对应的Query类
+ * @access public
+ * @return string
+ */
+ public function getQueryClass(): string;
+
+ /**
+ * 指定表名开始查询
+ * @param $table
+ * @return BaseQuery
+ */
+ public function table($table);
+
+ /**
+ * 指定表名开始查询(不带前缀)
+ * @param $name
+ * @return BaseQuery
+ */
+ public function name($name);
+
+ /**
+ * 连接数据库方法
+ * @access public
+ * @param array $config 接参数
+ * @param integer $linkNum 连接序号
+ * @return mixed
+ */
+ public function connect(array $config = [], $linkNum = 0);
+
+ /**
+ * 设置当前的数据库Db对象
+ * @access public
+ * @param DbManager $db
+ * @return void
+ */
+ public function setDb(DbManager $db);
+
+ /**
+ * 设置当前的缓存对象
+ * @access public
+ * @param CacheInterface $cache
+ * @return void
+ */
+ public function setCache(CacheInterface $cache);
+
+ /**
+ * 获取数据库的配置参数
+ * @access public
+ * @param string $config 配置名称
+ * @return mixed
+ */
+ public function getConfig(string $config = '');
+
+ /**
+ * 关闭数据库(或者重新连接)
+ * @access public
+ * @return $this
+ */
+ public function close();
+
+ /**
+ * 查找单条记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return array
+ */
+ public function find(BaseQuery $query): array;
+
+ /**
+ * 查找记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return array
+ */
+ public function select(BaseQuery $query): array;
+
+ /**
+ * 插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param boolean $getLastInsID 返回自增主键
+ * @return mixed
+ */
+ public function insert(BaseQuery $query, bool $getLastInsID = false);
+
+ /**
+ * 批量插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param mixed $dataSet 数据集
+ * @return integer
+ */
+ public function insertAll(BaseQuery $query, array $dataSet = []): int;
+
+ /**
+ * 更新记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return integer
+ */
+ public function update(BaseQuery $query): int;
+
+ /**
+ * 删除记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return int
+ */
+ public function delete(BaseQuery $query): int;
+
+ /**
+ * 得到某个字段的值
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $field 字段名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function value(BaseQuery $query, string $field, $default = null);
+
+ /**
+ * 得到某个列的数组
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $column 字段名 多个字段用逗号分隔
+ * @param string $key 索引
+ * @return array
+ */
+ public function column(BaseQuery $query, string $column, string $key = ''): array;
+
+ /**
+ * 执行数据库事务
+ * @access public
+ * @param callable $callback 数据操作方法回调
+ * @return mixed
+ */
+ public function transaction(callable $callback);
+
+ /**
+ * 启动事务
+ * @access public
+ * @return void
+ */
+ public function startTrans();
+
+ /**
+ * 用于非自动提交状态下面的查询提交
+ * @access public
+ * @return void
+ */
+ public function commit();
+
+ /**
+ * 事务回滚
+ * @access public
+ * @return void
+ */
+ public function rollback();
+
+ /**
+ * 获取最近一次查询的sql语句
+ * @access public
+ * @return string
+ */
+ public function getLastSql(): string;
+
+}
diff --git a/vendor/topthink/think-orm/src/db/Fetch.php b/vendor/topthink/think-orm/src/db/Fetch.php
new file mode 100755
index 0000000..16caed2
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/Fetch.php
@@ -0,0 +1,494 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
+
+/**
+ * SQL获取类
+ */
+class Fetch
+{
+ /**
+ * 查询对象
+ * @var Query
+ */
+ protected $query;
+
+ /**
+ * Connection对象
+ * @var Connection
+ */
+ protected $connection;
+
+ /**
+ * Builder对象
+ * @var Builder
+ */
+ protected $builder;
+
+ /**
+ * 创建一个查询SQL获取对象
+ *
+ * @param Query $query 查询对象
+ */
+ public function __construct(Query $query)
+ {
+ $this->query = $query;
+ $this->connection = $query->getConnection();
+ $this->builder = $this->connection->getBuilder();
+ }
+
+ /**
+ * 聚合查询
+ * @access protected
+ * @param string $aggregate 聚合方法
+ * @param string $field 字段名
+ * @return string
+ */
+ protected function aggregate(string $aggregate, string $field): string
+ {
+ $this->query->parseOptions();
+
+ $field = $aggregate . '(' . $this->builder->parseKey($this->query, $field) . ') AS think_' . strtolower($aggregate);
+
+ return $this->value($field, 0, false);
+ }
+
+ /**
+ * 得到某个字段的值
+ * @access public
+ * @param string $field 字段名
+ * @param mixed $default 默认值
+ * @param bool $one
+ * @return string
+ */
+ public function value(string $field, $default = null, bool $one = true): string
+ {
+ $options = $this->query->parseOptions();
+
+ if (isset($options['field'])) {
+ $this->query->removeOption('field');
+ }
+
+ $this->query->setOption('field', (array) $field);
+
+ // 生成查询SQL
+ $sql = $this->builder->select($this->query, $one);
+
+ if (isset($options['field'])) {
+ $this->query->setOption('field', $options['field']);
+ } else {
+ $this->query->removeOption('field');
+ }
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 得到某个列的数组
+ * @access public
+ * @param string $field 字段名 多个字段用逗号分隔
+ * @param string $key 索引
+ * @return string
+ */
+ public function column(string $field, string $key = ''): string
+ {
+ $options = $this->query->parseOptions();
+
+ if (isset($options['field'])) {
+ $this->query->removeOption('field');
+ }
+
+ if ($key && '*' != $field) {
+ $field = $key . ',' . $field;
+ }
+
+ $field = array_map('trim', explode(',', $field));
+
+ $this->query->setOption('field', $field);
+
+ // 生成查询SQL
+ $sql = $this->builder->select($this->query);
+
+ if (isset($options['field'])) {
+ $this->query->setOption('field', $options['field']);
+ } else {
+ $this->query->removeOption('field');
+ }
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 插入记录
+ * @access public
+ * @param array $data 数据
+ * @return string
+ */
+ public function insert(array $data = []): string
+ {
+ $options = $this->query->parseOptions();
+
+ if (!empty($data)) {
+ $this->query->setOption('data', $data);
+ }
+
+ $sql = $this->builder->insert($this->query);
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 插入记录并获取自增ID
+ * @access public
+ * @param array $data 数据
+ * @return string
+ */
+ public function insertGetId(array $data = []): string
+ {
+ return $this->insert($data);
+ }
+
+ /**
+ * 保存数据 自动判断insert或者update
+ * @access public
+ * @param array $data 数据
+ * @param bool $forceInsert 是否强制insert
+ * @return string
+ */
+ public function save(array $data = [], bool $forceInsert = false): string
+ {
+ if ($forceInsert) {
+ return $this->insert($data);
+ }
+
+ $data = array_merge($this->query->getOptions('data') ?: [], $data);
+
+ $this->query->setOption('data', $data);
+
+ if ($this->query->getOptions('where')) {
+ $isUpdate = true;
+ } else {
+ $isUpdate = $this->query->parseUpdateData($data);
+ }
+
+ return $isUpdate ? $this->update() : $this->insert();
+ }
+
+ /**
+ * 批量插入记录
+ * @access public
+ * @param array $dataSet 数据集
+ * @param integer $limit 每次写入数据限制
+ * @return string
+ */
+ public function insertAll(array $dataSet = [], int $limit = null): string
+ {
+ $options = $this->query->parseOptions();
+
+ if (empty($dataSet)) {
+ $dataSet = $options['data'];
+ }
+
+ if (empty($limit) && !empty($options['limit'])) {
+ $limit = $options['limit'];
+ }
+
+ if ($limit) {
+ $array = array_chunk($dataSet, $limit, true);
+ $fetchSql = [];
+ foreach ($array as $item) {
+ $sql = $this->builder->insertAll($this->query, $item);
+ $bind = $this->query->getBind();
+
+ $fetchSql[] = $this->connection->getRealSql($sql, $bind);
+ }
+
+ return implode(';', $fetchSql);
+ }
+
+ $sql = $this->builder->insertAll($this->query, $dataSet);
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 通过Select方式插入记录
+ * @access public
+ * @param array $fields 要插入的数据表字段名
+ * @param string $table 要插入的数据表名
+ * @return string
+ */
+ public function selectInsert(array $fields, string $table): string
+ {
+ $this->query->parseOptions();
+
+ $sql = $this->builder->selectInsert($this->query, $fields, $table);
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 更新记录
+ * @access public
+ * @param mixed $data 数据
+ * @return string
+ */
+ public function update(array $data = []): string
+ {
+ $options = $this->query->parseOptions();
+
+ $data = !empty($data) ? $data : $options['data'];
+
+ $pk = $this->query->getPk();
+
+ if (empty($options['where'])) {
+ // 如果存在主键数据 则自动作为更新条件
+ if (is_string($pk) && isset($data[$pk])) {
+ $this->query->where($pk, '=', $data[$pk]);
+ unset($data[$pk]);
+ } elseif (is_array($pk)) {
+ // 增加复合主键支持
+ foreach ($pk as $field) {
+ if (isset($data[$field])) {
+ $this->query->where($field, '=', $data[$field]);
+ } else {
+ // 如果缺少复合主键数据则不执行
+ throw new Exception('miss complex primary data');
+ }
+ unset($data[$field]);
+ }
+ }
+
+ if (empty($this->query->getOptions('where'))) {
+ // 如果没有任何更新条件则不执行
+ throw new Exception('miss update condition');
+ }
+ }
+
+ // 更新数据
+ $this->query->setOption('data', $data);
+
+ // 生成UPDATE SQL语句
+ $sql = $this->builder->update($this->query);
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param mixed $data 表达式 true 表示强制删除
+ * @return string
+ */
+ public function delete($data = null): string
+ {
+ $options = $this->query->parseOptions();
+
+ if (!is_null($data) && true !== $data) {
+ // AR模式分析主键条件
+ $this->query->parsePkWhere($data);
+ }
+
+ if (!empty($options['soft_delete'])) {
+ // 软删除
+ [$field, $condition] = $options['soft_delete'];
+ if ($condition) {
+ $this->query->setOption('soft_delete', null);
+ $this->query->setOption('data', [$field => $condition]);
+ // 生成删除SQL语句
+ $sql = $this->builder->delete($this->query);
+ return $this->fetch($sql);
+ }
+ }
+
+ // 生成删除SQL语句
+ $sql = $this->builder->delete($this->query);
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 查找记录 返回SQL
+ * @access public
+ * @param mixed $data
+ * @return string
+ */
+ public function select($data = null): string
+ {
+ $this->query->parseOptions();
+
+ if (!is_null($data)) {
+ // 主键条件分析
+ $this->query->parsePkWhere($data);
+ }
+
+ // 生成查询SQL
+ $sql = $this->builder->select($this->query);
+
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 查找单条记录 返回SQL语句
+ * @access public
+ * @param mixed $data
+ * @return string
+ */
+ public function find($data = null): string
+ {
+ $this->query->parseOptions();
+
+ if (!is_null($data)) {
+ // AR模式分析主键条件
+ $this->query->parsePkWhere($data);
+ }
+
+ // 生成查询SQL
+ $sql = $this->builder->select($this->query, true);
+
+ // 获取实际执行的SQL语句
+ return $this->fetch($sql);
+ }
+
+ /**
+ * 查找多条记录 如果不存在则抛出异常
+ * @access public
+ * @param mixed $data
+ * @return string
+ */
+ public function selectOrFail($data = null): string
+ {
+ return $this->select($data);
+ }
+
+ /**
+ * 查找单条记录 如果不存在则抛出异常
+ * @access public
+ * @param mixed $data
+ * @return string
+ */
+ public function findOrFail($data = null): string
+ {
+ return $this->find($data);
+ }
+
+ /**
+ * 查找单条记录 不存在返回空数据(或者空模型)
+ * @access public
+ * @param mixed $data 数据
+ * @return string
+ */
+ public function findOrEmpty($data = null)
+ {
+ return $this->find($data);
+ }
+
+ /**
+ * 获取实际的SQL语句
+ * @access public
+ * @param string $sql
+ * @return string
+ */
+ public function fetch(string $sql): string
+ {
+ $bind = $this->query->getBind();
+
+ return $this->connection->getRealSql($sql, $bind);
+ }
+
+ /**
+ * COUNT查询
+ * @access public
+ * @param string $field 字段名
+ * @return string
+ */
+ public function count(string $field = '*'): string
+ {
+ $options = $this->query->parseOptions();
+
+ if (!empty($options['group'])) {
+ // 支持GROUP
+ $bind = $this->query->getBind();
+ $subSql = $this->query->options($options)->field('count(' . $field . ') AS think_count')->bind($bind)->buildSql();
+
+ $query = $this->query->newQuery()->table([$subSql => '_group_count_']);
+
+ return $query->fetchsql()->aggregate('COUNT', '*');
+ } else {
+ return $this->aggregate('COUNT', $field);
+ }
+ }
+
+ /**
+ * SUM查询
+ * @access public
+ * @param string $field 字段名
+ * @return string
+ */
+ public function sum(string $field): string
+ {
+ return $this->aggregate('SUM', $field);
+ }
+
+ /**
+ * MIN查询
+ * @access public
+ * @param string $field 字段名
+ * @return string
+ */
+ public function min(string $field): string
+ {
+ return $this->aggregate('MIN', $field);
+ }
+
+ /**
+ * MAX查询
+ * @access public
+ * @param string $field 字段名
+ * @return string
+ */
+ public function max(string $field): string
+ {
+ return $this->aggregate('MAX', $field);
+ }
+
+ /**
+ * AVG查询
+ * @access public
+ * @param string $field 字段名
+ * @return string
+ */
+ public function avg(string $field): string
+ {
+ return $this->aggregate('AVG', $field);
+ }
+
+ public function __call($method, $args)
+ {
+ if (strtolower(substr($method, 0, 5)) == 'getby') {
+ // 根据某个字段获取记录
+ $field = Str::snake(substr($method, 5));
+ return $this->where($field, '=', $args[0])->find();
+ } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') {
+ // 根据某个字段获取记录的某个值
+ $name = Str::snake(substr($method, 10));
+ return $this->where($name, '=', $args[0])->value($args[1]);
+ }
+
+ $result = call_user_func_array([$this->query, $method], $args);
+ return $result === $this->query ? $this : $result;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/Mongo.php b/vendor/topthink/think-orm/src/db/Mongo.php
new file mode 100755
index 0000000..5e8a09a
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/Mongo.php
@@ -0,0 +1,712 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+namespace think\db;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Exception\AuthenticationException;
+use MongoDB\Driver\Exception\ConnectionException;
+use MongoDB\Driver\Exception\InvalidArgumentException;
+use MongoDB\Driver\Exception\RuntimeException;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\WriteConcern;
+use think\db\exception\DbException as Exception;
+use think\Paginator;
+
+class Mongo extends BaseQuery
+{
+ /**
+ * 当前数据库连接对象
+ * @var \think\db\connector\Mongo
+ */
+ protected $connection;
+
+ /**
+ * 执行指令 返回数据集
+ * @access public
+ * @param Command $command 指令
+ * @param string $dbName
+ * @param ReadPreference $readPreference readPreference
+ * @param string|array $typeMap 指定返回的typeMap
+ * @return mixed
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null)
+ {
+ return $this->connection->command($command, $dbName, $readPreference, $typeMap);
+ }
+
+ /**
+ * 执行command
+ * @access public
+ * @param string|array|object $command 指令
+ * @param mixed $extra 额外参数
+ * @param string $db 数据库名
+ * @return array
+ */
+ public function cmd($command, $extra = null, string $db = ''): array
+ {
+ $this->parseOptions();
+ return $this->connection->cmd($this, $command, $extra, $db);
+ }
+
+ /**
+ * 指定distinct查询
+ * @access public
+ * @param string $field 字段名
+ * @return array
+ */
+ public function getDistinct(string $field)
+ {
+ $result = $this->cmd('distinct', $field);
+ return $result[0]['values'];
+ }
+
+ /**
+ * 获取数据库的所有collection
+ * @access public
+ * @param string $db 数据库名称 留空为当前数据库
+ * @throws Exception
+ */
+ public function listCollections(string $db = '')
+ {
+ $cursor = $this->cmd('listCollections', null, $db);
+ $result = [];
+ foreach ($cursor as $collection) {
+ $result[] = $collection['name'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * COUNT查询
+ * @access public
+ * @param string $field 字段名
+ * @return integer
+ */
+ public function count(string $field = null): int
+ {
+ $result = $this->cmd('count');
+
+ return $result[0]['n'];
+ }
+
+ /**
+ * 聚合查询
+ * @access public
+ * @param string $aggregate 聚合指令
+ * @param string $field 字段名
+ * @param bool $force 强制转为数字类型
+ * @return mixed
+ */
+ public function aggregate(string $aggregate, $field, bool $force = false)
+ {
+ $result = $this->cmd('aggregate', [strtolower($aggregate), $field]);
+ $value = $result[0]['aggregate'] ?? 0;
+
+ if ($force) {
+ $value += 0;
+ }
+
+ return $value;
+ }
+
+ /**
+ * 多聚合操作
+ *
+ * @param array $aggregate 聚合指令, 可以聚合多个参数, 如 ['sum' => 'field1', 'avg' => 'field2']
+ * @param array $groupBy 类似mysql里面的group字段, 可以传入多个字段, 如 ['field_a', 'field_b', 'field_c']
+ * @return array 查询结果
+ */
+ public function multiAggregate(array $aggregate, array $groupBy): array
+ {
+ $result = $this->cmd('multiAggregate', [$aggregate, $groupBy]);
+
+ foreach ($result as &$row) {
+ if (isset($row['_id']) && !empty($row['_id'])) {
+ foreach ($row['_id'] as $k => $v) {
+ $row[$k] = $v;
+ }
+ unset($row['_id']);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 字段值增长
+ * @access public
+ * @param string $field 字段名
+ * @param float $step 增长值
+ * @return $this
+ */
+ public function inc(string $field, float $step = 1)
+ {
+ $this->options['data'][$field] = ['$inc', $step];
+
+ return $this;
+ }
+
+ /**
+ * 字段值减少
+ * @access public
+ * @param string $field 字段名
+ * @param float $step 减少值
+ * @return $this
+ */
+ public function dec(string $field, float $step = 1)
+ {
+ return $this->inc($field, -1 * $step);
+ }
+
+ /**
+ * 指定当前操作的Collection
+ * @access public
+ * @param string $table 表名
+ * @return $this
+ */
+ public function table($table)
+ {
+ $this->options['table'] = $table;
+
+ return $this;
+ }
+
+ /**
+ * table方法的别名
+ * @access public
+ * @param string $collection
+ * @return $this
+ */
+ public function collection(string $collection)
+ {
+ return $this->table($collection);
+ }
+
+ /**
+ * 设置typeMap
+ * @access public
+ * @param string|array $typeMap
+ * @return $this
+ */
+ public function typeMap($typeMap)
+ {
+ $this->options['typeMap'] = $typeMap;
+ return $this;
+ }
+
+ /**
+ * awaitData
+ * @access public
+ * @param bool $awaitData
+ * @return $this
+ */
+ public function awaitData(bool $awaitData)
+ {
+ $this->options['awaitData'] = $awaitData;
+ return $this;
+ }
+
+ /**
+ * batchSize
+ * @access public
+ * @param integer $batchSize
+ * @return $this
+ */
+ public function batchSize(int $batchSize)
+ {
+ $this->options['batchSize'] = $batchSize;
+ return $this;
+ }
+
+ /**
+ * exhaust
+ * @access public
+ * @param bool $exhaust
+ * @return $this
+ */
+ public function exhaust(bool $exhaust)
+ {
+ $this->options['exhaust'] = $exhaust;
+ return $this;
+ }
+
+ /**
+ * 设置modifiers
+ * @access public
+ * @param array $modifiers
+ * @return $this
+ */
+ public function modifiers(array $modifiers)
+ {
+ $this->options['modifiers'] = $modifiers;
+ return $this;
+ }
+
+ /**
+ * 设置noCursorTimeout
+ * @access public
+ * @param bool $noCursorTimeout
+ * @return $this
+ */
+ public function noCursorTimeout(bool $noCursorTimeout)
+ {
+ $this->options['noCursorTimeout'] = $noCursorTimeout;
+ return $this;
+ }
+
+ /**
+ * 设置oplogReplay
+ * @access public
+ * @param bool $oplogReplay
+ * @return $this
+ */
+ public function oplogReplay(bool $oplogReplay)
+ {
+ $this->options['oplogReplay'] = $oplogReplay;
+ return $this;
+ }
+
+ /**
+ * 设置partial
+ * @access public
+ * @param bool $partial
+ * @return $this
+ */
+ public function partial(bool $partial)
+ {
+ $this->options['partial'] = $partial;
+ return $this;
+ }
+
+ /**
+ * maxTimeMS
+ * @access public
+ * @param string $maxTimeMS
+ * @return $this
+ */
+ public function maxTimeMS(string $maxTimeMS)
+ {
+ $this->options['maxTimeMS'] = $maxTimeMS;
+ return $this;
+ }
+
+ /**
+ * collation
+ * @access public
+ * @param array $collation
+ * @return $this
+ */
+ public function collation(array $collation)
+ {
+ $this->options['collation'] = $collation;
+ return $this;
+ }
+
+ /**
+ * 设置是否REPLACE
+ * @access public
+ * @param bool $replace 是否使用REPLACE写入数据
+ * @return $this
+ */
+ public function replace(bool $replace = true)
+ {
+ return $this;
+ }
+
+ /**
+ * 设置返回字段
+ * @access public
+ * @param mixed $field 字段信息
+ * @return $this
+ */
+ public function field($field)
+ {
+ if (empty($field) || '*' == $field) {
+ return $this;
+ }
+
+ if (is_string($field)) {
+ $field = array_map('trim', explode(',', $field));
+ }
+
+ $projection = [];
+ foreach ($field as $key => $val) {
+ if (is_numeric($key)) {
+ $projection[$val] = 1;
+ } else {
+ $projection[$key] = $val;
+ }
+ }
+
+ $this->options['projection'] = $projection;
+
+ return $this;
+ }
+
+ /**
+ * 指定要排除的查询字段
+ * @access public
+ * @param array|string $field 要排除的字段
+ * @return $this
+ */
+ public function withoutField($field)
+ {
+ if (empty($field) || '*' == $field) {
+ return $this;
+ }
+
+ if (is_string($field)) {
+ $field = array_map('trim', explode(',', $field));
+ }
+
+ $projection = [];
+ foreach ($field as $key => $val) {
+ if (is_numeric($key)) {
+ $projection[$val] = 0;
+ } else {
+ $projection[$key] = $val;
+ }
+ }
+
+ $this->options['projection'] = $projection;
+ return $this;
+ }
+
+ /**
+ * 设置skip
+ * @access public
+ * @param integer $skip
+ * @return $this
+ */
+ public function skip(int $skip)
+ {
+ $this->options['skip'] = $skip;
+ return $this;
+ }
+
+ /**
+ * 设置slaveOk
+ * @access public
+ * @param bool $slaveOk
+ * @return $this
+ */
+ public function slaveOk(bool $slaveOk)
+ {
+ $this->options['slaveOk'] = $slaveOk;
+ return $this;
+ }
+
+ /**
+ * 指定查询数量
+ * @access public
+ * @param int $offset 起始位置
+ * @param int $length 查询数量
+ * @return $this
+ */
+ public function limit(int $offset, int $length = null)
+ {
+ if (is_null($length)) {
+ $length = $offset;
+ $offset = 0;
+ }
+
+ $this->options['skip'] = $offset;
+ $this->options['limit'] = $length;
+
+ return $this;
+ }
+
+ /**
+ * 设置sort
+ * @access public
+ * @param array|string $field
+ * @param string $order
+ * @return $this
+ */
+ public function order($field, string $order = '')
+ {
+ if (is_array($field)) {
+ $this->options['sort'] = $field;
+ } else {
+ $this->options['sort'][$field] = 'asc' == strtolower($order) ? 1 : -1;
+ }
+ return $this;
+ }
+
+ /**
+ * 设置tailable
+ * @access public
+ * @param bool $tailable
+ * @return $this
+ */
+ public function tailable(bool $tailable)
+ {
+ $this->options['tailable'] = $tailable;
+ return $this;
+ }
+
+ /**
+ * 设置writeConcern对象
+ * @access public
+ * @param WriteConcern $writeConcern
+ * @return $this
+ */
+ public function writeConcern(WriteConcern $writeConcern)
+ {
+ $this->options['writeConcern'] = $writeConcern;
+ return $this;
+ }
+
+ /**
+ * 获取当前数据表的主键
+ * @access public
+ * @return string|array
+ */
+ public function getPk()
+ {
+ return $this->pk ?: $this->connection->getConfig('pk');
+ }
+
+ /**
+ * 执行查询但只返回Cursor对象
+ * @access public
+ * @return Cursor
+ */
+ public function getCursor(): Cursor
+ {
+ $this->parseOptions();
+
+ return $this->connection->getCursor($this);
+ }
+
+ /**
+ * 获取当前的查询标识
+ * @access public
+ * @param mixed $data 要序列化的数据
+ * @return string
+ */
+ public function getQueryGuid($data = null): string
+ {
+ return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true)));
+ }
+
+ /**
+ * 分页查询
+ * @access public
+ * @param int|array $listRows 每页数量 数组表示配置参数
+ * @param int|bool $simple 是否简洁模式或者总记录数
+ * @return Paginator
+ * @throws Exception
+ */
+ public function paginate($listRows = null, $simple = false): Paginator
+ {
+ if (is_int($simple)) {
+ $total = $simple;
+ $simple = false;
+ }
+
+ $defaultConfig = [
+ 'query' => [], //url额外参数
+ 'fragment' => '', //url锚点
+ 'var_page' => 'page', //分页变量
+ 'list_rows' => 15, //每页数量
+ ];
+
+ if (is_array($listRows)) {
+ $config = array_merge($defaultConfig, $listRows);
+ $listRows = intval($config['list_rows']);
+ } else {
+ $config = $defaultConfig;
+ $listRows = intval($listRows ?: $config['list_rows']);
+ }
+
+ $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']);
+
+ $page = $page < 1 ? 1 : $page;
+
+ $config['path'] = $config['path'] ?? Paginator::getCurrentPath();
+
+ if (!isset($total) && !$simple) {
+ $options = $this->getOptions();
+
+ unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']);
+
+ $total = $this->count();
+ $results = $this->options($options)->page($page, $listRows)->select();
+ } elseif ($simple) {
+ $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select();
+ $total = null;
+ } else {
+ $results = $this->page($page, $listRows)->select();
+ }
+
+ $this->removeOption('limit');
+ $this->removeOption('page');
+
+ return Paginator::make($results, $listRows, $page, $total, $simple, $config);
+ }
+
+ /**
+ * 分批数据返回处理
+ * @access public
+ * @param integer $count 每次处理的数据数量
+ * @param callable $callback 处理回调方法
+ * @param string|array $column 分批处理的字段名
+ * @param string $order 字段排序
+ * @return bool
+ * @throws Exception
+ */
+ public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
+ {
+ $options = $this->getOptions();
+ $column = $column ?: $this->getPk();
+
+ if (isset($options['order'])) {
+ unset($options['order']);
+ }
+
+ if (is_array($column)) {
+ $times = 1;
+ $query = $this->options($options)->page($times, $count);
+ } else {
+ $query = $this->options($options)->limit($count);
+
+ if (strpos($column, '.')) {
+ [$alias, $key] = explode('.', $column);
+ } else {
+ $key = $column;
+ }
+ }
+
+ $resultSet = $query->order($column, $order)->select();
+
+ while (count($resultSet) > 0) {
+ if (false === call_user_func($callback, $resultSet)) {
+ return false;
+ }
+
+ if (isset($times)) {
+ $times++;
+ $query = $this->options($options)->page($times, $count);
+ } else {
+ $end = $resultSet->pop();
+ $lastId = is_array($end) ? $end[$key] : $end->getData($key);
+
+ $query = $this->options($options)
+ ->limit($count)
+ ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
+ }
+
+ $resultSet = $query->order($column, $order)->select();
+ }
+
+ return true;
+ }
+
+ /**
+ * 分析表达式(可用于查询或者写入操作)
+ * @access public
+ * @return array
+ */
+ public function parseOptions(): array
+ {
+ $options = $this->options;
+
+ // 获取数据表
+ if (empty($options['table'])) {
+ $options['table'] = $this->getTable();
+ }
+
+ foreach (['where', 'data'] as $name) {
+ if (!isset($options[$name])) {
+ $options[$name] = [];
+ }
+ }
+
+ $modifiers = empty($options['modifiers']) ? [] : $options['modifiers'];
+ if (isset($options['comment'])) {
+ $modifiers['$comment'] = $options['comment'];
+ }
+
+ if (isset($options['maxTimeMS'])) {
+ $modifiers['$maxTimeMS'] = $options['maxTimeMS'];
+ }
+
+ if (!empty($modifiers)) {
+ $options['modifiers'] = $modifiers;
+ }
+
+ if (!isset($options['projection'])) {
+ $options['projection'] = [];
+ }
+
+ if (!isset($options['typeMap'])) {
+ $options['typeMap'] = $this->getConfig('type_map');
+ }
+
+ if (!isset($options['limit'])) {
+ $options['limit'] = 0;
+ }
+
+ foreach (['master', 'fetch_sql', 'fetch_cursor'] as $name) {
+ if (!isset($options[$name])) {
+ $options[$name] = false;
+ }
+ }
+
+ if (isset($options['page'])) {
+ // 根据页数计算limit
+ [$page, $listRows] = $options['page'];
+
+ $page = $page > 0 ? $page : 1;
+ $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20);
+ $offset = $listRows * ($page - 1);
+ $options['skip'] = intval($offset);
+ $options['limit'] = intval($listRows);
+ }
+
+ $this->options = $options;
+
+ return $options;
+ }
+
+ /**
+ * 获取字段类型信息
+ * @access public
+ * @return array
+ */
+ public function getFieldsType(): array
+ {
+ if (!empty($this->options['field_type'])) {
+ return $this->options['field_type'];
+ }
+
+ return [];
+ }
+
+ /**
+ * 获取字段类型信息
+ * @access public
+ * @param string $field 字段名
+ * @return string|null
+ */
+ public function getFieldType(string $field)
+ {
+ $fieldType = $this->getFieldsType();
+
+ return $fieldType[$field] ?? null;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/PDOConnection.php b/vendor/topthink/think-orm/src/db/PDOConnection.php
new file mode 100755
index 0000000..540f135
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/PDOConnection.php
@@ -0,0 +1,1746 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use Closure;
+use PDO;
+use PDOStatement;
+use think\db\exception\BindParamException;
+use think\db\exception\DbException;
+use think\db\exception\PDOException;
+
+/**
+ * 数据库连接基础类
+ */
+abstract class PDOConnection extends Connection
+{
+ const PARAM_FLOAT = 21;
+
+ /**
+ * 数据库连接参数配置
+ * @var array
+ */
+ protected $config = [
+ // 数据库类型
+ 'type' => '',
+ // 服务器地址
+ 'hostname' => '',
+ // 数据库名
+ 'database' => '',
+ // 用户名
+ 'username' => '',
+ // 密码
+ 'password' => '',
+ // 端口
+ 'hostport' => '',
+ // 连接dsn
+ 'dsn' => '',
+ // 数据库连接参数
+ 'params' => [],
+ // 数据库编码默认采用utf8
+ 'charset' => 'utf8',
+ // 数据库表前缀
+ 'prefix' => '',
+ // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+ 'deploy' => 0,
+ // 数据库读写是否分离 主从式有效
+ 'rw_separate' => false,
+ // 读写分离后 主服务器数量
+ 'master_num' => 1,
+ // 指定从服务器序号
+ 'slave_no' => '',
+ // 模型写入后自动读取主服务器
+ 'read_master' => false,
+ // 是否严格检查字段是否存在
+ 'fields_strict' => true,
+ // 开启字段缓存
+ 'fields_cache' => false,
+ // 监听SQL
+ 'trigger_sql' => true,
+ // Builder类
+ 'builder' => '',
+ // Query类
+ 'query' => '',
+ // 是否需要断线重连
+ 'break_reconnect' => false,
+ // 断线标识字符串
+ 'break_match_str' => [],
+ ];
+
+ /**
+ * PDO操作实例
+ * @var PDOStatement
+ */
+ protected $PDOStatement;
+
+ /**
+ * 当前SQL指令
+ * @var string
+ */
+ protected $queryStr = '';
+
+ /**
+ * 事务指令数
+ * @var int
+ */
+ protected $transTimes = 0;
+
+ /**
+ * 重连次数
+ * @var int
+ */
+ protected $reConnectTimes = 0;
+
+ /**
+ * 查询结果类型
+ * @var int
+ */
+ protected $fetchType = PDO::FETCH_ASSOC;
+
+ /**
+ * 字段属性大小写
+ * @var int
+ */
+ protected $attrCase = PDO::CASE_LOWER;
+
+ /**
+ * 数据表信息
+ * @var array
+ */
+ protected $info = [];
+
+ /**
+ * 查询开始时间
+ * @var float
+ */
+ protected $queryStartTime;
+
+ /**
+ * PDO连接参数
+ * @var array
+ */
+ protected $params = [
+ PDO::ATTR_CASE => PDO::CASE_NATURAL,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
+ PDO::ATTR_STRINGIFY_FETCHES => false,
+ PDO::ATTR_EMULATE_PREPARES => false,
+ ];
+
+ /**
+ * 参数绑定类型映射
+ * @var array
+ */
+ protected $bindType = [
+ 'string' => PDO::PARAM_STR,
+ 'str' => PDO::PARAM_STR,
+ 'integer' => PDO::PARAM_INT,
+ 'int' => PDO::PARAM_INT,
+ 'boolean' => PDO::PARAM_BOOL,
+ 'bool' => PDO::PARAM_BOOL,
+ 'float' => self::PARAM_FLOAT,
+ 'datetime' => PDO::PARAM_STR,
+ 'timestamp' => PDO::PARAM_STR,
+ ];
+
+ /**
+ * 服务器断线标识字符
+ * @var array
+ */
+ protected $breakMatchStr = [
+ 'server has gone away',
+ 'no connection to the server',
+ 'Lost connection',
+ 'is dead or not enabled',
+ 'Error while sending',
+ 'decryption failed or bad record mac',
+ 'server closed the connection unexpectedly',
+ 'SSL connection has been closed unexpectedly',
+ 'Error writing data to the connection',
+ 'Resource deadlock avoided',
+ 'failed with errno',
+ ];
+
+ /**
+ * 绑定参数
+ * @var array
+ */
+ protected $bind = [];
+
+ /**
+ * 获取当前连接器类对应的Query类
+ * @access public
+ * @return string
+ */
+ public function getQueryClass(): string
+ {
+ return $this->getConfig('query') ?: Query::class;
+ }
+
+ /**
+ * 获取当前连接器类对应的Builder类
+ * @access public
+ * @return string
+ */
+ public function getBuilderClass(): string
+ {
+ return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type'));
+ }
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access protected
+ * @param array $config 连接信息
+ * @return string
+ */
+ abstract protected function parseDsn(array $config);
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @param string $tableName 数据表名称
+ * @return array
+ */
+ abstract public function getFields(string $tableName);
+
+ /**
+ * 取得数据库的表信息
+ * @access public
+ * @param string $dbName 数据库名称
+ * @return array
+ */
+ abstract public function getTables(string $dbName);
+
+ /**
+ * 对返数据表字段信息进行大小写转换出来
+ * @access public
+ * @param array $info 字段信息
+ * @return array
+ */
+ public function fieldCase(array $info): array
+ {
+ // 字段大小写转换
+ switch ($this->attrCase) {
+ case PDO::CASE_LOWER:
+ $info = array_change_key_case($info);
+ break;
+ case PDO::CASE_UPPER:
+ $info = array_change_key_case($info, CASE_UPPER);
+ break;
+ case PDO::CASE_NATURAL:
+ default:
+ // 不做转换
+ }
+
+ return $info;
+ }
+
+ /**
+ * 获取字段类型
+ * @access protected
+ * @param string $type 字段类型
+ * @return string
+ */
+ protected function getFieldType(string $type): string
+ {
+ if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) {
+ $result = 'string';
+ } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) {
+ $result = 'float';
+ } elseif (preg_match('/(int|serial|bit)/is', $type)) {
+ $result = 'int';
+ } elseif (preg_match('/bool/is', $type)) {
+ $result = 'bool';
+ } elseif (0 === strpos($type, 'timestamp')) {
+ $result = 'timestamp';
+ } elseif (0 === strpos($type, 'datetime')) {
+ $result = 'datetime';
+ } elseif (0 === strpos($type, 'date')) {
+ $result = 'date';
+ } else {
+ $result = 'string';
+ }
+
+ return $result;
+ }
+
+ /**
+ * 获取字段绑定类型
+ * @access public
+ * @param string $type 字段类型
+ * @return integer
+ */
+ public function getFieldBindType(string $type): int
+ {
+ if (in_array($type, ['integer', 'string', 'float', 'boolean', 'bool', 'int', 'str'])) {
+ $bind = $this->bindType[$type];
+ } elseif (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) {
+ $bind = PDO::PARAM_STR;
+ } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) {
+ $bind = self::PARAM_FLOAT;
+ } elseif (preg_match('/(int|serial|bit)/is', $type)) {
+ $bind = PDO::PARAM_INT;
+ } elseif (preg_match('/bool/is', $type)) {
+ $bind = PDO::PARAM_BOOL;
+ } else {
+ $bind = PDO::PARAM_STR;
+ }
+
+ return $bind;
+ }
+
+ /**
+ * 获取数据表信息缓存key
+ * @access protected
+ * @param string $schema 数据表名称
+ * @return string
+ */
+ protected function getSchemaCacheKey(string $schema): string
+ {
+ return $this->getConfig('hostname') . ':' . $this->getConfig('hostport') . '@' . $schema;
+ }
+
+ /**
+ * @param string $tableName 数据表名称
+ * @param bool $force 强制从数据库获取
+ * @return array
+ */
+ public function getSchemaInfo(string $tableName, $force = false)
+ {
+ if (!strpos($tableName, '.')) {
+ $schema = $this->getConfig('database') . '.' . $tableName;
+ } else {
+ $schema = $tableName;
+ }
+
+ if (!isset($this->info[$schema]) || $force) {
+ // 读取字段缓存
+ $cacheKey = $this->getSchemaCacheKey($schema);
+ $cacheField = $this->config['fields_cache'] && !empty($this->cache);
+
+ if ($cacheField && !$force) {
+ $info = $this->cache->get($cacheKey);
+ }
+
+ if (empty($info)) {
+ $info = $this->getTableFieldsInfo($tableName);
+ if ($cacheField) {
+ $this->cache->set($cacheKey, $info);
+ }
+ }
+
+ $pk = $info['_pk'] ?? null;
+ $autoinc = $info['_autoinc'] ?? null;
+ unset($info['_pk'], $info['_autoinc']);
+
+ $bind = [];
+ foreach ($info as $name => $val) {
+ $bind[$name] = $this->getFieldBindType($val);
+ }
+
+ $this->info[$schema] = [
+ 'fields' => array_keys($info),
+ 'type' => $info,
+ 'bind' => $bind,
+ 'pk' => $pk,
+ 'autoinc' => $autoinc,
+ ];
+ }
+
+ return $this->info[$schema];
+ }
+
+ /**
+ * 获取数据表信息
+ * @access public
+ * @param mixed $tableName 数据表名 留空自动获取
+ * @param string $fetch 获取信息类型 包括 fields type bind pk
+ * @return mixed
+ */
+ public function getTableInfo($tableName, string $fetch = '')
+ {
+ if (is_array($tableName)) {
+ $tableName = key($tableName) ?: current($tableName);
+ }
+
+ if (strpos($tableName, ',') || strpos($tableName, ')')) {
+ // 多表不获取字段信息
+ return [];
+ }
+
+ [$tableName] = explode(' ', $tableName);
+
+ $info = $this->getSchemaInfo($tableName);
+
+ return $fetch ? $info[$fetch] : $info;
+ }
+
+ /**
+ * 获取数据表的字段信息
+ * @access public
+ * @param string $tableName 数据表名
+ * @return array
+ */
+ public function getTableFieldsInfo(string $tableName): array
+ {
+ $fields = $this->getFields($tableName);
+ $info = [];
+
+ foreach ($fields as $key => $val) {
+ // 记录字段类型
+ $info[$key] = $this->getFieldType($val['type']);
+
+ if (!empty($val['primary'])) {
+ $pk[] = $key;
+ }
+
+ if (!empty($val['autoinc'])) {
+ $autoinc = $key;
+ }
+ }
+
+ if (isset($pk)) {
+ // 设置主键
+ $pk = count($pk) > 1 ? $pk : $pk[0];
+ $info['_pk'] = $pk;
+ }
+
+ if (isset($autoinc)) {
+ $info['_autoinc'] = $autoinc;
+ }
+
+ return $info;
+ }
+
+ /**
+ * 获取数据表的主键
+ * @access public
+ * @param mixed $tableName 数据表名
+ * @return string|array
+ */
+ public function getPk($tableName)
+ {
+ return $this->getTableInfo($tableName, 'pk');
+ }
+
+ /**
+ * 获取数据表的自增主键
+ * @access public
+ * @param mixed $tableName 数据表名
+ * @return string
+ */
+ public function getAutoInc($tableName)
+ {
+ return $this->getTableInfo($tableName, 'autoinc');
+ }
+
+ /**
+ * 获取数据表字段信息
+ * @access public
+ * @param mixed $tableName 数据表名
+ * @return array
+ */
+ public function getTableFields($tableName): array
+ {
+ return $this->getTableInfo($tableName, 'fields');
+ }
+
+ /**
+ * 获取数据表字段类型
+ * @access public
+ * @param mixed $tableName 数据表名
+ * @param string $field 字段名
+ * @return array|string
+ */
+ public function getFieldsType($tableName, string $field = null)
+ {
+ $result = $this->getTableInfo($tableName, 'type');
+
+ if ($field && isset($result[$field])) {
+ return $result[$field];
+ }
+
+ return $result;
+ }
+
+ /**
+ * 获取数据表绑定信息
+ * @access public
+ * @param mixed $tableName 数据表名
+ * @return array
+ */
+ public function getFieldsBind($tableName): array
+ {
+ return $this->getTableInfo($tableName, 'bind');
+ }
+
+ /**
+ * 连接数据库方法
+ * @access public
+ * @param array $config 连接参数
+ * @param integer $linkNum 连接序号
+ * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式)
+ * @return PDO
+ * @throws PDOException
+ */
+ public function connect(array $config = [], $linkNum = 0, $autoConnection = false): PDO
+ {
+ if (isset($this->links[$linkNum])) {
+ return $this->links[$linkNum];
+ }
+
+ if (empty($config)) {
+ $config = $this->config;
+ } else {
+ $config = array_merge($this->config, $config);
+ }
+
+ // 连接参数
+ if (isset($config['params']) && is_array($config['params'])) {
+ $params = $config['params'] + $this->params;
+ } else {
+ $params = $this->params;
+ }
+
+ // 记录当前字段属性大小写设置
+ $this->attrCase = $params[PDO::ATTR_CASE];
+
+ if (!empty($config['break_match_str'])) {
+ $this->breakMatchStr = array_merge($this->breakMatchStr, (array) $config['break_match_str']);
+ }
+
+ try {
+ if (empty($config['dsn'])) {
+ $config['dsn'] = $this->parseDsn($config);
+ }
+
+ $startTime = microtime(true);
+
+ $this->links[$linkNum] = $this->createPdo($config['dsn'], $config['username'], $config['password'], $params);
+
+ // SQL监控
+ if (!empty($config['trigger_sql'])) {
+ $this->trigger('CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']);
+ }
+
+ return $this->links[$linkNum];
+ } catch (\PDOException $e) {
+ if ($autoConnection) {
+ $this->db->log($e->getMessage(), 'error');
+ return $this->connect($autoConnection, $linkNum);
+ } else {
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * 视图查询
+ * @access public
+ * @param array $args
+ * @return BaseQuery
+ */
+ public function view(...$args)
+ {
+ return $this->newQuery()->view(...$args);
+ }
+
+ /**
+ * 创建PDO实例
+ * @param $dsn
+ * @param $username
+ * @param $password
+ * @param $params
+ * @return PDO
+ */
+ protected function createPdo($dsn, $username, $password, $params)
+ {
+ return new PDO($dsn, $username, $password, $params);
+ }
+
+ /**
+ * 释放查询结果
+ * @access public
+ */
+ public function free(): void
+ {
+ $this->PDOStatement = null;
+ }
+
+ /**
+ * 获取PDO对象
+ * @access public
+ * @return \PDO|false
+ */
+ public function getPdo()
+ {
+ if (!$this->linkID) {
+ return false;
+ }
+
+ return $this->linkID;
+ }
+
+ /**
+ * 执行查询 使用生成器返回数据
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $sql sql指令
+ * @param array $bind 参数绑定
+ * @param \think\Model $model 模型对象实例
+ * @param array $condition 查询条件
+ * @return \Generator
+ */
+ public function getCursor(BaseQuery $query, string $sql, array $bind = [], $model = null, $condition = null)
+ {
+ $this->queryPDOStatement($query, $sql, $bind);
+
+ // 返回结果集
+ while ($result = $this->PDOStatement->fetch($this->fetchType)) {
+ if ($model) {
+ yield $model->newInstance($result, $condition);
+ } else {
+ yield $result;
+ }
+ }
+ }
+
+ /**
+ * 执行查询 返回数据集
+ * @access public
+ * @param string $sql sql指令
+ * @param array $bind 参数绑定
+ * @param bool $master 主库读取
+ * @return array
+ * @throws BindParamException
+ * @throws \PDOException
+ */
+ public function query(string $sql, array $bind = [], bool $master = false): array
+ {
+ return $this->pdoQuery($this->newQuery(), $sql, $bind, $master);
+ }
+
+ /**
+ * 执行语句
+ * @access public
+ * @param string $sql sql指令
+ * @param array $bind 参数绑定
+ * @return int
+ * @throws BindParamException
+ * @throws \PDOException
+ */
+ public function execute(string $sql, array $bind = []): int
+ {
+ return $this->pdoExecute($this->newQuery(), $sql, $bind, true);
+ }
+
+ /**
+ * 执行查询 返回数据集
+ * @access protected
+ * @param BaseQuery $query 查询对象
+ * @param mixed $sql sql指令
+ * @param array $bind 参数绑定
+ * @param bool $master 主库读取
+ * @return array
+ * @throws BindParamException
+ * @throws \PDOException
+ * @throws \Exception
+ * @throws \Throwable
+ */
+ protected function pdoQuery(BaseQuery $query, $sql, array $bind = [], bool $master = null): array
+ {
+ // 分析查询表达式
+ $query->parseOptions();
+
+ if ($query->getOptions('cache')) {
+ // 检查查询缓存
+ $cacheItem = $this->parseCache($query, $query->getOptions('cache'));
+ $key = $cacheItem->getKey();
+
+ $data = $this->cache->get($key);
+
+ if (null !== $data) {
+ return $data;
+ }
+ }
+
+ if ($sql instanceof Closure) {
+ $sql = $sql($query);
+ $bind = $query->getBind();
+ }
+
+ if (!isset($master)) {
+ $master = $query->getOptions('master') ? true : false;
+ }
+
+ $procedure = $query->getOptions('procedure') ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']);
+
+ $this->getPDOStatement($sql, $bind, $master, $procedure);
+
+ $resultSet = $this->getResult($procedure);
+
+ if (isset($cacheItem) && $resultSet) {
+ // 缓存数据集
+ $cacheItem->set($resultSet);
+ $this->cacheData($cacheItem);
+ }
+
+ return $resultSet;
+ }
+
+ /**
+ * 执行查询但只返回PDOStatement对象
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return \PDOStatement
+ */
+ public function pdo(BaseQuery $query): PDOStatement
+ {
+ $bind = $query->getBind();
+ // 生成查询SQL
+ $sql = $this->builder->select($query);
+
+ return $this->queryPDOStatement($query, $sql, $bind);
+ }
+
+ /**
+ * 执行查询但只返回PDOStatement对象
+ * @access public
+ * @param string $sql sql指令
+ * @param array $bind 参数绑定
+ * @param bool $master 是否在主服务器读操作
+ * @param bool $procedure 是否为存储过程调用
+ * @return PDOStatement
+ * @throws BindParamException
+ * @throws \PDOException
+ * @throws \Exception
+ * @throws \Throwable
+ */
+ public function getPDOStatement(string $sql, array $bind = [], bool $master = false, bool $procedure = false): PDOStatement
+ {
+ $this->initConnect($this->readMaster ?: $master);
+
+ // 记录SQL语句
+ $this->queryStr = $sql;
+
+ $this->bind = $bind;
+
+ $this->db->updateQueryTimes();
+
+ try {
+ $this->queryStartTime = microtime(true);
+
+ // 预处理
+ $this->PDOStatement = $this->linkID->prepare($sql);
+
+ // 参数绑定
+ if ($procedure) {
+ $this->bindParam($bind);
+ } else {
+ $this->bindValue($bind);
+ }
+
+ // 执行查询
+ $this->PDOStatement->execute();
+
+ // SQL监控
+ if (!empty($this->config['trigger_sql'])) {
+ $this->trigger('', $master);
+ }
+
+ $this->reConnectTimes = 0;
+
+ return $this->PDOStatement;
+ } catch (\Throwable | \Exception $e) {
+ if ($this->reConnectTimes < 4 && $this->isBreak($e)) {
+ ++$this->reConnectTimes;
+ return $this->close()->getPDOStatement($sql, $bind, $master, $procedure);
+ }
+
+ if ($e instanceof \PDOException) {
+ throw new PDOException($e, $this->config, $this->getLastsql());
+ } else {
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * 执行语句
+ * @access protected
+ * @param BaseQuery $query 查询对象
+ * @param string $sql sql指令
+ * @param array $bind 参数绑定
+ * @param bool $origin 是否原生查询
+ * @return int
+ * @throws BindParamException
+ * @throws \PDOException
+ * @throws \Exception
+ * @throws \Throwable
+ */
+ protected function pdoExecute(BaseQuery $query, string $sql, array $bind = [], bool $origin = false): int
+ {
+ if ($origin) {
+ $query->parseOptions();
+ }
+
+ $this->queryPDOStatement($query->master(true), $sql, $bind);
+
+ if (!$origin && !empty($this->config['deploy']) && !empty($this->config['read_master'])) {
+ $this->readMaster = true;
+ }
+
+ $this->numRows = $this->PDOStatement->rowCount();
+
+ if ($query->getOptions('cache')) {
+ // 清理缓存数据
+ $cacheItem = $this->parseCache($query, $query->getOptions('cache'));
+ $key = $cacheItem->getKey();
+ $tag = $cacheItem->getTag();
+
+ if (isset($key) && $this->cache->has($key)) {
+ $this->cache->delete($key);
+ } elseif (!empty($tag) && method_exists($this->cache, 'tag')) {
+ $this->cache->tag($tag)->clear();
+ }
+ }
+
+ return $this->numRows;
+ }
+
+ protected function queryPDOStatement(BaseQuery $query, string $sql, array $bind = []): PDOStatement
+ {
+ $options = $query->getOptions();
+ $master = !empty($options['master']) ? true : false;
+ $procedure = !empty($options['procedure']) ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']);
+
+ return $this->getPDOStatement($sql, $bind, $master, $procedure);
+ }
+
+ /**
+ * 查找单条记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return array
+ * @throws DbException
+ */
+ public function find(BaseQuery $query): array
+ {
+ // 事件回调
+ $result = $this->db->trigger('before_find', $query);
+
+ if (!$result) {
+ // 执行查询
+ $resultSet = $this->pdoQuery($query, function ($query) {
+ return $this->builder->select($query, true);
+ });
+
+ $result = $resultSet[0] ?? [];
+ }
+
+ return $result;
+ }
+
+ /**
+ * 使用游标查询记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return \Generator
+ */
+ public function cursor(BaseQuery $query)
+ {
+ // 分析查询表达式
+ $options = $query->parseOptions();
+
+ // 生成查询SQL
+ $sql = $this->builder->select($query);
+
+ $condition = $options['where']['AND'] ?? null;
+
+ // 执行查询操作
+ return $this->getCursor($query, $sql, $query->getBind(), $query->getModel(), $condition);
+ }
+
+ /**
+ * 查找记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return array
+ * @throws DbException
+ */
+ public function select(BaseQuery $query): array
+ {
+ $resultSet = $this->db->trigger('before_select', $query);
+
+ if (!$resultSet) {
+ // 执行查询操作
+ $resultSet = $this->pdoQuery($query, function ($query) {
+ return $this->builder->select($query);
+ });
+ }
+
+ return $resultSet;
+ }
+
+ /**
+ * 插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param boolean $getLastInsID 返回自增主键
+ * @return mixed
+ */
+ public function insert(BaseQuery $query, bool $getLastInsID = false)
+ {
+ // 分析查询表达式
+ $options = $query->parseOptions();
+
+ // 生成SQL语句
+ $sql = $this->builder->insert($query);
+
+ // 执行操作
+ $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql, $query->getBind());
+
+ if ($result) {
+ $sequence = $options['sequence'] ?? null;
+ $lastInsId = $this->getLastInsID($query, $sequence);
+
+ $data = $options['data'];
+
+ if ($lastInsId) {
+ $pk = $query->getAutoInc();
+ if ($pk) {
+ $data[$pk] = $lastInsId;
+ }
+ }
+
+ $query->setOption('data', $data);
+
+ $this->db->trigger('after_insert', $query);
+
+ if ($getLastInsID && $lastInsId) {
+ return $lastInsId;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 批量插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param mixed $dataSet 数据集
+ * @param integer $limit 每次写入数据限制
+ * @return integer
+ * @throws \Exception
+ * @throws \Throwable
+ */
+ public function insertAll(BaseQuery $query, array $dataSet = [], int $limit = 0): int
+ {
+ if (!is_array(reset($dataSet))) {
+ return 0;
+ }
+
+ $options = $query->parseOptions();
+ $replace = !empty($options['replace']);
+
+ if (0 === $limit && count($dataSet) >= 5000) {
+ $limit = 1000;
+ }
+
+ if ($limit) {
+ // 分批写入 自动启动事务支持
+ $this->startTrans();
+
+ try {
+ $array = array_chunk($dataSet, $limit, true);
+ $count = 0;
+
+ foreach ($array as $item) {
+ $sql = $this->builder->insertAll($query, $item, $replace);
+ $count += $this->pdoExecute($query, $sql, $query->getBind());
+ }
+
+ // 提交事务
+ $this->commit();
+ } catch (\Exception | \Throwable $e) {
+ $this->rollback();
+ throw $e;
+ }
+
+ return $count;
+ }
+
+ $sql = $this->builder->insertAll($query, $dataSet, $replace);
+
+ return $this->pdoExecute($query, $sql, $query->getBind());
+ }
+
+ /**
+ * 通过Select方式插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param array $fields 要插入的数据表字段名
+ * @param string $table 要插入的数据表名
+ * @return integer
+ * @throws PDOException
+ */
+ public function selectInsert(BaseQuery $query, array $fields, string $table): int
+ {
+ // 分析查询表达式
+ $query->parseOptions();
+
+ $sql = $this->builder->selectInsert($query, $fields, $table);
+
+ return $this->pdoExecute($query, $sql, $query->getBind());
+ }
+
+ /**
+ * 更新记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return integer
+ * @throws PDOException
+ */
+ public function update(BaseQuery $query): int
+ {
+ $query->parseOptions();
+
+ // 生成UPDATE SQL语句
+ $sql = $this->builder->update($query);
+
+ // 执行操作
+ $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql, $query->getBind());
+
+ if ($result) {
+ $this->db->trigger('after_update', $query);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return int
+ * @throws PDOException
+ */
+ public function delete(BaseQuery $query): int
+ {
+ // 分析查询表达式
+ $query->parseOptions();
+
+ // 生成删除SQL语句
+ $sql = $this->builder->delete($query);
+
+ // 执行操作
+ $result = $this->pdoExecute($query, $sql, $query->getBind());
+
+ if ($result) {
+ $this->db->trigger('after_delete', $query);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 得到某个字段的值
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $field 字段名
+ * @param mixed $default 默认值
+ * @param bool $one 返回一个值
+ * @return mixed
+ */
+ public function value(BaseQuery $query, string $field, $default = null, bool $one = true)
+ {
+ $options = $query->parseOptions();
+
+ if (isset($options['field'])) {
+ $query->removeOption('field');
+ }
+
+ if (isset($options['group'])) {
+ $query->group('');
+ }
+
+ $query->setOption('field', (array) $field);
+
+ if (!empty($options['cache'])) {
+ $cacheItem = $this->parseCache($query, $options['cache'], 'value');
+ $key = $cacheItem->getKey();
+
+ if ($this->cache->has($key)) {
+ return $this->cache->get($key);
+ }
+ }
+
+ // 生成查询SQL
+ $sql = $this->builder->select($query, $one);
+
+ if (isset($options['field'])) {
+ $query->setOption('field', $options['field']);
+ } else {
+ $query->removeOption('field');
+ }
+
+ if (isset($options['group'])) {
+ $query->setOption('group', $options['group']);
+ }
+
+ // 执行查询操作
+ $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']);
+
+ $result = $pdo->fetchColumn();
+
+ if (isset($cacheItem)) {
+ // 缓存数据
+ $cacheItem->set($result);
+ $this->cacheData($cacheItem);
+ }
+
+ return false !== $result ? $result : $default;
+ }
+
+ /**
+ * 得到某个字段的值
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $aggregate 聚合方法
+ * @param mixed $field 字段名
+ * @param bool $force 强制转为数字类型
+ * @return mixed
+ */
+ public function aggregate(BaseQuery $query, string $aggregate, $field, bool $force = false)
+ {
+ if (is_string($field) && 0 === stripos($field, 'DISTINCT ')) {
+ [$distinct, $field] = explode(' ', $field);
+ }
+
+ $field = $aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $this->builder->parseKey($query, $field, true) . ') AS think_' . strtolower($aggregate);
+
+ $result = $this->value($query, $field, 0, false);
+
+ return $force ? (float) $result : $result;
+ }
+
+ /**
+ * 得到某个列的数组
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $column 字段名 多个字段用逗号分隔
+ * @param string $key 索引
+ * @return array
+ */
+ public function column(BaseQuery $query, string $column, string $key = ''): array
+ {
+ $options = $query->parseOptions();
+
+ if (isset($options['field'])) {
+ $query->removeOption('field');
+ }
+
+ if ($key && '*' != $column) {
+ $field = $key . ',' . $column;
+ } else {
+ $field = $column;
+ }
+
+ $field = array_map('trim', explode(',', $field));
+
+ $query->setOption('field', $field);
+
+ if (!empty($options['cache'])) {
+ // 判断查询缓存
+ $cacheItem = $this->parseCache($query, $options['cache'], 'column');
+ $name = $cacheItem->getKey();
+
+ if ($this->cache->has($name)) {
+ return $this->cache->get($name);
+ }
+ }
+
+ // 生成查询SQL
+ $sql = $this->builder->select($query);
+
+ if (isset($options['field'])) {
+ $query->setOption('field', $options['field']);
+ } else {
+ $query->removeOption('field');
+ }
+
+ // 执行查询操作
+ $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']);
+
+ $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC);
+
+ if (empty($resultSet)) {
+ $result = [];
+ } elseif (('*' == $column || strpos($column, ',')) && $key) {
+ $result = array_column($resultSet, null, $key);
+ } else {
+ if (empty($key)) {
+ $key = null;
+ }
+
+ if (strpos($column, ',')) {
+ $column = null;
+ } elseif (strpos($column, ' ')) {
+ $column = substr(strrchr(trim($column), ' '), 1);
+ } elseif (strpos($column, '.')) {
+ [$alias, $column] = explode('.', $column);
+ }
+
+ if (is_string($key) && strpos($key, '.')) {
+ [$alias, $key] = explode('.', $key);
+ }
+
+ $result = array_column($resultSet, $column, $key);
+ }
+
+ if (isset($cacheItem)) {
+ // 缓存数据
+ $cacheItem->set($result);
+ $this->cacheData($cacheItem);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 根据参数绑定组装最终的SQL语句 便于调试
+ * @access public
+ * @param string $sql 带参数绑定的sql语句
+ * @param array $bind 参数绑定列表
+ * @return string
+ */
+ public function getRealSql(string $sql, array $bind = []): string
+ {
+ foreach ($bind as $key => $val) {
+ $value = is_array($val) ? $val[0] : $val;
+ $type = is_array($val) ? $val[1] : PDO::PARAM_STR;
+
+ if ((self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) && is_string($value)) {
+ $value = '\'' . addslashes($value) . '\'';
+ } elseif (PDO::PARAM_INT == $type && '' === $value) {
+ $value = 0;
+ }
+
+ // 判断占位符
+ $sql = is_numeric($key) ?
+ substr_replace($sql, $value, strpos($sql, '?'), 1) :
+ substr_replace($sql, $value, strpos($sql, ':' . $key), strlen(':' . $key));
+ }
+
+ return rtrim($sql);
+ }
+
+ /**
+ * 参数绑定
+ * 支持 ['name'=>'value','id'=>123] 对应命名占位符
+ * 或者 ['value',123] 对应问号占位符
+ * @access public
+ * @param array $bind 要绑定的参数列表
+ * @return void
+ * @throws BindParamException
+ */
+ protected function bindValue(array $bind = []): void
+ {
+ foreach ($bind as $key => $val) {
+ // 占位符
+ $param = is_numeric($key) ? $key + 1 : ':' . $key;
+
+ if (is_array($val)) {
+ if (PDO::PARAM_INT == $val[1] && '' === $val[0]) {
+ $val[0] = 0;
+ } elseif (self::PARAM_FLOAT == $val[1]) {
+ $val[0] = is_string($val[0]) ? (float) $val[0] : $val[0];
+ $val[1] = PDO::PARAM_STR;
+ }
+
+ $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]);
+ } else {
+ $result = $this->PDOStatement->bindValue($param, $val);
+ }
+
+ if (!$result) {
+ throw new BindParamException(
+ "Error occurred when binding parameters '{$param}'",
+ $this->config,
+ $this->getLastsql(),
+ $bind
+ );
+ }
+ }
+ }
+
+ /**
+ * 存储过程的输入输出参数绑定
+ * @access public
+ * @param array $bind 要绑定的参数列表
+ * @return void
+ * @throws BindParamException
+ */
+ protected function bindParam(array $bind): void
+ {
+ foreach ($bind as $key => $val) {
+ $param = is_numeric($key) ? $key + 1 : ':' . $key;
+
+ if (is_array($val)) {
+ array_unshift($val, $param);
+ $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val);
+ } else {
+ $result = $this->PDOStatement->bindValue($param, $val);
+ }
+
+ if (!$result) {
+ $param = array_shift($val);
+
+ throw new BindParamException(
+ "Error occurred when binding parameters '{$param}'",
+ $this->config,
+ $this->getLastsql(),
+ $bind
+ );
+ }
+ }
+ }
+
+ /**
+ * 获得数据集数组
+ * @access protected
+ * @param bool $procedure 是否存储过程
+ * @return array
+ */
+ protected function getResult(bool $procedure = false): array
+ {
+ if ($procedure) {
+ // 存储过程返回结果
+ return $this->procedure();
+ }
+
+ $result = $this->PDOStatement->fetchAll($this->fetchType);
+
+ $this->numRows = count($result);
+
+ return $result;
+ }
+
+ /**
+ * 获得存储过程数据集
+ * @access protected
+ * @return array
+ */
+ protected function procedure(): array
+ {
+ $item = [];
+
+ do {
+ $result = $this->getResult();
+ if (!empty($result)) {
+ $item[] = $result;
+ }
+ } while ($this->PDOStatement->nextRowset());
+
+ $this->numRows = count($item);
+
+ return $item;
+ }
+
+ /**
+ * 执行数据库事务
+ * @access public
+ * @param callable $callback 数据操作方法回调
+ * @return mixed
+ * @throws PDOException
+ * @throws \Exception
+ * @throws \Throwable
+ */
+ public function transaction(callable $callback)
+ {
+ $this->startTrans();
+
+ try {
+ $result = null;
+ if (is_callable($callback)) {
+ $result = $callback($this);
+ }
+
+ $this->commit();
+ return $result;
+ } catch (\Exception | \Throwable $e) {
+ $this->rollback();
+ throw $e;
+ }
+ }
+
+ /**
+ * 启动事务
+ * @access public
+ * @return void
+ * @throws \PDOException
+ * @throws \Exception
+ */
+ public function startTrans(): void
+ {
+ $this->initConnect(true);
+
+ ++$this->transTimes;
+
+ try {
+ if (1 == $this->transTimes) {
+ $this->linkID->beginTransaction();
+ } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
+ $this->linkID->exec(
+ $this->parseSavepoint('trans' . $this->transTimes)
+ );
+ }
+ $this->reConnectTimes = 0;
+ } catch (\Exception $e) {
+ if ($this->reConnectTimes < 4 && $this->isBreak($e)) {
+ --$this->transTimes;
+ ++$this->reConnectTimes;
+ $this->close()->startTrans();
+ }
+ throw $e;
+ }
+ }
+
+ /**
+ * 用于非自动提交状态下面的查询提交
+ * @access public
+ * @return void
+ * @throws PDOException
+ */
+ public function commit(): void
+ {
+ $this->initConnect(true);
+
+ if (1 == $this->transTimes) {
+ $this->linkID->commit();
+ }
+
+ --$this->transTimes;
+ }
+
+ /**
+ * 事务回滚
+ * @access public
+ * @return void
+ * @throws PDOException
+ */
+ public function rollback(): void
+ {
+ $this->initConnect(true);
+
+ if (1 == $this->transTimes) {
+ $this->linkID->rollBack();
+ } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
+ $this->linkID->exec(
+ $this->parseSavepointRollBack('trans' . $this->transTimes)
+ );
+ }
+
+ $this->transTimes = max(0, $this->transTimes - 1);
+ }
+
+ /**
+ * 是否支持事务嵌套
+ * @return bool
+ */
+ protected function supportSavepoint(): bool
+ {
+ return false;
+ }
+
+ /**
+ * 生成定义保存点的SQL
+ * @access protected
+ * @param string $name 标识
+ * @return string
+ */
+ protected function parseSavepoint(string $name): string
+ {
+ return 'SAVEPOINT ' . $name;
+ }
+
+ /**
+ * 生成回滚到保存点的SQL
+ * @access protected
+ * @param string $name 标识
+ * @return string
+ */
+ protected function parseSavepointRollBack(string $name): string
+ {
+ return 'ROLLBACK TO SAVEPOINT ' . $name;
+ }
+
+ /**
+ * 批处理执行SQL语句
+ * 批处理的指令都认为是execute操作
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param array $sqlArray SQL批处理指令
+ * @param array $bind 参数绑定
+ * @return bool
+ */
+ public function batchQuery(BaseQuery $query, array $sqlArray = [], array $bind = []): bool
+ {
+ // 自动启动事务支持
+ $this->startTrans();
+
+ try {
+ foreach ($sqlArray as $sql) {
+ $this->pdoExecute($query, $sql, $bind);
+ }
+ // 提交事务
+ $this->commit();
+ } catch (\Exception $e) {
+ $this->rollback();
+ throw $e;
+ }
+
+ return true;
+ }
+
+ /**
+ * 关闭数据库(或者重新连接)
+ * @access public
+ * @return $this
+ */
+ public function close()
+ {
+ $this->linkID = null;
+ $this->linkWrite = null;
+ $this->linkRead = null;
+ $this->links = [];
+
+ $this->free();
+
+ return $this;
+ }
+
+ /**
+ * 是否断线
+ * @access protected
+ * @param \PDOException|\Exception $e 异常对象
+ * @return bool
+ */
+ protected function isBreak($e): bool
+ {
+ if (!$this->config['break_reconnect']) {
+ return false;
+ }
+
+ $error = $e->getMessage();
+
+ foreach ($this->breakMatchStr as $msg) {
+ if (false !== stripos($error, $msg)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 获取最近一次查询的sql语句
+ * @access public
+ * @return string
+ */
+ public function getLastSql(): string
+ {
+ return $this->getRealSql($this->queryStr, $this->bind);
+ }
+
+ /**
+ * 获取最近插入的ID
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $sequence 自增序列名
+ * @return mixed
+ */
+ public function getLastInsID(BaseQuery $query, string $sequence = null)
+ {
+ try {
+ $insertId = $this->linkID->lastInsertId($sequence);
+ } catch (\Exception $e) {
+ $insertId = '';
+ }
+
+ return $this->autoInsIDType($query, $insertId);
+ }
+
+ /**
+ * 获取最近插入的ID
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $insertId 自增ID
+ * @return mixed
+ */
+ protected function autoInsIDType(BaseQuery $query, string $insertId)
+ {
+ $pk = $query->getAutoInc();
+
+ if ($pk) {
+ $type = $this->getFieldBindType($pk);
+
+ if (PDO::PARAM_INT == $type) {
+ $insertId = (int) $insertId;
+ } elseif (self::PARAM_FLOAT == $type) {
+ $insertId = (float) $insertId;
+ }
+ }
+
+ return $insertId;
+ }
+
+ /**
+ * 获取最近的错误信息
+ * @access public
+ * @return string
+ */
+ public function getError(): string
+ {
+ if ($this->PDOStatement) {
+ $error = $this->PDOStatement->errorInfo();
+ $error = $error[1] . ':' . $error[2];
+ } else {
+ $error = '';
+ }
+
+ if ('' != $this->queryStr) {
+ $error .= "\n [ SQL语句 ] : " . $this->getLastsql();
+ }
+
+ return $error;
+ }
+
+ /**
+ * 初始化数据库连接
+ * @access protected
+ * @param boolean $master 是否主服务器
+ * @return void
+ */
+ protected function initConnect(bool $master = true): void
+ {
+ if (!empty($this->config['deploy'])) {
+ // 采用分布式数据库
+ if ($master || $this->transTimes) {
+ if (!$this->linkWrite) {
+ $this->linkWrite = $this->multiConnect(true);
+ }
+
+ $this->linkID = $this->linkWrite;
+ } else {
+ if (!$this->linkRead) {
+ $this->linkRead = $this->multiConnect(false);
+ }
+
+ $this->linkID = $this->linkRead;
+ }
+ } elseif (!$this->linkID) {
+ // 默认单数据库
+ $this->linkID = $this->connect();
+ }
+ }
+
+ /**
+ * 连接分布式服务器
+ * @access protected
+ * @param boolean $master 主服务器
+ * @return PDO
+ */
+ protected function multiConnect(bool $master = false): PDO
+ {
+ $config = [];
+
+ // 分布式数据库配置解析
+ foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
+ $config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name];
+ }
+
+ // 主服务器序号
+ $m = floor(mt_rand(0, $this->config['master_num'] - 1));
+
+ if ($this->config['rw_separate']) {
+ // 主从式采用读写分离
+ if ($master) // 主服务器写入
+ {
+ $r = $m;
+ } elseif (is_numeric($this->config['slave_no'])) {
+ // 指定服务器读
+ $r = $this->config['slave_no'];
+ } else {
+ // 读操作连接从服务器 每次随机连接的数据库
+ $r = floor(mt_rand($this->config['master_num'], count($config['hostname']) - 1));
+ }
+ } else {
+ // 读写操作不区分服务器 每次随机连接的数据库
+ $r = floor(mt_rand(0, count($config['hostname']) - 1));
+ }
+ $dbMaster = false;
+
+ if ($m != $r) {
+ $dbMaster = [];
+ foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
+ $dbMaster[$name] = $config[$name][$m] ?? $config[$name][0];
+ }
+ }
+
+ $dbConfig = [];
+
+ foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
+ $dbConfig[$name] = $config[$name][$r] ?? $config[$name][0];
+ }
+
+ return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster);
+ }
+
+ /**
+ * 启动XA事务
+ * @access public
+ * @param string $xid XA事务id
+ * @return void
+ */
+ public function startTransXa(string $xid)
+ {}
+
+ /**
+ * 预编译XA事务
+ * @access public
+ * @param string $xid XA事务id
+ * @return void
+ */
+ public function prepareXa(string $xid)
+ {}
+
+ /**
+ * 提交XA事务
+ * @access public
+ * @param string $xid XA事务id
+ * @return void
+ */
+ public function commitXa(string $xid)
+ {}
+
+ /**
+ * 回滚XA事务
+ * @access public
+ * @param string $xid XA事务id
+ * @return void
+ */
+ public function rollbackXa(string $xid)
+ {}
+}
diff --git a/vendor/topthink/think-orm/src/db/Query.php b/vendor/topthink/think-orm/src/db/Query.php
new file mode 100755
index 0000000..aa96123
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/Query.php
@@ -0,0 +1,451 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use PDOStatement;
+use think\helper\Str;
+
+/**
+ * PDO数据查询类
+ */
+class Query extends BaseQuery
+{
+ use concern\JoinAndViewQuery;
+ use concern\ParamsBind;
+ use concern\TableFieldInfo;
+
+ /**
+ * 表达式方式指定Field排序
+ * @access public
+ * @param string $field 排序字段
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function orderRaw(string $field, array $bind = [])
+ {
+ $this->options['order'][] = new Raw($field, $bind);
+
+ return $this;
+ }
+
+ /**
+ * 表达式方式指定查询字段
+ * @access public
+ * @param string $field 字段名
+ * @return $this
+ */
+ public function fieldRaw(string $field)
+ {
+ $this->options['field'][] = new Raw($field);
+
+ return $this;
+ }
+
+ /**
+ * 指定Field排序 orderField('id',[1,2,3],'desc')
+ * @access public
+ * @param string $field 排序字段
+ * @param array $values 排序值
+ * @param string $order 排序 desc/asc
+ * @return $this
+ */
+ public function orderField(string $field, array $values, string $order = '')
+ {
+ if (!empty($values)) {
+ $values['sort'] = $order;
+
+ $this->options['order'][$field] = $values;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 随机排序
+ * @access public
+ * @return $this
+ */
+ public function orderRand()
+ {
+ $this->options['order'][] = '[rand]';
+ return $this;
+ }
+
+ /**
+ * 使用表达式设置数据
+ * @access public
+ * @param string $field 字段名
+ * @param string $value 字段值
+ * @return $this
+ */
+ public function exp(string $field, string $value)
+ {
+ $this->options['data'][$field] = new Raw($value);
+ return $this;
+ }
+
+ /**
+ * 表达式方式指定当前操作的数据表
+ * @access public
+ * @param mixed $table 表名
+ * @return $this
+ */
+ public function tableRaw(string $table)
+ {
+ $this->options['table'] = new Raw($table);
+
+ return $this;
+ }
+
+ /**
+ * 获取执行的SQL语句而不进行实际的查询
+ * @access public
+ * @param bool $fetch 是否返回sql
+ * @return $this|Fetch
+ */
+ public function fetchSql(bool $fetch = true)
+ {
+ $this->options['fetch_sql'] = $fetch;
+
+ if ($fetch) {
+ return new Fetch($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 批处理执行SQL语句
+ * 批处理的指令都认为是execute操作
+ * @access public
+ * @param array $sql SQL批处理指令
+ * @return bool
+ */
+ public function batchQuery(array $sql = []): bool
+ {
+ return $this->connection->batchQuery($this, $sql);
+ }
+
+ /**
+ * USING支持 用于多表删除
+ * @access public
+ * @param mixed $using USING
+ * @return $this
+ */
+ public function using($using)
+ {
+ $this->options['using'] = $using;
+ return $this;
+ }
+
+ /**
+ * 存储过程调用
+ * @access public
+ * @param bool $procedure 是否为存储过程查询
+ * @return $this
+ */
+ public function procedure(bool $procedure = true)
+ {
+ $this->options['procedure'] = $procedure;
+ return $this;
+ }
+
+ /**
+ * 指定group查询
+ * @access public
+ * @param string|array $group GROUP
+ * @return $this
+ */
+ public function group($group)
+ {
+ $this->options['group'] = $group;
+ return $this;
+ }
+
+ /**
+ * 指定having查询
+ * @access public
+ * @param string $having having
+ * @return $this
+ */
+ public function having(string $having)
+ {
+ $this->options['having'] = $having;
+ return $this;
+ }
+
+ /**
+ * 指定distinct查询
+ * @access public
+ * @param bool $distinct 是否唯一
+ * @return $this
+ */
+ public function distinct(bool $distinct = true)
+ {
+ $this->options['distinct'] = $distinct;
+ return $this;
+ }
+
+ /**
+ * 指定强制索引
+ * @access public
+ * @param string $force 索引名称
+ * @return $this
+ */
+ public function force(string $force)
+ {
+ $this->options['force'] = $force;
+ return $this;
+ }
+
+ /**
+ * 查询注释
+ * @access public
+ * @param string $comment 注释
+ * @return $this
+ */
+ public function comment(string $comment)
+ {
+ $this->options['comment'] = $comment;
+ return $this;
+ }
+
+ /**
+ * 设置是否REPLACE
+ * @access public
+ * @param bool $replace 是否使用REPLACE写入数据
+ * @return $this
+ */
+ public function replace(bool $replace = true)
+ {
+ $this->options['replace'] = $replace;
+ return $this;
+ }
+
+ /**
+ * 设置当前查询所在的分区
+ * @access public
+ * @param string|array $partition 分区名称
+ * @return $this
+ */
+ public function partition($partition)
+ {
+ $this->options['partition'] = $partition;
+ return $this;
+ }
+
+ /**
+ * 设置DUPLICATE
+ * @access public
+ * @param array|string|Raw $duplicate DUPLICATE信息
+ * @return $this
+ */
+ public function duplicate($duplicate)
+ {
+ $this->options['duplicate'] = $duplicate;
+ return $this;
+ }
+
+ /**
+ * 设置查询的额外参数
+ * @access public
+ * @param string $extra 额外信息
+ * @return $this
+ */
+ public function extra(string $extra)
+ {
+ $this->options['extra'] = $extra;
+ return $this;
+ }
+
+ /**
+ * 创建子查询SQL
+ * @access public
+ * @param bool $sub 是否添加括号
+ * @return string
+ * @throws Exception
+ */
+ public function buildSql(bool $sub = true): string
+ {
+ return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select();
+ }
+
+ /**
+ * 获取当前数据表的主键
+ * @access public
+ * @return string|array
+ */
+ public function getPk()
+ {
+ if (empty($this->pk)) {
+ $this->pk = $this->connection->getPk($this->getTable());
+ }
+
+ return $this->pk;
+ }
+
+ /**
+ * 指定数据表自增主键
+ * @access public
+ * @param string $autoinc 自增键
+ * @return $this
+ */
+ public function autoinc(string $autoinc)
+ {
+ $this->autoinc = $autoinc;
+ return $this;
+ }
+
+ /**
+ * 获取当前数据表的自增主键
+ * @access public
+ * @return string|null
+ */
+ public function getAutoInc()
+ {
+ $tableName = $this->getTable();
+
+ if (empty($this->autoinc) && $tableName) {
+ $this->autoinc = $this->connection->getAutoInc($tableName);
+ }
+
+ return $this->autoinc;
+ }
+
+ /**
+ * 字段值增长
+ * @access public
+ * @param string $field 字段名
+ * @param float $step 增长值
+ * @return $this
+ */
+ public function inc(string $field, float $step = 1)
+ {
+ $this->options['data'][$field] = ['INC', $step];
+
+ return $this;
+ }
+
+ /**
+ * 字段值减少
+ * @access public
+ * @param string $field 字段名
+ * @param float $step 增长值
+ * @return $this
+ */
+ public function dec(string $field, float $step = 1)
+ {
+ $this->options['data'][$field] = ['DEC', $step];
+ return $this;
+ }
+
+ /**
+ * 获取当前的查询标识
+ * @access public
+ * @param mixed $data 要序列化的数据
+ * @return string
+ */
+ public function getQueryGuid($data = null): string
+ {
+ return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true)) . serialize($this->getBind(false)));
+ }
+
+ /**
+ * 执行查询但只返回PDOStatement对象
+ * @access public
+ * @return PDOStatement
+ */
+ public function getPdo(): PDOStatement
+ {
+ return $this->connection->pdo($this);
+ }
+
+ /**
+ * 使用游标查找记录
+ * @access public
+ * @param mixed $data 数据
+ * @return \Generator
+ */
+ public function cursor($data = null)
+ {
+ if (!is_null($data)) {
+ // 主键条件分析
+ $this->parsePkWhere($data);
+ }
+
+ $this->options['data'] = $data;
+
+ $connection = clone $this->connection;
+
+ return $connection->cursor($this);
+ }
+
+ /**
+ * 分批数据返回处理
+ * @access public
+ * @param integer $count 每次处理的数据数量
+ * @param callable $callback 处理回调方法
+ * @param string|array $column 分批处理的字段名
+ * @param string $order 字段排序
+ * @return bool
+ * @throws Exception
+ */
+ public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool
+ {
+ $options = $this->getOptions();
+ $column = $column ?: $this->getPk();
+
+ if (isset($options['order'])) {
+ unset($options['order']);
+ }
+
+ $bind = $this->bind;
+
+ if (is_array($column)) {
+ $times = 1;
+ $query = $this->options($options)->page($times, $count);
+ } else {
+ $query = $this->options($options)->limit($count);
+
+ if (strpos($column, '.')) {
+ [$alias, $key] = explode('.', $column);
+ } else {
+ $key = $column;
+ }
+ }
+
+ $resultSet = $query->order($column, $order)->select();
+
+ while (count($resultSet) > 0) {
+ if (false === call_user_func($callback, $resultSet)) {
+ return false;
+ }
+
+ if (isset($times)) {
+ $times++;
+ $query = $this->options($options)->page($times, $count);
+ } else {
+ $end = $resultSet->pop();
+ $lastId = is_array($end) ? $end[$key] : $end->getData($key);
+
+ $query = $this->options($options)
+ ->limit($count)
+ ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId);
+ }
+
+ $resultSet = $query->bind($bind)->order($column, $order)->select();
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/Raw.php b/vendor/topthink/think-orm/src/db/Raw.php
new file mode 100755
index 0000000..833fbf0
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/Raw.php
@@ -0,0 +1,71 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+/**
+ * SQL Raw
+ */
+class Raw
+{
+ /**
+ * 查询表达式
+ *
+ * @var string
+ */
+ protected $value;
+
+ /**
+ * 参数绑定
+ *
+ * @var array
+ */
+ protected $bind = [];
+
+ /**
+ * 创建一个查询表达式
+ *
+ * @param string $value
+ * @param array $bind
+ * @return void
+ */
+ public function __construct(string $value, array $bind = [])
+ {
+ $this->value = $value;
+ $this->bind = $bind;
+ }
+
+ /**
+ * 获取表达式
+ *
+ * @return string
+ */
+ public function getValue(): string
+ {
+ return $this->value;
+ }
+
+ /**
+ * 获取参数绑定
+ *
+ * @return string
+ */
+ public function getBind(): array
+ {
+ return $this->bind;
+ }
+
+ public function __toString()
+ {
+ return (string) $this->value;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/Where.php b/vendor/topthink/think-orm/src/db/Where.php
new file mode 100755
index 0000000..0880460
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/Where.php
@@ -0,0 +1,182 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db;
+
+use ArrayAccess;
+
+/**
+ * 数组查询对象
+ */
+class Where implements ArrayAccess
+{
+ /**
+ * 查询表达式
+ * @var array
+ */
+ protected $where = [];
+
+ /**
+ * 是否需要把查询条件两边增加括号
+ * @var bool
+ */
+ protected $enclose = false;
+
+ /**
+ * 创建一个查询表达式
+ *
+ * @param array $where 查询条件数组
+ * @param bool $enclose 是否增加括号
+ */
+ public function __construct(array $where = [], bool $enclose = false)
+ {
+ $this->where = $where;
+ $this->enclose = $enclose;
+ }
+
+ /**
+ * 设置是否添加括号
+ * @access public
+ * @param bool $enclose
+ * @return $this
+ */
+ public function enclose(bool $enclose = true)
+ {
+ $this->enclose = $enclose;
+ return $this;
+ }
+
+ /**
+ * 解析为Query对象可识别的查询条件数组
+ * @access public
+ * @return array
+ */
+ public function parse(): array
+ {
+ $where = [];
+
+ foreach ($this->where as $key => $val) {
+ if ($val instanceof Raw) {
+ $where[] = [$key, 'exp', $val];
+ } elseif (is_null($val)) {
+ $where[] = [$key, 'NULL', ''];
+ } elseif (is_array($val)) {
+ $where[] = $this->parseItem($key, $val);
+ } else {
+ $where[] = [$key, '=', $val];
+ }
+ }
+
+ return $this->enclose ? [$where] : $where;
+ }
+
+ /**
+ * 分析查询表达式
+ * @access protected
+ * @param string $field 查询字段
+ * @param array $where 查询条件
+ * @return array
+ */
+ protected function parseItem(string $field, array $where = []): array
+ {
+ $op = $where[0];
+ $condition = $where[1] ?? null;
+
+ if (is_array($op)) {
+ // 同一字段多条件查询
+ array_unshift($where, $field);
+ } elseif (is_null($condition)) {
+ if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
+ // null查询
+ $where = [$field, $op, ''];
+ } elseif (is_null($op) || '=' == $op) {
+ $where = [$field, 'NULL', ''];
+ } elseif ('<>' == $op) {
+ $where = [$field, 'NOTNULL', ''];
+ } else {
+ // 字段相等查询
+ $where = [$field, '=', $op];
+ }
+ } else {
+ $where = [$field, $op, $condition];
+ }
+
+ return $where;
+ }
+
+ /**
+ * 修改器 设置数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @param mixed $value 值
+ * @return void
+ */
+ public function __set($name, $value)
+ {
+ $this->where[$name] = $value;
+ }
+
+ /**
+ * 获取器 获取数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->where[$name] ?? null;
+ }
+
+ /**
+ * 检测数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return isset($this->where[$name]);
+ }
+
+ /**
+ * 销毁数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return void
+ */
+ public function __unset($name)
+ {
+ unset($this->where[$name]);
+ }
+
+ // ArrayAccess
+ public function offsetSet($name, $value)
+ {
+ $this->__set($name, $value);
+ }
+
+ public function offsetExists($name)
+ {
+ return $this->__isset($name);
+ }
+
+ public function offsetUnset($name)
+ {
+ $this->__unset($name);
+ }
+
+ public function offsetGet($name)
+ {
+ return $this->__get($name);
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/builder/Mongo.php b/vendor/topthink/think-orm/src/db/builder/Mongo.php
new file mode 100755
index 0000000..823156b
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/builder/Mongo.php
@@ -0,0 +1,675 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+namespace think\db\builder;
+
+use MongoDB\BSON\Javascript;
+use MongoDB\BSON\ObjectID;
+use MongoDB\BSON\Regex;
+use MongoDB\Driver\BulkWrite;
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Exception\InvalidArgumentException;
+use MongoDB\Driver\Query as MongoQuery;
+use think\db\connector\Mongo as Connection;
+use think\db\exception\DbException as Exception;
+use think\db\Mongo as Query;
+
+class Mongo
+{
+ // connection对象实例
+ protected $connection;
+ // 最后插入ID
+ protected $insertId = [];
+ // 查询表达式
+ protected $exp = ['<>' => 'ne', '=' => 'eq', '>' => 'gt', '>=' => 'gte', '<' => 'lt', '<=' => 'lte', 'in' => 'in', 'not in' => 'nin', 'nin' => 'nin', 'mod' => 'mod', 'exists' => 'exists', 'null' => 'null', 'notnull' => 'not null', 'not null' => 'not null', 'regex' => 'regex', 'type' => 'type', 'all' => 'all', '> time' => '> time', '< time' => '< time', 'between' => 'between', 'not between' => 'not between', 'between time' => 'between time', 'not between time' => 'not between time', 'notbetween time' => 'not between time', 'like' => 'like', 'near' => 'near', 'size' => 'size'];
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Connection $connection 数据库连接对象实例
+ */
+ public function __construct(Connection $connection)
+ {
+ $this->connection = $connection;
+ }
+
+ /**
+ * 获取当前的连接对象实例
+ * @access public
+ * @return Connection
+ */
+ public function getConnection(): Connection
+ {
+ return $this->connection;
+ }
+
+ /**
+ * key分析
+ * @access protected
+ * @param string $key
+ * @return string
+ */
+ protected function parseKey(Query $query, string $key): string
+ {
+ if (0 === strpos($key, '__TABLE__.')) {
+ [$collection, $key] = explode('.', $key, 2);
+ }
+
+ if ('id' == $key && $this->connection->getConfig('pk_convert_id')) {
+ $key = '_id';
+ }
+
+ return trim($key);
+ }
+
+ /**
+ * value分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $value
+ * @param string $field
+ * @return string
+ */
+ protected function parseValue(Query $query, $value, $field = '')
+ {
+ if ('_id' == $field && 'ObjectID' == $this->connection->getConfig('pk_type') && is_string($value)) {
+ try {
+ return new ObjectID($value);
+ } catch (InvalidArgumentException $e) {
+ return new ObjectID();
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * insert数据分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param array $data 数据
+ * @return array
+ */
+ protected function parseData(Query $query, array $data): array
+ {
+ if (empty($data)) {
+ return [];
+ }
+
+ $result = [];
+
+ foreach ($data as $key => $val) {
+ $item = $this->parseKey($query, $key);
+
+ if (is_object($val)) {
+ $result[$item] = $val;
+ } elseif (isset($val[0]) && 'exp' == $val[0]) {
+ $result[$item] = $val[1];
+ } elseif (is_null($val)) {
+ $result[$item] = 'NULL';
+ } else {
+ $result[$item] = $this->parseValue($query, $val, $key);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Set数据分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param array $data 数据
+ * @return array
+ */
+ protected function parseSet(Query $query, array $data): array
+ {
+ if (empty($data)) {
+ return [];
+ }
+
+ $result = [];
+
+ foreach ($data as $key => $val) {
+ $item = $this->parseKey($query, $key);
+
+ if (is_array($val) && isset($val[0]) && is_string($val[0]) && 0 === strpos($val[0], '$')) {
+ $result[$val[0]][$item] = $this->parseValue($query, $val[1], $key);
+ } else {
+ $result['$set'][$item] = $this->parseValue($query, $val, $key);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 生成查询过滤条件
+ * @access public
+ * @param Query $query 查询对象
+ * @param mixed $where
+ * @return array
+ */
+ public function parseWhere(Query $query, array $where): array
+ {
+ if (empty($where)) {
+ $where = [];
+ }
+
+ $filter = [];
+ foreach ($where as $logic => $val) {
+ $logic = '$' . strtolower($logic);
+ foreach ($val as $field => $value) {
+ if (is_array($value)) {
+ if (key($value) !== 0) {
+ throw new Exception('where express error:' . var_export($value, true));
+ }
+ $field = array_shift($value);
+ } elseif (!($value instanceof \Closure)) {
+ throw new Exception('where express error:' . var_export($value, true));
+ }
+
+ if ($value instanceof \Closure) {
+ // 使用闭包查询
+ $query = new Query($this->connection);
+ call_user_func_array($value, [ & $query]);
+ $filter[$logic][] = $this->parseWhere($query, $query->getOptions('where'));
+ } else {
+ if (strpos($field, '|')) {
+ // 不同字段使用相同查询条件(OR)
+ $array = explode('|', $field);
+ foreach ($array as $k) {
+ $filter['$or'][] = $this->parseWhereItem($query, $k, $value);
+ }
+ } elseif (strpos($field, '&')) {
+ // 不同字段使用相同查询条件(AND)
+ $array = explode('&', $field);
+ foreach ($array as $k) {
+ $filter['$and'][] = $this->parseWhereItem($query, $k, $value);
+ }
+ } else {
+ // 对字段使用表达式查询
+ $field = is_string($field) ? $field : '';
+ $filter[$logic][] = $this->parseWhereItem($query, $field, $value);
+ }
+ }
+ }
+ }
+
+ $options = $query->getOptions();
+ if (!empty($options['soft_delete'])) {
+ // 附加软删除条件
+ [$field, $condition] = $options['soft_delete'];
+ $filter['$and'][] = $this->parseWhereItem($query, $field, $condition);
+ }
+
+ return $filter;
+ }
+
+ // where子单元分析
+ protected function parseWhereItem(Query $query, $field, $val): array
+ {
+ $key = $field ? $this->parseKey($query, $field) : '';
+ // 查询规则和条件
+ if (!is_array($val)) {
+ $val = ['=', $val];
+ }
+ [$exp, $value] = $val;
+
+ // 对一个字段使用多个查询条件
+ if (is_array($exp)) {
+ $data = [];
+ foreach ($val as $value) {
+ $exp = $value[0];
+ $value = $value[1];
+ if (!in_array($exp, $this->exp)) {
+ $exp = strtolower($exp);
+ if (isset($this->exp[$exp])) {
+ $exp = $this->exp[$exp];
+ }
+ }
+ $k = '$' . $exp;
+ $data[$k] = $value;
+ }
+ $result[$key] = $data;
+ return $result;
+ } elseif (!in_array($exp, $this->exp)) {
+ $exp = strtolower($exp);
+ if (isset($this->exp[$exp])) {
+ $exp = $this->exp[$exp];
+ } else {
+ throw new Exception('where express error:' . $exp);
+ }
+ }
+
+ $result = [];
+ if ('=' == $exp) {
+ // 普通查询
+ $result[$key] = $this->parseValue($query, $value, $key);
+ } elseif (in_array($exp, ['neq', 'ne', 'gt', 'egt', 'gte', 'lt', 'lte', 'elt', 'mod'])) {
+ // 比较运算
+ $k = '$' . $exp;
+ $result[$key] = [$k => $this->parseValue($query, $value, $key)];
+ } elseif ('null' == $exp) {
+ // NULL 查询
+ $result[$key] = null;
+ } elseif ('not null' == $exp) {
+ $result[$key] = ['$ne' => null];
+ } elseif ('all' == $exp) {
+ // 满足所有指定条件
+ $result[$key] = ['$all', $this->parseValue($query, $value, $key)];
+ } elseif ('between' == $exp) {
+ // 区间查询
+ $value = is_array($value) ? $value : explode(',', $value);
+ $result[$key] = ['$gte' => $this->parseValue($query, $value[0], $key), '$lte' => $this->parseValue($query, $value[1], $key)];
+ } elseif ('not between' == $exp) {
+ // 范围查询
+ $value = is_array($value) ? $value : explode(',', $value);
+ $result[$key] = ['$lt' => $this->parseValue($query, $value[0], $key), '$gt' => $this->parseValue($query, $value[1], $key)];
+ } elseif ('exists' == $exp) {
+ // 字段是否存在
+ $result[$key] = ['$exists' => (bool) $value];
+ } elseif ('type' == $exp) {
+ // 类型查询
+ $result[$key] = ['$type' => intval($value)];
+ } elseif ('exp' == $exp) {
+ // 表达式查询
+ $result['$where'] = $value instanceof Javascript ? $value : new Javascript($value);
+ } elseif ('like' == $exp) {
+ // 模糊查询 采用正则方式
+ $result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i');
+ } elseif (in_array($exp, ['nin', 'in'])) {
+ // IN 查询
+ $value = is_array($value) ? $value : explode(',', $value);
+ foreach ($value as $k => $val) {
+ $value[$k] = $this->parseValue($query, $val, $key);
+ }
+ $result[$key] = ['$' . $exp => $value];
+ } elseif ('regex' == $exp) {
+ $result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i');
+ } elseif ('< time' == $exp) {
+ $result[$key] = ['$lt' => $this->parseDateTime($query, $value, $field)];
+ } elseif ('> time' == $exp) {
+ $result[$key] = ['$gt' => $this->parseDateTime($query, $value, $field)];
+ } elseif ('between time' == $exp) {
+ // 区间查询
+ $value = is_array($value) ? $value : explode(',', $value);
+ $result[$key] = ['$gte' => $this->parseDateTime($query, $value[0], $field), '$lte' => $this->parseDateTime($query, $value[1], $field)];
+ } elseif ('not between time' == $exp) {
+ // 范围查询
+ $value = is_array($value) ? $value : explode(',', $value);
+ $result[$key] = ['$lt' => $this->parseDateTime($query, $value[0], $field), '$gt' => $this->parseDateTime($query, $value[1], $field)];
+ } elseif ('near' == $exp) {
+ // 经纬度查询
+ $result[$key] = ['$near' => $this->parseValue($query, $value, $key)];
+ } elseif ('size' == $exp) {
+ // 元素长度查询
+ $result[$key] = ['$size' => intval($value)];
+ } else {
+ // 普通查询
+ $result[$key] = $this->parseValue($query, $value, $key);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 日期时间条件解析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $value
+ * @param string $key
+ * @return string
+ */
+ protected function parseDateTime(Query $query, $value, $key)
+ {
+ // 获取时间字段类型
+ $type = $query->getFieldType($key);
+
+ if ($type) {
+ if (is_string($value)) {
+ $value = strtotime($value) ?: $value;
+ }
+
+ if (is_int($value)) {
+ if (preg_match('/(datetime|timestamp)/is', $type)) {
+ // 日期及时间戳类型
+ $value = date('Y-m-d H:i:s', $value);
+ } elseif (preg_match('/(date)/is', $type)) {
+ // 日期及时间戳类型
+ $value = date('Y-m-d', $value);
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * 获取最后写入的ID 如果是insertAll方法的话 返回所有写入的ID
+ * @access public
+ * @return mixed
+ */
+ public function getLastInsID()
+ {
+ return $this->insertId;
+ }
+
+ /**
+ * 生成insert BulkWrite对象
+ * @access public
+ * @param Query $query 查询对象
+ * @return BulkWrite
+ */
+ public function insert(Query $query): BulkWrite
+ {
+ // 分析并处理数据
+ $options = $query->getOptions();
+
+ $data = $this->parseData($query, $options['data']);
+
+ $bulk = new BulkWrite;
+
+ if ($insertId = $bulk->insert($data)) {
+ $this->insertId = $insertId;
+ }
+
+ $this->log('insert', $data, $options);
+
+ return $bulk;
+ }
+
+ /**
+ * 生成insertall BulkWrite对象
+ * @access public
+ * @param Query $query 查询对象
+ * @param array $dataSet 数据集
+ * @return BulkWrite
+ */
+ public function insertAll(Query $query, array $dataSet): BulkWrite
+ {
+ $bulk = new BulkWrite;
+ $options = $query->getOptions();
+
+ $this->insertId = [];
+ foreach ($dataSet as $data) {
+ // 分析并处理数据
+ $data = $this->parseData($query, $data);
+ if ($insertId = $bulk->insert($data)) {
+ $this->insertId[] = $insertId;
+ }
+ }
+
+ $this->log('insert', $dataSet, $options);
+
+ return $bulk;
+ }
+
+ /**
+ * 生成update BulkWrite对象
+ * @access public
+ * @param Query $query 查询对象
+ * @return BulkWrite
+ */
+ public function update(Query $query): BulkWrite
+ {
+ $options = $query->getOptions();
+
+ $data = $this->parseSet($query, $options['data']);
+ $where = $this->parseWhere($query, $options['where']);
+
+ if (1 == $options['limit']) {
+ $updateOptions = ['multi' => false];
+ } else {
+ $updateOptions = ['multi' => true];
+ }
+
+ $bulk = new BulkWrite;
+
+ $bulk->update($where, $data, $updateOptions);
+
+ $this->log('update', $data, $where);
+
+ return $bulk;
+ }
+
+ /**
+ * 生成delete BulkWrite对象
+ * @access public
+ * @param Query $query 查询对象
+ * @return BulkWrite
+ */
+ public function delete(Query $query): BulkWrite
+ {
+ $options = $query->getOptions();
+ $where = $this->parseWhere($query, $options['where']);
+
+ $bulk = new BulkWrite;
+
+ if (1 == $options['limit']) {
+ $deleteOptions = ['limit' => 1];
+ } else {
+ $deleteOptions = ['limit' => 0];
+ }
+
+ $bulk->delete($where, $deleteOptions);
+
+ $this->log('remove', $where, $deleteOptions);
+
+ return $bulk;
+ }
+
+ /**
+ * 生成Mongo查询对象
+ * @access public
+ * @param Query $query 查询对象
+ * @param bool $one 是否仅获取一个记录
+ * @return MongoQuery
+ */
+ public function select(Query $query, bool $one = false): MongoQuery
+ {
+ $options = $query->getOptions();
+
+ $where = $this->parseWhere($query, $options['where']);
+
+ if ($one) {
+ $options['limit'] = 1;
+ }
+
+ $query = new MongoQuery($where, $options);
+
+ $this->log('find', $where, $options);
+
+ return $query;
+ }
+
+ /**
+ * 生成Count命令
+ * @access public
+ * @param Query $query 查询对象
+ * @return Command
+ */
+ public function count(Query $query): Command
+ {
+ $options = $query->getOptions();
+
+ $cmd['count'] = $options['table'];
+ $cmd['query'] = (object) $this->parseWhere($query, $options['where']);
+
+ foreach (['hint', 'limit', 'maxTimeMS', 'skip'] as $option) {
+ if (isset($options[$option])) {
+ $cmd[$option] = $options[$option];
+ }
+ }
+
+ $command = new Command($cmd);
+ $this->log('cmd', 'count', $cmd);
+
+ return $command;
+ }
+
+ /**
+ * 聚合查询命令
+ * @access public
+ * @param Query $query 查询对象
+ * @param array $extra 指令和字段
+ * @return Command
+ */
+ public function aggregate(Query $query, array $extra): Command
+ {
+ $options = $query->getOptions();
+ [$fun, $field] = $extra;
+
+ if ('id' == $field && $this->connection->getConfig('pk_convert_id')) {
+ $field = '_id';
+ }
+
+ $group = isset($options['group']) ? '$' . $options['group'] : null;
+
+ $pipeline = [
+ ['$match' => (object) $this->parseWhere($query, $options['where'])],
+ ['$group' => ['_id' => $group, 'aggregate' => ['$' . $fun => '$' . $field]]],
+ ];
+
+ $cmd = [
+ 'aggregate' => $options['table'],
+ 'allowDiskUse' => true,
+ 'pipeline' => $pipeline,
+ 'cursor' => new \stdClass,
+ ];
+
+ foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) {
+ if (isset($options[$option])) {
+ $cmd[$option] = $options[$option];
+ }
+ }
+
+ $command = new Command($cmd);
+
+ $this->log('aggregate', $cmd);
+
+ return $command;
+ }
+
+ /**
+ * 多聚合查询命令, 可以对多个字段进行 group by 操作
+ *
+ * @param Query $query 查询对象
+ * @param array $extra 指令和字段
+ * @return Command
+ */
+ public function multiAggregate(Query $query, $extra): Command
+ {
+ $options = $query->getOptions();
+
+ [$aggregate, $groupBy] = $extra;
+
+ $groups = ['_id' => []];
+
+ foreach ($groupBy as $field) {
+ $groups['_id'][$field] = '$' . $field;
+ }
+
+ foreach ($aggregate as $fun => $field) {
+ $groups[$field . '_' . $fun] = ['$' . $fun => '$' . $field];
+ }
+
+ $pipeline = [
+ ['$match' => (object) $this->parseWhere($query, $options['where'])],
+ ['$group' => $groups],
+ ];
+
+ $cmd = [
+ 'aggregate' => $options['table'],
+ 'allowDiskUse' => true,
+ 'pipeline' => $pipeline,
+ 'cursor' => new \stdClass,
+ ];
+
+ foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) {
+ if (isset($options[$option])) {
+ $cmd[$option] = $options[$option];
+ }
+ }
+
+ $command = new Command($cmd);
+ $this->log('group', $cmd);
+
+ return $command;
+ }
+
+ /**
+ * 生成distinct命令
+ * @access public
+ * @param Query $query 查询对象
+ * @param string $field 字段名
+ * @return Command
+ */
+ public function distinct(Query $query, $field): Command
+ {
+ $options = $query->getOptions();
+
+ $cmd = [
+ 'distinct' => $options['table'],
+ 'key' => $field,
+ ];
+
+ if (!empty($options['where'])) {
+ $cmd['query'] = (object) $this->parseWhere($query, $options['where']);
+ }
+
+ if (isset($options['maxTimeMS'])) {
+ $cmd['maxTimeMS'] = $options['maxTimeMS'];
+ }
+
+ $command = new Command($cmd);
+
+ $this->log('cmd', 'distinct', $cmd);
+
+ return $command;
+ }
+
+ /**
+ * 查询所有的collection
+ * @access public
+ * @return Command
+ */
+ public function listcollections(): Command
+ {
+ $cmd = ['listCollections' => 1];
+ $command = new Command($cmd);
+
+ $this->log('cmd', 'listCollections', $cmd);
+
+ return $command;
+ }
+
+ /**
+ * 查询数据表的状态信息
+ * @access public
+ * @param Query $query 查询对象
+ * @return Command
+ */
+ public function collStats(Query $query): Command
+ {
+ $options = $query->getOptions();
+
+ $cmd = ['collStats' => $options['table']];
+ $command = new Command($cmd);
+
+ $this->log('cmd', 'collStats', $cmd);
+
+ return $command;
+ }
+
+ protected function log($type, $data, $options = [])
+ {
+ $this->connection->mongoLog($type, $data, $options);
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/builder/Mysql.php b/vendor/topthink/think-orm/src/db/builder/Mysql.php
new file mode 100755
index 0000000..df6bffd
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/builder/Mysql.php
@@ -0,0 +1,421 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\builder;
+
+use think\db\Builder;
+use think\db\exception\DbException as Exception;
+use think\db\Query;
+use think\db\Raw;
+
+/**
+ * mysql数据库驱动
+ */
+class Mysql extends Builder
+{
+ /**
+ * 查询表达式解析
+ * @var array
+ */
+ protected $parser = [
+ 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='],
+ 'parseLike' => ['LIKE', 'NOT LIKE'],
+ 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'],
+ 'parseIn' => ['NOT IN', 'IN'],
+ 'parseExp' => ['EXP'],
+ 'parseRegexp' => ['REGEXP', 'NOT REGEXP'],
+ 'parseNull' => ['NOT NULL', 'NULL'],
+ 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'],
+ 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'],
+ 'parseExists' => ['NOT EXISTS', 'EXISTS'],
+ 'parseColumn' => ['COLUMN'],
+ 'parseFindInSet' => ['FIND IN SET'],
+ ];
+
+ /**
+ * SELECT SQL表达式
+ * @var string
+ */
+ protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%PARTITION%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * INSERT SQL表达式
+ * @var string
+ */
+ protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% SET %SET% %DUPLICATE%%COMMENT%';
+
+ /**
+ * INSERT ALL SQL表达式
+ * @var string
+ */
+ protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% (%FIELD%) VALUES %DATA% %DUPLICATE%%COMMENT%';
+
+ /**
+ * UPDATE SQL表达式
+ * @var string
+ */
+ protected $updateSql = 'UPDATE%EXTRA% %TABLE%%PARTITION% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * DELETE SQL表达式
+ * @var string
+ */
+ protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%PARTITION%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * 生成查询SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @param bool $one 是否仅获取一个记录
+ * @return string
+ */
+ public function select(Query $query, bool $one = false): string
+ {
+ $options = $query->getOptions();
+
+ return str_replace(
+ ['%TABLE%', '%PARTITION%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'],
+ [
+ $this->parseTable($query, $options['table']),
+ $this->parsePartition($query, $options['partition']),
+ $this->parseDistinct($query, $options['distinct']),
+ $this->parseExtra($query, $options['extra']),
+ $this->parseField($query, $options['field']),
+ $this->parseJoin($query, $options['join']),
+ $this->parseWhere($query, $options['where']),
+ $this->parseGroup($query, $options['group']),
+ $this->parseHaving($query, $options['having']),
+ $this->parseOrder($query, $options['order']),
+ $this->parseLimit($query, $one ? '1' : $options['limit']),
+ $this->parseUnion($query, $options['union']),
+ $this->parseLock($query, $options['lock']),
+ $this->parseComment($query, $options['comment']),
+ $this->parseForce($query, $options['force']),
+ ],
+ $this->selectSql);
+ }
+
+ /**
+ * 生成Insert SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @return string
+ */
+ public function insert(Query $query): string
+ {
+ $options = $query->getOptions();
+
+ // 分析并处理数据
+ $data = $this->parseData($query, $options['data']);
+ if (empty($data)) {
+ return '';
+ }
+
+ $set = [];
+ foreach ($data as $key => $val) {
+ $set[] = $key . ' = ' . $val;
+ }
+
+ return str_replace(
+ ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%SET%', '%DUPLICATE%', '%COMMENT%'],
+ [
+ !empty($options['replace']) ? 'REPLACE' : 'INSERT',
+ $this->parseExtra($query, $options['extra']),
+ $this->parseTable($query, $options['table']),
+ $this->parsePartition($query, $options['partition']),
+ implode(' , ', $set),
+ $this->parseDuplicate($query, $options['duplicate']),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->insertSql);
+ }
+
+ /**
+ * 生成insertall SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @param array $dataSet 数据集
+ * @param bool $replace 是否replace
+ * @return string
+ */
+ public function insertAll(Query $query, array $dataSet, bool $replace = false): string
+ {
+ $options = $query->getOptions();
+
+ // 获取绑定信息
+ $bind = $query->getFieldsBindType();
+
+ // 获取合法的字段
+ if ('*' == $options['field']) {
+ $allowFields = array_keys($bind);
+ } else {
+ $allowFields = $options['field'];
+ }
+
+ $fields = [];
+ $values = [];
+
+ foreach ($dataSet as $data) {
+ $data = $this->parseData($query, $data, $allowFields, $bind);
+
+ $values[] = '( ' . implode(',', array_values($data)) . ' )';
+
+ if (!isset($insertFields)) {
+ $insertFields = array_keys($data);
+ }
+ }
+
+ foreach ($insertFields as $field) {
+ $fields[] = $this->parseKey($query, $field);
+ }
+
+ return str_replace(
+ ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%FIELD%', '%DATA%', '%DUPLICATE%', '%COMMENT%'],
+ [
+ $replace ? 'REPLACE' : 'INSERT',
+ $this->parseExtra($query, $options['extra']),
+ $this->parseTable($query, $options['table']),
+ $this->parsePartition($query, $options['partition']),
+ implode(' , ', $fields),
+ implode(' , ', $values),
+ $this->parseDuplicate($query, $options['duplicate']),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->insertAllSql);
+ }
+
+ /**
+ * 生成update SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @return string
+ */
+ public function update(Query $query): string
+ {
+ $options = $query->getOptions();
+
+ $data = $this->parseData($query, $options['data']);
+
+ if (empty($data)) {
+ return '';
+ }
+ $set = [];
+ foreach ($data as $key => $val) {
+ $set[] = $key . ' = ' . $val;
+ }
+
+ return str_replace(
+ ['%TABLE%', '%PARTITION%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
+ [
+ $this->parseTable($query, $options['table']),
+ $this->parsePartition($query, $options['partition']),
+ $this->parseExtra($query, $options['extra']),
+ implode(' , ', $set),
+ $this->parseJoin($query, $options['join']),
+ $this->parseWhere($query, $options['where']),
+ $this->parseOrder($query, $options['order']),
+ $this->parseLimit($query, $options['limit']),
+ $this->parseLock($query, $options['lock']),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->updateSql);
+ }
+
+ /**
+ * 生成delete SQL
+ * @access public
+ * @param Query $query 查询对象
+ * @return string
+ */
+ public function delete(Query $query): string
+ {
+ $options = $query->getOptions();
+
+ return str_replace(
+ ['%TABLE%', '%PARTITION%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
+ [
+ $this->parseTable($query, $options['table']),
+ $this->parsePartition($query, $options['partition']),
+ $this->parseExtra($query, $options['extra']),
+ !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '',
+ $this->parseJoin($query, $options['join']),
+ $this->parseWhere($query, $options['where']),
+ $this->parseOrder($query, $options['order']),
+ $this->parseLimit($query, $options['limit']),
+ $this->parseLock($query, $options['lock']),
+ $this->parseComment($query, $options['comment']),
+ ],
+ $this->deleteSql);
+ }
+
+ /**
+ * 正则查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @return string
+ */
+ protected function parseRegexp(Query $query, string $key, string $exp, $value, string $field): string
+ {
+ if ($value instanceof Raw) {
+ $value = $this->parseRaw($query, $value);
+ }
+
+ return $key . ' ' . $exp . ' ' . $value;
+ }
+
+ /**
+ * FIND_IN_SET 查询
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $exp
+ * @param mixed $value
+ * @param string $field
+ * @return string
+ */
+ protected function parseFindInSet(Query $query, string $key, string $exp, $value, string $field): string
+ {
+ if ($value instanceof Raw) {
+ $value = $this->parseRaw($query, $value);
+ }
+
+ return 'FIND_IN_SET(' . $value . ', ' . $key . ')';
+ }
+
+ /**
+ * 字段和表名处理
+ * @access public
+ * @param Query $query 查询对象
+ * @param mixed $key 字段名
+ * @param bool $strict 严格检测
+ * @return string
+ */
+ public function parseKey(Query $query, $key, bool $strict = false): string
+ {
+ if (is_int($key)) {
+ return (string) $key;
+ } elseif ($key instanceof Raw) {
+ return $this->parseRaw($query, $key);
+ }
+
+ $key = trim($key);
+
+ if (strpos($key, '->') && false === strpos($key, '(')) {
+ // JSON字段支持
+ [$field, $name] = explode('->', $key, 2);
+ return 'json_extract(' . $this->parseKey($query, $field) . ', \'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->', '.', $name) . '\')';
+ } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
+ [$table, $key] = explode('.', $key, 2);
+
+ $alias = $query->getOptions('alias');
+
+ if ('__TABLE__' == $table) {
+ $table = $query->getOptions('table');
+ $table = is_array($table) ? array_shift($table) : $table;
+ }
+
+ if (isset($alias[$table])) {
+ $table = $alias[$table];
+ }
+ }
+
+ if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) {
+ throw new Exception('not support data:' . $key);
+ }
+
+ if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) {
+ $key = '`' . $key . '`';
+ }
+
+ if (isset($table)) {
+ if (strpos($table, '.')) {
+ $table = str_replace('.', '`.`', $table);
+ }
+
+ $key = '`' . $table . '`.' . $key;
+ }
+
+ return $key;
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @param Query $query 查询对象
+ * @return string
+ */
+ protected function parseRand(Query $query): string
+ {
+ return 'rand()';
+ }
+
+ /**
+ * Partition 分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param string|array $partition 分区
+ * @return string
+ */
+ protected function parsePartition(Query $query, $partition): string
+ {
+ if ('' == $partition) {
+ return '';
+ }
+
+ if (is_string($partition)) {
+ $partition = explode(',', $partition);
+ }
+
+ return ' PARTITION (' . implode(' , ', $partition) . ') ';
+ }
+
+ /**
+ * ON DUPLICATE KEY UPDATE 分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $duplicate
+ * @return string
+ */
+ protected function parseDuplicate(Query $query, $duplicate): string
+ {
+ if ('' == $duplicate) {
+ return '';
+ }
+
+ if ($duplicate instanceof Raw) {
+ return ' ON DUPLICATE KEY UPDATE ' . $this->parseRaw($query, $duplicate) . ' ';
+ }
+
+ if (is_string($duplicate)) {
+ $duplicate = explode(',', $duplicate);
+ }
+
+ $updates = [];
+ foreach ($duplicate as $key => $val) {
+ if (is_numeric($key)) {
+ $val = $this->parseKey($query, $val);
+ $updates[] = $val . ' = VALUES(' . $val . ')';
+ } elseif ($val instanceof Raw) {
+ $updates[] = $this->parseKey($query, $key) . " = " . $this->parseRaw($query, $val);
+ } else {
+ $name = $query->bindValue($val, $query->getConnection()->getFieldBindType($key));
+ $updates[] = $this->parseKey($query, $key) . " = :" . $name;
+ }
+ }
+
+ return ' ON DUPLICATE KEY UPDATE ' . implode(' , ', $updates) . ' ';
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/builder/Oracle.php b/vendor/topthink/think-orm/src/db/builder/Oracle.php
new file mode 100755
index 0000000..77ad3c8
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/builder/Oracle.php
@@ -0,0 +1,95 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\builder;
+
+use think\db\Builder;
+use think\db\Query;
+
+/**
+ * Oracle数据库驱动
+ */
+class Oracle extends Builder
+{
+ protected $selectSql = 'SELECT * FROM (SELECT thinkphp.*, rownum AS numrow FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%) thinkphp ) %LIMIT%%COMMENT%';
+
+ /**
+ * limit分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $limit
+ * @return string
+ */
+ protected function parseLimit(Query $query, string $limit): string
+ {
+ $limitStr = '';
+
+ if (!empty($limit)) {
+ $limit = explode(',', $limit);
+
+ if (count($limit) > 1) {
+ $limitStr = "(numrow>" . $limit[0] . ") AND (numrow<=" . ($limit[0] + $limit[1]) . ")";
+ } else {
+ $limitStr = "(numrow>0 AND numrow<=" . $limit[0] . ")";
+ }
+
+ }
+
+ return $limitStr ? ' WHERE ' . $limitStr : '';
+ }
+
+ /**
+ * 设置锁机制
+ * @access protected
+ * @param Query $query 查询对象
+ * @param bool|false $lock
+ * @return string
+ */
+ protected function parseLock(Query $query, $lock = false): string
+ {
+ if (!$lock) {
+ return '';
+ }
+
+ return ' FOR UPDATE NOWAIT ';
+ }
+
+ /**
+ * 字段和表名处理
+ * @access public
+ * @param Query $query 查询对象
+ * @param string $key
+ * @param string $strict
+ * @return string
+ */
+ public function parseKey(Query $query, $key, bool $strict = false): string
+ {
+ $key = trim($key);
+
+ if (strpos($key, '->') && false === strpos($key, '(')) {
+ // JSON字段支持
+ [$field, $name] = explode($key, '->');
+ $key = $field . '."' . $name . '"';
+ }
+
+ return $key;
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @param Query $query 查询对象
+ * @return string
+ */
+ protected function parseRand(Query $query): string
+ {
+ return 'DBMS_RANDOM.value';
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/builder/Pgsql.php b/vendor/topthink/think-orm/src/db/builder/Pgsql.php
new file mode 100755
index 0000000..4eace0a
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/builder/Pgsql.php
@@ -0,0 +1,118 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\builder;
+
+use think\db\Builder;
+use think\db\Query;
+use think\db\Raw;
+
+/**
+ * Pgsql数据库驱动
+ */
+class Pgsql extends Builder
+{
+ /**
+ * INSERT SQL表达式
+ * @var string
+ */
+ protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
+
+ /**
+ * INSERT ALL SQL表达式
+ * @var string
+ */
+ protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
+
+ /**
+ * limit分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $limit
+ * @return string
+ */
+ public function parseLimit(Query $query, string $limit): string
+ {
+ $limitStr = '';
+
+ if (!empty($limit)) {
+ $limit = explode(',', $limit);
+ if (count($limit) > 1) {
+ $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' ';
+ } else {
+ $limitStr .= ' LIMIT ' . $limit[0] . ' ';
+ }
+ }
+
+ return $limitStr;
+ }
+
+ /**
+ * 字段和表名处理
+ * @access public
+ * @param Query $query 查询对象
+ * @param mixed $key 字段名
+ * @param bool $strict 严格检测
+ * @return string
+ */
+ public function parseKey(Query $query, $key, bool $strict = false): string
+ {
+ if (is_int($key)) {
+ return (string) $key;
+ } elseif ($key instanceof Raw) {
+ return $this->parseRaw($query, $key);
+ }
+
+ $key = trim($key);
+
+ if (strpos($key, '->') && false === strpos($key, '(')) {
+ // JSON字段支持
+ [$field, $name] = explode('->', $key);
+ $key = '"' . $field . '"' . '->>\'' . $name . '\'';
+ } elseif (strpos($key, '.')) {
+ [$table, $key] = explode('.', $key, 2);
+
+ $alias = $query->getOptions('alias');
+
+ if ('__TABLE__' == $table) {
+ $table = $query->getOptions('table');
+ $table = is_array($table) ? array_shift($table) : $table;
+ }
+
+ if (isset($alias[$table])) {
+ $table = $alias[$table];
+ }
+
+ if ('*' != $key && !preg_match('/[,\"\*\(\).\s]/', $key)) {
+ $key = '"' . $key . '"';
+ }
+ }
+
+ if (isset($table)) {
+ $key = $table . '.' . $key;
+ }
+
+ return $key;
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @param Query $query 查询对象
+ * @return string
+ */
+ protected function parseRand(Query $query): string
+ {
+ return 'RANDOM()';
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/builder/Sqlite.php b/vendor/topthink/think-orm/src/db/builder/Sqlite.php
new file mode 100755
index 0000000..40cab7f
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/builder/Sqlite.php
@@ -0,0 +1,97 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\builder;
+
+use think\db\Builder;
+use think\db\Query;
+use think\db\Raw;
+
+/**
+ * Sqlite数据库驱动
+ */
+class Sqlite extends Builder
+{
+ /**
+ * limit
+ * @access public
+ * @param Query $query 查询对象
+ * @param mixed $limit
+ * @return string
+ */
+ public function parseLimit(Query $query, string $limit): string
+ {
+ $limitStr = '';
+
+ if (!empty($limit)) {
+ $limit = explode(',', $limit);
+ if (count($limit) > 1) {
+ $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' ';
+ } else {
+ $limitStr .= ' LIMIT ' . $limit[0] . ' ';
+ }
+ }
+
+ return $limitStr;
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @param Query $query 查询对象
+ * @return string
+ */
+ protected function parseRand(Query $query): string
+ {
+ return 'RANDOM()';
+ }
+
+ /**
+ * 字段和表名处理
+ * @access public
+ * @param Query $query 查询对象
+ * @param mixed $key 字段名
+ * @param bool $strict 严格检测
+ * @return string
+ */
+ public function parseKey(Query $query, $key, bool $strict = false): string
+ {
+ if (is_int($key)) {
+ return (string) $key;
+ } elseif ($key instanceof Raw) {
+ return $this->parseRaw($query, $key);
+ }
+
+ $key = trim($key);
+
+ if (strpos($key, '.')) {
+ [$table, $key] = explode('.', $key, 2);
+
+ $alias = $query->getOptions('alias');
+
+ if ('__TABLE__' == $table) {
+ $table = $query->getOptions('table');
+ $table = is_array($table) ? array_shift($table) : $table;
+ }
+
+ if (isset($alias[$table])) {
+ $table = $alias[$table];
+ }
+ }
+
+ if (isset($table)) {
+ $key = $table . '.' . $key;
+ }
+
+ return $key;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php b/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php
new file mode 100755
index 0000000..779b5e3
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php
@@ -0,0 +1,184 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\db\builder;
+
+use think\db\Builder;
+use think\db\exception\DbException as Exception;
+use think\db\Query;
+use think\db\Raw;
+
+/**
+ * Sqlsrv数据库驱动
+ */
+class Sqlsrv extends Builder
+{
+ /**
+ * SELECT SQL表达式
+ * @var string
+ */
+ protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%';
+ /**
+ * SELECT INSERT SQL表达式
+ * @var string
+ */
+ protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%';
+
+ /**
+ * UPDATE SQL表达式
+ * @var string
+ */
+ protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * DELETE SQL表达式
+ * @var string
+ */
+ protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%';
+
+ /**
+ * INSERT SQL表达式
+ * @var string
+ */
+ protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
+
+ /**
+ * INSERT ALL SQL表达式
+ * @var string
+ */
+ protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
+
+ /**
+ * order分析
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $order
+ * @return string
+ */
+ protected function parseOrder(Query $query, array $order): string
+ {
+ if (empty($order)) {
+ return ' ORDER BY rand()';
+ }
+
+ $array = [];
+
+ foreach ($order as $key => $val) {
+ if ($val instanceof Raw) {
+ $array[] = $this->parseRaw($query, $val);
+ } elseif ('[rand]' == $val) {
+ $array[] = $this->parseRand($query);
+ } else {
+ if (is_numeric($key)) {
+ [$key, $sort] = explode(' ', strpos($val, ' ') ? $val : $val . ' ');
+ } else {
+ $sort = $val;
+ }
+
+ $sort = in_array(strtolower($sort), ['asc', 'desc'], true) ? ' ' . $sort : '';
+ $array[] = $this->parseKey($query, $key, true) . $sort;
+ }
+ }
+
+ return ' ORDER BY ' . implode(',', $array);
+ }
+
+ /**
+ * 随机排序
+ * @access protected
+ * @param Query $query 查询对象
+ * @return string
+ */
+ protected function parseRand(Query $query): string
+ {
+ return 'rand()';
+ }
+
+ /**
+ * 字段和表名处理
+ * @access public
+ * @param Query $query 查询对象
+ * @param mixed $key 字段名
+ * @param bool $strict 严格检测
+ * @return string
+ */
+ public function parseKey(Query $query, $key, bool $strict = false): string
+ {
+ if (is_int($key)) {
+ return (string) $key;
+ } elseif ($key instanceof Raw) {
+ return $this->parseRaw($query, $key);
+ }
+
+ $key = trim($key);
+
+ if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) {
+ [$table, $key] = explode('.', $key, 2);
+
+ $alias = $query->getOptions('alias');
+
+ if ('__TABLE__' == $table) {
+ $table = $query->getOptions('table');
+ $table = is_array($table) ? array_shift($table) : $table;
+ }
+
+ if (isset($alias[$table])) {
+ $table = $alias[$table];
+ }
+ }
+
+ if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) {
+ throw new Exception('not support data:' . $key);
+ }
+
+ if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) {
+ $key = '[' . $key . ']';
+ }
+
+ if (isset($table)) {
+ $key = '[' . $table . '].' . $key;
+ }
+
+ return $key;
+ }
+
+ /**
+ * limit
+ * @access protected
+ * @param Query $query 查询对象
+ * @param mixed $limit
+ * @return string
+ */
+ protected function parseLimit(Query $query, string $limit): string
+ {
+ if (empty($limit)) {
+ return '';
+ }
+
+ $limit = explode(',', $limit);
+
+ if (count($limit) > 1) {
+ $limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')';
+ } else {
+ $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ")";
+ }
+
+ return 'WHERE ' . $limitStr;
+ }
+
+ public function selectInsert(Query $query, array $fields, string $table): string
+ {
+ $this->selectSql = $this->selectInsertSql;
+
+ return parent::selectInsert($query, $fields, $table);
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php b/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php
new file mode 100755
index 0000000..dabfb92
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php
@@ -0,0 +1,107 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use think\db\Raw;
+
+/**
+ * 聚合查询
+ */
+trait AggregateQuery
+{
+ /**
+ * 聚合查询
+ * @access protected
+ * @param string $aggregate 聚合方法
+ * @param string|Raw $field 字段名
+ * @param bool $force 强制转为数字类型
+ * @return mixed
+ */
+ protected function aggregate(string $aggregate, $field, bool $force = false)
+ {
+ return $this->connection->aggregate($this, $aggregate, $field, $force);
+ }
+
+ /**
+ * COUNT查询
+ * @access public
+ * @param string|Raw $field 字段名
+ * @return int
+ */
+ public function count(string $field = '*'): int
+ {
+ if (!empty($this->options['group'])) {
+ // 支持GROUP
+ $options = $this->getOptions();
+ $subSql = $this->options($options)
+ ->field('count(' . $field . ') AS think_count')
+ ->bind($this->bind)
+ ->buildSql();
+
+ $query = $this->newQuery()->table([$subSql => '_group_count_']);
+
+ $count = $query->aggregate('COUNT', '*');
+ } else {
+ $count = $this->aggregate('COUNT', $field);
+ }
+
+ return (int) $count;
+ }
+
+ /**
+ * SUM查询
+ * @access public
+ * @param string|Raw $field 字段名
+ * @return float
+ */
+ public function sum($field): float
+ {
+ return $this->aggregate('SUM', $field, true);
+ }
+
+ /**
+ * MIN查询
+ * @access public
+ * @param string|Raw $field 字段名
+ * @param bool $force 强制转为数字类型
+ * @return mixed
+ */
+ public function min($field, bool $force = true)
+ {
+ return $this->aggregate('MIN', $field, $force);
+ }
+
+ /**
+ * MAX查询
+ * @access public
+ * @param string|Raw $field 字段名
+ * @param bool $force 强制转为数字类型
+ * @return mixed
+ */
+ public function max($field, bool $force = true)
+ {
+ return $this->aggregate('MAX', $field, $force);
+ }
+
+ /**
+ * AVG查询
+ * @access public
+ * @param string|Raw $field 字段名
+ * @return float
+ */
+ public function avg($field): float
+ {
+ return $this->aggregate('AVG', $field, true);
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php b/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php
new file mode 100755
index 0000000..c33d1ed
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php
@@ -0,0 +1,229 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use think\db\Raw;
+use think\helper\Str;
+
+/**
+ * JOIN和VIEW查询
+ */
+trait JoinAndViewQuery
+{
+
+ /**
+ * 查询SQL组装 join
+ * @access public
+ * @param mixed $join 关联的表名
+ * @param mixed $condition 条件
+ * @param string $type JOIN类型
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function join($join, string $condition = null, string $type = 'INNER', array $bind = [])
+ {
+ $table = $this->getJoinTable($join);
+
+ if (!empty($bind) && $condition) {
+ $this->bindParams($condition, $bind);
+ }
+
+ $this->options['join'][] = [$table, strtoupper($type), $condition];
+
+ return $this;
+ }
+
+ /**
+ * LEFT JOIN
+ * @access public
+ * @param mixed $join 关联的表名
+ * @param mixed $condition 条件
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function leftJoin($join, string $condition = null, array $bind = [])
+ {
+ return $this->join($join, $condition, 'LEFT', $bind);
+ }
+
+ /**
+ * RIGHT JOIN
+ * @access public
+ * @param mixed $join 关联的表名
+ * @param mixed $condition 条件
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function rightJoin($join, string $condition = null, array $bind = [])
+ {
+ return $this->join($join, $condition, 'RIGHT', $bind);
+ }
+
+ /**
+ * FULL JOIN
+ * @access public
+ * @param mixed $join 关联的表名
+ * @param mixed $condition 条件
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function fullJoin($join, string $condition = null, array $bind = [])
+ {
+ return $this->join($join, $condition, 'FULL');
+ }
+
+ /**
+ * 获取Join表名及别名 支持
+ * ['prefix_table或者子查询'=>'alias'] 'table alias'
+ * @access protected
+ * @param array|string|Raw $join JION表名
+ * @param string $alias 别名
+ * @return string|array
+ */
+ protected function getJoinTable($join, &$alias = null)
+ {
+ if (is_array($join)) {
+ $table = $join;
+ $alias = array_shift($join);
+ return $table;
+ } elseif ($join instanceof Raw) {
+ return $join;
+ }
+
+ $join = trim($join);
+
+ if (false !== strpos($join, '(')) {
+ // 使用子查询
+ $table = $join;
+ } else {
+ // 使用别名
+ if (strpos($join, ' ')) {
+ // 使用别名
+ [$table, $alias] = explode(' ', $join);
+ } else {
+ $table = $join;
+ if (false === strpos($join, '.')) {
+ $alias = $join;
+ }
+ }
+
+ if ($this->prefix && false === strpos($table, '.') && 0 !== strpos($table, $this->prefix)) {
+ $table = $this->getTable($table);
+ }
+ }
+
+ if (!empty($alias) && $table != $alias) {
+ $table = [$table => $alias];
+ }
+
+ return $table;
+ }
+
+ /**
+ * 指定JOIN查询字段
+ * @access public
+ * @param string|array $join 数据表
+ * @param string|array $field 查询字段
+ * @param string $on JOIN条件
+ * @param string $type JOIN类型
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function view($join, $field = true, $on = null, string $type = 'INNER', array $bind = [])
+ {
+ $this->options['view'] = true;
+
+ $fields = [];
+ $table = $this->getJoinTable($join, $alias);
+
+ if (true === $field) {
+ $fields = $alias . '.*';
+ } else {
+ if (is_string($field)) {
+ $field = explode(',', $field);
+ }
+
+ foreach ($field as $key => $val) {
+ if (is_numeric($key)) {
+ $fields[] = $alias . '.' . $val;
+
+ $this->options['map'][$val] = $alias . '.' . $val;
+ } else {
+ if (preg_match('/[,=\.\'\"\(\s]/', $key)) {
+ $name = $key;
+ } else {
+ $name = $alias . '.' . $key;
+ }
+
+ $fields[] = $name . ' AS ' . $val;
+
+ $this->options['map'][$val] = $name;
+ }
+ }
+ }
+
+ $this->field($fields);
+
+ if ($on) {
+ $this->join($table, $on, $type, $bind);
+ } else {
+ $this->table($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 视图查询处理
+ * @access protected
+ * @param array $options 查询参数
+ * @return void
+ */
+ protected function parseView(array &$options): void
+ {
+ foreach (['AND', 'OR'] as $logic) {
+ if (isset($options['where'][$logic])) {
+ foreach ($options['where'][$logic] as $key => $val) {
+ if (array_key_exists($key, $options['map'])) {
+ array_shift($val);
+ array_unshift($val, $options['map'][$key]);
+ $options['where'][$logic][$options['map'][$key]] = $val;
+ unset($options['where'][$logic][$key]);
+ }
+ }
+ }
+ }
+
+ if (isset($options['order'])) {
+ // 视图查询排序处理
+ foreach ($options['order'] as $key => $val) {
+ if (is_numeric($key) && is_string($val)) {
+ if (strpos($val, ' ')) {
+ [$field, $sort] = explode(' ', $val);
+ if (array_key_exists($field, $options['map'])) {
+ $options['order'][$options['map'][$field]] = $sort;
+ unset($options['order'][$key]);
+ }
+ } elseif (array_key_exists($val, $options['map'])) {
+ $options['order'][$options['map'][$val]] = 'asc';
+ unset($options['order'][$key]);
+ }
+ } elseif (array_key_exists($key, $options['map'])) {
+ $options['order'][$options['map'][$key]] = $val;
+ unset($options['order'][$key]);
+ }
+ }
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php
new file mode 100755
index 0000000..ffb72de
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php
@@ -0,0 +1,524 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use Closure;
+use think\helper\Str;
+use think\Model;
+use think\model\Collection as ModelCollection;
+
+/**
+ * 模型及关联查询
+ */
+trait ModelRelationQuery
+{
+
+ /**
+ * 当前模型对象
+ * @var Model
+ */
+ protected $model;
+
+ /**
+ * 指定模型
+ * @access public
+ * @param Model $model 模型对象实例
+ * @return $this
+ */
+ public function model(Model $model)
+ {
+ $this->model = $model;
+ return $this;
+ }
+
+ /**
+ * 获取当前的模型对象
+ * @access public
+ * @return Model|null
+ */
+ public function getModel()
+ {
+ return $this->model;
+ }
+
+ /**
+ * 设置需要隐藏的输出属性
+ * @access public
+ * @param array $hidden 需要隐藏的字段名
+ * @return $this
+ */
+ public function hidden(array $hidden)
+ {
+ $this->options['hidden'] = $hidden;
+ return $this;
+ }
+
+ /**
+ * 设置需要输出的属性
+ * @access public
+ * @param array $visible 需要输出的属性
+ * @return $this
+ */
+ public function visible(array $visible)
+ {
+ $this->options['visible'] = $visible;
+ return $this;
+ }
+
+ /**
+ * 设置需要追加输出的属性
+ * @access public
+ * @param array $append 需要追加的属性
+ * @return $this
+ */
+ public function append(array $append)
+ {
+ $this->options['append'] = $append;
+ return $this;
+ }
+
+ /**
+ * 添加查询范围
+ * @access public
+ * @param array|string|Closure $scope 查询范围定义
+ * @param array $args 参数
+ * @return $this
+ */
+ public function scope($scope, ...$args)
+ {
+ // 查询范围的第一个参数始终是当前查询对象
+ array_unshift($args, $this);
+
+ if ($scope instanceof Closure) {
+ call_user_func_array($scope, $args);
+ return $this;
+ }
+
+ if (is_string($scope)) {
+ $scope = explode(',', $scope);
+ }
+
+ if ($this->model) {
+ // 检查模型类的查询范围方法
+ foreach ($scope as $name) {
+ $method = 'scope' . trim($name);
+
+ if (method_exists($this->model, $method)) {
+ call_user_func_array([$this->model, $method], $args);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置关联查询
+ * @access public
+ * @param array $relation 关联名称
+ * @return $this
+ */
+ public function relation(array $relation)
+ {
+ if (!empty($relation)) {
+ $this->options['relation'] = $relation;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 使用搜索器条件搜索字段
+ * @access public
+ * @param string|array $fields 搜索字段
+ * @param mixed $data 搜索数据
+ * @param string $prefix 字段前缀标识
+ * @return $this
+ */
+ public function withSearch($fields, $data = [], string $prefix = '')
+ {
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+
+ $likeFields = $this->getConfig('match_like_fields') ?: [];
+
+ foreach ($fields as $key => $field) {
+ if ($field instanceof Closure) {
+ $field($this, $data[$key] ?? null, $data, $prefix);
+ } elseif ($this->model) {
+ // 检测搜索器
+ $fieldName = is_numeric($key) ? $field : $key;
+ $method = 'search' . Str::studly($fieldName) . 'Attr';
+
+ if (method_exists($this->model, $method)) {
+ $this->model->$method($this, $data[$field] ?? null, $data, $prefix);
+ } elseif (isset($data[$field])) {
+ $this->where($fieldName, in_array($fieldName, $likeFields) ? 'like' : '=', in_array($fieldName, $likeFields) ? '%' . $data[$field] . '%' : $data[$field]);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置数据字段获取器
+ * @access public
+ * @param string|array $name 字段名
+ * @param callable $callback 闭包获取器
+ * @return $this
+ */
+ public function withAttr($name, callable $callback = null)
+ {
+ if (is_array($name)) {
+ $this->options['with_attr'] = $name;
+ } else {
+ $this->options['with_attr'][$name] = $callback;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 关联预载入 In方式
+ * @access public
+ * @param array|string $with 关联方法名称
+ * @return $this
+ */
+ public function with($with)
+ {
+ if (!empty($with)) {
+ $this->options['with'] = (array) $with;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 关联预载入 JOIN方式
+ * @access protected
+ * @param array|string $with 关联方法名
+ * @param string $joinType JOIN方式
+ * @return $this
+ */
+ public function withJoin($with, string $joinType = '')
+ {
+ if (empty($with)) {
+ return $this;
+ }
+
+ $with = (array) $with;
+ $first = true;
+
+ foreach ($with as $key => $relation) {
+ $closure = null;
+ $field = true;
+
+ if ($relation instanceof Closure) {
+ // 支持闭包查询过滤关联条件
+ $closure = $relation;
+ $relation = $key;
+ } elseif (is_array($relation)) {
+ $field = $relation;
+ $relation = $key;
+ } elseif (is_string($relation) && strpos($relation, '.')) {
+ $relation = strstr($relation, '.', true);
+ }
+
+ $result = $this->model->eagerly($this, $relation, $field, $joinType, $closure, $first);
+
+ if (!$result) {
+ unset($with[$key]);
+ } else {
+ $first = false;
+ }
+ }
+
+ $this->via();
+
+ $this->options['with_join'] = $with;
+
+ return $this;
+ }
+
+ /**
+ * 关联统计
+ * @access protected
+ * @param array|string $relations 关联方法名
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param bool $subQuery 是否使用子查询
+ * @return $this
+ */
+ protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true)
+ {
+ if (!$subQuery) {
+ $this->options['with_count'][] = [$relations, $aggregate, $field];
+ } else {
+ if (!isset($this->options['field'])) {
+ $this->field('*');
+ }
+
+ $this->model->relationCount($this, (array) $relations, $aggregate, $field, true);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 关联缓存
+ * @access public
+ * @param string|array|bool $relation 关联方法名
+ * @param mixed $key 缓存key
+ * @param integer|\DateTime $expire 缓存有效期
+ * @param string $tag 缓存标签
+ * @return $this
+ */
+ public function withCache($relation = true, $key = true, $expire = null, string $tag = null)
+ {
+ if (false === $relation || false === $key || !$this->getConnection()->getCache()) {
+ return $this;
+ }
+
+ if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) {
+ $expire = $key;
+ $key = true;
+ }
+
+ if (true === $relation || is_numeric($relation)) {
+ $this->options['with_cache'] = $relation;
+ return $this;
+ }
+
+ $relations = (array) $relation;
+ foreach ($relations as $name => $relation) {
+ if (!is_numeric($name)) {
+ $this->options['with_cache'][$name] = is_array($relation) ? $relation : [$key, $relation, $tag];
+ } else {
+ $this->options['with_cache'][$relation] = [$key, $expire, $tag];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param string|array $relation 关联方法名
+ * @param bool $subQuery 是否使用子查询
+ * @return $this
+ */
+ public function withCount($relation, bool $subQuery = true)
+ {
+ return $this->withAggregate($relation, 'count', '*', $subQuery);
+ }
+
+ /**
+ * 关联统计Sum
+ * @access public
+ * @param string|array $relation 关联方法名
+ * @param string $field 字段
+ * @param bool $subQuery 是否使用子查询
+ * @return $this
+ */
+ public function withSum($relation, string $field, bool $subQuery = true)
+ {
+ return $this->withAggregate($relation, 'sum', $field, $subQuery);
+ }
+
+ /**
+ * 关联统计Max
+ * @access public
+ * @param string|array $relation 关联方法名
+ * @param string $field 字段
+ * @param bool $subQuery 是否使用子查询
+ * @return $this
+ */
+ public function withMax($relation, string $field, bool $subQuery = true)
+ {
+ return $this->withAggregate($relation, 'max', $field, $subQuery);
+ }
+
+ /**
+ * 关联统计Min
+ * @access public
+ * @param string|array $relation 关联方法名
+ * @param string $field 字段
+ * @param bool $subQuery 是否使用子查询
+ * @return $this
+ */
+ public function withMin($relation, string $field, bool $subQuery = true)
+ {
+ return $this->withAggregate($relation, 'min', $field, $subQuery);
+ }
+
+ /**
+ * 关联统计Avg
+ * @access public
+ * @param string|array $relation 关联方法名
+ * @param string $field 字段
+ * @param bool $subQuery 是否使用子查询
+ * @return $this
+ */
+ public function withAvg($relation, string $field, bool $subQuery = true)
+ {
+ return $this->withAggregate($relation, 'avg', $field, $subQuery);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $relation 关联方法名
+ * @param mixed $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @return $this
+ */
+ public function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '')
+ {
+ return $this->model->has($relation, $operator, $count, $id, $joinType, $this);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $relation 关联方法名
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @return $this
+ */
+ public function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '')
+ {
+ return $this->model->hasWhere($relation, $where, $fields, $joinType, $this);
+ }
+
+ /**
+ * 查询数据转换为模型数据集对象
+ * @access protected
+ * @param array $resultSet 数据集
+ * @return ModelCollection
+ */
+ protected function resultSetToModelCollection(array $resultSet): ModelCollection
+ {
+ if (empty($resultSet)) {
+ return $this->model->toCollection();
+ }
+
+ // 检查动态获取器
+ if (!empty($this->options['with_attr'])) {
+ foreach ($this->options['with_attr'] as $name => $val) {
+ if (strpos($name, '.')) {
+ [$relation, $field] = explode('.', $name);
+
+ $withRelationAttr[$relation][$field] = $val;
+ unset($this->options['with_attr'][$name]);
+ }
+ }
+ }
+
+ $withRelationAttr = $withRelationAttr ?? [];
+
+ foreach ($resultSet as $key => &$result) {
+ // 数据转换为模型对象
+ $this->resultToModel($result, $this->options, true, $withRelationAttr);
+ }
+
+ if (!empty($this->options['with'])) {
+ // 预载入
+ $result->eagerlyResultSet($resultSet, $this->options['with'], $withRelationAttr, false, $this->options['with_cache'] ?? false);
+ }
+
+ if (!empty($this->options['with_join'])) {
+ // 预载入
+ $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true, $this->options['with_cache'] ?? false);
+ }
+
+ // 模型数据集转换
+ return $this->model->toCollection($resultSet);
+ }
+
+ /**
+ * 查询数据转换为模型对象
+ * @access protected
+ * @param array $result 查询数据
+ * @param array $options 查询参数
+ * @param bool $resultSet 是否为数据集查询
+ * @param array $withRelationAttr 关联字段获取器
+ * @return void
+ */
+ protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void
+ {
+ // 动态获取器
+ if (!empty($options['with_attr']) && empty($withRelationAttr)) {
+ foreach ($options['with_attr'] as $name => $val) {
+ if (strpos($name, '.')) {
+ [$relation, $field] = explode('.', $name);
+
+ $withRelationAttr[$relation][$field] = $val;
+ unset($options['with_attr'][$name]);
+ }
+ }
+ }
+
+ // JSON 数据处理
+ if (!empty($options['json'])) {
+ $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
+ }
+
+ $result = $this->model
+ ->newInstance($result, $resultSet ? null : $this->getModelUpdateCondition($options));
+
+ // 动态获取器
+ if (!empty($options['with_attr'])) {
+ $result->withAttribute($options['with_attr']);
+ }
+
+ // 输出属性控制
+ if (!empty($options['visible'])) {
+ $result->visible($options['visible']);
+ } elseif (!empty($options['hidden'])) {
+ $result->hidden($options['hidden']);
+ }
+
+ if (!empty($options['append'])) {
+ $result->append($options['append']);
+ }
+
+ // 关联查询
+ if (!empty($options['relation'])) {
+ $result->relationQuery($options['relation'], $withRelationAttr);
+ }
+
+ // 预载入查询
+ if (!$resultSet && !empty($options['with'])) {
+ $result->eagerlyResult($result, $options['with'], $withRelationAttr, false, $options['with_cache'] ?? false);
+ }
+
+ // JOIN预载入查询
+ if (!$resultSet && !empty($options['with_join'])) {
+ $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true, $options['with_cache'] ?? false);
+ }
+
+ // 关联统计
+ if (!empty($options['with_count'])) {
+ foreach ($options['with_count'] as $val) {
+ $result->relationCount($this, (array) $val[0], $val[1], $val[2], false);
+ }
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/ParamsBind.php b/vendor/topthink/think-orm/src/db/concern/ParamsBind.php
new file mode 100755
index 0000000..296e221
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/ParamsBind.php
@@ -0,0 +1,106 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use PDO;
+
+/**
+ * 参数绑定支持
+ */
+trait ParamsBind
+{
+ /**
+ * 当前参数绑定
+ * @var array
+ */
+ protected $bind = [];
+
+ /**
+ * 批量参数绑定
+ * @access public
+ * @param array $value 绑定变量值
+ * @return $this
+ */
+ public function bind(array $value)
+ {
+ $this->bind = array_merge($this->bind, $value);
+ return $this;
+ }
+
+ /**
+ * 单个参数绑定
+ * @access public
+ * @param mixed $value 绑定变量值
+ * @param integer $type 绑定类型
+ * @param string $name 绑定标识
+ * @return string
+ */
+ public function bindValue($value, int $type = null, string $name = null)
+ {
+ $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_' . mt_rand() . '_';
+
+ $this->bind[$name] = [$value, $type ?: PDO::PARAM_STR];
+ return $name;
+ }
+
+ /**
+ * 检测参数是否已经绑定
+ * @access public
+ * @param string $key 参数名
+ * @return bool
+ */
+ public function isBind($key)
+ {
+ return isset($this->bind[$key]);
+ }
+
+ /**
+ * 参数绑定
+ * @access public
+ * @param string $sql 绑定的sql表达式
+ * @param array $bind 参数绑定
+ * @return void
+ */
+ public function bindParams(string &$sql, array $bind = []): void
+ {
+ foreach ($bind as $key => $value) {
+ if (is_array($value)) {
+ $name = $this->bindValue($value[0], $value[1], $value[2] ?? null);
+ } else {
+ $name = $this->bindValue($value);
+ }
+
+ if (is_numeric($key)) {
+ $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1);
+ } else {
+ $sql = str_replace(':' . $key, ':' . $name, $sql);
+ }
+ }
+ }
+
+ /**
+ * 获取绑定的参数 并清空
+ * @access public
+ * @param bool $clear 是否清空绑定数据
+ * @return array
+ */
+ public function getBind(bool $clear = true): array
+ {
+ $bind = $this->bind;
+ if ($clear) {
+ $this->bind = [];
+ }
+
+ return $bind;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/ResultOperation.php b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php
new file mode 100755
index 0000000..d93c409
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php
@@ -0,0 +1,247 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use Closure;
+use think\Collection;
+use think\db\exception\DataNotFoundException;
+use think\db\exception\DbException;
+use think\db\exception\ModelNotFoundException;
+use think\db\Query;
+use think\helper\Str;
+use think\Model;
+
+/**
+ * 查询数据处理
+ */
+trait ResultOperation
+{
+ /**
+ * 是否允许返回空数据(或空模型)
+ * @access public
+ * @param bool $allowEmpty 是否允许为空
+ * @return $this
+ */
+ public function allowEmpty(bool $allowEmpty = true)
+ {
+ $this->options['allow_empty'] = $allowEmpty;
+ return $this;
+ }
+
+ /**
+ * 设置查询数据不存在是否抛出异常
+ * @access public
+ * @param bool $fail 数据不存在是否抛出异常
+ * @return $this
+ */
+ public function failException(bool $fail = true)
+ {
+ $this->options['fail'] = $fail;
+ return $this;
+ }
+
+ /**
+ * 处理数据
+ * @access protected
+ * @param array $result 查询数据
+ * @return void
+ */
+ protected function result(array &$result): void
+ {
+ if (!empty($this->options['json'])) {
+ $this->jsonResult($result, $this->options['json'], true);
+ }
+
+ if (!empty($this->options['with_attr'])) {
+ $this->getResultAttr($result, $this->options['with_attr']);
+ }
+
+ $this->filterResult($result);
+ }
+
+ /**
+ * 处理数据集
+ * @access public
+ * @param array $resultSet 数据集
+ * @return void
+ */
+ protected function resultSet(array &$resultSet): void
+ {
+ if (!empty($this->options['json'])) {
+ foreach ($resultSet as &$result) {
+ $this->jsonResult($result, $this->options['json'], true);
+ }
+ }
+
+ if (!empty($this->options['with_attr'])) {
+ foreach ($resultSet as &$result) {
+ $this->getResultAttr($result, $this->options['with_attr']);
+ }
+ }
+
+ if (!empty($this->options['visible']) || !empty($this->options['hidden'])) {
+ foreach ($resultSet as &$result) {
+ $this->filterResult($result);
+ }
+ }
+
+ // 返回Collection对象
+ $resultSet = new Collection($resultSet);
+ }
+
+ /**
+ * 处理数据的可见和隐藏
+ * @access protected
+ * @param array $result 查询数据
+ * @return void
+ */
+ protected function filterResult(&$result): void
+ {
+ $array = [];
+ if (!empty($this->options['visible'])) {
+ foreach ($this->options['visible'] as $key) {
+ $array[] = $key;
+ }
+ $result = array_intersect_key($result, array_flip($array));
+ } elseif (!empty($this->options['hidden'])) {
+ foreach ($this->options['hidden'] as $key) {
+ $array[] = $key;
+ }
+ $result = array_diff_key($result, array_flip($array));
+ }
+ }
+
+ /**
+ * 使用获取器处理数据
+ * @access protected
+ * @param array $result 查询数据
+ * @param array $withAttr 字段获取器
+ * @return void
+ */
+ protected function getResultAttr(array &$result, array $withAttr = []): void
+ {
+ foreach ($withAttr as $name => $closure) {
+ $name = Str::snake($name);
+
+ if (strpos($name, '.')) {
+ // 支持JSON字段 获取器定义
+ [$key, $field] = explode('.', $name);
+
+ if (isset($result[$key])) {
+ $result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]);
+ }
+ } else {
+ $result[$name] = $closure($result[$name] ?? null, $result);
+ }
+ }
+ }
+
+ /**
+ * 处理空数据
+ * @access protected
+ * @return array|Model|null
+ * @throws DbException
+ * @throws ModelNotFoundException
+ * @throws DataNotFoundException
+ */
+ protected function resultToEmpty()
+ {
+ if (!empty($this->options['fail'])) {
+ $this->throwNotFound();
+ } elseif (!empty($this->options['allow_empty'])) {
+ return !empty($this->model) ? $this->model->newInstance() : [];
+ }
+ }
+
+ /**
+ * 查找单条记录 不存在返回空数据(或者空模型)
+ * @access public
+ * @param mixed $data 数据
+ * @return array|Model
+ */
+ public function findOrEmpty($data = null)
+ {
+ return $this->allowEmpty(true)->find($data);
+ }
+
+ /**
+ * JSON字段数据转换
+ * @access protected
+ * @param array $result 查询数据
+ * @param array $json JSON字段
+ * @param bool $assoc 是否转换为数组
+ * @param array $withRelationAttr 关联获取器
+ * @return void
+ */
+ protected function jsonResult(array &$result, array $json = [], bool $assoc = false, array $withRelationAttr = []): void
+ {
+ foreach ($json as $name) {
+ if (!isset($result[$name])) {
+ continue;
+ }
+
+ $result[$name] = json_decode($result[$name], true);
+
+ if (isset($withRelationAttr[$name])) {
+ foreach ($withRelationAttr[$name] as $key => $closure) {
+ $result[$name][$key] = $closure($result[$name][$key] ?? null, $result[$name]);
+ }
+ }
+
+ if (!$assoc) {
+ $result[$name] = (object) $result[$name];
+ }
+ }
+ }
+
+ /**
+ * 查询失败 抛出异常
+ * @access protected
+ * @return void
+ * @throws ModelNotFoundException
+ * @throws DataNotFoundException
+ */
+ protected function throwNotFound(): void
+ {
+ if (!empty($this->model)) {
+ $class = get_class($this->model);
+ throw new ModelNotFoundException('model data Not Found:' . $class, $class, $this->options);
+ }
+
+ $table = $this->getTable();
+ throw new DataNotFoundException('table data not Found:' . $table, $table, $this->options);
+ }
+
+ /**
+ * 查找多条记录 如果不存在则抛出异常
+ * @access public
+ * @param array|string|Query|Closure $data 数据
+ * @return array|Model
+ */
+ public function selectOrFail($data = null)
+ {
+ return $this->failException(true)->select($data);
+ }
+
+ /**
+ * 查找单条记录 如果不存在则抛出异常
+ * @access public
+ * @param array|string|Query|Closure $data 数据
+ * @return array|Model
+ */
+ public function findOrFail($data = null)
+ {
+ return $this->failException(true)->find($data);
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php b/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php
new file mode 100755
index 0000000..9070bef
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php
@@ -0,0 +1,99 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+/**
+ * 数据字段信息
+ */
+trait TableFieldInfo
+{
+
+ /**
+ * 获取数据表字段信息
+ * @access public
+ * @param string $tableName 数据表名
+ * @return array
+ */
+ public function getTableFields($tableName = ''): array
+ {
+ if ('' == $tableName) {
+ $tableName = $this->getTable();
+ }
+
+ return $this->connection->getTableFields($tableName);
+ }
+
+ /**
+ * 获取详细字段类型信息
+ * @access public
+ * @param string $tableName 数据表名称
+ * @return array
+ */
+ public function getFields(string $tableName = ''): array
+ {
+ return $this->connection->getFields($tableName ?: $this->getTable());
+ }
+
+ /**
+ * 获取字段类型信息
+ * @access public
+ * @return array
+ */
+ public function getFieldsType(): array
+ {
+ if (!empty($this->options['field_type'])) {
+ return $this->options['field_type'];
+ }
+
+ return $this->connection->getFieldsType($this->getTable());
+ }
+
+ /**
+ * 获取字段类型信息
+ * @access public
+ * @param string $field 字段名
+ * @return string|null
+ */
+ public function getFieldType(string $field)
+ {
+ $fieldType = $this->getFieldsType();
+
+ return $fieldType[$field] ?? null;
+ }
+
+ /**
+ * 获取字段类型信息
+ * @access public
+ * @return array
+ */
+ public function getFieldsBindType(): array
+ {
+ $fieldType = $this->getFieldsType();
+
+ return array_map([$this->connection, 'getFieldBindType'], $fieldType);
+ }
+
+ /**
+ * 获取字段类型信息
+ * @access public
+ * @param string $field 字段名
+ * @return int
+ */
+ public function getFieldBindType(string $field): int
+ {
+ $fieldType = $this->getFieldType($field);
+
+ return $this->connection->getFieldBindType($fieldType ?: '');
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php b/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php
new file mode 100755
index 0000000..1267e54
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php
@@ -0,0 +1,214 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+/**
+ * 时间查询支持
+ */
+trait TimeFieldQuery
+{
+ /**
+ * 日期查询表达式
+ * @var array
+ */
+ protected $timeRule = [
+ 'today' => ['today', 'tomorrow -1second'],
+ 'yesterday' => ['yesterday', 'today -1second'],
+ 'week' => ['this week 00:00:00', 'next week 00:00:00 -1second'],
+ 'last week' => ['last week 00:00:00', 'this week 00:00:00 -1second'],
+ 'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00 -1second'],
+ 'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00 -1second'],
+ 'year' => ['this year 1/1', 'next year 1/1 -1second'],
+ 'last year' => ['last year 1/1', 'this year 1/1 -1second'],
+ ];
+
+ /**
+ * 添加日期或者时间查询规则
+ * @access public
+ * @param array $rule 时间表达式
+ * @return $this
+ */
+ public function timeRule(array $rule)
+ {
+ $this->timeRule = array_merge($this->timeRule, $rule);
+ return $this;
+ }
+
+ /**
+ * 查询日期或者时间
+ * @access public
+ * @param string $field 日期字段名
+ * @param string $op 比较运算符或者表达式
+ * @param string|array $range 比较范围
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereTime(string $field, string $op, $range = null, string $logic = 'AND')
+ {
+ if (is_null($range)) {
+ if (isset($this->timeRule[$op])) {
+ $range = $this->timeRule[$op];
+ } else {
+ $range = $op;
+ }
+ $op = is_array($range) ? 'between' : '>=';
+ }
+
+ return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true);
+ }
+
+ /**
+ * 查询某个时间间隔数据
+ * @access public
+ * @param string $field 日期字段名
+ * @param string $start 开始时间
+ * @param string $interval 时间间隔单位 day/month/year/week/hour/minute/second
+ * @param int $step 间隔
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereTimeInterval(string $field, string $start, string $interval = 'day', int $step = 1, string $logic = 'AND')
+ {
+ $startTime = strtotime($start);
+ $endTime = strtotime(($step > 0 ? '+' : '-') . abs($step) . ' ' . $interval . (abs($step) > 1 ? 's' : ''), $startTime);
+
+ return $this->whereTime($field, 'between', $step > 0 ? [$startTime, $endTime - 1] : [$endTime, $startTime - 1], $logic);
+ }
+
+ /**
+ * 查询月数据 whereMonth('time_field', '2018-1')
+ * @access public
+ * @param string $field 日期字段名
+ * @param string $month 月份信息
+ * @param int $step 间隔
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereMonth(string $field, string $month = 'this month', int $step = 1, string $logic = 'AND')
+ {
+ if (in_array($month, ['this month', 'last month'])) {
+ $month = date('Y-m', strtotime($month));
+ }
+
+ return $this->whereTimeInterval($field, $month, 'month', $step, $logic);
+ }
+
+ /**
+ * 查询周数据 whereWeek('time_field', '2018-1-1') 从2018-1-1开始的一周数据
+ * @access public
+ * @param string $field 日期字段名
+ * @param string $week 周信息
+ * @param int $step 间隔
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereWeek(string $field, string $week = 'this week', int $step = 1, string $logic = 'AND')
+ {
+ if (in_array($week, ['this week', 'last week'])) {
+ $week = date('Y-m-d', strtotime($week));
+ }
+
+ return $this->whereTimeInterval($field, $week, 'week', $step, $logic);
+ }
+
+ /**
+ * 查询年数据 whereYear('time_field', '2018')
+ * @access public
+ * @param string $field 日期字段名
+ * @param string $year 年份信息
+ * @param int $step 间隔
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereYear(string $field, string $year = 'this year', int $step = 1, string $logic = 'AND')
+ {
+ if (in_array($year, ['this year', 'last year'])) {
+ $year = date('Y', strtotime($year));
+ }
+
+ return $this->whereTimeInterval($field, $year . '-1-1', 'year', $step, $logic);
+ }
+
+ /**
+ * 查询日数据 whereDay('time_field', '2018-1-1')
+ * @access public
+ * @param string $field 日期字段名
+ * @param string $day 日期信息
+ * @param int $step 间隔
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereDay(string $field, string $day = 'today', int $step = 1, string $logic = 'AND')
+ {
+ if (in_array($day, ['today', 'yesterday'])) {
+ $day = date('Y-m-d', strtotime($day));
+ }
+
+ return $this->whereTimeInterval($field, $day, 'day', $step, $logic);
+ }
+
+ /**
+ * 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15')
+ * @access public
+ * @param string $field 日期字段名
+ * @param string|int $startTime 开始时间
+ * @param string|int $endTime 结束时间
+ * @param string $logic AND OR
+ * @return $this
+ */
+ public function whereBetweenTime(string $field, $startTime, $endTime, string $logic = 'AND')
+ {
+ return $this->whereTime($field, 'between', [$startTime, $endTime], $logic);
+ }
+
+ /**
+ * 查询日期或者时间范围 whereNotBetweenTime('time_field', '2018-1-1','2018-1-15')
+ * @access public
+ * @param string $field 日期字段名
+ * @param string|int $startTime 开始时间
+ * @param string|int $endTime 结束时间
+ * @return $this
+ */
+ public function whereNotBetweenTime(string $field, $startTime, $endTime)
+ {
+ return $this->whereTime($field, '<', $startTime)
+ ->whereTime($field, '>', $endTime);
+ }
+
+ /**
+ * 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time')
+ * @access public
+ * @param string $startField 开始时间字段
+ * @param string $endField 结束时间字段
+ * @return $this
+ */
+ public function whereBetweenTimeField(string $startField, string $endField)
+ {
+ return $this->whereTime($startField, '<=', time())
+ ->whereTime($endField, '>=', time());
+ }
+
+ /**
+ * 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time')
+ * @access public
+ * @param string $startField 开始时间字段
+ * @param string $endField 结束时间字段
+ * @return $this
+ */
+ public function whereNotBetweenTimeField(string $startField, string $endField)
+ {
+ return $this->whereTime($startField, '>', time())
+ ->whereTime($endField, '<', time(), 'OR');
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/Transaction.php b/vendor/topthink/think-orm/src/db/concern/Transaction.php
new file mode 100755
index 0000000..f804ae2
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/Transaction.php
@@ -0,0 +1,117 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use think\db\BaseQuery;
+
+/**
+ * 事务支持
+ */
+trait Transaction
+{
+
+ /**
+ * 执行数据库Xa事务
+ * @access public
+ * @param callable $callback 数据操作方法回调
+ * @param array $dbs 多个查询对象或者连接对象
+ * @return mixed
+ * @throws PDOException
+ * @throws \Exception
+ * @throws \Throwable
+ */
+ public function transactionXa($callback, array $dbs = [])
+ {
+ $xid = uniqid('xa');
+
+ if (empty($dbs)) {
+ $dbs[] = $this->getConnection();
+ }
+
+ foreach ($dbs as $key => $db) {
+ if ($db instanceof BaseQuery) {
+ $db = $db->getConnection();
+
+ $dbs[$key] = $db;
+ }
+
+ $db->startTransXa($xid);
+ }
+
+ try {
+ $result = null;
+ if (is_callable($callback)) {
+ $result = call_user_func_array($callback, [$this]);
+ }
+
+ foreach ($dbs as $db) {
+ $db->prepareXa($xid);
+ }
+
+ foreach ($dbs as $db) {
+ $db->commitXa($xid);
+ }
+
+ return $result;
+ } catch (\Exception | \Throwable $e) {
+ foreach ($dbs as $db) {
+ $db->rollbackXa($xid);
+ }
+ throw $e;
+ }
+ }
+
+ /**
+ * 执行数据库事务
+ * @access public
+ * @param callable $callback 数据操作方法回调
+ * @return mixed
+ */
+ public function transaction(callable $callback)
+ {
+ return $this->connection->transaction($callback);
+ }
+
+ /**
+ * 启动事务
+ * @access public
+ * @return void
+ */
+ public function startTrans(): void
+ {
+ $this->connection->startTrans();
+ }
+
+ /**
+ * 用于非自动提交状态下面的查询提交
+ * @access public
+ * @return void
+ * @throws PDOException
+ */
+ public function commit(): void
+ {
+ $this->connection->commit();
+ }
+
+ /**
+ * 事务回滚
+ * @access public
+ * @return void
+ * @throws PDOException
+ */
+ public function rollback(): void
+ {
+ $this->connection->rollback();
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/concern/WhereQuery.php b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php
new file mode 100755
index 0000000..62dc983
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php
@@ -0,0 +1,532 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\concern;
+
+use Closure;
+use think\db\BaseQuery;
+use think\db\Raw;
+
+trait WhereQuery
+{
+ /**
+ * 指定AND查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $op 查询表达式
+ * @param mixed $condition 查询条件
+ * @return $this
+ */
+ public function where($field, $op = null, $condition = null)
+ {
+ if ($field instanceof $this) {
+ $this->parseQueryWhere($field);
+ return $this;
+ } elseif (true === $field || 1 === $field) {
+ $this->options['where']['AND'][] = true;
+ return $this;
+ }
+
+ $param = func_get_args();
+ array_shift($param);
+ return $this->parseWhereExp('AND', $field, $op, $condition, $param);
+ }
+
+ /**
+ * 解析Query对象查询条件
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return void
+ */
+ protected function parseQueryWhere(BaseQuery $query): void
+ {
+ $this->options['where'] = $query->getOptions('where');
+
+ if ($query->getOptions('via')) {
+ $via = $query->getOptions('via');
+ foreach ($this->options['where'] as $logic => &$where) {
+ foreach ($where as $key => &$val) {
+ if (is_array($val) && !strpos($val[0], '.')) {
+ $val[0] = $via . '.' . $val[0];
+ }
+ }
+ }
+ }
+
+ $this->bind($query->getBind(false));
+ }
+
+ /**
+ * 指定OR查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $op 查询表达式
+ * @param mixed $condition 查询条件
+ * @return $this
+ */
+ public function whereOr($field, $op = null, $condition = null)
+ {
+ $param = func_get_args();
+ array_shift($param);
+ return $this->parseWhereExp('OR', $field, $op, $condition, $param);
+ }
+
+ /**
+ * 指定XOR查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $op 查询表达式
+ * @param mixed $condition 查询条件
+ * @return $this
+ */
+ public function whereXor($field, $op = null, $condition = null)
+ {
+ $param = func_get_args();
+ array_shift($param);
+ return $this->parseWhereExp('XOR', $field, $op, $condition, $param);
+ }
+
+ /**
+ * 指定Null查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereNull(string $field, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'NULL', null, [], true);
+ }
+
+ /**
+ * 指定NotNull查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereNotNull(string $field, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true);
+ }
+
+ /**
+ * 指定Exists查询条件
+ * @access public
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereExists($condition, string $logic = 'AND')
+ {
+ if (is_string($condition)) {
+ $condition = new Raw($condition);
+ }
+
+ $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition];
+ return $this;
+ }
+
+ /**
+ * 指定NotExists查询条件
+ * @access public
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereNotExists($condition, string $logic = 'AND')
+ {
+ if (is_string($condition)) {
+ $condition = new Raw($condition);
+ }
+
+ $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition];
+ return $this;
+ }
+
+ /**
+ * 指定In查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereIn(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true);
+ }
+
+ /**
+ * 指定NotIn查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereNotIn(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true);
+ }
+
+ /**
+ * 指定Like查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereLike(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true);
+ }
+
+ /**
+ * 指定NotLike查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereNotLike(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true);
+ }
+
+ /**
+ * 指定Between查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereBetween(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true);
+ }
+
+ /**
+ * 指定NotBetween查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereNotBetween(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true);
+ }
+
+ /**
+ * 指定FIND_IN_SET查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param mixed $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereFindInSet(string $field, $condition, string $logic = 'AND')
+ {
+ return $this->parseWhereExp($logic, $field, 'FIND IN SET', $condition, [], true);
+ }
+
+ /**
+ * 比较两个字段
+ * @access public
+ * @param string $field1 查询字段
+ * @param string $operator 比较操作符
+ * @param string $field2 比较字段
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND')
+ {
+ if (is_null($field2)) {
+ $field2 = $operator;
+ $operator = '=';
+ }
+
+ return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true);
+ }
+
+ /**
+ * 设置软删除字段及条件
+ * @access public
+ * @param string $field 查询字段
+ * @param mixed $condition 查询条件
+ * @return $this
+ */
+ public function useSoftDelete(string $field, $condition = null)
+ {
+ if ($field) {
+ $this->options['soft_delete'] = [$field, $condition];
+ }
+
+ return $this;
+ }
+
+ /**
+ * 指定Exp查询条件
+ * @access public
+ * @param mixed $field 查询字段
+ * @param string $where 查询条件
+ * @param array $bind 参数绑定
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND')
+ {
+ $this->options['where'][$logic][] = [$field, 'EXP', new Raw($where, $bind)];
+
+ return $this;
+ }
+
+ /**
+ * 指定字段Raw查询
+ * @access public
+ * @param string $field 查询字段表达式
+ * @param mixed $op 查询表达式
+ * @param string $condition 查询条件
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND')
+ {
+ if (is_null($condition)) {
+ $condition = $op;
+ $op = '=';
+ }
+
+ $this->options['where'][$logic][] = [new Raw($field), $op, $condition];
+ return $this;
+ }
+
+ /**
+ * 指定表达式查询条件
+ * @access public
+ * @param string $where 查询条件
+ * @param array $bind 参数绑定
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function whereRaw(string $where, array $bind = [], string $logic = 'AND')
+ {
+ $this->options['where'][$logic][] = new Raw($where, $bind);
+
+ return $this;
+ }
+
+ /**
+ * 指定表达式查询条件 OR
+ * @access public
+ * @param string $where 查询条件
+ * @param array $bind 参数绑定
+ * @return $this
+ */
+ public function whereOrRaw(string $where, array $bind = [])
+ {
+ return $this->whereRaw($where, $bind, 'OR');
+ }
+
+ /**
+ * 分析查询表达式
+ * @access protected
+ * @param string $logic 查询逻辑 and or xor
+ * @param mixed $field 查询字段
+ * @param mixed $op 查询表达式
+ * @param mixed $condition 查询条件
+ * @param array $param 查询参数
+ * @param bool $strict 严格模式
+ * @return $this
+ */
+ protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false)
+ {
+ $logic = strtoupper($logic);
+
+ if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) {
+ $field = $this->options['via'] . '.' . $field;
+ }
+
+ if ($field instanceof Raw) {
+ return $this->whereRaw($field, is_array($op) ? $op : [], $logic);
+ } elseif ($strict) {
+ // 使用严格模式查询
+ if ('=' == $op) {
+ $where = $this->whereEq($field, $condition);
+ } else {
+ $where = [$field, $op, $condition, $logic];
+ }
+ } elseif (is_array($field)) {
+ // 解析数组批量查询
+ return $this->parseArrayWhereItems($field, $logic);
+ } elseif ($field instanceof Closure) {
+ $where = $field;
+ } elseif (is_string($field)) {
+ if (preg_match('/[,=\<\'\"\(\s]/', $field)) {
+ return $this->whereRaw($field, is_array($op) ? $op : [], $logic);
+ } elseif (is_string($op) && strtolower($op) == 'exp') {
+ $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : [];
+ return $this->whereExp($field, $condition, $bind, $logic);
+ }
+
+ $where = $this->parseWhereItem($logic, $field, $op, $condition, $param);
+ }
+
+ if (!empty($where)) {
+ $this->options['where'][$logic][] = $where;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 分析查询表达式
+ * @access protected
+ * @param string $logic 查询逻辑 and or xor
+ * @param mixed $field 查询字段
+ * @param mixed $op 查询表达式
+ * @param mixed $condition 查询条件
+ * @param array $param 查询参数
+ * @return array
+ */
+ protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array
+ {
+ if (is_array($op)) {
+ // 同一字段多条件查询
+ array_unshift($param, $field);
+ $where = $param;
+ } elseif ($field && is_null($condition)) {
+ if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) {
+ // null查询
+ $where = [$field, $op, ''];
+ } elseif ('=' === $op || is_null($op)) {
+ $where = [$field, 'NULL', ''];
+ } elseif ('<>' === $op) {
+ $where = [$field, 'NOTNULL', ''];
+ } else {
+ // 字段相等查询
+ $where = $this->whereEq($field, $op);
+ }
+ } elseif (is_string($op) && in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) {
+ $where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition];
+ } else {
+ $where = $field ? [$field, $op, $condition, $param[2] ?? null] : [];
+ }
+
+ return $where;
+ }
+
+ /**
+ * 相等查询的主键处理
+ * @access protected
+ * @param string $field 字段名
+ * @param mixed $value 字段值
+ * @return array
+ */
+ protected function whereEq(string $field, $value): array
+ {
+ if ($this->getPk() == $field) {
+ $this->options['key'] = $value;
+ }
+
+ return [$field, '=', $value];
+ }
+
+ /**
+ * 数组批量查询
+ * @access protected
+ * @param array $field 批量查询
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ protected function parseArrayWhereItems(array $field, string $logic)
+ {
+ if (key($field) !== 0) {
+ $where = [];
+ foreach ($field as $key => $val) {
+ if ($val instanceof Raw) {
+ $where[] = [$key, 'exp', $val];
+ } else {
+ $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val];
+ }
+ }
+ } else {
+ // 数组批量查询
+ $where = $field;
+ }
+
+ if (!empty($where)) {
+ $this->options['where'][$logic] = isset($this->options['where'][$logic]) ?
+ array_merge($this->options['where'][$logic], $where) : $where;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 去除某个查询条件
+ * @access public
+ * @param string $field 查询字段
+ * @param string $logic 查询逻辑 and or xor
+ * @return $this
+ */
+ public function removeWhereField(string $field, string $logic = 'AND')
+ {
+ $logic = strtoupper($logic);
+
+ if (isset($this->options['where'][$logic])) {
+ foreach ($this->options['where'][$logic] as $key => $val) {
+ if (is_array($val) && $val[0] == $field) {
+ unset($this->options['where'][$logic][$key]);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 条件查询
+ * @access public
+ * @param mixed $condition 满足条件(支持闭包)
+ * @param Closure|array $query 满足条件后执行的查询表达式(闭包或数组)
+ * @param Closure|array $otherwise 不满足条件后执行
+ * @return $this
+ */
+ public function when($condition, $query, $otherwise = null)
+ {
+ if ($condition instanceof Closure) {
+ $condition = $condition($this);
+ }
+
+ if ($condition) {
+ if ($query instanceof Closure) {
+ $query($this, $condition);
+ } elseif (is_array($query)) {
+ $this->where($query);
+ }
+ } elseif ($otherwise) {
+ if ($otherwise instanceof Closure) {
+ $otherwise($this, $condition);
+ } elseif (is_array($otherwise)) {
+ $this->where($otherwise);
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/connector/Mongo.php b/vendor/topthink/think-orm/src/db/connector/Mongo.php
new file mode 100755
index 0000000..ec39939
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/connector/Mongo.php
@@ -0,0 +1,1167 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\connector;
+
+use Closure;
+use MongoDB\BSON\ObjectID;
+use MongoDB\Driver\BulkWrite;
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Exception\AuthenticationException;
+use MongoDB\Driver\Exception\BulkWriteException;
+use MongoDB\Driver\Exception\ConnectionException;
+use MongoDB\Driver\Exception\InvalidArgumentException;
+use MongoDB\Driver\Exception\RuntimeException;
+use MongoDB\Driver\Manager;
+use MongoDB\Driver\Query as MongoQuery;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\WriteConcern;
+use think\db\BaseQuery;
+use think\db\builder\Mongo as Builder;
+use think\db\Connection;
+use think\db\exception\DbException as Exception;
+use think\db\Mongo as Query;
+
+/**
+ * Mongo数据库驱动
+ */
+class Mongo extends Connection
+{
+
+ // 查询数据类型
+ protected $dbName = '';
+ protected $typeMap = 'array';
+ protected $mongo; // MongoDb Object
+ protected $cursor; // MongoCursor Object
+ protected $session_uuid; // sessions会话列表当前会话数组key 随机生成
+ protected $sessions = []; // 会话列表
+
+ /** @var Builder */
+ protected $builder;
+
+ // 数据库连接参数配置
+ protected $config = [
+ // 数据库类型
+ 'type' => '',
+ // 服务器地址
+ 'hostname' => '',
+ // 数据库名
+ 'database' => '',
+ // 是否是复制集
+ 'is_replica_set' => false,
+ // 用户名
+ 'username' => '',
+ // 密码
+ 'password' => '',
+ // 端口
+ 'hostport' => '',
+ // 连接dsn
+ 'dsn' => '',
+ // 数据库连接参数
+ 'params' => [],
+ // 数据库编码默认采用utf8
+ 'charset' => 'utf8',
+ // 主键名
+ 'pk' => '_id',
+ // 主键类型
+ 'pk_type' => 'ObjectID',
+ // 数据库表前缀
+ 'prefix' => '',
+ // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+ 'deploy' => 0,
+ // 数据库读写是否分离 主从式有效
+ 'rw_separate' => false,
+ // 读写分离后 主服务器数量
+ 'master_num' => 1,
+ // 指定从服务器序号
+ 'slave_no' => '',
+ // 是否严格检查字段是否存在
+ 'fields_strict' => true,
+ // 开启字段缓存
+ 'fields_cache' => false,
+ // 监听SQL
+ 'trigger_sql' => true,
+ // 自动写入时间戳字段
+ 'auto_timestamp' => false,
+ // 时间字段取出后的默认时间格式
+ 'datetime_format' => 'Y-m-d H:i:s',
+ // 是否_id转换为id
+ 'pk_convert_id' => false,
+ // typeMap
+ 'type_map' => ['root' => 'array', 'document' => 'array'],
+ ];
+
+ /**
+ * 获取当前连接器类对应的Query类
+ * @access public
+ * @return string
+ */
+ public function getQueryClass(): string
+ {
+ return Query::class;
+ }
+
+ /**
+ * 获取当前的builder实例对象
+ * @access public
+ * @return Builder
+ */
+ public function getBuilder()
+ {
+ return $this->builder;
+ }
+
+ /**
+ * 获取当前连接器类对应的Builder类
+ * @access public
+ * @return string
+ */
+ public function getBuilderClass(): string
+ {
+ return Builder::class;
+ }
+
+ /**
+ * 连接数据库方法
+ * @access public
+ * @param array $config 连接参数
+ * @param integer $linkNum 连接序号
+ * @return Manager
+ * @throws InvalidArgumentException
+ * @throws RuntimeException
+ */
+ public function connect(array $config = [], $linkNum = 0)
+ {
+ if (!isset($this->links[$linkNum])) {
+ if (empty($config)) {
+ $config = $this->config;
+ } else {
+ $config = array_merge($this->config, $config);
+ }
+
+ $this->dbName = $config['database'];
+ $this->typeMap = $config['type_map'];
+
+ if ($config['pk_convert_id'] && '_id' == $config['pk']) {
+ $this->config['pk'] = 'id';
+ }
+
+ if (empty($config['dsn'])) {
+ $config['dsn'] = 'mongodb://' . ($config['username'] ? "{$config['username']}" : '') . ($config['password'] ? ":{$config['password']}@" : '') . $config['hostname'] . ($config['hostport'] ? ":{$config['hostport']}" : '');
+ }
+
+ $startTime = microtime(true);
+
+ $this->links[$linkNum] = new Manager($config['dsn'], $config['params']);
+
+ if (!empty($config['trigger_sql'])) {
+ // 记录数据库连接信息
+ $this->trigger('CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']);
+ }
+
+ }
+
+ return $this->links[$linkNum];
+ }
+
+ /**
+ * 获取Mongo Manager对象
+ * @access public
+ * @return Manager|null
+ */
+ public function getMongo()
+ {
+ return $this->mongo ?: null;
+ }
+
+ /**
+ * 设置/获取当前操作的database
+ * @access public
+ * @param string $db db
+ * @return string
+ */
+ public function db(string $db = null)
+ {
+ if (is_null($db)) {
+ return $this->dbName;
+ } else {
+ $this->dbName = $db;
+ }
+ }
+
+ /**
+ * 执行查询但只返回Cursor对象
+ * @access public
+ * @param Query $query 查询对象
+ * @return Cursor
+ */
+ public function cursor($query)
+ {
+ // 分析查询表达式
+ $options = $query->parseOptions();
+
+ // 生成MongoQuery对象
+ $mongoQuery = $this->builder->select($query);
+
+ $master = $query->getOptions('master') ? true : false;
+
+ // 执行查询操作
+ return $this->getCursor($query, $mongoQuery, $master);
+ }
+
+ /**
+ * 执行查询并返回Cursor对象
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param MongoQuery|Closure $mongoQuery Mongo查询对象
+ * @param bool $master 是否主库操作
+ * @return Cursor
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ public function getCursor(BaseQuery $query, $mongoQuery, bool $master = false): Cursor
+ {
+ $this->initConnect($master);
+ $this->db->updateQueryTimes();
+
+ $options = $query->getOptions();
+ $namespace = $options['table'];
+
+ if (false === strpos($namespace, '.')) {
+ $namespace = $this->dbName . '.' . $namespace;
+ }
+
+ if (!empty($this->queryStr)) {
+ // 记录执行指令
+ $this->queryStr = 'db' . strstr($namespace, '.') . '.' . $this->queryStr;
+ }
+
+ if ($mongoQuery instanceof Closure) {
+ $mongoQuery = $mongoQuery($query);
+ }
+
+ $readPreference = $options['readPreference'] ?? null;
+ $this->queryStartTime = microtime(true);
+
+ if ($session = $this->getSession()) {
+ $this->cursor = $this->mongo->executeQuery($namespace, $query, [
+ 'readPreference' => is_null($readPreference) ? new ReadPreference(ReadPreference::RP_PRIMARY) : $readPreference,
+ 'session' => $session,
+ ]);
+ } else {
+ $this->cursor = $this->mongo->executeQuery($namespace, $mongoQuery, $readPreference);
+ }
+
+ // SQL监控
+ if (!empty($this->config['trigger_sql'])) {
+ $this->trigger('', $master);
+ }
+
+ return $this->cursor;
+ }
+
+ /**
+ * 执行查询 返回数据集
+ * @access public
+ * @param MongoQuery $query 查询对象
+ * @return mixed
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ public function query(MongoQuery $query)
+ {
+ return $this->mongoQuery($this->newQuery(), $query);
+ }
+
+ /**
+ * 执行语句
+ * @access public
+ * @param BulkWrite $bulk
+ * @return int
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ * @throws BulkWriteException
+ */
+ public function execute(BulkWrite $bulk)
+ {
+ return $this->mongoExecute($this->newQuery(), $bulk);
+ }
+
+ /**
+ * 执行查询
+ * @access protected
+ * @param BaseQuery $query 查询对象
+ * @param MongoQuery|Closure $mongoQuery Mongo查询对象
+ * @return array
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ protected function mongoQuery(BaseQuery $query, $mongoQuery): array
+ {
+ $options = $query->parseOptions();
+
+ if ($query->getOptions('cache')) {
+ // 检查查询缓存
+ $cacheItem = $this->parseCache($query, $query->getOptions('cache'));
+ $key = $cacheItem->getKey();
+
+ if ($this->cache->has($key)) {
+ return $this->cache->get($key);
+ }
+ }
+
+ if ($mongoQuery instanceof Closure) {
+ $mongoQuery = $mongoQuery($query);
+ }
+
+ $master = $query->getOptions('master') ? true : false;
+ $this->getCursor($query, $mongoQuery, $master);
+
+ $resultSet = $this->getResult($options['typeMap']);
+
+ if (isset($cacheItem) && $resultSet) {
+ // 缓存数据集
+ $cacheItem->set($resultSet);
+ $this->cacheData($cacheItem);
+ }
+
+ return $resultSet;
+ }
+
+ /**
+ * 执行写操作
+ * @access protected
+ * @param BaseQuery $query
+ * @param BulkWrite $bulk
+ *
+ * @return WriteResult
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ * @throws BulkWriteException
+ */
+ protected function mongoExecute(BaseQuery $query, BulkWrite $bulk)
+ {
+ $this->initConnect(true);
+ $this->db->updateQueryTimes();
+
+ $options = $query->getOptions();
+
+ $namespace = $options['table'];
+ if (false === strpos($namespace, '.')) {
+ $namespace = $this->dbName . '.' . $namespace;
+ }
+
+ if (!empty($this->queryStr)) {
+ // 记录执行指令
+ $this->queryStr = 'db' . strstr($namespace, '.') . '.' . $this->queryStr;
+ }
+
+ $writeConcern = $options['writeConcern'] ?? null;
+ $this->queryStartTime = microtime(true);
+
+ if ($session = $this->getSession()) {
+ $writeResult = $this->mongo->executeBulkWrite($namespace, $bulk, [
+ 'session' => $session,
+ 'writeConcern' => is_null($writeConcern) ? new WriteConcern(1) : $writeConcern,
+ ]);
+ } else {
+ $writeResult = $this->mongo->executeBulkWrite($namespace, $bulk, $writeConcern);
+ }
+
+ // SQL监控
+ if (!empty($this->config['trigger_sql'])) {
+ $this->trigger();
+ }
+
+ $this->numRows = $writeResult->getMatchedCount();
+
+ if ($query->getOptions('cache')) {
+ // 清理缓存数据
+ $cacheItem = $this->parseCache($query, $query->getOptions('cache'));
+ $key = $cacheItem->getKey();
+ $tag = $cacheItem->getTag();
+
+ if (isset($key) && $this->cache->has($key)) {
+ $this->cache->delete($key);
+ } elseif (!empty($tag) && method_exists($this->cache, 'tag')) {
+ $this->cache->tag($tag)->clear();
+ }
+ }
+
+ return $writeResult;
+ }
+
+ /**
+ * 执行指令
+ * @access public
+ * @param Command $command 指令
+ * @param string $dbName 当前数据库名
+ * @param ReadPreference $readPreference readPreference
+ * @param string|array $typeMap 指定返回的typeMap
+ * @param bool $master 是否主库操作
+ * @return array
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null, bool $master = false): array
+ {
+ $this->initConnect($master);
+ $this->db->updateQueryTimes();
+
+ $this->queryStartTime = microtime(true);
+
+ $dbName = $dbName ?: $this->dbName;
+
+ if (!empty($this->queryStr)) {
+ $this->queryStr = 'db.' . $this->queryStr;
+ }
+
+ if ($session = $this->getSession()) {
+ $this->cursor = $this->mongo->executeCommand($dbName, $command, [
+ 'readPreference' => is_null($readPreference) ? new ReadPreference(ReadPreference::RP_PRIMARY) : $readPreference,
+ 'session' => $session,
+ ]);
+ } else {
+ $this->cursor = $this->mongo->executeCommand($dbName, $command, $readPreference);
+ }
+
+ // SQL监控
+ if (!empty($this->config['trigger_sql'])) {
+ $this->trigger('', $master);
+ }
+
+ return $this->getResult($typeMap);
+ }
+
+ /**
+ * 获得数据集
+ * @access protected
+ * @param string|array $typeMap 指定返回的typeMap
+ * @return mixed
+ */
+ protected function getResult($typeMap = null): array
+ {
+ // 设置结果数据类型
+ if (is_null($typeMap)) {
+ $typeMap = $this->typeMap;
+ }
+
+ $typeMap = is_string($typeMap) ? ['root' => $typeMap] : $typeMap;
+
+ $this->cursor->setTypeMap($typeMap);
+
+ // 获取数据集
+ $result = $this->cursor->toArray();
+
+ if ($this->getConfig('pk_convert_id')) {
+ // 转换ObjectID 字段
+ foreach ($result as &$data) {
+ $this->convertObjectID($data);
+ }
+ }
+
+ $this->numRows = count($result);
+
+ return $result;
+ }
+
+ /**
+ * ObjectID处理
+ * @access protected
+ * @param array $data 数据
+ * @return void
+ */
+ protected function convertObjectID(array &$data): void
+ {
+ if (isset($data['_id']) && is_object($data['_id'])) {
+ $data['id'] = $data['_id']->__toString();
+ unset($data['_id']);
+ }
+ }
+
+ /**
+ * 数据库日志记录(仅供参考)
+ * @access public
+ * @param string $type 类型
+ * @param mixed $data 数据
+ * @param array $options 参数
+ * @return void
+ */
+ public function mongoLog(string $type, $data, array $options = [])
+ {
+ if (!$this->config['trigger_sql']) {
+ return;
+ }
+
+ if (is_array($data)) {
+ array_walk_recursive($data, function (&$value) {
+ if ($value instanceof ObjectID) {
+ $value = $value->__toString();
+ }
+ });
+ }
+
+ switch (strtolower($type)) {
+ case 'aggregate':
+ $this->queryStr = 'runCommand(' . ($data ? json_encode($data) : '') . ');';
+ break;
+ case 'find':
+ $this->queryStr = $type . '(' . ($data ? json_encode($data) : '') . ')';
+
+ if (isset($options['sort'])) {
+ $this->queryStr .= '.sort(' . json_encode($options['sort']) . ')';
+ }
+
+ if (isset($options['skip'])) {
+ $this->queryStr .= '.skip(' . $options['skip'] . ')';
+ }
+
+ if (isset($options['limit'])) {
+ $this->queryStr .= '.limit(' . $options['limit'] . ')';
+ }
+
+ $this->queryStr .= ';';
+ break;
+ case 'insert':
+ case 'remove':
+ $this->queryStr = $type . '(' . ($data ? json_encode($data) : '') . ');';
+ break;
+ case 'update':
+ $this->queryStr = $type . '(' . json_encode($options) . ',' . json_encode($data) . ');';
+ break;
+ case 'cmd':
+ $this->queryStr = $data . '(' . json_encode($options) . ');';
+ break;
+ }
+
+ $this->options = $options;
+ }
+
+ /**
+ * 获取最近执行的指令
+ * @access public
+ * @return string
+ */
+ public function getLastSql(): string
+ {
+ return $this->queryStr;
+ }
+
+ /**
+ * 关闭数据库
+ * @access public
+ */
+ public function close()
+ {
+ $this->mongo = null;
+ $this->cursor = null;
+ $this->linkRead = null;
+ $this->linkWrite = null;
+ $this->links = [];
+ }
+
+ /**
+ * 初始化数据库连接
+ * @access protected
+ * @param boolean $master 是否主服务器
+ * @return void
+ */
+ protected function initConnect(bool $master = true): void
+ {
+ if (!empty($this->config['deploy'])) {
+ // 采用分布式数据库
+ if ($master) {
+ if (!$this->linkWrite) {
+ $this->linkWrite = $this->multiConnect(true);
+ }
+
+ $this->mongo = $this->linkWrite;
+ } else {
+ if (!$this->linkRead) {
+ $this->linkRead = $this->multiConnect(false);
+ }
+
+ $this->mongo = $this->linkRead;
+ }
+ } elseif (!$this->mongo) {
+ // 默认单数据库
+ $this->mongo = $this->connect();
+ }
+ }
+
+ /**
+ * 连接分布式服务器
+ * @access protected
+ * @param boolean $master 主服务器
+ * @return Manager
+ */
+ protected function multiConnect(bool $master = false): Manager
+ {
+ $config = [];
+ // 分布式数据库配置解析
+ foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn'] as $name) {
+ $config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name];
+ }
+
+ // 主服务器序号
+ $m = floor(mt_rand(0, $this->config['master_num'] - 1));
+
+ if ($this->config['rw_separate']) {
+ // 主从式采用读写分离
+ if ($master) // 主服务器写入
+ {
+ if ($this->config['is_replica_set']) {
+ return $this->replicaSetConnect();
+ } else {
+ $r = $m;
+ }
+ } elseif (is_numeric($this->config['slave_no'])) {
+ // 指定服务器读
+ $r = $this->config['slave_no'];
+ } else {
+ // 读操作连接从服务器 每次随机连接的数据库
+ $r = floor(mt_rand($this->config['master_num'], count($config['hostname']) - 1));
+ }
+ } else {
+ // 读写操作不区分服务器 每次随机连接的数据库
+ $r = floor(mt_rand(0, count($config['hostname']) - 1));
+ }
+
+ $dbConfig = [];
+
+ foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn'] as $name) {
+ $dbConfig[$name] = $config[$name][$r] ?? $config[$name][0];
+ }
+
+ return $this->connect($dbConfig, $r);
+ }
+
+ /**
+ * 创建基于复制集的连接
+ * @return Manager
+ */
+ public function replicaSetConnect(): Manager
+ {
+ $this->dbName = $this->config['database'];
+ $this->typeMap = $this->config['type_map'];
+
+ $startTime = microtime(true);
+
+ $this->config['params']['replicaSet'] = $this->config['database'];
+
+ $manager = new Manager($this->buildUrl(), $this->config['params']);
+
+ // 记录数据库连接信息
+ if (!empty($config['trigger_sql'])) {
+ $this->trigger('CONNECT:ReplicaSet[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $this->config['dsn']);
+ }
+
+ return $manager;
+ }
+
+ /**
+ * 根据配置信息 生成适用于连接复制集的 URL
+ * @return string
+ */
+ private function buildUrl(): string
+ {
+ $url = 'mongodb://' . ($this->config['username'] ? "{$this->config['username']}" : '') . ($this->config['password'] ? ":{$this->config['password']}@" : '');
+
+ $hostList = is_string($this->config['hostname']) ? explode(',', $this->config['hostname']) : $this->config['hostname'];
+ $portList = is_string($this->config['hostport']) ? explode(',', $this->config['hostport']) : $this->config['hostport'];
+
+ for ($i = 0; $i < count($hostList); $i++) {
+ $url = $url . $hostList[$i] . ':' . $portList[0] . ',';
+ }
+
+ return rtrim($url, ",") . '/';
+ }
+
+ /**
+ * 插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param boolean $getLastInsID 返回自增主键
+ * @return mixed
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ * @throws BulkWriteException
+ */
+ public function insert(BaseQuery $query, bool $getLastInsID = false)
+ {
+ // 分析查询表达式
+ $options = $query->parseOptions();
+
+ if (empty($options['data'])) {
+ throw new Exception('miss data to insert');
+ }
+
+ // 生成bulk对象
+ $bulk = $this->builder->insert($query);
+
+ $writeResult = $this->mongoExecute($query, $bulk);
+ $result = $writeResult->getInsertedCount();
+
+ if ($result) {
+ $data = $options['data'];
+ $lastInsId = $this->getLastInsID($query);
+
+ if ($lastInsId) {
+ $pk = $query->getPk();
+ $data[$pk] = $lastInsId;
+ }
+
+ $query->setOption('data', $data);
+
+ $this->db->trigger('after_insert', $query);
+
+ if ($getLastInsID) {
+ return $lastInsId;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 获取最近插入的ID
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return mixed
+ */
+ public function getLastInsID(BaseQuery $query)
+ {
+ $id = $this->builder->getLastInsID();
+
+ if (is_array($id)) {
+ array_walk($id, function (&$item, $key) {
+ if ($item instanceof ObjectID) {
+ $item = $item->__toString();
+ }
+ });
+ } elseif ($id instanceof ObjectID) {
+ $id = $id->__toString();
+ }
+
+ return $id;
+ }
+
+ /**
+ * 批量插入记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param array $dataSet 数据集
+ * @return integer
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ * @throws BulkWriteException
+ */
+ public function insertAll(BaseQuery $query, array $dataSet = []): int
+ {
+ // 分析查询表达式
+ $query->parseOptions();
+
+ if (!is_array(reset($dataSet))) {
+ return 0;
+ }
+
+ // 生成bulkWrite对象
+ $bulk = $this->builder->insertAll($query, $dataSet);
+
+ $writeResult = $this->mongoExecute($query, $bulk);
+
+ return $writeResult->getInsertedCount();
+ }
+
+ /**
+ * 更新记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return int
+ * @throws Exception
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ * @throws BulkWriteException
+ */
+ public function update(BaseQuery $query): int
+ {
+ $query->parseOptions();
+
+ // 生成bulkWrite对象
+ $bulk = $this->builder->update($query);
+
+ $writeResult = $this->mongoExecute($query, $bulk);
+
+ $result = $writeResult->getModifiedCount();
+
+ if ($result) {
+ $this->db->trigger('after_update', $query);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return int
+ * @throws Exception
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ * @throws BulkWriteException
+ */
+ public function delete(BaseQuery $query): int
+ {
+ // 分析查询表达式
+ $query->parseOptions();
+
+ // 生成bulkWrite对象
+ $bulk = $this->builder->delete($query);
+
+ // 执行操作
+ $writeResult = $this->mongoExecute($query, $bulk);
+
+ $result = $writeResult->getDeletedCount();
+
+ if ($result) {
+ $this->db->trigger('after_delete', $query);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 查找记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return array
+ * @throws ModelNotFoundException
+ * @throws DataNotFoundException
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ public function select(BaseQuery $query): array
+ {
+ $resultSet = $this->db->trigger('before_select', $query);
+
+ if (!$resultSet) {
+ $resultSet = $this->mongoQuery($query, function ($query) {
+ return $this->builder->select($query);
+ });
+ }
+
+ return $resultSet;
+ }
+
+ /**
+ * 查找单条记录
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @return array
+ * @throws ModelNotFoundException
+ * @throws DataNotFoundException
+ * @throws AuthenticationException
+ * @throws InvalidArgumentException
+ * @throws ConnectionException
+ * @throws RuntimeException
+ */
+ public function find(BaseQuery $query): array
+ {
+ // 事件回调
+ $result = $this->db->trigger('before_find', $query);
+
+ if (!$result) {
+ // 执行查询
+ $resultSet = $this->mongoQuery($query, function ($query) {
+ return $this->builder->select($query, true);
+ });
+
+ $result = $resultSet[0] ?? [];
+ }
+
+ return $result;
+ }
+
+ /**
+ * 得到某个字段的值
+ * @access public
+ * @param string $field 字段名
+ * @param mixed $default 默认值
+ * @return mixed
+ */
+ public function value(BaseQuery $query, string $field, $default = null)
+ {
+ $options = $query->parseOptions();
+
+ if (isset($options['projection'])) {
+ $query->removeOption('projection');
+ }
+
+ $query->setOption('projection', (array) $field);
+
+ if (!empty($options['cache'])) {
+ $cacheItem = $this->parseCache($query, $options['cache']);
+ $key = $cacheItem->getKey();
+
+ if ($this->cache->has($key)) {
+ return $this->cache->get($key);
+ }
+ }
+
+ $mongoQuery = $this->builder->select($query, true);
+
+ if (isset($options['projection'])) {
+ $query->setOption('projection', $options['projection']);
+ } else {
+ $query->removeOption('projection');
+ }
+
+ // 执行查询操作
+ $resultSet = $this->mongoQuery($query, $mongoQuery);
+
+ if (!empty($resultSet)) {
+ $data = array_shift($resultSet);
+ $result = $data[$field];
+ } else {
+ $result = false;
+ }
+
+ if (isset($cacheItem) && false !== $result) {
+ // 缓存数据
+ $cacheItem->set($result);
+ $this->cacheData($cacheItem);
+ }
+
+ return false !== $result ? $result : $default;
+ }
+
+ /**
+ * 得到某个列的数组
+ * @access public
+ * @param string $field 字段名 多个字段用逗号分隔
+ * @param string $key 索引
+ * @return array
+ */
+ public function column(BaseQuery $query, string $field, string $key = ''): array
+ {
+ $options = $query->parseOptions();
+
+ if (isset($options['projection'])) {
+ $query->removeOption('projection');
+ }
+
+ if ($key && '*' != $field) {
+ $projection = $key . ',' . $field;
+ } else {
+ $projection = $field;
+ }
+
+ $query->field($projection);
+
+ if (!empty($options['cache'])) {
+ // 判断查询缓存
+ $cacheItem = $this->parseCache($query, $options['cache']);
+ $key = $cacheItem->getKey();
+
+ if ($this->cache->has($key)) {
+ return $this->cache->get($key);
+ }
+ }
+
+ $mongoQuery = $this->builder->select($query);
+
+ if (isset($options['projection'])) {
+ $query->setOption('projection', $options['projection']);
+ } else {
+ $query->removeOption('projection');
+ }
+
+ // 执行查询操作
+ $resultSet = $this->mongoQuery($query, $mongoQuery);
+
+ if (('*' == $field || strpos($field, ',')) && $key) {
+ $result = array_column($resultSet, null, $key);
+ } elseif (!empty($resultSet)) {
+ $result = array_column($resultSet, $field, $key);
+ } else {
+ $result = [];
+ }
+
+ if (isset($cacheItem)) {
+ // 缓存数据
+ $cacheItem->set($result);
+ $this->cacheData($cacheItem);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 执行command
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string|array|object $command 指令
+ * @param mixed $extra 额外参数
+ * @param string $db 数据库名
+ * @return array
+ */
+ public function cmd(BaseQuery $query, $command, $extra = null, string $db = ''): array
+ {
+ if (is_array($command) || is_object($command)) {
+
+ $this->mongoLog('cmd', 'cmd', $command);
+
+ // 直接创建Command对象
+ $command = new Command($command);
+ } else {
+ // 调用Builder封装的Command对象
+ $command = $this->builder->$command($query, $extra);
+ }
+
+ return $this->command($command, $db);
+ }
+
+ /**
+ * 获取数据库字段
+ * @access public
+ * @param mixed $tableName 数据表名
+ * @return array
+ */
+ public function getTableFields($tableName): array
+ {
+ return [];
+ }
+
+ /**
+ * 执行数据库事务
+ * @access public
+ * @param callable $callback 数据操作方法回调
+ * @return mixed
+ * @throws PDOException
+ * @throws \Exception
+ * @throws \Throwable
+ */
+ public function transaction(callable $callback)
+ {
+ $this->startTrans();
+ try {
+ $result = null;
+ if (is_callable($callback)) {
+ $result = call_user_func_array($callback, [$this]);
+ }
+ $this->commit();
+ return $result;
+ } catch (\Exception $e) {
+ $this->rollback();
+ throw $e;
+ } catch (\Throwable $e) {
+ $this->rollback();
+ throw $e;
+ }
+ }
+
+ /**
+ * 启动事务
+ * @access public
+ * @return void
+ * @throws \PDOException
+ * @throws \Exception
+ */
+ public function startTrans()
+ {
+ $this->initConnect(true);
+ $this->session_uuid = uniqid();
+ $this->sessions[$this->session_uuid] = $this->getMongo()->startSession();
+
+ $this->sessions[$this->session_uuid]->startTransaction([]);
+ }
+
+ /**
+ * 用于非自动提交状态下面的查询提交
+ * @access public
+ * @return void
+ * @throws PDOException
+ */
+ public function commit()
+ {
+ if ($session = $this->getSession()) {
+ $session->commitTransaction();
+ $this->setLastSession();
+ }
+ }
+
+ /**
+ * 事务回滚
+ * @access public
+ * @return void
+ * @throws PDOException
+ */
+ public function rollback()
+ {
+ if ($session = $this->getSession()) {
+ $session->abortTransaction();
+ $this->setLastSession();
+ }
+ }
+
+ /**
+ * 结束当前会话,设置上一个会话为当前会话
+ * @author klinson <klinson@163.com>
+ */
+ protected function setLastSession()
+ {
+ if ($session = $this->getSession()) {
+ $session->endSession();
+ unset($this->sessions[$this->session_uuid]);
+ if (empty($this->sessions)) {
+ $this->session_uuid = null;
+ } else {
+ end($this->sessions);
+ $this->session_uuid = key($this->sessions);
+ }
+ }
+ }
+
+ /**
+ * 获取当前会话
+ * @return \MongoDB\Driver\Session|null
+ * @author klinson <klinson@163.com>
+ */
+ public function getSession()
+ {
+ return ($this->session_uuid && isset($this->sessions[$this->session_uuid]))
+ ? $this->sessions[$this->session_uuid]
+ : null;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/connector/Mysql.php b/vendor/topthink/think-orm/src/db/connector/Mysql.php
new file mode 100755
index 0000000..483b447
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/connector/Mysql.php
@@ -0,0 +1,162 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\connector;
+
+use PDO;
+use think\db\PDOConnection;
+
+/**
+ * mysql数据库驱动
+ */
+class Mysql extends PDOConnection
+{
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access protected
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn(array $config): string
+ {
+ if (!empty($config['socket'])) {
+ $dsn = 'mysql:unix_socket=' . $config['socket'];
+ } elseif (!empty($config['hostport'])) {
+ $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . $config['hostport'];
+ } else {
+ $dsn = 'mysql:host=' . $config['hostname'];
+ }
+ $dsn .= ';dbname=' . $config['database'];
+
+ if (!empty($config['charset'])) {
+ $dsn .= ';charset=' . $config['charset'];
+ }
+
+ return $dsn;
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @param string $tableName
+ * @return array
+ */
+ public function getFields(string $tableName): array
+ {
+ [$tableName] = explode(' ', $tableName);
+
+ if (false === strpos($tableName, '`')) {
+ if (strpos($tableName, '.')) {
+ $tableName = str_replace('.', '`.`', $tableName);
+ }
+ $tableName = '`' . $tableName . '`';
+ }
+
+ $sql = 'SHOW FULL COLUMNS FROM ' . $tableName;
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ if (!empty($result)) {
+ foreach ($result as $key => $val) {
+ $val = array_change_key_case($val);
+
+ $info[$val['field']] = [
+ 'name' => $val['field'],
+ 'type' => $val['type'],
+ 'notnull' => 'NO' == $val['null'],
+ 'default' => $val['default'],
+ 'primary' => strtolower($val['key']) == 'pri',
+ 'autoinc' => strtolower($val['extra']) == 'auto_increment',
+ 'comment' => $val['comment'],
+ ];
+ }
+ }
+
+ return $this->fieldCase($info);
+ }
+
+ /**
+ * 取得数据库的表信息
+ * @access public
+ * @param string $dbName
+ * @return array
+ */
+ public function getTables(string $dbName = ''): array
+ {
+ $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES ';
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ foreach ($result as $key => $val) {
+ $info[$key] = current($val);
+ }
+
+ return $info;
+ }
+
+ protected function supportSavepoint(): bool
+ {
+ return true;
+ }
+
+ /**
+ * 启动XA事务
+ * @access public
+ * @param string $xid XA事务id
+ * @return void
+ */
+ public function startTransXa(string $xid)
+ {
+ $this->initConnect(true);
+ $this->linkID->exec("XA START '$xid'");
+ }
+
+ /**
+ * 预编译XA事务
+ * @access public
+ * @param string $xid XA事务id
+ * @return void
+ */
+ public function prepareXa(string $xid)
+ {
+ $this->initConnect(true);
+ $this->linkID->exec("XA END '$xid'");
+ $this->linkID->exec("XA PREPARE '$xid'");
+ }
+
+ /**
+ * 提交XA事务
+ * @access public
+ * @param string $xid XA事务id
+ * @return void
+ */
+ public function commitXa(string $xid)
+ {
+ $this->initConnect(true);
+ $this->linkID->exec("XA COMMIT '$xid'");
+ }
+
+ /**
+ * 回滚XA事务
+ * @access public
+ * @param string $xid XA事务id
+ * @return void
+ */
+ public function rollbackXa(string $xid)
+ {
+ $this->initConnect(true);
+ $this->linkID->exec("XA ROLLBACK '$xid'");
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/connector/Oracle.php b/vendor/topthink/think-orm/src/db/connector/Oracle.php
new file mode 100755
index 0000000..236d8bf
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/connector/Oracle.php
@@ -0,0 +1,117 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\db\connector;
+
+use PDO;
+use think\db\BaseQuery;
+use think\db\PDOConnection;
+
+/**
+ * Oracle数据库驱动
+ */
+class Oracle extends PDOConnection
+{
+ /**
+ * 解析pdo连接的dsn信息
+ * @access protected
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn(array $config): string
+ {
+ $dsn = 'oci:dbname=';
+
+ if (!empty($config['hostname'])) {
+ // Oracle Instant Client
+ $dsn .= '//' . $config['hostname'] . ($config['hostport'] ? ':' . $config['hostport'] : '') . '/';
+ }
+
+ $dsn .= $config['database'];
+
+ if (!empty($config['charset'])) {
+ $dsn .= ';charset=' . $config['charset'];
+ }
+
+ return $dsn;
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @param string $tableName
+ * @return array
+ */
+ public function getFields(string $tableName): array
+ {
+ [$tableName] = explode(' ', $tableName);
+ $sql = "select a.column_name,data_type,DECODE (nullable, 'Y', 0, 1) notnull,data_default, DECODE (A .column_name,b.column_name,1,0) pk from all_tab_columns a,(select column_name from all_constraints c, all_cons_columns col where c.constraint_name = col.constraint_name and c.constraint_type = 'P' and c.table_name = '" . strtoupper($tableName) . "' ) b where table_name = '" . strtoupper($tableName) . "' and a.column_name = b.column_name (+)";
+
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ if ($result) {
+ foreach ($result as $key => $val) {
+ $val = array_change_key_case($val);
+
+ $info[$val['column_name']] = [
+ 'name' => $val['column_name'],
+ 'type' => $val['data_type'],
+ 'notnull' => $val['notnull'],
+ 'default' => $val['data_default'],
+ 'primary' => $val['pk'],
+ 'autoinc' => $val['pk'],
+ ];
+ }
+ }
+
+ return $this->fieldCase($info);
+ }
+
+ /**
+ * 取得数据库的表信息(暂时实现取得用户表信息)
+ * @access public
+ * @param string $dbName
+ * @return array
+ */
+ public function getTables(string $dbName = ''): array
+ {
+ $sql = 'select table_name from all_tables';
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ foreach ($result as $key => $val) {
+ $info[$key] = current($val);
+ }
+
+ return $info;
+ }
+
+ /**
+ * 获取最近插入的ID
+ * @access public
+ * @param BaseQuery $query 查询对象
+ * @param string $sequence 自增序列名
+ * @return mixed
+ */
+ public function getLastInsID(BaseQuery $query, string $sequence = null)
+ {
+ $pdo = $this->linkID->query("select {$sequence}.currval as id from dual");
+ $result = $pdo->fetchColumn();
+
+ return $result;
+ }
+
+ protected function supportSavepoint(): bool
+ {
+ return true;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/connector/Pgsql.php b/vendor/topthink/think-orm/src/db/connector/Pgsql.php
new file mode 100755
index 0000000..fec8f84
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/connector/Pgsql.php
@@ -0,0 +1,108 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\db\connector;
+
+use PDO;
+use think\db\PDOConnection;
+
+/**
+ * Pgsql数据库驱动
+ */
+class Pgsql extends PDOConnection
+{
+
+ /**
+ * 默认PDO连接参数
+ * @var array
+ */
+ protected $params = [
+ PDO::ATTR_CASE => PDO::CASE_NATURAL,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
+ PDO::ATTR_STRINGIFY_FETCHES => false,
+ ];
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access protected
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn(array $config): string
+ {
+ $dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname'];
+
+ if (!empty($config['hostport'])) {
+ $dsn .= ';port=' . $config['hostport'];
+ }
+
+ return $dsn;
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @param string $tableName
+ * @return array
+ */
+ public function getFields(string $tableName): array
+ {
+ [$tableName] = explode(' ', $tableName);
+ $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');';
+
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ if (!empty($result)) {
+ foreach ($result as $key => $val) {
+ $val = array_change_key_case($val);
+
+ $info[$val['field']] = [
+ 'name' => $val['field'],
+ 'type' => $val['type'],
+ 'notnull' => (bool) ('' !== $val['null']),
+ 'default' => $val['default'],
+ 'primary' => !empty($val['key']),
+ 'autoinc' => (0 === strpos($val['extra'], 'nextval(')),
+ ];
+ }
+ }
+
+ return $this->fieldCase($info);
+ }
+
+ /**
+ * 取得数据库的表信息
+ * @access public
+ * @param string $dbName
+ * @return array
+ */
+ public function getTables(string $dbName = ''): array
+ {
+ $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'";
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ foreach ($result as $key => $val) {
+ $info[$key] = current($val);
+ }
+
+ return $info;
+ }
+
+ protected function supportSavepoint(): bool
+ {
+ return true;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/connector/Sqlite.php b/vendor/topthink/think-orm/src/db/connector/Sqlite.php
new file mode 100755
index 0000000..c664f20
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/connector/Sqlite.php
@@ -0,0 +1,96 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\db\connector;
+
+use PDO;
+use think\db\PDOConnection;
+
+/**
+ * Sqlite数据库驱动
+ */
+class Sqlite extends PDOConnection
+{
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access protected
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn(array $config): string
+ {
+ $dsn = 'sqlite:' . $config['database'];
+
+ return $dsn;
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @param string $tableName
+ * @return array
+ */
+ public function getFields(string $tableName): array
+ {
+ [$tableName] = explode(' ', $tableName);
+ $sql = 'PRAGMA table_info( ' . $tableName . ' )';
+
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ if (!empty($result)) {
+ foreach ($result as $key => $val) {
+ $val = array_change_key_case($val);
+
+ $info[$val['name']] = [
+ 'name' => $val['name'],
+ 'type' => $val['type'],
+ 'notnull' => 1 === $val['notnull'],
+ 'default' => $val['dflt_value'],
+ 'primary' => '1' == $val['pk'],
+ 'autoinc' => '1' == $val['pk'],
+ ];
+ }
+ }
+
+ return $this->fieldCase($info);
+ }
+
+ /**
+ * 取得数据库的表信息
+ * @access public
+ * @param string $dbName
+ * @return array
+ */
+ public function getTables(string $dbName = ''): array
+ {
+ $sql = "SELECT name FROM sqlite_master WHERE type='table' "
+ . "UNION ALL SELECT name FROM sqlite_temp_master "
+ . "WHERE type='table' ORDER BY name";
+
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ foreach ($result as $key => $val) {
+ $info[$key] = current($val);
+ }
+
+ return $info;
+ }
+
+ protected function supportSavepoint(): bool
+ {
+ return true;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php b/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php
new file mode 100755
index 0000000..63b2df0
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php
@@ -0,0 +1,122 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\db\connector;
+
+use PDO;
+use think\db\PDOConnection;
+
+/**
+ * Sqlsrv数据库驱动
+ */
+class Sqlsrv extends PDOConnection
+{
+ /**
+ * 默认PDO连接参数
+ * @var array
+ */
+ protected $params = [
+ PDO::ATTR_CASE => PDO::CASE_NATURAL,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
+ PDO::ATTR_STRINGIFY_FETCHES => false,
+ ];
+
+ /**
+ * 解析pdo连接的dsn信息
+ * @access protected
+ * @param array $config 连接信息
+ * @return string
+ */
+ protected function parseDsn(array $config): string
+ {
+ $dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname'];
+
+ if (!empty($config['hostport'])) {
+ $dsn .= ',' . $config['hostport'];
+ }
+
+ return $dsn;
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @param string $tableName
+ * @return array
+ */
+ public function getFields(string $tableName): array
+ {
+ [$tableName] = explode(' ', $tableName);
+
+ $sql = "SELECT column_name, data_type, column_default, is_nullable
+ FROM information_schema.tables AS t
+ JOIN information_schema.columns AS c
+ ON t.table_catalog = c.table_catalog
+ AND t.table_schema = c.table_schema
+ AND t.table_name = c.table_name
+ WHERE t.table_name = '$tableName'";
+
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ if (!empty($result)) {
+ foreach ($result as $key => $val) {
+ $val = array_change_key_case($val);
+
+ $info[$val['column_name']] = [
+ 'name' => $val['column_name'],
+ 'type' => $val['data_type'],
+ 'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes
+ 'default' => $val['column_default'],
+ 'primary' => false,
+ 'autoinc' => false,
+ ];
+ }
+ }
+
+ $sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'";
+ $pdo = $this->linkID->query($sql);
+ $result = $pdo->fetch(PDO::FETCH_ASSOC);
+
+ if ($result) {
+ $info[$result['column_name']]['primary'] = true;
+ }
+
+ return $this->fieldCase($info);
+ }
+
+ /**
+ * 取得数据表的字段信息
+ * @access public
+ * @param string $dbName
+ * @return array
+ */
+ public function getTables(string $dbName = ''): array
+ {
+ $sql = "SELECT TABLE_NAME
+ FROM INFORMATION_SCHEMA.TABLES
+ WHERE TABLE_TYPE = 'BASE TABLE'
+ ";
+
+ $pdo = $this->getPDOStatement($sql);
+ $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
+ $info = [];
+
+ foreach ($result as $key => $val) {
+ $info[$key] = current($val);
+ }
+
+ return $info;
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/connector/pgsql.sql b/vendor/topthink/think-orm/src/db/connector/pgsql.sql
new file mode 100755
index 0000000..e1a09a3
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/connector/pgsql.sql
@@ -0,0 +1,117 @@
+CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS
+$BODY$
+DECLARE
+ v_type varchar;
+BEGIN
+ IF a_type='int8' THEN
+ v_type:='bigint';
+ ELSIF a_type='int4' THEN
+ v_type:='integer';
+ ELSIF a_type='int2' THEN
+ v_type:='smallint';
+ ELSIF a_type='bpchar' THEN
+ v_type:='char';
+ ELSE
+ v_type:=a_type;
+ END IF;
+ RETURN v_type;
+END;
+$BODY$
+LANGUAGE PLPGSQL;
+
+CREATE TYPE "public"."tablestruct" AS (
+ "fields_key_name" varchar(100),
+ "fields_name" VARCHAR(200),
+ "fields_type" VARCHAR(20),
+ "fields_length" BIGINT,
+ "fields_not_null" VARCHAR(10),
+ "fields_default" VARCHAR(500),
+ "fields_comment" VARCHAR(1000)
+);
+
+CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
+$body$
+DECLARE
+ v_ret tablestruct;
+ v_oid oid;
+ v_sql varchar;
+ v_rec RECORD;
+ v_key varchar;
+BEGIN
+ SELECT
+ pg_class.oid INTO v_oid
+ FROM
+ pg_class
+ INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name)
+ WHERE
+ pg_class.relname=a_table_name;
+ IF NOT FOUND THEN
+ RETURN;
+ END IF;
+
+ v_sql='
+ SELECT
+ pg_attribute.attname AS fields_name,
+ pg_attribute.attnum AS fields_index,
+ pgsql_type(pg_type.typname::varchar) AS fields_type,
+ pg_attribute.atttypmod-4 as fields_length,
+ CASE WHEN pg_attribute.attnotnull THEN ''not null''
+ ELSE ''''
+ END AS fields_not_null,
+ pg_attrdef.adsrc AS fields_default,
+ pg_description.description AS fields_comment
+ FROM
+ pg_attribute
+ INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid
+ INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid
+ LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum
+ LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum
+ WHERE
+ pg_attribute.attnum > 0
+ AND attisdropped <> ''t''
+ AND pg_class.oid = ' || v_oid || '
+ ORDER BY pg_attribute.attnum' ;
+
+ FOR v_rec IN EXECUTE v_sql LOOP
+ v_ret.fields_name=v_rec.fields_name;
+ v_ret.fields_type=v_rec.fields_type;
+ IF v_rec.fields_length > 0 THEN
+ v_ret.fields_length:=v_rec.fields_length;
+ ELSE
+ v_ret.fields_length:=NULL;
+ END IF;
+ v_ret.fields_not_null=v_rec.fields_not_null;
+ v_ret.fields_default=v_rec.fields_default;
+ v_ret.fields_comment=v_rec.fields_comment;
+ SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name;
+ IF FOUND THEN
+ v_ret.fields_key_name=v_key;
+ ELSE
+ v_ret.fields_key_name='';
+ END IF;
+ RETURN NEXT v_ret;
+ END LOOP;
+ RETURN ;
+END;
+$body$
+LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
+
+COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar)
+IS '获得表信息';
+
+---重载一个函数
+CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
+$body$
+DECLARE
+ v_ret tablestruct;
+BEGIN
+ FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP
+ RETURN NEXT v_ret;
+ END LOOP;
+ RETURN;
+END;
+$body$
+LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
+
+COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar)
+IS '获得表信息';
\ No newline at end of file
diff --git a/vendor/topthink/think-orm/src/db/exception/BindParamException.php b/vendor/topthink/think-orm/src/db/exception/BindParamException.php
new file mode 100755
index 0000000..08bb388
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/exception/BindParamException.php
@@ -0,0 +1,35 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\exception;
+
+/**
+ * PDO参数绑定异常
+ */
+class BindParamException extends DbException
+{
+
+ /**
+ * BindParamException constructor.
+ * @access public
+ * @param string $message
+ * @param array $config
+ * @param string $sql
+ * @param array $bind
+ * @param int $code
+ */
+ public function __construct(string $message, array $config, string $sql, array $bind, int $code = 10502)
+ {
+ $this->setData('Bind Param', $bind);
+ parent::__construct($message, $config, $sql, $code);
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php b/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php
new file mode 100755
index 0000000..d10dd43
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php
@@ -0,0 +1,43 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\exception;
+
+class DataNotFoundException extends DbException
+{
+ protected $table;
+
+ /**
+ * DbException constructor.
+ * @access public
+ * @param string $message
+ * @param string $table
+ * @param array $config
+ */
+ public function __construct(string $message, string $table = '', array $config = [])
+ {
+ $this->message = $message;
+ $this->table = $table;
+
+ $this->setData('Database Config', $config);
+ }
+
+ /**
+ * 获取数据表名
+ * @access public
+ * @return string
+ */
+ public function getTable()
+ {
+ return $this->table;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/exception/DbException.php b/vendor/topthink/think-orm/src/db/exception/DbException.php
new file mode 100755
index 0000000..d5bfc3f
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/exception/DbException.php
@@ -0,0 +1,81 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\exception;
+
+use Exception;
+
+/**
+ * Database相关异常处理类
+ */
+class DbException extends Exception
+{
+ /**
+ * DbException constructor.
+ * @access public
+ * @param string $message
+ * @param array $config
+ * @param string $sql
+ * @param int $code
+ */
+ public function __construct(string $message, array $config = [], string $sql = '', int $code = 10500)
+ {
+ $this->message = $message;
+ $this->code = $code;
+
+ $this->setData('Database Status', [
+ 'Error Code' => $code,
+ 'Error Message' => $message,
+ 'Error SQL' => $sql,
+ ]);
+
+ unset($config['username'], $config['password']);
+ $this->setData('Database Config', $config);
+ }
+
+ /**
+ * 保存异常页面显示的额外Debug数据
+ * @var array
+ */
+ protected $data = [];
+
+ /**
+ * 设置异常额外的Debug数据
+ * 数据将会显示为下面的格式
+ *
+ * Exception Data
+ * --------------------------------------------------
+ * Label 1
+ * key1 value1
+ * key2 value2
+ * Label 2
+ * key1 value1
+ * key2 value2
+ *
+ * @param string $label 数据分类,用于异常页面显示
+ * @param array $data 需要显示的数据,必须为关联数组
+ */
+ final protected function setData($label, array $data)
+ {
+ $this->data[$label] = $data;
+ }
+
+ /**
+ * 获取异常额外Debug数据
+ * 主要用于输出到异常页面便于调试
+ * @return array 由setData设置的Debug数据
+ */
+ final public function getData()
+ {
+ return $this->data;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php b/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php
new file mode 100755
index 0000000..047e45e
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+namespace think\db\exception;
+
+use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface;
+
+/**
+ * 非法数据异常
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements SimpleCacheInvalidArgumentInterface
+{
+}
diff --git a/vendor/topthink/think-orm/src/db/exception/ModelEventException.php b/vendor/topthink/think-orm/src/db/exception/ModelEventException.php
new file mode 100755
index 0000000..767bc1a
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/exception/ModelEventException.php
@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\db\exception;
+
+/**
+ * 模型事件异常
+ */
+class ModelEventException extends DbException
+{
+}
diff --git a/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php b/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php
new file mode 100755
index 0000000..84a1525
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php
@@ -0,0 +1,44 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\exception;
+
+class ModelNotFoundException extends DbException
+{
+ protected $model;
+
+ /**
+ * 构造方法
+ * @access public
+ * @param string $message
+ * @param string $model
+ * @param array $config
+ */
+ public function __construct(string $message, string $model = '', array $config = [])
+ {
+ $this->message = $message;
+ $this->model = $model;
+
+ $this->setData('Database Config', $config);
+ }
+
+ /**
+ * 获取模型类名
+ * @access public
+ * @return string
+ */
+ public function getModel()
+ {
+ return $this->model;
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/db/exception/PDOException.php b/vendor/topthink/think-orm/src/db/exception/PDOException.php
new file mode 100755
index 0000000..41c0d73
--- /dev/null
+++ b/vendor/topthink/think-orm/src/db/exception/PDOException.php
@@ -0,0 +1,41 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\db\exception;
+
+/**
+ * PDO异常处理类
+ * 重新封装了系统的\PDOException类
+ */
+class PDOException extends DbException
+{
+ /**
+ * PDOException constructor.
+ * @access public
+ * @param \PDOException $exception
+ * @param array $config
+ * @param string $sql
+ * @param int $code
+ */
+ public function __construct(\PDOException $exception, array $config = [], string $sql = '', int $code = 10501)
+ {
+ $error = $exception->errorInfo;
+
+ $this->setData('PDO Error Info', [
+ 'SQLSTATE' => $error[0],
+ 'Driver Error Code' => isset($error[1]) ? $error[1] : 0,
+ 'Driver Error Message' => isset($error[2]) ? $error[2] : '',
+ ]);
+
+ parent::__construct($exception->getMessage(), $config, $sql, $code);
+ }
+}
diff --git a/vendor/topthink/think-orm/src/facade/Db.php b/vendor/topthink/think-orm/src/facade/Db.php
new file mode 100755
index 0000000..174a4f0
--- /dev/null
+++ b/vendor/topthink/think-orm/src/facade/Db.php
@@ -0,0 +1,86 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\facade;
+
+if (class_exists('think\Facade')) {
+ class Facade extends \think\Facade
+ {}
+} else {
+ class Facade
+ {
+ /**
+ * 始终创建新的对象实例
+ * @var bool
+ */
+ protected static $alwaysNewInstance;
+
+ protected static $instance;
+
+ /**
+ * 获取当前Facade对应类名
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {}
+
+ /**
+ * 创建Facade实例
+ * @static
+ * @access protected
+ * @param bool $newInstance 是否每次创建新的实例
+ * @return object
+ */
+ protected static function createFacade(bool $newInstance = false)
+ {
+ $class = static::getFacadeClass() ?: 'think\DbManager';
+
+ if (static::$alwaysNewInstance) {
+ $newInstance = true;
+ }
+
+ if ($newInstance) {
+ return new $class();
+ }
+
+ if (!self::$instance) {
+ self::$instance = new $class();
+ }
+
+ return self::$instance;
+
+ }
+
+ // 调用实际类的方法
+ public static function __callStatic($method, $params)
+ {
+ return call_user_func_array([static::createFacade(), $method], $params);
+ }
+ }
+}
+
+/**
+ * @see \think\DbManager
+ * @mixin \think\DbManager
+ */
+class Db extends Facade
+{
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'think\DbManager';
+ }
+}
diff --git a/vendor/topthink/think-orm/src/model/Collection.php b/vendor/topthink/think-orm/src/model/Collection.php
new file mode 100755
index 0000000..fd54272
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/Collection.php
@@ -0,0 +1,250 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model;
+
+use think\Collection as BaseCollection;
+use think\Model;
+use think\Paginator;
+
+/**
+ * 模型数据集类
+ */
+class Collection extends BaseCollection
+{
+ /**
+ * 延迟预载入关联查询
+ * @access public
+ * @param array|string $relation 关联
+ * @param mixed $cache 关联缓存
+ * @return $this
+ */
+ public function load($relation, $cache = false)
+ {
+ if (!$this->isEmpty()) {
+ $item = current($this->items);
+ $item->eagerlyResultSet($this->items, (array) $relation, [], false, $cache);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 删除数据集的数据
+ * @access public
+ * @return bool
+ */
+ public function delete(): bool
+ {
+ $this->each(function (Model $model) {
+ $model->delete();
+ });
+
+ return true;
+ }
+
+ /**
+ * 更新数据
+ * @access public
+ * @param array $data 数据数组
+ * @param array $allowField 允许字段
+ * @return bool
+ */
+ public function update(array $data, array $allowField = []): bool
+ {
+ $this->each(function (Model $model) use ($data, $allowField) {
+ if (!empty($allowField)) {
+ $model->allowField($allowField);
+ }
+
+ $model->save($data);
+ });
+
+ return true;
+ }
+
+ /**
+ * 设置需要隐藏的输出属性
+ * @access public
+ * @param array $hidden 属性列表
+ * @return $this
+ */
+ public function hidden(array $hidden)
+ {
+ $this->each(function (Model $model) use ($hidden) {
+ $model->hidden($hidden);
+ });
+
+ return $this;
+ }
+
+ /**
+ * 设置需要输出的属性
+ * @access public
+ * @param array $visible
+ * @return $this
+ */
+ public function visible(array $visible)
+ {
+ $this->each(function (Model $model) use ($visible) {
+ $model->visible($visible);
+ });
+
+ return $this;
+ }
+
+ /**
+ * 设置需要追加的输出属性
+ * @access public
+ * @param array $append 属性列表
+ * @return $this
+ */
+ public function append(array $append)
+ {
+ $this->each(function (Model $model) use ($append) {
+ $model->append($append);
+ });
+
+ return $this;
+ }
+
+ /**
+ * 设置父模型
+ * @access public
+ * @param Model $parent 父模型
+ * @return $this
+ */
+ public function setParent(Model $parent)
+ {
+ $this->each(function (Model $model) use ($parent) {
+ $model->setParent($parent);
+ });
+
+ return $this;
+ }
+
+ /**
+ * 设置数据字段获取器
+ * @access public
+ * @param string|array $name 字段名
+ * @param callable $callback 闭包获取器
+ * @return $this
+ */
+ public function withAttr($name, $callback = null)
+ {
+ $this->each(function (Model $model) use ($name, $callback) {
+ $model->withAttribute($name, $callback);
+ });
+
+ return $this;
+ }
+
+ /**
+ * 绑定(一对一)关联属性到当前模型
+ * @access protected
+ * @param string $relation 关联名称
+ * @param array $attrs 绑定属性
+ * @return $this
+ * @throws Exception
+ */
+ public function bindAttr(string $relation, array $attrs = [])
+ {
+ $this->each(function (Model $model) use ($relation, $attrs) {
+ $model->bindAttr($relation, $attrs);
+ });
+
+ return $this;
+ }
+
+ /**
+ * 按指定键整理数据
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @param string $indexKey 键名
+ * @return array
+ */
+ public function dictionary($items = null, string &$indexKey = null)
+ {
+ if ($items instanceof self || $items instanceof Paginator) {
+ $items = $items->all();
+ }
+
+ $items = is_null($items) ? $this->items : $items;
+
+ if ($items && empty($indexKey)) {
+ $indexKey = $items[0]->getPk();
+ }
+
+ if (isset($indexKey) && is_string($indexKey)) {
+ return array_column($items, null, $indexKey);
+ }
+
+ return $items;
+ }
+
+ /**
+ * 比较数据集,返回差集
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @param string $indexKey 指定比较的键名
+ * @return static
+ */
+ public function diff($items, string $indexKey = null)
+ {
+ if ($this->isEmpty()) {
+ return new static($items);
+ }
+
+ $diff = [];
+ $dictionary = $this->dictionary($items, $indexKey);
+
+ if (is_string($indexKey)) {
+ foreach ($this->items as $item) {
+ if (!isset($dictionary[$item[$indexKey]])) {
+ $diff[] = $item;
+ }
+ }
+ }
+
+ return new static($diff);
+ }
+
+ /**
+ * 比较数据集,返回交集
+ *
+ * @access public
+ * @param mixed $items 数据
+ * @param string $indexKey 指定比较的键名
+ * @return static
+ */
+ public function intersect($items, string $indexKey = null)
+ {
+ if ($this->isEmpty()) {
+ return new static([]);
+ }
+
+ $intersect = [];
+ $dictionary = $this->dictionary($items, $indexKey);
+
+ if (is_string($indexKey)) {
+ foreach ($this->items as $item) {
+ if (isset($dictionary[$item[$indexKey]])) {
+ $intersect[] = $item;
+ }
+ }
+ }
+
+ return new static($intersect);
+ }
+}
diff --git a/vendor/topthink/think-orm/src/model/Pivot.php b/vendor/topthink/think-orm/src/model/Pivot.php
new file mode 100755
index 0000000..893c01b
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/Pivot.php
@@ -0,0 +1,53 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model;
+
+use think\Model;
+
+/**
+ * 多对多中间表模型类
+ */
+class Pivot extends Model
+{
+
+ /**
+ * 父模型
+ * @var Model
+ */
+ public $parent;
+
+ /**
+ * 是否时间自动写入
+ * @var bool
+ */
+ protected $autoWriteTimestamp = false;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param array $data 数据
+ * @param Model $parent 上级模型
+ * @param string $table 中间数据表名
+ */
+ public function __construct(array $data = [], Model $parent = null, string $table = '')
+ {
+ $this->parent = $parent;
+
+ if (is_null($this->name)) {
+ $this->name = $table;
+ }
+
+ parent::__construct($data);
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/Relation.php b/vendor/topthink/think-orm/src/model/Relation.php
new file mode 100755
index 0000000..d9d02e3
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/Relation.php
@@ -0,0 +1,258 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model;
+
+use Closure;
+use ReflectionFunction;
+use think\db\BaseQuery as Query;
+use think\db\exception\DbException as Exception;
+use think\Model;
+
+/**
+ * 模型关联基础类
+ * @package think\model
+ * @mixin Query
+ */
+abstract class Relation
+{
+ /**
+ * 父模型对象
+ * @var Model
+ */
+ protected $parent;
+
+ /**
+ * 当前关联的模型类名
+ * @var string
+ */
+ protected $model;
+
+ /**
+ * 关联模型查询对象
+ * @var Query
+ */
+ protected $query;
+
+ /**
+ * 关联表外键
+ * @var string
+ */
+ protected $foreignKey;
+
+ /**
+ * 关联表主键
+ * @var string
+ */
+ protected $localKey;
+
+ /**
+ * 是否执行关联基础查询
+ * @var bool
+ */
+ protected $baseQuery;
+
+ /**
+ * 是否为自关联
+ * @var bool
+ */
+ protected $selfRelation = false;
+
+ /**
+ * 关联数据数量限制
+ * @var int
+ */
+ protected $withLimit;
+
+ /**
+ * 关联数据字段限制
+ * @var array
+ */
+ protected $withField;
+
+ /**
+ * 获取关联的所属模型
+ * @access public
+ * @return Model
+ */
+ public function getParent(): Model
+ {
+ return $this->parent;
+ }
+
+ /**
+ * 获取当前的关联模型类的Query实例
+ * @access public
+ * @return Query
+ */
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ /**
+ * 获取当前的关联模型类的实例
+ * @access public
+ * @return Model
+ */
+ public function getModel(): Model
+ {
+ return $this->query->getModel();
+ }
+
+ /**
+ * 当前关联是否为自关联
+ * @access public
+ * @return bool
+ */
+ public function isSelfRelation(): bool
+ {
+ return $this->selfRelation;
+ }
+
+ /**
+ * 封装关联数据集
+ * @access public
+ * @param array $resultSet 数据集
+ * @param Model $parent 父模型
+ * @return mixed
+ */
+ protected function resultSetBuild(array $resultSet, Model $parent = null)
+ {
+ return (new $this->model)->toCollection($resultSet)->setParent($parent);
+ }
+
+ protected function getQueryFields(string $model)
+ {
+ $fields = $this->query->getOptions('field');
+ return $this->getRelationQueryFields($fields, $model);
+ }
+
+ protected function getRelationQueryFields($fields, string $model)
+ {
+ if (empty($fields) || '*' == $fields) {
+ return $model . '.*';
+ }
+
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+
+ foreach ($fields as &$field) {
+ if (false === strpos($field, '.')) {
+ $field = $model . '.' . $field;
+ }
+ }
+
+ return $fields;
+ }
+
+ protected function getQueryWhere(array &$where, string $relation): void
+ {
+ foreach ($where as $key => &$val) {
+ if (is_string($key)) {
+ $where[] = [false === strpos($key, '.') ? $relation . '.' . $key : $key, '=', $val];
+ unset($where[$key]);
+ } elseif (isset($val[0]) && false === strpos($val[0], '.')) {
+ $val[0] = $relation . '.' . $val[0];
+ }
+ }
+ }
+
+ /**
+ * 更新数据
+ * @access public
+ * @param array $data 更新数据
+ * @return integer
+ */
+ public function update(array $data = []): int
+ {
+ return $this->query->update($data);
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param mixed $data 表达式 true 表示强制删除
+ * @return int
+ * @throws Exception
+ * @throws PDOException
+ */
+ public function delete($data = null): int
+ {
+ return $this->query->delete($data);
+ }
+
+ /**
+ * 限制关联数据的数量
+ * @access public
+ * @param int $limit 关联数量限制
+ * @return $this
+ */
+ public function withLimit(int $limit)
+ {
+ $this->withLimit = $limit;
+ return $this;
+ }
+
+ /**
+ * 限制关联数据的字段
+ * @access public
+ * @param array $field 关联字段限制
+ * @return $this
+ */
+ public function withField(array $field)
+ {
+ $this->withField = $field;
+ return $this;
+ }
+
+ /**
+ * 判断闭包的参数类型
+ * @access protected
+ * @return mixed
+ */
+ protected function getClosureType(Closure $closure)
+ {
+ $reflect = new ReflectionFunction($closure);
+ $params = $reflect->getParameters();
+
+ if (!empty($params)) {
+ $type = $params[0]->getType();
+ return is_null($type) || Relation::class == $type->getName() ? $this : $this->query;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 执行基础查询(仅执行一次)
+ * @access protected
+ * @return void
+ */
+ protected function baseQuery(): void
+ {}
+
+ public function __call($method, $args)
+ {
+ if ($this->query) {
+ // 执行基础查询
+ $this->baseQuery();
+
+ $result = call_user_func_array([$this->query, $method], $args);
+
+ return $result === $this->query ? $this : $result;
+ }
+
+ throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
+ }
+}
diff --git a/vendor/topthink/think-orm/src/model/concern/Attribute.php b/vendor/topthink/think-orm/src/model/concern/Attribute.php
new file mode 100755
index 0000000..9e4367b
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/concern/Attribute.php
@@ -0,0 +1,651 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\concern;
+
+use InvalidArgumentException;
+use think\db\Raw;
+use think\helper\Str;
+use think\model\Relation;
+
+/**
+ * 模型数据处理
+ */
+trait Attribute
+{
+ /**
+ * 数据表主键 复合主键使用数组定义
+ * @var string|array
+ */
+ protected $pk = 'id';
+
+ /**
+ * 数据表字段信息 留空则自动获取
+ * @var array
+ */
+ protected $schema = [];
+
+ /**
+ * 当前允许写入的字段
+ * @var array
+ */
+ protected $field = [];
+
+ /**
+ * 字段自动类型转换
+ * @var array
+ */
+ protected $type = [];
+
+ /**
+ * 数据表废弃字段
+ * @var array
+ */
+ protected $disuse = [];
+
+ /**
+ * 数据表只读字段
+ * @var array
+ */
+ protected $readonly = [];
+
+ /**
+ * 当前模型数据
+ * @var array
+ */
+ private $data = [];
+
+ /**
+ * 原始数据
+ * @var array
+ */
+ private $origin = [];
+
+ /**
+ * JSON数据表字段
+ * @var array
+ */
+ protected $json = [];
+
+ /**
+ * JSON数据表字段类型
+ * @var array
+ */
+ protected $jsonType = [];
+
+ /**
+ * JSON数据取出是否需要转换为数组
+ * @var bool
+ */
+ protected $jsonAssoc = false;
+
+ /**
+ * 是否严格字段大小写
+ * @var bool
+ */
+ protected $strict = true;
+
+ /**
+ * 修改器执行记录
+ * @var array
+ */
+ private $set = [];
+
+ /**
+ * 动态获取器
+ * @var array
+ */
+ private $withAttr = [];
+
+ /**
+ * 获取模型对象的主键
+ * @access public
+ * @return string|array
+ */
+ public function getPk()
+ {
+ return $this->pk;
+ }
+
+ /**
+ * 判断一个字段名是否为主键字段
+ * @access public
+ * @param string $key 名称
+ * @return bool
+ */
+ protected function isPk(string $key): bool
+ {
+ $pk = $this->getPk();
+
+ if (is_string($pk) && $pk == $key) {
+ return true;
+ } elseif (is_array($pk) && in_array($key, $pk)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 获取模型对象的主键值
+ * @access public
+ * @return mixed
+ */
+ public function getKey()
+ {
+ $pk = $this->getPk();
+
+ if (is_string($pk) && array_key_exists($pk, $this->data)) {
+ return $this->data[$pk];
+ }
+
+ return;
+ }
+
+ /**
+ * 设置允许写入的字段
+ * @access public
+ * @param array $field 允许写入的字段
+ * @return $this
+ */
+ public function allowField(array $field)
+ {
+ $this->field = $field;
+
+ return $this;
+ }
+
+ /**
+ * 设置只读字段
+ * @access public
+ * @param array $field 只读字段
+ * @return $this
+ */
+ public function readOnly(array $field)
+ {
+ $this->readonly = $field;
+
+ return $this;
+ }
+
+ /**
+ * 获取实际的字段名
+ * @access protected
+ * @param string $name 字段名
+ * @return string
+ */
+ protected function getRealFieldName(string $name): string
+ {
+ return $this->strict ? $name : Str::snake($name);
+ }
+
+ /**
+ * 设置数据对象值
+ * @access public
+ * @param array $data 数据
+ * @param bool $set 是否调用修改器
+ * @param array $allow 允许的字段名
+ * @return $this
+ */
+ public function data(array $data, bool $set = false, array $allow = [])
+ {
+ // 清空数据
+ $this->data = [];
+
+ // 废弃字段
+ foreach ($this->disuse as $key) {
+ if (array_key_exists($key, $data)) {
+ unset($data[$key]);
+ }
+ }
+
+ if (!empty($allow)) {
+ $result = [];
+ foreach ($allow as $name) {
+ if (isset($data[$name])) {
+ $result[$name] = $data[$name];
+ }
+ }
+ $data = $result;
+ }
+
+ if ($set) {
+ // 数据对象赋值
+ $this->setAttrs($data);
+ } else {
+ $this->data = $data;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 批量追加数据对象值
+ * @access public
+ * @param array $data 数据
+ * @param bool $set 是否需要进行数据处理
+ * @return $this
+ */
+ public function appendData(array $data, bool $set = false)
+ {
+ if ($set) {
+ $this->setAttrs($data);
+ } else {
+ $this->data = array_merge($this->data, $data);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 获取对象原始数据 如果不存在指定字段返回null
+ * @access public
+ * @param string $name 字段名 留空获取全部
+ * @return mixed
+ */
+ public function getOrigin(string $name = null)
+ {
+ if (is_null($name)) {
+ return $this->origin;
+ }
+
+ return array_key_exists($name, $this->origin) ? $this->origin[$name] : null;
+ }
+
+ /**
+ * 获取对象原始数据 如果不存在指定字段返回false
+ * @access public
+ * @param string $name 字段名 留空获取全部
+ * @return mixed
+ * @throws InvalidArgumentException
+ */
+ public function getData(string $name = null)
+ {
+ if (is_null($name)) {
+ return $this->data;
+ }
+
+ $fieldName = $this->getRealFieldName($name);
+
+ if (array_key_exists($fieldName, $this->data)) {
+ return $this->data[$fieldName];
+ } elseif (array_key_exists($fieldName, $this->relation)) {
+ return $this->relation[$fieldName];
+ }
+
+ throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
+ }
+
+ /**
+ * 获取变化的数据 并排除只读数据
+ * @access public
+ * @return array
+ */
+ public function getChangedData(): array
+ {
+ $data = $this->force ? $this->data : array_udiff_assoc($this->data, $this->origin, function ($a, $b) {
+ if ((empty($a) || empty($b)) && $a !== $b) {
+ return 1;
+ }
+
+ return is_object($a) || $a != $b ? 1 : 0;
+ });
+
+ // 只读字段不允许更新
+ foreach ($this->readonly as $key => $field) {
+ if (isset($data[$field])) {
+ unset($data[$field]);
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * 直接设置数据对象值
+ * @access public
+ * @param string $name 属性名
+ * @param mixed $value 值
+ * @return void
+ */
+ public function set(string $name, $value): void
+ {
+ $name = $this->getRealFieldName($name);
+
+ $this->data[$name] = $value;
+ }
+
+ /**
+ * 通过修改器 批量设置数据对象值
+ * @access public
+ * @param array $data 数据
+ * @return void
+ */
+ public function setAttrs(array $data): void
+ {
+ // 进行数据处理
+ foreach ($data as $key => $value) {
+ $this->setAttr($key, $value, $data);
+ }
+ }
+
+ /**
+ * 通过修改器 设置数据对象值
+ * @access public
+ * @param string $name 属性名
+ * @param mixed $value 属性值
+ * @param array $data 数据
+ * @return void
+ */
+ public function setAttr(string $name, $value, array $data = []): void
+ {
+ $name = $this->getRealFieldName($name);
+
+ if (isset($this->set[$name])) {
+ return;
+ }
+
+ if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {
+ // 自动写入的时间戳字段
+ $value = $this->autoWriteTimestamp();
+ } else {
+ // 检测修改器
+ $method = 'set' . Str::studly($name) . 'Attr';
+
+ if (method_exists($this, $method)) {
+ $array = $this->data;
+
+ $value = $this->$method($value, array_merge($this->data, $data));
+
+ $this->set[$name] = true;
+ if (is_null($value) && $array !== $this->data) {
+ return;
+ }
+ } elseif (isset($this->type[$name])) {
+ // 类型转换
+ $value = $this->writeTransform($value, $this->type[$name]);
+ }
+ }
+
+ // 设置数据对象属性
+ $this->data[$name] = $value;
+ }
+
+ /**
+ * 数据写入 类型转换
+ * @access protected
+ * @param mixed $value 值
+ * @param string|array $type 要转换的类型
+ * @return mixed
+ */
+ protected function writeTransform($value, $type)
+ {
+ if (is_null($value)) {
+ return;
+ }
+
+ if ($value instanceof Raw) {
+ return $value;
+ }
+
+ if (is_array($type)) {
+ [$type, $param] = $type;
+ } elseif (strpos($type, ':')) {
+ [$type, $param] = explode(':', $type, 2);
+ }
+
+ switch ($type) {
+ case 'integer':
+ $value = (int) $value;
+ break;
+ case 'float':
+ if (empty($param)) {
+ $value = (float) $value;
+ } else {
+ $value = (float) number_format($value, (int) $param, '.', '');
+ }
+ break;
+ case 'boolean':
+ $value = (bool) $value;
+ break;
+ case 'timestamp':
+ if (!is_numeric($value)) {
+ $value = strtotime($value);
+ }
+ break;
+ case 'datetime':
+ $value = is_numeric($value) ? $value : strtotime($value);
+ $value = $this->formatDateTime('Y-m-d H:i:s.u', $value, true);
+ break;
+ case 'object':
+ if (is_object($value)) {
+ $value = json_encode($value, JSON_FORCE_OBJECT);
+ }
+ break;
+ case 'array':
+ $value = (array) $value;
+ case 'json':
+ $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE;
+ $value = json_encode($value, $option);
+ break;
+ case 'serialize':
+ $value = serialize($value);
+ break;
+ default:
+ if (is_object($value) && false !== strpos($type, '\\') && method_exists($value, '__toString')) {
+ // 对象类型
+ $value = $value->__toString();
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * 获取器 获取数据对象的值
+ * @access public
+ * @param string $name 名称
+ * @return mixed
+ * @throws InvalidArgumentException
+ */
+ public function getAttr(string $name)
+ {
+ try {
+ $relation = false;
+ $value = $this->getData($name);
+ } catch (InvalidArgumentException $e) {
+ $relation = $this->isRelationAttr($name);
+ $value = null;
+ }
+
+ return $this->getValue($name, $value, $relation);
+ }
+
+ /**
+ * 获取经过获取器处理后的数据对象的值
+ * @access protected
+ * @param string $name 字段名称
+ * @param mixed $value 字段值
+ * @param bool|string $relation 是否为关联属性或者关联名
+ * @return mixed
+ * @throws InvalidArgumentException
+ */
+ protected function getValue(string $name, $value, $relation = false)
+ {
+ // 检测属性获取器
+ $fieldName = $this->getRealFieldName($name);
+ $method = 'get' . Str::studly($name) . 'Attr';
+
+ if (isset($this->withAttr[$fieldName])) {
+ if ($relation) {
+ $value = $this->getRelationValue($relation);
+ }
+
+ if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) {
+ $value = $this->getJsonValue($fieldName, $value);
+ } else {
+ $closure = $this->withAttr[$fieldName];
+ $value = $closure($value, $this->data);
+ }
+ } elseif (method_exists($this, $method)) {
+ if ($relation) {
+ $value = $this->getRelationValue($relation);
+ }
+
+ $value = $this->$method($value, $this->data);
+ } elseif (isset($this->type[$fieldName])) {
+ // 类型转换
+ $value = $this->readTransform($value, $this->type[$fieldName]);
+ } elseif ($this->autoWriteTimestamp && in_array($fieldName, [$this->createTime, $this->updateTime])) {
+ $value = $this->getTimestampValue($value);
+ } elseif ($relation) {
+ $value = $this->getRelationValue($relation);
+ // 保存关联对象值
+ $this->relation[$name] = $value;
+ }
+
+ return $value;
+ }
+
+ /**
+ * 获取JSON字段属性值
+ * @access protected
+ * @param string $name 属性名
+ * @param mixed $value JSON数据
+ * @return mixed
+ */
+ protected function getJsonValue($name, $value)
+ {
+ foreach ($this->withAttr[$name] as $key => $closure) {
+ if ($this->jsonAssoc) {
+ $value[$key] = $closure($value[$key], $value);
+ } else {
+ $value->$key = $closure($value->$key, $value);
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * 获取关联属性值
+ * @access protected
+ * @param string $relation 关联名
+ * @return mixed
+ */
+ protected function getRelationValue(string $relation)
+ {
+ $modelRelation = $this->$relation();
+
+ return $modelRelation instanceof Relation ? $this->getRelationData($modelRelation) : null;
+ }
+
+ /**
+ * 数据读取 类型转换
+ * @access protected
+ * @param mixed $value 值
+ * @param string|array $type 要转换的类型
+ * @return mixed
+ */
+ protected function readTransform($value, $type)
+ {
+ if (is_null($value)) {
+ return;
+ }
+
+ if (is_array($type)) {
+ [$type, $param] = $type;
+ } elseif (strpos($type, ':')) {
+ [$type, $param] = explode(':', $type, 2);
+ }
+
+ switch ($type) {
+ case 'integer':
+ $value = (int) $value;
+ break;
+ case 'float':
+ if (empty($param)) {
+ $value = (float) $value;
+ } else {
+ $value = (float) number_format($value, (int) $param, '.', '');
+ }
+ break;
+ case 'boolean':
+ $value = (bool) $value;
+ break;
+ case 'timestamp':
+ if (!is_null($value)) {
+ $format = !empty($param) ? $param : $this->dateFormat;
+ $value = $this->formatDateTime($format, $value, true);
+ }
+ break;
+ case 'datetime':
+ if (!is_null($value)) {
+ $format = !empty($param) ? $param : $this->dateFormat;
+ $value = $this->formatDateTime($format, $value);
+ }
+ break;
+ case 'json':
+ $value = json_decode($value, true);
+ break;
+ case 'array':
+ $value = empty($value) ? [] : json_decode($value, true);
+ break;
+ case 'object':
+ $value = empty($value) ? new \stdClass() : json_decode($value);
+ break;
+ case 'serialize':
+ try {
+ $value = unserialize($value);
+ } catch (\Exception $e) {
+ $value = null;
+ }
+ break;
+ default:
+ if (false !== strpos($type, '\\')) {
+ // 对象类型
+ $value = new $type($value);
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * 设置数据字段获取器
+ * @access public
+ * @param string|array $name 字段名
+ * @param callable $callback 闭包获取器
+ * @return $this
+ */
+ public function withAttribute($name, callable $callback = null)
+ {
+ if (is_array($name)) {
+ foreach ($name as $key => $val) {
+ $this->withAttribute($key, $val);
+ }
+ } else {
+ $name = $this->getRealFieldName($name);
+
+ if (strpos($name, '.')) {
+ [$name, $key] = explode('.', $name);
+
+ $this->withAttr[$name][$key] = $callback;
+ } else {
+ $this->withAttr[$name] = $callback;
+ }
+ }
+
+ return $this;
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/concern/Conversion.php b/vendor/topthink/think-orm/src/model/concern/Conversion.php
new file mode 100755
index 0000000..e12d0af
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/concern/Conversion.php
@@ -0,0 +1,278 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\concern;
+
+use think\Collection;
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
+use think\Model;
+use think\model\Collection as ModelCollection;
+use think\model\relation\OneToOne;
+
+/**
+ * 模型数据转换处理
+ */
+trait Conversion
+{
+ /**
+ * 数据输出显示的属性
+ * @var array
+ */
+ protected $visible = [];
+
+ /**
+ * 数据输出隐藏的属性
+ * @var array
+ */
+ protected $hidden = [];
+
+ /**
+ * 数据输出需要追加的属性
+ * @var array
+ */
+ protected $append = [];
+
+ /**
+ * 数据集对象名
+ * @var string
+ */
+ protected $resultSetType;
+
+ /**
+ * 设置需要附加的输出属性
+ * @access public
+ * @param array $append 属性列表
+ * @return $this
+ */
+ public function append(array $append = [])
+ {
+ $this->append = $append;
+
+ return $this;
+ }
+
+ /**
+ * 设置附加关联对象的属性
+ * @access public
+ * @param string $attr 关联属性
+ * @param string|array $append 追加属性名
+ * @return $this
+ * @throws Exception
+ */
+ public function appendRelationAttr(string $attr, array $append)
+ {
+ $relation = Str::camel($attr);
+
+ if (isset($this->relation[$relation])) {
+ $model = $this->relation[$relation];
+ } else {
+ $model = $this->getRelationData($this->$relation());
+ }
+
+ if ($model instanceof Model) {
+ foreach ($append as $key => $attr) {
+ $key = is_numeric($key) ? $attr : $key;
+ if (isset($this->data[$key])) {
+ throw new Exception('bind attr has exists:' . $key);
+ }
+
+ $this->data[$key] = $model->$attr;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 设置需要隐藏的输出属性
+ * @access public
+ * @param array $hidden 属性列表
+ * @return $this
+ */
+ public function hidden(array $hidden = [])
+ {
+ $this->hidden = $hidden;
+
+ return $this;
+ }
+
+ /**
+ * 设置需要输出的属性
+ * @access public
+ * @param array $visible
+ * @return $this
+ */
+ public function visible(array $visible = [])
+ {
+ $this->visible = $visible;
+
+ return $this;
+ }
+
+ /**
+ * 转换当前模型对象为数组
+ * @access public
+ * @return array
+ */
+ public function toArray(): array
+ {
+ $item = [];
+ $hasVisible = false;
+
+ foreach ($this->visible as $key => $val) {
+ if (is_string($val)) {
+ if (strpos($val, '.')) {
+ [$relation, $name] = explode('.', $val);
+ $this->visible[$relation][] = $name;
+ } else {
+ $this->visible[$val] = true;
+ $hasVisible = true;
+ }
+ unset($this->visible[$key]);
+ }
+ }
+
+ foreach ($this->hidden as $key => $val) {
+ if (is_string($val)) {
+ if (strpos($val, '.')) {
+ [$relation, $name] = explode('.', $val);
+ $this->hidden[$relation][] = $name;
+ } else {
+ $this->hidden[$val] = true;
+ }
+ unset($this->hidden[$key]);
+ }
+ }
+
+ // 合并关联数据
+ $data = array_merge($this->data, $this->relation);
+
+ foreach ($data as $key => $val) {
+ if ($val instanceof Model || $val instanceof ModelCollection) {
+ // 关联模型对象
+ if (isset($this->visible[$key]) && is_array($this->visible[$key])) {
+ $val->visible($this->visible[$key]);
+ } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {
+ $val->hidden($this->hidden[$key]);
+ }
+ // 关联模型对象
+ if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {
+ $item[$key] = $val->toArray();
+ }
+ } elseif (isset($this->visible[$key])) {
+ $item[$key] = $this->getAttr($key);
+ } elseif (!isset($this->hidden[$key]) && !$hasVisible) {
+ $item[$key] = $this->getAttr($key);
+ }
+ }
+
+ // 追加属性(必须定义获取器)
+ foreach ($this->append as $key => $name) {
+ $this->appendAttrToArray($item, $key, $name);
+ }
+
+ return $item;
+ }
+
+ protected function appendAttrToArray(array &$item, $key, $name)
+ {
+ if (is_array($name)) {
+ // 追加关联对象属性
+ $relation = $this->getRelation($key, true);
+ $item[$key] = $relation ? $relation->append($name)
+ ->toArray() : [];
+ } elseif (strpos($name, '.')) {
+ [$key, $attr] = explode('.', $name);
+ // 追加关联对象属性
+ $relation = $this->getRelation($key, true);
+ $item[$key] = $relation ? $relation->append([$attr])
+ ->toArray() : [];
+ } else {
+ $value = $this->getAttr($name);
+ $item[$name] = $value;
+
+ $this->getBindAttr($name, $value, $item);
+ }
+ }
+
+ protected function getBindAttr(string $name, $value, array &$item = [])
+ {
+ $relation = $this->isRelationAttr($name);
+ if (!$relation) {
+ return false;
+ }
+
+ $modelRelation = $this->$relation();
+
+ if ($modelRelation instanceof OneToOne) {
+ $bindAttr = $modelRelation->getBindAttr();
+
+ if (!empty($bindAttr)) {
+ unset($item[$name]);
+ }
+
+ foreach ($bindAttr as $key => $attr) {
+ $key = is_numeric($key) ? $attr : $key;
+
+ if (isset($item[$key])) {
+ throw new Exception('bind attr has exists:' . $key);
+ }
+
+ $item[$key] = $value ? $value->getAttr($attr) : null;
+ }
+ }
+ }
+
+ /**
+ * 转换当前模型对象为JSON字符串
+ * @access public
+ * @param integer $options json参数
+ * @return string
+ */
+ public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
+ {
+ return json_encode($this->toArray(), $options);
+ }
+
+ public function __toString()
+ {
+ return $this->toJson();
+ }
+
+ // JsonSerializable
+ public function jsonSerialize()
+ {
+ return $this->toArray();
+ }
+
+ /**
+ * 转换数据集为数据集对象
+ * @access public
+ * @param array|Collection $collection 数据集
+ * @param string $resultSetType 数据集类
+ * @return Collection
+ */
+ public function toCollection(iterable $collection = [], string $resultSetType = null): Collection
+ {
+ $resultSetType = $resultSetType ?: $this->resultSetType;
+
+ if ($resultSetType && false !== strpos($resultSetType, '\\')) {
+ $collection = new $resultSetType($collection);
+ } else {
+ $collection = new ModelCollection($collection);
+ }
+
+ return $collection;
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/concern/ModelEvent.php b/vendor/topthink/think-orm/src/model/concern/ModelEvent.php
new file mode 100755
index 0000000..f560379
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/concern/ModelEvent.php
@@ -0,0 +1,88 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\concern;
+
+use think\db\exception\ModelEventException;
+use think\helper\Str;
+
+/**
+ * 模型事件处理
+ */
+trait ModelEvent
+{
+
+ /**
+ * Event对象
+ * @var object
+ */
+ protected static $event;
+
+ /**
+ * 是否需要事件响应
+ * @var bool
+ */
+ protected $withEvent = true;
+
+ /**
+ * 设置Event对象
+ * @access public
+ * @param object $event Event对象
+ * @return void
+ */
+ public static function setEvent($event)
+ {
+ self::$event = $event;
+ }
+
+ /**
+ * 当前操作的事件响应
+ * @access protected
+ * @param bool $event 是否需要事件响应
+ * @return $this
+ */
+ public function withEvent(bool $event)
+ {
+ $this->withEvent = $event;
+ return $this;
+ }
+
+ /**
+ * 触发事件
+ * @access protected
+ * @param string $event 事件名
+ * @return bool
+ */
+ protected function trigger(string $event): bool
+ {
+ if (!$this->withEvent) {
+ return true;
+ }
+
+ $call = 'on' . Str::studly($event);
+
+ try {
+ if (method_exists(static::class, $call)) {
+ $result = call_user_func([static::class, $call], $this);
+ } elseif (is_object(self::$event) && method_exists(self::$event, 'trigger')) {
+ $result = self::$event->trigger(static::class . '.' . $event, $this);
+ $result = empty($result) ? true : end($result);
+ } else {
+ $result = true;
+ }
+
+ return false === $result ? false : true;
+ } catch (ModelEventException $e) {
+ return false;
+ }
+ }
+}
diff --git a/vendor/topthink/think-orm/src/model/concern/OptimLock.php b/vendor/topthink/think-orm/src/model/concern/OptimLock.php
new file mode 100755
index 0000000..5e61318
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/concern/OptimLock.php
@@ -0,0 +1,85 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\concern;
+
+use think\db\exception\DbException as Exception;
+
+/**
+ * 乐观锁
+ */
+trait OptimLock
+{
+ protected function getOptimLockField()
+ {
+ return property_exists($this, 'optimLock') && isset($this->optimLock) ? $this->optimLock : 'lock_version';
+ }
+
+ /**
+ * 数据检查
+ * @access protected
+ * @return void
+ */
+ protected function checkData(): void
+ {
+ $this->isExists() ? $this->updateLockVersion() : $this->recordLockVersion();
+ }
+
+ /**
+ * 记录乐观锁
+ * @access protected
+ * @return void
+ */
+ protected function recordLockVersion(): void
+ {
+ $optimLock = $this->getOptimLockField();
+
+ if ($optimLock) {
+ $this->set($optimLock, 0);
+ }
+ }
+
+ /**
+ * 更新乐观锁
+ * @access protected
+ * @return void
+ */
+ protected function updateLockVersion(): void
+ {
+ $optimLock = $this->getOptimLockField();
+
+ if ($optimLock && $lockVer = $this->getOrigin($optimLock)) {
+ // 更新乐观锁
+ $this->set($optimLock, $lockVer + 1);
+ }
+ }
+
+ public function getWhere()
+ {
+ $where = parent::getWhere();
+ $optimLock = $this->getOptimLockField();
+
+ if ($optimLock && $lockVer = $this->getOrigin($optimLock)) {
+ $where[] = [$optimLock, '=', $lockVer];
+ }
+
+ return $where;
+ }
+
+ protected function checkResult($result): void
+ {
+ if (!$result) {
+ throw new Exception('record has update');
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/concern/RelationShip.php b/vendor/topthink/think-orm/src/model/concern/RelationShip.php
new file mode 100755
index 0000000..f3da1c4
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/concern/RelationShip.php
@@ -0,0 +1,841 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\concern;
+
+use Closure;
+use think\Collection;
+use think\db\BaseQuery as Query;
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
+use think\Model;
+use think\model\Relation;
+use think\model\relation\BelongsTo;
+use think\model\relation\BelongsToMany;
+use think\model\relation\HasMany;
+use think\model\relation\HasManyThrough;
+use think\model\relation\HasOne;
+use think\model\relation\HasOneThrough;
+use think\model\relation\MorphMany;
+use think\model\relation\MorphOne;
+use think\model\relation\MorphTo;
+use think\model\relation\MorphToMany;
+use think\model\relation\OneToOne;
+
+/**
+ * 模型关联处理
+ */
+trait RelationShip
+{
+ /**
+ * 父关联模型对象
+ * @var object
+ */
+ private $parent;
+
+ /**
+ * 模型关联数据
+ * @var array
+ */
+ private $relation = [];
+
+ /**
+ * 关联写入定义信息
+ * @var array
+ */
+ private $together = [];
+
+ /**
+ * 关联自动写入信息
+ * @var array
+ */
+ protected $relationWrite = [];
+
+ /**
+ * 设置父关联对象
+ * @access public
+ * @param Model $model 模型对象
+ * @return $this
+ */
+ public function setParent(Model $model)
+ {
+ $this->parent = $model;
+
+ return $this;
+ }
+
+ /**
+ * 获取父关联对象
+ * @access public
+ * @return Model
+ */
+ public function getParent(): Model
+ {
+ return $this->parent;
+ }
+
+ /**
+ * 获取当前模型的关联模型数据
+ * @access public
+ * @param string $name 关联方法名
+ * @param bool $auto 不存在是否自动获取
+ * @return mixed
+ */
+ public function getRelation(string $name = null, bool $auto = false)
+ {
+ if (is_null($name)) {
+ return $this->relation;
+ }
+
+ if (array_key_exists($name, $this->relation)) {
+ return $this->relation[$name];
+ } elseif ($auto) {
+ $relation = Str::camel($name);
+ return $this->getRelationValue($relation);
+ }
+ }
+
+ /**
+ * 设置关联数据对象值
+ * @access public
+ * @param string $name 属性名
+ * @param mixed $value 属性值
+ * @param array $data 数据
+ * @return $this
+ */
+ public function setRelation(string $name, $value, array $data = [])
+ {
+ // 检测修改器
+ $method = 'set' . Str::studly($name) . 'Attr';
+
+ if (method_exists($this, $method)) {
+ $value = $this->$method($value, array_merge($this->data, $data));
+ }
+
+ $this->relation[$this->getRealFieldName($name)] = $value;
+
+ return $this;
+ }
+
+ /**
+ * 查询当前模型的关联数据
+ * @access public
+ * @param array $relations 关联名
+ * @param array $withRelationAttr 关联获取器
+ * @return void
+ */
+ public function relationQuery(array $relations, array $withRelationAttr = []): void
+ {
+ foreach ($relations as $key => $relation) {
+ $subRelation = '';
+ $closure = null;
+
+ if ($relation instanceof Closure) {
+ // 支持闭包查询过滤关联条件
+ $closure = $relation;
+ $relation = $key;
+ }
+
+ if (is_array($relation)) {
+ $subRelation = $relation;
+ $relation = $key;
+ } elseif (strpos($relation, '.')) {
+ [$relation, $subRelation] = explode('.', $relation, 2);
+ }
+
+ $method = Str::camel($relation);
+ $relationName = Str::snake($relation);
+
+ $relationResult = $this->$method();
+
+ if (isset($withRelationAttr[$relationName])) {
+ $relationResult->withAttr($withRelationAttr[$relationName]);
+ }
+
+ $this->relation[$relation] = $relationResult->getRelation($subRelation, $closure);
+ }
+ }
+
+ /**
+ * 关联数据写入
+ * @access public
+ * @param array $relation 关联
+ * @return $this
+ */
+ public function together(array $relation)
+ {
+ $this->together = $relation;
+
+ $this->checkAutoRelationWrite();
+
+ return $this;
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $relation 关联方法名
+ * @param mixed $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public static function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query
+ {
+ return (new static())
+ ->$relation()
+ ->has($operator, $count, $id, $joinType, $query);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $relation 关联方法名
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public static function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '', Query $query = null): Query
+ {
+ return (new static())
+ ->$relation()
+ ->hasWhere($where, $fields, $joinType, $query);
+ }
+
+ /**
+ * 预载入关联查询 JOIN方式
+ * @access public
+ * @param Query $query Query对象
+ * @param string $relation 关联方法名
+ * @param mixed $field 字段
+ * @param string $joinType JOIN类型
+ * @param Closure $closure 闭包
+ * @param bool $first
+ * @return bool
+ */
+ public function eagerly(Query $query, string $relation, $field, string $joinType = '', Closure $closure = null, bool $first = false): bool
+ {
+ $relation = Str::camel($relation);
+ $class = $this->$relation();
+
+ if ($class instanceof OneToOne) {
+ $class->eagerly($query, $relation, $field, $joinType, $closure, $first);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 预载入关联查询 返回数据集
+ * @access public
+ * @param array $resultSet 数据集
+ * @param string $relation 关联名
+ * @param array $withRelationAttr 关联获取器
+ * @param bool $join 是否为JOIN方式
+ * @param mixed $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void
+ {
+ foreach ($relations as $key => $relation) {
+ $subRelation = [];
+ $closure = null;
+
+ if ($relation instanceof Closure) {
+ $closure = $relation;
+ $relation = $key;
+ }
+
+ if (is_array($relation)) {
+ $subRelation = $relation;
+ $relation = $key;
+ } elseif (strpos($relation, '.')) {
+ [$relation, $subRelation] = explode('.', $relation, 2);
+
+ $subRelation = [$subRelation];
+ }
+
+ $relationName = $relation;
+ $relation = Str::camel($relation);
+
+ $relationResult = $this->$relation();
+
+ if (isset($withRelationAttr[$relationName])) {
+ $relationResult->withAttr($withRelationAttr[$relationName]);
+ }
+
+ if (is_scalar($cache)) {
+ $relationCache = [$cache];
+ } else {
+ $relationCache = $cache[$relationName] ?? $cache;
+ }
+
+ $relationResult->eagerlyResultSet($resultSet, $relationName, $subRelation, $closure, $relationCache, $join);
+ }
+ }
+
+ /**
+ * 预载入关联查询 返回模型对象
+ * @access public
+ * @param Model $result 数据对象
+ * @param array $relations 关联
+ * @param array $withRelationAttr 关联获取器
+ * @param bool $join 是否为JOIN方式
+ * @param mixed $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void
+ {
+ foreach ($relations as $key => $relation) {
+ $subRelation = [];
+ $closure = null;
+
+ if ($relation instanceof Closure) {
+ $closure = $relation;
+ $relation = $key;
+ }
+
+ if (is_array($relation)) {
+ $subRelation = $relation;
+ $relation = $key;
+ } elseif (strpos($relation, '.')) {
+ [$relation, $subRelation] = explode('.', $relation, 2);
+
+ $subRelation = [$subRelation];
+ }
+
+ $relationName = $relation;
+ $relation = Str::camel($relation);
+
+ $relationResult = $this->$relation();
+
+ if (isset($withRelationAttr[$relationName])) {
+ $relationResult->withAttr($withRelationAttr[$relationName]);
+ }
+
+ if (is_scalar($cache)) {
+ $relationCache = [$cache];
+ } else {
+ $relationCache = $cache[$relationName] ?? [];
+ }
+
+ $relationResult->eagerlyResult($result, $relationName, $subRelation, $closure, $relationCache, $join);
+ }
+ }
+
+ /**
+ * 绑定(一对一)关联属性到当前模型
+ * @access protected
+ * @param string $relation 关联名称
+ * @param array $attrs 绑定属性
+ * @return $this
+ * @throws Exception
+ */
+ public function bindAttr(string $relation, array $attrs = [])
+ {
+ $relation = $this->getRelation($relation);
+
+ foreach ($attrs as $key => $attr) {
+ $key = is_numeric($key) ? $attr : $key;
+ $value = $this->getOrigin($key);
+
+ if (!is_null($value)) {
+ throw new Exception('bind attr has exists:' . $key);
+ }
+
+ $this->set($key, $relation ? $relation->$attr : null);
+ }
+
+ return $this;
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Query $query 查询对象
+ * @param array $relations 关联名
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param bool $useSubQuery 子查询
+ * @return void
+ */
+ public function relationCount(Query $query, array $relations, string $aggregate = 'sum', string $field = '*', bool $useSubQuery = true): void
+ {
+ foreach ($relations as $key => $relation) {
+ $closure = $name = null;
+
+ if ($relation instanceof Closure) {
+ $closure = $relation;
+ $relation = $key;
+ } elseif (is_string($key)) {
+ $name = $relation;
+ $relation = $key;
+ }
+
+ $relation = Str::camel($relation);
+
+ if ($useSubQuery) {
+ $count = $this->$relation()->getRelationCountQuery($closure, $aggregate, $field, $name);
+ } else {
+ $count = $this->$relation()->relationCount($this, $closure, $aggregate, $field, $name);
+ }
+
+ if (empty($name)) {
+ $name = Str::snake($relation) . '_' . $aggregate;
+ }
+
+ if ($useSubQuery) {
+ $query->field(['(' . $count . ')' => $name]);
+ } else {
+ $this->setAttr($name, $count);
+ }
+ }
+ }
+
+ /**
+ * HAS ONE 关联定义
+ * @access public
+ * @param string $model 模型名
+ * @param string $foreignKey 关联外键
+ * @param string $localKey 当前主键
+ * @return HasOne
+ */
+ public function hasOne(string $model, string $foreignKey = '', string $localKey = ''): HasOne
+ {
+ // 记录当前关联信息
+ $model = $this->parseModel($model);
+ $localKey = $localKey ?: $this->getPk();
+ $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
+
+ return new HasOne($this, $model, $foreignKey, $localKey);
+ }
+
+ /**
+ * BELONGS TO 关联定义
+ * @access public
+ * @param string $model 模型名
+ * @param string $foreignKey 关联外键
+ * @param string $localKey 关联主键
+ * @return BelongsTo
+ */
+ public function belongsTo(string $model, string $foreignKey = '', string $localKey = ''): BelongsTo
+ {
+ // 记录当前关联信息
+ $model = $this->parseModel($model);
+ $foreignKey = $foreignKey ?: $this->getForeignKey((new $model)->getName());
+ $localKey = $localKey ?: (new $model)->getPk();
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
+ $relation = Str::snake($trace[1]['function']);
+
+ return new BelongsTo($this, $model, $foreignKey, $localKey, $relation);
+ }
+
+ /**
+ * HAS MANY 关联定义
+ * @access public
+ * @param string $model 模型名
+ * @param string $foreignKey 关联外键
+ * @param string $localKey 当前主键
+ * @return HasMany
+ */
+ public function hasMany(string $model, string $foreignKey = '', string $localKey = ''): HasMany
+ {
+ // 记录当前关联信息
+ $model = $this->parseModel($model);
+ $localKey = $localKey ?: $this->getPk();
+ $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
+
+ return new HasMany($this, $model, $foreignKey, $localKey);
+ }
+
+ /**
+ * HAS MANY 远程关联定义
+ * @access public
+ * @param string $model 模型名
+ * @param string $through 中间模型名
+ * @param string $foreignKey 关联外键
+ * @param string $throughKey 关联外键
+ * @param string $localKey 当前主键
+ * @param string $throughPk 中间表主键
+ * @return HasManyThrough
+ */
+ public function hasManyThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasManyThrough
+ {
+ // 记录当前关联信息
+ $model = $this->parseModel($model);
+ $through = $this->parseModel($through);
+ $localKey = $localKey ?: $this->getPk();
+ $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
+ $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName());
+ $throughPk = $throughPk ?: (new $through)->getPk();
+
+ return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk);
+ }
+
+ /**
+ * HAS ONE 远程关联定义
+ * @access public
+ * @param string $model 模型名
+ * @param string $through 中间模型名
+ * @param string $foreignKey 关联外键
+ * @param string $throughKey 关联外键
+ * @param string $localKey 当前主键
+ * @param string $throughPk 中间表主键
+ * @return HasOneThrough
+ */
+ public function hasOneThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasOneThrough
+ {
+ // 记录当前关联信息
+ $model = $this->parseModel($model);
+ $through = $this->parseModel($through);
+ $localKey = $localKey ?: $this->getPk();
+ $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
+ $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName());
+ $throughPk = $throughPk ?: (new $through)->getPk();
+
+ return new HasOneThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk);
+ }
+
+ /**
+ * BELONGS TO MANY 关联定义
+ * @access public
+ * @param string $model 模型名
+ * @param string $middle 中间表/模型名
+ * @param string $foreignKey 关联外键
+ * @param string $localKey 当前模型关联键
+ * @return BelongsToMany
+ */
+ public function belongsToMany(string $model, string $middle = '', string $foreignKey = '', string $localKey = ''): BelongsToMany
+ {
+ // 记录当前关联信息
+ $model = $this->parseModel($model);
+ $name = Str::snake(class_basename($model));
+ $middle = $middle ?: Str::snake($this->name) . '_' . $name;
+ $foreignKey = $foreignKey ?: $name . '_id';
+ $localKey = $localKey ?: $this->getForeignKey($this->name);
+
+ return new BelongsToMany($this, $model, $middle, $foreignKey, $localKey);
+ }
+
+ /**
+ * MORPH One 关联定义
+ * @access public
+ * @param string $model 模型名
+ * @param string|array $morph 多态字段信息
+ * @param string $type 多态类型
+ * @return MorphOne
+ */
+ public function morphOne(string $model, $morph = null, string $type = ''): MorphOne
+ {
+ // 记录当前关联信息
+ $model = $this->parseModel($model);
+
+ if (is_null($morph)) {
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
+ $morph = Str::snake($trace[1]['function']);
+ }
+
+ if (is_array($morph)) {
+ [$morphType, $foreignKey] = $morph;
+ } else {
+ $morphType = $morph . '_type';
+ $foreignKey = $morph . '_id';
+ }
+
+ $type = $type ?: get_class($this);
+
+ return new MorphOne($this, $model, $foreignKey, $morphType, $type);
+ }
+
+ /**
+ * MORPH MANY 关联定义
+ * @access public
+ * @param string $model 模型名
+ * @param string|array $morph 多态字段信息
+ * @param string $type 多态类型
+ * @return MorphMany
+ */
+ public function morphMany(string $model, $morph = null, string $type = ''): MorphMany
+ {
+ // 记录当前关联信息
+ $model = $this->parseModel($model);
+
+ if (is_null($morph)) {
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
+ $morph = Str::snake($trace[1]['function']);
+ }
+
+ $type = $type ?: get_class($this);
+
+ if (is_array($morph)) {
+ [$morphType, $foreignKey] = $morph;
+ } else {
+ $morphType = $morph . '_type';
+ $foreignKey = $morph . '_id';
+ }
+
+ return new MorphMany($this, $model, $foreignKey, $morphType, $type);
+ }
+
+ /**
+ * MORPH TO 关联定义
+ * @access public
+ * @param string|array $morph 多态字段信息
+ * @param array $alias 多态别名定义
+ * @return MorphTo
+ */
+ public function morphTo($morph = null, array $alias = []): MorphTo
+ {
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
+ $relation = Str::snake($trace[1]['function']);
+
+ if (is_null($morph)) {
+ $morph = $relation;
+ }
+
+ // 记录当前关联信息
+ if (is_array($morph)) {
+ [$morphType, $foreignKey] = $morph;
+ } else {
+ $morphType = $morph . '_type';
+ $foreignKey = $morph . '_id';
+ }
+
+ return new MorphTo($this, $morphType, $foreignKey, $alias, $relation);
+ }
+
+ /**
+ * MORPH TO MANY关联定义
+ * @access public
+ * @param string $model 模型名
+ * @param string $middle 中间表名/模型名
+ * @param string|array $morph 多态字段信息
+ * @param string $localKey 当前模型关联键
+ * @return MorphToMany
+ */
+ public function morphToMany(string $model, string $middle, $morph = null, string $localKey = null): MorphToMany
+ {
+ if (is_null($morph)) {
+ $morph = $middle;
+ }
+
+ // 记录当前关联信息
+ if (is_array($morph)) {
+ [$morphType, $morphKey] = $morph;
+ } else {
+ $morphType = $morph . '_type';
+ $morphKey = $morph . '_id';
+ }
+
+ $model = $this->parseModel($model);
+ $name = Str::snake(class_basename($model));
+ $localKey = $localKey ?: $this->getForeignKey($name);
+
+ return new MorphToMany($this, $model, $middle, $morphType, $morphKey, $localKey);
+ }
+
+ /**
+ * MORPH BY MANY关联定义
+ * @access public
+ * @param string $model 模型名
+ * @param string $middle 中间表名/模型名
+ * @param string|array $morph 多态字段信息
+ * @param string $foreignKey 关联外键
+ * @return MorphToMany
+ */
+ public function morphByMany(string $model, string $middle, $morph = null, string $foreignKey = null): MorphToMany
+ {
+ if (is_null($morph)) {
+ $morph = $middle;
+ }
+
+ // 记录当前关联信息
+ if (is_array($morph)) {
+ [$morphType, $morphKey] = $morph;
+ } else {
+ $morphType = $morph . '_type';
+ $morphKey = $morph . '_id';
+ }
+
+ $model = $this->parseModel($model);
+ $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
+
+ return new MorphToMany($this, $model, $middle, $morphType, $morphKey, $foreignKey, true);
+ }
+
+ /**
+ * 解析模型的完整命名空间
+ * @access protected
+ * @param string $model 模型名(或者完整类名)
+ * @return string
+ */
+ protected function parseModel(string $model): string
+ {
+ if (false === strpos($model, '\\')) {
+ $path = explode('\\', static::class);
+ array_pop($path);
+ array_push($path, Str::studly($model));
+ $model = implode('\\', $path);
+ }
+
+ return $model;
+ }
+
+ /**
+ * 获取模型的默认外键名
+ * @access protected
+ * @param string $name 模型名
+ * @return string
+ */
+ protected function getForeignKey(string $name): string
+ {
+ if (strpos($name, '\\')) {
+ $name = class_basename($name);
+ }
+
+ return Str::snake($name) . '_id';
+ }
+
+ /**
+ * 检查属性是否为关联属性 如果是则返回关联方法名
+ * @access protected
+ * @param string $attr 关联属性名
+ * @return string|false
+ */
+ protected function isRelationAttr(string $attr)
+ {
+ $relation = Str::camel($attr);
+
+ if ((method_exists($this, $relation) && !method_exists('think\Model', $relation)) || isset(static::$macro[static::class][$relation])) {
+ return $relation;
+ }
+
+ return false;
+ }
+
+ /**
+ * 智能获取关联模型数据
+ * @access protected
+ * @param Relation $modelRelation 模型关联对象
+ * @return mixed
+ */
+ protected function getRelationData(Relation $modelRelation)
+ {
+ if ($this->parent && !$modelRelation->isSelfRelation()
+ && get_class($this->parent) == get_class($modelRelation->getModel())) {
+ return $this->parent;
+ }
+
+ // 获取关联数据
+ return $modelRelation->getRelation();
+ }
+
+ /**
+ * 关联数据自动写入检查
+ * @access protected
+ * @return void
+ */
+ protected function checkAutoRelationWrite(): void
+ {
+ foreach ($this->together as $key => $name) {
+ if (is_array($name)) {
+ if (key($name) === 0) {
+ $this->relationWrite[$key] = [];
+ // 绑定关联属性
+ foreach ($name as $val) {
+ if (isset($this->data[$val])) {
+ $this->relationWrite[$key][$val] = $this->data[$val];
+ }
+ }
+ } else {
+ // 直接传入关联数据
+ $this->relationWrite[$key] = $name;
+ }
+ } elseif (isset($this->relation[$name])) {
+ $this->relationWrite[$name] = $this->relation[$name];
+ } elseif (isset($this->data[$name])) {
+ $this->relationWrite[$name] = $this->data[$name];
+ unset($this->data[$name]);
+ }
+ }
+ }
+
+ /**
+ * 自动关联数据更新(针对一对一关联)
+ * @access protected
+ * @return void
+ */
+ protected function autoRelationUpdate(): void
+ {
+ foreach ($this->relationWrite as $name => $val) {
+ if ($val instanceof Model) {
+ $val->exists(true)->save();
+ } else {
+ $model = $this->getRelation($name, true);
+
+ if ($model instanceof Model) {
+ $model->exists(true)->save($val);
+ }
+ }
+ }
+ }
+
+ /**
+ * 自动关联数据写入(针对一对一关联)
+ * @access protected
+ * @return void
+ */
+ protected function autoRelationInsert(): void
+ {
+ foreach ($this->relationWrite as $name => $val) {
+ $method = Str::camel($name);
+ $this->$method()->save($val);
+ }
+ }
+
+ /**
+ * 自动关联数据删除(支持一对一及一对多关联)
+ * @access protected
+ * @return void
+ */
+ protected function autoRelationDelete(): void
+ {
+ foreach ($this->relationWrite as $key => $name) {
+ $name = is_numeric($key) ? $name : $key;
+ $result = $this->getRelation($name, true);
+
+ if ($result instanceof Model) {
+ $result->delete();
+ } elseif ($result instanceof Collection) {
+ foreach ($result as $model) {
+ $model->delete();
+ }
+ }
+ }
+ }
+
+ /**
+ * 移除当前模型的关联属性
+ * @access public
+ * @return $this
+ */
+ public function removeRelation()
+ {
+ $this->relation = [];
+ return $this;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/model/concern/SoftDelete.php b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php
new file mode 100755
index 0000000..ce5d392
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php
@@ -0,0 +1,248 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\concern;
+
+use think\db\BaseQuery as Query;
+use think\Model;
+
+/**
+ * 数据软删除
+ * @mixin Model
+ */
+trait SoftDelete
+{
+ /**
+ * 是否包含软删除数据
+ * @var bool
+ */
+ protected $withTrashed = false;
+
+ /**
+ * 判断当前实例是否被软删除
+ * @access public
+ * @return bool
+ */
+ public function trashed(): bool
+ {
+ $field = $this->getDeleteTimeField();
+
+ if ($field && !empty($this->getOrigin($field))) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 查询软删除数据
+ * @access public
+ * @return Query
+ */
+ public static function withTrashed(): Query
+ {
+ $model = new static();
+
+ return $model->withTrashedData(true)->db();
+ }
+
+ /**
+ * 是否包含软删除数据
+ * @access protected
+ * @param bool $withTrashed 是否包含软删除数据
+ * @return $this
+ */
+ protected function withTrashedData(bool $withTrashed)
+ {
+ $this->withTrashed = $withTrashed;
+ return $this;
+ }
+
+ /**
+ * 只查询软删除数据
+ * @access public
+ * @return Query
+ */
+ public static function onlyTrashed(): Query
+ {
+ $model = new static();
+ $field = $model->getDeleteTimeField(true);
+
+ if ($field) {
+ return $model
+ ->db()
+ ->useSoftDelete($field, $model->getWithTrashedExp());
+ }
+
+ return $model->db();
+ }
+
+ /**
+ * 获取软删除数据的查询条件
+ * @access protected
+ * @return array
+ */
+ protected function getWithTrashedExp(): array
+ {
+ return is_null($this->defaultSoftDelete) ? ['notnull', ''] : ['<>', $this->defaultSoftDelete];
+ }
+
+ /**
+ * 删除当前的记录
+ * @access public
+ * @return bool
+ */
+ public function delete(): bool
+ {
+ if (!$this->isExists() || $this->isEmpty() || false === $this->trigger('BeforeDelete')) {
+ return false;
+ }
+
+ $name = $this->getDeleteTimeField();
+
+ if ($name && !$this->isForce()) {
+ // 软删除
+ $this->set($name, $this->autoWriteTimestamp($name));
+
+ $result = $this->exists()->withEvent(false)->save();
+
+ $this->withEvent(true);
+ } else {
+ // 读取更新条件
+ $where = $this->getWhere();
+
+ // 删除当前模型数据
+ $result = $this->db()
+ ->where($where)
+ ->removeOption('soft_delete')
+ ->delete();
+
+ $this->lazySave(false);
+ }
+
+ // 关联删除
+ if (!empty($this->relationWrite)) {
+ $this->autoRelationDelete();
+ }
+
+ $this->trigger('AfterDelete');
+
+ $this->exists(false);
+
+ return true;
+ }
+
+ /**
+ * 删除记录
+ * @access public
+ * @param mixed $data 主键列表 支持闭包查询条件
+ * @param bool $force 是否强制删除
+ * @return bool
+ */
+ public static function destroy($data, bool $force = false): bool
+ {
+ // 包含软删除数据
+ $query = (new static())->withTrashedData(true)->db(false);
+
+ if (is_array($data) && key($data) !== 0) {
+ $query->where($data);
+ $data = null;
+ } elseif ($data instanceof \Closure) {
+ call_user_func_array($data, [ & $query]);
+ $data = null;
+ } elseif (is_null($data)) {
+ return false;
+ }
+
+ $resultSet = $query->select($data);
+
+ foreach ($resultSet as $result) {
+ $result->force($force)->delete();
+ }
+
+ return true;
+ }
+
+ /**
+ * 恢复被软删除的记录
+ * @access public
+ * @param array $where 更新条件
+ * @return bool
+ */
+ public function restore($where = []): bool
+ {
+ $name = $this->getDeleteTimeField();
+
+ if (!$name || false === $this->trigger('BeforeRestore')) {
+ return false;
+ }
+
+ if (empty($where)) {
+ $pk = $this->getPk();
+ if (is_string($pk)) {
+ $where[] = [$pk, '=', $this->getData($pk)];
+ }
+ }
+
+ // 恢复删除
+ $this->db(false)
+ ->where($where)
+ ->useSoftDelete($name, $this->getWithTrashedExp())
+ ->update([$name => $this->defaultSoftDelete]);
+
+ $this->trigger('AfterRestore');
+
+ return true;
+ }
+
+ /**
+ * 获取软删除字段
+ * @access protected
+ * @param bool $read 是否查询操作 写操作的时候会自动去掉表别名
+ * @return string|false
+ */
+ protected function getDeleteTimeField(bool $read = false)
+ {
+ $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? $this->deleteTime : 'delete_time';
+
+ if (false === $field) {
+ return false;
+ }
+
+ if (false === strpos($field, '.')) {
+ $field = '__TABLE__.' . $field;
+ }
+
+ if (!$read && strpos($field, '.')) {
+ $array = explode('.', $field);
+ $field = array_pop($array);
+ }
+
+ return $field;
+ }
+
+ /**
+ * 查询的时候默认排除软删除数据
+ * @access protected
+ * @param Query $query
+ * @return void
+ */
+ protected function withNoTrashed(Query $query): void
+ {
+ $field = $this->getDeleteTimeField(true);
+
+ if ($field) {
+ $condition = is_null($this->defaultSoftDelete) ? ['null', ''] : ['=', $this->defaultSoftDelete];
+ $query->useSoftDelete($field, $condition);
+ }
+ }
+}
diff --git a/vendor/topthink/think-orm/src/model/concern/TimeStamp.php b/vendor/topthink/think-orm/src/model/concern/TimeStamp.php
new file mode 100755
index 0000000..e207961
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/concern/TimeStamp.php
@@ -0,0 +1,208 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\concern;
+
+use DateTime;
+
+/**
+ * 自动时间戳
+ */
+trait TimeStamp
+{
+ /**
+ * 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型
+ * @var bool|string
+ */
+ protected $autoWriteTimestamp;
+
+ /**
+ * 创建时间字段 false表示关闭
+ * @var false|string
+ */
+ protected $createTime = 'create_time';
+
+ /**
+ * 更新时间字段 false表示关闭
+ * @var false|string
+ */
+ protected $updateTime = 'update_time';
+
+ /**
+ * 时间字段显示格式
+ * @var string
+ */
+ protected $dateFormat;
+
+ /**
+ * 是否需要自动写入时间字段
+ * @access public
+ * @param bool|string $auto
+ * @return $this
+ */
+ public function isAutoWriteTimestamp($auto)
+ {
+ $this->autoWriteTimestamp = $this->checkTimeFieldType($auto);
+
+ return $this;
+ }
+
+ /**
+ * 检测时间字段的实际类型
+ * @access public
+ * @param bool|string $type
+ * @return mixed
+ */
+ protected function checkTimeFieldType($type)
+ {
+ if (true === $type) {
+ if (isset($this->type[$this->createTime])) {
+ $type = $this->type[$this->createTime];
+ } elseif (isset($this->schema[$this->createTime]) && in_array($this->schema[$this->createTime], ['datetime', 'date', 'timestamp', 'int'])) {
+ $type = $this->schema[$this->createTime];
+ } else {
+ $type = $this->getFieldType($this->createTime);
+ }
+ }
+
+ return $type;
+ }
+
+ /**
+ * 获取自动写入时间字段
+ * @access public
+ * @return bool|string
+ */
+ public function getAutoWriteTimestamp()
+ {
+ return $this->autoWriteTimestamp;
+ }
+
+ /**
+ * 设置时间字段格式化
+ * @access public
+ * @param string|false $format
+ * @return $this
+ */
+ public function setDateFormat($format)
+ {
+ $this->dateFormat = $format;
+
+ return $this;
+ }
+
+ /**
+ * 获取自动写入时间字段
+ * @access public
+ * @return string|false
+ */
+ public function getDateFormat()
+ {
+ return $this->dateFormat;
+ }
+
+ /**
+ * 自动写入时间戳
+ * @access protected
+ * @return mixed
+ */
+ protected function autoWriteTimestamp()
+ {
+ // 检测时间字段类型
+ $type = $this->checkTimeFieldType($this->autoWriteTimestamp);
+
+ return is_string($type) ? $this->getTimeTypeValue($type) : time();
+ }
+
+ /**
+ * 获取指定类型的时间字段值
+ * @access protected
+ * @param string $type 时间字段类型
+ * @return mixed
+ */
+ protected function getTimeTypeValue(string $type)
+ {
+ $value = time();
+
+ switch ($type) {
+ case 'datetime':
+ case 'date':
+ case 'timestamp':
+ $value = $this->formatDateTime('Y-m-d H:i:s.u');
+ break;
+ default:
+ if (false !== strpos($type, '\\')) {
+ // 对象数据写入
+ $obj = new $type();
+ if (method_exists($obj, '__toString')) {
+ // 对象数据写入
+ $value = $obj->__toString();
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * 时间日期字段格式化处理
+ * @access protected
+ * @param mixed $format 日期格式
+ * @param mixed $time 时间日期表达式
+ * @param bool $timestamp 时间表达式是否为时间戳
+ * @return mixed
+ */
+ protected function formatDateTime($format, $time = 'now', bool $timestamp = false)
+ {
+ if (empty($time)) {
+ return;
+ }
+
+ if (false === $format) {
+ return $time;
+ } elseif (false !== strpos($format, '\\')) {
+ return new $format($time);
+ }
+
+ if ($time instanceof DateTime) {
+ $dateTime = $time;
+ } elseif ($timestamp) {
+ $dateTime = new DateTime();
+ $dateTime->setTimestamp((int) $time);
+ } else {
+ $dateTime = new DateTime($time);
+ }
+
+ return $dateTime->format($format);
+ }
+
+ /**
+ * 获取时间字段值
+ * @access protected
+ * @param mixed $value
+ * @return mixed
+ */
+ protected function getTimestampValue($value)
+ {
+ $type = $this->checkTimeFieldType($this->autoWriteTimestamp);
+
+ if (is_string($type) && in_array(strtolower($type), [
+ 'datetime', 'date', 'timestamp',
+ ])) {
+ $value = $this->formatDateTime($this->dateFormat, $value);
+ } else {
+ $value = $this->formatDateTime($this->dateFormat, $value, true);
+ }
+
+ return $value;
+ }
+}
diff --git a/vendor/topthink/think-orm/src/model/relation/BelongsTo.php b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php
new file mode 100755
index 0000000..789c944
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php
@@ -0,0 +1,331 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\relation;
+
+use Closure;
+use think\db\BaseQuery as Query;
+use think\helper\Str;
+use think\Model;
+
+/**
+ * BelongsTo关联类
+ */
+class BelongsTo extends OneToOne
+{
+ /**
+ * 架构函数
+ * @access public
+ * @param Model $parent 上级模型对象
+ * @param string $model 模型名
+ * @param string $foreignKey 关联外键
+ * @param string $localKey 关联主键
+ * @param string $relation 关联名
+ */
+ public function __construct(Model $parent, string $model, string $foreignKey, string $localKey, string $relation = null)
+ {
+ $this->parent = $parent;
+ $this->model = $model;
+ $this->foreignKey = $foreignKey;
+ $this->localKey = $localKey;
+ $this->query = (new $model)->db();
+ $this->relation = $relation;
+
+ if (get_class($parent) == $model) {
+ $this->selfRelation = true;
+ }
+ }
+
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Model
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null)
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ $foreignKey = $this->foreignKey;
+
+ $relationModel = $this->query
+ ->removeWhereField($this->localKey)
+ ->where($this->localKey, $this->parent->$foreignKey)
+ ->relation($subRelation)
+ ->find();
+
+ if ($relationModel) {
+ if (!empty($this->bindAttr)) {
+ // 绑定关联属性
+ $this->bindAttr($this->parent, $relationModel);
+ }
+
+ $relationModel->setParent(clone $this->parent);
+ }
+
+ return $relationModel;
+ }
+
+ /**
+ * 创建关联统计子查询
+ * @access public
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 聚合字段别名
+ * @return string
+ */
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', &$name = ''): string
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->query
+ ->whereExp($this->localKey, '=' . $this->parent->getTable() . '.' . $this->foreignKey)
+ ->fetchSql()
+ ->$aggregate($field);
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return integer
+ */
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null)
+ {
+ $foreignKey = $this->foreignKey;
+
+ if (!isset($result->$foreignKey)) {
+ return 0;
+ }
+
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->query
+ ->where($this->localKey, '=', $result->$foreignKey)
+ ->$aggregate($field);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query
+ {
+ $table = $this->query->getTable();
+ $model = class_basename($this->parent);
+ $relation = class_basename($this->model);
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ?: $this->parent->db()->alias($model);
+
+ return $query->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) {
+ $query->table([$table => $relation])
+ ->field($relation . '.' . $localKey)
+ ->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey)
+ ->when($softDelete, function ($query) use ($softDelete, $relation) {
+ $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ });
+ });
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query
+ {
+ $table = $this->query->getTable();
+ $model = class_basename($this->parent);
+ $relation = class_basename($this->model);
+
+ if (is_array($where)) {
+ $this->getQueryWhere($where, $relation);
+ } elseif ($where instanceof Query) {
+ $where->via($relation);
+ } elseif ($where instanceof Closure) {
+ $where($this->query->via($relation));
+ $where = $this->query;
+ }
+
+ $fields = $this->getRelationQueryFields($fields, $model);
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ?: $this->parent->db()->alias($model);
+
+ return $query->field($fields)
+ ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $joinType ?: $this->joinType)
+ ->when($softDelete, function ($query) use ($softDelete, $relation) {
+ $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ })
+ ->where($where);
+ }
+
+ /**
+ * 预载入关联查询(数据集)
+ * @access protected
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+
+ $range = [];
+ foreach ($resultSet as $result) {
+ // 获取关联外键列表
+ if (isset($result->$foreignKey)) {
+ $range[] = $result->$foreignKey;
+ }
+ }
+
+ if (!empty($range)) {
+ $this->query->removeWhereField($localKey);
+
+ $data = $this->eagerlyWhere([
+ [$localKey, 'in', $range],
+ ], $localKey, $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ foreach ($resultSet as $result) {
+ // 关联模型
+ if (!isset($data[$result->$foreignKey])) {
+ $relationModel = null;
+ } else {
+ $relationModel = $data[$result->$foreignKey];
+ $relationModel->setParent(clone $result);
+ $relationModel->exists(true);
+ }
+
+ if (!empty($this->bindAttr)) {
+ // 绑定关联属性
+ $this->bindAttr($result, $relationModel);
+ } else {
+ // 设置关联属性
+ $result->setRelation($relation, $relationModel);
+ }
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询(数据)
+ * @access protected
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+
+ $this->query->removeWhereField($localKey);
+
+ $data = $this->eagerlyWhere([
+ [$localKey, '=', $result->$foreignKey],
+ ], $localKey, $subRelation, $closure, $cache);
+
+ // 关联模型
+ if (!isset($data[$result->$foreignKey])) {
+ $relationModel = null;
+ } else {
+ $relationModel = $data[$result->$foreignKey];
+ $relationModel->setParent(clone $result);
+ $relationModel->exists(true);
+ }
+
+ if (!empty($this->bindAttr)) {
+ // 绑定关联属性
+ $this->bindAttr($result, $relationModel);
+ } else {
+ // 设置关联属性
+ $result->setRelation($relation, $relationModel);
+ }
+ }
+
+ /**
+ * 添加关联数据
+ * @access public
+ * @param Model $model关联模型对象
+ * @return Model
+ */
+ public function associate(Model $model): Model
+ {
+ $this->parent->setAttr($this->foreignKey, $model->getKey());
+ $this->parent->save();
+
+ return $this->parent->setRelation($this->relation, $model);
+ }
+
+ /**
+ * 注销关联数据
+ * @access public
+ * @return Model
+ */
+ public function dissociate(): Model
+ {
+ $foreignKey = $this->foreignKey;
+
+ $this->parent->setAttr($foreignKey, null);
+ $this->parent->save();
+
+ return $this->parent->setRelation($this->relation, null);
+ }
+
+ /**
+ * 执行基础查询(仅执行一次)
+ * @access protected
+ * @return void
+ */
+ protected function baseQuery(): void
+ {
+ if (empty($this->baseQuery)) {
+ if (isset($this->parent->{$this->foreignKey})) {
+ // 关联查询带入关联条件
+ $this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey});
+ }
+
+ $this->baseQuery = true;
+ }
+ }
+}
diff --git a/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php
new file mode 100755
index 0000000..a3c8aa4
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php
@@ -0,0 +1,677 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\model\relation;
+
+use Closure;
+use think\Collection;
+use think\db\BaseQuery as Query;
+use think\db\exception\DbException as Exception;
+use think\db\Raw;
+use think\Model;
+use think\model\Pivot;
+use think\model\Relation;
+use think\Paginator;
+
+/**
+ * 多对多关联类
+ */
+class BelongsToMany extends Relation
+{
+ /**
+ * 中间表表名
+ * @var string
+ */
+ protected $middle;
+
+ /**
+ * 中间表模型名称
+ * @var string
+ */
+ protected $pivotName;
+
+ /**
+ * 中间表模型对象
+ * @var Pivot
+ */
+ protected $pivot;
+
+ /**
+ * 中间表数据名称
+ * @var string
+ */
+ protected $pivotDataName = 'pivot';
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Model $parent 上级模型对象
+ * @param string $model 模型名
+ * @param string $middle 中间表/模型名
+ * @param string $foreignKey 关联模型外键
+ * @param string $localKey 当前模型关联键
+ */
+ public function __construct(Model $parent, string $model, string $middle, string $foreignKey, string $localKey)
+ {
+ $this->parent = $parent;
+ $this->model = $model;
+ $this->foreignKey = $foreignKey;
+ $this->localKey = $localKey;
+
+ if (false !== strpos($middle, '\\')) {
+ $this->pivotName = $middle;
+ $this->middle = class_basename($middle);
+ } else {
+ $this->middle = $middle;
+ }
+
+ $this->query = (new $model)->db();
+ $this->pivot = $this->newPivot();
+ }
+
+ /**
+ * 设置中间表模型
+ * @access public
+ * @param $pivot
+ * @return $this
+ */
+ public function pivot(string $pivot)
+ {
+ $this->pivotName = $pivot;
+ return $this;
+ }
+
+ /**
+ * 设置中间表数据名称
+ * @access public
+ * @param string $name
+ * @return $this
+ */
+ public function name(string $name)
+ {
+ $this->pivotDataName = $name;
+ return $this;
+ }
+
+ /**
+ * 实例化中间表模型
+ * @access public
+ * @param $data
+ * @return Pivot
+ * @throws Exception
+ */
+ protected function newPivot(array $data = []): Pivot
+ {
+ $class = $this->pivotName ?: Pivot::class;
+ $pivot = new $class($data, $this->parent, $this->middle);
+
+ if ($pivot instanceof Pivot) {
+ return $pivot;
+ } else {
+ throw new Exception('pivot model must extends: \think\model\Pivot');
+ }
+ }
+
+ /**
+ * 合成中间表模型
+ * @access protected
+ * @param array|Collection|Paginator $models
+ */
+ protected function hydratePivot(iterable $models)
+ {
+ foreach ($models as $model) {
+ $pivot = [];
+
+ foreach ($model->getData() as $key => $val) {
+ if (strpos($key, '__')) {
+ [$name, $attr] = explode('__', $key, 2);
+
+ if ('pivot' == $name) {
+ $pivot[$attr] = $val;
+ unset($model->$key);
+ }
+ }
+ }
+
+ $model->setRelation($this->pivotDataName, $this->newPivot($pivot));
+ }
+ }
+
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Collection
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null): Collection
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ $result = $this->relation($subRelation)
+ ->select()
+ ->setParent(clone $this->parent);
+
+ $this->hydratePivot($result);
+
+ return $result;
+ }
+
+ /**
+ * 重载select方法
+ * @access public
+ * @param mixed $data
+ * @return Collection
+ */
+ public function select($data = null): Collection
+ {
+ $this->baseQuery();
+ $result = $this->query->select($data);
+ $this->hydratePivot($result);
+
+ return $result;
+ }
+
+ /**
+ * 重载paginate方法
+ * @access public
+ * @param int|array $listRows
+ * @param int|bool $simple
+ * @return Paginator
+ */
+ public function paginate($listRows = null, $simple = false): Paginator
+ {
+ $this->baseQuery();
+ $result = $this->query->paginate($listRows, $simple);
+ $this->hydratePivot($result);
+
+ return $result;
+ }
+
+ /**
+ * 重载find方法
+ * @access public
+ * @param mixed $data
+ * @return Model
+ */
+ public function find($data = null)
+ {
+ $this->baseQuery();
+ $result = $this->query->find($data);
+
+ if ($result && !$result->isEmpty()) {
+ $this->hydratePivot([$result]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Model
+ */
+ public function has(string $operator = '>=', $count = 1, $id = '*', string $joinType = 'INNER', Query $query = null)
+ {
+ return $this->parent;
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ * @throws Exception
+ */
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null)
+ {
+ throw new Exception('relation not support: hasWhere');
+ }
+
+ /**
+ * 设置中间表的查询条件
+ * @access public
+ * @param string $field
+ * @param string $op
+ * @param mixed $condition
+ * @return $this
+ */
+ public function wherePivot($field, $op = null, $condition = null)
+ {
+ $this->query->where('pivot.' . $field, $op, $condition);
+ return $this;
+ }
+
+ /**
+ * 预载入关联查询(数据集)
+ * @access public
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $pk = $resultSet[0]->getPk();
+ $range = [];
+
+ foreach ($resultSet as $result) {
+ // 获取关联外键列表
+ if (isset($result->$pk)) {
+ $range[] = $result->$pk;
+ }
+ }
+
+ if (!empty($range)) {
+ // 查询关联数据
+ $data = $this->eagerlyManyToMany([
+ ['pivot.' . $localKey, 'in', $range],
+ ], $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ foreach ($resultSet as $result) {
+ if (!isset($data[$result->$pk])) {
+ $data[$result->$pk] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent));
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询(单个数据)
+ * @access public
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
+ {
+ $pk = $result->getPk();
+
+ if (isset($result->$pk)) {
+ $pk = $result->$pk;
+ // 查询管理数据
+ $data = $this->eagerlyManyToMany([
+ ['pivot.' . $this->localKey, '=', $pk],
+ ], $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ if (!isset($data[$pk])) {
+ $data[$pk] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent));
+ }
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return integer
+ */
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): float
+ {
+ $pk = $result->getPk();
+
+ if (!isset($result->$pk)) {
+ return 0;
+ }
+
+ $pk = $result->$pk;
+
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
+ ['pivot.' . $this->localKey, '=', $pk],
+ ])->$aggregate($field);
+ }
+
+ /**
+ * 获取关联统计子查询
+ * @access public
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return string
+ */
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
+ [
+ 'pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(false)->getTable() . '.' . $this->parent->getPk()),
+ ],
+ ])->fetchSql()->$aggregate($field);
+ }
+
+ /**
+ * 多对多 关联模型预查询
+ * @access protected
+ * @param array $where 关联预查询条件
+ * @param array $subRelation 子关联
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return array
+ */
+ protected function eagerlyManyToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ // 预载入关联查询 支持嵌套预载入
+ $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)
+ ->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select();
+
+ // 组装模型数据
+ $data = [];
+ foreach ($list as $set) {
+ $pivot = [];
+ foreach ($set->getData() as $key => $val) {
+ if (strpos($key, '__')) {
+ [$name, $attr] = explode('__', $key, 2);
+ if ('pivot' == $name) {
+ $pivot[$attr] = $val;
+ unset($set->$key);
+ }
+ }
+ }
+ $key = $pivot[$this->localKey];
+
+ if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) {
+ continue;
+ }
+
+ $set->setRelation($this->pivotDataName, $this->newPivot($pivot));
+
+ $data[$key][] = $set;
+ }
+
+ return $data;
+ }
+
+ /**
+ * BELONGS TO MANY 关联查询
+ * @access protected
+ * @param string $foreignKey 关联模型关联键
+ * @param string $localKey 当前模型关联键
+ * @param array $condition 关联查询条件
+ * @return Query
+ */
+ protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query
+ {
+ // 关联查询封装
+ $tableName = $this->query->getTable();
+ $table = $this->pivot->db()->getTable();
+ $fields = $this->getQueryFields($tableName);
+
+ if ($this->withLimit) {
+ $this->query->limit($this->withLimit);
+ }
+
+ $this->query
+ ->field($fields)
+ ->tableField(true, $table, 'pivot', 'pivot__')
+ ->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $this->query->getPk())
+ ->where($condition);
+
+ return $this->query;
+ }
+
+ /**
+ * 保存(新增)当前关联数据对象
+ * @access public
+ * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键
+ * @param array $pivot 中间表额外数据
+ * @return array|Pivot
+ */
+ public function save($data, array $pivot = [])
+ {
+ // 保存关联表/中间表数据
+ return $this->attach($data, $pivot);
+ }
+
+ /**
+ * 批量保存当前关联数据对象
+ * @access public
+ * @param iterable $dataSet 数据集
+ * @param array $pivot 中间表额外数据
+ * @param bool $samePivot 额外数据是否相同
+ * @return array|false
+ */
+ public function saveAll(iterable $dataSet, array $pivot = [], bool $samePivot = false)
+ {
+ $result = [];
+
+ foreach ($dataSet as $key => $data) {
+ if (!$samePivot) {
+ $pivotData = $pivot[$key] ?? [];
+ } else {
+ $pivotData = $pivot;
+ }
+
+ $result[] = $this->attach($data, $pivotData);
+ }
+
+ return empty($result) ? false : $result;
+ }
+
+ /**
+ * 附加关联的一个中间表数据
+ * @access public
+ * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键
+ * @param array $pivot 中间表额外数据
+ * @return array|Pivot
+ * @throws Exception
+ */
+ public function attach($data, array $pivot = [])
+ {
+ if (is_array($data)) {
+ if (key($data) === 0) {
+ $id = $data;
+ } else {
+ // 保存关联表数据
+ $model = new $this->model;
+ $id = $model->insertGetId($data);
+ }
+ } elseif (is_numeric($data) || is_string($data)) {
+ // 根据关联表主键直接写入中间表
+ $id = $data;
+ } elseif ($data instanceof Model) {
+ // 根据关联表主键直接写入中间表
+ $id = $data->getKey();
+ }
+
+ if (!empty($id)) {
+ // 保存中间表数据
+ $pivot[$this->localKey] = $this->parent->getKey();
+
+ $ids = (array) $id;
+ foreach ($ids as $id) {
+ $pivot[$this->foreignKey] = $id;
+ $this->pivot->replace()
+ ->exists(false)
+ ->data([])
+ ->save($pivot);
+ $result[] = $this->newPivot($pivot);
+ }
+
+ if (count($result) == 1) {
+ // 返回中间表模型对象
+ $result = $result[0];
+ }
+
+ return $result;
+ } else {
+ throw new Exception('miss relation data');
+ }
+ }
+
+ /**
+ * 判断是否存在关联数据
+ * @access public
+ * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键
+ * @return Pivot|false
+ */
+ public function attached($data)
+ {
+ if ($data instanceof Model) {
+ $id = $data->getKey();
+ } else {
+ $id = $data;
+ }
+
+ $pivot = $this->pivot
+ ->where($this->localKey, $this->parent->getKey())
+ ->where($this->foreignKey, $id)
+ ->find();
+
+ return $pivot ?: false;
+ }
+
+ /**
+ * 解除关联的一个中间表数据
+ * @access public
+ * @param integer|array $data 数据 可以使用关联对象的主键
+ * @param bool $relationDel 是否同时删除关联表数据
+ * @return integer
+ */
+ public function detach($data = null, bool $relationDel = false): int
+ {
+ if (is_array($data)) {
+ $id = $data;
+ } elseif (is_numeric($data) || is_string($data)) {
+ // 根据关联表主键直接写入中间表
+ $id = $data;
+ } elseif ($data instanceof Model) {
+ // 根据关联表主键直接写入中间表
+ $id = $data->getKey();
+ }
+
+ // 删除中间表数据
+ $pivot = [];
+ $pivot[] = [$this->localKey, '=', $this->parent->getKey()];
+
+ if (isset($id)) {
+ $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id];
+ }
+
+ $result = $this->pivot->where($pivot)->delete();
+
+ // 删除关联表数据
+ if (isset($id) && $relationDel) {
+ $model = $this->model;
+ $model::destroy($id);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 数据同步
+ * @access public
+ * @param array $ids
+ * @param bool $detaching
+ * @return array
+ */
+ public function sync(array $ids, bool $detaching = true): array
+ {
+ $changes = [
+ 'attached' => [],
+ 'detached' => [],
+ 'updated' => [],
+ ];
+
+ $current = $this->pivot
+ ->where($this->localKey, $this->parent->getKey())
+ ->column($this->foreignKey);
+
+ $records = [];
+
+ foreach ($ids as $key => $value) {
+ if (!is_array($value)) {
+ $records[$value] = [];
+ } else {
+ $records[$key] = $value;
+ }
+ }
+
+ $detach = array_diff($current, array_keys($records));
+
+ if ($detaching && count($detach) > 0) {
+ $this->detach($detach);
+ $changes['detached'] = $detach;
+ }
+
+ foreach ($records as $id => $attributes) {
+ if (!in_array($id, $current)) {
+ $this->attach($id, $attributes);
+ $changes['attached'][] = $id;
+ } elseif (count($attributes) > 0 && $this->attach($id, $attributes)) {
+ $changes['updated'][] = $id;
+ }
+ }
+
+ return $changes;
+ }
+
+ /**
+ * 执行基础查询(仅执行一次)
+ * @access protected
+ * @return void
+ */
+ protected function baseQuery(): void
+ {
+ if (empty($this->baseQuery)) {
+ $foreignKey = $this->foreignKey;
+ $localKey = $this->localKey;
+
+ // 关联查询
+ $condition = ['pivot.' . $localKey, '=', $this->parent->getKey()];
+
+ $this->belongsToManyQuery($foreignKey, $localKey, [$condition]);
+
+ $this->baseQuery = true;
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/relation/HasMany.php b/vendor/topthink/think-orm/src/model/relation/HasMany.php
new file mode 100755
index 0000000..a67d41b
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/HasMany.php
@@ -0,0 +1,367 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\relation;
+
+use Closure;
+use think\Collection;
+use think\db\BaseQuery as Query;
+use think\helper\Str;
+use think\Model;
+use think\model\Relation;
+
+/**
+ * 一对多关联类
+ */
+class HasMany extends Relation
+{
+ /**
+ * 架构函数
+ * @access public
+ * @param Model $parent 上级模型对象
+ * @param string $model 模型名
+ * @param string $foreignKey 关联外键
+ * @param string $localKey 当前模型主键
+ */
+ public function __construct(Model $parent, string $model, string $foreignKey, string $localKey)
+ {
+ $this->parent = $parent;
+ $this->model = $model;
+ $this->foreignKey = $foreignKey;
+ $this->localKey = $localKey;
+ $this->query = (new $model)->db();
+
+ if (get_class($parent) == $model) {
+ $this->selfRelation = true;
+ }
+ }
+
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Collection
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null): Collection
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ if ($this->withLimit) {
+ $this->query->limit($this->withLimit);
+ }
+
+ return $this->query
+ ->where($this->foreignKey, $this->parent->{$this->localKey})
+ ->relation($subRelation)
+ ->select()
+ ->setParent(clone $this->parent);
+ }
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $range = [];
+
+ foreach ($resultSet as $result) {
+ // 获取关联外键列表
+ if (isset($result->$localKey)) {
+ $range[] = $result->$localKey;
+ }
+ }
+
+ if (!empty($range)) {
+ $data = $this->eagerlyOneToMany([
+ [$this->foreignKey, 'in', $range],
+ ], $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ foreach ($resultSet as $result) {
+ $pk = $result->$localKey;
+ if (!isset($data[$pk])) {
+ $data[$pk] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent));
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+
+ if (isset($result->$localKey)) {
+ $pk = $result->$localKey;
+ $data = $this->eagerlyOneToMany([
+ [$this->foreignKey, '=', $pk],
+ ], $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ if (!isset($data[$pk])) {
+ $data[$pk] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent));
+ }
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return integer
+ */
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null)
+ {
+ $localKey = $this->localKey;
+
+ if (!isset($result->$localKey)) {
+ return 0;
+ }
+
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->query
+ ->where($this->foreignKey, '=', $result->$localKey)
+ ->$aggregate($field);
+ }
+
+ /**
+ * 创建关联统计子查询
+ * @access public
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return string
+ */
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->query->alias($aggregate . '_table')
+ ->whereExp($aggregate . '_table.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey)
+ ->fetchSql()
+ ->$aggregate($field);
+ }
+
+ /**
+ * 一对多 关联模型预查询
+ * @access public
+ * @param array $where 关联预查询条件
+ * @param array $subRelation 子关联
+ * @param Closure $closure
+ * @param array $cache 关联缓存
+ * @return array
+ */
+ protected function eagerlyOneToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array
+ {
+ $foreignKey = $this->foreignKey;
+
+ $this->query->removeWhereField($this->foreignKey);
+
+ // 预载入关联查询 支持嵌套预载入
+ if ($closure) {
+ $this->baseQuery = true;
+ $closure($this->getClosureType($closure));
+ }
+
+ $list = $this->query
+ ->where($where)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->with($subRelation)
+ ->select();
+
+ // 组装模型数据
+ $data = [];
+
+ foreach ($list as $set) {
+ $key = $set->$foreignKey;
+
+ if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) {
+ continue;
+ }
+
+ $data[$key][] = $set;
+ }
+
+ return $data;
+ }
+
+ /**
+ * 保存(新增)当前关联数据对象
+ * @access public
+ * @param mixed $data 数据 可以使用数组 关联模型对象
+ * @param boolean $replace 是否自动识别更新和写入
+ * @return Model|false
+ */
+ public function save($data, bool $replace = true)
+ {
+ $model = $this->make();
+
+ return $model->replace($replace)->save($data) ? $model : false;
+ }
+
+ /**
+ * 创建关联对象实例
+ * @param array|Model $data
+ * @return Model
+ */
+ public function make($data = []): Model
+ {
+ if ($data instanceof Model) {
+ $data = $data->getData();
+ }
+
+ // 保存关联表数据
+ $data[$this->foreignKey] = $this->parent->{$this->localKey};
+
+ return new $this->model($data);
+ }
+
+ /**
+ * 批量保存当前关联数据对象
+ * @access public
+ * @param iterable $dataSet 数据集
+ * @param boolean $replace 是否自动识别更新和写入
+ * @return array|false
+ */
+ public function saveAll(iterable $dataSet, bool $replace = true)
+ {
+ $result = [];
+
+ foreach ($dataSet as $key => $data) {
+ $result[] = $this->save($data, $replace);
+ }
+
+ return empty($result) ? false : $result;
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER', Query $query = null): Query
+ {
+ $table = $this->query->getTable();
+
+ $model = class_basename($this->parent);
+ $relation = class_basename($this->model);
+
+ if ('*' != $id) {
+ $id = $relation . '.' . (new $this->model)->getPk();
+ }
+
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ?: $this->parent->db()->alias($model);
+
+ return $query->field($model . '.*')
+ ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType)
+ ->when($softDelete, function ($query) use ($softDelete, $relation) {
+ $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ })
+ ->group($relation . '.' . $this->foreignKey)
+ ->having('count(' . $id . ')' . $operator . $count);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query
+ {
+ $table = $this->query->getTable();
+ $model = class_basename($this->parent);
+ $relation = class_basename($this->model);
+
+ if (is_array($where)) {
+ $this->getQueryWhere($where, $relation);
+ } elseif ($where instanceof Query) {
+ $where->via($relation);
+ } elseif ($where instanceof Closure) {
+ $where($this->query->via($relation));
+ $where = $this->query;
+ }
+
+ $fields = $this->getRelationQueryFields($fields, $model);
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ?: $this->parent->db()->alias($model);
+
+ return $query->group($model . '.' . $this->localKey)
+ ->field($fields)
+ ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType)
+ ->when($softDelete, function ($query) use ($softDelete, $relation) {
+ $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ })
+ ->where($where);
+ }
+
+ /**
+ * 执行基础查询(仅执行一次)
+ * @access protected
+ * @return void
+ */
+ protected function baseQuery(): void
+ {
+ if (empty($this->baseQuery)) {
+ if (isset($this->parent->{$this->localKey})) {
+ // 关联查询带入关联条件
+ $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey});
+ }
+
+ $this->baseQuery = true;
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php
new file mode 100755
index 0000000..30d5ca4
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php
@@ -0,0 +1,382 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\model\relation;
+
+use Closure;
+use think\Collection;
+use think\db\BaseQuery as Query;
+use think\helper\Str;
+use think\Model;
+use think\model\Relation;
+
+/**
+ * 远程一对多关联类
+ */
+class HasManyThrough extends Relation
+{
+ /**
+ * 中间关联表外键
+ * @var string
+ */
+ protected $throughKey;
+
+ /**
+ * 中间主键
+ * @var string
+ */
+ protected $throughPk;
+
+ /**
+ * 中间表查询对象
+ * @var Query
+ */
+ protected $through;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Model $parent 上级模型对象
+ * @param string $model 关联模型名
+ * @param string $through 中间模型名
+ * @param string $foreignKey 关联外键
+ * @param string $throughKey 中间关联外键
+ * @param string $localKey 当前模型主键
+ * @param string $throughPk 中间模型主键
+ */
+ public function __construct(Model $parent, string $model, string $through, string $foreignKey, string $throughKey, string $localKey, string $throughPk)
+ {
+ $this->parent = $parent;
+ $this->model = $model;
+ $this->through = (new $through)->db();
+ $this->foreignKey = $foreignKey;
+ $this->throughKey = $throughKey;
+ $this->localKey = $localKey;
+ $this->throughPk = $throughPk;
+ $this->query = (new $model)->db();
+ }
+
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Collection
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null)
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ $this->baseQuery();
+
+ if ($this->withLimit) {
+ $this->query->limit($this->withLimit);
+ }
+
+ return $this->query->relation($subRelation)
+ ->select()
+ ->setParent(clone $this->parent);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query
+ {
+ $model = Str::snake(class_basename($this->parent));
+ $throughTable = $this->through->getTable();
+ $pk = $this->throughPk;
+ $throughKey = $this->throughKey;
+ $relation = new $this->model;
+ $relationTable = $relation->getTable();
+ $softDelete = $this->query->getOptions('soft_delete');
+
+ if ('*' != $id) {
+ $id = $relationTable . '.' . $relation->getPk();
+ }
+ $query = $query ?: $this->parent->db()->alias($model);
+
+ return $query->field($model . '.*')
+ ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey)
+ ->join($relationTable, $relationTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk)
+ ->when($softDelete, function ($query) use ($softDelete, $relationTable) {
+ $query->where($relationTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ })
+ ->group($relationTable . '.' . $this->throughKey)
+ ->having('count(' . $id . ')' . $operator . $count);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function hasWhere($where = [], $fields = null, $joinType = '', Query $query = null): Query
+ {
+ $model = Str::snake(class_basename($this->parent));
+ $throughTable = $this->through->getTable();
+ $pk = $this->throughPk;
+ $throughKey = $this->throughKey;
+ $modelTable = (new $this->model)->getTable();
+
+ if (is_array($where)) {
+ $this->getQueryWhere($where, $modelTable);
+ } elseif ($where instanceof Query) {
+ $where->via($modelTable);
+ } elseif ($where instanceof Closure) {
+ $where($this->query->via($modelTable));
+ $where = $this->query;
+ }
+
+ $fields = $this->getRelationQueryFields($fields, $model);
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ?: $this->parent->db()->alias($model);
+
+ return $query->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey)
+ ->join($modelTable, $modelTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk, $joinType)
+ ->when($softDelete, function ($query) use ($softDelete, $modelTable) {
+ $query->where($modelTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ })
+ ->group($modelTable . '.' . $this->throughKey)
+ ->where($where)
+ ->field($fields);
+ }
+
+ /**
+ * 预载入关联查询(数据集)
+ * @access protected
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+
+ $range = [];
+ foreach ($resultSet as $result) {
+ // 获取关联外键列表
+ if (isset($result->$localKey)) {
+ $range[] = $result->$localKey;
+ }
+ }
+
+ if (!empty($range)) {
+ $this->query->removeWhereField($foreignKey);
+
+ $data = $this->eagerlyWhere([
+ [$this->foreignKey, 'in', $range],
+ ], $foreignKey, $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ foreach ($resultSet as $result) {
+ $pk = $result->$localKey;
+ if (!isset($data[$pk])) {
+ $data[$pk] = [];
+ }
+
+ // 设置关联属性
+ $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent));
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询(数据)
+ * @access protected
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+ $pk = $result->$localKey;
+
+ $this->query->removeWhereField($foreignKey);
+
+ $data = $this->eagerlyWhere([
+ [$foreignKey, '=', $pk],
+ ], $foreignKey, $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ if (!isset($data[$pk])) {
+ $data[$pk] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent));
+ }
+
+ /**
+ * 关联模型预查询
+ * @access public
+ * @param array $where 关联预查询条件
+ * @param string $key 关联键名
+ * @param array $subRelation 子关联
+ * @param Closure $closure
+ * @param array $cache 关联缓存
+ * @return array
+ */
+ protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []): array
+ {
+ // 预载入关联查询 支持嵌套预载入
+ $throughList = $this->through->where($where)->select();
+ $keys = $throughList->column($this->throughPk, $this->throughPk);
+
+ if ($closure) {
+ $this->baseQuery = true;
+ $closure($this->getClosureType($closure));
+ }
+
+ $list = $this->query
+ ->where($this->throughKey, 'in', $keys)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select();
+
+ // 组装模型数据
+ $data = [];
+ $keys = $throughList->column($this->foreignKey, $this->throughPk);
+
+ foreach ($list as $set) {
+ $key = $keys[$set->{$this->throughKey}];
+
+ if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) {
+ continue;
+ }
+
+ $data[$key][] = $set;
+ }
+
+ return $data;
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return mixed
+ */
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null)
+ {
+ $localKey = $this->localKey;
+
+ if (!isset($result->$localKey)) {
+ return 0;
+ }
+
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ $alias = Str::snake(class_basename($this->model));
+ $throughTable = $this->through->getTable();
+ $pk = $this->throughPk;
+ $throughKey = $this->throughKey;
+ $modelTable = $this->parent->getTable();
+
+ if (false === strpos($field, '.')) {
+ $field = $alias . '.' . $field;
+ }
+
+ return $this->query
+ ->alias($alias)
+ ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
+ ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
+ ->where($throughTable . '.' . $this->foreignKey, $result->$localKey)
+ ->$aggregate($field);
+ }
+
+ /**
+ * 创建关联统计子查询
+ * @access public
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return string
+ */
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ $alias = Str::snake(class_basename($this->model));
+ $throughTable = $this->through->getTable();
+ $pk = $this->throughPk;
+ $throughKey = $this->throughKey;
+ $modelTable = $this->parent->getTable();
+
+ if (false === strpos($field, '.')) {
+ $field = $alias . '.' . $field;
+ }
+
+ return $this->query
+ ->alias($alias)
+ ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
+ ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
+ ->whereExp($throughTable . '.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey)
+ ->fetchSql()
+ ->$aggregate($field);
+ }
+
+ /**
+ * 执行基础查询(仅执行一次)
+ * @access protected
+ * @return void
+ */
+ protected function baseQuery(): void
+ {
+ if (empty($this->baseQuery) && $this->parent->getData()) {
+ $alias = Str::snake(class_basename($this->model));
+ $throughTable = $this->through->getTable();
+ $pk = $this->throughPk;
+ $throughKey = $this->throughKey;
+ $modelTable = $this->parent->getTable();
+ $fields = $this->getQueryFields($alias);
+
+ $this->query
+ ->field($fields)
+ ->alias($alias)
+ ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
+ ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
+ ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey});
+
+ $this->baseQuery = true;
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/relation/HasOne.php b/vendor/topthink/think-orm/src/model/relation/HasOne.php
new file mode 100755
index 0000000..d0dc21d
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/HasOne.php
@@ -0,0 +1,300 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\model\relation;
+
+use Closure;
+use think\db\BaseQuery as Query;
+use think\helper\Str;
+use think\Model;
+
+/**
+ * HasOne 关联类
+ */
+class HasOne extends OneToOne
+{
+ /**
+ * 架构函数
+ * @access public
+ * @param Model $parent 上级模型对象
+ * @param string $model 模型名
+ * @param string $foreignKey 关联外键
+ * @param string $localKey 当前模型主键
+ */
+ public function __construct(Model $parent, string $model, string $foreignKey, string $localKey)
+ {
+ $this->parent = $parent;
+ $this->model = $model;
+ $this->foreignKey = $foreignKey;
+ $this->localKey = $localKey;
+ $this->query = (new $model)->db();
+
+ if (get_class($parent) == $model) {
+ $this->selfRelation = true;
+ }
+ }
+
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Model
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null)
+ {
+ $localKey = $this->localKey;
+
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ // 判断关联类型执行查询
+ $relationModel = $this->query
+ ->removeWhereField($this->foreignKey)
+ ->where($this->foreignKey, $this->parent->$localKey)
+ ->relation($subRelation)
+ ->find();
+
+ if ($relationModel) {
+ if (!empty($this->bindAttr)) {
+ // 绑定关联属性
+ $this->bindAttr($this->parent, $relationModel);
+ }
+
+ $relationModel->setParent(clone $this->parent);
+ }
+
+ return $relationModel;
+ }
+
+ /**
+ * 创建关联统计子查询
+ * @access public
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return string
+ */
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->query
+ ->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey)
+ ->fetchSql()
+ ->$aggregate($field);
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return integer
+ */
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null)
+ {
+ $localKey = $this->localKey;
+
+ if (!isset($result->$localKey)) {
+ return 0;
+ }
+
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->query
+ ->where($this->foreignKey, '=', $result->$localKey)
+ ->$aggregate($field);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query
+ {
+ $table = $this->query->getTable();
+ $model = class_basename($this->parent);
+ $relation = class_basename($this->model);
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ?: $this->parent->db()->alias($model);
+
+ return $query->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) {
+ $query->table([$table => $relation])
+ ->field($relation . '.' . $foreignKey)
+ ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey)
+ ->when($softDelete, function ($query) use ($softDelete, $relation) {
+ $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ });
+ });
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query
+ {
+ $table = $this->query->getTable();
+ $model = class_basename($this->parent);
+ $relation = class_basename($this->model);
+
+ if (is_array($where)) {
+ $this->getQueryWhere($where, $relation);
+ } elseif ($where instanceof Query) {
+ $where->via($relation);
+ } elseif ($where instanceof Closure) {
+ $where($this->query->via($relation));
+ $where = $this->query;
+ }
+
+ $fields = $this->getRelationQueryFields($fields, $model);
+ $softDelete = $this->query->getOptions('soft_delete');
+ $query = $query ?: $this->parent->db()->alias($model);
+
+ return $query->field($fields)
+ ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType ?: $this->joinType)
+ ->when($softDelete, function ($query) use ($softDelete, $relation) {
+ $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
+ })
+ ->where($where);
+ }
+
+ /**
+ * 预载入关联查询(数据集)
+ * @access protected
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+
+ $range = [];
+ foreach ($resultSet as $result) {
+ // 获取关联外键列表
+ if (isset($result->$localKey)) {
+ $range[] = $result->$localKey;
+ }
+ }
+
+ if (!empty($range)) {
+ $this->query->removeWhereField($foreignKey);
+
+ $data = $this->eagerlyWhere([
+ [$foreignKey, 'in', $range],
+ ], $foreignKey, $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ foreach ($resultSet as $result) {
+ // 关联模型
+ if (!isset($data[$result->$localKey])) {
+ $relationModel = null;
+ } else {
+ $relationModel = $data[$result->$localKey];
+ $relationModel->setParent(clone $result);
+ $relationModel->exists(true);
+ }
+
+ if (!empty($this->bindAttr)) {
+ // 绑定关联属性
+ $this->bindAttr($result, $relationModel);
+ } else {
+ // 设置关联属性
+ $result->setRelation($relation, $relationModel);
+ }
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询(数据)
+ * @access protected
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+
+ $this->query->removeWhereField($foreignKey);
+
+ $data = $this->eagerlyWhere([
+ [$foreignKey, '=', $result->$localKey],
+ ], $foreignKey, $subRelation, $closure, $cache);
+
+ // 关联模型
+ if (!isset($data[$result->$localKey])) {
+ $relationModel = null;
+ } else {
+ $relationModel = $data[$result->$localKey];
+ $relationModel->setParent(clone $result);
+ $relationModel->exists(true);
+ }
+
+ if (!empty($this->bindAttr)) {
+ // 绑定关联属性
+ $this->bindAttr($result, $relationModel);
+ } else {
+ $result->setRelation($relation, $relationModel);
+ }
+ }
+
+ /**
+ * 执行基础查询(仅执行一次)
+ * @access protected
+ * @return void
+ */
+ protected function baseQuery(): void
+ {
+ if (empty($this->baseQuery)) {
+ if (isset($this->parent->{$this->localKey})) {
+ // 关联查询带入关联条件
+ $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey});
+ }
+
+ $this->baseQuery = true;
+ }
+ }
+}
diff --git a/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php b/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php
new file mode 100755
index 0000000..8ec42df
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php
@@ -0,0 +1,163 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\model\relation;
+
+use Closure;
+use think\helper\Str;
+use think\Model;
+
+/**
+ * 远程一对一关联类
+ */
+class HasOneThrough extends HasManyThrough
+{
+
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Model
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null)
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ $this->baseQuery();
+
+ $relationModel = $this->query->relation($subRelation)->find();
+
+ if ($relationModel) {
+ $relationModel->setParent(clone $this->parent);
+ }
+
+ return $relationModel;
+ }
+
+ /**
+ * 预载入关联查询(数据集)
+ * @access protected
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+
+ $range = [];
+ foreach ($resultSet as $result) {
+ // 获取关联外键列表
+ if (isset($result->$localKey)) {
+ $range[] = $result->$localKey;
+ }
+ }
+
+ if (!empty($range)) {
+ $this->query->removeWhereField($foreignKey);
+
+ $data = $this->eagerlyWhere([
+ [$this->foreignKey, 'in', $range],
+ ], $foreignKey, $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ foreach ($resultSet as $result) {
+ // 关联模型
+ if (!isset($data[$result->$localKey])) {
+ $relationModel = null;
+ } else {
+ $relationModel = $data[$result->$localKey];
+ $relationModel->setParent(clone $result);
+ $relationModel->exists(true);
+ }
+
+ // 设置关联属性
+ $result->setRelation($relation, $relationModel);
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询(数据)
+ * @access protected
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $localKey = $this->localKey;
+ $foreignKey = $this->foreignKey;
+
+ $this->query->removeWhereField($foreignKey);
+
+ $data = $this->eagerlyWhere([
+ [$foreignKey, '=', $result->$localKey],
+ ], $foreignKey, $subRelation, $closure, $cache);
+
+ // 关联模型
+ if (!isset($data[$result->$localKey])) {
+ $relationModel = null;
+ } else {
+ $relationModel = $data[$result->$localKey];
+ $relationModel->setParent(clone $result);
+ $relationModel->exists(true);
+ }
+
+ $result->setRelation($relation, $relationModel);
+ }
+
+ /**
+ * 关联模型预查询
+ * @access public
+ * @param array $where 关联预查询条件
+ * @param string $key 关联键名
+ * @param array $subRelation 子关联
+ * @param Closure $closure
+ * @param array $cache 关联缓存
+ * @return array
+ */
+ protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []): array
+ {
+ // 预载入关联查询 支持嵌套预载入
+ $keys = $this->through->where($where)->column($this->throughPk, $this->foreignKey);
+
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ $list = $this->query
+ ->where($this->throughKey, 'in', $keys)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select();
+
+ // 组装模型数据
+ $data = [];
+ $keys = array_flip($keys);
+
+ foreach ($list as $set) {
+ $data[$keys[$set->{$this->throughKey}]] = $set;
+ }
+
+ return $data;
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/relation/MorphMany.php b/vendor/topthink/think-orm/src/model/relation/MorphMany.php
new file mode 100755
index 0000000..82910cb
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/MorphMany.php
@@ -0,0 +1,353 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\model\relation;
+
+use Closure;
+use think\Collection;
+use think\db\BaseQuery as Query;
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
+use think\Model;
+use think\model\Relation;
+
+/**
+ * 多态一对多关联
+ */
+class MorphMany extends Relation
+{
+
+ /**
+ * 多态关联外键
+ * @var string
+ */
+ protected $morphKey;
+ /**
+ * 多态字段名
+ * @var string
+ */
+ protected $morphType;
+
+ /**
+ * 多态类型
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Model $parent 上级模型对象
+ * @param string $model 模型名
+ * @param string $morphKey 关联外键
+ * @param string $morphType 多态字段名
+ * @param string $type 多态类型
+ */
+ public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type)
+ {
+ $this->parent = $parent;
+ $this->model = $model;
+ $this->type = $type;
+ $this->morphKey = $morphKey;
+ $this->morphType = $morphType;
+ $this->query = (new $model)->db();
+ }
+
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Collection
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null): Collection
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ $this->baseQuery();
+
+ if ($this->withLimit) {
+ $this->query->limit($this->withLimit);
+ }
+
+ return $this->query->relation($subRelation)
+ ->select()
+ ->setParent(clone $this->parent);
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null)
+ {
+ throw new Exception('relation not support: has');
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null)
+ {
+ throw new Exception('relation not support: hasWhere');
+ }
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
+ {
+ $morphType = $this->morphType;
+ $morphKey = $this->morphKey;
+ $type = $this->type;
+ $range = [];
+
+ foreach ($resultSet as $result) {
+ $pk = $result->getPk();
+ // 获取关联外键列表
+ if (isset($result->$pk)) {
+ $range[] = $result->$pk;
+ }
+ }
+
+ if (!empty($range)) {
+ $where = [
+ [$morphKey, 'in', $range],
+ [$morphType, '=', $type],
+ ];
+ $data = $this->eagerlyMorphToMany($where, $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ foreach ($resultSet as $result) {
+ if (!isset($data[$result->$pk])) {
+ $data[$result->$pk] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent));
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $pk = $result->getPk();
+
+ if (isset($result->$pk)) {
+ $key = $result->$pk;
+ $data = $this->eagerlyMorphToMany([
+ [$this->morphKey, '=', $key],
+ [$this->morphType, '=', $this->type],
+ ], $subRelation, $closure, $cache);
+
+ if (!isset($data[$key])) {
+ $data[$key] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$key], clone $this->parent));
+ }
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return mixed
+ */
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null)
+ {
+ $pk = $result->getPk();
+
+ if (!isset($result->$pk)) {
+ return 0;
+ }
+
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->query
+ ->where([
+ [$this->morphKey, '=', $result->$pk],
+ [$this->morphType, '=', $this->type],
+ ])
+ ->$aggregate($field);
+ }
+
+ /**
+ * 获取关联统计子查询
+ * @access public
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return string
+ */
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->query
+ ->whereExp($this->morphKey, '=' . $this->parent->getTable() . '.' . $this->parent->getPk())
+ ->where($this->morphType, '=', $this->type)
+ ->fetchSql()
+ ->$aggregate($field);
+ }
+
+ /**
+ * 多态一对多 关联模型预查询
+ * @access protected
+ * @param array $where 关联预查询条件
+ * @param array $subRelation 子关联
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return array
+ */
+ protected function eagerlyMorphToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array
+ {
+ // 预载入关联查询 支持嵌套预载入
+ $this->query->removeOption('where');
+
+ if ($closure) {
+ $this->baseQuery = true;
+ $closure($this->getClosureType($closure));
+ }
+
+ $list = $this->query
+ ->where($where)
+ ->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select();
+ $morphKey = $this->morphKey;
+
+ // 组装模型数据
+ $data = [];
+ foreach ($list as $set) {
+ $key = $set->$morphKey;
+
+ if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) {
+ continue;
+ }
+
+ $data[$key][] = $set;
+ }
+
+ return $data;
+ }
+
+ /**
+ * 保存(新增)当前关联数据对象
+ * @access public
+ * @param mixed $data 数据 可以使用数组 关联模型对象
+ * @param bool $replace 是否自动识别更新和写入
+ * @return Model|false
+ */
+ public function save($data, bool $replace = true)
+ {
+ $model = $this->make();
+
+ return $model->replace($replace)->save($data) ? $model : false;
+ }
+
+ /**
+ * 创建关联对象实例
+ * @param array|Model $data
+ * @return Model
+ */
+ public function make($data = []): Model
+ {
+ if ($data instanceof Model) {
+ $data = $data->getData();
+ }
+
+ // 保存关联表数据
+ $pk = $this->parent->getPk();
+
+ $data[$this->morphKey] = $this->parent->$pk;
+ $data[$this->morphType] = $this->type;
+
+ return new $this->model($data);
+ }
+
+ /**
+ * 批量保存当前关联数据对象
+ * @access public
+ * @param iterable $dataSet 数据集
+ * @param boolean $replace 是否自动识别更新和写入
+ * @return array|false
+ */
+ public function saveAll(iterable $dataSet, bool $replace = true)
+ {
+ $result = [];
+
+ foreach ($dataSet as $key => $data) {
+ $result[] = $this->save($data, $replace);
+ }
+
+ return empty($result) ? false : $result;
+ }
+
+ /**
+ * 执行基础查询(仅执行一次)
+ * @access protected
+ * @return void
+ */
+ protected function baseQuery(): void
+ {
+ if (empty($this->baseQuery) && $this->parent->getData()) {
+ $pk = $this->parent->getPk();
+
+ $this->query->where([
+ [$this->morphKey, '=', $this->parent->$pk],
+ [$this->morphType, '=', $this->type],
+ ]);
+
+ $this->baseQuery = true;
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/relation/MorphOne.php b/vendor/topthink/think-orm/src/model/relation/MorphOne.php
new file mode 100755
index 0000000..6789c76
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/MorphOne.php
@@ -0,0 +1,280 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\model\relation;
+
+use Closure;
+use think\db\BaseQuery as Query;
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
+use think\Model;
+use think\model\Relation;
+
+/**
+ * 多态一对一关联类
+ */
+class MorphOne extends Relation
+{
+ /**
+ * 多态关联外键
+ * @var string
+ */
+ protected $morphKey;
+
+ /**
+ * 多态字段
+ * @var string
+ */
+ protected $morphType;
+
+ /**
+ * 多态类型
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * 构造函数
+ * @access public
+ * @param Model $parent 上级模型对象
+ * @param string $model 模型名
+ * @param string $morphKey 关联外键
+ * @param string $morphType 多态字段名
+ * @param string $type 多态类型
+ */
+ public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type)
+ {
+ $this->parent = $parent;
+ $this->model = $model;
+ $this->type = $type;
+ $this->morphKey = $morphKey;
+ $this->morphType = $morphType;
+ $this->query = (new $model)->db();
+ }
+
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Model
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null)
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ $this->baseQuery();
+
+ $relationModel = $this->query->relation($subRelation)->find();
+
+ if ($relationModel) {
+ $relationModel->setParent(clone $this->parent);
+ }
+
+ return $relationModel;
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null)
+ {
+ return $this->parent;
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null)
+ {
+ throw new Exception('relation not support: hasWhere');
+ }
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
+ {
+ $morphType = $this->morphType;
+ $morphKey = $this->morphKey;
+ $type = $this->type;
+ $range = [];
+
+ foreach ($resultSet as $result) {
+ $pk = $result->getPk();
+ // 获取关联外键列表
+ if (isset($result->$pk)) {
+ $range[] = $result->$pk;
+ }
+ }
+
+ if (!empty($range)) {
+ $data = $this->eagerlyMorphToOne([
+ [$morphKey, 'in', $range],
+ [$morphType, '=', $type],
+ ], $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ foreach ($resultSet as $result) {
+ if (!isset($data[$result->$pk])) {
+ $relationModel = null;
+ } else {
+ $relationModel = $data[$result->$pk];
+ $relationModel->setParent(clone $result);
+ $relationModel->exists(true);
+ }
+
+ $result->setRelation($relation, $relationModel);
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ $pk = $result->getPk();
+
+ if (isset($result->$pk)) {
+ $pk = $result->$pk;
+ $data = $this->eagerlyMorphToOne([
+ [$this->morphKey, '=', $pk],
+ [$this->morphType, '=', $this->type],
+ ], $subRelation, $closure, $cache);
+
+ if (isset($data[$pk])) {
+ $relationModel = $data[$pk];
+ $relationModel->setParent(clone $result);
+ $relationModel->exists(true);
+ } else {
+ $relationModel = null;
+ }
+
+ $result->setRelation($relation, $relationModel);
+ }
+ }
+
+ /**
+ * 多态一对一 关联模型预查询
+ * @access protected
+ * @param array $where 关联预查询条件
+ * @param array $subRelation 子关联
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return array
+ */
+ protected function eagerlyMorphToOne(array $where, array $subRelation = [], $closure = null, array $cache = []): array
+ {
+ // 预载入关联查询 支持嵌套预载入
+ if ($closure) {
+ $this->baseQuery = true;
+ $closure($this->getClosureType($closure));
+ }
+
+ $list = $this->query
+ ->where($where)
+ ->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select();
+ $morphKey = $this->morphKey;
+
+ // 组装模型数据
+ $data = [];
+
+ foreach ($list as $set) {
+ $data[$set->$morphKey] = $set;
+ }
+
+ return $data;
+ }
+
+ /**
+ * 保存(新增)当前关联数据对象
+ * @access public
+ * @param mixed $data 数据 可以使用数组 关联模型对象
+ * @param boolean $replace 是否自动识别更新和写入
+ * @return Model|false
+ */
+ public function save($data, bool $replace = true)
+ {
+ $model = $this->make();
+ return $model->replace($replace)->save($data) ? $model : false;
+ }
+
+ /**
+ * 创建关联对象实例
+ * @param array|Model $data
+ * @return Model
+ */
+ public function make($data = []): Model
+ {
+ if ($data instanceof Model) {
+ $data = $data->getData();
+ }
+
+ // 保存关联表数据
+ $pk = $this->parent->getPk();
+
+ $data[$this->morphKey] = $this->parent->$pk;
+ $data[$this->morphType] = $this->type;
+
+ return new $this->model($data);
+ }
+
+ /**
+ * 执行基础查询(进执行一次)
+ * @access protected
+ * @return void
+ */
+ protected function baseQuery(): void
+ {
+ if (empty($this->baseQuery) && $this->parent->getData()) {
+ $pk = $this->parent->getPk();
+
+ $this->query->where([
+ [$this->morphKey, '=', $this->parent->$pk],
+ [$this->morphType, '=', $this->type],
+ ]);
+ $this->baseQuery = true;
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/relation/MorphTo.php b/vendor/topthink/think-orm/src/model/relation/MorphTo.php
new file mode 100755
index 0000000..986380e
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/MorphTo.php
@@ -0,0 +1,332 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\model\relation;
+
+use Closure;
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
+use think\Model;
+use think\model\Relation;
+
+/**
+ * 多态关联类
+ */
+class MorphTo extends Relation
+{
+ /**
+ * 多态关联外键
+ * @var string
+ */
+ protected $morphKey;
+
+ /**
+ * 多态字段
+ * @var string
+ */
+ protected $morphType;
+
+ /**
+ * 多态别名
+ * @var array
+ */
+ protected $alias = [];
+
+ /**
+ * 关联名
+ * @var string
+ */
+ protected $relation;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Model $parent 上级模型对象
+ * @param string $morphType 多态字段名
+ * @param string $morphKey 外键名
+ * @param array $alias 多态别名定义
+ * @param string $relation 关联名
+ */
+ public function __construct(Model $parent, string $morphType, string $morphKey, array $alias = [], string $relation = null)
+ {
+ $this->parent = $parent;
+ $this->morphType = $morphType;
+ $this->morphKey = $morphKey;
+ $this->alias = $alias;
+ $this->relation = $relation;
+ }
+
+ /**
+ * 获取当前的关联模型类的实例
+ * @access public
+ * @return Model
+ */
+ public function getModel(): Model
+ {
+ $morphType = $this->morphType;
+ $model = $this->parseModel($this->parent->$morphType);
+
+ return (new $model);
+ }
+
+ /**
+ * 延迟获取关联数据
+ * @access public
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包查询条件
+ * @return Model
+ */
+ public function getRelation(array $subRelation = [], Closure $closure = null)
+ {
+ $morphKey = $this->morphKey;
+ $morphType = $this->morphType;
+
+ // 多态模型
+ $model = $this->parseModel($this->parent->$morphType);
+
+ // 主键数据
+ $pk = $this->parent->$morphKey;
+
+ $relationModel = (new $model)->relation($subRelation)->find($pk);
+
+ if ($relationModel) {
+ $relationModel->setParent(clone $this->parent);
+ }
+
+ return $relationModel;
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param string $operator 比较操作符
+ * @param integer $count 个数
+ * @param string $id 关联表的统计字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null)
+ {
+ return $this->parent;
+ }
+
+ /**
+ * 根据关联条件查询当前模型
+ * @access public
+ * @param mixed $where 查询条件(数组或者闭包)
+ * @param mixed $fields 字段
+ * @param string $joinType JOIN类型
+ * @param Query $query Query对象
+ * @return Query
+ */
+ public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null)
+ {
+ throw new Exception('relation not support: hasWhere');
+ }
+
+ /**
+ * 解析模型的完整命名空间
+ * @access protected
+ * @param string $model 模型名(或者完整类名)
+ * @return string
+ */
+ protected function parseModel(string $model): string
+ {
+ if (isset($this->alias[$model])) {
+ $model = $this->alias[$model];
+ }
+
+ if (false === strpos($model, '\\')) {
+ $path = explode('\\', get_class($this->parent));
+ array_pop($path);
+ array_push($path, Str::studly($model));
+ $model = implode('\\', $path);
+ }
+
+ return $model;
+ }
+
+ /**
+ * 设置多态别名
+ * @access public
+ * @param array $alias 别名定义
+ * @return $this
+ */
+ public function setAlias(array $alias)
+ {
+ $this->alias = $alias;
+
+ return $this;
+ }
+
+ /**
+ * 移除关联查询参数
+ * @access public
+ * @return $this
+ */
+ public function removeOption()
+ {
+ return $this;
+ }
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ * @throws Exception
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
+ {
+ $morphKey = $this->morphKey;
+ $morphType = $this->morphType;
+ $range = [];
+
+ foreach ($resultSet as $result) {
+ // 获取关联外键列表
+ if (!empty($result->$morphKey)) {
+ $range[$result->$morphType][] = $result->$morphKey;
+ }
+ }
+
+ if (!empty($range)) {
+
+ foreach ($range as $key => $val) {
+ // 多态类型映射
+ $model = $this->parseModel($key);
+ $obj = new $model;
+ $pk = $obj->getPk();
+ $list = $obj->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select($val);
+ $data = [];
+
+ foreach ($list as $k => $vo) {
+ $data[$vo->$pk] = $vo;
+ }
+
+ foreach ($resultSet as $result) {
+ if ($key == $result->$morphType) {
+ // 关联模型
+ if (!isset($data[$result->$morphKey])) {
+ $relationModel = null;
+ } else {
+ $relationModel = $data[$result->$morphKey];
+ $relationModel->setParent(clone $result);
+ $relationModel->exists(true);
+ }
+
+ $result->setRelation($relation, $relationModel);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询
+ * @access public
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void
+ {
+ // 多态类型映射
+ $model = $this->parseModel($result->{$this->morphType});
+
+ $this->eagerlyMorphToOne($model, $relation, $result, $subRelation, $cache);
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @return integer
+ */
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*')
+ {}
+
+ /**
+ * 多态MorphTo 关联模型预查询
+ * @access protected
+ * @param string $model 关联模型对象
+ * @param string $relation 关联名
+ * @param Model $result
+ * @param array $subRelation 子关联
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ protected function eagerlyMorphToOne(string $model, string $relation, Model $result, array $subRelation = [], array $cache = []): void
+ {
+ // 预载入关联查询 支持嵌套预载入
+ $pk = $this->parent->{$this->morphKey};
+ $data = (new $model)->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->find($pk);
+
+ if ($data) {
+ $data->setParent(clone $result);
+ $data->exists(true);
+ }
+
+ $result->setRelation($relation, $data ?: null);
+ }
+
+ /**
+ * 添加关联数据
+ * @access public
+ * @param Model $model 关联模型对象
+ * @param string $type 多态类型
+ * @return Model
+ */
+ public function associate(Model $model, string $type = ''): Model
+ {
+ $morphKey = $this->morphKey;
+ $morphType = $this->morphType;
+ $pk = $model->getPk();
+
+ $this->parent->setAttr($morphKey, $model->$pk);
+ $this->parent->setAttr($morphType, $type ?: get_class($model));
+ $this->parent->save();
+
+ return $this->parent->setRelation($this->relation, $model);
+ }
+
+ /**
+ * 注销关联数据
+ * @access public
+ * @return Model
+ */
+ public function dissociate(): Model
+ {
+ $morphKey = $this->morphKey;
+ $morphType = $this->morphType;
+
+ $this->parent->setAttr($morphKey, null);
+ $this->parent->setAttr($morphType, null);
+ $this->parent->save();
+
+ return $this->parent->setRelation($this->relation, null);
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/relation/MorphToMany.php b/vendor/topthink/think-orm/src/model/relation/MorphToMany.php
new file mode 100755
index 0000000..88bbf9a
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/MorphToMany.php
@@ -0,0 +1,458 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\model\relation;
+
+use Closure;
+use Exception;
+use think\db\BaseQuery as Query;
+use think\db\Raw;
+use think\Model;
+use think\model\Pivot;
+
+/**
+ * 多态多对多关联
+ */
+class MorphToMany extends BelongsToMany
+{
+
+ /**
+ * 多态字段名
+ * @var string
+ */
+ protected $morphType;
+
+ /**
+ * 多态模型名
+ * @var string
+ */
+ protected $morphClass;
+
+ /**
+ * 是否反向关联
+ * @var bool
+ */
+ protected $inverse;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Model $parent 上级模型对象
+ * @param string $model 模型名
+ * @param string $middle 中间表名/模型名
+ * @param string $morphKey 关联外键
+ * @param string $morphType 多态字段名
+ * @param string $localKey 当前模型关联键
+ * @param bool $inverse 反向关联
+ */
+ public function __construct(Model $parent, string $model, string $middle, string $morphType, string $morphKey, string $localKey, bool $inverse = false)
+ {
+ $this->morphType = $morphType;
+ $this->inverse = $inverse;
+ $this->morphClass = $inverse ? $model : get_class($parent);
+
+ $foreignKey = $inverse ? $morphKey : $localKey;
+ $localKey = $inverse ? $localKey : $morphKey;
+
+ parent::__construct($parent, $model, $middle, $foreignKey, $localKey);
+ }
+
+ /**
+ * 预载入关联查询(数据集)
+ * @access public
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
+ {
+ $pk = $resultSet[0]->getPk();
+ $range = [];
+
+ foreach ($resultSet as $result) {
+ // 获取关联外键列表
+ if (isset($result->$pk)) {
+ $range[] = $result->$pk;
+ }
+ }
+
+ if (!empty($range)) {
+ // 查询关联数据
+ $data = $this->eagerlyManyToMany([
+ ['pivot.' . $this->localKey, 'in', $range],
+ ['pivot.' . $this->morphType, '=', $this->morphClass],
+ ], $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ foreach ($resultSet as $result) {
+ if (!isset($data[$result->$pk])) {
+ $data[$result->$pk] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent));
+ }
+ }
+ }
+
+ /**
+ * 预载入关联查询(单个数据)
+ * @access public
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void
+ {
+ $pk = $result->getPk();
+
+ if (isset($result->$pk)) {
+ $pk = $result->$pk;
+ // 查询管理数据
+ $data = $this->eagerlyManyToMany([
+ ['pivot.' . $this->localKey, '=', $pk],
+ ['pivot.' . $this->morphType, '=', $this->morphClass],
+ ], $subRelation, $closure, $cache);
+
+ // 关联数据封装
+ if (!isset($data[$pk])) {
+ $data[$pk] = [];
+ }
+
+ $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent));
+ }
+ }
+
+ /**
+ * 关联统计
+ * @access public
+ * @param Model $result 数据对象
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return integer
+ */
+ public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): float
+ {
+ $pk = $result->getPk();
+
+ if (!isset($result->$pk)) {
+ return 0;
+ }
+
+ $pk = $result->$pk;
+
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
+ ['pivot.' . $this->localKey, '=', $pk],
+ ['pivot.' . $this->morphType, '=', $this->morphClass],
+ ])->$aggregate($field);
+ }
+
+ /**
+ * 获取关联统计子查询
+ * @access public
+ * @param Closure $closure 闭包
+ * @param string $aggregate 聚合查询方法
+ * @param string $field 字段
+ * @param string $name 统计字段别名
+ * @return string
+ */
+ public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure), $name);
+ }
+
+ return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
+ ['pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(false)->getTable() . '.' . $this->parent->getPk())],
+ ['pivot.' . $this->morphType, '=', $this->morphClass],
+ ])->fetchSql()->$aggregate($field);
+ }
+
+ /**
+ * BELONGS TO MANY 关联查询
+ * @access protected
+ * @param string $foreignKey 关联模型关联键
+ * @param string $localKey 当前模型关联键
+ * @param array $condition 关联查询条件
+ * @return Query
+ */
+ protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query
+ {
+ // 关联查询封装
+ $tableName = $this->query->getTable();
+ $table = $this->pivot->db()->getTable();
+ $fields = $this->getQueryFields($tableName);
+
+ if ($this->withLimit) {
+ $this->query->limit($this->withLimit);
+ }
+
+ $query = $this->query
+ ->field($fields)
+ ->tableField(true, $table, 'pivot', 'pivot__');
+
+ if (empty($this->baseQuery)) {
+ $relationFk = $this->query->getPk();
+ $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk)
+ ->where($condition);
+ }
+
+ return $query;
+ }
+
+ /**
+ * 多对多 关联模型预查询
+ * @access protected
+ * @param array $where 关联预查询条件
+ * @param array $subRelation 子关联
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @return array
+ */
+ protected function eagerlyManyToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array
+ {
+ if ($closure) {
+ $closure($this->getClosureType($closure));
+ }
+
+ // 预载入关联查询 支持嵌套预载入
+ $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)
+ ->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select();
+
+ // 组装模型数据
+ $data = [];
+ foreach ($list as $set) {
+ $pivot = [];
+ foreach ($set->getData() as $key => $val) {
+ if (strpos($key, '__')) {
+ [$name, $attr] = explode('__', $key, 2);
+ if ('pivot' == $name) {
+ $pivot[$attr] = $val;
+ unset($set->$key);
+ }
+ }
+ }
+
+ $key = $pivot[$this->localKey];
+
+ if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) {
+ continue;
+ }
+
+ $set->setRelation($this->pivotDataName, $this->newPivot($pivot));
+
+ $data[$key][] = $set;
+ }
+
+ return $data;
+ }
+
+ /**
+ * 附加关联的一个中间表数据
+ * @access public
+ * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键
+ * @param array $pivot 中间表额外数据
+ * @return array|Pivot
+ */
+ public function attach($data, array $pivot = [])
+ {
+ if (is_array($data)) {
+ if (key($data) === 0) {
+ $id = $data;
+ } else {
+ // 保存关联表数据
+ $model = new $this->model;
+ $id = $model->insertGetId($data);
+ }
+ } else if (is_numeric($data) || is_string($data)) {
+ // 根据关联表主键直接写入中间表
+ $id = $data;
+ } else if ($data instanceof Model) {
+ // 根据关联表主键直接写入中间表
+ $id = $data->getKey();
+ }
+
+ if (!empty($id)) {
+ // 保存中间表数据
+ $pivot[$this->localKey] = $this->parent->getKey();
+ $pivot[$this->morphType] = $this->morphClass;
+ $ids = (array) $id;
+
+ $result = [];
+
+ foreach ($ids as $id) {
+ $pivot[$this->foreignKey] = $id;
+
+ $this->pivot->replace()
+ ->exists(false)
+ ->data([])
+ ->save($pivot);
+ $result[] = $this->newPivot($pivot);
+ }
+
+ if (count($result) == 1) {
+ // 返回中间表模型对象
+ $result = $result[0];
+ }
+
+ return $result;
+ } else {
+ throw new Exception('miss relation data');
+ }
+ }
+
+ /**
+ * 判断是否存在关联数据
+ * @access public
+ * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键
+ * @return Pivot|false
+ */
+ public function attached($data)
+ {
+ if ($data instanceof Model) {
+ $id = $data->getKey();
+ } else {
+ $id = $data;
+ }
+
+ $pivot = $this->pivot
+ ->where($this->localKey, $this->parent->getKey())
+ ->where($this->morphType, $this->morphClass)
+ ->where($this->foreignKey, $id)
+ ->find();
+
+ return $pivot ?: false;
+ }
+
+ /**
+ * 解除关联的一个中间表数据
+ * @access public
+ * @param integer|array $data 数据 可以使用关联对象的主键
+ * @param bool $relationDel 是否同时删除关联表数据
+ * @return integer
+ */
+ public function detach($data = null, bool $relationDel = false): int
+ {
+ if (is_array($data)) {
+ $id = $data;
+ } else if (is_numeric($data) || is_string($data)) {
+ // 根据关联表主键直接写入中间表
+ $id = $data;
+ } else if ($data instanceof Model) {
+ // 根据关联表主键直接写入中间表
+ $id = $data->getKey();
+ }
+
+ // 删除中间表数据
+ $pivot = [
+ [$this->localKey, '=', $this->parent->getKey()],
+ [$this->morphType, '=', $this->morphClass],
+ ];
+
+ if (isset($id)) {
+ $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id];
+ }
+
+ $result = $this->pivot->where($pivot)->delete();
+
+ // 删除关联表数据
+ if (isset($id) && $relationDel) {
+ $model = $this->model;
+ $model::destroy($id);
+ }
+
+ return $result;
+ }
+
+ /**
+ * 数据同步
+ * @access public
+ * @param array $ids
+ * @param bool $detaching
+ * @return array
+ */
+ public function sync(array $ids, bool $detaching = true): array
+ {
+ $changes = [
+ 'attached' => [],
+ 'detached' => [],
+ 'updated' => [],
+ ];
+
+ $current = $this->pivot
+ ->where($this->localKey, $this->parent->getKey())
+ ->where($this->morphType, $this->morphClass)
+ ->column($this->foreignKey);
+
+ $records = [];
+
+ foreach ($ids as $key => $value) {
+ if (!is_array($value)) {
+ $records[$value] = [];
+ } else {
+ $records[$key] = $value;
+ }
+ }
+
+ $detach = array_diff($current, array_keys($records));
+
+ if ($detaching && count($detach) > 0) {
+ $this->detach($detach);
+ $changes['detached'] = $detach;
+ }
+
+ foreach ($records as $id => $attributes) {
+ if (!in_array($id, $current)) {
+ $this->attach($id, $attributes);
+ $changes['attached'][] = $id;
+ } else if (count($attributes) > 0 && $this->attach($id, $attributes)) {
+ $changes['updated'][] = $id;
+ }
+ }
+
+ return $changes;
+ }
+
+ /**
+ * 执行基础查询(仅执行一次)
+ * @access protected
+ * @return void
+ */
+ protected function baseQuery(): void
+ {
+ if (empty($this->baseQuery)) {
+ $foreignKey = $this->foreignKey;
+ $localKey = $this->localKey;
+
+ // 关联查询
+ $this->belongsToManyQuery($foreignKey, $localKey, [
+ ['pivot.' . $localKey, '=', $this->parent->getKey()],
+ ['pivot.' . $this->morphType, '=', $this->morphClass],
+ ]);
+
+ $this->baseQuery = true;
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/model/relation/OneToOne.php b/vendor/topthink/think-orm/src/model/relation/OneToOne.php
new file mode 100755
index 0000000..efb2359
--- /dev/null
+++ b/vendor/topthink/think-orm/src/model/relation/OneToOne.php
@@ -0,0 +1,332 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\model\relation;
+
+use Closure;
+use think\db\BaseQuery as Query;
+use think\db\exception\DbException as Exception;
+use think\helper\Str;
+use think\Model;
+use think\model\Relation;
+
+/**
+ * 一对一关联基础类
+ * @package think\model\relation
+ */
+abstract class OneToOne extends Relation
+{
+ /**
+ * JOIN类型
+ * @var string
+ */
+ protected $joinType = 'INNER';
+
+ /**
+ * 绑定的关联属性
+ * @var array
+ */
+ protected $bindAttr = [];
+
+ /**
+ * 关联名
+ * @var string
+ */
+ protected $relation;
+
+ /**
+ * 设置join类型
+ * @access public
+ * @param string $type JOIN类型
+ * @return $this
+ */
+ public function joinType(string $type)
+ {
+ $this->joinType = $type;
+ return $this;
+ }
+
+ /**
+ * 预载入关联查询(JOIN方式)
+ * @access public
+ * @param Query $query 查询对象
+ * @param string $relation 关联名
+ * @param mixed $field 关联字段
+ * @param string $joinType JOIN方式
+ * @param Closure $closure 闭包条件
+ * @param bool $first
+ * @return void
+ */
+ public function eagerly(Query $query, string $relation, $field = true, string $joinType = '', Closure $closure = null, bool $first = false): void
+ {
+ $name = Str::snake(class_basename($this->parent));
+
+ if ($first) {
+ $table = $query->getTable();
+ $query->table([$table => $name]);
+
+ if ($query->getOptions('field')) {
+ $masterField = $query->getOptions('field');
+ $query->removeOption('field');
+ } else {
+ $masterField = true;
+ }
+
+ $query->tableField($masterField, $table, $name);
+ }
+
+ // 预载入封装
+ $joinTable = $this->query->getTable();
+ $joinAlias = $relation;
+ $joinType = $joinType ?: $this->joinType;
+
+ $query->via($joinAlias);
+
+ if ($this instanceof BelongsTo) {
+ $joinOn = $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey;
+ } else {
+ $joinOn = $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey;
+ }
+
+ if ($closure) {
+ // 执行闭包查询
+ $closure($this->getClosureType($closure));
+
+ // 使用withField指定获取关联的字段
+ if ($this->withField) {
+ $field = $this->withField;
+ }
+ }
+
+ $query->join([$joinTable => $joinAlias], $joinOn, $joinType)
+ ->tableField($field, $joinTable, $joinAlias, $relation . '__');
+ }
+
+ /**
+ * 预载入关联查询(数据集)
+ * @access protected
+ * @param array $resultSet
+ * @param string $relation
+ * @param array $subRelation
+ * @param Closure $closure
+ * @return mixed
+ */
+ abstract protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null);
+
+ /**
+ * 预载入关联查询(数据)
+ * @access protected
+ * @param Model $result
+ * @param string $relation
+ * @param array $subRelation
+ * @param Closure $closure
+ * @return mixed
+ */
+ abstract protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null);
+
+ /**
+ * 预载入关联查询(数据集)
+ * @access public
+ * @param array $resultSet 数据集
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @param bool $join 是否为JOIN方式
+ * @return void
+ */
+ public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = [], bool $join = false): void
+ {
+ if ($join) {
+ // 模型JOIN关联组装
+ foreach ($resultSet as $result) {
+ $this->match($this->model, $relation, $result);
+ }
+ } else {
+ // IN查询
+ $this->eagerlySet($resultSet, $relation, $subRelation, $closure, $cache);
+ }
+ }
+
+ /**
+ * 预载入关联查询(数据)
+ * @access public
+ * @param Model $result 数据对象
+ * @param string $relation 当前关联名
+ * @param array $subRelation 子关联名
+ * @param Closure $closure 闭包
+ * @param array $cache 关联缓存
+ * @param bool $join 是否为JOIN方式
+ * @return void
+ */
+ public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = [], bool $join = false): void
+ {
+ if ($join) {
+ // 模型JOIN关联组装
+ $this->match($this->model, $relation, $result);
+ } else {
+ // IN查询
+ $this->eagerlyOne($result, $relation, $subRelation, $closure, $cache);
+ }
+ }
+
+ /**
+ * 保存(新增)当前关联数据对象
+ * @access public
+ * @param mixed $data 数据 可以使用数组 关联模型对象
+ * @param boolean $replace 是否自动识别更新和写入
+ * @return Model|false
+ */
+ public function save($data, bool $replace = true)
+ {
+ if ($data instanceof Model) {
+ $data = $data->getData();
+ }
+
+ $model = new $this->model;
+ // 保存关联表数据
+ $data[$this->foreignKey] = $this->parent->{$this->localKey};
+
+ return $model->replace($replace)->save($data) ? $model : false;
+ }
+
+ /**
+ * 绑定关联表的属性到父模型属性
+ * @access public
+ * @param array $attr 要绑定的属性列表
+ * @return $this
+ */
+ public function bind(array $attr)
+ {
+ $this->bindAttr = $attr;
+
+ return $this;
+ }
+
+ /**
+ * 获取绑定属性
+ * @access public
+ * @return array
+ */
+ public function getBindAttr(): array
+ {
+ return $this->bindAttr;
+ }
+
+ /**
+ * 一对一 关联模型预查询拼装
+ * @access public
+ * @param string $model 模型名称
+ * @param string $relation 关联名
+ * @param Model $result 模型对象实例
+ * @return void
+ */
+ protected function match(string $model, string $relation, Model $result): void
+ {
+ // 重新组装模型数据
+ foreach ($result->getData() as $key => $val) {
+ if (strpos($key, '__')) {
+ [$name, $attr] = explode('__', $key, 2);
+ if ($name == $relation) {
+ $list[$name][$attr] = $val;
+ unset($result->$key);
+ }
+ }
+ }
+
+ if (isset($list[$relation])) {
+ $array = array_unique($list[$relation]);
+
+ if (count($array) == 1 && null === current($array)) {
+ $relationModel = null;
+ } else {
+ $relationModel = new $model($list[$relation]);
+ $relationModel->setParent(clone $result);
+ $relationModel->exists(true);
+ }
+
+ if (!empty($this->bindAttr)) {
+ $this->bindAttr($result, $relationModel);
+ }
+ } else {
+ $relationModel = null;
+ }
+
+ $result->setRelation($relation, $relationModel);
+ }
+
+ /**
+ * 绑定关联属性到父模型
+ * @access protected
+ * @param Model $result 父模型对象
+ * @param Model $model 关联模型对象
+ * @return void
+ * @throws Exception
+ */
+ protected function bindAttr(Model $result, Model $model = null): void
+ {
+ foreach ($this->bindAttr as $key => $attr) {
+ $key = is_numeric($key) ? $attr : $key;
+ $value = $result->getOrigin($key);
+
+ if (!is_null($value)) {
+ throw new Exception('bind attr has exists:' . $key);
+ }
+
+ $result->setAttr($key, $model ? $model->$attr : null);
+ }
+ }
+
+ /**
+ * 一对一 关联模型预查询(IN方式)
+ * @access public
+ * @param array $where 关联预查询条件
+ * @param string $key 关联键名
+ * @param array $subRelation 子关联
+ * @param Closure $closure
+ * @param array $cache 关联缓存
+ * @return array
+ */
+ protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = [])
+ {
+ // 预载入关联查询 支持嵌套预载入
+ if ($closure) {
+ $this->baseQuery = true;
+ $closure($this->getClosureType($closure));
+ }
+
+ if ($this->withField) {
+ $this->query->field($this->withField);
+ }
+
+ if ($this->query->getOptions('order')) {
+ $this->query->group($key);
+ }
+
+ $list = $this->query
+ ->where($where)
+ ->with($subRelation)
+ ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
+ ->select();
+
+ // 组装模型数据
+ $data = [];
+
+ foreach ($list as $set) {
+ if (!isset($data[$set->$key])) {
+ $data[$set->$key] = $set;
+ }
+ }
+
+ return $data;
+ }
+
+}
diff --git a/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php b/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php
new file mode 100755
index 0000000..6d55c39
--- /dev/null
+++ b/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php
@@ -0,0 +1,209 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\paginator\driver;
+
+use think\Paginator;
+
+/**
+ * Bootstrap 分页驱动
+ */
+class Bootstrap extends Paginator
+{
+
+ /**
+ * 上一页按钮
+ * @param string $text
+ * @return string
+ */
+ protected function getPreviousButton(string $text = "«"): string
+ {
+
+ if ($this->currentPage() <= 1) {
+ return $this->getDisabledTextWrapper($text);
+ }
+
+ $url = $this->url(
+ $this->currentPage() - 1
+ );
+
+ return $this->getPageLinkWrapper($url, $text);
+ }
+
+ /**
+ * 下一页按钮
+ * @param string $text
+ * @return string
+ */
+ protected function getNextButton(string $text = '»'): string
+ {
+ if (!$this->hasMore) {
+ return $this->getDisabledTextWrapper($text);
+ }
+
+ $url = $this->url($this->currentPage() + 1);
+
+ return $this->getPageLinkWrapper($url, $text);
+ }
+
+ /**
+ * 页码按钮
+ * @return string
+ */
+ protected function getLinks(): string
+ {
+ if ($this->simple) {
+ return '';
+ }
+
+ $block = [
+ 'first' => null,
+ 'slider' => null,
+ 'last' => null,
+ ];
+
+ $side = 3;
+ $window = $side * 2;
+
+ if ($this->lastPage < $window + 6) {
+ $block['first'] = $this->getUrlRange(1, $this->lastPage);
+ } elseif ($this->currentPage <= $window) {
+ $block['first'] = $this->getUrlRange(1, $window + 2);
+ $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage);
+ } elseif ($this->currentPage > ($this->lastPage - $window)) {
+ $block['first'] = $this->getUrlRange(1, 2);
+ $block['last'] = $this->getUrlRange($this->lastPage - ($window + 2), $this->lastPage);
+ } else {
+ $block['first'] = $this->getUrlRange(1, 2);
+ $block['slider'] = $this->getUrlRange($this->currentPage - $side, $this->currentPage + $side);
+ $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage);
+ }
+
+ $html = '';
+
+ if (is_array($block['first'])) {
+ $html .= $this->getUrlLinks($block['first']);
+ }
+
+ if (is_array($block['slider'])) {
+ $html .= $this->getDots();
+ $html .= $this->getUrlLinks($block['slider']);
+ }
+
+ if (is_array($block['last'])) {
+ $html .= $this->getDots();
+ $html .= $this->getUrlLinks($block['last']);
+ }
+
+ return $html;
+ }
+
+ /**
+ * 渲染分页html
+ * @return mixed
+ */
+ public function render()
+ {
+ if ($this->hasPages()) {
+ if ($this->simple) {
+ return sprintf(
+ '<ul class="pager">%s %s</ul>',
+ $this->getPreviousButton(),
+ $this->getNextButton()
+ );
+ } else {
+ return sprintf(
+ '<ul class="pagination">%s %s %s</ul>',
+ $this->getPreviousButton(),
+ $this->getLinks(),
+ $this->getNextButton()
+ );
+ }
+ }
+ }
+
+ /**
+ * 生成一个可点击的按钮
+ *
+ * @param string $url
+ * @param string $page
+ * @return string
+ */
+ protected function getAvailablePageWrapper(string $url, string $page): string
+ {
+ return '<li><a href="' . htmlentities($url) . '">' . $page . '</a></li>';
+ }
+
+ /**
+ * 生成一个禁用的按钮
+ *
+ * @param string $text
+ * @return string
+ */
+ protected function getDisabledTextWrapper(string $text): string
+ {
+ return '<li class="disabled"><span>' . $text . '</span></li>';
+ }
+
+ /**
+ * 生成一个激活的按钮
+ *
+ * @param string $text
+ * @return string
+ */
+ protected function getActivePageWrapper(string $text): string
+ {
+ return '<li class="active"><span>' . $text . '</span></li>';
+ }
+
+ /**
+ * 生成省略号按钮
+ *
+ * @return string
+ */
+ protected function getDots(): string
+ {
+ return $this->getDisabledTextWrapper('...');
+ }
+
+ /**
+ * 批量生成页码按钮.
+ *
+ * @param array $urls
+ * @return string
+ */
+ protected function getUrlLinks(array $urls): string
+ {
+ $html = '';
+
+ foreach ($urls as $page => $url) {
+ $html .= $this->getPageLinkWrapper($url, $page);
+ }
+
+ return $html;
+ }
+
+ /**
+ * 生成普通页码按钮
+ *
+ * @param string $url
+ * @param string $page
+ * @return string
+ */
+ protected function getPageLinkWrapper(string $url, string $page): string
+ {
+ if ($this->currentPage() == $page) {
+ return $this->getActivePageWrapper($page);
+ }
+
+ return $this->getAvailablePageWrapper($url, $page);
+ }
+}
diff --git a/vendor/topthink/think-queue/.gitignore b/vendor/topthink/think-queue/.gitignore
new file mode 100755
index 0000000..4aec782
--- /dev/null
+++ b/vendor/topthink/think-queue/.gitignore
@@ -0,0 +1,4 @@
+/vendor/
+/.idea/
+/composer.lock
+/thinkphp/
diff --git a/vendor/topthink/think-queue/LICENSE b/vendor/topthink/think-queue/LICENSE
new file mode 100755
index 0000000..8dada3e
--- /dev/null
+++ b/vendor/topthink/think-queue/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/topthink/think-queue/README.md b/vendor/topthink/think-queue/README.md
new file mode 100755
index 0000000..08266d8
--- /dev/null
+++ b/vendor/topthink/think-queue/README.md
@@ -0,0 +1,118 @@
+# think-queue for ThinkPHP6
+
+## 安装
+
+> composer require topthink/think-queue
+
+## 配置
+
+> 配置文件位于 `config/queue.php`
+
+### 公共配置
+
+```
+[
+ 'default'=>'sync' //驱动类型,可选择 sync(默认):同步执行,database:数据库驱动,redis:Redis驱动,topthink:Topthink驱动
+ //或其他自定义的完整的类名
+]
+```
+
+### 驱动配置
+> 各个驱动的具体可用配置项在`think\queue\connector`目录下各个驱动类里的`options`属性中,写在上面的`queue`配置里即可覆盖
+
+
+## 创建任务类
+> 单模块项目推荐使用 `app\job` 作为任务类的命名空间
+> 多模块项目可用使用 `app\module\job` 作为任务类的命名空间
+> 也可以放在任意可以自动加载到的地方
+
+任务类不需继承任何类,如果这个类只有一个任务,那么就只需要提供一个`fire`方法就可以了,如果有多个小任务,就写多个方法,下面发布任务的时候会有区别
+每个方法会传入两个参数 `think\queue\Job $job`(当前的任务对象) 和 `$data`(发布任务时自定义的数据)
+
+还有个可选的任务失败执行的方法 `failed` 传入的参数为`$data`(发布任务时自定义的数据)
+
+### 下面写两个例子
+
+```
+namespace app\job;
+
+use think\queue\Job;
+
+class Job1{
+
+ public function fire(Job $job, $data){
+
+ //....这里执行具体的任务
+
+ if ($job->attempts() > 3) {
+ //通过这个方法可以检查这个任务已经重试了几次了
+ }
+
+
+ //如果任务执行成功后 记得删除任务,不然这个任务会重复执行,直到达到最大重试次数后失败后,执行failed方法
+ $job->delete();
+
+ // 也可以重新发布这个任务
+ $job->release($delay); //$delay为延迟时间
+
+ }
+
+ public function failed($data){
+
+ // ...任务达到最大重试次数后,失败了
+ }
+
+}
+
+```
+
+```
+
+namespace app\lib\job;
+
+use think\queue\Job;
+
+class Job2{
+
+ public function task1(Job $job, $data){
+
+
+ }
+
+ public function task2(Job $job, $data){
+
+
+ }
+
+ public function failed($data){
+
+
+ }
+
+}
+
+```
+
+
+## 发布任务
+> `think\Queue::push($job, $data = '', $queue = null)` 和 `think\Queue::later($delay, $job, $data = '', $queue = null)` 两个方法,前者是立即执行,后者是在`$delay`秒后执行
+
+`$job` 是任务名
+单模块的,且命名空间是`app\job`的,比如上面的例子一,写`Job1`类名即可
+多模块的,且命名空间是`app\module\job`的,写`model/Job1`即可
+其他的需要些完整的类名,比如上面的例子二,需要写完整的类名`app\lib\job\Job2`
+如果一个任务类里有多个小任务的话,如上面的例子二,需要用@+方法名`app\lib\job\Job2@task1`、`app\lib\job\Job2@task2`
+
+`$data` 是你要传到任务里的参数
+
+`$queue` 队列名,指定这个任务是在哪个队列上执行,同下面监控队列的时候指定的队列名,可不填
+
+## 监听任务并执行
+
+> php think queue:listen
+
+> php think queue:work
+
+两种,具体的可选参数可以输入命令加 --help 查看
+
+>可配合supervisor使用,保证进程常驻
diff --git a/vendor/topthink/think-queue/composer.json b/vendor/topthink/think-queue/composer.json
new file mode 100755
index 0000000..c128c25
--- /dev/null
+++ b/vendor/topthink/think-queue/composer.json
@@ -0,0 +1,47 @@
+{
+ "name": "topthink/think-queue",
+ "description": "The ThinkPHP5 Queue Package",
+ "authors": [
+ {
+ "name": "yunwuxin",
+ "email": "448901948@qq.com"
+ }
+ ],
+ "license": "Apache-2.0",
+ "autoload": {
+ "psr-4": {
+ "think\\": "src"
+ },
+ "files": [
+ "src/common.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "think\\test\\queue\\": "tests"
+ }
+ },
+ "minimum-stability": "dev",
+ "require": {
+ "topthink/think-helper": "^3.0.4",
+ "topthink/framework": "^6.0.0",
+ "topthink/think-factory": "^1.0.0",
+ "symfony/process": "^4.2",
+ "nesbot/carbon": "^2.16"
+ },
+ "extra": {
+ "think": {
+ "services": [
+ "think\\queue\\Service"
+ ],
+ "config": {
+ "queue": "src/config.php"
+ }
+ }
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.2",
+ "mockery/mockery": "^1.2",
+ "topthink/think-migration": "^3.0.0"
+ }
+}
diff --git a/vendor/topthink/think-queue/phpunit.xml.dist b/vendor/topthink/think-queue/phpunit.xml.dist
new file mode 100755
index 0000000..0234fbc
--- /dev/null
+++ b/vendor/topthink/think-queue/phpunit.xml.dist
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ beStrictAboutTestsThatDoNotTestAnything="false"
+ bootstrap="tests/bootstrap.php"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnError="false"
+ stopOnFailure="false"
+ verbose="true"
+>
+ <testsuites>
+ <testsuite name="Think Queue Test Suite">
+ <directory suffix="Test.php">./tests</directory>
+ </testsuite>
+ </testsuites>
+ <filter>
+ <whitelist processUncoveredFilesFromWhitelist="true">
+ <directory suffix=".php">./src</directory>
+ <exclude>
+ <file>./src/queue/Service.php</file>
+ <file>./src/common.php</file>
+ <file>./src/config.php</file>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/vendor/topthink/think-queue/src/Queue.php b/vendor/topthink/think-queue/src/Queue.php
new file mode 100755
index 0000000..175d89f
--- /dev/null
+++ b/vendor/topthink/think-queue/src/Queue.php
@@ -0,0 +1,77 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use InvalidArgumentException;
+use think\cache\driver\Redis;
+use think\helper\Str;
+use think\queue\Connector;
+use think\queue\connector\Database;
+
+/**
+ * Class Queue
+ * @package think\queue
+ *
+ * @method Connector driver($driver = null)
+ * @mixin Database
+ * @mixin Redis
+ */
+class Queue extends Factory
+{
+ protected $namespace = '\\think\\queue\\connector\\';
+
+ /**
+ * Get the queue connector configuration.
+ *
+ * @param string $name
+ * @return array
+ */
+ protected function getConfig($name)
+ {
+ return $this->app->config->get("queue.connections.{$name}", ['driver' => 'sync']);
+ }
+
+ protected function createDriver($name)
+ {
+ $driver = $this->getConfig($name)['driver'];
+
+ $class = false !== strpos($driver, '\\') ? $driver : $this->namespace . Str::studly($driver);
+
+ /** @var Connector $driver */
+ if (class_exists($class)) {
+ $driver = $this->app->invokeClass($class, [$this->getConfig($driver)]);
+
+ return $driver->setApp($this->app)
+ ->setConnection($name);
+ }
+
+ throw new InvalidArgumentException("Driver [$driver] not supported.");
+ }
+
+ /**
+ * @param null|string $name
+ * @return Connector
+ */
+ public function connection($name = null)
+ {
+ return $this->driver($name);
+ }
+
+ /**
+ * 默认驱动
+ * @return string
+ */
+ public function getDefaultDriver()
+ {
+ return $this->app->config->get('queue.default');
+ }
+}
diff --git a/vendor/topthink/think-queue/src/common.php b/vendor/topthink/think-queue/src/common.php
new file mode 100755
index 0000000..207b0b8
--- /dev/null
+++ b/vendor/topthink/think-queue/src/common.php
@@ -0,0 +1,31 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+use think\facade\Queue;
+
+if (!function_exists('queue')) {
+
+ /**
+ * 添加到队列
+ * @param $job
+ * @param string $data
+ * @param int $delay
+ * @param null $queue
+ */
+ function queue($job, $data = '', $delay = 0, $queue = null)
+ {
+ if ($delay > 0) {
+ Queue::later($delay, $job, $data, $queue);
+ } else {
+ Queue::push($job, $data, $queue);
+ }
+ }
+}
diff --git a/vendor/topthink/think-queue/src/config.php b/vendor/topthink/think-queue/src/config.php
new file mode 100755
index 0000000..e2f25c8
--- /dev/null
+++ b/vendor/topthink/think-queue/src/config.php
@@ -0,0 +1,38 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+return [
+ 'default' => 'sync',
+ 'connections' => [
+ 'sync' => [
+ 'driver' => 'sync',
+ ],
+ 'database' => [
+ 'driver' => 'database',
+ 'queue' => 'default',
+ 'table' => 'jobs',
+ ],
+ 'redis' => [
+ 'driver' => 'redis',
+ 'queue' => 'default',
+ 'host' => '127.0.0.1',
+ 'port' => 6379,
+ 'password' => '',
+ 'select' => 0,
+ 'timeout' => 0,
+ 'persistent' => false,
+ ],
+ ],
+ 'failed' => [
+ 'type' => 'none',
+ 'table' => 'failed_jobs',
+ ],
+];
diff --git a/vendor/topthink/think-queue/src/facade/Queue.php b/vendor/topthink/think-queue/src/facade/Queue.php
new file mode 100755
index 0000000..23c2c30
--- /dev/null
+++ b/vendor/topthink/think-queue/src/facade/Queue.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace think\facade;
+
+use think\Facade;
+
+/**
+ * Class Queue
+ * @package think\facade
+ * @mixin \think\Queue
+ */
+class Queue extends Facade
+{
+ protected static function getFacadeClass()
+ {
+ return 'queue';
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/CallQueuedHandler.php b/vendor/topthink/think-queue/src/queue/CallQueuedHandler.php
new file mode 100755
index 0000000..0f1a627
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/CallQueuedHandler.php
@@ -0,0 +1,36 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue;
+
+class CallQueuedHandler
+{
+
+ public function call(Job $job, array $data)
+ {
+ $command = unserialize($data['command']);
+
+ call_user_func([$command, 'handle']);
+
+ if (!$job->isDeletedOrReleased()) {
+ $job->delete();
+ }
+ }
+
+ public function failed(array $data)
+ {
+ $command = unserialize($data['command']);
+
+ if (method_exists($command, 'failed')) {
+ $command->failed();
+ }
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/Connector.php b/vendor/topthink/think-queue/src/queue/Connector.php
new file mode 100755
index 0000000..618500e
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/Connector.php
@@ -0,0 +1,163 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue;
+
+use DateTimeInterface;
+use InvalidArgumentException;
+use think\App;
+
+abstract class Connector
+{
+ /** @var App */
+ protected $app;
+
+ /**
+ * The connector name for the queue.
+ *
+ * @var string
+ */
+ protected $connection;
+
+ protected $options = [];
+
+ abstract public function size($queue);
+
+ abstract public function push($job, $data = '', $queue = null);
+
+ public function pushOn($queue, $job, $data = '')
+ {
+ return $this->push($job, $data, $queue);
+ }
+
+ abstract public function pushRaw($payload, $queue = null, array $options = []);
+
+ abstract public function later($delay, $job, $data = '', $queue = null);
+
+ public function laterOn($queue, $delay, $job, $data = '')
+ {
+ return $this->later($delay, $job, $data, $queue);
+ }
+
+ public function bulk($jobs, $data = '', $queue = null)
+ {
+ foreach ((array) $jobs as $job) {
+ $this->push($job, $data, $queue);
+ }
+ }
+
+ abstract public function pop($queue = null);
+
+ protected function createPayload($job, $data = '')
+ {
+ $payload = $this->createPayloadArray($job, $data);
+
+ $payload = json_encode($payload);
+
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new InvalidArgumentException('Unable to create payload: ' . json_last_error_msg());
+ }
+
+ return $payload;
+ }
+
+ protected function createPayloadArray($job, $data = '')
+ {
+ return is_object($job)
+ ? $this->createObjectPayload($job)
+ : $this->createPlainPayload($job, $data);
+ }
+
+ protected function createPlainPayload($job, $data)
+ {
+ return [
+ 'job' => $job,
+ 'maxTries' => null,
+ 'timeout' => null,
+ 'data' => $data,
+ ];
+ }
+
+ protected function createObjectPayload($job)
+ {
+ $payload = [
+ 'job' => 'think\queue\CallQueuedHandler@call',
+ 'maxTries' => $job->tries ?? null,
+ 'timeout' => $job->timeout ?? null,
+ 'timeoutAt' => $this->getJobExpiration($job),
+ 'data' => [
+ 'commandName' => $job,
+ 'command' => $job,
+ ],
+ ];
+
+ return array_merge($payload, [
+ 'data' => [
+ 'commandName' => get_class($job),
+ 'command' => serialize(clone $job),
+ ],
+ ]);
+ }
+
+ public function getJobExpiration($job)
+ {
+ if (!method_exists($job, 'retryUntil') && !isset($job->timeoutAt)) {
+ return;
+ }
+
+ $expiration = $job->timeoutAt ?? $job->retryUntil();
+
+ return $expiration instanceof DateTimeInterface
+ ? $expiration->getTimestamp() : $expiration;
+ }
+
+ protected function setMeta($payload, $key, $value)
+ {
+ $payload = json_decode($payload, true);
+ $payload[$key] = $value;
+ $payload = json_encode($payload);
+
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new InvalidArgumentException('Unable to create payload: ' . json_last_error_msg());
+ }
+
+ return $payload;
+ }
+
+ public function setApp(App $app)
+ {
+ $this->app = $app;
+ return $this;
+ }
+
+ /**
+ * Get the connector name for the queue.
+ *
+ * @return string
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ /**
+ * Set the connector name for the queue.
+ *
+ * @param string $name
+ * @return $this
+ */
+ public function setConnection($name)
+ {
+ $this->connection = $name;
+
+ return $this;
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/FailedJob.php b/vendor/topthink/think-queue/src/queue/FailedJob.php
new file mode 100755
index 0000000..e03f816
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/FailedJob.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace think\queue;
+
+abstract class FailedJob
+{
+ /**
+ * Log a failed job into storage.
+ *
+ * @param string $connection
+ * @param string $queue
+ * @param string $payload
+ * @param \Exception $exception
+ * @return int|null
+ */
+ abstract public function log($connection, $queue, $payload, $exception);
+
+ /**
+ * Get a list of all of the failed jobs.
+ *
+ * @return array
+ */
+ abstract public function all();
+
+ /**
+ * Get a single failed job.
+ *
+ * @param mixed $id
+ * @return object|null
+ */
+ abstract public function find($id);
+
+ /**
+ * Delete a single failed job from storage.
+ *
+ * @param mixed $id
+ * @return bool
+ */
+ abstract public function forget($id);
+
+ /**
+ * Flush all of the failed jobs from storage.
+ *
+ * @return void
+ */
+ abstract public function flush();
+}
diff --git a/vendor/topthink/think-queue/src/queue/InteractsWithTime.php b/vendor/topthink/think-queue/src/queue/InteractsWithTime.php
new file mode 100755
index 0000000..04602c9
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/InteractsWithTime.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace think\queue;
+
+use Carbon\Carbon;
+use DateInterval;
+use DateTimeInterface;
+
+trait InteractsWithTime
+{
+ /**
+ * Get the number of seconds until the given DateTime.
+ *
+ * @param DateTimeInterface|DateInterval|int $delay
+ * @return int
+ */
+ protected function secondsUntil($delay)
+ {
+ $delay = $this->parseDateInterval($delay);
+
+ return $delay instanceof DateTimeInterface
+ ? max(0, $delay->getTimestamp() - $this->currentTime())
+ : (int) $delay;
+ }
+
+ /**
+ * Get the "available at" UNIX timestamp.
+ *
+ * @param DateTimeInterface|DateInterval|int $delay
+ * @return int
+ */
+ protected function availableAt($delay = 0)
+ {
+ $delay = $this->parseDateInterval($delay);
+
+ return $delay instanceof DateTimeInterface
+ ? $delay->getTimestamp()
+ : Carbon::now()->addRealSeconds($delay)->getTimestamp();
+ }
+
+ /**
+ * If the given value is an interval, convert it to a DateTime instance.
+ *
+ * @param DateTimeInterface|DateInterval|int $delay
+ * @return DateTimeInterface|int
+ */
+ protected function parseDateInterval($delay)
+ {
+ if ($delay instanceof DateInterval) {
+ $delay = Carbon::now()->add($delay);
+ }
+
+ return $delay;
+ }
+
+ /**
+ * Get the current system time as a UNIX timestamp.
+ *
+ * @return int
+ */
+ protected function currentTime()
+ {
+ return Carbon::now()->getTimestamp();
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/Job.php b/vendor/topthink/think-queue/src/queue/Job.php
new file mode 100755
index 0000000..27357f3
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/Job.php
@@ -0,0 +1,282 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue;
+
+use Exception;
+use think\App;
+
+abstract class Job
+{
+
+ /**
+ * The job handler instance.
+ * @var mixed
+ */
+ protected $instance;
+
+ /**
+ * @var App
+ */
+ protected $app;
+
+ /**
+ * The name of the queue the job belongs to.
+ * @var string
+ */
+ protected $queue;
+
+ /**
+ * The name of the connection the job belongs to.
+ */
+ protected $connection;
+
+ /**
+ * Indicates if the job has been deleted.
+ * @var bool
+ */
+ protected $deleted = false;
+
+ /**
+ * Indicates if the job has been released.
+ * @var bool
+ */
+ protected $released = false;
+
+ /**
+ * Indicates if the job has failed.
+ *
+ * @var bool
+ */
+ protected $failed = false;
+
+ /**
+ * Get the decoded body of the job.
+ *
+ * @return array
+ */
+ public function payload()
+ {
+ return json_decode($this->getRawBody(), true);
+ }
+
+ /**
+ * Fire the job.
+ * @return void
+ */
+ public function fire()
+ {
+ $payload = $this->payload();
+
+ list($class, $method) = $this->parseJob($payload['job']);
+
+ $this->instance = $this->resolve($class);
+ if ($this->instance) {
+ $this->instance->{$method}($this, $payload['data']);
+ }
+ }
+
+ /**
+ * Delete the job from the queue.
+ * @return void
+ */
+ public function delete()
+ {
+ $this->deleted = true;
+ }
+
+ /**
+ * Determine if the job has been deleted.
+ * @return bool
+ */
+ public function isDeleted()
+ {
+ return $this->deleted;
+ }
+
+ /**
+ * Release the job back into the queue.
+ * @param int $delay
+ * @return void
+ */
+ public function release($delay = 0)
+ {
+ $this->released = true;
+ }
+
+ /**
+ * Determine if the job was released back into the queue.
+ * @return bool
+ */
+ public function isReleased()
+ {
+ return $this->released;
+ }
+
+ /**
+ * Determine if the job has been deleted or released.
+ * @return bool
+ */
+ public function isDeletedOrReleased()
+ {
+ return $this->isDeleted() || $this->isReleased();
+ }
+
+ /**
+ * Get the job identifier.
+ *
+ * @return string
+ */
+ abstract public function getJobId();
+
+ /**
+ * Get the number of times the job has been attempted.
+ * @return int
+ */
+ abstract public function attempts();
+
+ /**
+ * Get the raw body string for the job.
+ * @return string
+ */
+ abstract public function getRawBody();
+
+ /**
+ * Parse the job declaration into class and method.
+ * @param string $job
+ * @return array
+ */
+ protected function parseJob($job)
+ {
+ $segments = explode('@', $job);
+
+ return count($segments) > 1 ? $segments : [$segments[0], 'fire'];
+ }
+
+ /**
+ * Resolve the given job handler.
+ * @param string $name
+ * @return mixed
+ */
+ protected function resolve($name)
+ {
+ if (strpos($name, '\\') === false) {
+
+ if (strpos($name, '/') === false) {
+ $app = '';
+ } else {
+ list($app, $name) = explode('/', $name, 2);
+ }
+
+ $name = ($this->app->config->get('app.app_namespace') ?: 'app\\') . ($app ? strtolower($app) . '\\' : '') . 'job\\' . $name;
+ }
+
+ return $this->app->make($name);
+ }
+
+ /**
+ * Determine if the job has been marked as a failure.
+ *
+ * @return bool
+ */
+ public function hasFailed()
+ {
+ return $this->failed;
+ }
+
+ /**
+ * Mark the job as "failed".
+ *
+ * @return void
+ */
+ public function markAsFailed()
+ {
+ $this->failed = true;
+ }
+
+ /**
+ * Process an exception that caused the job to fail.
+ *
+ * @param Exception $e
+ * @return void
+ */
+ public function failed($e)
+ {
+ $this->markAsFailed();
+
+ $payload = $this->payload();
+
+ list($class, $method) = $this->parseJob($payload['job']);
+
+ if (method_exists($this->instance = $this->resolve($class), 'failed')) {
+ $this->instance->failed($payload['data'], $e);
+ }
+ }
+
+ /**
+ * Get the number of times to attempt a job.
+ *
+ * @return int|null
+ */
+ public function maxTries()
+ {
+ return $this->payload()['maxTries'] ?? null;
+ }
+
+ /**
+ * Get the number of seconds the job can run.
+ *
+ * @return int|null
+ */
+ public function timeout()
+ {
+ return $this->payload()['timeout'] ?? null;
+ }
+
+ /**
+ * Get the timestamp indicating when the job should timeout.
+ *
+ * @return int|null
+ */
+ public function timeoutAt()
+ {
+ return $this->payload()['timeoutAt'] ?? null;
+ }
+
+ /**
+ * Get the name of the queued job class.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->payload()['job'];
+ }
+
+ /**
+ * Get the name of the connection the job belongs to.
+ *
+ * @return string
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ /**
+ * Get the name of the queue the job belongs to.
+ * @return string
+ */
+ public function getQueue()
+ {
+ return $this->queue;
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/Listener.php b/vendor/topthink/think-queue/src/queue/Listener.php
new file mode 100755
index 0000000..632510d
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/Listener.php
@@ -0,0 +1,162 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue;
+
+use Closure;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\Process;
+use think\App;
+
+class Listener
+{
+
+ /**
+ * @var string
+ */
+ protected $commandPath;
+
+ /**
+ * @var string
+ */
+ protected $workerCommand;
+
+ /**
+ * @var \Closure|null
+ */
+ protected $outputHandler;
+
+ /**
+ * @param string $commandPath
+ */
+ public function __construct($commandPath)
+ {
+ $this->commandPath = $commandPath;
+ }
+
+ public static function __make(App $app)
+ {
+ return new self($app->getRootPath());
+ }
+
+ /**
+ * Get the PHP binary.
+ *
+ * @return string
+ */
+ protected function phpBinary()
+ {
+ return (new PhpExecutableFinder)->find(false);
+ }
+
+ /**
+ * @param string $connection
+ * @param string $queue
+ * @param int $delay
+ * @param int $sleep
+ * @param int $maxTries
+ * @param int $memory
+ * @param int $timeout
+ * @return void
+ */
+ public function listen($connection, $queue, $delay = 0, $sleep = 3, $maxTries = 0, $memory = 128, $timeout = 60)
+ {
+ $process = $this->makeProcess($connection, $queue, $delay, $sleep, $maxTries, $memory, $timeout);
+
+ while (true) {
+ $this->runProcess($process, $memory);
+ }
+ }
+
+ /**
+ * @param string $connection
+ * @param string $queue
+ * @param int $delay
+ * @param int $sleep
+ * @param int $maxTries
+ * @param int $memory
+ * @param int $timeout
+ * @return Process
+ */
+ public function makeProcess($connection, $queue, $delay, $sleep, $maxTries, $memory, $timeout)
+ {
+ $command = array_filter([
+ $this->phpBinary(),
+ 'think',
+ 'queue:work',
+ $connection,
+ '--once',
+ "--queue={$queue}",
+ "--delay={$delay}",
+ "--memory={$memory}",
+ "--sleep={$sleep}",
+ "--tries={$maxTries}",
+ ], function ($value) {
+ return !is_null($value);
+ });
+
+ return new Process($command, $this->commandPath, null, null, $timeout);
+ }
+
+ /**
+ * @param Process $process
+ * @param int $memory
+ */
+ public function runProcess(Process $process, $memory)
+ {
+ $process->run(function ($type, $line) {
+ $this->handleWorkerOutput($type, $line);
+ });
+
+ if ($this->memoryExceeded($memory)) {
+ $this->stop();
+ }
+ }
+
+ /**
+ * @param int $type
+ * @param string $line
+ * @return void
+ */
+ protected function handleWorkerOutput($type, $line)
+ {
+ if (isset($this->outputHandler)) {
+ call_user_func($this->outputHandler, $type, $line);
+ }
+ }
+
+ /**
+ * @param int $memoryLimit
+ * @return bool
+ */
+ public function memoryExceeded($memoryLimit)
+ {
+ return (memory_get_usage() / 1024 / 1024) >= $memoryLimit;
+ }
+
+ /**
+ * @return void
+ */
+ public function stop()
+ {
+ die;
+ }
+
+ /**
+ * @param \Closure $outputHandler
+ * @return void
+ */
+ public function setOutputHandler(Closure $outputHandler)
+ {
+ $this->outputHandler = $outputHandler;
+ }
+
+}
diff --git a/vendor/topthink/think-queue/src/queue/Queueable.php b/vendor/topthink/think-queue/src/queue/Queueable.php
new file mode 100755
index 0000000..2a6ec4e
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/Queueable.php
@@ -0,0 +1,46 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue;
+
+trait Queueable
+{
+
+ /** @var string 队列名称 */
+ public $queue;
+
+ /** @var integer 延迟时间 */
+ public $delay;
+
+ /**
+ * 设置队列名
+ * @param $queue
+ * @return $this
+ */
+ public function queue($queue)
+ {
+ $this->queue = $queue;
+
+ return $this;
+ }
+
+ /**
+ * 设置延迟时间
+ * @param $delay
+ * @return $this
+ */
+ public function delay($delay)
+ {
+ $this->delay = $delay;
+
+ return $this;
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/Service.php b/vendor/topthink/think-queue/src/queue/Service.php
new file mode 100755
index 0000000..a6b3f4c
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/Service.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace think\queue;
+
+use think\helper\Arr;
+use think\helper\Str;
+use think\Queue;
+use think\queue\command\FailedTable;
+use think\queue\command\FlushFailed;
+use think\queue\command\ForgetFailed;
+use think\queue\command\Listen;
+use think\queue\command\ListFailed;
+use think\queue\command\Restart;
+use think\queue\command\Retry;
+use think\queue\command\Table;
+use think\queue\command\Work;
+
+class Service extends \think\Service
+{
+ public function register()
+ {
+ $this->app->bind('queue', Queue::class);
+ $this->app->bind('queue.failer', function () {
+
+ $config = $this->app->config->get('queue.failed', []);
+
+ $type = Arr::pull($config, 'type', 'none');
+
+ $class = false !== strpos($type, '\\') ? $type : '\\think\\queue\\failed\\' . Str::studly($type);
+
+ return $this->app->invokeClass($class, [$config]);
+ });
+ }
+
+ public function boot()
+ {
+ $this->commands([
+ FailedJob::class,
+ Table::class,
+ FlushFailed::class,
+ ForgetFailed::class,
+ ListFailed::class,
+ Retry::class,
+ Work::class,
+ Restart::class,
+ Listen::class,
+ FailedTable::class,
+ ]);
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/ShouldQueue.php b/vendor/topthink/think-queue/src/queue/ShouldQueue.php
new file mode 100755
index 0000000..cb49c12
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/ShouldQueue.php
@@ -0,0 +1,17 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue;
+
+interface ShouldQueue
+{
+
+}
diff --git a/vendor/topthink/think-queue/src/queue/Worker.php b/vendor/topthink/think-queue/src/queue/Worker.php
new file mode 100755
index 0000000..f98079f
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/Worker.php
@@ -0,0 +1,414 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue;
+
+use Carbon\Carbon;
+use Exception;
+use RuntimeException;
+use think\Cache;
+use think\Event;
+use think\exception\Handle;
+use think\Queue;
+use think\queue\event\JobExceptionOccurred;
+use think\queue\event\JobFailed;
+use think\queue\event\JobProcessed;
+use think\queue\event\JobProcessing;
+use think\queue\event\WorkerStopping;
+use think\queue\exception\MaxAttemptsExceededException;
+use Throwable;
+
+class Worker
+{
+ /** @var Event */
+ protected $event;
+ /** @var Handle */
+ protected $handle;
+ /** @var Queue */
+ protected $queue;
+
+ /** @var Cache */
+ protected $cache;
+
+ /**
+ * Indicates if the worker should exit.
+ *
+ * @var bool
+ */
+ public $shouldQuit = false;
+
+ /**
+ * Indicates if the worker is paused.
+ *
+ * @var bool
+ */
+ public $paused = false;
+
+ public function __construct(Queue $queue, Event $event, Handle $handle, Cache $cache = null)
+ {
+ $this->queue = $queue;
+ $this->event = $event;
+ $this->handle = $handle;
+ $this->cache = $cache;
+ }
+
+ /**
+ * @param string $connection
+ * @param string $queue
+ * @param int $delay
+ * @param int $sleep
+ * @param int $maxTries
+ * @param int $memory
+ * @param int $timeout
+ */
+ public function daemon($connection, $queue, $delay = 0, $sleep = 3, $maxTries = 0, $memory = 128, $timeout = 60)
+ {
+ if ($this->supportsAsyncSignals()) {
+ $this->listenForSignals();
+ }
+
+ $lastRestart = $this->getTimestampOfLastQueueRestart();
+
+ while (true) {
+
+ $job = $this->getNextJob(
+ $this->queue->connection($connection), $queue
+ );
+
+ if ($this->supportsAsyncSignals()) {
+ $this->registerTimeoutHandler($job, $timeout);
+ }
+
+ if ($job) {
+ $this->runJob($job, $connection, $maxTries, $delay);
+ } else {
+ $this->sleep($sleep);
+ }
+
+ $this->stopIfNecessary($job, $lastRestart, $memory);
+ }
+ }
+
+ protected function stopIfNecessary($job, $lastRestart, $memory)
+ {
+ if ($this->shouldQuit || $this->queueShouldRestart($lastRestart)) {
+ $this->stop();
+ } elseif ($this->memoryExceeded($memory)) {
+ $this->stop(12);
+ }
+ }
+
+ /**
+ * Determine if the queue worker should restart.
+ *
+ * @param int|null $lastRestart
+ * @return bool
+ */
+ protected function queueShouldRestart($lastRestart)
+ {
+ return $this->getTimestampOfLastQueueRestart() != $lastRestart;
+ }
+
+ /**
+ * Determine if the memory limit has been exceeded.
+ *
+ * @param int $memoryLimit
+ * @return bool
+ */
+ public function memoryExceeded($memoryLimit)
+ {
+ return (memory_get_usage(true) / 1024 / 1024) >= $memoryLimit;
+ }
+
+ /**
+ * 获取队列重启时间
+ * @return mixed
+ */
+ protected function getTimestampOfLastQueueRestart()
+ {
+ if ($this->cache) {
+ return $this->cache->get('think:queue:restart');
+ }
+ }
+
+ /**
+ * Register the worker timeout handler.
+ *
+ * @param Job|null $job
+ * @param int $timeout
+ * @return void
+ */
+ protected function registerTimeoutHandler($job, $timeout)
+ {
+ pcntl_signal(SIGALRM, function () {
+ $this->kill(1);
+ });
+
+ pcntl_alarm(
+ max($this->timeoutForJob($job, $timeout), 0)
+ );
+ }
+
+ /**
+ * Stop listening and bail out of the script.
+ *
+ * @param int $status
+ * @return void
+ */
+ public function stop($status = 0)
+ {
+ $this->event->trigger(new WorkerStopping($status));
+
+ exit($status);
+ }
+
+ /**
+ * Kill the process.
+ *
+ * @param int $status
+ * @return void
+ */
+ public function kill($status = 0)
+ {
+ $this->event->trigger(new WorkerStopping($status));
+
+ if (extension_loaded('posix')) {
+ posix_kill(getmypid(), SIGKILL);
+ }
+
+ exit($status);
+ }
+
+ /**
+ * Get the appropriate timeout for the given job.
+ *
+ * @param Job|null $job
+ * @param int $timeout
+ * @return int
+ */
+ protected function timeoutForJob($job, $timeout)
+ {
+ return $job && !is_null($job->timeout()) ? $job->timeout() : $timeout;
+ }
+
+ /**
+ * Determine if "async" signals are supported.
+ *
+ * @return bool
+ */
+ protected function supportsAsyncSignals()
+ {
+ return extension_loaded('pcntl');
+ }
+
+ /**
+ * Enable async signals for the process.
+ *
+ * @return void
+ */
+ protected function listenForSignals()
+ {
+ pcntl_async_signals(true);
+
+ pcntl_signal(SIGTERM, function () {
+ $this->shouldQuit = true;
+ });
+
+ pcntl_signal(SIGUSR2, function () {
+ $this->paused = true;
+ });
+
+ pcntl_signal(SIGCONT, function () {
+ $this->paused = false;
+ });
+ }
+
+ /**
+ * 执行下个任务
+ * @param string $connection
+ * @param string $queue
+ * @param int $delay
+ * @param int $sleep
+ * @param int $maxTries
+ * @return void
+ * @throws Exception
+ */
+ public function runNextJob($connection, $queue, $delay = 0, $sleep = 3, $maxTries = 0)
+ {
+
+ $job = $this->getNextJob($this->queue->connection($connection), $queue);
+
+ if ($job) {
+ $this->runJob($job, $connection, $maxTries, $delay);
+ } else {
+ $this->sleep($sleep);
+ }
+ }
+
+ /**
+ * 执行任务
+ * @param Job $job
+ * @param string $connection
+ * @param int $maxTries
+ * @param int $delay
+ * @return void
+ */
+ protected function runJob($job, $connection, $maxTries, $delay)
+ {
+ try {
+ $this->process($connection, $job, $maxTries, $delay);
+ } catch (Exception | Throwable $e) {
+ $this->handle->report($e);
+ }
+ }
+
+ /**
+ * 获取下个任务
+ * @param Connector $connector
+ * @param string $queue
+ * @return Job
+ */
+ protected function getNextJob($connector, $queue)
+ {
+ try {
+ foreach (explode(',', $queue) as $queue) {
+ if (!is_null($job = $connector->pop($queue))) {
+ return $job;
+ }
+ }
+ } catch (Exception | Throwable $e) {
+ $this->handle->report($e);
+ $this->sleep(1);
+ }
+ }
+
+ /**
+ * Process a given job from the queue.
+ * @param string $connection
+ * @param Job $job
+ * @param int $maxTries
+ * @param int $delay
+ * @return void
+ * @throws Exception
+ */
+ public function process($connection, $job, $maxTries = 0, $delay = 0)
+ {
+ try {
+ $this->event->trigger(new JobProcessing($connection, $job));
+
+ $this->markJobAsFailedIfAlreadyExceedsMaxAttempts(
+ $connection, $job, (int) $maxTries
+ );
+
+ $job->fire();
+
+ $this->event->trigger(new JobProcessed($connection, $job));
+ } catch (Exception | Throwable $e) {
+ try {
+ if (!$job->hasFailed()) {
+ $this->markJobAsFailedIfWillExceedMaxAttempts($connection, $job, (int) $maxTries, $e);
+ }
+
+ $this->event->trigger(new JobExceptionOccurred($connection, $job, $e));
+ } finally {
+ if (!$job->isDeleted() && !$job->isReleased() && !$job->hasFailed()) {
+ $job->release($delay);
+ }
+ }
+
+ throw $e;
+ }
+ }
+
+ /**
+ * @param string $connection
+ * @param Job $job
+ * @param int $maxTries
+ */
+ protected function markJobAsFailedIfAlreadyExceedsMaxAttempts($connection, $job, $maxTries)
+ {
+ $maxTries = !is_null($job->maxTries()) ? $job->maxTries() : $maxTries;
+
+ $timeoutAt = $job->timeoutAt();
+
+ if ($timeoutAt && Carbon::now()->getTimestamp() <= $timeoutAt) {
+ return;
+ }
+
+ if (!$timeoutAt && (0 === $maxTries || $job->attempts() <= $maxTries)) {
+ return;
+ }
+
+ $this->failJob($connection, $job, $e = new MaxAttemptsExceededException(
+ $job->getName() . ' has been attempted too many times or run too long. The job may have previously timed out.'
+ ));
+
+ throw $e;
+ }
+
+ /**
+ * @param string $connection
+ * @param Job $job
+ * @param int $maxTries
+ * @param Exception $e
+ */
+ protected function markJobAsFailedIfWillExceedMaxAttempts($connection, $job, $maxTries, $e)
+ {
+ $maxTries = !is_null($job->maxTries()) ? $job->maxTries() : $maxTries;
+
+ if ($job->timeoutAt() && $job->timeoutAt() <= Carbon::now()->getTimestamp()) {
+ $this->failJob($connection, $job, $e);
+ }
+
+ if ($maxTries > 0 && $job->attempts() >= $maxTries) {
+ $this->failJob($connection, $job, $e);
+ }
+ }
+
+ /**
+ * @param string $connection
+ * @param Job $job
+ * @param Exception $e
+ */
+ protected function failJob($connection, $job, $e)
+ {
+ $job->markAsFailed();
+
+ if ($job->isDeleted()) {
+ return;
+ }
+
+ try {
+ $job->delete();
+
+ $job->failed($e);
+ } finally {
+ $this->event->trigger(new JobFailed(
+ $connection, $job, $e ?: new RuntimeException('ManuallyFailed')
+ ));
+ }
+ }
+
+ /**
+ * Sleep the script for a given number of seconds.
+ * @param int $seconds
+ * @return void
+ */
+ public function sleep($seconds)
+ {
+ if ($seconds < 1) {
+ usleep($seconds * 1000000);
+ } else {
+ sleep($seconds);
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-queue/src/queue/command/FailedTable.php b/vendor/topthink/think-queue/src/queue/command/FailedTable.php
new file mode 100755
index 0000000..620af93
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/command/FailedTable.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace think\queue\command;
+
+use think\console\Command;
+use think\helper\Str;
+use think\migration\Creator;
+
+class FailedTable extends Command
+{
+ protected function configure()
+ {
+ $this->setName('queue:failed-table')
+ ->setDescription('Create a migration for the failed queue jobs database table');
+ }
+
+ public function handle()
+ {
+ if (!$this->app->has('migration.creator')) {
+ $this->output->error('Install think-migration first please');
+ return;
+ }
+
+ $table = $this->app->config->get('queue.failed.table');
+
+ $className = Str::studly("create_{$table}_table");
+
+ /** @var Creator $creator */
+ $creator = $this->app->get('migration.creator');
+
+ $path = $creator->create($className);
+
+ // Load the alternative template if it is defined.
+ $contents = file_get_contents(__DIR__ . '/stubs/failed_jobs.stub');
+
+ // inject the class names appropriate to this migration
+ $contents = strtr($contents, [
+ 'CreateFailedJobsTable' => $className,
+ '{{table}}' => $table,
+ ]);
+
+ file_put_contents($path, $contents);
+
+ $this->output->info('Migration created successfully!');
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/command/FlushFailed.php b/vendor/topthink/think-queue/src/queue/command/FlushFailed.php
new file mode 100755
index 0000000..69ea8b4
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/command/FlushFailed.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace think\queue\command;
+
+use think\console\Command;
+
+class FlushFailed extends Command
+{
+ protected function configure()
+ {
+ $this->setName('queue:flush')
+ ->setDescription('Flush all of the failed queue jobs');
+ }
+
+ public function handle()
+ {
+ $this->app->get('queue.failer')->flush();
+
+ $this->output->info('All failed jobs deleted successfully!');
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/command/ForgetFailed.php b/vendor/topthink/think-queue/src/queue/command/ForgetFailed.php
new file mode 100755
index 0000000..37d175d
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/command/ForgetFailed.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace think\queue\command;
+
+use think\console\Command;
+use think\console\input\Argument;
+
+class ForgetFailed extends Command
+{
+ protected function configure()
+ {
+ $this->setName('queue:forget')
+ ->addArgument('id', Argument::REQUIRED, 'The ID of the failed job')
+ ->setDescription('Delete a failed queue job');
+ }
+
+ public function handle()
+ {
+ if ($this->app['queue.failer']->forget($this->input->getArgument('id'))) {
+ $this->output->info('Failed job deleted successfully!');
+ } else {
+ $this->output->error('No failed job matches the given ID.');
+ }
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/command/ListFailed.php b/vendor/topthink/think-queue/src/queue/command/ListFailed.php
new file mode 100755
index 0000000..0604d72
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/command/ListFailed.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace think\queue\command;
+
+use think\console\Command;
+use think\console\Table;
+use think\helper\Arr;
+
+class ListFailed extends Command
+{
+ /**
+ * The table headers for the command.
+ *
+ * @var array
+ */
+ protected $headers = ['ID', 'Connection', 'Queue', 'Class', 'Failed At'];
+
+ protected function configure()
+ {
+ $this->setName('queue:failed')
+ ->setDescription('List all of the failed queue jobs');
+ }
+
+ public function handle()
+ {
+ if (count($jobs = $this->getFailedJobs()) === 0) {
+ $this->output->info('No failed jobs!');
+ return;
+ }
+ $this->displayFailedJobs($jobs);
+ }
+
+ /**
+ * Display the failed jobs in the console.
+ *
+ * @param array $jobs
+ * @return void
+ */
+ protected function displayFailedJobs(array $jobs)
+ {
+ $table = new Table();
+ $table->setHeader($this->headers);
+ $table->setRows($jobs);
+
+ $this->table($table);
+ }
+
+ /**
+ * Compile the failed jobs into a displayable format.
+ *
+ * @return array
+ */
+ protected function getFailedJobs()
+ {
+ $failed = $this->app['queue.failer']->all();
+
+ return collect($failed)->map(function ($failed) {
+ return $this->parseFailedJob((array) $failed);
+ })->filter()->all();
+ }
+
+ /**
+ * Parse the failed job row.
+ *
+ * @param array $failed
+ * @return array
+ */
+ protected function parseFailedJob(array $failed)
+ {
+ $row = array_values(Arr::except($failed, ['payload', 'exception']));
+
+ array_splice($row, 3, 0, $this->extractJobName($failed['payload']));
+
+ return $row;
+ }
+
+ /**
+ * Extract the failed job name from payload.
+ *
+ * @param string $payload
+ * @return string|null
+ */
+ private function extractJobName($payload)
+ {
+ $payload = json_decode($payload, true);
+
+ if ($payload && (!isset($payload['data']['command']))) {
+ return $payload['job'] ?? null;
+ } elseif ($payload && isset($payload['data']['command'])) {
+ return $this->matchJobName($payload);
+ }
+ }
+
+ /**
+ * Match the job name from the payload.
+ *
+ * @param array $payload
+ * @return string
+ */
+ protected function matchJobName($payload)
+ {
+ preg_match('/"([^"]+)"/', $payload['data']['command'], $matches);
+
+ if (isset($matches[1])) {
+ return $matches[1];
+ }
+
+ return $payload['job'] ?? null;
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/command/Listen.php b/vendor/topthink/think-queue/src/queue/command/Listen.php
new file mode 100755
index 0000000..8f8e19d
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/command/Listen.php
@@ -0,0 +1,63 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\input\Option;
+use think\console\Output;
+use think\queue\Listener;
+
+class Listen extends Command
+{
+ /** @var Listener */
+ protected $listener;
+
+ public function __construct(Listener $listener)
+ {
+ parent::__construct();
+ $this->listener = $listener;
+ $this->listener->setOutputHandler(function ($type, $line) {
+ $this->output->write($line);
+ });
+ }
+
+ protected function configure()
+ {
+ $this->setName('queue:listen')
+ ->addArgument('connection', Argument::OPTIONAL, 'The name of the queue connection to work', null)
+ ->addOption('queue', null, Option::VALUE_OPTIONAL, 'The queue to listen on', null)
+ ->addOption('delay', null, Option::VALUE_OPTIONAL, 'Amount of time to delay failed jobs', 0)
+ ->addOption('memory', null, Option::VALUE_OPTIONAL, 'The memory limit in megabytes', 128)
+ ->addOption('timeout', null, Option::VALUE_OPTIONAL, 'Seconds a job may run before timing out', 60)
+ ->addOption('sleep', null, Option::VALUE_OPTIONAL, 'Seconds to wait before checking queue for jobs', 3)
+ ->addOption('tries', null, Option::VALUE_OPTIONAL, 'Number of times to attempt a job before logging it failed', 0)
+ ->setDescription('Listen to a given queue');
+ }
+
+ public function execute(Input $input, Output $output)
+ {
+ $connection = $input->getArgument('connection') ?: $this->app->config->get('queue.default');
+
+ $queue = $input->getOption('queue') ?: $this->app->config->get("queue.connections.{$connection}.queue", 'default');
+ $delay = $input->getOption('delay');
+ $memory = $input->getOption('memory');
+ $timeout = $input->getOption('timeout');
+ $sleep = $input->getOption('sleep');
+ $tries = $input->getOption('tries');
+
+ $this->app->event->trigger('QueueStartListener', [$output]);
+
+ $this->listener->listen($connection, $queue, $delay, $sleep, $tries, $memory, $timeout);
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/command/Restart.php b/vendor/topthink/think-queue/src/queue/command/Restart.php
new file mode 100755
index 0000000..f0d3b4d
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/command/Restart.php
@@ -0,0 +1,33 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue\command;
+
+use think\Cache;
+use think\console\Command;
+use think\queue\InteractsWithTime;
+
+class Restart extends Command
+{
+ use InteractsWithTime;
+
+ protected function configure()
+ {
+ $this->setName('queue:restart')
+ ->setDescription('Restart queue worker daemons after their current job');
+ }
+
+ public function handle(Cache $cache)
+ {
+ $cache->set('think:queue:restart', $this->currentTime());
+ $this->output->info("Broadcasting queue restart signal.");
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/command/Retry.php b/vendor/topthink/think-queue/src/queue/command/Retry.php
new file mode 100755
index 0000000..7551a37
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/command/Retry.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace think\queue\command;
+
+use stdClass;
+use think\console\Command;
+use think\console\input\Argument;
+use think\helper\Arr;
+
+class Retry extends Command
+{
+ protected function configure()
+ {
+ $this->setName('queue:retry')
+ ->addArgument('id', Argument::IS_ARRAY | Argument::REQUIRED, 'The ID of the failed job or "all" to retry all jobs')
+ ->setDescription('Retry a failed queue job');
+ }
+
+ public function handle()
+ {
+ foreach ($this->getJobIds() as $id) {
+ $job = $this->app['queue.failer']->find($id);
+
+ if (is_null($job)) {
+ $this->output->error("Unable to find failed job with ID [{$id}].");
+ } else {
+ $this->retryJob($job);
+
+ $this->output->info("The failed job [{$id}] has been pushed back onto the queue!");
+
+ $this->app['queue.failer']->forget($id);
+ }
+ }
+ }
+
+ /**
+ * Retry the queue job.
+ *
+ * @param stdClass $job
+ * @return void
+ */
+ protected function retryJob($job)
+ {
+ $this->app['queue']->connection($job['connection'])->pushRaw(
+ $this->resetAttempts($job['payload']), $job['queue']
+ );
+ }
+
+ /**
+ * Reset the payload attempts.
+ *
+ * Applicable to Redis jobs which store attempts in their payload.
+ *
+ * @param string $payload
+ * @return string
+ */
+ protected function resetAttempts($payload)
+ {
+ $payload = json_decode($payload, true);
+
+ if (isset($payload['attempts'])) {
+ $payload['attempts'] = 0;
+ }
+
+ return json_encode($payload);
+ }
+
+ /**
+ * Get the job IDs to be retried.
+ *
+ * @return array
+ */
+ protected function getJobIds()
+ {
+ $ids = (array) $this->input->getArgument('id');
+
+ if (count($ids) === 1 && $ids[0] === 'all') {
+ $ids = Arr::pluck($this->app['queue.failer']->all(), 'id');
+ }
+
+ return $ids;
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/command/Table.php b/vendor/topthink/think-queue/src/queue/command/Table.php
new file mode 100755
index 0000000..9354dc7
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/command/Table.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace think\queue\command;
+
+use think\console\Command;
+use think\helper\Str;
+use think\migration\Creator;
+
+class Table extends Command
+{
+ protected function configure()
+ {
+ $this->setName('queue:table')
+ ->setDescription('Create a migration for the queue jobs database table');
+ }
+
+ public function handle()
+ {
+ if (!$this->app->has('migration.creator')) {
+ $this->output->error('Install think-migration first please');
+ return;
+ }
+
+ $table = $this->app->config->get('queue.connections.database.table');
+
+ $className = Str::studly("create_{$table}_table");
+
+ /** @var Creator $creator */
+ $creator = $this->app->get('migration.creator');
+
+ $path = $creator->create($className);
+
+ // Load the alternative template if it is defined.
+ $contents = file_get_contents(__DIR__ . '/stubs/jobs.stub');
+
+ // inject the class names appropriate to this migration
+ $contents = strtr($contents, [
+ 'CreateJobsTable' => $className,
+ '{{table}}' => $table,
+ ]);
+
+ file_put_contents($path, $contents);
+
+ $this->output->info('Migration created successfully!');
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/command/Work.php b/vendor/topthink/think-queue/src/queue/command/Work.php
new file mode 100755
index 0000000..5ead2d4
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/command/Work.php
@@ -0,0 +1,151 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\queue\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\input\Option;
+use think\console\Output;
+use think\queue\event\JobFailed;
+use think\queue\event\JobProcessed;
+use think\queue\event\JobProcessing;
+use think\queue\Job;
+use think\queue\Worker;
+
+class Work extends Command
+{
+
+ /**
+ * The queue worker instance.
+ * @var Worker
+ */
+ protected $worker;
+
+ public function __construct(Worker $worker)
+ {
+ parent::__construct();
+ $this->worker = $worker;
+ }
+
+ protected function configure()
+ {
+ $this->setName('queue:work')
+ ->addArgument('connection', Argument::OPTIONAL, 'The name of the queue connection to work', null)
+ ->addOption('queue', null, Option::VALUE_OPTIONAL, 'The queue to listen on')
+ ->addOption('once', null, Option::VALUE_NONE, 'Only process the next job on the queue')
+ ->addOption('delay', null, Option::VALUE_OPTIONAL, 'Amount of time to delay failed jobs', 0)
+ ->addOption('force', null, Option::VALUE_NONE, 'Force the worker to run even in maintenance mode')
+ ->addOption('memory', null, Option::VALUE_OPTIONAL, 'The memory limit in megabytes', 128)
+ ->addOption('timeout', null, Option::VALUE_OPTIONAL, 'The number of seconds a child process can run', 60)
+ ->addOption('sleep', null, Option::VALUE_OPTIONAL, 'Number of seconds to sleep when no job is available', 3)
+ ->addOption('tries', null, Option::VALUE_OPTIONAL, 'Number of times to attempt a job before logging it failed', 0)
+ ->setDescription('Process the next job on a queue');
+ }
+
+ /**
+ * Execute the console command.
+ * @param Input $input
+ * @param Output $output
+ * @return int|null|void
+ */
+ public function execute(Input $input, Output $output)
+ {
+ $connection = $input->getArgument('connection') ?: $this->app->config->get('queue.default');
+
+ $queue = $input->getOption('queue') ?: $this->app->config->get("queue.connections.{$connection}.queue", 'default');
+ $delay = $input->getOption('delay');
+ $sleep = $input->getOption('sleep');
+ $tries = $input->getOption('tries');
+
+ $this->listenForEvents();
+
+ if ($input->getOption('once')) {
+ $this->worker->runNextJob($connection, $queue, $delay, $sleep, $tries);
+ } else {
+ $memory = $input->getOption('memory');
+ $timeout = $input->getOption('timeout');
+ $this->worker->daemon($connection, $queue, $delay, $sleep, $tries, $memory, $timeout);
+ }
+ }
+
+ /**
+ * 注册事件
+ */
+ protected function listenForEvents()
+ {
+ $this->app->event->listen(JobProcessing::class, function (JobProcessing $event) {
+ $this->writeOutput($event->job, 'starting');
+ });
+
+ $this->app->event->listen(JobProcessed::class, function (JobProcessed $event) {
+ $this->writeOutput($event->job, 'success');
+ });
+
+ $this->app->event->listen(JobFailed::class, function (JobFailed $event) {
+ $this->writeOutput($event->job, 'failed');
+
+ $this->logFailedJob($event);
+ });
+ }
+
+ /**
+ * Write the status output for the queue worker.
+ *
+ * @param Job $job
+ * @param $status
+ */
+ protected function writeOutput(Job $job, $status)
+ {
+ switch ($status) {
+ case 'starting':
+ $this->writeStatus($job, 'Processing', 'comment');
+ break;
+ case 'success':
+ $this->writeStatus($job, 'Processed', 'info');
+ break;
+ case 'failed':
+ $this->writeStatus($job, 'Failed', 'error');
+ break;
+ }
+ }
+
+ /**
+ * Format the status output for the queue worker.
+ *
+ * @param Job $job
+ * @param string $status
+ * @param string $type
+ * @return void
+ */
+ protected function writeStatus(Job $job, $status, $type)
+ {
+ $this->output->writeln(sprintf(
+ "<{$type}>[%s][%s] %s</{$type}> %s",
+ date('Y-m-d H:i:s'),
+ $job->getJobId(),
+ str_pad("{$status}:", 11), $job->getName()
+ ));
+ }
+
+ /**
+ * 记录失败任务
+ * @param JobFailed $event
+ */
+ protected function logFailedJob(JobFailed $event)
+ {
+ $this->app['queue.failer']->log(
+ $event->connection, $event->job->getQueue(),
+ $event->job->getRawBody(), $event->exception
+ );
+ }
+
+}
diff --git a/vendor/topthink/think-queue/src/queue/command/stubs/failed_jobs.stub b/vendor/topthink/think-queue/src/queue/command/stubs/failed_jobs.stub
new file mode 100755
index 0000000..d44ebcd
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/command/stubs/failed_jobs.stub
@@ -0,0 +1,18 @@
+<?php
+
+use think\migration\db\Column;
+use think\migration\Migrator;
+
+class CreateFailedJobsTable extends Migrator
+{
+ public function change()
+ {
+ $this->table('{{table}}')
+ ->addColumn(Column::text('connection'))
+ ->addColumn(Column::text('queue'))
+ ->addColumn(Column::longText('payload'))
+ ->addColumn(Column::longText('exception'))
+ ->addColumn(Column::timestamp('failed_at')->setDefault('CURRENT_TIMESTAMP'))
+ ->create();
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/command/stubs/jobs.stub b/vendor/topthink/think-queue/src/queue/command/stubs/jobs.stub
new file mode 100755
index 0000000..937d549
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/command/stubs/jobs.stub
@@ -0,0 +1,20 @@
+<?php
+
+use think\migration\db\Column;
+use think\migration\Migrator;
+
+class CreateJobsTable extends Migrator
+{
+ public function change()
+ {
+ $this->table('{{table}}')
+ ->addColumn(Column::string('queue'))
+ ->addColumn(Column::longText('payload'))
+ ->addColumn(Column::tinyInteger('attempts')->setUnsigned())
+ ->addColumn(Column::unsignedInteger('reserved_at')->setNullable())
+ ->addColumn(Column::unsignedInteger('available_at'))
+ ->addColumn(Column::unsignedInteger('created_at'))
+ ->addIndex('queue')
+ ->create();
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/connector/Database.php b/vendor/topthink/think-queue/src/queue/connector/Database.php
new file mode 100755
index 0000000..ba4f1da
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/connector/Database.php
@@ -0,0 +1,220 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue\connector;
+
+use Carbon\Carbon;
+use stdClass;
+use think\Db;
+use think\db\Query;
+use think\queue\Connector;
+use think\queue\InteractsWithTime;
+use think\queue\job\Database as DatabaseJob;
+
+class Database extends Connector
+{
+
+ use InteractsWithTime;
+
+ protected $db;
+
+ /**
+ * The database table that holds the jobs.
+ *
+ * @var string
+ */
+ protected $table;
+
+ /**
+ * The name of the default queue.
+ *
+ * @var string
+ */
+ protected $default;
+
+ /**
+ * The expiration time of a job.
+ *
+ * @var int|null
+ */
+ protected $retryAfter = 60;
+
+ public function __construct(Db $db, $table, $default = 'default', $retryAfter = 60)
+ {
+ $this->db = $db;
+ $this->table = $table;
+ $this->default = $default;
+ $this->retryAfter = $retryAfter;
+ }
+
+ public static function __make(Db $db, $config)
+ {
+ return new self($db, $config['table'], $config['queue'], $config['retry_after'] ?? 60);
+ }
+
+ public function size($queue = null)
+ {
+ $this->db->name($this->table)
+ ->where('queue', $this->getQueue($queue))
+ ->count();
+ }
+
+ public function push($job, $data = '', $queue = null)
+ {
+ return $this->pushToDatabase($queue, $this->createPayload($job, $data));
+ }
+
+ public function pushRaw($payload, $queue = null, array $options = [])
+ {
+ return $this->pushToDatabase($queue, $payload);
+ }
+
+ public function later($delay, $job, $data = '', $queue = null)
+ {
+ return $this->pushToDatabase($queue, $this->createPayload($job, $data), $delay);
+ }
+
+ public function bulk($jobs, $data = '', $queue = null)
+ {
+ $queue = $this->getQueue($queue);
+
+ $availableAt = $this->availableAt();
+
+ return $this->db->name($this->table)->insertAll(collect((array) $jobs)->map(
+ function ($job) use ($queue, $data, $availableAt) {
+ return [
+ 'queue' => $queue,
+ 'attempts' => 0,
+ 'reserved_at' => null,
+ 'available_at' => $availableAt,
+ 'created_at' => $this->currentTime(),
+ 'payload' => $this->createPayload($job, $data),
+ ];
+ }
+ )->all());
+ }
+
+ /**
+ * 重新发布任务
+ *
+ * @param string $queue
+ * @param StdClass $job
+ * @param int $delay
+ * @return mixed
+ */
+ public function release($queue, $job, $delay)
+ {
+ return $this->pushToDatabase($queue, $job->payload, $delay, $job->attempts);
+ }
+
+ /**
+ * Push a raw payload to the database with a given delay.
+ *
+ * @param \DateTime|int $delay
+ * @param string|null $queue
+ * @param string $payload
+ * @param int $attempts
+ * @return mixed
+ */
+ protected function pushToDatabase($queue, $payload, $delay = 0, $attempts = 0)
+ {
+ return $this->db->name($this->table)->insertGetId([
+ 'queue' => $this->getQueue($queue),
+ 'attempts' => $attempts,
+ 'reserved_at' => null,
+ 'available_at' => $this->availableAt($delay),
+ 'created_at' => $this->currentTime(),
+ 'payload' => $payload,
+ ]);
+ }
+
+ public function pop($queue = null)
+ {
+ $queue = $this->getQueue($queue);
+
+ return $this->db->transaction(function () use ($queue) {
+
+ if ($job = $this->getNextAvailableJob($queue)) {
+
+ $job = $this->markJobAsReserved($job);
+
+ return new DatabaseJob($this->app, $this, $job, $this->connection, $queue);
+ }
+ });
+ }
+
+ /**
+ * 获取下个有效任务
+ *
+ * @param string|null $queue
+ * @return StdClass|null
+ */
+ protected function getNextAvailableJob($queue)
+ {
+
+ $job = $this->db->name($this->table)
+ ->lock(true)
+ ->where('queue', $this->getQueue($queue))
+ ->where(function (Query $query) {
+ $query->where(function (Query $query) {
+ $query->whereNull('reserved_at')
+ ->where('available_at', '<=', $this->currentTime());
+ });
+
+ //超时任务重试
+ $expiration = Carbon::now()->subSeconds($this->retryAfter)->getTimestamp();
+
+ $query->whereOr(function (Query $query) use ($expiration) {
+ $query->where('reserved_at', '<=', $expiration);
+ });
+ })
+ ->order('id', 'asc')
+ ->find();
+
+ return $job ? (object) $job : null;
+ }
+
+ /**
+ * 标记任务正在执行.
+ *
+ * @param stdClass $job
+ * @return stdClass
+ */
+ protected function markJobAsReserved($job)
+ {
+ $this->db->name($this->table)->where('id', $job->id)->update([
+ 'reserved_at' => $job->reserved_at = $this->currentTime(),
+ 'attempts' => ++$job->attempts,
+ ]);
+
+ return $job;
+ }
+
+ /**
+ * 删除任务
+ *
+ * @param string $id
+ * @return void
+ */
+ public function deleteReserved($id)
+ {
+ $this->db->transaction(function () use ($id) {
+ if ($this->db->name($this->table)->lock(true)->find($id)) {
+ $this->db->name($this->table)->where('id', $id)->delete();
+ }
+ });
+ }
+
+ protected function getQueue($queue)
+ {
+ return $queue ?: $this->default;
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/connector/Redis.php b/vendor/topthink/think-queue/src/queue/connector/Redis.php
new file mode 100755
index 0000000..063565a
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/connector/Redis.php
@@ -0,0 +1,298 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue\connector;
+
+use Closure;
+use Exception;
+use think\helper\Str;
+use think\queue\Connector;
+use think\queue\InteractsWithTime;
+use think\queue\job\Redis as RedisJob;
+
+class Redis extends Connector
+{
+ use InteractsWithTime;
+
+ /** @var \Redis */
+ protected $redis;
+
+ /**
+ * The name of the default queue.
+ *
+ * @var string
+ */
+ protected $default;
+
+ /**
+ * The expiration time of a job.
+ *
+ * @var int|null
+ */
+ protected $retryAfter = 60;
+
+ /**
+ * The maximum number of seconds to block for a job.
+ *
+ * @var int|null
+ */
+ protected $blockFor = null;
+
+ public function __construct(\Redis $redis, $default = 'default', $retryAfter = 60, $blockFor = null)
+ {
+ $this->redis = $redis;
+ $this->default = $default;
+ $this->retryAfter = $retryAfter;
+ $this->blockFor = $blockFor;
+ }
+
+ public static function __make($config)
+ {
+ if (!extension_loaded('redis')) {
+ throw new Exception('redis扩展未安装');
+ }
+
+ $func = $config['persistent'] ? 'pconnect' : 'connect';
+
+ $redis = new \Redis;
+ $redis->$func($config['host'], $config['port'], $config['timeout']);
+
+ if ('' != $config['password']) {
+ $redis->auth($config['password']);
+ }
+
+ if (0 != $config['select']) {
+ $redis->select($config['select']);
+ }
+
+ return new self($redis, $config['queue'], $config['retry_after'] ?? 60, $config['block_for'] ?? null);
+ }
+
+ public function size($queue)
+ {
+ $queue = $this->getQueue($queue);
+
+ return $this->redis->lLen($queue) + $this->redis->zCard("{$queue}:delayed") + $this->redis->zCard("{$queue}:reserved");
+ }
+
+ public function push($job, $data = '', $queue = null)
+ {
+ return $this->pushRaw($this->createPayload($job, $data), $queue);
+ }
+
+ public function pushRaw($payload, $queue = null, array $options = [])
+ {
+ $this->redis->rPush($this->getQueue($queue), $payload);
+
+ return json_decode($payload, true)['id'] ?? null;
+ }
+
+ public function later($delay, $job, $data = '', $queue = null)
+ {
+ return $this->laterRaw($delay, $this->createPayload($job, $data), $queue);
+ }
+
+ protected function laterRaw($delay, $payload, $queue = null)
+ {
+ $this->redis->zadd(
+ $this->getQueue($queue) . ':delayed', $this->availableAt($delay), $payload
+ );
+
+ return json_decode($payload, true)['id'] ?? null;
+ }
+
+ public function pop($queue = null)
+ {
+ $this->migrate($prefixed = $this->getQueue($queue));
+
+ if (empty($nextJob = $this->retrieveNextJob($prefixed))) {
+ return;
+ }
+
+ [$job, $reserved] = $nextJob;
+
+ if ($reserved) {
+ return new RedisJob($this->app, $this, $job, $reserved, $this->connection, $queue);
+ }
+ }
+
+ /**
+ * Migrate any delayed or expired jobs onto the primary queue.
+ *
+ * @param string $queue
+ * @return void
+ */
+ protected function migrate($queue)
+ {
+ $this->migrateExpiredJobs($queue . ':delayed', $queue);
+
+ if (!is_null($this->retryAfter)) {
+ $this->migrateExpiredJobs($queue . ':reserved', $queue);
+ }
+ }
+
+ /**
+ * 移动延迟任务
+ *
+ * @param string $from
+ * @param string $to
+ * @param bool $attempt
+ */
+ public function migrateExpiredJobs($from, $to, $attempt = true)
+ {
+ $this->redis->watch($from);
+
+ $jobs = $this->redis->zRangeByScore($from, '-inf', $this->currentTime());
+
+ if (!empty($jobs)) {
+ $this->transaction(function () use ($from, $to, $jobs, $attempt) {
+
+ $this->redis->zRemRangeByRank($from, 0, count($jobs) - 1);
+
+ for ($i = 0; $i < count($jobs); $i += 100) {
+
+ $values = array_slice($jobs, $i, 100);
+
+ $this->redis->rPush($to, ...$values);
+ }
+ });
+ }
+
+ $this->redis->unwatch();
+ }
+
+ /**
+ * Retrieve the next job from the queue.
+ *
+ * @param string $queue
+ * @return array
+ */
+ protected function retrieveNextJob($queue)
+ {
+ if (!is_null($this->blockFor)) {
+ return $this->blockingPop($queue);
+ }
+
+ $job = $this->redis->lpop($queue);
+ $reserved = false;
+
+ if ($job) {
+ $reserved = json_decode($job, true);
+ $reserved['attempts']++;
+ $reserved = json_encode($reserved);
+ $this->redis->zAdd($queue . ':reserved', $this->availableAt($this->retryAfter), $reserved);
+ }
+
+ return [$job, $reserved];
+ }
+
+ /**
+ * Retrieve the next job by blocking-pop.
+ *
+ * @param string $queue
+ * @return array
+ */
+ protected function blockingPop($queue)
+ {
+ $rawBody = $this->redis->blpop($queue, $this->blockFor);
+
+ if (!empty($rawBody)) {
+ $payload = json_decode($rawBody[1], true);
+
+ $payload['attempts']++;
+
+ $reserved = json_encode($payload);
+
+ $this->redis->zadd($queue . ':reserved', $this->availableAt($this->retryAfter), $reserved);
+
+ return [$rawBody[1], $reserved];
+ }
+
+ return [null, null];
+ }
+
+ /**
+ * 删除任务
+ *
+ * @param string $queue
+ * @param RedisJob $job
+ * @return void
+ */
+ public function deleteReserved($queue, $job)
+ {
+ $this->redis->zRem($this->getQueue($queue) . ':reserved', $job->getReservedJob());
+ }
+
+ /**
+ * Delete a reserved job from the reserved queue and release it.
+ *
+ * @param string $queue
+ * @param RedisJob $job
+ * @param int $delay
+ * @return void
+ */
+ public function deleteAndRelease($queue, $job, $delay)
+ {
+ $queue = $this->getQueue($queue);
+
+ $reserved = $job->getReservedJob();
+
+ $this->redis->zRem($queue . ':reserved', $reserved);
+
+ $this->redis->zAdd($queue . ':delayed', $this->availableAt($delay), $reserved);
+ }
+
+ /**
+ * redis事务
+ * @param Closure $closure
+ */
+ protected function transaction(Closure $closure)
+ {
+ $this->redis->multi();
+ try {
+ call_user_func($closure);
+ if (!$this->redis->exec()) {
+ $this->redis->discard();
+ }
+ } catch (Exception $e) {
+ $this->redis->discard();
+ }
+ }
+
+ protected function createPayloadArray($job, $data = '')
+ {
+ return array_merge(parent::createPayloadArray($job, $data), [
+ 'id' => $this->getRandomId(),
+ 'attempts' => 0,
+ ]);
+ }
+
+ /**
+ * 随机id
+ *
+ * @return string
+ */
+ protected function getRandomId()
+ {
+ return Str::random(32);
+ }
+
+ /**
+ * 获取队列名
+ *
+ * @param string|null $queue
+ * @return string
+ */
+ protected function getQueue($queue)
+ {
+ return 'queues:' . ($queue ?: $this->default);
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/connector/Sync.php b/vendor/topthink/think-queue/src/queue/connector/Sync.php
new file mode 100755
index 0000000..3f4d420
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/connector/Sync.php
@@ -0,0 +1,74 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue\connector;
+
+use Exception;
+use think\queue\Connector;
+use think\queue\event\JobFailed;
+use think\queue\event\JobProcessed;
+use think\queue\event\JobProcessing;
+use think\queue\job\Sync as SyncJob;
+use Throwable;
+
+class Sync extends Connector
+{
+
+ public function size($queue = null)
+ {
+ return 0;
+ }
+
+ public function push($job, $data = '', $queue = null)
+ {
+ $queueJob = $this->resolveJob($this->createPayload($job, $data), $queue);
+
+ try {
+ $this->triggerEvent(new JobProcessing($this->connection, $job));
+
+ $queueJob->fire();
+
+ $this->triggerEvent(new JobProcessed($this->connection, $job));
+ } catch (Exception | Throwable $e) {
+
+ $this->triggerEvent(new JobFailed($this->connection, $job, $e));
+
+ throw $e;
+ }
+
+ return 0;
+ }
+
+ protected function triggerEvent($event)
+ {
+ $this->app->event->trigger($event);
+ }
+
+ public function pop($queue = null)
+ {
+
+ }
+
+ protected function resolveJob($payload, $queue)
+ {
+ return new SyncJob($this->app, $payload, $this->connection, $queue);
+ }
+
+ public function pushRaw($payload, $queue = null, array $options = [])
+ {
+
+ }
+
+ public function later($delay, $job, $data = '', $queue = null)
+ {
+ return $this->push($job, $data, $queue);
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/event/JobExceptionOccurred.php b/vendor/topthink/think-queue/src/queue/event/JobExceptionOccurred.php
new file mode 100755
index 0000000..2e7d1eb
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/event/JobExceptionOccurred.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace think\queue\event;
+
+use Exception;
+use think\queue\Job;
+
+class JobExceptionOccurred
+{
+ /**
+ * The connection name.
+ *
+ * @var string
+ */
+ public $connectionName;
+
+ /**
+ * The job instance.
+ *
+ * @var Job
+ */
+ public $job;
+
+ /**
+ * The exception instance.
+ *
+ * @var Exception
+ */
+ public $exception;
+
+ /**
+ * Create a new event instance.
+ *
+ * @param string $connectionName
+ * @param Job $job
+ * @param Exception $exception
+ * @return void
+ */
+ public function __construct($connectionName, $job, $exception)
+ {
+ $this->job = $job;
+ $this->exception = $exception;
+ $this->connectionName = $connectionName;
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/event/JobFailed.php b/vendor/topthink/think-queue/src/queue/event/JobFailed.php
new file mode 100755
index 0000000..9e3706a
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/event/JobFailed.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace think\queue\event;
+
+use think\queue\Job;
+
+class JobFailed
+{
+ /** @var string */
+ public $connection;
+
+ /** @var Job */
+ public $job;
+
+ /** @var \Exception */
+ public $exception;
+
+ public function __construct($connection, $job, $exception)
+ {
+ $this->connection = $connection;
+ $this->job = $job;
+ $this->exception = $exception;
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/event/JobProcessed.php b/vendor/topthink/think-queue/src/queue/event/JobProcessed.php
new file mode 100755
index 0000000..9ae9fca
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/event/JobProcessed.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace think\queue\event;
+
+use think\queue\Job;
+
+class JobProcessed
+{
+ /** @var string */
+ public $connection;
+
+ /** @var Job */
+ public $job;
+
+ public function __construct($connection, $job)
+ {
+ $this->connection = $connection;
+ $this->job = $job;
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/event/JobProcessing.php b/vendor/topthink/think-queue/src/queue/event/JobProcessing.php
new file mode 100755
index 0000000..72ff3c5
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/event/JobProcessing.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace think\queue\event;
+
+use think\queue\Job;
+
+class JobProcessing
+{
+ /** @var string */
+ public $connection;
+
+ /** @var Job */
+ public $job;
+
+ public function __construct($connection, $job)
+ {
+ $this->connection = $connection;
+ $this->job = $job;
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/event/WorkerStopping.php b/vendor/topthink/think-queue/src/queue/event/WorkerStopping.php
new file mode 100755
index 0000000..5f1fc75
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/event/WorkerStopping.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace think\queue\event;
+
+class WorkerStopping
+{
+ /**
+ * The exit status.
+ *
+ * @var int
+ */
+ public $status;
+
+ /**
+ * Create a new event instance.
+ *
+ * @param int $status
+ * @return void
+ */
+ public function __construct($status = 0)
+ {
+ $this->status = $status;
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/exception/MaxAttemptsExceededException.php b/vendor/topthink/think-queue/src/queue/exception/MaxAttemptsExceededException.php
new file mode 100755
index 0000000..fb8440b
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/exception/MaxAttemptsExceededException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace think\queue\exception;
+
+use RuntimeException;
+
+class MaxAttemptsExceededException extends RuntimeException
+{
+
+}
diff --git a/vendor/topthink/think-queue/src/queue/failed/Database.php b/vendor/topthink/think-queue/src/queue/failed/Database.php
new file mode 100755
index 0000000..3f15af9
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/failed/Database.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace think\queue\failed;
+
+use Carbon\Carbon;
+use think\Db;
+use think\queue\FailedJob;
+
+class Database extends FailedJob
+{
+
+ /** @var Db */
+ protected $db;
+
+ /**
+ * The database table.
+ *
+ * @var string
+ */
+ protected $table;
+
+ public function __construct(Db $db, $table)
+ {
+ $this->db = $db;
+ $this->table = $table;
+ }
+
+ public static function __make(Db $db, $config)
+ {
+ return new self($db, $config['table']);
+ }
+
+ /**
+ * Log a failed job into storage.
+ *
+ * @param string $connection
+ * @param string $queue
+ * @param string $payload
+ * @param \Exception $exception
+ * @return int|null
+ */
+ public function log($connection, $queue, $payload, $exception)
+ {
+ $failed_at = Carbon::now();
+
+ $exception = (string) $exception;
+
+ return $this->getTable()->insertGetId(compact(
+ 'connection', 'queue', 'payload', 'exception', 'failed_at'
+ ));
+ }
+
+ /**
+ * Get a list of all of the failed jobs.
+ *
+ * @return array
+ */
+ public function all()
+ {
+ return collect($this->getTable()->order('id', 'desc')->select())->all();
+ }
+
+ /**
+ * Get a single failed job.
+ *
+ * @param mixed $id
+ * @return object|null
+ */
+ public function find($id)
+ {
+ return $this->getTable()->find($id);
+ }
+
+ /**
+ * Delete a single failed job from storage.
+ *
+ * @param mixed $id
+ * @return bool
+ */
+ public function forget($id)
+ {
+ return $this->getTable()->where('id', $id)->delete() > 0;
+ }
+
+ /**
+ * Flush all of the failed jobs from storage.
+ *
+ * @return void
+ */
+ public function flush()
+ {
+ $this->getTable()->delete(true);
+ }
+
+ protected function getTable()
+ {
+ return $this->db->name($this->table);
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/failed/None.php b/vendor/topthink/think-queue/src/queue/failed/None.php
new file mode 100755
index 0000000..205dd2f
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/failed/None.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace think\queue\failed;
+
+use think\queue\FailedJob;
+
+class None extends FailedJob
+{
+
+ /**
+ * Log a failed job into storage.
+ *
+ * @param string $connection
+ * @param string $queue
+ * @param string $payload
+ * @param \Exception $exception
+ */
+ public function log($connection, $queue, $payload, $exception)
+ {
+
+ }
+
+ /**
+ * Get a list of all of the failed jobs.
+ *
+ * @return array
+ */
+ public function all()
+ {
+ return [];
+ }
+
+ /**
+ * Get a single failed job.
+ *
+ * @param mixed $id
+ */
+ public function find($id)
+ {
+
+ }
+
+ /**
+ * Delete a single failed job from storage.
+ *
+ * @param mixed $id
+ * @return bool
+ */
+ public function forget($id)
+ {
+ return true;
+ }
+
+ /**
+ * Flush all of the failed jobs from storage.
+ *
+ * @return void
+ */
+ public function flush()
+ {
+
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/job/Database.php b/vendor/topthink/think-queue/src/queue/job/Database.php
new file mode 100755
index 0000000..bef756a
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/job/Database.php
@@ -0,0 +1,91 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\queue\job;
+
+use think\App;
+use think\queue\connector\Database as DatabaseQueue;
+use think\queue\Job;
+
+class Database extends Job
+{
+ /**
+ * The database queue instance.
+ * @var DatabaseQueue
+ */
+ protected $database;
+
+ /**
+ * The database job payload.
+ * @var Object
+ */
+ protected $job;
+
+ public function __construct(App $app, DatabaseQueue $database, $job, $connection, $queue)
+ {
+ $this->app = $app;
+ $this->job = $job;
+ $this->queue = $queue;
+ $this->database = $database;
+ $this->connection = $connection;
+ }
+
+ /**
+ * 删除任务
+ * @return void
+ */
+ public function delete()
+ {
+ parent::delete();
+ $this->database->deleteReserved($this->job->id);
+ }
+
+ /**
+ * 重新发布任务
+ * @param int $delay
+ * @return void
+ */
+ public function release($delay = 0)
+ {
+ parent::release($delay);
+
+ $this->delete();
+
+ $this->database->release($this->queue, $this->job, $delay);
+ }
+
+ /**
+ * 获取当前任务尝试次数
+ * @return int
+ */
+ public function attempts()
+ {
+ return (int) $this->job->attempts;
+ }
+
+ /**
+ * Get the raw body string for the job.
+ * @return string
+ */
+ public function getRawBody()
+ {
+ return $this->job->payload;
+ }
+
+ /**
+ * Get the job identifier.
+ *
+ * @return string
+ */
+ public function getJobId()
+ {
+ return $this->job->id;
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/job/Redis.php b/vendor/topthink/think-queue/src/queue/job/Redis.php
new file mode 100755
index 0000000..79b6e40
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/job/Redis.php
@@ -0,0 +1,121 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue\job;
+
+use think\App;
+use think\queue\connector\Redis as RedisQueue;
+use think\queue\Job;
+
+class Redis extends Job
+{
+
+ /**
+ * The redis queue instance.
+ * @var RedisQueue
+ */
+ protected $redis;
+
+ /**
+ * The database job payload.
+ * @var Object
+ */
+ protected $job;
+
+ /**
+ * The JSON decoded version of "$job".
+ *
+ * @var array
+ */
+ protected $decoded;
+
+ /**
+ * The Redis job payload inside the reserved queue.
+ *
+ * @var string
+ */
+ protected $reserved;
+
+ public function __construct(App $app, RedisQueue $redis, $job, $reserved, $connection, $queue)
+ {
+ $this->app = $app;
+ $this->job = $job;
+ $this->queue = $queue;
+ $this->connection = $connection;
+ $this->redis = $redis;
+ $this->reserved = $reserved;
+
+ $this->decoded = $this->payload();
+ }
+
+ /**
+ * Get the number of times the job has been attempted.
+ * @return int
+ */
+ public function attempts()
+ {
+ return ($this->decoded['attempts'] ?? null) + 1;
+ }
+
+ /**
+ * Get the raw body string for the job.
+ * @return string
+ */
+ public function getRawBody()
+ {
+ return $this->job;
+ }
+
+ /**
+ * 删除任务
+ *
+ * @return void
+ */
+ public function delete()
+ {
+ parent::delete();
+
+ $this->redis->deleteReserved($this->queue, $this);
+ }
+
+ /**
+ * 重新发布任务
+ *
+ * @param int $delay
+ * @return void
+ */
+ public function release($delay = 0)
+ {
+ parent::release($delay);
+
+ $this->redis->deleteAndRelease($this->queue, $this, $delay);
+ }
+
+ /**
+ * Get the job identifier.
+ *
+ * @return string
+ */
+ public function getJobId()
+ {
+ return $this->decoded['id'] ?? null;
+ }
+
+ /**
+ * Get the underlying reserved Redis job.
+ *
+ * @return string
+ */
+ public function getReservedJob()
+ {
+ return $this->reserved;
+ }
+}
diff --git a/vendor/topthink/think-queue/src/queue/job/Sync.php b/vendor/topthink/think-queue/src/queue/job/Sync.php
new file mode 100755
index 0000000..c2299d8
--- /dev/null
+++ b/vendor/topthink/think-queue/src/queue/job/Sync.php
@@ -0,0 +1,66 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\queue\job;
+
+use think\App;
+use think\queue\Job;
+
+class Sync extends Job
+{
+ /**
+ * The queue message data.
+ *
+ * @var string
+ */
+ protected $payload;
+
+ public function __construct(App $app, $payload, $connection, $queue)
+ {
+ $this->app = $app;
+ $this->connection = $connection;
+ $this->queue = $queue;
+ $this->payload = $payload;
+ }
+
+ /**
+ * Get the number of times the job has been attempted.
+ * @return int
+ */
+ public function attempts()
+ {
+ return 1;
+ }
+
+ /**
+ * Get the raw body string for the job.
+ * @return string
+ */
+ public function getRawBody()
+ {
+ return $this->payload;
+ }
+
+ /**
+ * Get the job identifier.
+ *
+ * @return string
+ */
+ public function getJobId()
+ {
+ return '';
+ }
+
+ public function getQueue()
+ {
+ return 'sync';
+ }
+}
diff --git a/vendor/topthink/think-queue/tests/DatabaseConnectorTest.php b/vendor/topthink/think-queue/tests/DatabaseConnectorTest.php
new file mode 100755
index 0000000..3856886
--- /dev/null
+++ b/vendor/topthink/think-queue/tests/DatabaseConnectorTest.php
@@ -0,0 +1,123 @@
+<?php
+
+namespace think\test\queue;
+
+use Carbon\Carbon;
+use Mockery as m;
+use Mockery\MockInterface;
+use ReflectionClass;
+use stdClass;
+use think\Db;
+use think\queue\Connector;
+use think\queue\connector\Database;
+
+class DatabaseConnectorTest extends TestCase
+{
+ /** @var Database|MockInterface */
+ protected $connector;
+
+ /** @var Db|MockInterface */
+ protected $db;
+
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->db = m::mock(Db::class);
+ $this->connector = new Database($this->db, 'table', 'default');
+ }
+
+ public function testPushProperlyPushesJobOntoDatabase()
+ {
+ $this->db->shouldReceive('name')->with('table')->andReturn($query = m::mock(stdClass::class));
+
+ $query->shouldReceive('insertGetId')->once()->andReturnUsing(function ($array) {
+ $this->assertEquals('default', $array['queue']);
+ $this->assertEquals(json_encode(['job' => 'foo', 'maxTries' => null, 'timeout' => null, 'data' => ['data']]), $array['payload']);
+ $this->assertEquals(0, $array['attempts']);
+ $this->assertNull($array['reserved_at']);
+ $this->assertInternalType('int', $array['available_at']);
+ });
+ $this->connector->push('foo', ['data']);
+ }
+
+ public function testDelayedPushProperlyPushesJobOntoDatabase()
+ {
+ $this->db->shouldReceive('name')->with('table')->andReturn($query = m::mock(stdClass::class));
+
+ $query->shouldReceive('insertGetId')->once()->andReturnUsing(function ($array) {
+ $this->assertEquals('default', $array['queue']);
+ $this->assertEquals(json_encode(['job' => 'foo', 'maxTries' => null, 'timeout' => null, 'data' => ['data']]), $array['payload']);
+ $this->assertEquals(0, $array['attempts']);
+ $this->assertNull($array['reserved_at']);
+ $this->assertInternalType('int', $array['available_at']);
+ });
+
+ $this->connector->later(10, 'foo', ['data']);
+ }
+
+ public function testFailureToCreatePayloadFromObject()
+ {
+ $this->expectException('InvalidArgumentException');
+
+ $job = new stdClass;
+ $job->invalid = "\xc3\x28";
+
+ $queue = $this->getMockForAbstractClass(Connector::class);
+ $class = new ReflectionClass(Connector::class);
+
+ $createPayload = $class->getMethod('createPayload');
+ $createPayload->setAccessible(true);
+ $createPayload->invokeArgs($queue, [
+ $job,
+ 'queue-name',
+ ]);
+ }
+
+ public function testFailureToCreatePayloadFromArray()
+ {
+ $this->expectException('InvalidArgumentException');
+
+ $queue = $this->getMockForAbstractClass(Connector::class);
+ $class = new ReflectionClass(Connector::class);
+
+ $createPayload = $class->getMethod('createPayload');
+ $createPayload->setAccessible(true);
+ $createPayload->invokeArgs($queue, [
+ ["\xc3\x28"],
+ 'queue-name',
+ ]);
+ }
+
+ public function testBulkBatchPushesOntoDatabase()
+ {
+
+ $this->db->shouldReceive('name')->with('table')->andReturn($query = m::mock(stdClass::class));
+
+ Carbon::setTestNow(
+ $now = Carbon::now()->addSeconds()
+ );
+
+ $query->shouldReceive('insertAll')->once()->andReturnUsing(function ($records) use ($now) {
+ $this->assertEquals([
+ [
+ 'queue' => 'queue',
+ 'payload' => json_encode(['job' => 'foo', 'maxTries' => null, 'timeout' => null, 'data' => ['data']]),
+ 'attempts' => 0,
+ 'reserved_at' => null,
+ 'available_at' => $now->getTimestamp(),
+ 'created_at' => $now->getTimestamp(),
+ ], [
+ 'queue' => 'queue',
+ 'payload' => json_encode(['job' => 'bar', 'maxTries' => null, 'timeout' => null, 'data' => ['data']]),
+ 'attempts' => 0,
+ 'reserved_at' => null,
+ 'available_at' => $now->getTimestamp(),
+ 'created_at' => $now->getTimestamp(),
+ ],
+ ], $records);
+ });
+
+ $this->connector->bulk(['foo', 'bar'], ['data'], 'queue');
+ }
+
+}
diff --git a/vendor/topthink/think-queue/tests/ListenerTest.php b/vendor/topthink/think-queue/tests/ListenerTest.php
new file mode 100755
index 0000000..883b033
--- /dev/null
+++ b/vendor/topthink/think-queue/tests/ListenerTest.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace think\test\queue;
+
+use Mockery as m;
+use Mockery\MockInterface;
+use Symfony\Component\Process\Process;
+use think\queue\Listener;
+
+class ListenerTest extends TestCase
+{
+ /** @var Process|MockInterface */
+ protected $process;
+ /** @var Listener|MockInterface */
+ protected $listener;
+
+ public function testRunProcessCallsProcess()
+ {
+ /** @var Process|MockInterface $process */
+ $process = m::mock(Process::class)->makePartial();
+ $process->shouldReceive('run')->once();
+ /** @var Listener|MockInterface $listener */
+ $listener = m::mock(Listener::class)->makePartial();
+ $listener->shouldReceive('memoryExceeded')->once()->with(1)->andReturn(false);
+
+ $listener->runProcess($process, 1);
+ }
+
+ public function testListenerStopsWhenMemoryIsExceeded()
+ {
+ /** @var Process|MockInterface $process */
+ $process = m::mock(Process::class)->makePartial();
+ $process->shouldReceive('run')->once();
+ /** @var Listener|MockInterface $listener */
+ $listener = m::mock(Listener::class)->makePartial();
+ $listener->shouldReceive('memoryExceeded')->once()->with(1)->andReturn(true);
+ $listener->shouldReceive('stop')->once();
+
+ $listener->runProcess($process, 1);
+ }
+
+ public function testMakeProcessCorrectlyFormatsCommandLine()
+ {
+ $listener = new Listener(__DIR__);
+
+ $process = $listener->makeProcess('connection', 'queue', 1, 3, 0, 2, 3);
+ $escape = '\\' === DIRECTORY_SEPARATOR ? '"' : '\'';
+
+ $this->assertInstanceOf(Process::class, $process);
+ $this->assertEquals(__DIR__, $process->getWorkingDirectory());
+ $this->assertEquals(3, $process->getTimeout());
+ $this->assertEquals($escape . PHP_BINARY . $escape . " {$escape}think{$escape} {$escape}queue:work{$escape} {$escape}connection{$escape} {$escape}--once{$escape} {$escape}--queue=queue{$escape} {$escape}--delay=1{$escape} {$escape}--memory=2{$escape} {$escape}--sleep=3{$escape} {$escape}--tries=0{$escape}", $process->getCommandLine());
+ }
+
+ public function testMakeProcessCorrectlyFormatsCommandLineWithAnEnvironmentSpecified()
+ {
+ $listener = new Listener(__DIR__);
+
+ $process = $listener->makeProcess('connection', 'queue', 1, 3, 0, 2, 3);
+ $escape = '\\' === DIRECTORY_SEPARATOR ? '"' : '\'';
+
+ $this->assertInstanceOf(Process::class, $process);
+ $this->assertEquals(__DIR__, $process->getWorkingDirectory());
+ $this->assertEquals(3, $process->getTimeout());
+ $this->assertEquals($escape . PHP_BINARY . $escape . " {$escape}think{$escape} {$escape}queue:work{$escape} {$escape}connection{$escape} {$escape}--once{$escape} {$escape}--queue=queue{$escape} {$escape}--delay=1{$escape} {$escape}--memory=2{$escape} {$escape}--sleep=3{$escape} {$escape}--tries=0{$escape}", $process->getCommandLine());
+ }
+
+ public function testMakeProcessCorrectlyFormatsCommandLineWhenTheConnectionIsNotSpecified()
+ {
+ $listener = new Listener(__DIR__);
+
+ $process = $listener->makeProcess(null, 'queue', 1, 3, 0, 2, 3);
+ $escape = '\\' === DIRECTORY_SEPARATOR ? '"' : '\'';
+
+ $this->assertInstanceOf(Process::class, $process);
+ $this->assertEquals(__DIR__, $process->getWorkingDirectory());
+ $this->assertEquals(3, $process->getTimeout());
+ $this->assertEquals($escape . PHP_BINARY . $escape . " {$escape}think{$escape} {$escape}queue:work{$escape} {$escape}--once{$escape} {$escape}--queue=queue{$escape} {$escape}--delay=1{$escape} {$escape}--memory=2{$escape} {$escape}--sleep=3{$escape} {$escape}--tries=0{$escape}", $process->getCommandLine());
+ }
+}
diff --git a/vendor/topthink/think-queue/tests/QueueTest.php b/vendor/topthink/think-queue/tests/QueueTest.php
new file mode 100755
index 0000000..a87c80d
--- /dev/null
+++ b/vendor/topthink/think-queue/tests/QueueTest.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace think\test\queue;
+
+use InvalidArgumentException;
+use Mockery as m;
+use think\Config;
+use think\Queue;
+use think\queue\connector\Sync;
+
+class QueueTest extends TestCase
+{
+ /** @var Queue */
+ protected $queue;
+
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->queue = new Queue($this->app);
+ }
+
+ public function testDefaultConnectionCanBeResolved()
+ {
+ $sync = new Sync();
+
+ $this->app->shouldReceive('invokeClass')->once()->with('\think\queue\connector\Sync', [['driver' => 'sync']])->andReturn($sync);
+
+ $config = m::mock(Config::class);
+
+ $config->shouldReceive('get')->twice()->with('queue.connectors.sync', ['driver' => 'sync'])->andReturn(['driver' => 'sync']);
+ $config->shouldReceive('get')->once()->with('queue.default', 'sync')->andReturn('sync');
+
+ $this->app->shouldReceive('get')->times(3)->with('config')->andReturn($config);
+
+ $this->assertSame($sync, $this->queue->driver('sync'));
+ $this->assertSame($sync, $this->queue->driver());
+ }
+
+ public function testNotSupportDriver()
+ {
+ $config = m::mock(Config::class);
+
+ $config->shouldReceive('get')->once()->with('queue.connectors.hello', ['driver' => 'sync'])->andReturn(['driver' => 'hello']);
+ $this->app->shouldReceive('get')->once()->with('config')->andReturn($config);
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->queue->driver('hello');
+ }
+}
diff --git a/vendor/topthink/think-queue/tests/TestCase.php b/vendor/topthink/think-queue/tests/TestCase.php
new file mode 100755
index 0000000..5a9279d
--- /dev/null
+++ b/vendor/topthink/think-queue/tests/TestCase.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace think\test\queue;
+
+use Mockery as m;
+use Mockery\MockInterface;
+use think\App;
+
+abstract class TestCase extends \PHPUnit\Framework\TestCase
+{
+ /** @var App|MockInterface */
+ protected $app;
+
+ public function tearDown()
+ {
+ m::close();
+ }
+
+ protected function setUp()
+ {
+ $this->app = m::mock(App::class)->makePartial();
+ }
+}
diff --git a/vendor/topthink/think-queue/tests/WorkerTest.php b/vendor/topthink/think-queue/tests/WorkerTest.php
new file mode 100755
index 0000000..87eeacc
--- /dev/null
+++ b/vendor/topthink/think-queue/tests/WorkerTest.php
@@ -0,0 +1,420 @@
+<?php
+
+namespace think\test\queue;
+
+use Carbon\Carbon;
+use Mockery as m;
+use Mockery\MockInterface;
+use RuntimeException;
+use think\Cache;
+use think\Event;
+use think\exception\Handle;
+use think\Queue;
+use think\queue\connector\Sync;
+use think\queue\event\JobExceptionOccurred;
+use think\queue\event\JobFailed;
+use think\queue\event\JobProcessed;
+use think\queue\event\JobProcessing;
+use think\queue\exception\MaxAttemptsExceededException;
+
+class WorkerTest extends TestCase
+{
+ /** @var Handle|MockInterface */
+ protected $handle;
+
+ /** @var Event|MockInterface */
+ protected $event;
+
+ /** @var Cache|MockInterface */
+ protected $cache;
+
+ /** @var Queue|MockInterface */
+ protected $queue;
+
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->queue = m::mock(Queue::class);
+ $this->handle = m::spy(Handle::class);
+ $this->event = m::spy(Event::class);
+ $this->cache = m::spy(Cache::class);
+ }
+
+ public function testJobCanBeFired()
+ {
+
+ $worker = $this->getWorker(['default' => [$job = new WorkerFakeJob]]);
+
+ $this->event->shouldReceive('trigger')->with(m::type(JobProcessing::class))->once();
+ $this->event->shouldReceive('trigger')->with(m::type(JobProcessed::class))->once();
+
+ $worker->runNextJob('sync', 'default');
+ }
+
+ public function testWorkerCanWorkUntilQueueIsEmpty()
+ {
+ $worker = $this->getWorker(['default' => [
+ $firstJob = new WorkerFakeJob,
+ $secondJob = new WorkerFakeJob,
+ ]]);
+
+ $this->expectException(LoopBreakerException::class);
+
+ $worker->daemon('sync', 'default');
+
+ $this->assertTrue($firstJob->fired);
+
+ $this->assertTrue($secondJob->fired);
+
+ $this->assertSame(0, $worker->stoppedWithStatus);
+
+ $this->event->shouldHaveReceived('trigger')->with(m::type(JobProcessing::class))->twice();
+
+ $this->event->shouldHaveReceived('trigger')->with(m::type(JobProcessed::class))->twice();
+ }
+
+ public function testJobCanBeFiredBasedOnPriority()
+ {
+ $worker = $this->getWorker([
+ 'high' => [
+ $highJob = new WorkerFakeJob,
+ $secondHighJob = new WorkerFakeJob,
+ ],
+ 'low' => [$lowJob = new WorkerFakeJob],
+ ]);
+
+ $worker->runNextJob('sync', 'high,low');
+
+ $this->assertTrue($highJob->fired);
+ $this->assertFalse($secondHighJob->fired);
+ $this->assertFalse($lowJob->fired);
+
+ $worker->runNextJob('sync', 'high,low');
+ $this->assertTrue($secondHighJob->fired);
+ $this->assertFalse($lowJob->fired);
+
+ $worker->runNextJob('sync', 'high,low');
+ $this->assertTrue($lowJob->fired);
+ }
+
+ public function testExceptionIsReportedIfConnectionThrowsExceptionOnJobPop()
+ {
+ $e = new RuntimeException();
+
+ $sync = m::mock(Sync::class);
+
+ $sync->shouldReceive('pop')->andReturnUsing(function () use ($e) {
+ throw $e;
+ });
+
+ $this->queue->shouldReceive('driver')->with('sync')->andReturn($sync);
+
+ $worker = new Worker($this->queue, $this->event, $this->handle);
+
+ $worker->runNextJob('sync', 'default');
+
+ $this->handle->shouldHaveReceived('report')->with($e);
+ }
+
+ public function testWorkerSleepsWhenQueueIsEmpty()
+ {
+ $worker = $this->getWorker(['default' => []]);
+ $worker->runNextJob('sync', 'default', 0, 5);
+ $this->assertEquals(5, $worker->sleptFor);
+ }
+
+ public function testJobIsReleasedOnException()
+ {
+ $e = new RuntimeException;
+
+ $job = new WorkerFakeJob(function () use ($e) {
+ throw $e;
+ });
+
+ $worker = $this->getWorker(['default' => [$job]]);
+ $worker->runNextJob('sync', 'default', 10);
+
+ $this->assertEquals(10, $job->releaseAfter);
+ $this->assertFalse($job->deleted);
+ $this->handle->shouldHaveReceived('report')->with($e);
+ $this->event->shouldHaveReceived('trigger')->with(m::type(JobExceptionOccurred::class))->once();
+ $this->event->shouldNotHaveReceived('trigger', [m::type(JobProcessed::class)]);
+ }
+
+ public function testJobIsNotReleasedIfItHasExceededMaxAttempts()
+ {
+ $e = new RuntimeException;
+
+ $job = new WorkerFakeJob(function ($job) use ($e) {
+ // In normal use this would be incremented by being popped off the queue
+ $job->attempts++;
+
+ throw $e;
+ });
+ $job->attempts = 1;
+
+ $worker = $this->getWorker(['default' => [$job]]);
+ $worker->runNextJob('sync', 'default', 0, 3, 1);
+
+ $this->assertNull($job->releaseAfter);
+ $this->assertTrue($job->deleted);
+ $this->assertEquals($e, $job->failedWith);
+ $this->handle->shouldHaveReceived('report')->with($e);
+ $this->event->shouldHaveReceived('trigger')->with(m::type(JobExceptionOccurred::class))->once();
+ $this->event->shouldHaveReceived('trigger')->with(m::type(JobFailed::class))->once();
+ $this->event->shouldNotHaveReceived('trigger', [m::type(JobProcessed::class)]);
+ }
+
+ public function testJobIsNotReleasedIfItHasExpired()
+ {
+ $e = new RuntimeException;
+
+ $job = new WorkerFakeJob(function ($job) use ($e) {
+ // In normal use this would be incremented by being popped off the queue
+ $job->attempts++;
+
+ throw $e;
+ });
+
+ $job->timeoutAt = Carbon::now()->addSeconds(1)->getTimestamp();
+
+ $job->attempts = 0;
+
+ Carbon::setTestNow(
+ Carbon::now()->addSeconds(1)
+ );
+
+ $worker = $this->getWorker(['default' => [$job]]);
+ $worker->runNextJob('sync', 'default');
+
+ $this->assertNull($job->releaseAfter);
+ $this->assertTrue($job->deleted);
+ $this->assertEquals($e, $job->failedWith);
+ $this->handle->shouldHaveReceived('report')->with($e);
+ $this->event->shouldHaveReceived('trigger')->with(m::type(JobExceptionOccurred::class))->once();
+ $this->event->shouldHaveReceived('trigger')->with(m::type(JobFailed::class))->once();
+ $this->event->shouldNotHaveReceived('trigger', [m::type(JobProcessed::class)]);
+ }
+
+ public function testJobIsFailedIfItHasAlreadyExceededMaxAttempts()
+ {
+ $job = new WorkerFakeJob(function ($job) {
+ $job->attempts++;
+ });
+
+ $job->attempts = 2;
+
+ $worker = $this->getWorker(['default' => [$job]]);
+ $worker->runNextJob('sync', 'default', 0, 3, 1);
+
+ $this->assertNull($job->releaseAfter);
+ $this->assertTrue($job->deleted);
+ $this->assertInstanceOf(MaxAttemptsExceededException::class, $job->failedWith);
+ $this->handle->shouldHaveReceived('report')->with(m::type(MaxAttemptsExceededException::class));
+ $this->event->shouldHaveReceived('trigger')->with(m::type(JobExceptionOccurred::class))->once();
+ $this->event->shouldHaveReceived('trigger')->with(m::type(JobFailed::class))->once();
+ $this->event->shouldNotHaveReceived('trigger', [m::type(JobProcessed::class)]);
+ }
+
+ public function testJobIsFailedIfItHasAlreadyExpired()
+ {
+ $job = new WorkerFakeJob(function ($job) {
+ $job->attempts++;
+ });
+
+ $job->timeoutAt = Carbon::now()->addSeconds(2)->getTimestamp();
+
+ $job->attempts = 1;
+
+ Carbon::setTestNow(
+ Carbon::now()->addSeconds(3)
+ );
+
+ $worker = $this->getWorker(['default' => [$job]]);
+ $worker->runNextJob('sync', 'default');
+
+ $this->assertNull($job->releaseAfter);
+ $this->assertTrue($job->deleted);
+ $this->assertInstanceOf(MaxAttemptsExceededException::class, $job->failedWith);
+ $this->handle->shouldHaveReceived('report')->with(m::type(MaxAttemptsExceededException::class));
+ $this->event->shouldHaveReceived('trigger')->with(m::type(JobExceptionOccurred::class))->once();
+ $this->event->shouldHaveReceived('trigger')->with(m::type(JobFailed::class))->once();
+ $this->event->shouldNotHaveReceived('trigger', [m::type(JobProcessed::class)]);
+ }
+
+ public function testJobBasedMaxRetries()
+ {
+ $job = new WorkerFakeJob(function ($job) {
+ $job->attempts++;
+ });
+
+ $job->attempts = 2;
+
+ $job->maxTries = 10;
+
+ $worker = $this->getWorker(['default' => [$job]]);
+ $worker->runNextJob('sync', 'default', 0, 3, 1);
+
+ $this->assertFalse($job->deleted);
+ $this->assertNull($job->failedWith);
+ }
+
+ protected function getWorker($jobs)
+ {
+ $sync = m::mock(Sync::class);
+
+ $sync->shouldReceive('pop')->andReturnUsing(function ($queue) use (&$jobs) {
+ return array_shift($jobs[$queue]);
+ });
+
+ $this->queue->shouldReceive('driver')->with('sync')->andReturn($sync);
+
+ return new Worker($this->queue, $this->event, $this->handle, $this->cache);
+ }
+}
+
+class WorkerFakeConnector
+{
+ public $jobs = [];
+
+ public function __construct($jobs)
+ {
+ $this->jobs = $jobs;
+ }
+
+ public function pop($queue)
+ {
+ return array_shift($this->jobs[$queue]);
+ }
+}
+
+class Worker extends \think\queue\Worker
+{
+ public $sleptFor;
+
+ public $stoppedWithStatus;
+
+ public function sleep($seconds)
+ {
+ $this->sleptFor = $seconds;
+ }
+
+ public function stop($status = 0)
+ {
+ $this->stoppedWithStatus = $status;
+
+ throw new LoopBreakerException;
+ }
+
+ protected function stopIfNecessary($job, $lastRestart, $memory)
+ {
+ if (is_null($job)) {
+ $this->stop();
+ } else {
+ parent::stopIfNecessary($job, $lastRestart, $memory);
+ }
+ }
+}
+
+class WorkerFakeJob
+{
+
+ public $fired = false;
+ public $callback;
+ public $deleted = false;
+ public $releaseAfter;
+ public $released = false;
+ public $maxTries;
+ public $timeoutAt;
+ public $attempts = 0;
+ public $failedWith;
+ public $failed = false;
+ public $connectionName;
+
+ public function __construct($callback = null)
+ {
+ $this->callback = $callback ?: function () {
+ //
+ };
+ }
+
+ public function fire()
+ {
+ $this->fired = true;
+ $this->callback->__invoke($this);
+ }
+
+ public function payload()
+ {
+ return [];
+ }
+
+ public function maxTries()
+ {
+ return $this->maxTries;
+ }
+
+ public function timeoutAt()
+ {
+ return $this->timeoutAt;
+ }
+
+ public function delete()
+ {
+ $this->deleted = true;
+ }
+
+ public function isDeleted()
+ {
+ return $this->deleted;
+ }
+
+ public function release($delay)
+ {
+ $this->released = true;
+
+ $this->releaseAfter = $delay;
+ }
+
+ public function isReleased()
+ {
+ return $this->released;
+ }
+
+ public function attempts()
+ {
+ return $this->attempts;
+ }
+
+ public function markAsFailed()
+ {
+ $this->failed = true;
+ }
+
+ public function failed($e)
+ {
+ $this->markAsFailed();
+
+ $this->failedWith = $e;
+ }
+
+ public function hasFailed()
+ {
+ return $this->failed;
+ }
+
+ public function timeout()
+ {
+ return time() + 60;
+ }
+
+ public function getName()
+ {
+ return 'WorkerFakeJob';
+ }
+}
+
+class LoopBreakerException extends RuntimeException
+{
+ //
+}
diff --git a/vendor/topthink/think-queue/tests/bootstrap.php b/vendor/topthink/think-queue/tests/bootstrap.php
new file mode 100755
index 0000000..401013c
--- /dev/null
+++ b/vendor/topthink/think-queue/tests/bootstrap.php
@@ -0,0 +1,3 @@
+<?php
+
+include __DIR__.'/../vendor/autoload.php';
diff --git a/vendor/topthink/think-template/.gitignore b/vendor/topthink/think-template/.gitignore
new file mode 100755
index 0000000..485dee6
--- /dev/null
+++ b/vendor/topthink/think-template/.gitignore
@@ -0,0 +1 @@
+.idea
diff --git a/vendor/topthink/think-template/LICENSE b/vendor/topthink/think-template/LICENSE
new file mode 100755
index 0000000..8dada3e
--- /dev/null
+++ b/vendor/topthink/think-template/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/topthink/think-template/README.md b/vendor/topthink/think-template/README.md
new file mode 100755
index 0000000..1d19064
--- /dev/null
+++ b/vendor/topthink/think-template/README.md
@@ -0,0 +1,70 @@
+# ThinkTemplate
+
+基于XML和标签库的编译型模板引擎
+
+## 主要特性
+
+- 支持XML标签库和普通标签的混合定义;
+- 支持直接使用PHP代码书写;
+- 支持文件包含;
+- 支持多级标签嵌套;
+- 支持布局模板功能;
+- 一次编译多次运行,编译和运行效率非常高;
+- 模板文件和布局模板更新,自动更新模板缓存;
+- 系统变量无需赋值直接输出;
+- 支持多维数组的快速输出;
+- 支持模板变量的默认值;
+- 支持页面代码去除Html空白;
+- 支持变量组合调节器和格式化功能;
+- 允许定义模板禁用函数和禁用PHP语法;
+- 通过标签库方式扩展;
+
+## 安装
+
+~~~php
+composer require topthink/think-template
+~~~
+
+## 用法示例
+
+
+~~~php
+<?php
+namespace think;
+
+require __DIR__.'/vendor/autoload.php';
+
+// 设置模板引擎参数
+$config = [
+ 'view_path' => './template/',
+ 'cache_path' => './runtime/',
+ 'view_suffix' => 'html',
+];
+
+$template = new Template($config);
+// 模板变量赋值
+$template->assign(['name' => 'think']);
+// 读取模板文件渲染输出
+$template->fetch('index');
+// 完整模板文件渲染
+$template->fetch('./template/test.php');
+// 渲染内容输出
+$template->display($content);
+~~~
+
+支持静态调用
+
+~~~
+use think\facade\Template;
+
+Template::config([
+ 'view_path' => './template/',
+ 'cache_path' => './runtime/',
+ 'view_suffix' => 'html',
+]);
+Template::assign(['name' => 'think']);
+Template::fetch('index',['name' => 'think']);
+Template::display($content,['name' => 'think']);
+~~~
+
+详细用法参考[开发手册](https://www.kancloud.cn/manual/think-template/content)
\ No newline at end of file
diff --git a/vendor/topthink/think-template/composer.json b/vendor/topthink/think-template/composer.json
new file mode 100755
index 0000000..f4e1205
--- /dev/null
+++ b/vendor/topthink/think-template/composer.json
@@ -0,0 +1,20 @@
+{
+ "name": "topthink/think-template",
+ "description": "the php template engine",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=7.1.0",
+ "psr/simple-cache": "^1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\": "src"
+ }
+ }
+}
\ No newline at end of file
diff --git a/vendor/topthink/think-template/src/Template.php b/vendor/topthink/think-template/src/Template.php
new file mode 100755
index 0000000..84d35a5
--- /dev/null
+++ b/vendor/topthink/think-template/src/Template.php
@@ -0,0 +1,1320 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Exception;
+use Psr\SimpleCache\CacheInterface;
+
+/**
+ * ThinkPHP分离出来的模板引擎
+ * 支持XML标签和普通标签的模板解析
+ * 编译型模板引擎 支持动态缓存
+ */
+class Template
+{
+ /**
+ * 模板变量
+ * @var array
+ */
+ protected $data = [];
+
+ /**
+ * 模板配置参数
+ * @var array
+ */
+ protected $config = [
+ 'view_path' => '', // 模板路径
+ 'view_suffix' => 'html', // 默认模板文件后缀
+ 'view_depr' => DIRECTORY_SEPARATOR,
+ 'cache_path' => '',
+ 'cache_suffix' => 'php', // 默认模板缓存后缀
+ 'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数
+ 'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码
+ 'tpl_begin' => '{', // 模板引擎普通标签开始标记
+ 'tpl_end' => '}', // 模板引擎普通标签结束标记
+ 'strip_space' => false, // 是否去除模板文件里面的html空格与换行
+ 'tpl_cache' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译
+ 'compile_type' => 'file', // 模板编译类型
+ 'cache_prefix' => '', // 模板缓存前缀标识,可以动态改变
+ 'cache_time' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒)
+ 'layout_on' => false, // 布局模板开关
+ 'layout_name' => 'layout', // 布局模板入口文件
+ 'layout_item' => '{__CONTENT__}', // 布局模板的内容替换标识
+ 'taglib_begin' => '{', // 标签库标签开始标记
+ 'taglib_end' => '}', // 标签库标签结束标记
+ 'taglib_load' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测
+ 'taglib_build_in' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序
+ 'taglib_pre_load' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔
+ 'display_cache' => false, // 模板渲染缓存
+ 'cache_id' => '', // 模板缓存ID
+ 'tpl_replace_string' => [],
+ 'tpl_var_identify' => 'array', // .语法变量识别,array|object|'', 为空时自动识别
+ 'default_filter' => 'htmlentities', // 默认过滤方法 用于普通标签输出
+ ];
+
+ /**
+ * 保留内容信息
+ * @var array
+ */
+ private $literal = [];
+
+ /**
+ * 扩展解析规则
+ * @var array
+ */
+ private $extend = [];
+
+ /**
+ * 模板包含信息
+ * @var array
+ */
+ private $includeFile = [];
+
+ /**
+ * 模板存储对象
+ * @var object
+ */
+ protected $storage;
+
+ /**
+ * 查询缓存对象
+ * @var CacheInterface
+ */
+ protected $cache;
+
+ /**
+ * 架构函数
+ * @access public
+ * @param array $config
+ */
+ public function __construct(array $config = [])
+ {
+ $this->config = array_merge($this->config, $config);
+
+ $this->config['taglib_begin_origin'] = $this->config['taglib_begin'];
+ $this->config['taglib_end_origin'] = $this->config['taglib_end'];
+
+ $this->config['taglib_begin'] = preg_quote($this->config['taglib_begin'], '/');
+ $this->config['taglib_end'] = preg_quote($this->config['taglib_end'], '/');
+ $this->config['tpl_begin'] = preg_quote($this->config['tpl_begin'], '/');
+ $this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/');
+
+ // 初始化模板编译存储器
+ $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File';
+ $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type);
+
+ $this->storage = new $class();
+ }
+
+ /**
+ * 模板变量赋值
+ * @access public
+ * @param array $vars 模板变量
+ * @return $this
+ */
+ public function assign(array $vars = [])
+ {
+ $this->data = array_merge($this->data, $vars);
+ return $this;
+ }
+
+ /**
+ * 模板引擎参数赋值
+ * @access public
+ * @param string $name
+ * @param mixed $value
+ */
+ public function __set($name, $value)
+ {
+ $this->config[$name] = $value;
+ }
+
+ /**
+ * 设置缓存对象
+ * @access public
+ * @param CacheInterface $cache 缓存对象
+ * @return void
+ */
+ public function setCache(CacheInterface $cache): void
+ {
+ $this->cache = $cache;
+ }
+
+ /**
+ * 模板引擎配置
+ * @access public
+ * @param array $config
+ * @return $this
+ */
+ public function config(array $config)
+ {
+ $this->config = array_merge($this->config, $config);
+ return $this;
+ }
+
+ /**
+ * 获取模板引擎配置项
+ * @access public
+ * @param string $name
+ * @return mixed
+ */
+ public function getConfig(string $name)
+ {
+ return $this->config[$name] ?? null;
+ }
+
+ /**
+ * 模板变量获取
+ * @access public
+ * @param string $name 变量名
+ * @return mixed
+ */
+ public function get(string $name = '')
+ {
+ if ('' == $name) {
+ return $this->data;
+ }
+
+ $data = $this->data;
+
+ foreach (explode('.', $name) as $key => $val) {
+ if (isset($data[$val])) {
+ $data = $data[$val];
+ } else {
+ $data = null;
+ break;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * 扩展模板解析规则
+ * @access public
+ * @param string $rule 解析规则
+ * @param callable $callback 解析规则
+ * @return void
+ */
+ public function extend(string $rule, callable $callback = null): void
+ {
+ $this->extend[$rule] = $callback;
+ }
+
+ /**
+ * 渲染模板文件
+ * @access public
+ * @param string $template 模板文件
+ * @param array $vars 模板变量
+ * @return void
+ */
+ public function fetch(string $template, array $vars = []): void
+ {
+ if ($vars) {
+ $this->data = array_merge($this->data, $vars);
+ }
+
+ if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) {
+ // 读取渲染缓存
+ if ($this->cache->has($this->config['cache_id'])) {
+ echo $this->cache->get($this->config['cache_id']);
+ return;
+ }
+ }
+
+ $template = $this->parseTemplateFile($template);
+
+ if ($template) {
+ $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_on'] . $this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.');
+
+ if (!$this->checkCache($cacheFile)) {
+ // 缓存无效 重新模板编译
+ $content = file_get_contents($template);
+ $this->compiler($content, $cacheFile);
+ }
+
+ // 页面缓存
+ ob_start();
+ ob_implicit_flush(0);
+
+ // 读取编译存储
+ $this->storage->read($cacheFile, $this->data);
+
+ // 获取并清空缓存
+ $content = ob_get_clean();
+
+ if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) {
+ // 缓存页面输出
+ $this->cache->set($this->config['cache_id'], $content, $this->config['cache_time']);
+ }
+
+ echo $content;
+ }
+ }
+
+ /**
+ * 检查编译缓存是否存在
+ * @access public
+ * @param string $cacheId 缓存的id
+ * @return boolean
+ */
+ public function isCache(string $cacheId): bool
+ {
+ if ($cacheId && $this->cache && $this->config['display_cache']) {
+ // 缓存页面输出
+ return $this->cache->has($cacheId);
+ }
+
+ return false;
+ }
+
+ /**
+ * 渲染模板内容
+ * @access public
+ * @param string $content 模板内容
+ * @param array $vars 模板变量
+ * @return void
+ */
+ public function display(string $content, array $vars = []): void
+ {
+ if ($vars) {
+ $this->data = array_merge($this->data, $vars);
+ }
+
+ $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.');
+
+ if (!$this->checkCache($cacheFile)) {
+ // 缓存无效 模板编译
+ $this->compiler($content, $cacheFile);
+ }
+
+ // 读取编译存储
+ $this->storage->read($cacheFile, $this->data);
+ }
+
+ /**
+ * 设置布局
+ * @access public
+ * @param mixed $name 布局模板名称 false 则关闭布局
+ * @param string $replace 布局模板内容替换标识
+ * @return $this
+ */
+ public function layout($name, string $replace = '')
+ {
+ if (false === $name) {
+ // 关闭布局
+ $this->config['layout_on'] = false;
+ } else {
+ // 开启布局
+ $this->config['layout_on'] = true;
+
+ // 名称必须为字符串
+ if (is_string($name)) {
+ $this->config['layout_name'] = $name;
+ }
+
+ if (!empty($replace)) {
+ $this->config['layout_item'] = $replace;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * 检查编译缓存是否有效
+ * 如果无效则需要重新编译
+ * @access private
+ * @param string $cacheFile 缓存文件名
+ * @return bool
+ */
+ private function checkCache(string $cacheFile): bool
+ {
+ if (!$this->config['tpl_cache'] || !is_file($cacheFile) || !$handle = @fopen($cacheFile, "r")) {
+ return false;
+ }
+
+ // 读取第一行
+ preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches);
+
+ if (!isset($matches[1])) {
+ return false;
+ }
+
+ $includeFile = unserialize($matches[1]);
+
+ if (!is_array($includeFile)) {
+ return false;
+ }
+
+ // 检查模板文件是否有更新
+ foreach ($includeFile as $path => $time) {
+ if (is_file($path) && filemtime($path) > $time) {
+ // 模板文件如果有更新则缓存需要更新
+ return false;
+ }
+ }
+
+ // 检查编译存储是否有效
+ return $this->storage->check($cacheFile, $this->config['cache_time']);
+ }
+
+ /**
+ * 编译模板文件内容
+ * @access private
+ * @param string $content 模板内容
+ * @param string $cacheFile 缓存文件名
+ * @return void
+ */
+ private function compiler(string &$content, string $cacheFile): void
+ {
+ // 判断是否启用布局
+ if ($this->config['layout_on']) {
+ if (false !== strpos($content, '{__NOLAYOUT__}')) {
+ // 可以单独定义不使用布局
+ $content = str_replace('{__NOLAYOUT__}', '', $content);
+ } else {
+ // 读取布局模板
+ $layoutFile = $this->parseTemplateFile($this->config['layout_name']);
+
+ if ($layoutFile) {
+ // 替换布局的主体内容
+ $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile));
+ }
+ }
+ } else {
+ $content = str_replace('{__NOLAYOUT__}', '', $content);
+ }
+
+ // 模板解析
+ $this->parse($content);
+
+ if ($this->config['strip_space']) {
+ /* 去除html空格与换行 */
+ $find = ['~>\s+<~', '~>(\s+\n|\r)~'];
+ $replace = ['><', '>'];
+ $content = preg_replace($find, $replace, $content);
+ }
+
+ // 优化生成的php代码
+ $content = preg_replace('/\?>\s*<\?php\s(?!echo\b|\bend)/s', '', $content);
+
+ // 模板过滤输出
+ $replace = $this->config['tpl_replace_string'];
+ $content = str_replace(array_keys($replace), array_values($replace), $content);
+
+ // 添加安全代码及模板引用记录
+ $content = '<?php /*' . serialize($this->includeFile) . '*/ ?>' . "\n" . $content;
+ // 编译存储
+ $this->storage->write($cacheFile, $content);
+
+ $this->includeFile = [];
+ }
+
+ /**
+ * 模板解析入口
+ * 支持普通标签和TagLib解析 支持自定义标签库
+ * @access public
+ * @param string $content 要解析的模板内容
+ * @return void
+ */
+ public function parse(string &$content): void
+ {
+ // 内容为空不解析
+ if (empty($content)) {
+ return;
+ }
+
+ // 替换literal标签内容
+ $this->parseLiteral($content);
+
+ // 解析继承
+ $this->parseExtend($content);
+
+ // 解析布局
+ $this->parseLayout($content);
+
+ // 检查include语法
+ $this->parseInclude($content);
+
+ // 替换包含文件中literal标签内容
+ $this->parseLiteral($content);
+
+ // 检查PHP语法
+ $this->parsePhp($content);
+
+ // 获取需要引入的标签库列表
+ // 标签库只需要定义一次,允许引入多个一次
+ // 一般放在文件的最前面
+ // 格式:<taglib name="html,mytag..." />
+ // 当TAGLIB_LOAD配置为true时才会进行检测
+ if ($this->config['taglib_load']) {
+ $tagLibs = $this->getIncludeTagLib($content);
+
+ if (!empty($tagLibs)) {
+ // 对导入的TagLib进行解析
+ foreach ($tagLibs as $tagLibName) {
+ $this->parseTagLib($tagLibName, $content);
+ }
+ }
+ }
+
+ // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀
+ if ($this->config['taglib_pre_load']) {
+ $tagLibs = explode(',', $this->config['taglib_pre_load']);
+
+ foreach ($tagLibs as $tag) {
+ $this->parseTagLib($tag, $content);
+ }
+ }
+
+ // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀
+ $tagLibs = explode(',', $this->config['taglib_build_in']);
+
+ foreach ($tagLibs as $tag) {
+ $this->parseTagLib($tag, $content, true);
+ }
+
+ // 解析普通模板标签 {$tagName}
+ $this->parseTag($content);
+
+ // 还原被替换的Literal标签
+ $this->parseLiteral($content, true);
+ }
+
+ /**
+ * 检查PHP语法
+ * @access private
+ * @param string $content 要解析的模板内容
+ * @return void
+ * @throws Exception
+ */
+ private function parsePhp(string &$content): void
+ {
+ // 短标签的情况要将<?标签用echo方式输出 否则无法正常输出xml标识
+ $content = preg_replace('/(<\?(?!php|=|$))/i', '<?php echo \'\\1\'; ?>' . "\n", $content);
+
+ // PHP语法检查
+ if ($this->config['tpl_deny_php'] && false !== strpos($content, '<?php')) {
+ throw new Exception('not allow php tag');
+ }
+ }
+
+ /**
+ * 解析模板中的布局标签
+ * @access private
+ * @param string $content 要解析的模板内容
+ * @return void
+ */
+ private function parseLayout(string &$content): void
+ {
+ // 读取模板中的布局标签
+ if (preg_match($this->getRegex('layout'), $content, $matches)) {
+ // 替换Layout标签
+ $content = str_replace($matches[0], '', $content);
+ // 解析Layout标签
+ $array = $this->parseAttr($matches[0]);
+
+ if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) {
+ // 读取布局模板
+ $layoutFile = $this->parseTemplateFile($array['name']);
+
+ if ($layoutFile) {
+ $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item'];
+ // 替换布局的主体内容
+ $content = str_replace($replace, $content, file_get_contents($layoutFile));
+ }
+ }
+ } else {
+ $content = str_replace('{__NOLAYOUT__}', '', $content);
+ }
+ }
+
+ /**
+ * 解析模板中的include标签
+ * @access private
+ * @param string $content 要解析的模板内容
+ * @return void
+ */
+ private function parseInclude(string &$content): void
+ {
+ $regex = $this->getRegex('include');
+ $func = function ($template) use (&$func, &$regex, &$content) {
+ if (preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $match) {
+ $array = $this->parseAttr($match[0]);
+ $file = $array['file'];
+ unset($array['file']);
+
+ // 分析模板文件名并读取内容
+ $parseStr = $this->parseTemplateName($file);
+
+ foreach ($array as $k => $v) {
+ // 以$开头字符串转换成模板变量
+ if (0 === strpos($v, '$')) {
+ $v = $this->get(substr($v, 1));
+ }
+
+ $parseStr = str_replace('[' . $k . ']', $v, $parseStr);
+ }
+
+ $content = str_replace($match[0], $parseStr, $content);
+ // 再次对包含文件进行模板分析
+ $func($parseStr);
+ }
+ unset($matches);
+ }
+ };
+
+ // 替换模板中的include标签
+ $func($content);
+ }
+
+ /**
+ * 解析模板中的extend标签
+ * @access private
+ * @param string $content 要解析的模板内容
+ * @return void
+ */
+ private function parseExtend(string &$content): void
+ {
+ $regex = $this->getRegex('extend');
+ $array = $blocks = $baseBlocks = [];
+ $extend = '';
+
+ $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) {
+ if (preg_match($regex, $template, $matches)) {
+ if (!isset($array[$matches['name']])) {
+ $array[$matches['name']] = 1;
+ // 读取继承模板
+ $extend = $this->parseTemplateName($matches['name']);
+
+ // 递归检查继承
+ $func($extend);
+
+ // 取得block标签内容
+ $blocks = array_merge($blocks, $this->parseBlock($template));
+
+ return;
+ }
+ } else {
+ // 取得顶层模板block标签内容
+ $baseBlocks = $this->parseBlock($template, true);
+
+ if (empty($extend)) {
+ // 无extend标签但有block标签的情况
+ $extend = $template;
+ }
+ }
+ };
+
+ $func($content);
+
+ if (!empty($extend)) {
+ if ($baseBlocks) {
+ $children = [];
+ foreach ($baseBlocks as $name => $val) {
+ $replace = $val['content'];
+
+ if (!empty($children[$name])) {
+ // 如果包含有子block标签
+ foreach ($children[$name] as $key) {
+ $replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace);
+ }
+ }
+
+ if (isset($blocks[$name])) {
+ // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖
+ $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']);
+
+ if (!empty($val['parent'])) {
+ // 如果不是最顶层的block标签
+ $parent = $val['parent'];
+
+ if (isset($blocks[$parent])) {
+ $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']);
+ }
+
+ $blocks[$name]['content'] = $replace;
+ $children[$parent][] = $name;
+
+ continue;
+ }
+ } elseif (!empty($val['parent'])) {
+ // 如果子标签没有被继承则用原值
+ $children[$val['parent']][] = $name;
+ $blocks[$name] = $val;
+ }
+
+ if (!$val['parent']) {
+ // 替换模板中的顶级block标签
+ $extend = str_replace($val['begin'] . $val['content'] . $val['end'], $replace, $extend);
+ }
+ }
+ }
+
+ $content = $extend;
+ unset($blocks, $baseBlocks);
+ }
+ }
+
+ /**
+ * 替换页面中的literal标签
+ * @access private
+ * @param string $content 模板内容
+ * @param boolean $restore 是否为还原
+ * @return void
+ */
+ private function parseLiteral(string &$content, bool $restore = false): void
+ {
+ $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal');
+
+ if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
+ if (!$restore) {
+ $count = count($this->literal);
+
+ // 替换literal标签
+ foreach ($matches as $match) {
+ $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2]));
+ $content = str_replace($match[0], "<!--###literal{$count}###-->", $content);
+ $count++;
+ }
+ } else {
+ // 还原literal标签
+ foreach ($matches as $match) {
+ $content = str_replace($match[0], $this->literal[$match[1]], $content);
+ }
+
+ // 清空literal记录
+ $this->literal = [];
+ }
+
+ unset($matches);
+ }
+ }
+
+ /**
+ * 获取模板中的block标签
+ * @access private
+ * @param string $content 模板内容
+ * @param boolean $sort 是否排序
+ * @return array
+ */
+ private function parseBlock(string &$content, bool $sort = false): array
+ {
+ $regex = $this->getRegex('block');
+ $result = [];
+
+ if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
+ $right = $keys = [];
+
+ foreach ($matches as $match) {
+ if (empty($match['name'][0])) {
+ if (count($right) > 0) {
+ $tag = array_pop($right);
+ $start = $tag['offset'] + strlen($tag['tag']);
+ $length = $match[0][1] - $start;
+
+ $result[$tag['name']] = [
+ 'begin' => $tag['tag'],
+ 'content' => substr($content, $start, $length),
+ 'end' => $match[0][0],
+ 'parent' => count($right) ? end($right)['name'] : '',
+ ];
+
+ $keys[$tag['name']] = $match[0][1];
+ }
+ } else {
+ // 标签头压入栈
+ $right[] = [
+ 'name' => $match[2][0],
+ 'offset' => $match[0][1],
+ 'tag' => $match[0][0],
+ ];
+ }
+ }
+
+ unset($right, $matches);
+
+ if ($sort) {
+ // 按block标签结束符在模板中的位置排序
+ array_multisort($keys, $result);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 搜索模板页面中包含的TagLib库
+ * 并返回列表
+ * @access private
+ * @param string $content 模板内容
+ * @return array|null
+ */
+ private function getIncludeTagLib(string &$content)
+ {
+ // 搜索是否有TagLib标签
+ if (preg_match($this->getRegex('taglib'), $content, $matches)) {
+ // 替换TagLib标签
+ $content = str_replace($matches[0], '', $content);
+
+ return explode(',', $matches['name']);
+ }
+ }
+
+ /**
+ * TagLib库解析
+ * @access public
+ * @param string $tagLib 要解析的标签库
+ * @param string $content 要解析的模板内容
+ * @param boolean $hide 是否隐藏标签库前缀
+ * @return void
+ */
+ public function parseTagLib(string $tagLib, string &$content, bool $hide = false): void
+ {
+ if (false !== strpos($tagLib, '\\')) {
+ // 支持指定标签库的命名空间
+ $className = $tagLib;
+ $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1);
+ } else {
+ $className = '\\think\\template\\taglib\\' . ucwords($tagLib);
+ }
+
+ $tLib = new $className($this);
+
+ $tLib->parseTag($content, $hide ? '' : $tagLib);
+ }
+
+ /**
+ * 分析标签属性
+ * @access public
+ * @param string $str 属性字符串
+ * @param string $name 不为空时返回指定的属性名
+ * @return array
+ */
+ public function parseAttr(string $str, string $name = null): array
+ {
+ $regex = '/\s+(?>(?P<name>[\w-]+)\s*)=(?>\s*)([\"\'])(?P<value>(?:(?!\\2).)*)\\2/is';
+ $array = [];
+
+ if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $match) {
+ $array[$match['name']] = $match['value'];
+ }
+ unset($matches);
+ }
+
+ if (!empty($name) && isset($array[$name])) {
+ return $array[$name];
+ }
+
+ return $array;
+ }
+
+ /**
+ * 模板标签解析
+ * 格式: {TagName:args [|content] }
+ * @access private
+ * @param string $content 要解析的模板内容
+ * @return void
+ */
+ private function parseTag(string &$content): void
+ {
+ $regex = $this->getRegex('tag');
+
+ if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $match) {
+ $str = stripslashes($match[1]);
+ $flag = substr($str, 0, 1);
+
+ switch ($flag) {
+ case '$':
+ // 解析模板变量 格式 {$varName}
+ // 是否带有?号
+ if (false !== $pos = strpos($str, '?')) {
+ $array = preg_split('/([!=]={1,2}|(?<!-)[><]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE);
+ $name = $array[0];
+
+ $this->parseVar($name);
+ //$this->parseVarFunction($name);
+
+ $str = trim(substr($str, $pos + 1));
+ $this->parseVar($str);
+ $first = substr($str, 0, 1);
+
+ if (strpos($name, ')')) {
+ // $name为对象或是自动识别,或者含有函数
+ if (isset($array[1])) {
+ $this->parseVar($array[2]);
+ $name .= $array[1] . $array[2];
+ }
+
+ switch ($first) {
+ case '?':
+ $this->parseVarFunction($name);
+ $str = '<?php echo (' . $name . ') ? ' . $name . ' : ' . substr($str, 1) . '; ?>';
+ break;
+ case '=':
+ $str = '<?php if(' . $name . ') echo ' . substr($str, 1) . '; ?>';
+ break;
+ default:
+ $str = '<?php echo ' . $name . '?' . $str . '; ?>';
+ }
+ } else {
+ if (isset($array[1])) {
+ $express = true;
+ $this->parseVar($array[2]);
+ $express = $name . $array[1] . $array[2];
+ } else {
+ $express = false;
+ }
+
+ if (in_array($first, ['?', '=', ':'])) {
+ $str = trim(substr($str, 1));
+ if ('$' == substr($str, 0, 1)) {
+ $str = $this->parseVarFunction($str);
+ }
+ }
+
+ // $name为数组
+ switch ($first) {
+ case '?':
+ // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx
+ $str = '<?php echo ' . ($express ?: 'isset(' . $name . ')') . ' ? ' . $this->parseVarFunction($name) . ' : ' . $str . '; ?>';
+ break;
+ case '=':
+ // {$varname?='xxx'} $varname为真时才输出xxx
+ $str = '<?php if(' . ($express ?: '!empty(' . $name . ')') . ') echo ' . $str . '; ?>';
+ break;
+ case ':':
+ // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx
+ $str = '<?php echo ' . ($express ?: '!empty(' . $name . ')') . ' ? ' . $this->parseVarFunction($name) . ' : ' . $str . '; ?>';
+ break;
+ default:
+ if (strpos($str, ':')) {
+ // {$varname ? 'a' : 'b'} $varname为真时输出a,否则输出b
+ $array = explode(':', $str, 2);
+
+ $array[0] = '$' == substr(trim($array[0]), 0, 1) ? $this->parseVarFunction($array[0]) : $array[0];
+ $array[1] = '$' == substr(trim($array[1]), 0, 1) ? $this->parseVarFunction($array[1]) : $array[1];
+
+ $str = implode(' : ', $array);
+ }
+ $str = '<?php echo ' . ($express ?: '!empty(' . $name . ')') . ' ? ' . $str . '; ?>';
+ }
+ }
+ } else {
+ $this->parseVar($str);
+ $this->parseVarFunction($str);
+ $str = '<?php echo ' . $str . '; ?>';
+ }
+ break;
+ case ':':
+ // 输出某个函数的结果
+ $str = substr($str, 1);
+ $this->parseVar($str);
+ $str = '<?php echo ' . $str . '; ?>';
+ break;
+ case '~':
+ // 执行某个函数
+ $str = substr($str, 1);
+ $this->parseVar($str);
+ $str = '<?php ' . $str . '; ?>';
+ break;
+ case '-':
+ case '+':
+ // 输出计算
+ $this->parseVar($str);
+ $str = '<?php echo ' . $str . '; ?>';
+ break;
+ case '/':
+ // 注释标签
+ $flag2 = substr($str, 1, 1);
+ if ('/' == $flag2 || ('*' == $flag2 && substr(rtrim($str), -2) == '*/')) {
+ $str = '';
+ }
+ break;
+ default:
+ // 未识别的标签直接返回
+ $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end'];
+ break;
+ }
+
+ $content = str_replace($match[0], $str, $content);
+ }
+
+ unset($matches);
+ }
+ }
+
+ /**
+ * 模板变量解析,支持使用函数
+ * 格式: {$varname|function1|function2=arg1,arg2}
+ * @access public
+ * @param string $varStr 变量数据
+ * @return void
+ */
+ public function parseVar(string &$varStr): void
+ {
+ $varStr = trim($varStr);
+
+ if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) {
+ static $_varParseList = [];
+
+ while ($matches[0]) {
+ $match = array_pop($matches[0]);
+
+ //如果已经解析过该变量字串,则直接返回变量值
+ if (isset($_varParseList[$match[0]])) {
+ $parseStr = $_varParseList[$match[0]];
+ } else {
+ if (strpos($match[0], '.')) {
+ $vars = explode('.', $match[0]);
+ $first = array_shift($vars);
+
+ if (isset($this->extend[$first])) {
+ $callback = $this->extend[$first];
+ $parseStr = $callback($vars);
+ } elseif ('$Request' == $first) {
+ // 输出请求变量
+ $parseStr = $this->parseRequestVar($vars);
+ } elseif ('$Think' == $first) {
+ // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出
+ $parseStr = $this->parseThinkVar($vars);
+ } else {
+ switch ($this->config['tpl_var_identify']) {
+ case 'array': // 识别为数组
+ $parseStr = $first . '[\'' . implode('\'][\'', $vars) . '\']';
+ break;
+ case 'obj': // 识别为对象
+ $parseStr = $first . '->' . implode('->', $vars);
+ break;
+ default: // 自动判断数组或对象
+ $parseStr = '(is_array(' . $first . ')?' . $first . '[\'' . implode('\'][\'', $vars) . '\']:' . $first . '->' . implode('->', $vars) . ')';
+ }
+ }
+ } else {
+ $parseStr = str_replace(':', '->', $match[0]);
+ }
+
+ $_varParseList[$match[0]] = $parseStr;
+ }
+
+ $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0]));
+ }
+ unset($matches);
+ }
+ }
+
+ /**
+ * 对模板中使用了函数的变量进行解析
+ * 格式 {$varname|function1|function2=arg1,arg2}
+ * @access public
+ * @param string $varStr 变量字符串
+ * @param bool $autoescape 自动转义
+ * @return string
+ */
+ public function parseVarFunction(string &$varStr, bool $autoescape = true): string
+ {
+ if (!$autoescape && false === strpos($varStr, '|')) {
+ return $varStr;
+ } elseif ($autoescape && !preg_match('/\|(\s)?raw(\||\s)?/i', $varStr)) {
+ $varStr .= '|' . $this->config['default_filter'];
+ }
+
+ static $_varFunctionList = [];
+
+ $_key = md5($varStr);
+
+ //如果已经解析过该变量字串,则直接返回变量值
+ if (isset($_varFunctionList[$_key])) {
+ $varStr = $_varFunctionList[$_key];
+ } else {
+ $varArray = explode('|', $varStr);
+
+ // 取得变量名称
+ $name = trim(array_shift($varArray));
+
+ // 对变量使用函数
+ $length = count($varArray);
+
+ // 取得模板禁止使用函数列表
+ $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']);
+
+ for ($i = 0; $i < $length; $i++) {
+ $args = explode('=', $varArray[$i], 2);
+
+ // 模板函数过滤
+ $fun = trim($args[0]);
+ if (in_array($fun, $template_deny_funs)) {
+ continue;
+ }
+
+ switch (strtolower($fun)) {
+ case 'raw':
+ break;
+ case 'date':
+ $name = 'date(' . $args[1] . ',!is_numeric(' . $name . ')? strtotime(' . $name . ') : ' . $name . ')';
+ break;
+ case 'first':
+ $name = 'current(' . $name . ')';
+ break;
+ case 'last':
+ $name = 'end(' . $name . ')';
+ break;
+ case 'upper':
+ $name = 'strtoupper(' . $name . ')';
+ break;
+ case 'lower':
+ $name = 'strtolower(' . $name . ')';
+ break;
+ case 'format':
+ $name = 'sprintf(' . $args[1] . ',' . $name . ')';
+ break;
+ case 'default': // 特殊模板函数
+ if (false === strpos($name, '(')) {
+ $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')';
+ } else {
+ $name = '(' . $name . ' ?: ' . $args[1] . ')';
+ }
+ break;
+ default: // 通用模板函数
+ if (isset($args[1])) {
+ if (strstr($args[1], '###')) {
+ $args[1] = str_replace('###', $name, $args[1]);
+ $name = "$fun($args[1])";
+ } else {
+ $name = "$fun($name,$args[1])";
+ }
+ } else {
+ if (!empty($args[0])) {
+ $name = "$fun($name)";
+ }
+ }
+ }
+ }
+
+ $_varFunctionList[$_key] = $name;
+ $varStr = $name;
+ }
+ return $varStr;
+ }
+
+ /**
+ * 请求变量解析
+ * 格式 以 $Request. 打头的变量属于请求变量
+ * @access public
+ * @param array $vars 变量数组
+ * @return string
+ */
+ public function parseRequestVar(array $vars): string
+ {
+ $type = strtoupper(trim(array_shift($vars)));
+ $param = implode('.', $vars);
+
+ switch ($type) {
+ case 'SERVER':
+ $parseStr = '$_SERVER[\'' . $param . '\']';
+ break;
+ case 'GET':
+ $parseStr = '$_GET[\'' . $param . '\']';
+ break;
+ case 'POST':
+ $parseStr = '$_POST[\'' . $param . '\']';
+ break;
+ case 'COOKIE':
+ $parseStr = '$_COOKIE[\'' . $param . '\']';
+ break;
+ case 'SESSION':
+ $parseStr = '$_SESSION[\'' . $param . '\']';
+ break;
+ case 'ENV':
+ $parseStr = '$_ENV[\'' . $param . '\']';
+ break;
+ case 'REQUEST':
+ $parseStr = '$_REQUEST[\'' . $param . '\']';
+ break;
+ default:
+ $parseStr = '\'\'';
+ }
+
+ return $parseStr;
+ }
+
+ /**
+ * 特殊模板变量解析
+ * 格式 以 $Think. 打头的变量属于特殊模板变量
+ * @access public
+ * @param array $vars 变量数组
+ * @return string
+ */
+ public function parseThinkVar(array $vars): string
+ {
+ $type = strtoupper(trim(array_shift($vars)));
+ $param = implode('.', $vars);
+
+ switch ($type) {
+ case 'CONST':
+ $parseStr = strtoupper($param);
+ break;
+ case 'NOW':
+ $parseStr = "date('Y-m-d g:i a',time())";
+ break;
+ case 'LDELIM':
+ $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\'';
+ break;
+ case 'RDELIM':
+ $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\'';
+ break;
+ default:
+ $parseStr = defined($type) ? $type : '\'\'';
+ }
+
+ return $parseStr;
+ }
+
+ /**
+ * 分析加载的模板文件并读取内容 支持多个模板文件读取
+ * @access private
+ * @param string $templateName 模板文件名
+ * @return string
+ */
+ private function parseTemplateName(string $templateName): string
+ {
+ $array = explode(',', $templateName);
+ $parseStr = '';
+
+ foreach ($array as $templateName) {
+ if (empty($templateName)) {
+ continue;
+ }
+
+ if (0 === strpos($templateName, '$')) {
+ //支持加载变量文件名
+ $templateName = $this->get(substr($templateName, 1));
+ }
+
+ $template = $this->parseTemplateFile($templateName);
+
+ if ($template) {
+ // 获取模板文件内容
+ $parseStr .= file_get_contents($template);
+ }
+ }
+
+ return $parseStr;
+ }
+
+ /**
+ * 解析模板文件名
+ * @access private
+ * @param string $template 文件名
+ * @return string
+ */
+ private function parseTemplateFile(string $template): string
+ {
+ if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
+
+ if (0 !== strpos($template, '/')) {
+ $template = str_replace(['/', ':'], $this->config['view_depr'], $template);
+ } else {
+ $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1));
+ }
+
+ $template = $this->config['view_path'] . $template . '.' . ltrim($this->config['view_suffix'], '.');
+ }
+
+ if (is_file($template)) {
+ // 记录模板文件的更新时间
+ $this->includeFile[$template] = filemtime($template);
+
+ return $template;
+ }
+
+ throw new Exception('template not exists:' . $template);
+ }
+
+ /**
+ * 按标签生成正则
+ * @access private
+ * @param string $tagName 标签名
+ * @return string
+ */
+ private function getRegex(string $tagName): string
+ {
+ $regex = '';
+ if ('tag' == $tagName) {
+ $begin = $this->config['tpl_begin'];
+ $end = $this->config['tpl_end'];
+
+ if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) {
+ $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end;
+ } else {
+ $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end;
+ }
+ } else {
+ $begin = $this->config['taglib_begin'];
+ $end = $this->config['taglib_end'];
+ $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false;
+
+ switch ($tagName) {
+ case 'block':
+ if ($single) {
+ $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P<name>[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end;
+ } else {
+ $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P<name>[\$\w\-\/\.]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end;
+ }
+ break;
+ case 'literal':
+ if ($single) {
+ $regex = '(' . $begin . $tagName . '\b(?>[^' . $end . ']*)' . $end . ')';
+ $regex .= '(?:(?>[^' . $begin . ']*)(?>(?!' . $begin . '(?>' . $tagName . '\b[^' . $end . ']*|\/' . $tagName . ')' . $end . ')' . $begin . '[^' . $begin . ']*)*)';
+ $regex .= '(' . $begin . '\/' . $tagName . $end . ')';
+ } else {
+ $regex = '(' . $begin . $tagName . '\b(?>(?:(?!' . $end . ').)*)' . $end . ')';
+ $regex .= '(?:(?>(?:(?!' . $begin . ').)*)(?>(?!' . $begin . '(?>' . $tagName . '\b(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end . ')' . $begin . '(?>(?:(?!' . $begin . ').)*))*)';
+ $regex .= '(' . $begin . '\/' . $tagName . $end . ')';
+ }
+ break;
+ case 'restoreliteral':
+ $regex = '<!--###literal(\d+)###-->';
+ break;
+ case 'include':
+ $name = 'file';
+ case 'taglib':
+ case 'layout':
+ case 'extend':
+ if (empty($name)) {
+ $name = 'name';
+ }
+ if ($single) {
+ $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P<name>[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end;
+ } else {
+ $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P<name>[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end;
+ }
+ break;
+ }
+ }
+
+ return '/' . $regex . '/is';
+ }
+
+ public function __debugInfo()
+ {
+ $data = get_object_vars($this);
+ unset($data['storage']);
+
+ return $data;
+ }
+}
diff --git a/vendor/topthink/think-template/src/facade/Template.php b/vendor/topthink/think-template/src/facade/Template.php
new file mode 100755
index 0000000..665a180
--- /dev/null
+++ b/vendor/topthink/think-template/src/facade/Template.php
@@ -0,0 +1,83 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\facade;
+
+if (class_exists('think\Facade')) {
+ class Facade extends \think\Facade
+ {}
+} else {
+ class Facade
+ {
+ /**
+ * 始终创建新的对象实例
+ * @var bool
+ */
+ protected static $alwaysNewInstance;
+
+ protected static $instance;
+
+ /**
+ * 获取当前Facade对应类名
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {}
+
+ /**
+ * 创建Facade实例
+ * @static
+ * @access protected
+ * @return object
+ */
+ protected static function createFacade()
+ {
+ $class = static::getFacadeClass() ?: 'think\Template';
+
+ if (static::$alwaysNewInstance) {
+ return new $class();
+ }
+
+ if (!self::$instance) {
+ self::$instance = new $class();
+ }
+
+ return self::$instance;
+
+ }
+
+ // 调用实际类的方法
+ public static function __callStatic($method, $params)
+ {
+ return call_user_func_array([static::createFacade(), $method], $params);
+ }
+ }
+}
+
+/**
+ * @see \think\Template
+ * @mixin \think\Template
+ */
+class Template extends Facade
+{
+ protected static $alwaysNewInstance = true;
+
+ /**
+ * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
+ * @access protected
+ * @return string
+ */
+ protected static function getFacadeClass()
+ {
+ return 'think\Template';
+ }
+}
diff --git a/vendor/topthink/think-template/src/template/TagLib.php b/vendor/topthink/think-template/src/template/TagLib.php
new file mode 100755
index 0000000..f6c8fbb
--- /dev/null
+++ b/vendor/topthink/think-template/src/template/TagLib.php
@@ -0,0 +1,349 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\template;
+
+use Exception;
+use think\Template;
+
+/**
+ * ThinkPHP标签库TagLib解析基类
+ * @category Think
+ * @package Think
+ * @subpackage Template
+ * @author liu21st <liu21st@gmail.com>
+ */
+class TagLib
+{
+
+ /**
+ * 标签库定义XML文件
+ * @var string
+ * @access protected
+ */
+ protected $xml = '';
+ protected $tags = []; // 标签定义
+ /**
+ * 标签库名称
+ * @var string
+ * @access protected
+ */
+ protected $tagLib = '';
+
+ /**
+ * 标签库标签列表
+ * @var array
+ * @access protected
+ */
+ protected $tagList = [];
+
+ /**
+ * 标签库分析数组
+ * @var array
+ * @access protected
+ */
+ protected $parse = [];
+
+ /**
+ * 标签库是否有效
+ * @var bool
+ * @access protected
+ */
+ protected $valid = false;
+
+ /**
+ * 当前模板对象
+ * @var object
+ * @access protected
+ */
+ protected $tpl;
+
+ protected $comparison = [' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < '];
+
+ /**
+ * 架构函数
+ * @access public
+ * @param Template $template 模板引擎对象
+ */
+ public function __construct(Template $template)
+ {
+ $this->tpl = $template;
+ }
+
+ /**
+ * 按签标库替换页面中的标签
+ * @access public
+ * @param string $content 模板内容
+ * @param string $lib 标签库名
+ * @return void
+ */
+ public function parseTag(string &$content, string $lib = ''): void
+ {
+ $tags = [];
+ $lib = $lib ? strtolower($lib) . ':' : '';
+
+ foreach ($this->tags as $name => $val) {
+ $close = !isset($val['close']) || $val['close'] ? 1 : 0;
+ $tags[$close][$lib . $name] = $name;
+ if (isset($val['alias'])) {
+ // 别名设置
+ $array = (array) $val['alias'];
+ foreach (explode(',', $array[0]) as $v) {
+ $tags[$close][$lib . $v] = $name;
+ }
+ }
+ }
+
+ // 闭合标签
+ if (!empty($tags[1])) {
+ $nodes = [];
+ $regex = $this->getRegex(array_keys($tags[1]), 1);
+ if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
+ $right = [];
+ foreach ($matches as $match) {
+ if ('' == $match[1][0]) {
+ $name = strtolower($match[2][0]);
+ // 如果有没闭合的标签头则取出最后一个
+ if (!empty($right[$name])) {
+ // $match[0][1]为标签结束符在模板中的位置
+ $nodes[$match[0][1]] = [
+ 'name' => $name,
+ 'begin' => array_pop($right[$name]), // 标签开始符
+ 'end' => $match[0], // 标签结束符
+ ];
+ }
+ } else {
+ // 标签头压入栈
+ $right[strtolower($match[1][0])][] = $match[0];
+ }
+ }
+ unset($right, $matches);
+ // 按标签在模板中的位置从后向前排序
+ krsort($nodes);
+ }
+
+ $break = '<!--###break###--!>';
+ if ($nodes) {
+ $beginArray = [];
+ // 标签替换 从后向前
+ foreach ($nodes as $pos => $node) {
+ // 对应的标签名
+ $name = $tags[1][$node['name']];
+ $alias = $lib . $name != $node['name'] ? ($lib ? strstr($node['name'], $lib) : $node['name']) : '';
+
+ // 解析标签属性
+ $attrs = $this->parseAttr($node['begin'][0], $name, $alias);
+ $method = 'tag' . $name;
+
+ // 读取标签库中对应的标签内容 replace[0]用来替换标签头,replace[1]用来替换标签尾
+ $replace = explode($break, $this->$method($attrs, $break));
+
+ if (count($replace) > 1) {
+ while ($beginArray) {
+ $begin = end($beginArray);
+ // 判断当前标签尾的位置是否在栈中最后一个标签头的后面,是则为子标签
+ if ($node['end'][1] > $begin['pos']) {
+ break;
+ } else {
+ // 不为子标签时,取出栈中最后一个标签头
+ $begin = array_pop($beginArray);
+ // 替换标签头部
+ $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']);
+ }
+ }
+ // 替换标签尾部
+ $content = substr_replace($content, $replace[1], $node['end'][1], strlen($node['end'][0]));
+ // 把标签头压入栈
+ $beginArray[] = ['pos' => $node['begin'][1], 'len' => strlen($node['begin'][0]), 'str' => $replace[0]];
+ }
+ }
+
+ while ($beginArray) {
+ $begin = array_pop($beginArray);
+ // 替换标签头部
+ $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']);
+ }
+ }
+ }
+ // 自闭合标签
+ if (!empty($tags[0])) {
+ $regex = $this->getRegex(array_keys($tags[0]), 0);
+ $content = preg_replace_callback($regex, function ($matches) use (&$tags, &$lib) {
+ // 对应的标签名
+ $name = $tags[0][strtolower($matches[1])];
+ $alias = $lib . $name != $matches[1] ? ($lib ? strstr($matches[1], $lib) : $matches[1]) : '';
+ // 解析标签属性
+ $attrs = $this->parseAttr($matches[0], $name, $alias);
+ $method = 'tag' . $name;
+ return $this->$method($attrs, '');
+ }, $content);
+ }
+ }
+
+ /**
+ * 按标签生成正则
+ * @access public
+ * @param array|string $tags 标签名
+ * @param boolean $close 是否为闭合标签
+ * @return string
+ */
+ public function getRegex($tags, bool $close): string
+ {
+ $begin = $this->tpl->getConfig('taglib_begin');
+ $end = $this->tpl->getConfig('taglib_end');
+ $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false;
+ $tagName = is_array($tags) ? implode('|', $tags) : $tags;
+
+ if ($single) {
+ if ($close) {
+ // 如果是闭合标签
+ $regex = $begin . '(?:(' . $tagName . ')\b(?>[^' . $end . ']*)|\/(' . $tagName . '))' . $end;
+ } else {
+ $regex = $begin . '(' . $tagName . ')\b(?>[^' . $end . ']*)' . $end;
+ }
+ } else {
+ if ($close) {
+ // 如果是闭合标签
+ $regex = $begin . '(?:(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)|\/(' . $tagName . '))' . $end;
+ } else {
+ $regex = $begin . '(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)' . $end;
+ }
+ }
+
+ return '/' . $regex . '/is';
+ }
+
+ /**
+ * 分析标签属性 正则方式
+ * @access public
+ * @param string $str 标签属性字符串
+ * @param string $name 标签名
+ * @param string $alias 别名
+ * @return array
+ */
+ public function parseAttr(string $str, string $name, string $alias = ''): array
+ {
+ $regex = '/\s+(?>(?P<name>[\w-]+)\s*)=(?>\s*)([\"\'])(?P<value>(?:(?!\\2).)*)\\2/is';
+ $result = [];
+
+ if (preg_match_all($regex, $str, $matches)) {
+ foreach ($matches['name'] as $key => $val) {
+ $result[$val] = $matches['value'][$key];
+ }
+
+ if (!isset($this->tags[$name])) {
+ // 检测是否存在别名定义
+ foreach ($this->tags as $key => $val) {
+ if (isset($val['alias'])) {
+ $array = (array) $val['alias'];
+ if (in_array($name, explode(',', $array[0]))) {
+ $tag = $val;
+ $type = !empty($array[1]) ? $array[1] : 'type';
+ $result[$type] = $name;
+ break;
+ }
+ }
+ }
+ } else {
+ $tag = $this->tags[$name];
+ // 设置了标签别名
+ if (!empty($alias) && isset($tag['alias'])) {
+ $type = !empty($tag['alias'][1]) ? $tag['alias'][1] : 'type';
+ $result[$type] = $alias;
+ }
+ }
+
+ if (!empty($tag['must'])) {
+ $must = explode(',', $tag['must']);
+ foreach ($must as $name) {
+ if (!isset($result[$name])) {
+ throw new Exception('tag attr must:' . $name);
+ }
+ }
+ }
+ } else {
+ // 允许直接使用表达式的标签
+ if (!empty($this->tags[$name]['expression'])) {
+ static $_taglibs;
+ if (!isset($_taglibs[$name])) {
+ $_taglibs[$name][0] = strlen($this->tpl->getConfig('taglib_begin_origin') . $name);
+ $_taglibs[$name][1] = strlen($this->tpl->getConfig('taglib_end_origin'));
+ }
+ $result['expression'] = substr($str, $_taglibs[$name][0], -$_taglibs[$name][1]);
+ // 清除自闭合标签尾部/
+ $result['expression'] = rtrim($result['expression'], '/');
+ $result['expression'] = trim($result['expression']);
+ } elseif (empty($this->tags[$name]) || !empty($this->tags[$name]['attr'])) {
+ throw new Exception('tag error:' . $name);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * 解析条件表达式
+ * @access public
+ * @param string $condition 表达式标签内容
+ * @return string
+ */
+ public function parseCondition(string $condition): string
+ {
+ if (strpos($condition, ':')) {
+ $condition = ' ' . substr(strstr($condition, ':'), 1);
+ }
+
+ $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition);
+ $this->tpl->parseVar($condition);
+
+ return $condition;
+ }
+
+ /**
+ * 自动识别构建变量
+ * @access public
+ * @param string $name 变量描述
+ * @return string
+ */
+ public function autoBuildVar(string &$name): string
+ {
+ $flag = substr($name, 0, 1);
+
+ if (':' == $flag) {
+ // 以:开头为函数调用,解析前去掉:
+ $name = substr($name, 1);
+ } elseif ('$' != $flag && preg_match('/[a-zA-Z_]/', $flag)) {
+ // XXX: 这句的写法可能还需要改进
+ // 常量不需要解析
+ if (defined($name)) {
+ return $name;
+ }
+
+ // 不以$开头并且也不是常量,自动补上$前缀
+ $name = '$' . $name;
+ }
+
+ $this->tpl->parseVar($name);
+ $this->tpl->parseVarFunction($name, false);
+
+ return $name;
+ }
+
+ /**
+ * 获取标签列表
+ * @access public
+ * @return array
+ */
+ public function getTags(): array
+ {
+ return $this->tags;
+ }
+}
diff --git a/vendor/topthink/think-template/src/template/driver/File.php b/vendor/topthink/think-template/src/template/driver/File.php
new file mode 100755
index 0000000..510d10a
--- /dev/null
+++ b/vendor/topthink/think-template/src/template/driver/File.php
@@ -0,0 +1,83 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\template\driver;
+
+use Exception;
+
+class File
+{
+ protected $cacheFile;
+
+ /**
+ * 写入编译缓存
+ * @access public
+ * @param string $cacheFile 缓存的文件名
+ * @param string $content 缓存的内容
+ * @return void
+ */
+ public function write(string $cacheFile, string $content): void
+ {
+ // 检测模板目录
+ $dir = dirname($cacheFile);
+
+ if (!is_dir($dir)) {
+ mkdir($dir, 0755, true);
+ }
+
+ // 生成模板缓存文件
+ if (false === file_put_contents($cacheFile, $content)) {
+ throw new Exception('cache write error:' . $cacheFile, 11602);
+ }
+ }
+
+ /**
+ * 读取编译编译
+ * @access public
+ * @param string $cacheFile 缓存的文件名
+ * @param array $vars 变量数组
+ * @return void
+ */
+ public function read(string $cacheFile, array $vars = []): void
+ {
+ $this->cacheFile = $cacheFile;
+
+ if (!empty($vars) && is_array($vars)) {
+ // 模板阵列变量分解成为独立变量
+ extract($vars, EXTR_OVERWRITE);
+ }
+
+ //载入模版缓存文件
+ include $this->cacheFile;
+ }
+
+ /**
+ * 检查编译缓存是否有效
+ * @access public
+ * @param string $cacheFile 缓存的文件名
+ * @param int $cacheTime 缓存时间
+ * @return bool
+ */
+ public function check(string $cacheFile, int $cacheTime): bool
+ {
+ // 缓存文件不存在, 直接返回false
+ if (!file_exists($cacheFile)) {
+ return false;
+ }
+
+ if (0 != $cacheTime && time() > filemtime($cacheFile) + $cacheTime) {
+ // 缓存是否在有效期
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php b/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php
new file mode 100755
index 0000000..dd88b32
--- /dev/null
+++ b/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php
@@ -0,0 +1,33 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\template\exception;
+
+class TemplateNotFoundException extends \RuntimeException
+{
+ protected $template;
+
+ public function __construct(string $message, string $template = '')
+ {
+ $this->message = $message;
+ $this->template = $template;
+ }
+
+ /**
+ * 获取模板文件
+ * @access public
+ * @return string
+ */
+ public function getTemplate(): string
+ {
+ return $this->template;
+ }
+}
diff --git a/vendor/topthink/think-template/src/template/taglib/Cx.php b/vendor/topthink/think-template/src/template/taglib/Cx.php
new file mode 100755
index 0000000..bccafc1
--- /dev/null
+++ b/vendor/topthink/think-template/src/template/taglib/Cx.php
@@ -0,0 +1,715 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\template\taglib;
+
+use think\template\TagLib;
+
+/**
+ * CX标签库解析类
+ * @category Think
+ * @package Think
+ * @subpackage Driver.Taglib
+ * @author liu21st <liu21st@gmail.com>
+ */
+class Cx extends Taglib
+{
+
+ // 标签定义
+ protected $tags = [
+ // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次
+ 'php' => ['attr' => ''],
+ 'volist' => ['attr' => 'name,id,offset,length,key,mod', 'alias' => 'iterate'],
+ 'foreach' => ['attr' => 'name,id,item,key,offset,length,mod', 'expression' => true],
+ 'if' => ['attr' => 'condition', 'expression' => true],
+ 'elseif' => ['attr' => 'condition', 'close' => 0, 'expression' => true],
+ 'else' => ['attr' => '', 'close' => 0],
+ 'switch' => ['attr' => 'name', 'expression' => true],
+ 'case' => ['attr' => 'value,break', 'expression' => true],
+ 'default' => ['attr' => '', 'close' => 0],
+ 'compare' => ['attr' => 'name,value,type', 'alias' => ['eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq', 'type']],
+ 'range' => ['attr' => 'name,value,type', 'alias' => ['in,notin,between,notbetween', 'type']],
+ 'empty' => ['attr' => 'name'],
+ 'notempty' => ['attr' => 'name'],
+ 'present' => ['attr' => 'name'],
+ 'notpresent' => ['attr' => 'name'],
+ 'defined' => ['attr' => 'name'],
+ 'notdefined' => ['attr' => 'name'],
+ 'load' => ['attr' => 'file,href,type,value,basepath', 'close' => 0, 'alias' => ['import,css,js', 'type']],
+ 'assign' => ['attr' => 'name,value', 'close' => 0],
+ 'define' => ['attr' => 'name,value', 'close' => 0],
+ 'for' => ['attr' => 'start,end,name,comparison,step'],
+ 'url' => ['attr' => 'link,vars,suffix,domain', 'close' => 0, 'expression' => true],
+ 'function' => ['attr' => 'name,vars,use,call'],
+ ];
+
+ /**
+ * php标签解析
+ * 格式:
+ * {php}echo $name{/php}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagPhp(array $tag, string $content): string
+ {
+ $parseStr = '<?php ' . $content . ' ?>';
+ return $parseStr;
+ }
+
+ /**
+ * volist标签解析 循环输出数据集
+ * 格式:
+ * {volist name="userList" id="user" empty=""}
+ * {user.username}
+ * {user.email}
+ * {/volist}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagVolist(array $tag, string $content): string
+ {
+ $name = $tag['name'];
+ $id = $tag['id'];
+ $empty = isset($tag['empty']) ? $tag['empty'] : '';
+ $key = !empty($tag['key']) ? $tag['key'] : 'i';
+ $mod = isset($tag['mod']) ? $tag['mod'] : '2';
+ $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0;
+ $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null';
+ // 允许使用函数设定数据集 <volist name=":fun('arg')" id="vo">{$vo.name}</volist>
+ $parseStr = '<?php ';
+ $flag = substr($name, 0, 1);
+
+ if (':' == $flag) {
+ $name = $this->autoBuildVar($name);
+ $parseStr .= '$_result=' . $name . ';';
+ $name = '$_result';
+ } else {
+ $name = $this->autoBuildVar($name);
+ }
+
+ $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;';
+
+ // 设置了输出数组长度
+ if (0 != $offset || 'null' != $length) {
+ $parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); ';
+ } else {
+ $parseStr .= ' $__LIST__ = ' . $name . ';';
+ }
+
+ $parseStr .= 'if( count($__LIST__)==0 ) : echo "' . $empty . '" ;';
+ $parseStr .= 'else: ';
+ $parseStr .= 'foreach($__LIST__ as $key=>$' . $id . '): ';
+ $parseStr .= '$mod = ($' . $key . ' % ' . $mod . ' );';
+ $parseStr .= '++$' . $key . ';?>';
+ $parseStr .= $content;
+ $parseStr .= '<?php endforeach; endif; else: echo "' . $empty . '" ;endif; ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * foreach标签解析 循环输出数据集
+ * 格式:
+ * {foreach name="userList" id="user" key="key" index="i" mod="2" offset="3" length="5" empty=""}
+ * {user.username}
+ * {/foreach}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagForeach(array $tag, string $content): string
+ {
+ // 直接使用表达式
+ if (!empty($tag['expression'])) {
+ $expression = ltrim(rtrim($tag['expression'], ')'), '(');
+ $expression = $this->autoBuildVar($expression);
+ $parseStr = '<?php foreach(' . $expression . '): ?>';
+ $parseStr .= $content;
+ $parseStr .= '<?php endforeach; ?>';
+ return $parseStr;
+ }
+
+ $name = $tag['name'];
+ $key = !empty($tag['key']) ? $tag['key'] : 'key';
+ $item = !empty($tag['id']) ? $tag['id'] : $tag['item'];
+ $empty = isset($tag['empty']) ? $tag['empty'] : '';
+ $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0;
+ $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null';
+
+ $parseStr = '<?php ';
+
+ // 支持用函数传数组
+ if (':' == substr($name, 0, 1)) {
+ $var = '$_' . uniqid();
+ $name = $this->autoBuildVar($name);
+ $parseStr .= $var . '=' . $name . '; ';
+ $name = $var;
+ } else {
+ $name = $this->autoBuildVar($name);
+ }
+
+ $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): ';
+
+ // 设置了输出数组长度
+ if (0 != $offset || 'null' != $length) {
+ if (!isset($var)) {
+ $var = '$_' . uniqid();
+ }
+ $parseStr .= $var . ' = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); ';
+ } else {
+ $var = &$name;
+ }
+
+ $parseStr .= 'if( count(' . $var . ')==0 ) : echo "' . $empty . '" ;';
+ $parseStr .= 'else: ';
+
+ // 设置了索引项
+ if (isset($tag['index'])) {
+ $index = $tag['index'];
+ $parseStr .= '$' . $index . '=0; ';
+ }
+
+ $parseStr .= 'foreach(' . $var . ' as $' . $key . '=>$' . $item . '): ';
+
+ // 设置了索引项
+ if (isset($tag['index'])) {
+ $index = $tag['index'];
+ if (isset($tag['mod'])) {
+ $mod = (int) $tag['mod'];
+ $parseStr .= '$mod = ($' . $index . ' % ' . $mod . '); ';
+ }
+ $parseStr .= '++$' . $index . '; ';
+ }
+
+ $parseStr .= '?>';
+ // 循环体中的内容
+ $parseStr .= $content;
+ $parseStr .= '<?php endforeach; endif; else: echo "' . $empty . '" ;endif; ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * if标签解析
+ * 格式:
+ * {if condition=" $a eq 1"}
+ * {elseif condition="$a eq 2" /}
+ * {else /}
+ * {/if}
+ * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || &&
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagIf(array $tag, string $content): string
+ {
+ $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition'];
+ $condition = $this->parseCondition($condition);
+ $parseStr = '<?php if(' . $condition . '): ?>' . $content . '<?php endif; ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * elseif标签解析
+ * 格式:见if标签
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagElseif(array $tag, string $content): string
+ {
+ $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition'];
+ $condition = $this->parseCondition($condition);
+ $parseStr = '<?php elseif(' . $condition . '): ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * else标签解析
+ * 格式:见if标签
+ * @access public
+ * @param array $tag 标签属性
+ * @return string
+ */
+ public function tagElse(array $tag): string
+ {
+ $parseStr = '<?php else: ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * switch标签解析
+ * 格式:
+ * {switch name="a.name"}
+ * {case value="1" break="false"}1{/case}
+ * {case value="2" }2{/case}
+ * {default /}other
+ * {/switch}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagSwitch(array $tag, string $content): string
+ {
+ $name = !empty($tag['expression']) ? $tag['expression'] : $tag['name'];
+ $name = $this->autoBuildVar($name);
+ $parseStr = '<?php switch(' . $name . '): ?>' . $content . '<?php endswitch; ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * case标签解析 需要配合switch才有效
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagCase(array $tag, string $content): string
+ {
+ $value = isset($tag['expression']) ? $tag['expression'] : $tag['value'];
+ $flag = substr($value, 0, 1);
+
+ if ('$' == $flag || ':' == $flag) {
+ $value = $this->autoBuildVar($value);
+ $value = 'case ' . $value . ':';
+ } elseif (strpos($value, '|')) {
+ $values = explode('|', $value);
+ $value = '';
+ foreach ($values as $val) {
+ $value .= 'case "' . addslashes($val) . '":';
+ }
+ } else {
+ $value = 'case "' . $value . '":';
+ }
+
+ $parseStr = '<?php ' . $value . ' ?>' . $content;
+ $isBreak = isset($tag['break']) ? $tag['break'] : '';
+
+ if ('' == $isBreak || $isBreak) {
+ $parseStr .= '<?php break; ?>';
+ }
+
+ return $parseStr;
+ }
+
+ /**
+ * default标签解析 需要配合switch才有效
+ * 使用: {default /}ddfdf
+ * @access public
+ * @param array $tag 标签属性
+ * @return string
+ */
+ public function tagDefault(array $tag): string
+ {
+ $parseStr = '<?php default: ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * compare标签解析
+ * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq
+ * 格式: {compare name="" type="eq" value="" }content{/compare}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagCompare(array $tag, string $content): string
+ {
+ $name = $tag['name'];
+ $value = $tag['value'];
+ $type = isset($tag['type']) ? $tag['type'] : 'eq'; // 比较类型
+ $name = $this->autoBuildVar($name);
+ $flag = substr($value, 0, 1);
+
+ if ('$' == $flag || ':' == $flag) {
+ $value = $this->autoBuildVar($value);
+ } else {
+ $value = '\'' . $value . '\'';
+ }
+
+ switch ($type) {
+ case 'equal':
+ $type = 'eq';
+ break;
+ case 'notequal':
+ $type = 'neq';
+ break;
+ }
+ $type = $this->parseCondition(' ' . $type . ' ');
+ $parseStr = '<?php if(' . $name . ' ' . $type . ' ' . $value . '): ?>' . $content . '<?php endif; ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * range标签解析
+ * 如果某个变量存在于某个范围 则输出内容 type= in 表示在范围内 否则表示在范围外
+ * 格式: {range name="var|function" value="val" type='in|notin' }content{/range}
+ * example: {range name="a" value="1,2,3" type='in' }content{/range}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagRange(array $tag, string $content): string
+ {
+ $name = $tag['name'];
+ $value = $tag['value'];
+ $type = isset($tag['type']) ? $tag['type'] : 'in'; // 比较类型
+
+ $name = $this->autoBuildVar($name);
+ $flag = substr($value, 0, 1);
+
+ if ('$' == $flag || ':' == $flag) {
+ $value = $this->autoBuildVar($value);
+ $str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')';
+ } else {
+ $value = '"' . $value . '"';
+ $str = 'explode(\',\',' . $value . ')';
+ }
+
+ if ('between' == $type) {
+ $parseStr = '<?php $_RANGE_VAR_=' . $str . ';if(' . $name . '>= $_RANGE_VAR_[0] && ' . $name . '<= $_RANGE_VAR_[1]):?>' . $content . '<?php endif; ?>';
+ } elseif ('notbetween' == $type) {
+ $parseStr = '<?php $_RANGE_VAR_=' . $str . ';if(' . $name . '<$_RANGE_VAR_[0] || ' . $name . '>$_RANGE_VAR_[1]):?>' . $content . '<?php endif; ?>';
+ } else {
+ $fun = ('in' == $type) ? 'in_array' : '!in_array';
+ $parseStr = '<?php if(' . $fun . '((' . $name . '), ' . $str . ')): ?>' . $content . '<?php endif; ?>';
+ }
+
+ return $parseStr;
+ }
+
+ /**
+ * present标签解析
+ * 如果某个变量已经设置 则输出内容
+ * 格式: {present name="" }content{/present}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagPresent(array $tag, string $content): string
+ {
+ $name = $tag['name'];
+ $name = $this->autoBuildVar($name);
+ $parseStr = '<?php if(isset(' . $name . ')): ?>' . $content . '<?php endif; ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * notpresent标签解析
+ * 如果某个变量没有设置,则输出内容
+ * 格式: {notpresent name="" }content{/notpresent}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagNotpresent(array $tag, string $content): string
+ {
+ $name = $tag['name'];
+ $name = $this->autoBuildVar($name);
+ $parseStr = '<?php if(!isset(' . $name . ')): ?>' . $content . '<?php endif; ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * empty标签解析
+ * 如果某个变量为empty 则输出内容
+ * 格式: {empty name="" }content{/empty}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagEmpty(array $tag, string $content): string
+ {
+ $name = $tag['name'];
+ $name = $this->autoBuildVar($name);
+ $parseStr = '<?php if(empty(' . $name . ') || ((' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator ) && ' . $name . '->isEmpty())): ?>' . $content . '<?php endif; ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * notempty标签解析
+ * 如果某个变量不为empty 则输出内容
+ * 格式: {notempty name="" }content{/notempty}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagNotempty(array $tag, string $content): string
+ {
+ $name = $tag['name'];
+ $name = $this->autoBuildVar($name);
+ $parseStr = '<?php if(!(empty(' . $name . ') || ((' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator ) && ' . $name . '->isEmpty()))): ?>' . $content . '<?php endif; ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * 判断是否已经定义了该常量
+ * {defined name='TXT'}已定义{/defined}
+ * @access public
+ * @param array $tag
+ * @param string $content
+ * @return string
+ */
+ public function tagDefined(array $tag, string $content): string
+ {
+ $name = $tag['name'];
+ $parseStr = '<?php if(defined("' . $name . '")): ?>' . $content . '<?php endif; ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * 判断是否没有定义了该常量
+ * {notdefined name='TXT'}已定义{/notdefined}
+ * @access public
+ * @param array $tag
+ * @param string $content
+ * @return string
+ */
+ public function tagNotdefined(array $tag, string $content): string
+ {
+ $name = $tag['name'];
+ $parseStr = '<?php if(!defined("' . $name . '")): ?>' . $content . '<?php endif; ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * load 标签解析 {load file="/static/js/base.js" /}
+ * 格式:{load file="/static/css/base.css" /}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagLoad(array $tag, string $content): string
+ {
+ $file = isset($tag['file']) ? $tag['file'] : $tag['href'];
+ $type = isset($tag['type']) ? strtolower($tag['type']) : '';
+
+ $parseStr = '';
+ $endStr = '';
+
+ // 判断是否存在加载条件 允许使用函数判断(默认为isset)
+ if (isset($tag['value'])) {
+ $name = $tag['value'];
+ $name = $this->autoBuildVar($name);
+ $name = 'isset(' . $name . ')';
+ $parseStr .= '<?php if(' . $name . '): ?>';
+ $endStr = '<?php endif; ?>';
+ }
+
+ // 文件方式导入
+ $array = explode(',', $file);
+
+ foreach ($array as $val) {
+ $type = strtolower(substr(strrchr($val, '.'), 1));
+ switch ($type) {
+ case 'js':
+ $parseStr .= '<script type="text/javascript" src="' . $val . '"></script>';
+ break;
+ case 'css':
+ $parseStr .= '<link rel="stylesheet" type="text/css" href="' . $val . '" />';
+ break;
+ case 'php':
+ $parseStr .= '<?php include "' . $val . '"; ?>';
+ break;
+ }
+ }
+
+ return $parseStr . $endStr;
+ }
+
+ /**
+ * assign标签解析
+ * 在模板中给某个变量赋值 支持变量赋值
+ * 格式: {assign name="" value="" /}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagAssign(array $tag, string $content): string
+ {
+ $name = $this->autoBuildVar($tag['name']);
+ $flag = substr($tag['value'], 0, 1);
+
+ if ('$' == $flag || ':' == $flag) {
+ $value = $this->autoBuildVar($tag['value']);
+ } else {
+ $value = '\'' . $tag['value'] . '\'';
+ }
+
+ $parseStr = '<?php ' . $name . ' = ' . $value . '; ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * define标签解析
+ * 在模板中定义常量 支持变量赋值
+ * 格式: {define name="" value="" /}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagDefine(array $tag, string $content): string
+ {
+ $name = '\'' . $tag['name'] . '\'';
+ $flag = substr($tag['value'], 0, 1);
+
+ if ('$' == $flag || ':' == $flag) {
+ $value = $this->autoBuildVar($tag['value']);
+ } else {
+ $value = '\'' . $tag['value'] . '\'';
+ }
+
+ $parseStr = '<?php define(' . $name . ', ' . $value . '); ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * for标签解析
+ * 格式:
+ * {for start="" end="" comparison="" step="" name=""}
+ * content
+ * {/for}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagFor(array $tag, string $content): string
+ {
+ //设置默认值
+ $start = 0;
+ $end = 0;
+ $step = 1;
+ $comparison = 'lt';
+ $name = 'i';
+ $rand = rand(); //添加随机数,防止嵌套变量冲突
+
+ //获取属性
+ foreach ($tag as $key => $value) {
+ $value = trim($value);
+ $flag = substr($value, 0, 1);
+ if ('$' == $flag || ':' == $flag) {
+ $value = $this->autoBuildVar($value);
+ }
+
+ switch ($key) {
+ case 'start':
+ $start = $value;
+ break;
+ case 'end':
+ $end = $value;
+ break;
+ case 'step':
+ $step = $value;
+ break;
+ case 'comparison':
+ $comparison = $value;
+ break;
+ case 'name':
+ $name = $value;
+ break;
+ }
+ }
+
+ $parseStr = '<?php $__FOR_START_' . $rand . '__=' . $start . ';$__FOR_END_' . $rand . '__=' . $end . ';';
+ $parseStr .= 'for($' . $name . '=$__FOR_START_' . $rand . '__;' . $this->parseCondition('$' . $name . ' ' . $comparison . ' $__FOR_END_' . $rand . '__') . ';$' . $name . '+=' . $step . '){ ?>';
+ $parseStr .= $content;
+ $parseStr .= '<?php } ?>';
+
+ return $parseStr;
+ }
+
+ /**
+ * url函数的tag标签
+ * 格式:{url link="模块/控制器/方法" vars="参数" suffix="true或者false 是否带有后缀" domain="true或者false 是否携带域名" /}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagUrl(array $tag, string $content): string
+ {
+ $url = isset($tag['link']) ? $tag['link'] : '';
+ $vars = isset($tag['vars']) ? $tag['vars'] : '';
+ $suffix = isset($tag['suffix']) ? $tag['suffix'] : 'true';
+ $domain = isset($tag['domain']) ? $tag['domain'] : 'false';
+
+ return '<?php echo url("' . $url . '","' . $vars . '",' . $suffix . ',' . $domain . ');?>';
+ }
+
+ /**
+ * function标签解析 匿名函数,可实现递归
+ * 使用:
+ * {function name="func" vars="$data" call="$list" use="&$a,&$b"}
+ * {if is_array($data)}
+ * {foreach $data as $val}
+ * {~func($val) /}
+ * {/foreach}
+ * {else /}
+ * {$data}
+ * {/if}
+ * {/function}
+ * @access public
+ * @param array $tag 标签属性
+ * @param string $content 标签内容
+ * @return string
+ */
+ public function tagFunction(array $tag, string $content): string
+ {
+ $name = !empty($tag['name']) ? $tag['name'] : 'func';
+ $vars = !empty($tag['vars']) ? $tag['vars'] : '';
+ $call = !empty($tag['call']) ? $tag['call'] : '';
+ $use = ['&$' . $name];
+
+ if (!empty($tag['use'])) {
+ foreach (explode(',', $tag['use']) as $val) {
+ $use[] = '&' . ltrim(trim($val), '&');
+ }
+ }
+
+ $parseStr = '<?php $' . $name . '=function(' . $vars . ') use(' . implode(',', $use) . ') {';
+ $parseStr .= ' ?>' . $content . '<?php }; ';
+ $parseStr .= $call ? '$' . $name . '(' . $call . '); ?>' : '?>';
+
+ return $parseStr;
+ }
+}
diff --git a/vendor/topthink/think-view/.gitignore b/vendor/topthink/think-view/.gitignore
new file mode 100755
index 0000000..485dee6
--- /dev/null
+++ b/vendor/topthink/think-view/.gitignore
@@ -0,0 +1 @@
+.idea
diff --git a/vendor/topthink/think-view/LICENSE b/vendor/topthink/think-view/LICENSE
new file mode 100755
index 0000000..8dada3e
--- /dev/null
+++ b/vendor/topthink/think-view/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/topthink/think-view/README.md b/vendor/topthink/think-view/README.md
new file mode 100755
index 0000000..4e52def
--- /dev/null
+++ b/vendor/topthink/think-view/README.md
@@ -0,0 +1,36 @@
+# think-view
+
+ThinkPHP6.0 Think-Template模板引擎驱动
+
+
+## 安装
+
+~~~php
+composer require topthink/think-view
+~~~
+
+## 用法示例
+
+本扩展不能单独使用,依赖ThinkPHP6.0+
+
+首先配置config目录下的template.php配置文件,然后可以按照下面的用法使用。
+
+~~~php
+
+use think\facade\View;
+
+// 模板变量赋值和渲染输出
+View::assign(['name' => 'think'])
+ // 输出过滤
+ ->filter(function($content){
+ return str_replace('search', 'replace', $content);
+ })
+ // 读取模板文件渲染输出
+ ->fetch('index');
+
+
+// 或者使用助手函数
+view('index', ['name' => 'think']);
+~~~
+
+具体的模板引擎配置请参考think-template库。
\ No newline at end of file
diff --git a/vendor/topthink/think-view/composer.json b/vendor/topthink/think-view/composer.json
new file mode 100755
index 0000000..f4e6431
--- /dev/null
+++ b/vendor/topthink/think-view/composer.json
@@ -0,0 +1,20 @@
+{
+ "name": "topthink/think-view",
+ "description": "thinkphp template driver",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "liu21st",
+ "email": "liu21st@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=7.1.0",
+ "topthink/think-template": "^2.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "think\\view\\driver\\": "src"
+ }
+ }
+}
diff --git a/vendor/topthink/think-view/src/Think.php b/vendor/topthink/think-view/src/Think.php
new file mode 100755
index 0000000..02be10f
--- /dev/null
+++ b/vendor/topthink/think-view/src/Think.php
@@ -0,0 +1,263 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\view\driver;
+
+use think\App;
+use think\helper\Str;
+use think\Template;
+use think\template\exception\TemplateNotFoundException;
+
+class Think
+{
+ // 模板引擎实例
+ private $template;
+ private $app;
+
+ // 模板引擎参数
+ protected $config = [
+ // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法
+ 'auto_rule' => 1,
+ // 视图目录名
+ 'view_dir_name' => 'view',
+ // 模板起始路径
+ 'view_path' => '',
+ // 模板文件后缀
+ 'view_suffix' => 'html',
+ // 模板文件名分隔符
+ 'view_depr' => DIRECTORY_SEPARATOR,
+ // 是否开启模板编译缓存,设为false则每次都会重新编译
+ 'tpl_cache' => true,
+ ];
+
+ public function __construct(App $app, array $config = [])
+ {
+ $this->app = $app;
+
+ $this->config = array_merge($this->config, (array) $config);
+
+ if (empty($this->config['cache_path'])) {
+ $this->config['cache_path'] = $app->getRuntimePath() . 'temp' . DIRECTORY_SEPARATOR;
+ }
+
+ $this->template = new Template($this->config);
+ $this->template->setCache($app->cache);
+ $this->template->extend('$Think', function (array $vars) {
+ $type = strtoupper(trim(array_shift($vars)));
+ $param = implode('.', $vars);
+
+ switch ($type) {
+ case 'CONST':
+ $parseStr = strtoupper($param);
+ break;
+ case 'CONFIG':
+ $parseStr = 'config(\'' . $param . '\')';
+ break;
+ case 'LANG':
+ $parseStr = 'lang(\'' . $param . '\')';
+ break;
+ case 'NOW':
+ $parseStr = "date('Y-m-d g:i a',time())";
+ break;
+ case 'LDELIM':
+ $parseStr = '\'' . ltrim($this->getConfig('tpl_begin'), '\\') . '\'';
+ break;
+ case 'RDELIM':
+ $parseStr = '\'' . ltrim($this->getConfig('tpl_end'), '\\') . '\'';
+ break;
+ default:
+ $parseStr = defined($type) ? $type : '\'\'';
+ }
+
+ return $parseStr;
+ });
+
+ $this->template->extend('$Request', function (array $vars) {
+ // 获取Request请求对象参数
+ $method = array_shift($vars);
+ if (!empty($vars)) {
+ $params = implode('.', $vars);
+ if ('true' != $params) {
+ $params = '\'' . $params . '\'';
+ }
+ } else {
+ $params = '';
+ }
+
+ return 'app(\'request\')->' . $method . '(' . $params . ')';
+ });
+ }
+
+ /**
+ * 检测是否存在模板文件
+ * @access public
+ * @param string $template 模板文件或者模板规则
+ * @return bool
+ */
+ public function exists(string $template): bool
+ {
+ if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
+ // 获取模板文件名
+ $template = $this->parseTemplate($template);
+ }
+
+ return is_file($template);
+ }
+
+ /**
+ * 渲染模板文件
+ * @access public
+ * @param string $template 模板文件
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function fetch(string $template, array $data = []): void
+ {
+ if (empty($this->config['view_path'])) {
+ $view = $this->config['view_dir_name'];
+
+ if (is_dir($this->app->getAppPath() . $view)) {
+ $path = $this->app->getAppPath() . $view . DIRECTORY_SEPARATOR;
+ } else {
+ $appName = $this->app->http->getName();
+ $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . ($appName ? $appName . DIRECTORY_SEPARATOR : '');
+ }
+
+ $this->config['view_path'] = $path;
+ $this->template->view_path = $path;
+ }
+
+ if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
+ // 获取模板文件名
+ $template = $this->parseTemplate($template);
+ }
+
+ // 模板不存在 抛出异常
+ if (!is_file($template)) {
+ throw new TemplateNotFoundException('template not exists:' . $template, $template);
+ }
+
+ // 记录视图信息
+ $this->app['log']
+ ->record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]');
+
+ $this->template->fetch($template, $data);
+ }
+
+ /**
+ * 渲染模板内容
+ * @access public
+ * @param string $template 模板内容
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function display(string $template, array $data = []): void
+ {
+ $this->template->display($template, $data);
+ }
+
+ /**
+ * 自动定位模板文件
+ * @access private
+ * @param string $template 模板文件规则
+ * @return string
+ */
+ private function parseTemplate(string $template): string
+ {
+ // 分析模板文件规则
+ $request = $this->app['request'];
+
+ // 获取视图根目录
+ if (strpos($template, '@')) {
+ // 跨模块调用
+ list($app, $template) = explode('@', $template);
+ }
+
+ if (isset($app)) {
+ $view = $this->config['view_dir_name'];
+ $viewPath = $this->app->getBasePath() . $app . DIRECTORY_SEPARATOR . $view . DIRECTORY_SEPARATOR;
+
+ if (is_dir($viewPath)) {
+ $path = $viewPath;
+ } else {
+ $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . $app . DIRECTORY_SEPARATOR;
+ }
+
+ $this->template->view_path = $path;
+ } else {
+ $path = $this->config['view_path'];
+ }
+
+ $depr = $this->config['view_depr'];
+
+ if (0 !== strpos($template, '/')) {
+ $template = str_replace(['/', ':'], $depr, $template);
+ $controller = $request->controller();
+
+ if (strpos($controller, '.')) {
+ $pos = strrpos($controller, '.');
+ $controller = substr($controller, 0, $pos) . '.' . Str::snake(substr($controller, $pos + 1));
+ } else {
+ $controller = Str::snake($controller);
+ }
+
+ if ($controller) {
+ if ('' == $template) {
+ // 如果模板文件名为空 按照默认模板渲染规则定位
+ if (2 == $this->config['auto_rule']) {
+ $template = $request->action(true);
+ } elseif (3 == $this->config['auto_rule']) {
+ $template = $request->action();
+ } else {
+ $template = Str::snake($request->action());
+ }
+
+ $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template;
+ } elseif (false === strpos($template, $depr)) {
+ $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template;
+ }
+ }
+ } else {
+ $template = str_replace(['/', ':'], $depr, substr($template, 1));
+ }
+
+ return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.');
+ }
+
+ /**
+ * 配置模板引擎
+ * @access private
+ * @param array $config 参数
+ * @return void
+ */
+ public function config(array $config): void
+ {
+ $this->template->config($config);
+ $this->config = array_merge($this->config, $config);
+ }
+
+ /**
+ * 获取模板引擎配置
+ * @access public
+ * @param string $name 参数名
+ * @return void
+ */
+ public function getConfig(string $name)
+ {
+ return $this->template->getConfig($name);
+ }
+
+ public function __call($method, $params)
+ {
+ return call_user_func_array([$this->template, $method], $params);
+ }
+}
diff --git a/vendor/volcengine/volc-sdk-php/.gitignore b/vendor/volcengine/volc-sdk-php/.gitignore
new file mode 100755
index 0000000..c88f952
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/.gitignore
@@ -0,0 +1,9 @@
+vendor
+composer.lock
+.idea/**
+phpunit*
+*.phar*
+*.bat*
+*.DS_Store*
+.envrc
+composer-setup.php
diff --git a/vendor/volcengine/volc-sdk-php/Changelog b/vendor/volcengine/volc-sdk-php/Changelog
new file mode 100755
index 0000000..b3420ce
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/Changelog
@@ -0,0 +1,211 @@
+Change log
+
+2022-11-10 Bumped to version v1.0.54
+- Added live service
+- Updated apis for security
+
+2022-10-27 Bumped to version v1.0.53
+- Updated apis for vod
+
+2022-10-13 Bumped to version v1.0.52
+- Updated apis for vod
+
+2022-09-15 Bumped to version v1.0.51
+- Updated apis for vod
+
+2022-09-09 Bumped to version v1.0.50
+- Added apis for billing
+
+2022-09-06 Bumped to version v1.0.49
+- Updated apis for security
+
+2022-08-25 Bumped to version v1.0.48
+- Added apis for live
+- Fixed apis for imagex
+
+2022-08-11 Bumped to version v1.0.47
+- Updated apis for vod
+
+2022-08-04 Bumped to version v1.0.46
+- Added apis for sts
+- Updated demo for rtc
+
+2022-07-28 Bumped to version v1.0.45
+- Upgraded apis for vod\rtc
+
+2022-07-25 Bumped to version v1.0.44
+- Added apis for security
+
+2022-07-07 Bumped to version v1.0.43
+- Added apis for live\rtc
+
+2022-06-23 Bumped to version v1.0.42
+- Added apis for vod
+
+2022-06-17 Bumped to version v1.0.41
+- Updated apis for vod
+- Updated demo for rtc
+
+2022-06-09 Bumped to version v1.0.40
+- Added service secertNumber
+- Fixed apis for cdn
+
+2022-05-26 Bumped to version v1.0.39
+- Added rtc service
+- Added apis for ocr
+
+2022-05-20 Bumped to version v1.0.38
+- Added notify service
+
+2022-05-12 Bumped to version v1.0.37
+- Added apis for vod\live\FaceSwap
+
+2022-04-14 Bumped to version v1.0.36
+- Added apis for vod\cdn\imagex\security
+
+2022-04-07 Bumped to version v1.0.35
+- Added imp service
+
+2022-03-31 Bumped to version v1.0.34
+- Upgraded api for vod
+
+2022-03-18 Bumped to version v1.0.33
+- Add some apis of space\callback\cdn for vod
+
+2022-03-03 Bumped to version v1.0.32
+- BREAKING CHANGE: only accept json body as the in parameter.
+- Implement related example for apis
+- Add new apis
+- Add Video-Classification For VOD
+
+2022-02-15 Bumped to version v1.0.31
+- Fixed protobuf dependency
+
+2022-02-15 Bumped to version v1.0.30
+- Fixed sign only method
+- Fixed guzzle version to *
+- Added mobile_status api
+
+2022-02-10 Bumped to version v1.0.29
+- 号码验证2期需求
+
+2022-01-21 Bumped to version v1.0.28
+- 点播火山引擎转码处理新增 EnableLowPriority 字段
+
+2022-01-19 Bumped to version v1.0.27
+- Fixed signing url method
+- Removed multi catch for 7.0
+
+2022-01-14 Bumped to version v1.0.26
+- Downgrade PHP to 7.0
+
+2021-12-30 Bumped to version v1.0.25
+- Refactored signing method
+
+2021-12-28 Bumped to version v1.0.24
+- Added live php sdk
+
+2021-12-16 Bumped to version v1.0.23
+- element verify
+- DescribeCdnUpperIp and describeCdnDataDetail apis
+- Fix foreach warning
+
+
+2021-11-15 Bumped to version v1.0.22
+- Updated example and remove unused formal param
+- Increased default timeouts
+
+2021-10-27 Bumped to version v1.0.21
+- Fixed Vod large file
+- Fixed HDR support for vod api
+
+2021-09-29 Bumped to version v1.0.20
+- Minor refactor
+
+2021-09-28 Bumped to version v1.0.19
+- SubmitRefreshTask
+- SubmitPreloadTask
+- DescribeContentTasks
+- DescribeContentQuota
+- DescribeCdnData
+- DescribeCdnOriginData
+- DescribeCdnRegionAndIsp
+- DescribeCdnDomainTopData
+- DescribeCdnAccountingData
+- DescribeCdnAccessLog
+- StartCdnDomain
+- StopCdnDomain
+- DeleteCdnDomain
+- ListCdnDomains
+
+2021-09-16 Bumped to version v1.0.18
+- Added sms v2 sdk
+- Added private drm for vod
+
+2021-08-05 Bumped to version v1.0.17
+- Added sms sdk
+- Added ocr sdk
+
+2021-07-22 Bumped to version v1.0.16
+- Fixed cr32
+
+2021-07-15 Bumped to version v1.0.15
+- Upgraded signer method to latest
+
+2021-07-01 Bumped to version v1.0.14
+- Added subtitle sdk
+
+2021-06-02 Bumped to version v1.0.13
+- Added async risk result
+- Updated media list
+- Added product game protect
+- Added Vod support material ext
+
+2021-05-20 Bumped to version v1.0.12
+- Add hls drm utils for vod
+- Support material upload
+- Add retry after http error
+- Add imagex image fetch API
+
+2021-04-20 Bumped to version v1.0.11
+- Added pcdn and barrageMask support.
+- Added store key res.
+
+2021-03-24 Bumped to version v1.0.10
+- Added sms sdk.
+
+2021-03-24 Bumped to version v1.0.9
+- Added async and risk result.
+- Added delete media support.
+
+2021-03-02 Bumped to version v1.0.8
+- Visual: add new apis: FaceSwap, HumanSegment, GeneralSegment, EnhancePhoto, ConvertPhoto
+
+2021-02-22 Bumped to version v1.0.7
+- Visual:add JPCartoon api.
+
+2021-02-18 Bumped to version v1.0.6
+- Upgrade adblock sdk.
+- Vod: bug fix.
+
+2021-02-04 Bumped to version v1.0.5
+- VEdit: support more cloud-edit sdk.
+
+2021-01-28 Bumped to version v1.0.4
+- Fixed sts2_ak.
+- Fixed visual null pointer.
+- more vod sdk support.
+
+2021-01-19 Bumped to version v1.0.3
+- Add adblock sdk.
+- Vod:enable large-file slice upload function.
+
+2020-12-30 Bumped to version v1.0.2
+- ImageX:add delete api.
+
+2020-12-22 Bumped to version v1.0.1
+- Add vod sdk.
+- Add ImageX sdk.
+
+2020-12-11 Bumped to version v1.0.0
+- Add iam sdk.
diff --git a/vendor/volcengine/volc-sdk-php/README.md b/vendor/volcengine/volc-sdk-php/README.md
new file mode 100755
index 0000000..844c732
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/README.md
@@ -0,0 +1,49 @@
+<h1 align="center"><img src="https://iam.volccdn.com/obj/volcengine-public/pic/volcengine-icon.png"></h1>
+<h1 align="center">火山引擎SDK for PHP</h1>
+欢迎使用火山引擎SDK for PHP,本文档为您介绍如何获取及调用SDK。
+
+## 前置准备
+### 服务开通
+请确保您已开通了您需要访问的服务。您可前往[火山引擎控制台](https://console.volcengine.com/ ),在左侧菜单中选择或在顶部搜索栏中搜索您需要使用的服务,进入服务控制台内完成开通流程。
+### 获取安全凭证
+Access Key(访问密钥)是访问火山引擎服务的安全凭证,包含Access Key ID(简称为AK)和Secret Access Key(简称为SK)两部分。您可登录[火山引擎控制台](https://console.volcengine.com/ ),前往“[访问控制](https://console.volcengine.com/iam )”的“[访问密钥](https://console.volcengine.com/iam/keymanage/ )”中创建及管理您的Access Key。更多信息可参考[访问密钥帮助文档](https://www.volcengine.com/docs/6291/65568 )。
+###环境检查
+PHP版本需要不低于7.1。
+## 获取与安装
+推荐使用composer安装火山引擎SDK for PHP:
+```shell
+$ composer require volcengine/volc-sdk-php
+```
+## 相关配置
+### 安全凭证配置
+火山引擎SDK for PHP支持以下几种方式进行凭证管理:
+
+*注意:代码中Your AK及Your SK需要分别替换为您的AK及SK。*
+
+**方式一**:在Client中显式指定AK/SK **(推荐)**
+```php
+$client = Iam::getInstance();
+$client->setAccessKey(Your AK);
+$client->setSecretKey(Your SK);
+```
+
+**方式二**:从环境变量加载AK/SK
+ ```bash
+ VOLC_ACCESSKEY="Your AK"
+ VOLC_SECRETKEY="Your SK"
+ ```
+**方式三**:从HOME文件加载AK/SK
+
+在本地的~/.volc/config中添加如下内容:
+ ```json
+ {
+ "ak": "Your AK",
+ "sk": "Your SK"
+ }
+ ```
+
+##其它资源
+示例参见[examples](./examples)
+
+
+
diff --git a/vendor/volcengine/volc-sdk-php/VERSION b/vendor/volcengine/volc-sdk-php/VERSION
new file mode 100755
index 0000000..77bf58c
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/VERSION
@@ -0,0 +1 @@
+v1.0.54
\ No newline at end of file
diff --git a/vendor/volcengine/volc-sdk-php/composer.json b/vendor/volcengine/volc-sdk-php/composer.json
new file mode 100755
index 0000000..a61aabd
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "volcengine/volc-sdk-php",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "bytedance"
+ }
+ ],
+ "require": {
+ "php": ">=7.0",
+ "guzzlehttp/guzzle": "*",
+ "ext-json": "*",
+ "google/protobuf": "*",
+ "google/gax": "*",
+ "ext-openssl": "*"
+ },
+ "autoload": {
+ "psr-4": {
+ "Volc\\": "src/",
+ "Test\\": "tests/"
+ }
+ },
+ "description": ""
+}
diff --git a/vendor/volcengine/volc-sdk-php/examples/.gitignore b/vendor/volcengine/volc-sdk-php/examples/.gitignore
new file mode 100755
index 0000000..06e7e9f
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/.gitignore
@@ -0,0 +1 @@
+LocalTest.php
diff --git a/vendor/volcengine/volc-sdk-php/examples/AdBlocker/DemoAdBlock.php b/vendor/volcengine/volc-sdk-php/examples/AdBlocker/DemoAdBlock.php
new file mode 100755
index 0000000..c4f7e57
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/AdBlocker/DemoAdBlock.php
@@ -0,0 +1,12 @@
+<?php
+require('../../vendor/autoload.php');
+
+use Volc\Service\AdBlocker;
+$client = AdBlocker::getInstance();
+
+// call below method if you dont set ak and sk in ~/.volc/config
+$client->setAccessKey("ak");
+$client->setSecretKey("sk");
+
+$response = $client->adBlock(3332, "chat", "{\"uid\":123411, \"operate_time\":1609818934, \"chat_text\":\"a😊\"}");
+echo $response;
diff --git a/vendor/volcengine/volc-sdk-php/examples/Billing/DemoListBill.php b/vendor/volcengine/volc-sdk-php/examples/Billing/DemoListBill.php
new file mode 100755
index 0000000..10d65ba
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Billing/DemoListBill.php
@@ -0,0 +1,24 @@
+<?php
+require('../../vendor/autoload.php');
+
+use Volc\Service\Billing;
+
+$client = Billing::getInstance();
+
+// call below method if you dont set ak and sk in ~/.volc/config
+$ak = "<Your AK>";
+$sk = "<Your SK>";
+$client->setAccessKey($ak);
+$client->setSecretKey($sk);
+
+$response = $client->listBill(['query' => [
+ 'BillPeriod' => '2022-01',
+ 'Limit' => 10,
+ 'Offset' => 0,
+ 'Product' => '',
+ 'BillingMode' => '',
+ 'BillCategoryParent' => '',
+ 'PayStatus' => '',
+ 'IgnoreZero' => '0',
+ 'NeedRecordNum' => '0']]);
+echo $response;
\ No newline at end of file
diff --git a/vendor/volcengine/volc-sdk-php/examples/Billing/DemoListBillDetail.php b/vendor/volcengine/volc-sdk-php/examples/Billing/DemoListBillDetail.php
new file mode 100755
index 0000000..fc658a5
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Billing/DemoListBillDetail.php
@@ -0,0 +1,26 @@
+<?php
+require('../../vendor/autoload.php');
+
+use Volc\Service\Billing;
+
+$client = Billing::getInstance();
+
+// call below method if you dont set ak and sk in ~/.volc/config
+$ak = "<Your AK>";
+$sk = "<Your SK>";
+$client->setAccessKey($ak);
+$client->setSecretKey($sk);
+
+$response = $client->listBillDetail(['query' => [
+ 'BillPeriod' => '2022-01',
+ 'Limit' => 10,
+ 'Offset' => 0,
+ 'GroupTerm' => 0,
+ 'GroupPeriod' => 2,
+ 'Product' => '',
+ 'BillingMode' => '',
+ 'BillCategory' => '',
+ 'InstanceNo' => '',
+ 'IgnoreZero' => '0',
+ 'NeedRecordNum' => '0']]);
+echo $response;
\ No newline at end of file
diff --git a/vendor/volcengine/volc-sdk-php/examples/Billing/DemoListBillOverviewByProd.php b/vendor/volcengine/volc-sdk-php/examples/Billing/DemoListBillOverviewByProd.php
new file mode 100755
index 0000000..42bdd63
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Billing/DemoListBillOverviewByProd.php
@@ -0,0 +1,23 @@
+<?php
+require('../../vendor/autoload.php');
+
+use Volc\Service\Billing;
+
+$client = Billing::getInstance();
+
+// call below method if you dont set ak and sk in ~/.volc/config
+$ak = "<Your AK>";
+$sk = "<Your SK>";
+$client->setAccessKey($ak);
+$client->setSecretKey($sk);
+
+$response = $client->listBillOverviewByProd(['query' => [
+ 'BillPeriod' => '2022-01',
+ 'Limit' => 10,
+ 'Offset' => 0,
+ 'Product' => '',
+ 'BillingMode' => '',
+ 'BillCategoryParent' => '',
+ 'IgnoreZero' => '0',
+ 'NeedRecordNum' => '0']]);
+echo $response;
\ No newline at end of file
diff --git a/vendor/volcengine/volc-sdk-php/examples/BusinessSecurity/DemoRiskDetect.php b/vendor/volcengine/volc-sdk-php/examples/BusinessSecurity/DemoRiskDetect.php
new file mode 100755
index 0000000..760c260
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/BusinessSecurity/DemoRiskDetect.php
@@ -0,0 +1,36 @@
+<?php
+require('../../vendor/autoload.php');
+
+use Volc\Service\BusinessSecurity;
+$client = BusinessSecurity::getInstance();
+
+// call below method if you dont set ak and sk in ~/.volc/config
+$client->setAccessKey("ak");
+$client->setSecretKey("sk");
+
+$response = $client->RiskDetect(1, "chat", "{\"uid\":123411, \"operate_time\":1609818934, \"chat_text\":\"a😊\"}");
+echo $response;
+
+$response = $client->ElementVerify(5461, "idcard_two_element_verify", "{\"operate_time\":1609818934, \"idcard_no\": \"\", \"idcard_name\":\"\"}");
+echo $response;
+
+$response = $client->MobileStatus(5461, "mobile_empty_status", "{\"operate_time\":1609818934, \"mobile\": \"\"}");
+echo $response;
+
+$response = $client->ImageContentRisk(5461, "image_content_risk", "{\"operate_time\": 1617960951, \"ip\": \"127.0.0.9\", \"did\":1357924680, \"data_id\": \"wangyifan\", \"account_id\": \"212910\", \"type\":1,\"url\":\"待检测图片\", \"label\": \"301,302,305,351,303,310\"}");
+echo $response;
+
+$response = $client->AsyncImageRisk(5461, "image_content_risk", "{\"operate_time\": 1617960951, \"ip\": \"127.0.0.9\", \"did\":1357924680, \"data_id\": \"w\", \"account_id\": \"212910\", \"type\":1,\"url\":\"待检测图片\", \"label\": \"301,302,305,351,303,310\"}");
+echo $response;
+
+$response = $client->GetImageResult(5461, "image_content_risk", "w");
+echo $response;
+
+$response = $client->TextRisk(5461, "text_risk", "{\"operate_time\": 1652154752, \"text\": \"待检测文本\", \"did\":1357924680, \"account_id\": \"123\"}");
+echo $response;
+
+$response = $client->GetAudioResult(334361, "audio_risk", "70");
+echo $response;
+
+$response = $client->TextSliceRisk(415493, "text_risk", "{}");
+echo $response;
\ No newline at end of file
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoAddCdnDomain.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoAddCdnDomain.php
new file mode 100755
index 0000000..d2ed7b4
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoAddCdnDomain.php
@@ -0,0 +1,37 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'Domain' => $config->operateDomain,
+ 'ServiceType' => 'web',
+ 'Origin' => [
+ [
+ 'OriginAction' => [
+ 'OriginLines' => [
+ [
+ 'OriginType' => 'primary',
+ 'InstanceType' => 'ip',
+ 'Address' => '1.1.1.1',
+ 'HttpPort' => '80',
+ 'HttpsPort' => '443',
+ 'Weight' => '100'
+ ]
+ ]
+ ]
+ ]
+ ],
+ 'OriginProtocol' => 'HTTP'
+];
+
+$response = $client->addCdnDomain($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoAddResourceTags.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoAddResourceTags.php
new file mode 100755
index 0000000..ca52a68
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoAddResourceTags.php
@@ -0,0 +1,22 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'Resources' => [$config->exampleDomain],
+ 'ResourceTags' => [
+ ['Key' => 'userKey', 'Value' => 'userValue']
+ ]
+];
+
+$response = $client->addResourceTags($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDeleteCdnDomain.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDeleteCdnDomain.php
new file mode 100755
index 0000000..1bb3609
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDeleteCdnDomain.php
@@ -0,0 +1,19 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'Domain' => $config->operateDomain
+];
+
+$response = $client->deleteCdnDomain($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDeleteResourceTags.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDeleteResourceTags.php
new file mode 100755
index 0000000..e59f2d6
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDeleteResourceTags.php
@@ -0,0 +1,22 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'Resources' => [$config->exampleDomain],
+ 'ResourceTags' => [
+ ['Key' => 'userKey', 'Value' => 'userValue']
+ ]
+];
+
+$response = $client->deleteResourceTags($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeAccountingData.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeAccountingData.php
new file mode 100755
index 0000000..1fc94df
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeAccountingData.php
@@ -0,0 +1,22 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'StartTime' => $config->startTime,
+ 'EndTime' => $config->endTime,
+ 'Domain' => $config->exampleDomain,
+ "Metric" => "flux",
+];
+
+$response = $client->describeAccountingData($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnAccessLog.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnAccessLog.php
new file mode 100755
index 0000000..c1c863a
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnAccessLog.php
@@ -0,0 +1,23 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'StartTime' => $config->startTime,
+ 'EndTime' => $config->endTime,
+ 'Domain' => $config->exampleDomain,
+ 'PageSize' => 100,
+ 'PageNum' => 1
+];
+
+$response = $client->describeCdnAccessLog($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnConfig.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnConfig.php
new file mode 100755
index 0000000..3668ea8
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnConfig.php
@@ -0,0 +1,19 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'Domain' => $config->exampleDomain,
+];
+
+$response = $client->describeCdnConfig($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnData.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnData.php
new file mode 100755
index 0000000..414f864
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnData.php
@@ -0,0 +1,27 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'StartTime' => $config->startTime,
+ 'EndTime' => $config->endTime,
+ 'Metric' => 'pv',
+ 'Domain' => $config->exampleDomain,
+ 'Interval' => '5min',
+ 'Isp' => 'CT',
+ 'Region' => 'BJ',
+ 'Protocol' => 'http',
+ 'IpVersion' => 'ipv4'
+];
+
+$response = $client->describeCdnData($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnDataDetail.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnDataDetail.php
new file mode 100755
index 0000000..4f6cfa1
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnDataDetail.php
@@ -0,0 +1,27 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+
+$client = Cdn::getInstance();
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'StartTime' => $config->startTime,
+ 'EndTime' => $config->endTime,
+ 'Domain' => $config->exampleDomain,
+ 'Metric' => "bandwidth",
+ 'Interval' => '5min',
+ 'Protocol' => 'http',
+ 'IpVersion' => 'ipv4'
+];
+
+$response = $client->describeCdnDataDetail($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnDomainTopData.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnDomainTopData.php
new file mode 100755
index 0000000..56b52fa
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnDomainTopData.php
@@ -0,0 +1,23 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'StartTime' => $config->startTime,
+ 'EndTime' => $config->endTime,
+ 'Metric' => 'pv',
+ 'Domain' => $config->exampleDomain,
+ 'Item' => 'region'
+];
+
+$response = $client->describeCdnDomainTopData($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnOriginData.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnOriginData.php
new file mode 100755
index 0000000..c9a5b06
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnOriginData.php
@@ -0,0 +1,23 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'StartTime' => $config->startTime,
+ 'EndTime' => $config->endTime,
+ 'Metric' => 'pv',
+ 'Domain' => $config->exampleDomain,
+ 'Interval' => '5min',
+];
+
+$response = $client->describeCdnOriginData($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnRegionAndIsp.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnRegionAndIsp.php
new file mode 100755
index 0000000..69febb0
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnRegionAndIsp.php
@@ -0,0 +1,19 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+
+$body = [
+ 'Domain' => $config->exampleDomain,
+];
+$response = $client->describeCdnRegionAndIsp($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnService.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnService.php
new file mode 100755
index 0000000..cf6ba8c
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnService.php
@@ -0,0 +1,15 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$response = $client->describeCdnService();
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnUpperIp.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnUpperIp.php
new file mode 100755
index 0000000..cd9e0c0
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeCdnUpperIp.php
@@ -0,0 +1,19 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'Domain' => $config->exampleDomain,
+];
+
+$response = $client->describeCdnUpperIp(['json' => $body]);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeContentBlockTasks.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeContentBlockTasks.php
new file mode 100755
index 0000000..25bf862
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeContentBlockTasks.php
@@ -0,0 +1,21 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'TaskType' => 'block_url',
+ 'StartTime' => $config->startTime,
+ 'EndTime' => $config->endTime,
+];
+
+$response = $client->describeContentBlockTasks($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeContentQuota.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeContentQuota.php
new file mode 100755
index 0000000..b8f1eed
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeContentQuota.php
@@ -0,0 +1,15 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$response = $client->describeContentQuota();
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeContentTasks.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeContentTasks.php
new file mode 100755
index 0000000..c1e53ec
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeContentTasks.php
@@ -0,0 +1,20 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ "PageNum" => 1,
+ 'PageSize' => 10,
+ 'TaskType' => 'refresh_file'
+];
+$response = $client->describeContentTasks($body);
+var_dump($response);
diff --git a/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeEdgeNrtDataSummary.php b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeEdgeNrtDataSummary.php
new file mode 100755
index 0000000..6e490d6
--- /dev/null
+++ b/vendor/volcengine/volc-sdk-php/examples/Cdn/DemoDescribeEdgeNrtDataSummary.php
@@ -0,0 +1,29 @@
+<?php
+
+use Volc\Service\Cdn;
+
+require('../../vendor/autoload.php');
+require_once "init.php";
+
+$client = Cdn::getInstance();
+
+$config = new Config();
+$client->setAccessKey($config->ak);
+$client->setSecretKey($config->sk);
+
+$body = [
+ 'StartTime' =>